OpenCL矩阵乘法应该更快?

时间:2013-05-25 10:24:29

标签: performance opencl matrix-multiplication pyopencl

我正在尝试学习如何制作GPU优化的OpenCL kernells,我在本地内存中使用方块,采用矩阵乘法的例子。然而与numpy.dot()(5 Gflops,它正在使用BLAS)相比,我获得了最好的情况只有~10倍的加速(~50 Gflops)

我发现研究他们获得加速> 200x(> 1000 Gflops)ftp://ftp.u-aizu.ac.jp/u-aizu/doc/Tech-Report/2012/2012-002.pdf 我不知道我做错了什么,或者只是因为我的GPU(nvidia GTX 275)。或者,如果是因为一些pyOpenCl开销。但是我也确信将GPU的结果复制到RAM需要多长时间,它只是矩阵乘法时间的10%左右。

#define BLOCK_SIZE 22 
__kernel void matrixMul(
      __global float* Cij, 
      __global float* Aik, 
      __global float* Bkj, 
      __const int ni, 
      __const int nj,
      __const int nk
){
//   WARRNING : interchange of  i  and  j  dimension  lower the performance >2x on my nV GT275 GPU    
int gj = get_global_id(0);    int gi = get_global_id(1); 
int bj = get_group_id(0);     int bi = get_group_id(1);  // Block index
int tj = get_local_id(0);     int ti = get_local_id(1);  // Thread index
int oj = bi*BLOCK_SIZE;       int oi = bj*BLOCK_SIZE; 
float Csub =0; 
__local float As   [BLOCK_SIZE][BLOCK_SIZE];
__local float Bs   [BLOCK_SIZE][BLOCK_SIZE];
for (int ok = 0; ok < nk; ok += BLOCK_SIZE )   {
    As[ti][tj] = Aik[ nk*(gi   ) + tj + ok ];   // A[i][k]
    Bs[ti][tj] = Bkj[ nj*(ti+ok) + gj ];        // B[k][j]
    barrier(CLK_LOCAL_MEM_FENCE);
    for (int k = 0; k < BLOCK_SIZE; ++k) Csub += As[ti][k] * Bs[k][tj];
    barrier(CLK_LOCAL_MEM_FENCE);
}
Cij[ nj * ( gi ) + gj ] = Csub;

}

注意 - 奇怪的BLOCK_SIZE = 22是最大的BLOCK_SIZE,它适合我的GPU上的最大work_group_size,即512。在此代码中必须保持条件BLOCK_SIZE ^ 2&lt; max work_group_size。 22 = INT(SQRT(512))。我也尝试了BLOCK_SIZE = 16或8但是它与22的比较慢。

我也尝试过简单的matrixMul(不使用本地内存),但它甚至比numpy.dot()慢10倍。 我在这里复制了代码 http://gpgpu-computing4.blogspot.cz/2009/10/matrix-multiplication-3-opencl.html 他们说即使是简单的版本(没有本地内存)也应该比CPU快200倍?我并不感到不安。

在我的案例中,表现的依赖性是:

N =  220 numpy 3.680 [Gflops] GPU 16.428 [Gflops] speedUp 4.464 
N =  330 numpy 4.752 [Gflops] GPU 29.487 [Gflops] speedUp 6.205 
N =  440 numpy 4.914 [Gflops] GPU 37.096 [Gflops] speedUp 7.548 
N =  550 numpy 3.849 [Gflops] GPU 47.019 [Gflops] speedUp 12.217 
N =  660 numpy 5.251 [Gflops] GPU 49.999 [Gflops] speedUp 9.522 
N =  770 numpy 4.565 [Gflops] GPU 48.567 [Gflops] speedUp 10.638 
N =  880 numpy 5.452 [Gflops] GPU 44.444 [Gflops] speedUp 8.152 
N =  990 numpy 4.976 [Gflops] GPU 42.187 [Gflops] speedUp 8.478 
N = 1100 numpy 5.324 [Gflops] GPU 83.187 [Gflops] speedUp 15.625 
N = 1210 numpy 5.401 [Gflops] GPU 57.147 [Gflops] speedUp 10.581 
N = 1320 numpy 5.450 [Gflops] GPU 48.936 [Gflops] speedUp 8.979  

