我有一个玩具代码,大量借鉴了NVidia的simpleTensorCoreGEMM.cu。我换了一个随机生成的矩阵,换成一个从文件中读取矩阵的函数。
使用此玩具代码并将两个大小为[2000 x 10000] * [10000 x 3008]
的矩阵相乘,效果很好。输出符合预期。
当我尝试更大的乘法[20000 x 10000] * [10000 x 30000]
时,输出严重错误,并且2/3的行为0。
我坚信这是我不了解代码行的结果:
// blockDim.x must be a multple of warpSize
// 128x4 means we have 16 warps and a block computes a 64x64 output tile
blockDim.x = 128;
blockDim.y = 4;
gridDim.x = (MATRIX_M + (WMMA_M * blockDim.x / 32 - 1)) / (WMMA_M * blockDim.x / 32);
gridDim.y = (MATRIX_N + WMMA_N * blockDim.y - 1) / (WMMA_N * blockDim.y);
即使不是我的错误的根源,我仍然应该了解它在做什么。我了解设置blockDim.*
每条扭曲有32个线程,128 * 4/32 = 16个扭曲。
问题:有人可以向我解释gridDim.x
和gridDim.y
的值和计算背后的逻辑吗?张量核心的正确用法似乎对于为gridDim.*
使用正确的值非常敏感。
答案 0 :(得分:3)
几个介绍点:
为便于理解,此代码旨在随附this blog article。该博客的最后一部分,“在CUDA 9.0中以编程方式访问Tensor核心”部分对于理解此代码绝对有用。
正如readme for that code中所述,访问张量核性能的一种更简单的方法(尤其是对于您似乎正在使用的基本矩阵乘法运算)只是使用CUBLAS函数,例如cublasGemmEx,它将在适当的情况下智能地使用张量核心。
现在您的问题:
有人可以向我解释
gridDim.x
和gridDim.y
的值和计算背后的逻辑吗?
这些值将CUDA网格的大小确定为足以满足所要求的矩阵乘法问题的大小。我们需要对此进行分层处理。
首先,在扭曲级别访问张量核心功能。博客文章指出“我们将采用的策略是使单个扭曲负责输出矩阵的单个16×16截面”,因此,输出矩阵的尺寸将驱动用于计算结果的CUDA网格的尺寸。 (Typical naive realizations of matrix multiply还根据输出矩阵的大小确定网格的大小。更具体地说,它们为每个输出点分配一个线程。在这里,我们分配一个32线程扭曲来负责一个输出矩阵的16x16瓦片。)代码使用WMMA_M
(即多少行)和WMMA_N
(即多少列)来定义单个扭曲级张量核心操作将处理的内容。这些值为16,这会促使每个扭曲选择在输出中使用16x16瓦片。
与CUDA中的情况一样,块的尺寸可能是任意的,但它们确实会经常影响网格的大小(变量)。扭曲存在于块级别,并且块中的扭曲数量有效地决定了每个块将处理输出矩阵中的16x16瓦片数量。在这种特定情况下,代码选择的块尺寸为128(blockDim.x
)×4(blockDim.y
)。这恰好是4个“宽”乘以4个“高”乘,因此每个块在输出中处理4x4的图块集,这意味着每个块负责64x64的输出点。请注意,主机代码中的这些blockDim
和gridDim
变量在逻辑上与CUDA中的blockDim
和gridDim
内置变量在逻辑上是分开的(尽管最终在数值上相同)设备代码。
鉴于上述,典型的BLAS GEMM操作的m,n和k参数在此处具有相同的含义。 m是左侧输入矩阵的行数。 n是右侧输入矩阵的列数。 k是左矩阵的列数,必须与右矩阵的行数匹配。因此,m,n定义了输出矩阵的维数。这些在代码中分别表示为MATRIX_M
和MATRIX_N
。
在完成上述基础工作之后,我们可以在主机代码中陈述计算gridDim.x
和gridDim.y
所需的算法。
我们必须在x维度上选择足够的线程,以便将其除以32(x维度上的经线的宽度),然后乘以WMMA_M
(该值的输出图块宽度责任) warp),我们有足够的线程来覆盖输出矩阵的宽度。
我们必须在y维度上选择足够的线程,以便在除以1(y维度上的经线的“高度”)后再乘以WMMA_N
(输出瓦片高度责任) ),我们有足够的线程来覆盖输出矩阵的高度。请注意,在这种情况下,y方向上的经线的“高度”肯定为1,因为代码要求块宽度尺寸为经线尺寸的整数倍。因此,任何变形在整个变形中都具有恒定的threadIdx.y
分量。
要从上面1和2中确定的线程转到每个维度中的块,我们必须将每个线程按相应的线程块维度进行缩放(划分)。因此,x中的网格线程尺寸必须除以blockDim.x
(在主机代码中),并按上面的1进行缩放,以获取x中的总网格尺寸(块数)。此除法运算是通常的CUDA“向上舍入”整数除法运算,用于缩放等于或大于所需线程的块数,以解决矩阵大小不能被块大小均匀除的问题。
将所有内容放在一起,我们有:
gridDim.x = (MATRIX_M + (WMMA_M * blockDim.x / 32 - 1)) / (WMMA_M * blockDim.x / 32);
^ ^ ^ ^
| | | divided by the block size scaled for the
| | | portion of the output matrix it covers.
| | rounded up
| the matrix size
The grid in blocks is
同样,对于y网格尺寸。唯一真正的区别是x的32个线程(扭曲宽度)负责16x16的输出图块,而y的单线程中(扭曲“高度”)负责16x16的输出图块。