从0训练LLM · 现代大模型体系 阅读 + 实验 ≈ 40 分钟 页内实验 ×4
C9 精度与显存:一个参数要几字节 第 4 课

8 位还能训吗?

FP8 与细粒度缩放
FP8 只有 256 个位型,E4M3 最大 448,放进去之前必须先缩放。整张量共用一个缩放时,一个离群值会把同一张量里的小数推进次正规区:[1000, 0.01, 0.02, 0.5] 里 0.01 的误差 12.8%。DeepSeek-V3 让激活每 1 × 128、权重每 128 × 128 各配一个缩放,误差降到 0.45%,整模型的 loss 相对 BF16 差不到 0.25%。
❓

本课一问:16 位已经稳了。矩阵乘占了训练的绝大部分算力,如果它能用 8 位算,理论上又快一倍——可 8 位一共只有 256 个位型。还能训吗?

这一课先不碰:4 位(岔路课);激活占多少显存(第 5 课);推理时的量化(C13)。

从已有出发

同一步训练,这回矩阵乘换成 FP8。看格式标签:前向、反向的矩阵乘变成了 FP8,可梯度、主权重反而留在 FP32,m、v 改成了 BF16——这是 DeepSeek-V3 的实际做法,本课逐项讲为什么。

从已有出发 · 训练一步在显存里留下什么(FP8 细粒度(DeepSeek-V3)) 本课点任一环节看它在 Qwen/Qwen3.8-27B 上占多少
精度与显存的演进:每砍一次位宽,都在一个真数字上失败一次
  1. 起点 FP32 全精度 + AdamW 深度学习早期的默认做法
    解决了范围到 ±3.4 × 10³⁸,1 附近的间距 2⁻²³ ≈ 1.2 × 10⁻⁷,数值上几乎不用操心。
    卡在每个参数 4 + 4 + 4 + 4 = 16 字节(权重、梯度、Adam 的 m、v):锚点 27,781,427,952 个参数就是 444.5 GB,是 80 GB 单卡的 5.6 倍,还没算激活。
  2. 第一代 FP16 混合精度:FP32 主权重 + loss scaling Micikevicius et al. 2017
    解决了矩阵乘和激活用 16 位,带宽与激活显存减半;FP32 主权重接住小更新,loss 乘 S 把小梯度推出下溢区,更新前再除回来。
    卡在指数只有 5 位:fp16(10⁻⁸) = 0,fp16(1 + 10⁻⁴) = 1,fp16(70,000) = inf;缩放倍数要一直调,溢出就得跳过这一步。
  3. 第二代 BF16:用尾数换指数 Google Brain 2019 · Kalamkar et al. 2019
    解决了8 位指数,范围与 FP32 相同:不再下溢,不需要 loss scaling。2026 年的默认训练精度。
    卡在1 附近间距粗到 1/128:bf16(257) = 256,bf16(1 + 10⁻³) = 1——主权重和优化器状态仍要 FP32,账本还是 16 字节/参数。
  4. 第三代 FP8 + 细粒度块缩放 Micikevicius et al. 2022(格式)· DeepSeek-V3 2024(超大规模训练) 你在这里
    解决了激活每 1 × 128、权重每 128 × 128 一个缩放:张量 [1000, 0.01, 0.02, 0.5] 里,0.01 的误差从整张量缩放的 12.8% 降到 0.45%;DeepSeek-V3 相对 BF16 的 loss 相对误差 < 0.25%(官方自报)。
    卡在只有 256 个位型,E4M3 最大 448;块的形状要跟着离群值走——激活梯度改按 128 × 128 块量化,16B MoE 训到约 300B token 发散(附录 B.2);主权重和梯度仍是 FP32。
  5. 岔路 · 2025–2026 4 位:MX / NVFP4 块缩放 + QAT OCP MX 2023 · NVFP4 2025 · Kimi K3 / DeepSeek-V4 2026
    解决了MXFP4 每 32 个数共用一个 2 的幂(4.25 位/参数),NVFP4 每 16 个数一个 E4M3 缩放;K3、DeepSeek-V4 在后训练阶段对 MoE 专家权重做 QAT。
    卡在E2M1 只有 16 个码点:块 [0.3, −1.2, 2.7, 0.05] 用 MX 缩放后,0.05 → 0;全 FP4 预训练仍是前沿。
  6. 另一半账 激活:重计算与 FlashAttention Chen et al. 2016 · Korthikanti et al. 2022 · Dao et al. 2022
    解决了不把 s × s 的注意力分数存下来:锚点一条 4,096 token 的序列,激活从 162.5 GiB 降到 42.5 GiB(选择性重计算 / FlashAttention);全量重计算每层只存输入,只剩 2.5 GiB(标准块公式近似)。
    卡在重计算用多算一遍前向换显存;可 444.5 GB 的模型状态一张 80 GB 卡仍然放不下 → C10 并行。
