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

训练一个参数,要几字节?

一个参数几字节
推理时一个参数 2 字节,训练时是 16 字节:16 位的权重和梯度各 2,FP32 主权重与 Adam 的 m、v 各 4。锚点 Qwen3.8-27B 光模型状态就是 444.5 GB,是一张 80 GB 卡的 5.6 倍,还没算激活。
❓

本课一问:C8 让模型放大了也不炸。真要训练锚点模型 Qwen3.8-27B,第一个问题变成「放不放得下」——训练时,一个参数到底要占几个字节?

这一课先不碰:每个数该用几位、为什么(第 2–4 课);激活为什么比参数还大(第 5 课);一张卡放不下时怎么分到多张卡上(C10)。

从已有出发

C4 的岔路课把锚点模型的参数一个个数清了:27,781,427,952 个。C5 讲 Adam 时留了一句「m、v 各存一份 FP32,完整的显存账在 C9」。这一课把这本账算完。

贯穿全课程的那句话「小明把苹果给了小王」,在 Qwen3.8 里是 5 个 token。设想用它训练一步:取一批 → 前向 → loss → 反向 → 优化器 → 更新。先别管 16 位还是 8 位,就按最朴素的 FP32 训练,看这一步在显存里留下了什么:

从已有出发 · 训练一步在显存里留下什么(FP32 全精度) 本课点任一环节看它在 Qwen/Qwen3.8-27B 上占多少

点「反向传播」「优化器」「更新 θ」三个环节:梯度、m、v、权重,每一份都和参数一样多。这一批数据本身只有 5 个整数,占显存的是它流过模型时留下的东西。

精度与显存的演进:每砍一次位宽,都在一个真数字上失败一次
  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,官方自报)。

先自己设计:只算权重

最先想到的算法:参数有多少个,乘上每个参数的字节数。锚点 Qwen3.8 的权重以 BF16 发布,每个数 2 字节:

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案只算权重:参数个数 × 2 字节
只算权重的账

改进:把训练一步要放的东西列全

训练一步要同时放在显存里的「模型状态」有五份,每一份都和参数一样多(下面按混合精度训练的标准做法记账,为什么要这样混着用,第 2 课讲):

这一份谁用它格式每参数字节锚点合计
16 位权重前向、反向的矩阵乘BF16 / FP16255.6 GB
16 位梯度反向算出来,交给优化器BF16 / FP16255.6 GB
FP32 主权重优化器在它上面做更新,再转成下一步的 16 位权重FP324111.1 GB
Adam 一阶矩 m梯度的滑动平均(C5 第 2 课)FP324111.1 GB
Adam 二阶矩 v梯度平方的滑动平均FP324111.1 GB
合计16444.5 GB

这正是 ZeRO 论文给出的记账方式,把公式逐项拆开:

混合精度 + Adam 的模型状态 · Rajbhandari et al. 2019(ZeRO)§3.1
+ + =
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
「优化器状态」这 12 字节占模型状态的 12 ÷ 16 = 75%——旧版课程说「一半以上」,精确地说是四分之三(不算激活时)。

这个账是纯算术 ,ZeRO 论文用同样的算法得出「7.5B 模型的模型状态需要 120 GB」 。锚点的账:444.5 GB,一张 80 GB 的卡要 5.6 张才放得下——还一个激活都没存。

🎯

试三件事:① 在上面「训练一步的账」里切到「FP32 + AdamW」:4 + 4 + 4 + 4,合计还是 16 字节,一个字节都没省;② 点堆叠条上最宽的几段,读出它们的代入式——m、v、FP32 主权重三段合起来是 333.4 GB;③ 再切回「推理:只存 BF16 权重」,看训练和推理差了多少倍(8 倍)。

⚠

常见误解一:「显存主要花在参数上。」

参数本身(16 位权重)只占 16 字节里的 2 字节。训练时最大的一块是优化器状态,FP32 主权重、m、v 三份合计 12 字节,是参数本身的 6 倍。这也是后面那些「省显存」的办法最先砍的地方:把 m、v 存成 BF16(DeepSeek-V3)、只存一份动量(Muon)、或者把它们切到多张卡上(C10 的 ZeRO)。

常见误解二:「混合精度把训练显存减半。」

对模型状态来说不是:FP32 训练 4 + 4 + 4 + 4 = 16,混合精度 2 + 2 + 4 + 4 + 4 也是 16。混合精度原论文确实说总显存「几乎减半」,但它说的是另一件事——FP32 主权重让权重那部分多了 50%,而它的模型里显存大头是激活,激活改存 16 位就省下了一半 。它省下的是矩阵乘的带宽与算力(16 位的 Tensor Core 快得多)和激活(第 5 课),不是这 16 字节。

常见误解三:「推理跑得动,训练就放得下。」

推理只存权重(外加 KV cache,C3 第 8 课),55.6 GB;训练的模型状态是它的 8 倍,444.5 GB。

🔍 为什么还要一份 FP32 主权重?16 位的权重不是已经在了吗?(第 2 课兑现)

因为更新太小。一个权重 1.0 每步只该动 10⁻⁴ 左右,可 16 位格式在 1 附近的间距是 2⁻¹⁰(FP16)或 2⁻⁷(BF16),比这次更新大得多——直接加在 16 位权重上,舍入回来还是 1.0,这一步白走了。所以更新在 FP32 的主权重上做,做完再转一份 16 位的给下一步前向用。第 2 课会在浏览器里让同一个 w 连加 1,000 次,看它在三种格式里各停在哪。

同一本账的几种记法

堆叠条的「方案」下拉框里还有三种记法,都是真实存在的做法:

极小真数字:我们自己的模型

锚点的数太大,换我们从 0 训练的小模型算一遍:3,688,704 个参数,按 FP32 + AdamW 训练(项目基线就是 FP32),16 × 3,688,704 = 56.3 MiB。

我们的小模型:模型状态的账

这 56.3 MiB 要和实测对账:实验脚本分阶段读显存(建模型后 / 前向后 / 反向后 / 第一步后),差额里最大的一块是什么,第 5 课和实验课回答。

项目实测 · 显存截面(与 16 字节/参数对账)

证据三问

问本课的回答
① 它解决什么问题?训练前先知道「放不放得下」:模型状态 = 参数个数 × 每参数字节数,混合精度 + Adam 是 16
② 数学上为什么是这个数?每一份状态都和参数同形,字节数直接相加
③ 实验上呢?这本账不需要实验来支持;需要实验回答的是「能不能少存几份、存得更粗」而不伤训练——第 2–4 课和 DeepSeek-V3 的做法

一句话

📌

训练时一个参数不是 2 字节而是 16 字节:16 位的权重和梯度各 2,FP32 主权重与 Adam 的 m、v 各 4;锚点 27.8B 就是 444.5 GB,还没算激活。

🚦

先停在这里:记住「推理 2 字节、训练 16 字节,最大的一块是 12 字节的 FP32 优化器状态」就可以进下一课。
再深一层:在上面的堆叠条里把四种模型、六种方案都切一遍,再展开「为什么还要一份 FP32 主权重」。

下一个缺口

16 字节里有 12 字节是 FP32。能不能全换成 16 位? → 第 2 课:全换成 FP16,为什么会丢梯度?

两处回扣先记下:这本账怎么切到多张卡上,C10 第 2 课(ZeRO)兑现;激活那一段现在还没画进来,第 5 课补上。

你可能会问