NVIDIA 深度学习性能

矩阵乘法背景用户指南

摘要

本指南介绍了矩阵乘法及其在许多深度学习操作中的应用。此处描述的趋势构成了全连接层、卷积层和循环层等性能趋势的基础。

GEMM(通用矩阵乘法)是神经网络中许多操作的基本构建块,例如全连接层、循环层(如 RNN、LSTM 或 GRU)和卷积层。在本指南中,我们描述了理解此类层性能的通用 GEMM 性能基础。

GEMM 定义为以下运算 C   =   α AB + β C ,其中 AB 是矩阵输入,α 和 β 是标量输入,C 是预先存在的矩阵,将被输出覆盖。纯矩阵乘积 AB 是 α 等于 1 且 β 等于 0 的 GEMM。例如,在全连接层的前向传递中,权重矩阵将是参数 A,传入激活将是参数 B,α 和 β 通常分别为 1 和 0。在某些情况下,β 可以为 1,例如,如果我们正在将跳跃连接的加法与线性运算结合起来。

按照各种线性代数库(如 BLAS)的约定,我们将说矩阵 A 是一个 M x K 矩阵,这意味着它有 M 行和 K 列。类似地,B 和 C 将被假定为 K x NM x N 矩阵,分别。

A 和 B 的乘积有 M x N 个值,每个值都是 K 元素向量的点积。因此,总共需要 M * N * K 个融合乘加 (FMA) 来计算乘积。每个 FMA 是 2 个操作,一个乘法和一个加法,因此总共需要 2 * M * N * K 次 FLOPS。为简单起见,我们暂时忽略 α 和 β 参数;只要 K 足够大,它们对算术强度的贡献可以忽略不计。

为了估计特定的矩阵乘法是受数学运算限制还是受内存限制,我们将其算术强度与 GPU 的 ops:byte 比率进行比较,如理解性能中所述。假设 NVIDIA® V100 GPU 和 Tensor Core 在 FP16 输入上进行 FP32 累积运算,如果数据从 GPU 的内存加载,则 FLOPS:B 比率为 138.9。

算术   强度   = 数量     FLOPS 数量     字节   访问   = 2   ·   ( M   ·   N ·   K ) 2   ·   ( M   ·   K + N   ·   K + M   ·   N )   = M   ·   N   ·   K M   ·   K + N   ·   K + M   ·   N

例如,让我们考虑一个 M x N x K = 8192 x 128 x 8192 GEMM。对于这个特定情况,算术强度为 124.1 FLOPS/B,低于 V100 的 138.9 FLOPS:B,因此此操作将受内存限制。如果我们将 GEMM 大小增加到 8192 x 8192 x 8192,算术强度增加到 2730,远高于 V100 的 FLOPS:B,因此该操作受数学运算限制。特别是,从该分析可以得出,矩阵-向量乘积(通用矩阵-向量乘积或 GEMV),其中 M=1N=1,始终受内存限制;它们的算术强度小于 1。

值得记住的是,将算术强度与 ops:byte 比率进行比较只是一个简化的经验法则,并未考虑实现此计算的许多实际方面(例如,非算法指令,如指针算术,或 GPU 片上内存层次结构的贡献)。

2.1. GPU 实现

GPU 通过将输出矩阵划分为瓦片来实现 GEMM,然后将这些瓦片分配给线程块。

在本指南中,瓦片大小通常指的是这些瓦片的尺寸(图 1 中的 Mtile x Ntile)。每个线程块通过在 K 维度中以瓦片步进,加载来自 A 和 B 矩阵的所需值,并将它们相乘并累加到输出来计算其输出瓦片。

图 1. 用于 GEMM 的平铺外积方法

tiled-outer-prod.svg

2.2. Tensor Core 要求

