当前位置: 首页 > news >正文

cuda编程笔记(11)--学习cuBLAS的简单使用

cuBLAS 是 NVIDIA 提供的 GPU 加速 BLAS 库;使用时需要#include <cublas_v2.h>

如果使用VS,需要添加cublas.lib的链接;如果用命令编译,-l记得加上cublas

cuBLAS 的核心基础概念

cublasStatus_t

  • 类型:枚举类型

  • 作用:表示 cuBLAS API 的返回状态(错误码)。

  • 常用值

    常量含义
    CUBLAS_STATUS_SUCCESS成功
    CUBLAS_STATUS_NOT_INITIALIZEDcuBLAS 库未初始化
    CUBLAS_STATUS_ALLOC_FAILEDGPU 设备内存分配失败
    CUBLAS_STATUS_INVALID_VALUE传入参数无效
    CUBLAS_STATUS_ARCH_MISMATCH硬件不支持请求的功能(如 Tensor Core)
    CUBLAS_STATUS_EXECUTION_FAILED核函数执行失败

返回值检查(典型模式):

cublasStatus_t status = cublasCreate(&handle);
if (status != CUBLAS_STATUS_SUCCESS) {printf("cuBLAS initialization failed!\n");
}

 cublasHandle_t

  • 类型:指向 cuBLAS 库上下文的句柄(类似于会话)。

  • 作用

    • 所有 cuBLAS 函数都需要它。

    • cuBLAS 使用 上下文模型,通过 handle 管理状态。

  • 生命周期

    • 通过 cublasCreate() 创建。

    • 用完后调用 cublasDestroy() 销毁。

cublasOperation_t

  • 类型:枚举类型

  • 作用:指定矩阵是否需要 转置

  • 常量含义
    CUBLAS_OP_N不转置(Normal)
    CUBLAS_OP_T转置(Transpose)
    CUBLAS_OP_C共轭转置(Conjugate)

使用场景cublasSgemm() 等矩阵乘法接口。
例如:

cublasOperation_t transA = CUBLAS_OP_N;
cublasOperation_t transB = CUBLAS_OP_T;  // B 矩阵转置

cublasCreate()

cublasStatus_t cublasCreate(cublasHandle_t *handle);
  • 作用

    • 初始化 cuBLAS 库。

    • 创建 cuBLAS 句柄。

  • 参数

    • handle:指向 cublasHandle_t 的指针,返回创建的句柄。

  • 返回值

    • CUBLAS_STATUS_SUCCESS 表示成功。

cublasDestroy()

cublasStatus_t cublasDestroy(cublasHandle_t handle);
  • 作用

    • 释放 cublasHandle_t 占用的资源。

  • 参数

    • handle:之前用 cublasCreate() 创建的句柄。

  • 返回值

    • 成功返回 CUBLAS_STATUS_SUCCESS

矩阵乘法(GEMM)

函数原型

cublasStatus_t cublasSgemm(cublasHandle_t handle,cublasOperation_t transa,cublasOperation_t transb,int m, int n, int k,const float *alpha,const float *A, int lda,const float *B, int ldb,const float *beta,float *C, int ldc
);

参数说明

  • transa, transb:

    • CUBLAS_OP_N: 不转置

    • CUBLAS_OP_T: 转置

  • 矩阵维度:

    • m × k 矩阵 A

    • k × n 矩阵 B

    • m × n 矩阵 C

  • alpha, beta: 标量,计算公式:

C = alpha * op(A) * op(B) + beta * C

lda, ldb, ldc: leading dimension(行主序时是行数)

最小示例:矩阵乘法 C = A × B

#include <cublas_v2.h>
#include <cuda_runtime.h>
#include <iostream>
#include <vector>#define M 4
#define N 4
#define K 4int main() {// 创建 cuBLAS handlecublasHandle_t handle;cublasCreate(&handle);// Host 矩阵std::vector<float> h_A(M*K), h_B(K*N), h_C(M*N);for (int i = 0; i < M*K; i++) h_A[i] = 1.0f;for (int i = 0; i < K*N; i++) h_B[i] = 2.0f;float *d_A, *d_B, *d_C;cudaMalloc(&d_A, M*K*sizeof(float));cudaMalloc(&d_B, K*N*sizeof(float));cudaMalloc(&d_C, M*N*sizeof(float));cudaMemcpy(d_A, h_A.data(), M*K*sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_B, h_B.data(), K*N*sizeof(float), cudaMemcpyHostToDevice);const float alpha = 1.0f;const float beta = 0.0f;// 调用 cuBLAS GEMMcublasSgemm(handle,CUBLAS_OP_N, CUBLAS_OP_N,M, N, K,&alpha,d_A, M,d_B, K,&beta,d_C, M);cudaMemcpy(h_C.data(), d_C, M*N*sizeof(float), cudaMemcpyDeviceToHost);// 打印结果for (int i = 0; i < M; i++) {for (int j = 0; j < N; j++) {std::cout << h_C[i + j*M] << " ";  // 注意列主序}std::cout << "\n";}cudaFree(d_A);cudaFree(d_B);cudaFree(d_C);cublasDestroy(handle);
}

