Parallel processing 矩阵乘法:在CUDA中合并全局内存访问后性能下降

Parallel processing 矩阵乘法:在CUDA中合并全局内存访问后性能下降,parallel-processing,cuda,gpu,matrix-multiplication,Parallel Processing,Cuda,Gpu,Matrix Multiplication,我最近开始使用CUDA与GPU合作。作为一个入门程序,我试图有效地实现一个简单的矩阵乘法 C=AB ,从原始矩阵乘法(每个线程为C中的一个元素加载A和B的所有元素)开始,平铺实现(线程协作从共享内存中的平铺中的A和B加载元素的平铺,以减少全局内存流量)提供了很好的加速。 但是,在平铺实现中,对全局内存的访问也不是按合并顺序进行的。因此,为了提高性能,最好转置矩阵B,然后相乘。下面是我的代码 #include<stdio.h> #include<stdlib.h> #inc

我最近开始使用CUDA与GPU合作。作为一个入门程序,我试图有效地实现一个简单的矩阵乘法

C=AB

,从原始矩阵乘法(每个线程为C中的一个元素加载A和B的所有元素)开始,平铺实现(线程协作从共享内存中的平铺中的A和B加载元素的平铺,以减少全局内存流量)提供了很好的加速。 但是,在平铺实现中,对全局内存的访问也不是按合并顺序进行的。因此,为了提高性能,最好转置矩阵B,然后相乘。下面是我的代码

#include<stdio.h>
#include<stdlib.h>
#include<cuda_runtime.h>

#include <time.h>

#include <sys/time.h>


void querydeviceprop();
void allocate_matrix(float *h_a, float *h_b, int matDim);
void verify(float *h_c, float *h_c_check, int matDim);
void print_matrix(float *ha, int m,int n);
void transpose_matrix(float *ha, int matDim);

void mat_mul();

#define TILE_WIDTH 16 //should be equal to numThread for tiling implementation

__global__ void MatrixMult_tiling(float *d_a,float *d_b,float *d_c, int dim){

    __shared__ float ta[TILE_WIDTH][TILE_WIDTH]; //to load one tile of A
    __shared__ float tb[TILE_WIDTH][TILE_WIDTH]; //to load one tile of A
    int bx,by,tx,ty,i,j;
    float res;
    int row, col;

    bx=blockIdx.x;  by=blockIdx.y;
    tx=threadIdx.x; ty=threadIdx.y;

    row=by*TILE_WIDTH+ty;
    col=bx*TILE_WIDTH+tx;

    res=0;
    for(i=0;i<dim/TILE_WIDTH;i++){
        //collaboratively load the elements. Each thread loads a single element.
        ta[ty][tx]=d_a[row*dim+TILE_WIDTH*i+tx];
        tb[ty][tx]=d_b[(ty+i*TILE_WIDTH)*dim+col];

        __syncthreads();
        for(j=0;j<TILE_WIDTH;j++){

            res=res+ta[ty][j]*tb[j][tx];
        }
        __syncthreads();
    }
    d_c[row*dim+col]=res;
}

__global__ void MatrixMult_tiling_coalesced(float *d_a,float *d_b,float *d_c, int dim){

    __shared__ float ta[TILE_WIDTH][TILE_WIDTH]; //to load one tile of A
    __shared__ float tb[TILE_WIDTH][TILE_WIDTH]; //to load one tile of A
    int bx,by,tx,ty,i,j;
    float res;
    int row, col;

    bx=blockIdx.x;  by=blockIdx.y;
    tx=threadIdx.x; ty=threadIdx.y;

    row=by*TILE_WIDTH+ty;
    col=bx*TILE_WIDTH+tx;

    res=0;
    for(i=0;i<dim/TILE_WIDTH;i++){
        //collaboratively load the elements. Each thread loads a single element.
        ta[ty][tx]=d_a[row*dim+TILE_WIDTH*i+tx];
        tb[ty][tx]=d_b[bx*TILE_WIDTH*dim + TILE_WIDTH*i+ty*dim+tx];
        __syncthreads();

        for(j=0;j<TILE_WIDTH;j++){
            res=res+ta[ty][j]*tb[tx][j];
        }
        __syncthreads();
    }
    d_c[row*dim+col]=res;
}

