GPU 架构学习

第十课:Tensor Core 与低精度计算

这一课讲 Tensor Core 和低精度计算:先理解不同数值格式,再看 MMA 指令、混合精度累加,以及 Blackwell 中 Transformer Engine 的作用。

目录一、浮点数由什么组成二、常见数值格式三、动态范围和精度有什么区别四、为什么低精度可以提高性能1. 减少存储空间2. 提高有效带宽3. 一个计算单元可以处理更多数据4. 寄存器和片上存储利用率提高五、低精度为什么会产生误差六、必须区分四种“精度”七、为什么累加精度通常要更高八、Tensor Core 的 MMA 是什么九、Warp 如何协作执行 MMA十、Tensor Core Kernel 的典型数据流十一、Tensor Core 和 CUDA Core 的区别十二、FP16 和 BF16 应该怎么选择FP16BF16十三、什么是 Loss Scaling十四、TF32 是什么十五、FP8 为什么有两种主要格式E4M3E5M2十六、FP8 为什么需要缩放因子十七、Per-Tensor Scaling 的局限十八、Block Scaling 和 Micro-Tensor Scaling十九、Blackwell 的 NVFP4二十、Blackwell Transformer Engine 是什么二十一、INT8 量化与浮点低精度有什么区别二十二、INT8 和 FP8 的直观区别INT8FP8二十三、Per-Tensor、Per-Channel 和 Per-Block 量化Per-TensorPer-ChannelPer-Block二十四、训练和推理的低精度要求不同推理训练二十五、什么是混合精度训练二十六、哪些操作通常更敏感二十七、量化感知训练与训练后量化PTQ:Post-Training QuantizationQAT:Quantization-Aware Training二十八、低精度为什么不一定真的更快二十九、低精度与 Roofline 的关系提高峰值计算能力减少数据传输量三十、从 ASIC 角度看 Tensor Core本课核心结论第十一课:GPU 的缓存、显存控制器与内存事务

导语:这一课讲 Tensor Core 和低精度计算:先理解不同数值格式,再看 MMA 指令、混合精度累加,以及 Blackwell 中 Transformer Engine 的作用。

前面讲到,普通 CUDA Core 主要执行标量运算:

d=a×b+cd=a\times b+c

Tensor Core 则面向矩阵乘加:

D=A×B+CD=A\times B+C

Tensor Core 的高性能不仅来自“矩阵并行”,还来自大量采用 FP16、BF16、FP8、INT8、FP4 等低精度数据。

这一课需要解决三个问题:

  1. 低精度到底降低了什么?
  2. 为什么位数更少会更快?
  3. Tensor Core 如何在提高吞吐率的同时控制误差?

一、浮点数由什么组成

一个二进制浮点数通常包含三部分:

符号位 Sign
指数位 Exponent
尾数位 Mantissa/Fraction

可以近似写成:

x=(1)s×1.m×2ex=(-1)^s\times 1.m\times 2^e

其中:

  • 符号位决定正负;
  • 指数决定能够表示多大或多小的数;
  • 尾数决定数值表示得有多精细。

因此:

指数位越多 → 动态范围越大
尾数位越多 → 有效精度越高

例如两个格式都占 16 bit,但指数和尾数的分配不同,它们的数值特性也会完全不同。


二、常见数值格式

格式总位宽指数位尾数位主要特点
FP3232823范围和精度较高
FP1616510精度较好,但范围较小
BF161687范围接近 FP32,但精度较低
TF3232位接口格式8约10FP32 范围、较低乘法精度
FP8 E4M3843精度较高、范围较小
FP8 E5M2852范围较大、精度较低
FP4 E2M1421必须配合细粒度缩放使用

FP16 使用5位指数和10位尾数;BF16使用8位指数和7位尾数,因此 BF16 保留了接近 FP32 的动态范围,而 FP16 在有限范围内提供了更细的数值间隔。TF32 主要用于 Tensor Core 矩阵运算,不应简单理解为一种普通的32位存储格式。


三、动态范围和精度有什么区别

假设要表示:

0.00000001
1.23456789
100000000

动态范围决定:

能不能表示非常大或非常小的数。

尾数精度决定:

能不能区分两个非常接近的数。

