观察到cublas矩阵乘法比手动编写的CUDA GPU矩阵乘法慢的原因可能有几点:
1. cublas做了更多额外的错误检查和初始化工作,这会增加一些额外的开销。
2. cublas的默认配置可能不是针对特定矩阵大小进行了优化的。可以尝试调整cublas的配置(tile大小,流数量等)来获得更好的性能。
3. 手动CUDA实现可能进行了更多特定问题的优化,例如利用了共享内存,或者针对更好的内存访问模式进行了调整。cublas实现更通用。
4. 主机端传递矩阵参数的方式可能影响性能。cublas可能需要进行额外的数据格式转换。可以考虑使用cublas提供的GPU端内存分配和矩阵设置函数,避免主机和设备之间的数据传输。
5. 矩阵大小很小。cublas的额外开销在小矩阵上的影响可能更大。随着矩阵增大,cublas的优势可能会变得更明显。
cublas提供了几个函数可以在GPU端进行内存分配和矩阵初始化,避免CPU和GPU之间的数据传输:
1. cublasAlloc(): 在GPU上为矩阵分配内存。
2. cublasSetMatrix(): 将主机内存中的矩阵复制到设备上已分配的内存中。
3. cublasSetVector(): 将主机数组中的向量复制到设备上已分配的内存中。
4. cublasGetMatrix(): 将设备上的矩阵复制到主机内存中。
5. cublasGetVector(): 将设备上向量复制到主机数组中。这些函数可以确保数据在GPU端的内存中,从而避免了CPU和GPU之间复制矩阵的开销。
cublas的一些额外开销包括:
- 初始化和配置开销:cublasHandle的创建,stream的配置等。
- 错误检查开销:参数验证,内存检查等。
- 封装开销:比如将矩阵布局转换为cublas需要的布局。
- 更通用的实现:不针对特定大小的矩阵进行优化。
- 默认配置:tile大小,流数量等可能不是最优的。
要优化Cublas的矩阵乘法性能,可以考虑以下几点:
1. 设置cublas的配置参数:
- cublasSetStream() - 设置流以并行执行
- cublasSetMathMode() - 选择精度模式(CublasMath_t)
- cublasSetAtomicsMode() - 启用原子操作
- cublasSetPointerMode() - 设置指针模式
- cublasSetWorkspace() - 设置工作空间
2. 调整tile大小:
- cublasSetMathMode() 可以设置tile大小,找到最优大小。
3. 使用cublasGemmEx扩展API:
- 可以细粒度配置各种参数如算法选择,数据格式,CUDA核函数等。
4. 减少CPU-GPU数据传输:
- 用cublas函数在GPU端分配内存和设置矩阵,不复制到CPU。
- 用异步传输Overlap复制和计算。
5. 批量执行:
- 可以用cublasSgemmBatched()批量执行小矩阵乘法。
6. 浮点精度:
- 尝试half precision或float16来加速计算。
7. 利用共享内存:
- 考虑混合调用cublas和自己的cuda Kernel利用共享内存。
8. 测试不同的矩阵大小,找到最优配置。
几点优化
1. 设置流,允许并行执行:
cublasSetStream(handle, cudaStreamDefault);
2. 选择math模式,改变tile大小:
cublasSetMathMode(handle, CUBLAS_TENSOR_OP_MATH);
3. 使用异步内存复制,overlap计算和传输:
cudaMemcpyAsync(d_A, h_A, Axy * sizeof(float), cudaMemcpyHostToDevice, stream);
cudaMemcpyAsync(d_B, h_B, Bxy * sizeof(float), cudaMemcpyHostToDevice, stream);
cublasSgemm(handle, ..., stream);
cudaMemcpyAsync(deviceRef, d_C, Cxy * sizeof(float), cudaMemcpyDeviceToHost, stream);
cudaStreamSynchronize(stream);
4. 在GPU端分配内存,避免额外复制:
cublasAlloc(M*N, sizeof(float), (void**)&d_C);
5. 尝试half precision:
用cublasSetDataType(handle, CUBLAS_DATA_HALF)
定义半精度矩阵。
6. 测试不同的tile大小,找到最优配置。
7. 考虑调用cublasGemmEx进行更细粒度的配置。
8. 如果矩阵够大,调用cublasSgemmBatched批量执行。