__global__ void MatrixMult_naive(float *d_a,float *d_b,float *d_c, int dim){

    int row,col,i;

    col=blockIdx.y*blockDim.y+threadIdx.y;
    row=blockIdx.x*blockDim.x+threadIdx.x;

    float res;

    if(row<dim && col<dim){
        res=0;
        for(i=0;i<dim;i++){
            res=res+(d_a[row*dim+i]*d_b[i*dim+col]);
        }
        d_c[row*dim+col]=res;
    }
}



int main(){
    mat_mul();
return 0;
}

void mat_mul(){

    cudaSetDevice(0);

    time_t t;
    cudaError_t err = cudaSuccess;
    srand((unsigned) time(&t));

    cudaEvent_t start, stop;
    float milliseconds=0;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);


    int matDim = 8192;

    float *h_a, *h_b, *h_c, *h_c_check;

    /*declare the host memories*/
    h_a=(float *)malloc(matDim*matDim*sizeof(float));
    h_b=(float *)malloc(matDim*matDim*sizeof(float));
    h_c=(float *)malloc(matDim*matDim*sizeof(float));
    h_c_check=(float *)malloc(matDim*matDim*sizeof(float));

    // Verify that allocations succeeded
    if (h_a == NULL || h_b == NULL || h_c == NULL || h_c_check ==NULL)
    {
    fprintf(stderr, "Failed to allocate host vectors!\n");
    exit(EXIT_FAILURE);
    }

    allocate_matrix(h_a,h_b,matDim); // allocate memory to hold the matrix

    //allocate cuda memory
        float *d_a=NULL;
        float *d_b=NULL;
        float *d_c=NULL;

        err=cudaMalloc((void **)&d_a, matDim*matDim*sizeof(float));
    if (err != cudaSuccess)
    {
        fprintf(stderr, "Failed to allocate device matrix A (error code %s)!\n", cudaGetErrorString(err));
        exit(EXIT_FAILURE);
    }
        err=cudaMalloc((void **)&d_b, matDim*matDim*sizeof(float));
    if (err != cudaSuccess)
    {
        fprintf(stderr, "Failed to allocate device matrix A (error code %s)!\n", cudaGetErrorString(err));
        exit(EXIT_FAILURE);
    }
        err=cudaMalloc((void **)&d_c, matDim*matDim*sizeof(float));
    if (err != cudaSuccess)
    {
        fprintf(stderr, "Failed to allocate device matrix A (error code %s)!\n", cudaGetErrorString(err));
        exit(EXIT_FAILURE);
    }

    printf("Matrix dimension is : %d\n",matDim);

    // Copy the host input matrix A and B in host memory to the device matrix in device memory
    //printf("Copy input data from the host memory to the CUDA device\n");

    cudaEventRecord(start);
    err = cudaMemcpy(d_a, h_a, matDim*matDim*sizeof(float), cudaMemcpyHostToDevice);
    cudaEventRecord(stop);
    cudaEventSynchronize(stop);
    cudaEventElapsedTime(&milliseconds, start, stop);
    //printf("GPU memcpy matrix A %10.10f ms\n",milliseconds);


    if (err != cudaSuccess)
    {
        fprintf(stderr, "Failed to copy vector A from host to device (error code %s)!\n", cudaGetErrorString(err));
        exit(EXIT_FAILURE);
    }

    cudaEventRecord(start);
    err = cudaMemcpy(d_b, h_b, matDim*matDim*sizeof(float), cudaMemcpyHostToDevice);
    cudaEventRecord(stop);
    cudaEventSynchronize(stop);
    cudaEventElapsedTime(&milliseconds, start, stop);
    //printf("GPU memcpy matrix B %10.10f ms\n",milliseconds);

    if (err != cudaSuccess)
    {
        fprintf(stderr, "Failed to copy vector B from host to device (error code %s)!\n", cudaGetErrorString(err));
        exit(EXIT_FAILURE);
    }


    /*constants for kernel launch*/
    int numThread=16; //number of threads per Block axis
    int numBlocks=matDim/numThread;
    if(matDim%numThread)
        numBlocks++;
    dim3 dimGrid(numBlocks,numBlocks);
    dim3 dimBlock(numThread,numThread);

    //-------------------------------------------------------------
           //-------transpose and copy to GPU-------
           transpose_matrix(h_b, matDim);//transpose first the b matrix
           err = cudaMemcpy(d_b, h_b, matDim*matDim*sizeof(float), cudaMemcpyHostToDevice);
           cudaEventSynchronize(stop);
           if (err != cudaSuccess){
                   fprintf(stderr, "Failed to copy vector A from host to device (error code %s)!\n", cudaGetErrorString(err));
                   exit(EXIT_FAILURE);
               }
           //--------transpose and copy ends-------------

           cudaEventRecord(start);
           MatrixMult_tiling_coalesced<<<dimGrid,dimBlock>>>(d_a, d_b, d_c, matDim);
           cudaEventRecord(stop);
           err = cudaGetLastError();

           if (err != cudaSuccess){
               fprintf(stderr, "Failed to launch vector matrix kernel (error code %s)!\n", cudaGetErrorString(err));
               exit(EXIT_FAILURE);
           }

           cudaEventSynchronize(stop);
           cudaEventElapsedTime(&milliseconds, start, stop);
           printf("GPU time tiled & coalesced %10.10f ms\n",milliseconds);

           //printf("Copy output data from the CUDA device to the host memory\n");
           cudaEventRecord(start);
           err = cudaMemcpy(h_c_check, d_c, matDim*matDim*sizeof(float), cudaMemcpyDeviceToHost);
           cudaEventRecord(stop);
           cudaEventSynchronize(stop);
           cudaEventElapsedTime(&milliseconds, start, stop);
           //printf("GPU memcpy time tiled & coalesced %10.10f ms\n",milliseconds);

           //------------transpose back the original B matrix----------------
           transpose_matrix(h_b, matDim);//transpose first the b matrix
           err = cudaMemcpy(d_b, h_b, matDim*matDim*sizeof(float), cudaMemcpyHostToDevice);
           cudaEventSynchronize(stop);
           if (err != cudaSuccess){
                   fprintf(stderr, "Failed to copy vector A from host to device (error code %s)!\n", cudaGetErrorString(err));
                   exit(EXIT_FAILURE);
               }

           //------------transpose back the original matrix ends-------------
