cuBLAS入门

CUDA入坑

Posted by John Mactavish on October 4, 2019

前言

我现在入CUDA的大坑啦,大概看了一下CUDA并行计算的基本编程方法,现在开始 学习CUDA的基本线性代数库cuBLAS。因为我刚开始学,所以不要期待有太多干货。 本文其实主要是库文档的内容,只不过官方文档没有中文版,这个就当做是学习笔记吧。

cuBLAS使用

从版本4.0开始,除了现有的旧版API外,cuBLAS库还提供了新的更新的API。 通过包含头文件“ cublas_v2.h”来使用新的cuBLAS库API。

从6.5版开始,cuBLAS库在Linux和Mac OS上也以静态形式libcublas_static.a交付。 静态cuBLAS库和所有其他静态数学库都依赖于称为libculibos.a的公共线程抽象层库。

例如,在Linux上,要使用cuBLAS针对动态库编译小型应用程序,可以使用以下命令:

nvcc myCublasApp.c -lcublas -o myCublasApp

而要针对静态cuBLAS库进行编译,则必须使用以下命令:

nvcc myCublasApp.c -lcublas_static -lculibos -o myCublasApp

数据布局

为了与现有的Fortran环境最大程度地兼容,cuBLAS库使用列主存储和基于1的索引。 由于C和C ++使用行优先存储,因此用这些语言编写的应用程序不能对二维数组使用本机数组语义。 相反,应定义宏或内联函数以在一维数组的顶部实现矩阵。对于以机械方式移植到C的Fortran代码, 可以选择保留基于1的索引,以避免需要转换循环。在这种情况下, 可以通过以下宏计算“ i”行和“ j”列中矩阵元素的数组索引

#define IDX2F(i,j,ld)(((((j)-1)*(ld))+((i)-1))

在这里,ld是指矩阵的leading dimension,在列为主存储的情况下, 它是分配的矩阵的行数(即使仅使用其子矩阵)。 对于本机编写的C和C ++代码,很可能会选择基于0的索引,在这种情况下, 可以通过以下宏计算“ i”行和“ j”列中矩阵元素的数组索引

#define IDX2C(i,j,ld)(((j)*(ld))+(i))

解释一下,比如说: 矩阵空间是 3x4,其左上角有一个子矩阵2x3,表示如下

11 22 33 0

44 55 66 0

0  0  0  0

i, j分别表示行索引,列索引。如果用列存储的话,leading dimension = 3 (矩阵空间的行个数), 数组索引与元素位置的换算公式是i + j *ld,数组在C语言中存储为

[11, 44, 0, 22, 55, 0, 33, 66, 0, 0, 0, 0]

如果是用行存储, leading dimension = 4(矩阵空间的列个数),换算公式是 i*ld + j

基本使用

初始化句柄

cublasStatus_t cublasCreate(cublasHandle_t *handle)
cublasStatus_t cublasDestroy(cublasHandle_t handle)

第一个函数将初始化CUBLAS库,并为保存CUBLAS库上下文创建一个句柄。 它在主机和设备上分配硬件资源,并且在进行任何其他CUBLAS库调用时必须使用它。 CUBLAS库上下文绑定到当前CUDA设备。要在多个设备上使用该库, 需要为每个设备创建一个CUBLAS句柄。此外,对于给定的设备,可以创建具有不同配置的多个CUBLAS句柄。 因为cublasCreate 调用分配一些内部资源并且cublasDestroy 释放这些资源将隐式调用 cublasDeviceSynchronize,建议尽量减少cublasCreate /cublasDestroy 。 对于在不同线程中使用同一设备的多线程应用程序,建议的编程模式是为每个线程创建一个CUBLAS句柄, 并在整个线程寿命中使用该CUBLAS句柄。

向量、矩阵在device和host间的数据交互

cublasStatus_t cublasSetVector(int n,int elemSize,
                 const  void * x,int incx,void * y,int incy)

cublasStatus_t cublasGetVector(int n, int elemSize,
                const void *x, int incx, void *y, int incy)

cublasStatus_t cublasSetMatrix(int rows, int cols, int elemSize,
                const void *A, int lda, void *B, int ldb)

cublasStatus_t cublasGetMatrix(int rows, int cols, int elemSize,
                const void *A, int lda, void *B, int ldb)