正如我们在GPU 架构基础知识中讨论的那样,最新的 NVIDIA GPU 引入了 Tensor Core,以最大限度地提高张量乘法的速度。使用 Tensor Core 的要求取决于 NVIDIA 库版本。当等效矩阵维度 M、N 和 K 对齐到 16 字节(或 A100 上的 128 字节)的倍数时,性能会更好。对于 11.0 之前的 NVIDIA cuBLAS 版本或 7.6.3 之前的 NVIDIA cuDNN 版本,这是使用 Tensor Core 的要求;从 cuBLAS 11.0 和 cuDNN 7.6.3 开始,无论如何都可以使用 Tensor Core,但当矩阵维度是 16 字节的倍数时,效率更高。例如,当使用 FP16 数据时,每个 FP16 元素由 2 个字节表示,因此矩阵维度需要是 8 个元素的倍数才能获得最佳效率(或 A100 上的 64 个元素)。

表 1. 适用于某些常见数据精度的 cuBLAS 或 cuDNN 版本的 Tensor Core 要求。这些要求适用于矩阵维度 M、N 和 K。
Tensor Core 可用于...

cuBLAS 版本 < 11.0

cuDNN 版本 < 7.6.3

cuBLAS 版本 ≥ 11.0

cuDNN 版本 ≥ 7.6.3

INT8 16 的倍数 始终可以使用,但最有效的是 16 的倍数;在 A100 上,是 128 的倍数。
FP16 8 的倍数 始终可以使用,但最有效的是 8 的倍数;在 A100 上,是 64 的倍数。
TF32 不适用 始终可以使用,但最有效的是 4 的倍数;在 A100 上,是 32 的倍数。
FP64 不适用 始终可以使用,但最有效的是 2 的倍数;在 A100 上,是 16 的倍数。

实际上,要求更加宽松 - 只需要内存中最快变化的维度遵守此规则 - 但最简单的方法是将所有三个维度都视为相同的方式。对所有维度遵循这些对齐方式可确保 Tensor Core 将被启用并高效运行。这种效果可以在图 5 中看到 - 当 K 可被 8 整除时,计算速度最快(持续时间最短)。当 K 不可被 8 整除时,从 cuBLAS 10.2 切换到 cuBLAS 11.0 允许使用 Tensor Core,并带来 2-4 倍的加速。还值得注意的是,使用 cuBLAS 11.0,在不可被 8 整除的 K 值中,偶数值仍然比奇数值产生更快的计算速度。我们建议选择矩阵维度为 16 字节的倍数(对于 FP16,如表 1 中所示,为 8);如果不可能,选择较小的 2 的幂(如 8 或 4 字节)的倍数通常仍然有助于 cuBLAS 11.0 及更高版本的性能。在 A100 上,选择高达 128 字节(对于 FP16,为 64)的较大 2 的幂的倍数可以进一步提高效率。

图 2. 使用 (a) cuBLAS 10.1 和 (b) cuBLAS 11.0 的 GEMM 执行时间比较,两者都使用 FP16 数据。当 K 可被 8 整除时,计算速度最快(持续时间最短)。“NN”表示 A 和 B 矩阵都以非转置方式访问。NVIDIA V100-DGXS-16GB GPU。

gemm-speedup.svg

2.3. cuBLAS 中的典型瓦片尺寸和性能

cuBLAS 库包含 NVIDIA 优化的 GPU GEMM 实现(有关文档,请参阅此处)。

虽然有多种平铺策略可用,但较大的瓦片具有更多的数据重用,使其能够使用更少的带宽并且比小瓦片更有效。另一方面,对于给定大小的问题,使用较大的瓦片将生成更少的瓦片并行运行,这可能会导致 GPU 利用率不足。当 TensorFlow 或 PyTorch 等框架使用特定的 GEMM 维度调用 cuBLAS 时,cuBLAS 内部的启发式方法用于选择预期性能最佳的平铺选项之一。或者,某些框架提供“基准测试”模式,在训练之前,他们会对所有实现选择进行计时并选择最快的实现(这构成了每个训练会话一次的开销)。