//-------------------------------------------------------------

    cudaEventRecord(start);
    MatrixMult_tiling<<<dimGrid,dimBlock>>>(d_a, d_b, d_c, matDim);
    cudaEventRecord(stop);
    err = cudaGetLastError();

     if (err != cudaSuccess)
     {
         fprintf(stderr, "Failed to launch vector matrix kernel (error code %s)!\n", cudaGetErrorString(err));
         exit(EXIT_FAILURE);
     }

     cudaEventSynchronize(stop);
     cudaEventElapsedTime(&milliseconds, start, stop);
     printf("GPU time tiled %10.10f ms\n",milliseconds);

     //printf("Copy output data from the CUDA device to the host memory\n");
     cudaEventRecord(start);
     err = cudaMemcpy(h_c, d_c, matDim*matDim*sizeof(float), cudaMemcpyDeviceToHost);
     cudaEventRecord(stop);
     cudaEventSynchronize(stop);
     cudaEventElapsedTime(&milliseconds, start, stop);
     //printf("GPU memcpy time tiled %10.10f ms\n",milliseconds);


//-------------------------------------------------------------

    /*
    cudaEventRecord(start);
    MatrixMult_naive<<<dimGrid,dimBlock>>>(d_a, d_b, d_c, matDim);
    cudaEventRecord(stop);
    err = cudaGetLastError();

     if (err != cudaSuccess)
     {
         fprintf(stderr, "Failed to launch vector matrix kernel (error code %s)!\n", cudaGetErrorString(err));
         exit(EXIT_FAILURE);
     }

     cudaEventSynchronize(stop);
     cudaEventElapsedTime(&milliseconds, start, stop);
     printf("GPU time naive %10.10f ms\n",milliseconds);

     printf("Copy output data from the CUDA device to the host memory\n");
     cudaEventRecord(start);
     err = cudaMemcpy(h_c, d_c, matDim*matDim*sizeof(float), cudaMemcpyDeviceToHost);
     cudaEventRecord(stop);
     cudaEventSynchronize(stop);
     cudaEventElapsedTime(&milliseconds, start, stop);
     printf("GPU memcpy time naive %10.10f ms\n",milliseconds);
    */
