跳转至

第 1 章 训练显存构成与容量分析

开场问题:8B 模型 bf16 只有 16 GB,为什么训练放不进 80 GB 的卡?

1.1 把每个参数的字节数算清楚

混合精度训练下,每个参数长期占用的显存是这样构成的:

项目 精度 字节/参数
权重 bf16 2
梯度 bf16 2
fp32 主权重 fp32 4
AdamW 一阶矩 fp32 4
AdamW 二阶矩 fp32 4
合计 16

8B 参数乘 16 字节就是 128 GB,已经超过单卡容量,而这还没算激活。开场问题的答案就在这张表里:训练显存的大头不是权重,是优化器状态——它占了 12 字节中的 8 字节,加上主权重是 12。

这张表也解释了后面两章的动机:第 4 章的 ZeRO 切的就是这 12 字节,第 8 章换优化器能直接减掉其中一份 4 字节。

1.2 激活:随 batch 和序列长度增长的那一项

反向传播需要前向的中间结果,所以它们必须留着。激活量大致正比于 batch × 序列长度 × hidden × 层数,与参数量无关。

这意味着长序列训练时,激活可以轻松超过模型状态本身。而且它是唯一一项你能通过调 batch 和序列长度直接控制的显存,所以它通常是 OOM 时第一个被动的地方。

1.3 临时张量与碎片

还有一批容易被公式漏掉的:注意力和矩阵乘的 workspace、通信缓冲区、内存分配器为了避免频繁申请而保留的空闲块、以及碎片。

实际经验是理论算到 70 GB 不代表 80 GB 的卡能跑。规划时要留 10%–20% 余量,否则会在训练跑了几小时后遇到一次偶发的峰值 OOM。

1.4 梯度累积

目标全局 batch 放不下时,把它拆成若干 micro-batch,逐个做前反向并把梯度累加起来,最后统一更新一次参数。

它把峰值激活降到单个 micro-batch 的量,代价是一次参数更新要花好几倍的时间。注意它不减少权重、梯度和优化器状态的占用——那些是常驻的。

1.5 激活重计算

前向时不保存中间激活,反向需要时重新算一遍。用 FLOPs 换显存,是长序列训练的必备手段。

两种粒度:整层重算(每个 Transformer Block 只存输入,反向时整层重算)实现简单,额外计算约 30%;选择性重算只丢弃那些"存起来贵、算起来便宜"的中间结果(比如注意力矩阵),保留矩阵乘的输出,性价比明显更好。现代框架默认走后者。

1.6 低精度训练

bf16 已经是大模型训练的默认:矩阵乘用低精度跑得快,关键累加和优化器状态保留 fp32 保住精度。之所以选 bf16 而不是 fp16,是因为它的指数位和 fp32 一样多,动态范围足够,不需要 loss scaling 这套额外机制。

FP8 训练进一步减少显存和通信,但它不是把 dtype 改个名字那么简单:需要为不同张量维护缩放因子、追踪历史范围、处理溢出,并决定哪些运算保留高精度。它是一整套数值系统。截至 2026 年 FP8 已是大规模预训练的成熟可选项,但依赖硬件支持和细粒度缩放方案;FP4 端到端预训练仍属前沿探索,不能当成默认。

第四部分还会讲 FP8 推理量化——和这里的 FP8 训练是两件事,不要混。

核心问答

Q:训练显存为什么远大于推理? 推理只需要权重和 KV;训练还要梯度、优化器状态和反向所需的全部激活。按每参数字节算,训练是推理的八倍左右。

Q:为什么优化器状态要用 fp32? 参数更新量常常比参数本身小几个数量级,低精度下这些小更新会在舍入中直接消失,长期累积的效果就没了。

Q:OOM 了先开重计算还是先减 batch? 优先重计算,因为它保住了全局 batch(也就保住了实验的可比性),再微调 micro-batch。但要同时看吞吐,不能只求"能跑"。

Q:为什么实测显存总比公式高? 公式漏掉了 workspace、通信缓冲和碎片。按公式规划时留 10%–20% 余量。