瓦片效率和瓦片并行性之间的这种权衡表明,GEMM 越大,这种权衡就越不重要:在某个时候,GEMM 有足够的工作来使用最大的可用瓦片并且仍然填满 GPU。相反,如果 GEMM 太小,瓦片效率或瓦片并行性的降低都可能阻止 GPU 以峰值数学利用率运行。图 3图 4 说明了这种总体趋势;较大的 GEMM 实现更高的吞吐量。

图 3. 随着 GEMM 的 M-N footprint 增加,性能会提高。持续时间也会增加,但不如 M-N 维度本身增加得快;有时可以增加 GEMM 大小(使用更多权重),而持续时间仅略有增加。NVIDIA A100-SXM4-80GB,CUDA 11.2,cuBLAS 11.4。

mn-footprint.svg

图 4. 即使当 M=N 相对较大时,性能也会随着 K 维度的增加而提高,因为当点积更长时,计算的设置和拆卸开销会更好地分摊。NVIDIA A100-SXM4-80GB,CUDA 11.2,cuBLAS 11.4。

k-dim.svg

对于 cuBLAS GEMM,线程块瓦片大小通常但不一定使用 2 的幂维度。不同的瓦片大小可能用于不同的用例,但作为起点,以下瓦片可用:

  • 256x128 和 128x256(最有效)
  • 128x128
  • 256x64 和 64x256
  • 128x64 和 64x128
  • 64x64(效率最低)

图 5 显示了其中一些瓦片大小之间效率差异的示例:

图 5. 较大的瓦片运行效率更高。基于 256x128 的 GEMM 每个 SM 运行一个瓦片,其他 GEMM 根据各自的瓦片大小生成更多瓦片。NVIDIA A100-SXM4-80GB,CUDA 11.2,cuBLAS 11.4。

larger-tiles.svg

该图表显示了具有不同瓦片大小的 MxNxK = 6912x2048x4096 GEMM 的性能。它表明,使用较小瓦片增加的瓦片并行性(64x64 实现的并行性比 256x128 多 8 倍)是以显着的效率成本为代价的。在实践中,对于足够大的 GEMM 以便用较大的瓦片获得足够的并行性,cuBLAS 将避免使用小瓦片,并且仅当运行比此示例中 GEMM 小得多的 GEMM 时才会求助于较小的瓦片。作为旁注,NVIDIA 库还具有沿 K 维度“平铺”的能力,以防 M 和 N 都很小但 K 很大。由于 K 是点积的方向,因此 K 中的平铺需要在最后进行归约,这会限制可实现的性能。为简单起见,本指南的大部分内容假设没有 K 平铺。

GPU 执行模型中所述,GPU 函数通过启动多个线程块来执行,每个线程块具有相同数量的线程。这会对执行效率产生两个潜在影响 - 瓦片量化和波量化。

3.1. 瓦片量化

当矩阵维度不能被线程块瓦片大小整除时,会发生瓦片量化。

线程块瓦片的数量足够大,以确保覆盖所有输出元素,但是,如图图 6所示,一些瓦片的工作量非常小,该图假设使用 128x128 瓦片和两个矩阵维度。

图 6. 使用 128x128 线程块瓦片进行平铺的示例。(a) 最佳情况 - 矩阵维度可被瓦片维度整除 (b) 最坏情况 - 瓦片量化导致启动了六个线程块,其中两个线程块浪费了大部分工作。

tiling-ex.svg

虽然库确保任何瓦片都不会执行无效的内存访问,但所有瓦片都将执行相同数量的数学运算。因此,由于瓦片量化,图 6 (b) 中的情况执行的算术运算量是图 6 (a) 中的 1.5 倍,尽管算法上仅需要多 0.39% 的运算。正如这表明的那样,当输出矩阵维度可被瓦片维度整除时,可以实现最高的利用率。

