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

在V100上使用CUDA了解张量核的平铺,cuda,Cuda,我有一个从NVidia大量借用的玩具代码。我将随机生成的矩阵替换为从文件中读取矩阵的函数 使用此玩具代码并将两个大小为[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

我有一个从NVidia大量借用的玩具代码。我将随机生成的矩阵替换为从文件中读取矩阵的函数

使用此玩具代码并将两个大小为
[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.

的正确值非常敏感。几个介绍性要点:

  • 为便于理解,本代码旨在附带。该博客的最后一部分“CUDA9.0中Tensor内核的编程访问”一节对于理解该代码绝对有用

  • 如中所述,访问张量核性能的一种更简单的方法(特别是对于您似乎正在玩的基本矩阵乘法运算)是简单地使用CUBLAS函数,例如,它将在适当的情况下智能地使用张量核

  • 现在谈谈你的问题:

    有人能给我解释一下gridDim.x和gridDim.y的值和计算背后的逻辑吗

    这些值正在调整CUDA网格的大小,以满足所请求的矩阵乘法问题的大小。我们需要按层次来处理这个问题

    • 首先,tensor核心能力是在warp级别访问的。博客文章指出,“我们将采用的策略是让一个warp负责输出矩阵的单个16×16部分”,因此输出矩阵维度将驱动用于计算结果的CUDA网格维度。(还可以根据输出矩阵大小确定网格大小。更具体地说,他们为每个输出点分配一个线程。这里我们分配一个32线程扭曲来负责输出矩阵的一个16x16平铺。)代码使用
      WMMA_M
      (即多少行)和
      WMMA_N
      (即多少列)定义单个扭曲级别tensor core操作将处理的内容。这些值为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定义了输出矩阵的维数。这些在代码中分别表示为
      矩阵M
      矩阵N

    在上述基础上,我们可以说明在宿主代码中计算
    gridDim.x
    gridDim.y
    所需的算法

  • 我们必须在x维度中选择足够的线程,这样当除以32(x维度中扭曲的宽度)然后乘以
    WMMA_M
    (该扭曲的输出平铺宽度责任),我们就有足够的线程覆盖输出矩阵的宽度

  • 我们必须在y维度中选择足够的线程,这样当除以1(y维度中扭曲的“高度”)然后乘以
    WMMA\N
    (该扭曲的输出平铺高度责任),我们就有足够的线程覆盖输出矩阵的高度。请注意,在这种情况下,y维度中扭曲的“高度”绝对为1,因为代码要求块宽度维度为扭曲大小的整数倍。因此,任何扭曲都有一个恒定的
    threadIdx.y
    组件穿过扭曲

  • 要从上面1和2中确定的线程到每个维度中的块,我们必须用相应的threadblock维度缩放(除以)每个线程。因此,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输出平铺,其中