38

混合精度训练——哪些算 BF16、哪些必须 FP32

阅读约 3 分钟第 38/60 课

如果 BF16 这么好用,为什么不把所有计算都用 BF16?因为有些操作对精度极度敏感。

对精度敏感的操作

Loss 计算。 交叉熵涉及 log 和 exp——当概率非常接近 0 或 1 时,log 和 exp 的结果可能变得非常大或非常小。BF16 只有 2-3 位精度,在这些极端区域的舍入误差可能让 loss 变成 NaN。

参数更新。 优化器更新参数时,更新量通常很小——比如 10⁻⁵ 量级。如果参数本身是 1.0,那新参数应该是 1.00001。BF16 的精度不够表示这个差异(1.0 和 1.00001 在 BF16 中是同一个值),导致参数实际上没有被更新——训练完全卡住。

梯度累加。 多步梯度累加需要精确地把小数加在一起。BF16 的精度不足以在长期累加中保持准确性。

混合精度的策略

混合精度训练的策略是:前向和反向传播的大部分计算用 BF16(快),少数关键操作和参数存储用 FP32(准)。

具体来说:

参数的主副本:FP32(永远保留一份完整精度的参数)
前向传播的矩阵乘法:BF16(占计算量的大头,BF16 快一倍)
激活函数、注意力的 softmax:BF16
Loss 计算:FP32
反向传播的矩阵乘法:BF16
优化器状态(m, v):FP32
参数更新:FP32(在 FP32 主副本上更新)

每一步训练的流程是:

  1. 把 FP32 参数复制一份转成 BF16。
  2. 用 BF16 参数做前向和反向传播(快)。
  3. 用 BF16 梯度更新 FP32 的参数主副本(准)。

这样做的效果:矩阵乘法——占计算量 90% 以上的操作——用 BF16 执行,速度翻倍。而需要精度的部分(参数更新、loss 计算)用 FP32 保证正确性。

显存占用

混合精度下 Sophia 的显存分布:

FP32 参数主副本:10 亿 × 4 字节 = 4 GB
BF16 参数副本:10 亿 × 2 字节 = 2 GB
FP32 优化器状态:10 亿 × 8 字节 = 8 GB(AdamW 的 m 和 v 各 4 字节)
BF16 梯度:10 亿 × 2 字节 = 2 GB
激活值(中间计算结果):取决于 batch size,约几 GB

总计约 16-20 GB

RTX 5090 有 32 GB 显存,刚好装得下。

下一章节来看这些计算实际上是在怎样的硬件上执行的。