数字口径:舍入结果在构建时按 IEEE 754 / OCP 规则现算(先存 FP32,再就近舍入、平局取偶);显存由 anchor-model.json 的 param_total 现算,1 GB = 10⁹ 字节;激活按 Korthikanti et al. 2022 式 1(标准块近似,s = 4,096、b = 1);DeepSeek-V3 的数字出自其技术报告 §3.3 与附录 B(本地 PDF,官方自报)。

为什么要 8 位:DeepSeek-V3 把线性层的三个矩阵乘(前向 Fprop、求激活梯度的 Dgrad、求权重梯度的 Wgrad)都放进 FP8,报告说这在理论上让计算速度比 BF16 翻倍 。例子沿用前两课的做法:追踪具体的数,贯穿例句不进入这一层。

8 位里一共有哪些数

8 位有两种常用分法:E4M3(4 位指数、3 位尾数)和 E5M2(5 位指数、2 位尾数)。把它们能表示的每一个正数画成一根刻度:

FP8 一共只有 256 个位型:每个可表示值一根刻度

🎯

试三件事:① 数一数 1 到 2 之间的刻度:E4M3 有 8 根(间距 0.125),E5M2 只有 4 根;② 点「E4M3」展开全位型表,找到 NaN:只有指数、尾数全为 1 的那一个位型(正负各一,0x7F 与 0xFF)——E4M3 把 IEEE 留给 inf 的那一整行拿来多表示了 7 个数,最大值才到 448;③ 看两条刻度带的左端:E5M2 能表示到 1.5 × 10⁻⁵,E4M3 只到 0.00195。

FP8 的格式定义出自 NVIDIA、Arm、Intel 的联合提案:E4M3 不表示 inf、只保留一个 NaN 位型,把最大值扩到 448;E5M2 沿用 IEEE 的规则,最大 57,344。提案建议权重和激活用精度更高的 E4M3,梯度用范围更大的 E5M2 。

但 E4M3 最大才 448,激活和梯度的真实数值随便就能超出——所以 FP8 没法「直接存」,必须先缩放:乘一个缩放因子,让张量的最大值刚好落到 448,量化;用的时候再除回来。

先自己设计:整张量共用一个缩放

最自然的做法:整张量取一个最大值 amax,缩放因子 = 448 ÷ amax。拿一个 4 个数的小张量试试:

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案E4M3 + 整张量一个缩放(448 ÷ amax)
手算小张量:整张量一个缩放

张量是教学构造:8 个 token × 256 个通道,固定种子的标准正态数,其中第 17、90 两个通道(同在前 128 个通道里)整体乘离群倍数 k——模拟「少数通道数值特别大」,不是任何模型的真实激活。量化、反量化、误差全部由浏览器实算(元素格式与 FloatLab 同一套编解码,开发时与 torch 的 float8_e4m3fn 逐值对照)。

这里有一个容易讲错的地方,值得停下来说准:浮点数在正规区里的相对精度处处一样。E4M3 有 3 位尾数,不管数在 0.02 附近还是 200 附近,舍入误差都在同一个相对量级——缩放因子小一点,本身不增加相对误差。离群值真正的害处是把同一块里的其他数推出正规区的下沿:块最大值被放到 448,正规区最小是 2⁻⁶ = 0.015625,比块最大值小 448 ÷ 2⁻⁶ = 28,672 倍以上的数就进了次正规区,有效位变少;再小就直接变 0。手算例里 1000 和 0.01 差了 10 万倍,远远越过这条线。

🎯

在 8 × 256 的张量上试三件事:① 把离群倍数 k 拖到 1,000 以下:四种粒度的误差几乎一样——没有越过 28,672 倍那条线;② 拖到 10,000 以上:「整张量」那一行落进次正规区的比例成片上升(k = 10,000 时约一半),「1 × 128」只有含离群通道的那一半受影响,「1 × 32」受影响的块更少;③ 点「1 × 128」那一行看热力图:两个离群通道都在前 128 个通道里,坏掉的只有左半张,后 128 个通道完全不受影响。

改进:DeepSeek-V3 的细粒度缩放

DeepSeek-V3 的做法是把「一个缩放管整张量」换成「一个缩放管一小块」,而且激活和权重切法不同:

DeepSeek-V3 发布的 config 里就写着这个块大小:quantization_config 的 fmt = e4m3,weight_block_size = 128, 128。把一次前向的矩阵乘画成张量,用它一个路由专家的 up 投影的真实维度:

DeepSeek-V3 一个路由专家的 up 投影,用 FP8 细粒度缩放来算(config 真实维度)
Y = (Xq ⊙ sX) · (Wq ⊙ sW)
输入参数(可学习)输出中间结果
边长按对数比例缩放,块边上的数字是真实值;同一维度符号的边在全图等长。
维度含义 真实规模 小规模
T 这一批进入该专家的 token 数(这里取贯穿例句的 5 个,作为输入规模) 5 3
K 输入维 = hidden_size 7168 8
N 输出维 = 路由专家的中间维(moe_intermediate_size) 2048 8
Kb K 方向的块数 = K ÷ 128 56 2
Nb N 方向的块数 = N ÷ 128 16 2
维度取自 DeepSeek-V3 官方 config(frontier-models.json):hidden_size = 7168,moe_intermediate_size = 2048,weight_block_size = [128, 128]。缩放因子的个数:每个 token 56 个,整块权重 56 × 16 = 896 个——相对 14,680,064 个权重,可以忽略。

还有两处配套,缺一不可:

结果:在约 16B 参数训 1.33T token、约 230B 参数训 0.9T token 两档 MoE 模型上,FP8 训练相对 BF16 基线的 loss 相对误差始终低于 0.25%;报告称这是首次在超大规模模型上验证 FP8 训练 。

新的失败:块的形状要跟着离群值走

DeepSeek-V3 的附录记了一次失败:为了省事,他们试过让激活梯度也像权重那样按 128 × 128 的块量化(反向时只需转置,不用重新分组)。结果在一个约 16B 参数的 MoE 模型上训到约 300B token 时发散。他们的解释是:激活梯度在不同 token 之间极不均衡,离群值是「跟着 token 走」的,一个 128 × 128 的块横跨 128 个 token,一个坏 token 拖累整块 。

这正是上面热力图里看到的事:缩放粒度的形状要和离群值的形状对齐。离群值按通道分布,按通道切;按 token 分布,就得按 token 切。DeepSeek 自己后来也在收窄块:V4.1-Flash 的 config 里 weight_block_size 变成 [32, 32],缩放格式是 ue8m0(只存 2 的幂的 8 位指数),路由专家用 fp4——那是岔路课的 4 位。

什么没有变成 8 位

FP8 训练不是「所有东西都存 8 位」。DeepSeek-V3 明确保留在高精度的有:embedding、输出头、MoE 门控、归一化算子、注意力算子(BF16 或 FP32);主权重、权重梯度仍是 FP32;Adam 的 m、v 改用 BF16,「没有可观察到的性能下降」 。所以第 1 课的账本变成 4 + 4 + 2 + 2 = 12 字节/参数(不含计算用的低精度副本):

按 DeepSeek-V3 的做法记账(以锚点为例)

FP8 真正省下的是矩阵乘的算力与带宽,以及以 FP8 缓存的那部分激活;模型状态只从 16 字节降到 12 字节。报告也说,这些高精度部分的显存开销可以通过在数据并行的各卡之间分片来摊薄——那是 C10 的事。

⚠

常见误解一:「FP8 训练 = 所有东西都用 8 位存。」

8 位的是矩阵乘的输入(和一部分缓存的激活);主权重、梯度是 FP32,m、v 是 BF16,敏感算子留在 BF16 / FP32。账本是 12 字节/参数,不是 1。

常见误解二:「缩放因子取整张量的最大值就行。」

整张量一个缩放,等于让最大的那个离群值替整张量决定网格。越过 E4M3 正规区的动态范围(约 2.9 万倍)之后,小数成片掉进次正规区甚至变 0;缩放要按块做,块的形状还要跟着离群值走(附录 B.2 的发散)。

常见误解三:「E4M3 一定比 E5M2 好。」

两者是精度与范围的取舍:E4M3 多一位尾数,E5M2 多一位指数。没有细粒度缩放时,梯度需要 E5M2 的范围(FP8 提案的推荐);DeepSeek-V3 能全用 E4M3,前提正是一小块一个缩放。

证据三问

问本课的回答
① 它解决什么问题?让矩阵乘用 8 位算(理论上比 BF16 快一倍),而不被 256 个位型的窄范围拖垮
② 数学上为什么可能有效?缩放把每一块的最大值对齐到 448,块内其余数只要不小于它的 1/28,672 就留在正规区,相对精度与格式本身一致 ;块越小,「块内最大值 ÷ 最小值」越可能落在这个范围内——但这不保证训练收敛
③ 实验上为什么被选中?DeepSeek-V3 在两档 MoE 模型上 loss 相对误差 < 0.25%,随后用它训练了 671B 的正式模型

一句话

📌

8 位只有 256 个位型,E4M3 最大 448;整张量共用一个缩放,一个离群值就把小值挤进粗网格——修法是每一小块各配一个缩放,累加回到 FP32。

🚦

先停在这里:记住「FP8 要先缩放;缩放按小块做,块的形状跟着离群值走;主权重和梯度仍是 FP32」就可以进第 5 课。
再深一层:走岔路课看 4 位;或者读 DeepSeek-V3 §3.3 和附录 B.2。

下一个缺口

参数、梯度、矩阵乘都压下来了,为什么一开长序列显存还是爆?

你可能会问