例如 BF16 的指数位与 FP32 一样多,因此不容易因为数值太大而溢出,但它的尾数只有7位,所以:

1.234567

可能只能近似表示成:

1.234375

FP16 尾数更多,在相同数量级下通常更精细,但它的指数位较少,更容易发生:

  • Overflow:数值过大,溢出;
  • Underflow:数值过小,变成0或次正规数。

因此不能简单说:

BF16 一定比 FP16 准确

更准确的是:

BF16:范围更大
FP16:有限范围内精度更高

四、为什么低精度可以提高性能

低精度的优势来自多个层面。

1. 减少存储空间

同样有十亿个参数:

FP32:约 4 GB
FP16:约 2 GB
INT8:约 1 GB
FP4:约 0.5 GB

这意味着:

  • 显存可以放入更大的模型;
  • Cache 可以容纳更多数据;
  • 数据从显存搬到芯片内所需流量减少。

2. 提高有效带宽

假设显存带宽固定为:

1 TB/s1\text{ TB/s}

理论上每秒可传输的元素数为:

FP32:约 2500 亿个
FP16:约 5000 亿个
INT8:约 1 万亿个
FP4:约 2 万亿个

位宽减半,理想情况下同样带宽可以传输约两倍元素。

对于带宽受限程序,低精度可能直接提高性能。


3. 一个计算单元可以处理更多数据

低位宽乘法器通常:

  • 面积更小;
  • 功耗更低;
  • 数据通路更窄;
  • 更容易在相同芯片面积内布置更多并行计算单元。

可以简化理解为:

一个较大的 FP32 乘法器占用的面积
可能容纳多个更低精度乘法器

具体比例取决于架构,不能简单按位宽线性换算,但总体趋势成立。


4. 寄存器和片上存储利用率提高

假设一个寄存器或数据通路宽度为32位:

可放 1 个 FP32
可放 2 个 FP16
可放 4 个 INT8
可放 8 个 4-bit 数据

硬件可以采用打包方式,在一次读取或一条指令中处理多个低位宽元素。


五、低精度为什么会产生误差

低精度格式能够表示的数值数量更少。

假设真实值是:

1.1371.137

而某种格式附近只能表示:

1.125
1.250

那么它可能被舍入为:

1.1251.125

产生量化误差:

e=1.1371.125=0.012e=1.137-1.125=0.012

主要误差包括:

舍入误差
截断误差
溢出
下溢
饱和
多个运算中的误差累积

所以低精度的核心矛盾是:

更高性能、更低存储开销

更低的数值表示能力

六、必须区分四种“精度”

讨论 Tensor Core 时,不能只说“使用 FP16”。

需要区分:

输入存储精度
乘法输入精度
累加精度
输出存储精度

例如一种常见的矩阵计算可以是:

A:FP16
B:FP16
乘法:FP16 输入
累加器:FP32
输出:FP16 或 FP32

表示为:

DFP32=AFP16×BFP16+CFP32D_{\text{FP32}} = A_{\text{FP16}} \times B_{\text{FP16}} + C_{\text{FP32}}

输入较低精度可以提高乘法吞吐率和降低带宽,而使用较高精度累加器可以减少长时间累加导致的误差。


七、为什么累加精度通常要更高

考虑点积:

y=k=01023akbky=\sum_{k=0}^{1023}a_kb_k

需要进行1024次乘法和累加。

如果每次累加都舍入为很低的精度:

第1次累加 → 舍入
第2次累加 → 再舍入
第3次累加 → 再舍入
……

误差可能不断积累。

而使用高精度累加器:

低精度输入

完成乘法

在 FP32 累加器中累积

最后再转换为目标格式

可以显著降低重复舍入的影响。

这就是混合精度计算的重要思想:

不需要让所有数据都保持高精度,但应让数值敏感的环节使用更高精度。

CUDA 的 WMMA 接口支持例如 BF16 输入配合 FP32 累加器,TF32 矩阵运算也使用浮点累加器。


八、Tensor Core 的 MMA 是什么

MMA 是:

Matrix Multiply-Accumulate,矩阵乘加。

数学表达式为:

D=A×B+CD=A\times B+C

它不是简单执行一个标量:

d=a×b+cd=a\times b+c

而是一次处理矩阵小块。

例如概念上:

A Tile × B Tile + C Tile → D Tile

不同架构支持的矩阵形状、数据格式和线程组织不同,因此不能认为所有 Tensor Core 都固定执行某一种大小的矩阵。


九、Warp 如何协作执行 MMA

在经典 WMMA 编程模型中,一个 Warp 共同完成一次矩阵操作。

矩阵小块并不是完整地存放在某一个线程中,而是分散在 Warp 的多个线程中:

九、Warp 如何协作执行 MMA

图示:九、Warp 如何协作执行 MMA

这些局部数据称为 Fragment。

CUDA 文档明确指出,Fragment 中的矩阵元素分散在整个 Warp 的线程之间,而且具体元素到线程的映射属于架构实现细节。mma_sync 要求参与 Warp 的线程共同到达,然后执行:

D=A×B+CD=A\times B+C

所以不应理解成:

一个 Thread 独占一个 Tensor Core

更合理的理解是:

九、Warp 如何协作执行 MMA

图示:九、Warp 如何协作执行 MMA


十、Tensor Core Kernel 的典型数据流

一个高性能 Tensor Core 矩阵乘法通常为:

十、Tensor Core Kernel 的典型数据流

图示:十、Tensor Core Kernel 的典型数据流

这里仍然需要前面学过的:

  • Tiling;
  • 合并访存;
  • Shared Memory;
  • 双缓冲;
  • 异步搬运;
  • 寄存器累加。

Tensor Core 只解决“算得快”,并不会自动解决“数据送不进来”的问题。


十一、Tensor Core 和 CUDA Core 的区别

对比CUDA CoreTensor Core
典型操作标量加法、乘法、FMA矩阵块 MMA
数据组织每线程标量或向量线程组协作的矩阵片段
适用任务通用计算矩阵、卷积、Transformer
支持精度FP32、整数等多种混合低精度格式
灵活性较高对形状和布局要求较高
峰值吞吐率相对较低矩阵运算吞吐率很高

Tensor Core 不是用于替代所有 CUDA Core。

例如以下工作仍需要普通管线:

地址计算
循环控制
分支判断
数据布局变换
特殊函数
索引计算
非矩阵算子

一个 Tensor Core Kernel 通常是多种硬件单元共同工作的结果。


十二、FP16 和 BF16 应该怎么选择

FP16

特点:

指数较少
尾数较多
范围较小
同一数量级下精度较高

适合数值已经得到良好缩放、范围较可控的场景。

训练时可能需要 Loss Scaling,防止较小梯度下溢。


BF16

特点:

指数范围接近 FP32
尾数较少
动态范围大
转换和训练通常更稳定

BF16 可以表示非常大和非常小的数量级,但每个数量级内的数值间隔更粗。

因此很多训练任务偏好 BF16,而某些对局部精度要求更高、范围可控的任务可能适合 FP16。


十三、什么是 Loss Scaling

假设 FP16 无法表示非常小的梯度:

g=1010g=10^{-10}

转换成 FP16 后可能变为0。

可以在反向传播前把损失乘以一个较大系数:

L=S×LL'=S\times L

梯度也会相应放大:

g=S×gg'=S\times g

更新参数前再除以 SS

流程为:

原始梯度太小

乘以较大的 Scale

在 FP16 可表示范围内计算

更新前恢复原来的尺度

动态 Loss Scaling 会根据是否出现溢出自动调整缩放系数。


十四、TF32 是什么

TF32 是 Ampere 及后续架构中面向 Tensor Core 的一种计算精度。

它的核心特点是:

指数范围接近 FP32
尾数精度低于 FP32
面向矩阵乘法

因此,程序仍可能以 FP32 形式存放和传递数据,但 Tensor Core 在乘法路径中采用较低尾数精度,再使用较高精度累加。

可以把它理解成:

FP32 的动态范围
+
接近较低精度格式的乘法吞吐优势

但 TF32 不适合要求严格 FP32 逐位结果一致的场景。CUDA 文档将 TF32 定义为 Tensor Core 专用的特殊格式,具有与 FP32 相近的范围和降低的有效精度。


十五、FP8 为什么有两种主要格式

FP8 常见格式包括:

E4M3
E5M2

E4M3

1 个符号位
4 个指数位
3 个尾数位