//-------------------------------------------------------------

     verify(h_c, h_c_check, matDim);

     // Free device global memory
     err = cudaFree(d_a);

     if (err != cudaSuccess)
     {
         fprintf(stderr, "Failed to free device vector A (error code %s)!\n", cudaGetErrorString(err));
         exit(EXIT_FAILURE);
     }

     err = cudaFree(d_b);

     if (err != cudaSuccess)
     {
         fprintf(stderr, "Failed to free device vector B (error code %s)!\n", cudaGetErrorString(err));
         exit(EXIT_FAILURE);
     }

     err = cudaFree(d_c);

     if (err != cudaSuccess)
     {
         fprintf(stderr, "Failed to free device vector C (error code %s)!\n", cudaGetErrorString(err));
         exit(EXIT_FAILURE);
     }

     // Free host memory
     free(h_a);
     free(h_b);
     free(h_c);

     printf("Done\n");

}

void allocate_matrix(float *h_a, float *h_b, int matDim){

    int i,j;
    // Initialize the host input vectors
    for (i = 0; i < matDim; i++)
    {
        for(j=0;j< matDim;j++){
            h_a[i*matDim+j] = rand()%10;
            h_b[i*matDim+j] = rand()%10;
        }
    }

}

void print_matrix(float *ha, int m,int n){

    int i, j;
    for(i=0;i<m;i++){
        for(j=0;j<n;j++){
            printf("  %.1f ",ha[i*m+j]);
        }
    printf("\n");
    }
}

void transpose_matrix(float *h_a, int matDim){

    int i, j;
    int temp;
    for(i=0;i<matDim;i++)
    {
    for(j=0;j<i;j++)
    {
        temp=h_a[i*matDim+j];
        h_a[i*matDim+j]=h_a[j*matDim+i];
        h_a[j*matDim+i]=temp;
    }
    }
}


void verify(float *h_c, float *h_c_check, int matDim){

    int i,j;
    //check the code
     for (i = 0; i < matDim; i++)
     {
         for(j=0;j<matDim;j++){
         if (fabs(h_c[i*matDim+j] - h_c_check[i*matDim+j]) > 1e-5)
         {
             printf("cpu : %f , gpu : %f\t",h_c[i*matDim+j],h_c_check[i*matDim+j]);
             fprintf(stderr, "Result verification failed at element %d,%d !\n\n", i,j);
             exit(EXIT_FAILURE);
         }

         }
     }

     printf("Test PASSED\n");

}

从1500毫秒到1000毫秒,
MatrixMult\u tiling\u coalesced
函数的这一复杂性能。然而,函数
MatrixMult\u tiling
只需879毫秒。因此,合并例程仍然较慢。我不明白现在哪里出了问题

编辑2: 我意识到在edit1中,我刚刚将bank冲突问题从elementwise乘法转移到element loading部分。代码中的错误更改没有银行冲突

    tb[tx][ty]=d_b[bx*TILE_WIDTH*dim + TILE_WIDTH*i+ty*dim+tx];

但它仍然比
矩阵结果平铺
函数稍慢。
MatrixMult\u tiling\u coalesced
函数需要982毫秒,而
MatrixMult\u tiling
函数需要870毫秒。如果不是更快,它至少应该类似于矩阵结果平铺

最终编辑:

