Mixed precision 怎么做?
FP16、BF16、FP8 有什么区别?

英文问法Why do we train LLMs in BF16 rather than FP16? · What is loss scaling and why is it needed? · Why keep FP32 master weights? · E4M3 vs E5M2?

考过面经:国内:讯飞("为什么大模型训练用BF16?")· 网易("FP8格式差异(E4M3高精度窄范围 vs E5M2宽动态范围)")· 牛客("是否了解混合精度,FP16 和 BF16 的区别?")· 智谱("nvfp4的原理是什么样的,怎么做缩放的,在哪个维度缩放?")

先弄清楚:每个概念是什么

Exponent 与 mantissa(range vs precision)
浮点数 = 符号 × 1.mantissa × 2exponent。exponent 的 bit 决定尺子有多长(range),mantissa 的 bit 决定刻度有多密(precision)。例:1 附近相邻两个数的间距,FP16 是 \(2^{-10}\approx 0.001\),BF16 是 \(2^{-7}\approx 0.008\)。
Underflow / overflow
比最小值还小就变成 0,比最大值还大就变成 inf。例:np.float16(1e-8) = 0;np.float16(70000) = inf。
Loss scaling
backward 之前把 loss 乘 \(S\),所有 gradient 一起放大 \(S\) 倍,小 gradient 不会变成 0,optimizer 更新前再除回去。例:gradient \(10^{-8}\) 存成 FP16 是 0;乘 \(S = 2^{16}\) 得 \(6.55\times10^{-4}\),存得下;在 FP32 里除回来仍 ≈ \(10^{-8}\)。
FP32 master weights
weight 的正本用 FP32 保存和更新,forward/backward 用它转出的 16-bit 副本。FP16 只有 10 位 mantissa,weight 比 update 大超过 2048 倍时 \(w + \Delta w\) 舍入后还是 \(w\)。例:\(w = 1\)、update \(= 10^{-4}\):FP16、BF16 算出来都还是 1;FP16 连加 10000 次仍是 1,FP32 是 2.0002。
Per-tensor scaling(FP8)
FP8 的 range 够放下任意一个 tensor,但放不下所有 tensor。所以每个 tensor 各配一个 scale,把它的 amax(最大绝对值)对准格式的最大值。

看图:bit 怎么分,一个 step 怎么走

signexponent:rangemantissa:precisionFP32master weight823最大 3.4e38 · 最小 normal 1.2e-38 · 1 附近间距 2⁻²³FP16要 loss scaling510最大 65504 · 最小 normal 6.1e-5 · 1 附近间距 2⁻¹⁰BF16LLM 训练首选87最大 3.4e38 · 最小 normal 1.2e-38 · 1 附近间距 2⁻⁷FP8 E4M3forward43最大 448 · 最小 normal 2⁻⁶ · 1 附近间距 2⁻³FP8 E5M2gradient52最大 57344 · 最小 normal 2⁻¹⁴ · 1 附近间距 2⁻²
图 1:每个小格是 1 bit。BF16 的 exponent 和 FP32 一样是 8 位,所以 range 一样。FP16 把位数让给了 mantissa,最大只有 65504。
FP3216-bit(BF16 / FP16)master weight(正本)16-bit 副本① 转出副本② forward:matmul 用 16-bitaccumulate、softmax、norm 用 FP32③ loss × S → backward得到 16-bit gradient(BF16:S = 1)④ gradient ÷ S查 inf/NaN、clipping更新master weight有 inf/NaN:跳过这步,S ← S/2连续 2000 步正常:S ← 2S
图 2:一个 training step。左边蓝色在 FP32 里做,右边橙色在 16-bit 里做,虚线框是 dynamic loss scaling 遇到溢出时的处理。

30 秒回答

