1、cusparse简介
cusparse是一个非常好的进行系数代数运算的库。不得不提到的是,它的效率是相当高效的,尤其是当进行大规模的稀疏计算的时候,cuda的优势就体现得淋漓尽致了(相比于MATLAB而言)。先说明一下,如果你是一个有着丰富编程经验的老手,那么本文对于你可能没有太大作用,然而如果你是想使用cusparse加速程序的新手,本文可以为你节约相当的时间。
2、稀疏矩阵于一般矩阵的乘法
Cusparse的使用是有很多需要注意的地方。这里拿大家比较关心的稀疏矩阵*一般矩阵(不一定是稀疏)的问题来说吧。
在cusparse库里面这是有相应的函数对应的,调用起来非常方便,但是初学起来会非常疑惑,怎么自己辛辛苦苦输入的玩意算出来的结果居然是不对的。对于我这种断断续续看了得有大半年cuda的同学来说也是一头雾水,好在折腾了几天也算是把它给弄出来了,现在就说说cusparse的用法。
首先得说说cuda的存储方式,与C\C++按行存储的方式不同,cuda对于矩阵的存储方式是列,也就是相当于转置的关系,这个问题在cublas是有提到的,但是不同的是,cublas那个sgemm只要把A和B调换过来,改改m,n,k,就可以得到正确的C。但是在cusparse的函数cusparseScsrmm里面就完全不是那么一回事了。在介绍这个函数之前,可以先简单介绍一下CSR的存储算法,它的基本思想就是说,我现在有一个稀疏矩阵A,我把A的非零元素存在val数组里,把A的非零元素的列标存在col_indx里,然后把A的每行开头的非零元素在val的位置保存在row_ptr里面(这句话有点绕)。总之就是用三个一维数组保存A的所有信息。但是,我有一点没有太想明白,就是如果A有一行恰好都是0那咋办。。。先不扯这个了,估计是有办法解决,日后看到了再更新。那么在对应的cusparseScsrmm函数里,val和col_indx就按上述方式做就好了,它们的维数都是nnz(number of non-zero).但是,row_ptr的维数是(m+1),其中m是行数,然后最后一个元素就是nnz(其实我也不知道为啥这样设计)。
关键的地方就是我们输入的这个一般矩阵B,由于它在cusparse里面它根本是按列来存储的,那么我们在C++里面输入这个B的时候就要按列来输入,而不是按行。
下面举个例子,大家就懂了。
假如,A是一个2*3的矩阵,记成A=[1 2 3;4 5 6]。
然后,B是一个3*3的矩阵,不妨记成B=[1 2 3;4 5 6;7 8 9].
这个时候我们想把A转化成CSR的形式来进行存储,那么val就是[1 2 3 4 5 6],而row_ptr=[0 3 6],col_indx=[0 1 2 0 1 2].
现在我们想计算A*B,利用cusparseScsrmm来进行计算,那么它的格式就如下所示:
cusparseScsrmm(sparse_handle,CUSPARSE_OPERATION_NON_TRANSPOSE,m,n,k,nnz,&alpha,descr,d_val,d_rp,d_cp,d_B,k,&beta,d_C,m);
这里着重介绍一下m,n,k这三个整数,其中m代表的是A的行,k代表的是A的列也就是B的行,然后n代表的是B和C的列,这个在官方的文档是有说明的。然后还要说一点的就是这样子算出来的答案是不对的,也就是C不是我们想要的,由于上文提