| // GEMM dispatch benchmark: measure DeviceMatrix GEMM throughput across sizes. | |
| // | |
| // Compares cublasLtMatmul with plan cache against a raw cublasGemmEx call | |
| // (no descriptor overhead) to verify no regression. | |
| // | |
| // Usage: | |
| // cmake --build build-bench-gpu --target bench_gemm | |
| // ./build-bench-gpu/bench_gemm | |
| // | |
| // Profiling: | |
| // nsys profile --trace=cuda ./build-bench-gpu/bench_gemm | |
| // SPDX-FileCopyrightText: The Eigen Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| #include <benchmark/benchmark.h> | |
| // EIGEN_USE_GPU is set by the CMake target (eigen_add_gpu_benchmark). | |
| #include <contrib/Eigen/GPU> | |
| using namespace Eigen; | |
| #ifndef SCALAR | |
| #define SCALAR double | |
| #endif | |
| using Scalar = SCALAR; | |
| using Mat = Matrix<Scalar, Dynamic, Dynamic>; | |
| static void cuda_warmup() { | |
| static bool done = false; | |
| if (!done) { | |
| void* p; | |
| cudaMalloc(&p, 256); | |
| cudaFree(p); | |
| // Force context creation and JIT. | |
| gpu::Context ctx; | |
| Mat A = Mat::Random(64, 64); | |
| Mat B = Mat::Random(64, 64); | |
| auto d_A = gpu::DeviceMatrix<Scalar>::fromHost(A, ctx.stream()); | |
| auto d_B = gpu::DeviceMatrix<Scalar>::fromHost(B, ctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_C; | |
| d_C.device(ctx) = d_A * d_B; | |
| if (cudaDeviceSynchronize() != cudaSuccess) abort(); | |
| done = true; | |
| } | |
| } | |
| // -------------------------------------------------------------------------- | |
| // DeviceMatrix GEMM (uses cublasLtMatmul with plan cache) | |
| // -------------------------------------------------------------------------- | |
| static void BM_DeviceMatrix_Gemm(benchmark::State& state) { | |
| cuda_warmup(); | |
| const Index n = state.range(0); | |
| gpu::Context ctx; | |
| Mat hostA = Mat::Random(n, n); | |
| Mat hostB = Mat::Random(n, n); | |
| auto d_A = gpu::DeviceMatrix<Scalar>::fromHost(hostA, ctx.stream()); | |
| auto d_B = gpu::DeviceMatrix<Scalar>::fromHost(hostB, ctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_C; | |
| // Warmup: run a few GEMMs to stabilize clocks and populate plan cache. | |
| for (int i = 0; i < 5; ++i) { | |
| d_C.device(ctx) = d_A * d_B; | |
| } | |
| if (cudaDeviceSynchronize() != cudaSuccess) abort(); | |
| for (auto _ : state) { | |
| d_C.device(ctx) = d_A * d_B; | |
| if (cudaDeviceSynchronize() != cudaSuccess) abort(); | |
| } | |
| double flops = 2.0 * n * n * n; | |
| state.counters["GFLOPS"] = | |
| benchmark::Counter(flops, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000); | |
| state.counters["n"] = n; | |
| } | |
| // -------------------------------------------------------------------------- | |
| // Raw cublasGemmEx (direct call, no descriptor overhead) | |
| // -------------------------------------------------------------------------- | |
| static void BM_Raw_CublasGemmEx(benchmark::State& state) { | |
| cuda_warmup(); | |
| const Index n = state.range(0); | |
| gpu::Context ctx; | |
| Mat hostA = Mat::Random(n, n); | |
| Mat hostB = Mat::Random(n, n); | |
| auto d_A = gpu::DeviceMatrix<Scalar>::fromHost(hostA, ctx.stream()); | |
| auto d_B = gpu::DeviceMatrix<Scalar>::fromHost(hostB, ctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_C(n, n); | |
| constexpr cudaDataType_t dtype = gpu::internal::cuda_data_type<Scalar>::value; | |
| constexpr cublasComputeType_t compute = gpu::internal::cuda_compute_type<Scalar>::value; | |
| Scalar alpha = Scalar(1); | |
| Scalar beta = Scalar(0); | |
| const int ni = static_cast<int>(n); | |
| constexpr cublasGemmAlgo_t algo = gpu::internal::cuda_gemm_algo(); | |
| // Warmup. | |
| for (int i = 0; i < 5; ++i) { | |
| cublasStatus_t s = cublasGemmEx(ctx.cublasHandle(), CUBLAS_OP_N, CUBLAS_OP_N, ni, ni, ni, &alpha, d_A.data(), dtype, | |
| ni, d_B.data(), dtype, ni, &beta, d_C.data(), dtype, ni, compute, algo); | |
| if (s != CUBLAS_STATUS_SUCCESS) { | |
| state.SkipWithError("cublasGemmEx failed"); | |
| return; | |
| } | |
| } | |
| if (cudaDeviceSynchronize() != cudaSuccess) abort(); | |
| for (auto _ : state) { | |
| cublasStatus_t s = cublasGemmEx(ctx.cublasHandle(), CUBLAS_OP_N, CUBLAS_OP_N, ni, ni, ni, &alpha, d_A.data(), dtype, | |
| ni, d_B.data(), dtype, ni, &beta, d_C.data(), dtype, ni, compute, algo); | |
| if (s != CUBLAS_STATUS_SUCCESS) { | |
| state.SkipWithError("cublasGemmEx failed"); | |
| return; | |
| } | |
| if (cudaDeviceSynchronize() != cudaSuccess) abort(); | |
| } | |
| double flops = 2.0 * n * n * n; | |
| state.counters["GFLOPS"] = | |
| benchmark::Counter(flops, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000); | |
| state.counters["n"] = n; | |
| } | |
| // -------------------------------------------------------------------------- | |
| // DeviceMatrix GEMM with transpose: C = A^T * B | |
| // -------------------------------------------------------------------------- | |
| static void BM_DeviceMatrix_Gemm_TransA(benchmark::State& state) { | |
| cuda_warmup(); | |
| const Index n = state.range(0); | |
| gpu::Context ctx; | |
| Mat hostA = Mat::Random(n, n); | |
| Mat hostB = Mat::Random(n, n); | |
| auto d_A = gpu::DeviceMatrix<Scalar>::fromHost(hostA, ctx.stream()); | |
| auto d_B = gpu::DeviceMatrix<Scalar>::fromHost(hostB, ctx.stream()); | |
| gpu::DeviceMatrix<Scalar> d_C; | |
| for (int i = 0; i < 5; ++i) { | |
| d_C.device(ctx) = d_A.transpose() * d_B; | |
| } | |
| if (cudaDeviceSynchronize() != cudaSuccess) abort(); | |
| for (auto _ : state) { | |
| d_C.device(ctx) = d_A.transpose() * d_B; | |
| if (cudaDeviceSynchronize() != cudaSuccess) abort(); | |
| } | |
| double flops = 2.0 * n * n * n; | |
| state.counters["GFLOPS"] = | |
| benchmark::Counter(flops, benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::kIs1000); | |
| state.counters["n"] = n; | |
| } | |
| // Square GEMM: range of sizes from small (where descriptor overhead matters) | |
| // to large (where compute dominates). | |
| BENCHMARK(BM_DeviceMatrix_Gemm) | |
| ->ArgsProduct({{16, 32, 64, 128, 256, 512, 1024, 2048, 4096}}) | |
| ->Unit(benchmark::kMicrosecond) | |
| ->UseRealTime() | |
| ->MinWarmUpTime(0.5); | |
| BENCHMARK(BM_Raw_CublasGemmEx) | |
| ->ArgsProduct({{16, 32, 64, 128, 256, 512, 1024, 2048, 4096}}) | |
| ->Unit(benchmark::kMicrosecond) | |
| ->UseRealTime() | |
| ->MinWarmUpTime(0.5); | |
| BENCHMARK(BM_DeviceMatrix_Gemm_TransA) | |
| ->ArgsProduct({{16, 32, 64, 128, 256, 512, 1024, 2048, 4096}}) | |
| ->Unit(benchmark::kMicrosecond) | |
| ->UseRealTime() | |
| ->MinWarmUpTime(0.5); |