矩阵转置or矩阵加法

cuBLAS 提供了 cublasSgeam

cublasSgeamcuBLAS 提供的矩阵加法/转置操作接口,可以完成:

其中 op(X) 表示矩阵 X 是否转置。

cublasStatus_t cublasSgeam(cublasHandle_t handle,      // cuBLAS 句柄cublasOperation_t transa,   // A 是否转置 (CUBLAS_OP_N 或 CUBLAS_OP_T)cublasOperation_t transb,   // B 是否转置int m,                      // C 的行数int n,                      // C 的列数const float *alpha,         // 缩放系数 αconst float *A,             // 矩阵 Aint lda,                    // A 的 leading dimension (步长)const float *beta,          // 缩放系数 βconst float *B,             // 矩阵 Bint ldb,                    // B 的 leading dimensionfloat *C,                   // 结果矩阵 Cint ldc                     // C 的 leading dimension
);
  • handle:cuBLAS 上下文。

  • transa / transb

    • CUBLAS_OP_N → 不转置

    • CUBLAS_OP_T → 转置

  • m, n:结果矩阵 C 的维度 (m 行 n 列)。

  • alpha, beta:系数,通常 alpha = 1.0fbeta = 1.0f

  • A, lda:矩阵 A 的指针和 leading dimension。

  • B, ldb:矩阵 B 的指针和 leading dimension。

  • C, ldc:结果矩阵 C 的指针和 leading dimension。

leading dimension (lda, ldb, ldc)
cuBLAS 默认使用 列主存储(和 Fortran 一致),所以 lda = A 的行数,即 A 每列元素的间隔。

  • 可以实现 矩阵转置(单独把 beta 设为 0)。

  • 可以实现 矩阵加法

  • 可以实现 带缩放的线性组合

 示例代码

#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <iostream>int main() {int m = 2, n = 3;float alpha = 1.0f, beta = 1.0f;// Host 数据float h_A[6] = {1, 2, 3, 4, 5, 6};  // 2x3float h_B[6] = {10, 20, 30, 40, 50, 60};float h_C[6];// Device 指针float *d_A, *d_B, *d_C;cudaMalloc((void**)&d_A, 6 * sizeof(float));cudaMalloc((void**)&d_B, 6 * sizeof(float));cudaMalloc((void**)&d_C, 6 * sizeof(float));cudaMemcpy(d_A, h_A, 6 * sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_B, h_B, 6 * sizeof(float), cudaMemcpyHostToDevice);cublasHandle_t handle;cublasCreate(&handle);// C = A^T + BcublasSgeam(handle,CUBLAS_OP_T, CUBLAS_OP_N,m, n,                // C 大小 (m x n)&alpha,d_A, n,              // A^T,所以 lda = n(原矩阵的列数)&beta,d_B, m,              // B 不转置,ldb = md_C, m);             // C 不转置,ldc = mcudaMemcpy(h_C, d_C, 6 * sizeof(float), cudaMemcpyDeviceToHost);std::cout << "Result C:\n";for (int i = 0; i < 6; i++) {std::cout << h_C[i] << " ";}cublasDestroy(handle);cudaFree(d_A);cudaFree(d_B);cudaFree(d_C);return 0;
}

向量乘矩阵

  • op(A) 表示 A 或 Aᵀ(取决于 trans 参数)

  • xy 是向量

  • alphabeta 是缩放因子

cublasStatus_t cublasSgemv(cublasHandle_t handle,cublasOperation_t trans,   // 是否转置 Aint m, int n,              // 矩阵 A 的维度 (m x n)const float *alpha,        // 缩放因子 alphaconst float *A, int lda,   // 矩阵 Aconst float *x, int incx,  // 向量 xconst float *beta,         // 缩放因子 betafloat *y, int incy         // 向量 y
);
  • trans

    • CUBLAS_OP_N → A 不转置,维度 m×n

    • CUBLAS_OP_T → A 转置,维度 n×m

  • lda:leading dimension,通常等于 m(A 的行数)

  • incxincy:向量步长,通常 = 1

示例代码:

#include <cublas_v2.h>
#include <cuda_runtime.h>
#include <iostream>int main() {cublasHandle_t handle;cublasCreate(&handle);const int m = 3, n = 3;float h_A[m*n] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // 3x3 矩阵float h_x[n]   = { 1, 1, 1 }; // 向量float h_y[m]   = { 0, 0, 0 };float *d_A, *d_x, *d_y;cudaMalloc(&d_A, m*n*sizeof(float));cudaMalloc(&d_x, n*sizeof(float));cudaMalloc(&d_y, m*sizeof(float));cudaMemcpy(d_A, h_A, m*n*sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_x, h_x, n*sizeof(float), cudaMemcpyHostToDevice);cudaMemcpy(d_y, h_y, m*sizeof(float), cudaMemcpyHostToDevice);float alpha = 1.0f;float beta = 0.0f;// y = alpha * A * x + beta * ycublasSgemv(handle, CUBLAS_OP_N, m, n,&alpha, d_A, m, d_x, 1, &beta, d_y, 1);cudaMemcpy(h_y, d_y, m*sizeof(float), cudaMemcpyDeviceToHost);std::cout << "Result y: ";for (int i = 0; i < m; i++) std::cout << h_y[i] << " ";std::cout << std::endl;cudaFree(d_A); cudaFree(d_x); cudaFree(d_y);cublasDestroy(handle);return 0;
}

