1.cublasSgem参数列表解析
在我的前两篇文章里面写到了基于cublasSgemm实现y=x@w和y=x@w.T,并且实现了在不改变原始数据的行列顺序的情况下实现CUDA库函数调用且与CPU行优先计算库Numpy计算结果保持一致。
今天我们基于上面两个公式的实现来讲一下cublasSgemm的参数列表,参数的含义;废话不多说,相关参数的数据类型和应该传入的数值我已经用中文注释了。
cublasStatus_t cublasSgemm(
cublasHandle_t handle, // cuBLAS 句柄
cublasOperation_t transa, // A 是否需要转置
cublasOperation_t transb, // B 是否需要转置
int m, // 结果矩阵 C 的行数
int n, // 结果矩阵 C 的列数
int k, // A 的列数 / B 的行数
const float *alpha, // 标量 α
const float *A, // 矩阵 A 的设备指针
int lda, // A 的 leading dimension(行主序存储时是 A 的列数)
const float *B, // 矩阵 B 的设备指针
int ldb, // B 的 leading dimension(行主序存储时是 B 的列数)
const float *beta, // 标量 β
float *C, // 结果矩阵 C 的设备指针
int ldc // C 的 leading dimension(行主序存储时是 C 的列数)
);
2.cublasSgemm实现y=x@w参数含义
cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N,N,M,K,&alpha,d_B,N,d_A,K,&beta,d_C,N);
这里我们计算的是y.T=w.T@x.T;转置是因为cublas默认列优先进行数据访存,不是cublasOperation_t参数影响的哦,我们这里不需要做任何转置所以都是CUBLAS_OP_N
很多文章里面写的m,n,k解释是m是A矩阵的行,n是B矩阵的列,k是A矩阵的列同时也是B矩阵的行,这是因为他是基于Trasformer里面多头注意力机制Q@K.T进行定义的,在这里我感觉容易给大家造成混淆并且难以与这里的cublas多种维度进行结合。
这里我们从结果矩阵C出发,m是结果矩阵的行,n是结果矩阵的列,k是左矩阵的列也是右矩阵的行;那么问题来了,是依次传入M,N,K吗,当然不是啦,我们用逆向思维思考一下,我们在cublas调用结束之后用c++默认行主序进行读取的结果矩阵C是[M,N]维度,那么在cublas中列主序,它的维度是不是应该是[N,M]呀,因此这里传入N,M,K
y.T=w.T@x.T中左矩阵是权重矩阵维度是[N,K],因此leading dimension是N;右矩阵是X矩阵维度是[K,M],因此leading dimension是K;结果矩阵的维度是[N,M]因此leading dimension是N;看到这儿大家应该都明白了吧,嘿嘿嘿。
3.cublasSgemm实现y=x@w参数含义
如果大家还对上面的参数列表的定义存在疑惑,那么接下来我们在基于nn.liear的计算来验证一下上面的解释。
cublasSgemm(handle, CUBLAS_OP_T, CUBLAS_OP_N, 4, 2, 3, &alpha, d_B, 3, d_A, 3, &beta, d_C, 4);
我们的x矩阵的维度是[2,3];w矩阵的维度是[4,3],很显然这样的维度是不能直接y=x@w的,需要进行转置;y = x@w.T-->y.T=w@x.T;在这儿对权重矩阵进行转置我们不手动进行数据行列转换,直接用cublasSgemm里面的转置参数就好,如果左右矩阵都不进行转置计算的就是y.T=w.T@x.T;这儿我需要再次强调一下,这里转置不是cublasOperation_t影响的,是因为cublas默认列主序。我们这里w需要进行转置,那么则需要将左矩阵设定为CUBLAS_OP_T,右矩阵不需要转置为CUBLAS_OP_N
结果C矩阵是[4,2],因此m,n,k依次是4,2,3;w左矩阵在进行trans_op之前维度是[3,4],x右矩阵维度是[3,2],结果矩阵维度是[4,2]-->因此leading dimension分别是3,3,4