尾数较多,因此精度相对更好,但动态范围较小。

E5M2

1 个符号位
5 个指数位
2 个尾数位

指数更多,动态范围更大,但精度更低。

传统的 FP8 混合方案中,前向传播常偏向精度更高的 E4M3,而梯度可能需要动态范围更大的 E5M2。


十六、FP8 为什么需要缩放因子

FP8 的表示范围比 FP16、BF16 小得多。

假设一个张量包含:

0.001
0.1
1
100
10000

直接转换为同一种 FP8 格式时:

  • 小数可能下溢;
  • 大数可能溢出;
  • 中间值也可能损失较多精度。

因此先选择缩放因子 SS

xFP8=quantize(xS)x_{\text{FP8}} = \operatorname{quantize} \left( \frac{x}{S} \right)

恢复时:

x^=S×xFP8\hat{x} = S\times x_{\text{FP8}}

缩放的目标是让张量中的主要数据尽量落入 FP8 的有效表示范围。

Transformer Engine 可以维护张量的最大绝对值和缩放因子,减少用户手工管理低精度数值范围的负担。


十七、Per-Tensor Scaling 的局限

假设整个张量共享一个缩放因子。

张量的一部分数值在:

[0.1,0.1][-0.1,0.1]

另一部分在:

[1000,1000][-1000,1000]

为了避免大数溢出,缩放因子必须适配大数。

结果是小数部分被压缩到很窄的范围,可能大量舍入为0。

少数异常大值

决定整个张量的缩放因子

大量普通小值精度下降

这就是低精度量化中的 Outlier 问题。


十八、Block Scaling 和 Micro-Tensor Scaling

解决办法是把张量划分为多个小块,每一块使用自己的缩放因子。

十八、Block Scaling 和 Micro-Tensor Scaling

图示:十八、Block Scaling 和 Micro-Tensor Scaling

这样每个缩放因子只需要适应局部数据范围。

例如 MXFP8 可对连续的小数据块分别缩放,而不是整个张量只使用一个 FP32 缩放因子。NVIDIA 当前文档中的 MXFP8 方案以32个连续元素为一个缩放块。

这种方法的本质是:

增加少量缩放元数据

换取更精细的局部动态范围

减少溢出、下溢和精度损失

十九、Blackwell 的 NVFP4

Blackwell 支持用于 AI 计算的 FP4 路径。

NVFP4 的基本数据元素采用 E2M1:

1 位符号
2 位指数
1 位尾数

仅靠4位数据本身,可表示的范围和精度都非常有限,因此 NVFP4 使用分层缩放:

x=xE2M1×sblock×sglobalx = x_{\text{E2M1}} \times s_{\text{block}} \times s_{\text{global}}

其中:

  • xE2M1x_{\text{E2M1}}:4位数据;
  • sblocks_{\text{block}}:局部块缩放因子;
  • sglobals_{\text{global}}:整个张量的全局缩放因子。

当前 Transformer Engine 文档描述的 NVFP4 中,连续16个元素共享一个 E4M3 局部缩放因子,并额外使用一个张量级 FP32 缩放因子。

可以简单理解为:

FP4 数据负责低成本保存主要数值
局部 Scale 适应小范围变化
全局 Scale 适应整个 Tensor 的数量级

二十、Blackwell Transformer Engine 是什么

Transformer Engine 不应简单理解成一个单独的“Transformer 专用 ALU”。

它更像一套硬件和软件协同机制,包括:

支持低精度的 Tensor Core
缩放因子和动态范围管理
低精度矩阵乘法 Kernel
张量格式转换
融合算子
框架和运行库支持

Blackwell 的第二代 Transformer Engine 引入了更细粒度的缩放机制,并支持面向 AI 的 FP4 计算,以提高大模型训练和推理中的计算吞吐率及模型容量。

其核心逻辑仍然是:

二十、Blackwell Transformer Engine 是什么

图示:二十、Blackwell Transformer Engine 是什么


二十一、INT8 量化与浮点低精度有什么区别

INT8 没有指数位。

它需要通过缩放因子,把浮点数映射到整数区间。

常见对称量化为:

q=clip(round(xS),128,127)q= \operatorname{clip} \left( \operatorname{round} \left( \frac{x}{S} \right), -128,127 \right)