注意 - “Gflops”数字是以N ^ 3 /时间获得的,它确实包括将结果从GPU复制到主存储器所需的时间,但这次只占总时间的百分之几,特别是对于N> 1000

或许更多的画面是时间在secons:

N =  220 numpy 0.003 [s] GPU 0.001 [s] load 0.001 [s] speedUp 5.000 
N =  330 numpy 0.008 [s] GPU 0.001 [s] load 0.001 [s] speedUp 7.683 
N =  440 numpy 0.017 [s] GPU 0.002 [s] load 0.001 [s] speedUp 7.565 
N =  550 numpy 0.043 [s] GPU 0.004 [s] load 0.001 [s] speedUp 11.957 
N =  660 numpy 0.055 [s] GPU 0.006 [s] load 0.002 [s] speedUp 9.298 
N =  770 numpy 0.100 [s] GPU 0.009 [s] load 0.003 [s] speedUp 10.638 
N =  880 numpy 0.125 [s] GPU 0.010 [s] load 0.000 [s] speedUp 12.097 
N =  990 numpy 0.195 [s] GPU 0.015 [s] load 0.000 [s] speedUp 12.581 
N = 1100 numpy 0.250 [s] GPU 0.031 [s] load 0.000 [s] speedUp 8.065 
N = 1210 numpy 0.328 [s] GPU 0.031 [s] load 0.000 [s] speedUp 10.581 
N = 1320 numpy 0.422 [s] GPU 0.047 [s] load 0.000 [s] speedUp 8.979

我在想,也许可以通过使用获得一些速度提升 async_work_group_copy或甚至read_imageui将块复制到本地内存。 但是我不明白为什么当我使用基本相同的代码时我会有如此大的差异,因为那些人​​说他们有200倍的加速时间?????

2 个答案:

答案 0 :(得分:5)

如果不查看您的代码,请让我对您的基准做一些评论。让我们忽略numpy并比较Intel CPU与Nvidia和AMD GPU的最大SP FLOP / s和DP FLOP / s。

4 GHz的Intel 2600K可以做4 GHz *(8 AVX)*(2 ILP)*(4核)= 256 SP GFLOPs / s。对于DP,它是一半:128 DP GFLOPs / s。几周后出现的Haswell将使这两者都加倍。英特尔MKL库在GEMM中的效率优于80%。我自己的GEMM代码在我的i7-2700上得到了70%所以你用numpy引用的5 GFlops / s很小,并且不公平。

我不知道GTX 275的功能是什么,但我猜它会超过50 GFLOPs / s。

您引用的文章比较了AMD 7970.它们获得了848(90%效率)DP GFlops / s和2646(70%效率)SP GFlops / s。这比CPU的性能提高了10倍而不是200倍!

编辑: 你对FLOP的计算是错误的应该是2.0 * n ^ 3。这仍然是近似的,但它渐近正确。让我解释一下。

考虑3D点产品。它是x1 * x2 + y1 * y2 + z1 * z2。这是3次乘法和两次加法。因此,N维点积是n次乘法和(n-1)次加法。矩阵乘积相当于n×n点积,即n * n * n次乘法和n * n *(n-1)次加法。这大约是2.0 * n ^ 3 FLOPS。所以你应该把所有的Gflops / s数加倍。

编辑: 您可能想要考虑内核时间。自从我使用OpenCL以来已经有一段时间了但是使用C ++绑定我做了类似的事情

queue = cl::CommandQueue(context, devices[device], CL_QUEUE_PROFILING_ENABLE|CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE, &err);
//other code...run kernel

time_end = clevent.getProfilingInfo<CL_PROFILING_COMMAND_END>();  
time_start = clevent.getProfilingInfo<CL_PROFILING_COMMAND_START>();

答案 1 :(得分:1)

良好的GPU矩阵乘法不只是使用本地存储器,它将A,B和/或C的块存储在寄存器中(这导致更高的寄存器使用率和更低的占用率,但最终会更快)。这是因为GPU的寄存器数多于本地存储器(128-256KB,而NVIDIA为48KB),寄存器提供的带宽与ALU可以处理的一样多。