FP32、FP16、BF16 和混合精度有什么区别?

位数变少为什么更快,又为什么可能让训练崩掉

目录

  第五课里我们用“每个参数占多少 Bytes”估算显存,第六课又用多卡分摊训练状态。这里一直出现 FP32、FP16 和 BF16,它们到底有什么区别?

  直观上,它们都是保存小数的方法。位数越少,显存和带宽压力越小,硬件通常也能算得更快;但能表达的范围和精细程度会发生变化。

低精度训练不是把所有数字粗暴地变小,而是让不同计算使用适合自己的精度。

FP32 FP16 BF16 对比

三种浮点格式与混合精度分工,点击查看原始 SVG

1、浮点数在保存什么

  浮点数通常由三部分组成:符号位、指数位和尾数位。

符号:正数还是负数
指数:数量级有多大或多小
尾数:这个数能表示得多精细

  指数位决定动态范围,尾数位决定精度。范围和精度不是同一回事:能表示很大的数字,不代表能精细地区分两个很接近的数字。

2、FP32、FP16、BF16 的区别

格式 总位数 指数位 尾数位 每个数大小
FP32 32 8 23 4 Bytes
FP16 16 5 10 2 Bytes
BF16 16 8 7 2 Bytes

  FP16 把指数和尾数都缩短,因此更精细,但范围明显小于 FP32。BF16 保留了与 FP32 相同的 8 位指数,所以范围接近 FP32;代价是尾数只有 7 位,数字更粗糙。

3、为什么低精度更快

  从 FP32 换成 16 位格式,通常会带来三类收益:

  1. 同样数量的数据只占一半显存。
  2. GPU 与显存之间需要搬运的数据更少。
  3. Tensor Core 等硬件能以更高吞吐执行低精度矩阵乘法。

  但“理论减半”不等于总显存一定减半。优化器状态、临时 Buffer、对齐和某些仍保持 FP32 的数据都会占空间。

4、FP16 为什么容易出问题

  训练中的梯度可能非常小,也可能突然变大。FP16 动态范围较窄,容易出现:

问题 含义 后果
Underflow 小数太小,被舍入为 0 梯度消失
Overflow 数值超出上限,变成 Inf Loss 变成 NaN
Rounding 相近数字无法区分 更新不够精细

  FP16 训练常使用 Loss Scaling:先把 Loss 乘以较大的缩放因子,让微小梯度进入 FP16 可表示范围;反向传播后再把梯度缩回去。

\[L_{scaled}=S\times L\]

  如果发现梯度溢出,动态 Loss Scaling 会跳过这次更新并降低 $S$。

5、混合精度怎样分工

  混合精度不是“全程 FP16”,而是按照计算特点分工:

矩阵乘法、参数副本、激活:FP16 或 BF16
累加、归一化、部分归约:必要时 FP32
优化器状态、主权重:常保留 FP32

  低精度负责大规模计算和存储,FP32 负责对数值稳定性敏感的部分。框架中的自动混合精度通常会根据算子选择合适类型。

6、BF16 为什么更适合大模型

  BF16 的指数位与 FP32 一样,动态范围大,因此不容易因为数值过大或过小而溢出。它通常不需要像 FP16 那样依赖 Loss Scaling。

  它的尾数更短,精度低于 FP16,但神经网络训练往往更怕范围不够,而不是每个数少几位小数。配合 FP32 累加和优化器状态,BF16 成为现代大模型训练的常见选择。

  前提是硬件原生支持 BF16;否则速度收益可能有限。

三种格式对比

维度 FP32 FP16 BF16
显存 高 低 低
动态范围 大 小 大
尾数精度 高 中 较低
Loss Scaling 不需要 常需要 通常不需要
大模型训练 稳定但慢 快,需谨慎 常用折中方案

容易混淆的三件事

  1. BF16 并不比 FP16 更“精确”。 它范围更大,但尾数更短。
  2. 混合精度不等于模型所有数据都是 16 位。 敏感计算和训练状态仍可能使用 FP32。
  3. 精度格式与 INT8/INT4 量化不是同一件事。 前者仍是浮点格式,后者通常属于整数或低比特表示。

记住这 5 件事

  1. 指数决定范围,尾数决定精细程度。
  2. FP16 范围较小,可能发生 Underflow 和 Overflow。
  3. BF16 保留 FP32 的指数范围,但尾数更短。
  4. 混合精度让低精度负责速度,FP32 负责稳定性。
  5. 最终选择取决于硬件支持、模型和训练配置。

第七课复习总图

大模型第七课复习总图

第七课复习总图,点击查看原始 SVG

  下一课,我们可以把前七课串起来:从 Dataset、Forward、Loss、Backward 到 Optimizer Step,亲手实现一次完整训练循环。