我最近发现,如果矩阵有一个"大"那么用于矩阵乘法的cblas_sgemm调用的性能会显着提高。其中的零个数。它改善到它击败其古巴拉斯表兄弟大约100倍的程度。这可能很大程度上归因于一些自动检测稀疏性和cblas_sgemm函数的合适格式转换。
不幸的是,它的cuda对应物即cublasSgemm没有表现出这种行为。
所以,问题是,对于可能大量零的矩阵,如何在cublasSgemm上获得相同类型的优化。
cblas_sgemm使用什么技术自动调整为稀疏矩阵?
请不要推荐cuSparse / CUSP等,因为
提前致谢
已修改为包含重现上述方案的代码
#include <iostream>
#include <stdio.h>
#include <time.h>
#include <cblas.h>
#include <cublas_v2.h>
using namespace std;
int main()
{
const int m = 5000;
timespec blas_start, blas_end, cublas_start, cublas_end;
long totalnsec; //total nano sec
double totalsec, totaltime;
int i, j;
float *A = new float[m]; // 1 x m
float *B = new float[m*m]; // m x m
float *C = new float[m]; // 1 x m
// input martix A: every 32nd element is non-zero
for(i = 0; i < m; i++)
{
A[i] = 0;
if( i % 32 == 0) //adjust for sparsity
A[i] = i;
}
// input matrix B: identity matrix
// col major = row major
for(i = 0; i < m; i++)
for(j = 0; j < m; j++)
{
if (i==j)
B[j*m + i] = 1;
else
B[j*m + i] = 0;
}
clock_gettime(CLOCK_REALTIME, &blas_start);
cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, 1, m, m, 1, A, m, B, m, 0, C, m);
clock_gettime(CLOCK_REALTIME, &blas_end);
/*
for(i = 0; i < 12; i++)
printf("%f ", C[i]);
*/
//cublas section
cudaError_t cudaStat;
cublasHandle_t handle;
cublasCreate(&handle);
//Declaring Device Variables
float *A_d, *B_d, *C_d;
//Allocating Memory for Device Variables
cudaStat = cudaMalloc(&A_d, sizeof(float)*m);
if(cudaStat != cudaSuccess) printf("Error Allocating Memory for A_d\n");
cudaStat = cudaMalloc(&B_d, sizeof(float)*m*m);
if(cudaStat != cudaSuccess) printf("Error Allocating Memory for B_d\n");
cudaStat = cudaMalloc(&C_d, sizeof(float)*m);
if(cudaStat != cudaSuccess) printf("Error Allocating Memory for C_d\n");
// Moving values of A, B onto Device variables
cublasSetVector(m, sizeof(float), A, 1, A_d, 1);
cublasSetMatrix(m, m, sizeof(float), B, m, B_d, m);
// Do the actual multiplication
float alpha = 1.0f, beta = 0.0f;
cudaDeviceSynchronize();
clock_gettime(CLOCK_REALTIME, &cublas_start);
cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, 1, m, m, &alpha, A_d, 1, B_d, m, &beta, C_d, 1);
cudaDeviceSynchronize();
clock_gettime(CLOCK_REALTIME, &cublas_end);
cublasGetVector(m, sizeof(float), C, 1, C_d, 1);
/*
for(i = 0; i < 12; i++)
printf("%f ", C[i]);
*/
// Print times
// blas time
totalsec = (double)blas_end.tv_sec - (double)blas_start.tv_sec;
totalnsec = blas_end.tv_nsec - blas_start.tv_nsec;
if(totalnsec < 0)
{
totalnsec += 1e9;
totalsec -= 1;
}
totaltime = totalsec + (double)totalnsec*1e-9;
cout<<"BLAS Time = "<< totaltime << "\n";
//cublas
totalsec = (double)cublas_end.tv_sec - (double)cublas_start.tv_sec;
totalnsec = cublas_end.tv_nsec - cublas_start.tv_nsec;
if(totalnsec < 0)
{
totalnsec += 1e9;
totalsec -= 1;
}
totaltime = totalsec + (double)totalnsec*1e-9;
cout<<"CUBLAS Time = "<< totaltime << "\n";
return 0;
}
让它获得以下结果
malang@ubuntu:~/uas/stackoverflow$ nvcc -arch=sm_12 blascomp.cu -o blascomp.o -lblas -lcublas
malang@ubuntu:~/uas/stackoverflow$ ./blascomp.o
BLAS Time = 0.000964504
CUBLAS Time = 0.0365322
修改
在@Eric
的答案后编辑使用cublasSgemv极大地提高了GPU的性能。但是,我仍然有这个问题,cblas_sgemm对CPU上的稀疏矩阵更有效。可能的原因是什么?
编辑根据@Eric @osgx @Robert Crovella
的建议执行以下命令erisp@ubuntu:~/uas/stackoverflow$ ldd ./gemmcomp.o
linux-gate.so.1 => (0xb76f6000)
libblas.so.3 => /usr/lib/libblas.so.3 (0xb765e000)
libstdc++.so.6 => /usr/lib/i386-linux-gnu/libstdc++.so.6 (0xb7576000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb73c7000)
libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb7381000)
/lib/ld-linux.so.2 (0xb76f7000)
libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb7364000)
erisp@ubuntu:~/uas/stackoverflow$ ll -d /usr/lib/libblas* /etc/alternatives/libblas.*
lrwxrwxrwx 1 root root 26 مارچ 13 2015 /etc/alternatives/libblas.a -> /usr/lib/libblas/libblas.a
lrwxrwxrwx 1 root root 27 مارچ 13 2015 /etc/alternatives/libblas.so -> /usr/lib/libblas/libblas.so
lrwxrwxrwx 1 root root 29 مارچ 13 2015 /etc/alternatives/libblas.so.3 -> /usr/lib/libblas/libblas.so.3
lrwxrwxrwx 1 root root 29 مارچ 13 2015 /etc/alternatives/libblas.so.3gf -> /usr/lib/libblas/libblas.so.3
drwxr-xr-x 2 root root 4096 مارچ 13 2015 /usr/lib/libblas/
lrwxrwxrwx 1 root root 27 مارچ 13 2015 /usr/lib/libblas.a -> /etc/alternatives/libblas.a
lrwxrwxrwx 1 root root 28 مارچ 13 2015 /usr/lib/libblas.so -> /etc/alternatives/libblas.so
lrwxrwxrwx 1 root root 30 مارچ 13 2015 /usr/lib/libblas.so.3 -> /etc/alternatives/libblas.so.3
lrwxrwxrwx 1 root root 32 مارچ 13 2015 /usr/lib/libblas.so.3gf -> /etc/alternatives/libblas.so.3gf
答案 0 :(得分:3)
您的代码存在问题 - 您使用的是错误的BLAS API。您可以使用矩阵 - 矩阵乘法例程gemm()
来执行向量矩阵乘法运算。
对于vec-mat-mul或mat-vec-mul,您应该使用gemv()
。当然gemm()
可以使用只有1行的矩阵给出正确的结果。但这是gemv()
应该处理的一个意想不到的极端情况,
所以你可能无法在GPU和/或CPU上获得最佳性能。
您可以更改为gemv()
并再次进行基准测试。
修改强>
这是单线程MKL的基准测试结果。 A
和B
的值与您的代码中的值相同。我无法在CPU上重现'0.000964504s'的结果。您可以检查结果向量的正确性。您的cblas库有可能存在错误。
使用gemm()
BLAS Time = 0.0169784
CUBLAS Time = 0.00356155
使用gemv()
BLAS Time = 0.0167557
CUBLAS Time = 0.0013809
<强> EDIT2 强>
我现在可以使用包libblas-dev
在unbuntu 14.04上重现FAST结果。
原因在以下问题中得到解答。
cblas gemm time dependent on input matrix values - Ubuntu 14.04
在特定版本的BLAS中,有代码来检查零元素。检查成本为O(n ^ 2),因此值得在矩阵乘法中进行此操作,其成本为O(n ^ 3)。
对于GPU gemm(),由于计算顺序不同(逐块而不是逐行),这种优化可能不可行。但它可以用于GPU gemv(),其中可以节省从全局存储器加载矩阵所花费的时间。