编辑2将不会产生正确的结果。因此,编辑为1的代码将是最佳的。转置被乘数矩阵之一可能不是一个好主意-(


谢谢大家的帮助。

B
当然不是我想在
C=AB
中转置的矩阵。但这既不在这里也不在那里

我不知道你为什么认为:

在平铺实现中,对全局内存的访问也不是按合并顺序进行的

在您的
矩阵结果平铺中,我没有看到任何导致未平衡访问的代码行

为了确保我们不会被术语绊倒,“合并”或“未合并”是我们应用于访问全局内存(非共享内存)的访问模式的术语。您的全局内存访问模式在平铺内核中的以下行中:

    ta[ty][tx]=d_a[row*dim+TILE_WIDTH*i+tx];
    tb[ty][tx]=d_b[(ty+i*TILE_WIDTH)*dim+col];
    ...
    d_c[row*dim+col]=res;
在生成的
d_a
d_b
d_c
索引中,如果执行替换,您会发现
threadIdx.x
变量在所有索引中都存在,并且不会乘以任何值、常量或其他值。因此ese模式将(很好地)结合在一起

如果有人能指出原因,我将不胜感激

在共享内存方面,您做了一些不好的事情

在平铺内核中,乘法运算如下所示:

        res=res+ta[ty][j]*tb[j][tx];
在这种情况下:

   ta[ty][j]
  tb[j][tx]
  ta[ty][j]
  tb[tx][j]
在这种情况下,warp中的所有线程(具有线性增加的
tx
值,但具有相同的
ty
值)都在共享内存中读取相同的位置。这是一种“最佳”访问模式-它不存在任何银行冲突,并将在尽可能短的时间内得到服务

在这种情况下:

   ta[ty][j]
  tb[j][tx]
  ta[ty][j]
  tb[tx][j]
我们遇到的情况是,warp中的相邻线程正在读取共享内存中的相邻位置。这也是一种“最佳”的、无银行冲突的模式,将在尽可能短的时间内提供服务

但是,在合并的
MatrixMult\u tiling\u内核中,相应的乘法运算是:

  res=res+ta[ty][j]*tb[tx][j];
同样,在这种情况下:

   ta[ty][j]
  tb[j][tx]
  ta[ty][j]
  tb[tx][j]
我们有一个共享内存“广播”模式(从同一位置读取的扭曲中的所有线程),这是最佳且快速的。但在这种情况下:

   ta[ty][j]
  tb[j][tx]
  ta[ty][j]
  tb[tx][j]
实际上,您已经创建了对共享内存的columnar访问。这是共享内存最糟糕的访问模式,它将导致32路序列化(对于16x16线程块,可能是16路序列化)加载过程中,性能肯定更差。为什么?请记住,对于给定的加载,
j
在整个扭曲中是常数,
tx
在整个扭曲中线性增加。因此,假设在特定循环迭代中,
j
为1。扭曲0中的线程将读取:

tb[0][1], tb[1][1], tb[2][1], tb[3][1], ...
这些位置都属于共享内存的特定“列”,也就是说,它们都属于同一个共享内存库。这是共享内存的最坏模式

为完整起见,我声明
MatrixMult\u tiling\u联合的
内核中的所有全局内存访问模式也联合在一起:

    ta[ty][tx]=d_a[row*dim+TILE_WIDTH*i+tx];
    tb[ty][tx]=d_b[bx*TILE_WIDTH*dim + TILE_WIDTH*i+ty*dim+tx];
    ...
    d_c[row*dim+col]=res;
因此,您的两个内核实现之间的全局内存访问模式/活动/效率应该没有重大差异


作为一个旁注,我认为这都是一个学习练习。如果你对GPU上的高性能矩阵乘法感兴趣,我鼓励你考虑使用.

<代码> B<代码>当然不是我在代码> C= Ab中转置的矩阵。但这既不在这里也不存在。

我不知道你为什么认为:

在平铺实现中,对全局内存的访问也不是按合并顺序进行的

在您的
矩阵结果平铺中,我没有看到任何导致未平衡访问的代码行

为了确保我们不会被术语绊倒,“合并”或“未合并”是我们应用于访问全局内存(非共享内存)的访问模式的术语。您的全局内存访问模式在平铺内核中的以下行中:

    ta[ty][tx]=d_a[row*dim+TILE_WIDTH*i+tx];
    tb[ty][tx]=d_b[(ty+i*TILE_WIDTH)*dim+col];
    ...
    d_c[row*dim+col]=res;
全局内存的所有模式都是不可恢复的。在生成的
d_a
d_b
d_c
索引中,如果