恢复为:

x^=S×q\hat{x}=S\times q

非对称量化还会加入 Zero Point:

q=round(xS)+Zq= \operatorname{round} \left( \frac{x}{S} \right)+Z x^=S(qZ)\hat{x}=S(q-Z)

其中:

  • SS 是 Scale;
  • ZZ 是 Zero Point。

二十二、INT8 和 FP8 的直观区别

INT8

在一个固定缩放范围内,量化级别通常均匀分布:

... -3S, -2S, -S, 0, S, 2S, 3S ...

优点:

  • 硬件简单;
  • 整数乘加效率高;
  • 在已校准范围内精度稳定。

缺点:

  • 同一个 Scale 难以同时适应很大和很小的数;
  • 对动态范围变化敏感。

FP8

浮点数的间隔随数量级变化:

靠近 0:间隔较小
数值变大:间隔也变大

优点:

  • 能覆盖更宽的动态范围;
  • 更适合数值范围随层和训练阶段变化的场景。

缺点:

  • 尾数很少;
  • 仍然需要良好的缩放管理。

二十三、Per-Tensor、Per-Channel 和 Per-Block 量化

Per-Tensor

整个张量一个 Scale:

Tensor → 一个 Scale

硬件和存储开销小,但精度可能较差。

Per-Channel

每个输出通道一个 Scale:

Channel 0 → Scale 0
Channel 1 → Scale 1
Channel 2 → Scale 2

常用于神经网络权重量化,因为不同输出通道的权重范围可能差异较大。

Per-Block

每个小数据块一个 Scale:

每16、32或更多元素 → 一个 Scale

精度通常更高,但需要:

  • 保存更多 Scale;
  • 计算地址和缩放关系;
  • 硬件支持相应数据布局。

粒度越细,通常越容易适应局部数据分布,但元数据和控制开销也越高。


二十四、训练和推理的低精度要求不同

推理

模型参数已经固定。

可以提前:

  • 统计激活范围;
  • 校准 Scale;
  • 量化权重;
  • 测试精度下降;
  • 对异常层保留高精度。

因此 INT8、FP8、FP4 更容易用于推理。

训练

训练过程中:

  • 权重持续变化;
  • 激活分布变化;
  • 梯度范围变化;
  • 很小的更新可能影响最终收敛。

所以训练通常需要更复杂的混合精度策略,例如:

低精度执行主要矩阵乘法
高精度保存关键状态
高精度执行归约或参数更新
动态调整缩放因子

二十五、什么是混合精度训练

一种简化的混合精度训练流程是:

二十五、什么是混合精度训练

图示:二十五、什么是混合精度训练

实际框架中的具体精度组合会因模型、硬件和训练方案而不同。

核心原则是:

把大量规则矩阵计算放到低精度 Tensor Core 上,把数值敏感操作保留在较高精度。


二十六、哪些操作通常更敏感

以下操作往往比普通矩阵乘法更容易受到低精度影响:

Softmax
归一化
方差计算
长距离归约
Loss 计算
很小的梯度更新
概率和指数运算
优化器状态更新

原因包括:

  • 需要计算很小的差值;
  • 存在大量累加;
  • 包含指数和除法;
  • 对极值敏感;
  • 误差可能被后续操作放大。

因此一个模型使用 FP8 或 FP4,不代表所有操作都以 FP8 或 FP4 执行。


二十七、量化感知训练与训练后量化

PTQ:Post-Training Quantization

训练完成后进行量化:

训练好的浮点模型

校准数据统计范围

转换为 INT8 / FP8 / FP4

优点是实现简单、无需重新完整训练。

缺点是低位宽下精度可能下降明显。

QAT:Quantization-Aware Training

训练时模拟量化过程:

浮点权重

模拟量化和反量化

前向传播感受到量化误差

通过训练适应误差

QAT 通常更适合:

  • INT8 以下精度;
  • 对精度敏感的模型;
  • 激活分布复杂的模型;
  • 极低位宽部署。

二十八、低精度为什么不一定真的更快

即使理论吞吐率更高,也可能因为以下原因得不到加速:

矩阵尺寸太小
维度没有良好对齐
需要频繁格式转换
Scale 计算开销较大
数据布局不适合 Tensor Core
Kernel 启动开销占比高
访存或同步成为瓶颈
Tensor Core 供数不足
某些算子仍使用高精度

例如一个很小的矩阵:

8×8 × 8×8

转换、加载和启动开销可能比计算本身还大。

所以低精度峰值算力高,不代表所有程序都能按相同比例加速。


二十九、低精度与 Roofline 的关系

低精度同时可能改变 Roofline 模型中的两个量。

提高峰值计算能力

Tensor Core 对低精度格式通常提供更高吞吐率:

PpeakP_{\text{peak}}\uparrow

减少数据传输量

数据位宽降低:

Bytes\text{Bytes}\downarrow

因此计算强度:

AI=FLOPsBytesAI= \frac{\text{FLOPs}}{\text{Bytes}}

可能提高。

但是峰值算力提高后,新的 Ridge Point 也会变化:

AIridge=PpeakBAI_{\text{ridge}} = \frac{P_{\text{peak}}}{B}

这意味着 Tensor Core 越快,越需要更高的数据复用,否则更容易受内存系统限制。


三十、从 ASIC 角度看 Tensor Core

一个高度简化的 Tensor Core 可以理解成:

三十、从 ASIC 角度看 Tensor Core

图示:三十、从 ASIC 角度看 Tensor Core

与普通标量 ALU 相比,它的特点是:

  • 固定面向矩阵数据流;
  • 大量乘法并行;
  • 共享输入数据;
  • 使用规则的归约结构;
  • 部分和保持在累加器中;
  • 牺牲通用性换取密度和能效。

从硬件设计角度,这与神经网络 ASIC 中的 MAC Array 或 Systolic Array 有相似之处,但 NVIDIA 并未公开所有内部实现细节,不能把 Tensor Core 简单等同于某一种固定脉动阵列。


本课核心结论

第一,浮点数的指数决定动态范围,尾数决定有效精度。

第二,FP16 尾数更多但范围较小,BF16 范围接近 FP32 但尾数更少。

第三,低精度可以减少存储、降低带宽需求,并提高矩阵计算吞吐率。

第四,低精度会产生舍入、溢出、下溢和误差累积问题。

第五,必须区分输入精度、乘法精度、累加精度和输出精度。

第六,低精度输入配合较高精度累加,是混合精度计算的核心。

第七,Tensor Core 执行矩阵乘加:

D=A×B+CD=A\times B+C

第八,经典 WMMA 模型由一个 Warp 共同持有 Fragment 并执行矩阵运算,而不是一个线程独占 Tensor Core。

第九,FP8 E4M3 更偏向精度,E5M2 更偏向动态范围。

第十,FP8、FP4 通常需要缩放因子来管理数值范围。

第十一,Per-Block 或 Micro-Tensor Scaling 可以降低异常值对整个张量的影响。

第十二,Blackwell 的 NVFP4 采用4位 E2M1 数据,并配合局部和全局两级缩放。

第十三,Transformer Engine 是 Tensor Core、低精度格式、动态范围管理和软件 Kernel 的协同系统。

第十四,INT8 使用固定缩放映射,FP8 使用浮点指数并通常配合缩放。

第十五,低精度不一定自动加速,仍需满足矩阵尺寸、数据布局和数据供应要求。

下一课将学习:

第十一课:GPU 的缓存、显存控制器与内存事务

主要包括:

显存请求如何从 Warp 到达 HBM/GDDR
Cache Line 和 Memory Transaction
L1、L2 Cache 如何组织
显存分区与内存控制器
地址交织
为什么对齐很重要
为什么跨 Cache Line 会增加事务
HBM 和 GDDR 的结构差异
ECC 和显存容量开销

Blackwell 架构概览

我会用最基础的层次,把 Blackwell 的定位、核心改进和它解决什么问题讲清楚。

第二十七课:GPU 架构学习总结与统一分析框架

最后一课将把前面所有内容压缩成一套统一分析框架,并给出面向 GPU 架构与 RTL 设计的后续实践路线。

第二十六课:如何从零设计一个简化的 SIMT GPU

这一课从 RTL 设计角度搭建一颗最小可运行的 SIMT GPU。重点是模块边界、状态机和实现顺序,而不是一开始复制完整商用 GPU。