对于此效应的另一个示例,让我们考虑针对 N 的各种选择的 GEMM,其中 M = 27648K = 4096 和使用 256x128 瓦片的库函数。随着 N 从 136 增加到 256(以 8 为增量),Tensor Core 加速的 GEMM 始终运行相同数量的瓦片,这意味着 N 维度始终分为 2 个瓦片。虽然瓦片的数量保持不变,但包含有用数据的瓦片比例以及执行的有用 FLOPS 的数量随着 N 的增加而增加,这反映在下面的图 7中的 GFLOPS 中。请注意,吞吐量在 N = 128(其中每行的单个瓦片都充满了有用数据)和 N = 136(其中每行添加了第二个瓦片,但仅包含 8/128 = 6.25% 的有用数据)之间显着降低。另请注意,每当瓦片数量恒定时,持续时间如何保持恒定。

图 7. 瓦片量化效应对 (a) 实现的 FLOPS 吞吐量和 (b) 经过时间以及 (c) 创建的瓦片数量的影响。使用强制在 MxN 输出矩阵上使用 256x128 瓦片的函数测量。在实践中,cuBLAS 会选择更窄的瓦片(例如,64 宽)以减少量化效应。NVIDIA A100-SXM4-80GB,CUDA 11.2,cuBLAS 11.4。

tile-quant.svg

3.2. 波量化

虽然瓦片量化意味着问题大小量化为每个瓦片的大小,但还有第二个量化效应,其中瓦片总数量量化为 GPU 上的多处理器数量:波量化。

让我们考虑与之前的示例相关的示例,再次改变 N,并且 K = 4096,但使用较小的 M = 2304。NVIDIA A100 GPU 有 108 个 SM;在 256x128 线程块瓦片的特定情况下,它可以在每个 SM 上执行一个线程块,从而导致波大小为 108 个瓦片,可以同时执行。因此,当瓦片数量是 108 的整数倍或略低于 108 时,GPU 利用率最高。

M 维度将始终划分为每列 2304/256 = 9 个瓦片。当 N = 1536 时,N 维度划分为每行 1536/128 = 12 个瓦片,总共创建 9*12 = 108 个瓦片,组成一个完整的波。当 1536 < N <= 1664 时,每行创建一个额外的瓦片,总共 9*13 = 117 个瓦片,导致一个完整的波和一个仅包含 9 个瓦片的“尾”波。在本例中,尾波的执行时间与完整的 108 瓦片波几乎相同,但在该时间内仅使用了 A100 的 SM 的 9/108 = 8.33%。因此,GFLOPS 大约减半,持续时间从 N = 1536N = 1544 大约翻倍(图 8)。在 N = 3072N = 4608N = 6144 之后也可以看到类似的跳跃,它们也映射到整数个完整波。

图 8. 波量化在 (a) 实现的 FLOPS 吞吐量和 (b) 经过时间以及 (c) 创建的瓦片数量方面的影响。使用在 MxN 输出矩阵上使用 256x128 瓦片的函数测量。请注意,当量化效应发生在瓦片数量超过 108 的倍数时。NVIDIA A100-SXM4-80GB,CUDA 11.2,cuBLAS 11.4。

wave-quant-effects.svg

值得注意的是,波量化的吞吐量和持续时间图与瓦片量化的吞吐量和持续时间图非常相似,只是水平轴上的比例不同。由于这两种现象都是量化效应,因此这是预期的。区别在于量化发生的位置:瓦片量化意味着工作量量化为瓦片的大小,而波量化意味着工作量量化为 GPU 的大小。图 7 (c) 和图 8 (c) 在瓦片和波量化说明中都显示了这种差异。

声明

本文档仅供参考,不得视为对产品的特定功能、条件或质量的保证。NVIDIA Corporation(“NVIDIA”)对本文档中包含的信息的准确性或完整性不作任何明示或暗示的陈述或保证,并且对本文档中包含的任何错误不承担任何责任。NVIDIA 对使用此类信息或因使用此类信息而可能导致的任何专利或第三方其他权利的侵权行为的后果或使用不承担任何责任。本文档不构成对开发、发布或交付任何材料(如下定义)、代码或功能的承诺。

