我正在用《 大规模并行处理器编程》一书学习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;
}
}
答案 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
个数量而言)。