cublasSetVector 把在主机内存空间中的向量x的n个元素复制到GPU内存空间中作为向量y。 默认假定两个向量中的元素的大小都为elemSize个字节。连续元素之间的存储间距为incx 用于源向量 X 和通过 印西 为目的地矢量 ÿ。

一般来说, y指向通过cublasAlloc() 分配了空间的一个对象或对象的一部分 。 由于假定了二维矩阵采用列主格式,如果这个向量是矩阵的一部分,则向量增量等于1个访问该矩阵的(部分)列。 类似地,使用等于矩阵的前导维的增量会导致访问该矩阵的(部分)行。

cublasGetVector 与第一个函数刚好相反,是将向量从GPU内存空间复制到主机内存空间。

cublasSetMatrix 与cublasGetMatrix 同上面两个类似,只不过操作的是矩阵。 lda和ldb分别是A,B矩阵的leading dimension 。

cublasStatus_t cublasGetVectorAsync(int n,int elemSize,const  void * devicePtr,int incx,
                      void * hostPtr,int incy,cudaStream_t stream)

此函数与cublasGetVector() 功能相同,但使用给定的CUDA™流参数以异步方式(相对于主机)进行数据传输。 上面的其他几个函数都有对应的 Async 函数。

运算API

总览

cuBLAS中把基本的运算API分成3个level 。从level 1 到level 3 分别定义了向量-向量运算、 向量矩阵运算、矩阵矩阵运算。 介绍时与官方文档一致,我将使用缩写< type >代表类型(type),使用< t >代替对应的短类型, 以更简洁明了地介绍所实现的功能。除非另有说明,否则< type >和< t >具有以下含义:

< type > < t > 意思
float ‘s’ or ‘S’ 单精度浮点实数
double ‘d’ or ‘D’ 双精度浮点实数
cuComplex ‘c’ or ‘C’ 单精度浮点复数
cuDoubleComplex ‘z’ or ‘Z’ 双精度浮点复数

例如, cublasI< t >amax()代表以下函数族:

cublasStatus_t cublasIsamax(cublasHandle_t handle, int n,
                            const float *x, int incx, int *result)
cublasStatus_t cublasIdamax(cublasHandle_t handle, int n,
                            const double *x, int incx, int *result)
cublasStatus_t cublasIcamax(cublasHandle_t handle, int n,
                            const cuComplex *x, int incx, int *result)
cublasStatus_t cublasIzamax(cublasHandle_t handle, int n,
                            const cuDoubleComplex *x, int incx, int *result)

它们分别是用来处理上述四种数据的。

当函数的参数和返回值不同时(有时在复杂的输入中会发生这种情况),< t >还可以具有以下含义: “ Sc”,“ Cs”,“ Dz”和“ Zd”。

缩写Re(。)和Im(。)分别代表数字的实部和虚部。由于实数的虚部不存在,因此我们将其视为零, 并且通常可以简单地从使用它的方程式中将其丢弃。另外, α ̄ 将表示的复共轭 α 。

小写的希腊符号 α 和 β 表示标量,小写英文字母x, y将表示向量,而大写英文字母A, B 和 C 将表示矩阵。

我不会介绍所有的API,事实上,函数接口基本上都是相似的,而且可以从函数名很容易猜测出函数功能, 参考官方文档困难不会很大。

level 1

cublas< t >asum() 计算向量内元素的绝对值(或者模)的和。签名示例:

cublasStatus_t  cublasSasum(cublasHandle_t handle, int n,
                            const float           *x, int incx, float  *result)

cublas< t >axpy() 将向量 x 与标量 α 相乘并将其加到向量 y 上,用计算结果覆盖y 变量。 签名示例:

cublasStatus_t cublasSaxpy(cublasHandle_t handle, int n,
                           const float           *alpha,
                           const float           *x, int incx,
                           float                 *y, int incy)

cublascopy() 复制向量 X 到向量 y 。签名示例:

cublasStatus_t cublasScopy(cublasHandle_t handle, int n,
                           const float           *x, int incx,
                           float                 *y, int incy)

cublasdot() 计算向量X 和 y 的点积。签名示例:

cublasStatus_t cublasSdot (cublasHandle_t handle, int n,
                           const float           *x, int incx,
                           const float           *y, int incy,
                           float           *result)