cublas的文档中提供了一个用LU分解求逆矩阵的方法,需要用到两个函数:
第一个函数用于做LU分解,第二个函数把LU分解的结果变为逆矩阵。
但官方文档对这两个函数的用法语焉不详,我花了几个小时才把这个问题搞定。主要遇到两个问题:
函数有一个参数是 const float *[] 类型,直接把 float **指针传进去的话编译通不过,以前没接触过这个类型的指针,费了不少功夫,上网查了些资料才搞定。
编译通过后,又遇到第二个问题:运行两个函数都会提示:
unspecified launch failure
这是显存变量越界造成的。但仔细检查代码,也没找到问题。后来翻墙到http://stackoverflow.com/看看有没有人遇到过类似问题,不得不说国外的社区就是强大,果然有人遇到过类似问题,别且找到了症结所在。
原来,CUDA 的指针的指针(例如 float **)就像普通数据一样,也分为 host和 device, host上的,例如:
float ** hostPrt = (float **)malloc(sizeof(float *));//这是host上的定义方法
float ** devicePrt ;
cudaMalloc((void **) & devicePrt, sizeof(float *));//这是device上的定义方法。
而这两个函数参数中,接受的float * [] 参数都是 device指针, 传入host指针就出出错。
解决了这个问题后,用cublas求逆矩阵就顺利通过了。
但最后又遇到一个问题:
我测试了用cublas计算逆矩阵的时间,和CPU上用EIGEN计算用的时间,(我的显卡是GTX980ti 算是不错的显卡了),计算矩阵大小是1000 x 1000。结果cublas用的时间是CPU的5倍!!!看来用cublas计算逆矩阵,毫无速度优势。我又想是不是矩阵不够大?就改为2000 x 2000的矩阵试试看,结果是显卡直接罢工了(cublas的文档上就说求逆的矩阵不宜过大),而EIGEN也只是用了0.6秒左右的时间。究其原因,应该是求逆矩阵并不是一个可以通过并行方法解决的问题(求特征矩阵也是如此)。
那么为何cublas为何还要提供一个求逆矩阵的函数呢?
因为cublas提供的这两个函数,并非计算单个逆矩阵,而是可以计算逆矩阵组,比如你有几十个相同大小的矩阵需要求逆,就可以发挥并行运算的威力,可能计算几十个的时间比计算一个的时间多不了太多,这样GPU的优势就显示出来了。毕竟在实际应用中,求一系列矩阵的逆矩阵的情况还是常见的,比如做岭回归分析的时候。
最后,我就把用这两个函数计算逆矩阵组的代码贴出来,供大家参考:
cublasHandle_t handle;
cublasCreate(&cublasHandle);
int size = 50; //矩阵的行和列
int num = 100;//矩阵组的矩阵个数
int * info ;//用于记录LU分解是否成功
int * pivo;//用于记录LU分解的信息
cudaMalloc((void **) & info, sizeof(int) * num);
cudaMalloc((void **) & pivo, sizeof(int) * size * num);
float ** mat = new float *[num];//待求逆的矩阵组
float ** invMat = new float *[num];//存放逆矩阵的矩阵组
for(int i = 0; i< num; i++){
cudaMalloc((void **) & mat[i], sizeof(float) * size * size);
cudaMalloc((void **) & invMat[i], sizeof(float) * size * size);
/*
这里将矩阵的数据载入mat[i]中,这里假设矩阵的数据在内存中是连续存放的
*/
}
float ** gpuMat;
cudaMalloc((void **) & gpuMat, sizeof(float *) * num);
cudaMemcpy(gpuMat, mat, sizeof(float *) * num, cudaMemcpyHostToDevice);
//以上三步的目的是把host上的float ** 指针转变为 device上的 float ** 指针
cublasSgetrfBatched(handle, size, gpuMat, size , pivo, info, num);//第四个参数是矩阵的主导维,由于这里假设数据在内存中的存放是连续的,所以是size
const float ** constMat;
cudaMalloc((void **) & constMat, sizeof(float *) * num);
cudaMemcpy(constMat, gpuMat, sizeof(float *) * num, cudaMemcpyDeviceToDevice);
//以上三步的目的是把 float ** 指针转变为 float *[]指针
float ** gpuInvMat;
cudaMalloc((void **) & gpuInvMat, sizeof(float *) * num);
cudaMemcpy(gpuInvMat, invMat, sizeof(float *) * num, cudaMemcpyHostToDevice);
//以上三步的目的是把host上的float ** 指针转变为 device上的 float ** 指针
cublasSgetriBatched(handle, size, constMat, size, pivo, gpuInvMat, size, info, num);
cudaFree(info);
cudaFree(pivo);
cudaFree(mat);
cudaFree(gpuMat);
cudaFree(gpuInvMat);
cudaFree(constMat);