如何避免矩阵乘法CUDA内核中的非强制访问?

时间:2019-01-02 09:06:54

标签: parallel-processing cuda

我正在用《 大规模并行处理器编程》一书学习CUDA。第5章中的一个实践问题使我感到困惑:

  

对于超出值可能范围的图块矩阵乘法   BLOCK_SIZE,对于什么BLOCK_SIZE值,内核将完全   避免对全局内存进行非强制访问? (您只需要考虑正方形块)

据我了解,BLOCK_SIZE对内存合并没有什么帮助。只要单个扭曲中的线程访问连续元素,我们就将具有合并访问。我不知道内核在何处可以对全局内存进行非强制访问。你们有什么提示吗?

这是内核的源代码:

#define COMMON_WIDTH 512
#define ROW_LEFT 500 
#define COL_RIGHT 250
#define K 1000
#define TILE_WIDTH 32
__device__ int D_ROW_LEFT = ROW_LEFT;
__device__ int D_COL_RIGHT = COL_RIGHT;
__device__ int D_K = K;
.....
__global__
void MatrixMatrixMultTiled(float *matrixLeft, float *matrixRight, float *output){
    __shared__  float sMatrixLeft[TILE_WIDTH][TILE_WIDTH];
    __shared__  float sMatrixRight[TILE_WIDTH][TILE_WIDTH];  
   int bx = blockIdx.x; int by = blockIdx.y;
   int tx = threadIdx.x; int ty = threadIdx.y;
   int col = bx * TILE_WIDTH + tx;
   int row = by * TILE_WIDTH + ty;
   float value = 0;
   for (int i = 0; i < ceil(D_K/(float)TILE_WIDTH); ++i){
       if (row < D_ROW_LEFT && row * D_K + i * TILE_WIDTH  +tx < D_K){
        sMatrixLeft[ty][tx]  = matrixLeft[row * D_K + i * TILE_WIDTH  +tx];
       }
       if (col < D_COL_RIGHT && (ty + i * TILE_WIDTH) * D_COL_RIGHT  + col < D_K ){
        sMatrixRight[ty][tx] = matrixRight[(ty + i * TILE_WIDTH) * D_COL_RIGHT  + col];
       }
       __syncthreads();
       for (int j = 0; j < TILE_WIDTH; j++){
           value += sMatrixLeft[ty][j] * sMatrixRight[j][tx]; 
       }
       __syncthreads();
   }
   if (row < D_ROW_LEFT && col < D_COL_RIGHT ){
        output[row * D_COL_RIGHT + col] = value;
       }
}

1 个答案:

答案 0 :(得分:1)

您的问题是不完整的,因为您发布的代码没有引用BLOCK_SIZE,并且至少与本书​​中提出的问题非常相关。更一般地,没有启动配置的内核问题通常是不完整的,因为启动配置通常与内核的正确性和行为相关。

目前我还没有重新阅读本书的这一部分。但是,我假设内核启动配置包括一个类似于以下内容的块尺寸:(您的问题中没有此信息,但我认为应该将其包含在一个明智的问题中)

dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);
dim3 dimGrid(...,...);

我将假设内核启动是由类似的东西给出的:

MatrixMatrixMultTiled<<<dimGrid, dimBlock>>>(...);

您的声明:“只要单个warp中的线程访问连续的元素,我们就可以合并访问。”是一个合理的工作定义。让我们证明,鉴于上述假设弥补了您不完整问题中的空白,对于BLOCK_SIZE的某些选择,这是违反的。

强制访问是一个仅适用于全局内存访问的术语。因此,我们将忽略对共享内存的访问。在此讨论中,我们还将忽略对__device__之类的D_ROW_LEFT变量的访问。 (对这些变量的访问似乎是 uniform 。我们可以质疑这是否构成合并访问。我的主张是它确实构成了合并访问,但我们无需在此处拆包。)因此我们只剩下3个“访问”点:

matrixLeft[row * D_K + i * TILE_WIDTH  +tx];
matrixRight[(ty + i * TILE_WIDTH) * D_COL_RIGHT  + col];
output[row * D_COL_RIGHT + col]

现在,举一个例子,假设BLOCK_SIZE是16。上述访问点中的任何一个都会违反您的声明“单个扭曲访问连续元素中的线程”吗?

让我们从块(0,0)开始。因此,row等于threadIdx.y,而col等于threadIdx.x。让我们考虑该块中的第一个扭曲。因此,该扭曲中的前16个线程的threadIdx.y值将为0,并且它们的threadIdx.x值将从0..15开始增加。同样,该扭曲中的后16个线程的threadIdx.y值将为1,并且它们的threadIdx.x值将从0..15开始递增。

现在,让我们计算通过扭曲为上述第一个访问点生成的实际索引。假设我们处于第一次循环迭代中,那么i为零。因此:

matrixLeft[row * D_K + i * TILE_WIDTH  +tx];

简化为:

matrixLeft[threadIdx.y * D_K + threadIdx.x];

D_K只是K变量的设备副本,它是1000。现在,让我们在选定块(0,0 ):

warp lane:    0  1  2  3  4  5  6  .. 15     16   17   18 .. 31
threadIdx.x   0  1  2  3  4  5  6     15      0    1    2    15
threadIdx.y   0  0  0  0  0  0  0      0      1    1    1     1
index:        0  1  2  3  4  5  6     15   1000 1001 1002  1015

因此,此处生成的索引模式显示了经线中第16和第17线程之间的不连续,并且访问模式不符合您先前声明的条件:

“单个扭曲访问连续元素内的线程”

并且在这种情况下,我们没有合并的访问权限(至少对于float个数量而言)。