2、数值精度基础:FP32、FP16、BF16、TF32 的表示范围、精度差异与硬件支持

在混合精度训练中,选择哪种浮点格式并不是“越省显存越好”这么简单。每种格式都在表示范围、有效精度、硬件吞吐和算子支持之间做了不同取舍。理解 FP32、FP16、BF16、TF32 的底层差异,是后续正确配置动态损失缩放、避免梯度溢出或下溢的前提。

一、浮点数的三个关键字段

所有 IEEE 754 风格的浮点格式都由三部分组成:符号位 S、指数位 E、尾数位 M。指数位决定“能表示多大/多小的数”,尾数位决定“能表示多精细的数”。

  • 指数位越多:动态范围越大,越不容易上溢或下溢。
  • 尾数位越多:有效精度越高,累加误差越小。
  • 总位宽越小:显存占用和带宽越低,Tensor Core 吞吐通常越高。

下面这张图直观对比了四种格式的位分配与它们最擅长的场景。

FP32 / TF32 / BF16 / FP16 位分配与特性对比 FP32 S E 8 位 M 23 位 范围 ±3.4e38 · 精度 ~7 位十进制 · 基准格式 TF32 S E 8 位 M 10 位 范围同 FP32 · 精度 ~3 位十进制 · 仅 Tensor Core 内部使用 BF16 S E 8 位 M 7 位 范围同 FP32 · 精度 ~2 位十进制 · 训练稳定性好 FP16 S E 5 位 M 10 位 范围 ±65504 · 精度 ~3 位十进制 · 需损失缩放防下溢 ← 位宽从左到右递增(S 符号位 / E 指数位 / M 尾数位)

二、表示范围与精度差异

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 矩阵乘法时的一种内部计算模式,对用户几乎透明。

格式 总位宽 指数位 尾数位 最大正规数 最小正规数 典型用途
FP32328233.4e381.2e-38基准、优化器状态
TF3219(存储32)8103.4e381.2e-38Ampere+ 矩阵乘加速
BF1616873.4e381.2e-38大模型训练、激活存储
FP1616510655046.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 控制。
在 A100 上,FP16 与 BF16 的 Tensor Core 吞吐相同,但 BF16 无需损失缩放,工程上更省心;在 V100 上只能选 FP16,必须配置 GradScaler。

四、实操:查看与切换精度模式

在 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)

五、常见误区与复盘

误区一:BF16 精度低所以不能用。实际上 BF16 的尾数虽少,但动态范围与 FP32 一致,训练中梯度很少因下溢丢失,反而比 FP16 更稳定。
误区二:TF32 会改变模型精度。TF32 只影响矩阵乘内部累加,权重和激活仍以 FP32 存储,通常精度损失极小。
误区三:FP16 只要加损失缩放就万事大吉。若初始 scale 过大,仍会在前几个 step 产生 Inf;需要配合 GradScaler 的自动回退机制。

复盘时建议记录:当前 GPU 架构、所选精度格式、是否启用 TF32、损失缩放初始值与实际回退次数。这些信息能帮助你在出现 NaN/Inf 时快速定位是格式范围问题还是缩放策略问题。