在V100上使用CUDA了解张量核心的平铺

时间:2019-01-04 20:16:00

标签: cuda

我有一个玩具代码,大量借鉴了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.xgridDim.y的值和计算背后的逻辑吗?张量核心的正确用法似乎对于为gridDim.*使用正确的值非常敏感。

1 个答案:

答案 0 :(得分:3)

几个介绍点:

  1. 为便于理解,此代码旨在随附this blog article。该博客的最后一部分,“在CUDA 9.0中以编程方式访问Tensor核心”部分对于理解此代码绝对有用。

  2. 正如readme for that code中所述,访问张量核性能的一种更简单的方法(尤其是对于您似乎正在使用的基本矩阵乘法运算)只是使用CUBLAS函数,例如cublasGemmEx,它将在适当的情况下智能地使用张量核心。

现在您的问题:

  

有人可以向我解释gridDim.xgridDim.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的输出点。请注意,主机代码中的这些blockDimgridDim变量在逻辑上与CUDA中的blockDimgridDim内置变量在逻辑上是分开的(尽管最终在数值上相同)设备代码。

  • 鉴于上述,典型的BLAS GEMM操作的m,n和k参数在此处具有相同的含义。 m是左侧输入矩阵的行数。 n是右侧输入矩阵的列数。 k是左矩阵的列数,必须与右矩阵的行数匹配。因此,m,n定义了输出矩阵的维数。这些在代码中分别表示为MATRIX_MMATRIX_N

在完成上述基础工作之后,我们可以在主机代码中陈述计算gridDim.xgridDim.y所需的算法。

  1. 我们必须在x维度上选择足够的线程,以便将其除以32(x维度上的经线的宽度),然后乘以WMMA_M(该值的输出图块宽度责任) warp),我们有足够的线程来覆盖输出矩阵的宽度。

  2. 我们必须在y维度上选择足够的线程,以便在除以1(y维度上的经线的“高度”)后再乘以WMMA_N(输出瓦片高度责任) ),我们有足够的线程来覆盖输出矩阵的高度。请注意,在这种情况下,y方向上的经线的“高度”肯定为1,因为代码要求块宽度尺寸为经线尺寸的整数倍。因此,任何变形在整个变形中都具有恒定的threadIdx.y分量。

  3. 要从上面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的输出图块。