矩阵 × 向量gemv)其实可以用 矩阵 × 矩阵gemm)来替代,但有几点需要注意:

  • 性能原因
    gemvBLAS Level 2 操作,专门针对 矩阵-向量做了优化,尤其在小规模问题上,它的内存访问模式和寄存器利用更高效。

  • 内存开销
    gemm 处理 m×n 矩阵 × n×1 向量,cuBLAS 内部仍按矩阵矩阵方式调度,会引入不必要的开销。

  • 语义清晰
    gemv API 直接说明这是矩阵-向量操作,避免错误。

总结

  • gemm 是通用 API,可以替代 gemv

  • 但如果只是单次 矩阵 × 向量gemv 更高效。

  • 深度学习场景,通常是批处理(GEMM dominate),所以主流框架用 gemm

多流并发

cublasHandle_t 绑定 CUDA 流:

  • 默认情况下,cuBLAS 的操作运行在 默认流(stream 0) 上,这意味着 cuBLAS 操作会和其他默认流的操作 顺序执行,无法并发。

  • 绑定流后(通过 cublasSetStream(handle, stream)

    • cuBLAS 的运算将提交到指定的 CUDA 流

    • 可以和其他流中的 kernel 并发执行,实现任务流水线和异步调度

    • 对多 GPU 或 pipeline 加速非常重要(比如计算和数据拷贝重叠)

cuBLAS系列函数的本质:

  • cuBLAS API(如 cublasSgemm)是主机端函数,它的作用是:

    • 通过 handle 向 CUDA 驱动提交任务

    • 把矩阵乘法封装成 GPU 内核,并异步调度到 GPU 上运行

  • 计算实际发生在 GPU 上,不是 CPU

  • 调用 cublasSgemm 后,不会阻塞主机(异步执行),除非你显式调用 cudaDeviceSynchronize 或访问 GPU 结果

核心区别:

  • cublasSgemm 不做计算本身,它只是 发起任务

  • CUDA 流 + handle 控制 任务在哪个流上执行

  • 默认流会串行执行,多个流能并发

举个并发例子:

cublasHandle_t handle;
cublasCreate(&handle);cudaStream_t stream1, stream2;
cudaStreamCreate(&stream1);
cudaStreamCreate(&stream2);// 第一批数据在 stream1
cublasSetStream(handle, stream1);
cublasSgemm(handle, ... /* batch1 */);// 第二批数据在 stream2
cublasSetStream(handle, stream2);
cublasSgemm(handle, ... /* batch2 */);// 两个 GEMM 并行执行
cudaStreamSynchronize(stream1);
cudaStreamSynchronize(stream2);

如果学习过我的Boost.Asio文章的话,cublasHandle_t非常像Boost.Asio里的io_context

http://www.lryc.cn/news/606243.html

相关文章:

  • Coze Studio概览(四)--Prompt 管理功能详细分析
  • 分布式锁的基本原理和基于lua脚本的实现(Redisson)
  • 红黑树×协程×内存序:2025 C++后端核心三体问题攻防手册
  • 旅游城市数量最大化 01背包问题
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘plotly’问题
  • Mac安装Navicat教程Navicat Premium for Mac v17.1.9 Mac安装navicat【亲测】
  • IK 字段级别词典的升级之路
  • 【RH134 问答题】第 11 章 管理网络安全
  • ACL 2024 大模型方向优秀论文:洞察NLP前沿​关键突破!
  • 前端框架Vue3(四)——组件通信及其他API
  • SecurityContextHolder 管理安全上下文的核心组件详解
  • python之使用ffmpeg下载直播推流视频rtmp、m3u8协议实时获取时间进度
  • 金融分类提示词演示
  • 代码随想录Day35:动态规划(背包问题 二维 一维、分割等和子集)
  • 守护金融核心业务 | 博睿数据《金融业务全景与全链路智能可观测体系建设白皮书》发布!
  • 云上服务器常见的存储方式和类型
  • MySQL 中的 JOIN 操作有哪些类型?它们之间有什么区别?
  • vk框架或者普通函数封装的一些函数可以拿取使用【会持续更新】
  • Maven模块化开发与设计笔记
  • 一起学springAI系列一:初体验
  • 解释 MySQL 中的 EXPLAIN 命令的作用和使用场景
  • 机器学习——互信息(超详细)
  • 机器学习基础-seaborn
  • Mysql超详细安装配置教程(详细图文,保姆级)
  • 大数据之Hive
  • mysql主从搭建(docker)
  • Java设计模式之《命令模式》
  • 【LY88】ubuntu下的常用操作
  • 常用的ROS(Robot Operating System,机器人操作系统)包,用于机器人软件开发的工具和库
  • Prometheus监控多个MySQL及服务器性能:配置指南与最佳实践