Published on

cuBLAS 基础教程:GPU 上的高性能矩阵运算

cuBLAS 教程

学习目标

  1. 理解 cuBLAS 库及其在 CUDA 编程中的作用
  2. 学习如何使用 cuBLAS 执行基本的矩阵运算
  3. 探索 cuBLAS 的高级功能以优化性能

1. cuBLAS 概述

cuBLAS 是 NVIDIA 在 GPU 上实现的基本线性代数子程序库。它提供了经过高度优化的常用线性代数运算例程,例如矩阵乘法、向量加法等。cuBLAS 专门设计用于利用 NVIDIA GPU 的并行处理能力,是高性能计算应用的有力工具。

cuBLAS 特别适用于需要高效矩阵运算的应用,例如科学计算、深度学习等。

cuBLAS 提供了以下常用线性代数运算的优化例程:

  • 向量运算(Level 1 BLAS)
  • 矩阵-向量运算(Level 2 BLAS)
  • 矩阵-矩阵运算(Level 3 BLAS)

2. 使用 cuBLAS 进行矩阵乘法

cuBLAS 的矩阵乘法操作为:C = alpha * A * B + beta * C,其中 ABC 都是矩阵。

重要提示: cuBLAS 的头文件是 cublas_v2.h,链接库时需要添加 -lcublas 参数。


3. 列主序与行主序

存储方式对比

  • 行主序:矩阵按行连续存储在内存中
  • 列主序:矩阵按列连续存储在内存中

示例

对于矩阵 A[2][2] = {{1, 2}, {3, 4}}

行主序存储

内存地址: 0  1  2  3
存储元素: 1  2  3  4

列主序存储

内存地址: 0  1  2  3
存储元素: 1  3  2  4

重要区别

cuBLAS 使用列主序(类似 Fortran),而 C/C++ 使用行主序。如果需要在两者之间转换,可以使用以下宏定义:

#define IDX2C(i,j,ld) (((j)*(ld))+(i))  // 列主序索引宏

4. cuBLAS 句柄

cuBLAS 是一个有状态库。它不使用全局状态,而是通过句柄对象(cublasHandle_t)来跟踪:

  • 使用哪个 CUDA 流
  • 分配的工作空间
  • 算法偏好
  • 错误状态

有状态库说明:有状态库是指在函数调用之间通过上下文或句柄维护内部状态的库。这种状态会影响函数的行为,允许库管理配置、资源或执行上下文。

这种设计支持:

  • 多个独立的 cuBLAS 上下文
  • 线程安全(可在不同线程中使用不同句柄)
  • 更好的性能调优控制

创建和销毁句柄

// 创建句柄
cublasHandle_t handle;
cublasCreate(&handle);

// 使用句柄进行运算...

// 销毁句柄(释放资源)
cublasDestroy(handle);

5. cuBLAS 运算

cublasSgemm 函数

cublasSgemm 是用于单精度矩阵乘法的函数,执行的操作为:C = alpha * A * B + beta * C

其中:

  • A 是 m × n 矩阵
  • B 是 n × k 矩阵
  • C 是 m × k 矩阵
  • alpha 是乘积 A * B 的标量乘数
  • beta 是现有矩阵 C 的标量乘数

领先维度说明

领先维度 是指内存中一列开始到下一列开始之间的距离。对于 cuBLAS 使用的列主序存储,它指的是矩阵的行数

函数调用示例

cublasSgemm(
    handle,       // cuBLAS 句柄
    CUBLAS_OP_N,  // A 的操作类型(CUBLAS_OP_N 表示不转置)
    CUBLAS_OP_N,  // B 的操作类型
    N,            // A 和 C 的行数
    N,            // B 和 C 的列数
    N,            // A 的列数和 B 的行数
    &alpha,       // A*B 的标量乘数
    d_A,          // 设备内存中矩阵 A 的指针
    N,            // A 的领先维度
    d_B,          // 设备内存中矩阵 B 的指针
    N,            // B 的领先维度
    &beta,        // C 的标量乘数
    d_C,          // 设备内存中矩阵 C 的指针
    N             // C 的领先维度
);

注意:在使用 cuBLAS 时,你不需要像原始 CUDA 内核启动那样手动配置线程块和网格。cuBLAS 内部会自动:

  1. 检查矩阵大小和布局
  2. 选择最佳的内核和块/线程/网格配置
  3. 使用其内部逻辑启动内核

6. 结果输出

最终结果 C 采用列主序存储,这是 cuBLAS 的默认方式。打印结果时需要使用正确的索引:

for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
        std::cout << h_C[IDX2C(i, j, N)] << " ";
    }
    std::cout << "\n";
}

7. 关键要点总结

✅ cuBLAS 是一个有状态库,使用句柄管理其状态 ✅ 句柄通过 cublasCreate 创建,通过 cublasDestroy 销毁 ✅ 矩阵乘法使用 cublasSgemm 执行,需要指定操作类型、维度和矩阵指针 ✅ cuBLAS 对矩阵使用列主序,与 C/C++ 使用的行主序不同 ✅ 领先维度对于在内存中正确访问矩阵元素非常重要 ✅ 可以在同一程序中运行多个 cuBLAS 内核,甚至可以并发运行

THE END