CUDA-Samples cuBLAS 示例实践:矩阵乘法 GPU 性能的 3 个决策点
【免费下载链接】cuda-samplesSamples for CUDA Developers which demonstrates features in CUDA Toolkit项目地址: https://gitcode.com/GitHub_Trending/cu/cuda-samples
场景切入:批处理图像滤波,GEMM 吞吐差在哪
批处理图像管线里,每一轮滤波或卷积本质上是向 GPU 投喂一次 GEMM(通用矩阵乘法)。NVIDIA CUDA-Samples 在cpp/4_CUDA_Libraries/下提供 simpleCUBLAS、matrixMulCUBLAS、batchCUBLAS 三个示例,恰好覆盖调用序列、内存布局、批量并发三层,是 GPU 端矩阵运算优化的入口。本文按三个独立决策点拆解,每个结论都可回溯到源码文件。
示例地图:三个 cuBLAS 示例的入口
📌 三个示例同属cpp/4_CUDA_Libraries/,解决的问题互不重叠,先看表再选阅读顺序:
| 示例 | 回答的问题 | 仓库路径 | 关键 API |
|---|---|---|---|
| simpleCUBLAS | 一次 GEMM 的调用链路是否正确:275×275 单矩阵 + CPU 三重循环对照 | cpp/4_CUDA_Libraries/simpleCUBLAS/ | cublasSgemm、cublasSetVector |
| matrixMulCUBLAS | 行主序数据如何免转置喂给列主序的 cuBLAS,以及 GPU 侧计时方法 | cpp/4_CUDA_Libraries/matrixMulCUBLAS/ | cublasSgemm(B、A 逆序)、cudaEvent 计时 |
| batchCUBLAS | N 个小 GEMM 如何一次跑完:regular / streams / batched 三模式对照 | cpp/4_CUDA_Libraries/batchCUBLAS/ | cublasSgemmBatched、cublasSetStream |
cuBLAS 句柄与 GEMM 调用序列怎么定
cublasHandle_t是有状态对象,内部持有设备、流和数学模式。创建成本不低(涉及内核选择表初始化),所以边界条件很清晰:
- 长驻服务:进程启动时
cublasCreate一次,handle 生命周期与进程一致,不要按请求反复创建/销毁; - 短离线作业:程序入口创建、退出前
cublasDestroy,simpleCUBLAS 就是这个模式; - 需要 CPU 参照值做校验时:保留 host 端矩阵副本,用
cublasSetVector/cublasGetVector上传下载;数据已在 GPU 管线内部、无需与 CPU 比对时,这两步可并入管线,直接用cudaMemcpy或免拷贝。
调用序列是固定的五步,simpleCUBLAS 的关键行如下(摘自 simpleCUBLAS.cpp 第 96、149、175、191、238 行):
status = cublasCreate(&handle); status = cublasSetVector(n2, sizeof(h_A[0]), h_A, 1, d_A, 1); status = cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, N, N, N, &alpha, d_A, N, d_B, N, &beta, d_C, N); status = cublasGetVector(n2, sizeof(h_C[0]), d_C, 1, h_C, 1); status = cublasDestroy(handle);正确性判定也值得注意:simpleCUBLAS 用相对 L2 误差error_norm / ref_norm < 1e-6f判定通过(第 245 行),而不是逐元素相等——float GEMM 的累加顺序与 CPU 不同,逐位相等永远做不到。
cuBLAS 列优先存储下如何免转置
cuBLAS 按列主序解释指针。把一个行主序的 C++ 数组直接传进去,它在 cuBLAS 眼里就是该矩阵的转置——这个"隐式转置"是新手踩坑的高发点。matrixMulCUBLAS.cpp 的文件头注释(第 36-57 行)给出了完整推导,结论只有一条:行主序的 C = A·B,按 (B, A) 逆序调用即可,等价于列主序下的 Cᵀ = Bᵀ·Aᵀ,不需要任何显式转置核或额外内存流量。
// 行主序 C = A*B → 列主序 C^T = B^T*A^T,故参数顺序换为 d_B, d_A checkCudaErrors(cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, matrix_size.uiWB, matrix_size.uiHA, matrix_size.uiWA, &alpha, d_B, matrix_size.uiWB, d_A, matrix_size.uiWA, &beta, d_C, matrix_size.uiWB));注意三个维度和 leading dimension 的对应关系:行主序 A 是uiHA×uiWA,传进去后在 cuBLAS 眼里是uiWA×uiHA的 Aᵀ,所以lda = uiWA;结果矩阵 C 的ldc = uiWB同理。边界条件:
- 数据由 GPU 侧直接按列主序生成(比如上游算子输出就是列主序):按自然顺序 (A, B) 调用,不要换序;
- 行主序 C++ 数组且布局不可改:换序 (B, A),零拷贝,优先于一切转置方案;
- 确实需要另一种逻辑转置(如 Aᵀ·B):用
transa/transb = CUBLAS_OP_T,并同步调整 m/n/k 与 ld——此时 ld 取原矩阵的行数,与换序方案不是一回事。
batched API 与流并发的适用规模
batchCUBLAS 在同一组输入上跑三种模式:tmRegular(同一默认流串行 N 次 GEMM)、tmStream(每个 GEMM 一条独立流)、tmBatched(一次cublasSgemmBatched),默认矩阵 128×128×128、N=10(可用-m/-n/-k/-N覆盖)。它打印的正是三种模式各自的elapsed与 GFLOPS,对照关系要自己按机器跑出来,但决策边界可以从实现里读出来:
// batched 模式:指针数组本身必须在 device 端 cublasSetStream(handle, streamArray[0]); status1 = cublasXgemmBatched(handle, params.transa, params.transb, params.m, params.n, params.k, ¶ms.alpha, (const T_ELEM **)devPtrA_dev, rowsA, (const T_ELEM **)devPtrB_dev, rowsB, ¶ms.beta, devPtrC_dev, rowsC, opts.N);边界条件:
- N 个小矩阵、尺寸统一:选 batched。单 GEMM 小到不足以喂满 GPU(128³ 的 sgemm 是微秒级)时,合并成一次调用摊薄启动开销收益最大;
- N 个矩阵尺寸不一:batched 不可用(m/n/k/ld 全部相同),退回多流并发——
cublasSetStream(handle, streamArray[i])后逐个派发,前提是每个 GEMM 之间无数据依赖; - 单矩阵已大到饱和算力:两者都不必上,单次
cublasSgemm即可,batched 只会多一次 N×3 个指针的 H2D 拷贝(batchCUBLAS.cpp 第 451-463 行就是为这件事额外cudaMalloc+cudaMemcpy指针数组)。
数据与可复现:三个示例内置的对比配置
三个示例都不读外部数据文件,输入由rand()/RAND_MAX在代码内生成(matrixMulCUBLAS 固定srand(2006),结果可复现)。各自内置的测量与校验配置如下:
| 配置项 | simpleCUBLAS | matrixMulCUBLAS | batchCUBLAS |
|---|---|---|---|
| 矩阵规模 | 275×275,方阵 | 默认 sizemult=5:A 640×480、B 480×320、C 640×320(32 块对齐) | 128×128×128 |
| 迭代 / 批量 | 1 次 | nIter=30 | N=10,跑 regular/streams/batched 三组 |
| 预热 | 无 | 计时 event 开始前额外执行 1 次 GEMM | 无 |
| 计时方式 | 不计时 | cudaEventRecord/cudaEventElapsedTime,输出 GFlop/s | gettimeofday,每组输出 elapsed 与 GFLOPS |
| CPU 对照 | simple_sgemm三重循环,float 累加 | matrixMulCPU三重循环,double 累加 | 无(用 ULP/相对误差容忍度校验,sgemm 相对误差 6e-6) |
两个诚实性声明:一是性能数字以示例自身 stdout 输出为准,matrixMulCUBLAS 源码里就带着NOTE: The CUDA Samples are not meant for performance measurements的免责声明,GPU Boost 会让结果漂移;二是仓库的共享图像数据在Common/data/(teapot512.pgm 等 8 个文件),服务于纹理/图像类示例,本组 cuBLAS 示例并未引用它们——下面这张图取自cpp/5_Domain_Specific/bilateralFilter/的真实样本数据,用来说明"图像即矩阵"这一负载形态:
选型速查与常见坑 ⚠️
- 换序写反:行主序 C=A·B 必须按 (B, A) 传参;按 (A, B) 传,输出整体等价于 Cᵀ,每个元素位置全错,且 L2 校验未必能立刻暴露——推导见 matrixMulCUBLAS.cpp 文件头。
- host 指针数组进 batched 会崩:
cublasSgemmBatched的 Aarray/Barray/Carray 必须已拷到 device 端(batchCUBLAS.cpp 第 451-463 行)。 - batched 的规模边界:样本默认 128³、N=10;单矩阵大到已饱和算力时上 batched/多流只有成本没有收益。
cublasSetStream是有状态调用:多线程共用一个 handle 必须串行化,否则流设置互相覆盖;样本中每次派发前都重新 SetStream(batchCUBLAS.cpp 第 567 行)。
延伸阅读
- matrixMulCUBLAS.cpp:文件头 20 行注释是行主序/列主序等价关系的完整推导,动布局前先读它
- batchCUBLAS.cpp:三种派发模式共用同一组测试参数,是仓库里最干净的 A/B 对照写法
- simpleCUBLAS.cpp:CPU 参照实现
simple_sgemm与相对 L2 误差判据 - cpp/4_CUDA_Libraries/README.md:该目录下全部库示例的索引
- Common/data/:仓库共享图像数据目录,纹理与图像示例的输入源
【免费下载链接】cuda-samplesSamples for CUDA Developers which demonstrates features in CUDA Toolkit项目地址: https://gitcode.com/GitHub_Trending/cu/cuda-samples
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考