2、数值精度基础:FP32、FP16、BF16、TF32 的表示范围、精度差异与硬件支持
在混合精度训练中,选择哪种浮点格式并不是“越省显存越好”这么简单。每种格式都在表示范围、有效精度、硬件吞吐和算子支持之间做了不同取舍。理解 FP32、FP16、BF16、TF32 的底层差异,是后续正确配置动态损失缩放、避免梯度溢出或下溢的前提。
一、浮点数的三个关键字段
所有 IEEE 754 风格的浮点格式都由三部分组成:符号位 S、指数位 E、尾数位 M。指数位决定“能表示多大/多小的数”,尾数位决定“能表示多精细的数”。
- 指数位越多:动态范围越大,越不容易上溢或下溢。
- 尾数位越多:有效精度越高,累加误差越小。
- 总位宽越小:显存占用和带宽越低,Tensor Core 吞吐通常越高。
下面这张图直观对比了四种格式的位分配与它们最擅长的场景。
二、表示范围与精度差异
FP16 的指数只有 5 位,最大正规数约为 65504,最小正规数约为 6.1e-5。这意味着当梯度值小于约 6e-5 时,会进入非正规数区间甚至直接下溢为 0;当激活值超过 65504 时,会直接上溢为 Inf。这正是 FP16 训练必须配合损失缩放的根本原因。
BF16 保留了与 FP32 相同的 8 位指数,因此动态范围与 FP32 一致,几乎不会上溢或下溢。代价是尾数只有 7 位,精度较低,但在深度学习中,梯度更新的方向往往比绝对精度更重要,因此 BF16 通常能稳定训练。
TF32 是一种“折中格式”:指数与 FP32 相同,尾数截断到 10 位。它不是一个可存储的数据类型,而是 NVIDIA Ampere 及之后架构中 Tensor Core 在执行 FP32 矩阵乘法时的一种内部计算模式,对用户几乎透明。
| 格式 | 总位宽 | 指数位 | 尾数位 | 最大正规数 | 最小正规数 | 典型用途 |
|---|---|---|---|---|---|---|
| FP32 | 32 | 8 | 23 | 3.4e38 | 1.2e-38 | 基准、优化器状态 |
| TF32 | 19(存储32) | 8 | 10 | 3.4e38 | 1.2e-38 | Ampere+ 矩阵乘加速 |
| BF16 | 16 | 8 | 7 | 3.4e38 | 1.2e-38 | 大模型训练、激活存储 |
| FP16 | 16 | 5 | 10 | 65504 | 6.1e-5 | 推理、混合精度训练 |
三、硬件支持与算子覆盖
不同 GPU 架构对四种格式的支持程度差异很大,这直接决定了你能用哪种混合精度策略。
- FP32:所有 GPU 全支持,CUDA Core 原生计算。
- FP16:Pascal 及之后支持存储;Volta 及之后 Tensor Core 支持 FP16 矩阵乘。
- BF16:Ampere(A100、RTX 30 系)及之后原生支持;Volta/Turing 不支持。
- TF32:Ampere 及之后 Tensor Core 支持,可通过
torch.backends.cuda.matmul.allow_tf32控制。
四、实操:查看与切换精度模式
在 PyTorch 中,可以通过以下代码快速确认当前硬件支持的精度能力,并切换 TF32 开关。
import torch
# 查看 GPU 计算能力
print(torch.cuda.get_device_capability()) # 例如 (8, 0) 表示 Ampere
# 开启/关闭 TF32(Ampere 及以上有效)
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True
# 使用自动混合精度
scaler = torch.cuda.amp.GradScaler(enabled=True) # FP16 场景
with torch.cuda.amp.autocast(dtype=torch.bfloat16): # BF16 场景
output = model(input)
loss = criterion(output, target)
五、常见误区与复盘
误区二:TF32 会改变模型精度。TF32 只影响矩阵乘内部累加,权重和激活仍以 FP32 存储,通常精度损失极小。
误区三:FP16 只要加损失缩放就万事大吉。若初始 scale 过大,仍会在前几个 step 产生 Inf;需要配合
GradScaler 的自动回退机制。
复盘时建议记录:当前 GPU 架构、所选精度格式、是否启用 TF32、损失缩放初始值与实际回退次数。这些信息能帮助你在出现 NaN/Inf 时快速定位是格式范围问题还是缩放策略问题。