Mixed precision runs matmuls in 16-bit or FP8 and keeps the sensitive parts in FP32: master weights, accumulation inside matmuls, and reductions like softmax and norms. FP16 has only 5 exponent bits, so small gradients underflow; dynamic loss scaling fixes that: multiply the loss by S, unscale before the step, and on inf or NaN skip the step and halve S. BF16 keeps FP32's 8 exponent bits, so it needs no loss scaling, but with only 7 mantissa bits it still needs FP32 master weights. FP8 uses E4M3 for weights and activations and E5M2 for gradients, with a scale per tensor.

关键公式

格式sign / exp / mantissa最大值最小 normal1 附近间距用在哪
FP321 / 8 / 233.4e381.2e-38\(2^{-23}\)master weight、optimizer state、accumulate
FP161 / 5 / 10655046.1e-5\(2^{-10}\)要 loss scaling
BF161 / 8 / 73.4e381.2e-38\(2^{-7}\)LLM 训练首选
FP8 E4M31 / 4 / 3448\(2^{-6}\)\(2^{-3}\)forward:weight、activation
FP8 E5M21 / 5 / 257344\(2^{-14}\)\(2^{-2}\)backward:gradient

loss scaling:backward 是线性的

\[ \nabla_\theta (S\cdot L) = S\cdot \nabla_\theta L \]

dynamic loss scaling(PyTorch GradScaler 默认值):

\[ S_0 = 2^{16};\quad \text{有 inf/NaN} \Rightarrow \text{跳过这步},\ S \leftarrow S/2 \] \[ \text{连续 2000 步正常} \Rightarrow S \leftarrow 2S \]

一个 training step

  1. FP32 master weight → 转出 BF16/FP16 副本
  2. forward:matmul 16-bit 输入、FP32 accumulate;softmax、norm 统计量用 FP32
  3. loss × \(S\) → backward,得到 16-bit gradient(BF16 时 \(S = 1\))
  4. gradient ÷ \(S\),检查 inf/NaN,再 clipping → 更新 FP32 master weight

model states 每个 parameter 占 16 bytes,见 training memory。

必答点

追问

为什么大模型都用 BF16,不用 FP16?

range 和 FP32 一样,不用调 loss scale。PyTorch 文档提醒,多数 BF16 预训练的模型放进 FP16 时 gradient 会 overflow。

loss scale 选多大?太大会怎样?

只要不 overflow 越大越好,也就是 \(S\cdot\max|g| < 65504\)。太大会出 inf/NaN,所以实际用 dynamic loss scaling。\(S\) 取 2 的幂,乘除不引入误差。

FP8 溢出了怎么办?delayed scaling 是什么?

溢出时直接 saturate 到最大值,不跳过 step,因为 FP8 溢出太频繁。Transformer Engine 的 delayed scaling 用前几个 iteration 的 amax history 算 scale,不用现算 amax 多扫一遍数据。

MXFP8 / NVFP4 怎么缩放?在哪个维度?(智谱)

按 block 缩放,block 沿 matmul 的 reduction 维连续。MXFP8 每 32 个元素一个 E8M0 scale,NVFP4 每 16 个元素一个 E4M3 scale 再加一个 per-tensor FP32 scale(见 MXFP8、NVFP4)。NVFP4 的 weight 用 16×16 的 2D block,训练 recipe 见 Nemotron 3 Ultra。

哪些 op 必须留在 FP32?

大的 reduction 和容易溢出的 op(softmax、layer_norm、cross_entropy、exp/log、sum,PyTorch autocast 自动放 FP32)、matmul 的累加、optimizer update 和 Adam state。逐元素 op 受 memory bandwidth 限制,用 FP16 还是 FP32 速度一样。

来源Micikevicius et al. 2018, Mixed Precision Training §3.1(pp.2–3)、§3.2(pp.3–5)、§3.3(p.5)· Micikevicius et al. 2022, FP8 Formats §2(p.2)、§3 Table 1(p.3)· Kalamkar et al. 2019, BFLOAT16 abstract、§3 Table 1(pp.3–4)· PyTorch AMP(Gradient Scaling、GradScaler)· Transformer Engine: Using FP8 and FP4(delayed scaling、MXFP8、NVFP4)