NVIDIA 保留随时修改、增强、改进和对本文档进行任何其他更改的权利,恕不另行通知。

客户在下订单前应获取最新的相关信息,并应验证此类信息是否为最新且完整。

除非 NVIDIA 和客户的授权代表签署的单独销售协议(“销售条款”)另有约定,否则 NVIDIA 产品根据订单确认时提供的 NVIDIA 标准销售条款和条件进行销售。NVIDIA 特此明确反对将任何客户通用条款和条件应用于购买本文档中引用的 NVIDIA 产品。本文档不直接或间接地形成任何合同义务。

NVIDIA 产品并非设计、授权或保证适用于医疗、军事、航空、航天或生命支持设备,也不适用于 NVIDIA 产品的故障或故障可能合理预期会导致人身伤害、死亡或财产或环境损害的应用。NVIDIA 对在上述设备或应用中包含和/或使用 NVIDIA 产品不承担任何责任,因此,此类包含和/或使用由客户自行承担风险。

NVIDIA 不保证基于本文档的产品适用于任何特定用途。NVIDIA 不一定对每个产品的所有参数进行测试。客户全权负责评估和确定本文档中包含的任何信息的适用性,确保产品适合并符合客户计划的应用,并为该应用执行必要的测试,以避免应用或产品的默认设置。客户产品设计中的缺陷可能会影响 NVIDIA 产品的质量和可靠性,并可能导致超出本文档中包含的附加或不同条件和/或要求。对于可能基于或归因于以下原因的任何默认、损坏、成本或问题,NVIDIA 不承担任何责任:(i) 以任何违反本文档的方式使用 NVIDIA 产品或 (ii) 客户产品设计。

本文档未授予 NVIDIA 专利权、版权或其他 NVIDIA 知识产权下的任何明示或暗示的许可。NVIDIA 发布的有关第三方产品或服务的信息不构成 NVIDIA 授予使用此类产品或服务的许可,也不构成对其的保证或认可。使用此类信息可能需要从第三方获得专利或许可,或第三方其他知识产权的许可,或从 NVIDIA 获得 NVIDIA 专利或其他知识产权的许可。

只有在事先获得 NVIDIA 书面批准的情况下,才可以复制本文档中的信息,并且复制必须在不进行更改的情况下完全符合所有适用的出口法律和法规,并附带所有相关的条件、限制和声明。

本文档和所有 NVIDIA 设计规范、参考板、文件、图纸、诊断程序、列表和其他文档(统称为“材料”)均按“原样”提供。NVIDIA 对材料不作任何明示、暗示、法定或其他方面的保证,并明确否认所有关于不侵权、适销性和特定用途适用性的暗示保证。在法律未禁止的范围内,在任何情况下,NVIDIA 均不对因使用本文档而引起的任何损害(包括但不限于任何直接、间接、特殊、偶然、惩罚性或后果性损害,无论如何造成,也无论责任理论如何)承担责任,即使 NVIDIA 已被告知可能发生此类损害。尽管客户可能因任何原因而遭受任何损害,但 NVIDIA 对此处描述的产品的累积和累计责任应根据产品的销售条款进行限制。

Google

Android、Android TV、Google Play 和 Google Play 徽标是 Google, Inc. 的商标。

商标

NVIDIA、NVIDIA 徽标、CUDA、Merlin、RAPIDS、Triton Inference Server、Turing 和 Volta 是 NVIDIA Corporation 在美国和其他国家/地区的商标和/或注册商标。其他公司和产品名称可能是与其相关的各自公司的商标。

© 2020-2023 NVIDIA Corporation 及关联公司。保留所有权利。 上次更新于 2023 年 2 月 1 日。