阶段 6 · 让模型跑起来

显存账本:训练一个模型
到底要多少显卡

这是全书最实用的一章。上一章讲了怎么把模型切开,这一章回答更前置的问题: 到底需要多大?以及——为什么一个 7B 的模型,微调时光是权重、梯度和优化器状态就要 112 GB, 而"跑"它只需要 14 GB?

1

先记住那个数字:16 倍

一条值得背下来的经验:用 Adam 训练一个模型,需要的显存大约是参数量的 16 倍 (单位:字节/参数)。推理只需要 2 倍。这个 16 是混合精度下的账:权重和梯度用 16 位, 优化器状态用 32 位。

为什么是 16 倍:每一份都要单独存

为什么要分成五份?权重(BF16,2 字节)是模型本身;梯度(BF16,2 字节)是反向传播算出的方向; 参数主副本(FP32,4 字节)是另存的一份精确权重——16 位下每次更新都会被舍入吃掉一点, 小更新累不起来,所以用 FP32 主副本累积,更新完再转回 16 位;Adam 再用两份 FP32(各 4 字节) 记住梯度的滑动平均(一阶动量 m)和它的波动幅度(二阶动量 v)。五份加起来,每个参数 16 字节。 (FP32 / FP16 / BF16 里的数字是位数,FP 指浮点。)

💡 三个必须记住的比例

这一章后面所有的账,都从上面这张图里长出来。
① 推理 : 训练 = 1 : 8(2 字节 vs 16 字节)。这就是为什么微调比推理贵得多。
② 优化器状态占训练的 3/4(12 字节 / 16 字节)。省显存第一刀永远砍这里。
③ 权重本身只占 1/8。你为模型大小准备的显存,只是实际需求的零头。

🎯 类比

像请一个厨师做菜。"模型权重"是食材(2 份),"梯度"是他记下的笔记(2 份), 但真正占地方的是他的厨房设备——炒锅、备料盆、调味罐(12 份)。
你请的是一个厨师,但要准备的是一整套厨房。 这个类比管到「账目多寡」为止:厨房设备不会因为客人变多而变多,而优化器状态是每个参数固定的 12 字节。

2

亲手算一遍:你的卡够不够

下面把四项分开算。激活值那一项特别值得注意——它主要随 batch、序列长度、层数和隐藏维度涨,和参数占的那 16 字节不是一回事, 在长上下文场景下常常是最大的一块。前面那个 112 GB 只算权重、梯度、优化器,加上激活值才是下面的合计。

互动 · 显存账本计算器(真实逐项计算)

⚠️ 激活值这一项要说清楚

权重、梯度、优化器状态都是精确可算的——就是参数量的倍数。
但激活值取决于实现细节:用不用 FlashAttention、有没有重计算、 框架怎么安排算子和释放时机。差 2~3 倍很正常。
所以这一章把激活值标成"数量级估算",而前三项是精确值。 看到别人说"7B 微调要 80GB"或者"要 160GB",两个都可能对——差的不只是激活值怎么管, 还包括换没换省显存的手段:8 位优化器和 LoRA 会直接改掉固定成本那一大块。
换个优化器自己看:SGD + 动量没有二阶动量 v,每参数从 16 字节降到 12。

同样一个 7B,三种用法差多少

试试这几个真实的场景

7B · 全量微调 · 序列 4096 权重梯度优化器就要 112 GB —— 单张 80GB 的卡根本放不下,还没算激活值。
7B · LoRA 微调 · 序列 4096 只训 1% 的参数 → 优化器状态几乎消失。总需求降到 约 27 GB, 单卡就能跑。这就是 LoRA 真正的价值。
7B · 只是推理 只要权重 14 GB。但注意:如果把序列拉到 128K,KV Cache 会变成新的大头 ——这一项在下一章(推理与 KV Cache)。

换个卡自己套:24 GB 卡上,纯固定成本按 16 字节/参数算,参数量上限约 24 ÷ 16 = 1.5B,还没算激活值。

M

数学 · 训练贵的 8 倍买了什么

第 1 节说「训练要 16 字节/参数,推理只要 2」。这一节把那 16 字节拆开摆出来—— 而且下面每一根柱子都是当场按公式算的,你拖滑块它就变。

互动 · 六根柱子:前五根一起动,第六根自己动

🎯 用「团队出差」理解这两笔账

固定成本(前五根)= 你带了几个人出差,每个人都要买一张票。人越多,票钱越多,和住几天无关。
可变成本(第六根)= 每个人要在外面住几天,天数越多,住宿费越多。
这两笔账不是完全互不相干:人数一多,每个人带的行李也跟着变重。 对应到模型上,参数量变大时隐藏维度和层数也会变,激活值跟着涨——只是涨得比固定成本慢得多。 所以显存不够时,先搞清楚是哪一笔爆了,再决定砍什么。

🎬 自己验一遍:激活值什么时候反超模型本身

把「序列长度」一路往右拖。前面那五根一动不动,第六根成倍往上翻—— 在很长上下文下,激活值会比模型本身那 112 GB(还没算激活值)还贵。
这就是为什么第 2 节那个「7B 全量微调要 125 GB」之外,人们还常常被再吓一跳。

3

混合精度:为什么不全用 FP32

混合精度几乎不省参数侧的显存。两笔账都是 16 字节/参数:
· 全 FP32:权重 4 + 梯度 4 + 一阶动量 4 + 二阶动量 4 = 16——没有单独的主副本,权重自己就是那份精确副本。
· 混合精度:权重 2 + 梯度 2 + FP32 主副本 4 + 一阶动量 4 + 二阶动量 4 = 16。
两笔账里,跟更新绑在一起的那 12 字节(精确权重/主副本 4 + 动量 4 + 方差 4)都占 3/4,「省显存第一刀砍优化器状态」对两者都成立。混合精度真正买的是速度和激活值: 16 位张量核心的矩阵乘比 FP32 快得多(A100:312 vs 19.5 TFLOPS),激活值只要一半。所以业界都做"混合":计算用 16 位,状态用 32 位。
每个浮点数由三部分组成:符号位(正负)、指数位(决定能表示多大的范围)、 尾数位(决定能表示多细)。下面这张表按这三段拆开比。

格式位数(符号+指数+尾数)指数位尾数位能表示的范围精度
FP3232(1+8+23)823 ±3.4e38约 7 位十进制
TF3219(1+8+10,存储 32)810 ±3.4e38计算用,存储还是 32 位
BF1616(1+8+7)87 ±3.4e38约 2~3 位十进制
FP1616(1+5+10)510 ±65504约 3~4 位十进制
FP8(E4M3)8(1+4+3)43 ±448很粗,但训练大规模模型时够用

三种格式的位分配:差别就在那几格“指数”里

每一格是一个比特。“指数”那一段决定能表示多大的范围,“尾数”那一段决定能表示多细。看图就明白:BF16 把指数原封不动留下,FP16 却把它砍到了 5 格。

互动 · 同一把尺子上的“相对精度”:越小的地方谁先坏掉

看"能表示的范围"那一栏。BF16 的指数位和 FP32 完全一样(8 位), 所以它能表示的范围和 FP32 一模一样,只是尾数短、精度低。FP16 只有 5 位指数, 最大只能到 65504,最小正规数只有 6.1e-5,范围窄了 30 多个数量级。 一个数小到表示不了叫下溢,大到表示不了叫溢出。
所以 BF16 是"范围换精度",FP16 是"精度换范围"。 深度学习里范围比精度重要得多——梯度可以算得糙一点,但绝不能变成 0 或者溢出。 这就是今天几乎所有模型都用 BF16 的原因。

4

亲手看一次 FP16 下溢

下面做的是真实的位级转换——把你的输入按 IEEE 754(浮点数的国际标准)的规则 真的转成 FP16 / BF16 / FP32 的位模式,不是查表。拖滑块把一个数变小,看它什么时候变成 0。

互动 · 位级转换与下溢(含损失缩放)

💡 你会看到三条线

FP32:一直到 1e-45 都还有值。
BF16:到 1e-38 附近才归零——和 FP32 几乎一样宽。
FP16:低于 6.1e-5 就进入"非正规数"区间,精度急剧下降; 低于 5.96e-8 就直接变成 0。

一个梯度值真的变成 0,对随机梯度下降是这一步收不到新信号;对 Adam 只是丢了一次信息, 旧的动量还能让它动一点。但被碾平的那部分精度,两种优化器都找不回来。

🎯 类比

像用一把最小刻度是 1 毫米的尺子去量一根头发丝。量不出来不是因为你不会量, 是这把尺子根本表示不了这么小的数。
FP16 就是那把尺子。而"损失缩放"就是先把头发丝放大 1024 倍再量, 量完再除回去。

一张图看清损失缩放这四步

梯度就是损失对每个参数的导数,所以损失乘 S、梯度也乘 S。 注意一个精妙之处:损失缩放并没有让 FP16 的精度变好,它只是绕开了 FP16 表达不了的那个区间。 缩放完,相对精度还是那个相对精度。

5

六种省显存的手段,各自的代价

手段省什么省多少代价
混合精度 权重和梯度从 FP32 降到 16 位 权重和梯度各减半(主副本补回 4 字节,参数侧不省) 几乎免费,还更快。今天没有理由不做
梯度累积 不用真的放大 batch,用多次小 batch 累加梯度 激活值 ÷ 累积步数 训练变慢(同样的数据量要跑更多步)。但数学上等价于大 batch
梯度检查点
activation checkpointing
反向传播时不保存中间激活,需要时重新算一遍 激活值常能省 60~80% 慢 20~30%。用时间换空间。要选好切分点—— 分段太少省不了,分段太多重算开销大
优化器状态量化 Adam 的一阶动量和二阶动量降到 8 位(主副本仍 FP32) 12 → 6 字节/参数 可能影响收敛,需要专门的量化实现(8-bit Adam)
CPU 卸载
offload
把优化器状态搬到内存里,用的时候再传回来 显存省一大块 受 PCIe 带宽限制,可能慢好几倍。适合"显存不够但又必须训"的场景
LoRA / 参数高效微调 只训练 0.1%~1% 的参数 优化器状态几乎归零 只能改行为,不能往里灌新知识(见阶段 5 那一章)

显存不够时,这六步从哪一步开始试(越往下越慢 / 越难)

6

数值格式的进化

互动 · 数值精度时间轴

每种格式能表示多大范围:为什么 FP16 会被淘汰

格式什么时候用注意
FP32参数主副本、优化器状态、损失计算 基准。不用来存激活值和权重(太浪费)
TF32NVIDIA Ampere 之后的默认矩阵乘法精度 改个开关就能提速,几乎无损——最容易被忽略的一次免费优化
BF16权重、梯度、激活值。今天的绝对主流 范围足够大,基本不用担心下溢。不需要损失缩放
FP16老一代的混合精度方案 必须配损失缩放,否则梯度会变成 0
FP8H100 之后的新选项,用于超大规模训练 需要精细的缩放策略(按张量或按块)。已有大规模成功案例
INT8 / INT4推理量化 主要给推理用,训练的梯度不适合用整数表示
⚠️ 一个容易忽略的免费优化

如果你在用 NVIDIA 的卡训练,检查一下 TF32 有没有打开。 它让矩阵乘法用 19 位而不是 32 位算,速度提升明显,精度损失在大多数任务上看不出来。
做过实验的话你会发现:这是性价比最高的一次改动——改一行配置,什么都不用重构。

7

每一种省法都在别处付账

你省下了你在哪里付账什么时候真的会痛 → 换什么
用 BF16 主要省激活值(约一半)和搬运量;参数侧几乎不省 数值精度降到 2~3 位十进制 对数值敏感的任务(某些强化学习、小规模微调)→ 留 FP32 主副本兜底
用梯度检查点省 70% 激活 训练速度慢 20~30% 算力本来就紧、多花的时间换不回显存 → 先减小 batch 或梯度累积
减小 batch 省激活 梯度噪声变大,归一化层的统计量不稳 需要稳定收敛 → 用梯度累积凑回大 batch
用 8 位优化器省状态 收敛可能变差,还多一层实现复杂度 大模型对精度敏感 → 换回 FP32 Adam,或只对部分状态用 8 位
CPU 卸载省显存 慢好几倍,瓶颈从显存换成 PCIe 要反复迭代训练 → 换成多卡 ZeRO / FSDP
LoRA 省掉优化器状态 只能学"怎么做",不能学"知道什么" 要往里灌新知识 → 全量微调或继续预训练
⚠️ 最不可控的一项是激活值

权重、梯度、优化器状态都是精确可算的,所以你总能提前规划。 但激活值受实现影响极大:
· 用不用 FlashAttention —— 影响注意力的中间矩阵(长序列下差异巨大)
· 用不用重计算 —— 影响 2~5 倍
· 框架怎么安排算子 —— 影响峰值还是均值
所以真实的显存规划,不能只靠公式,必须实测。 惯用做法是:先按公式估一个下界,然后在目标硬件上跑一个最小配置, 实测峰值显存,再线性外推。

8

小结

它对应哪条线 ① 表达力 vs 泛化——不过这一章看的是它的背面。
这条线平时说的是「表达力过剩会过拟合」;这里说的是:表达力过剩意味着精度可以当零钱花。 把每个数字从 32 位砍到 16 位、把优化器动量砍到 8 位,模型学出来的东西几乎不变—— 因为神经网络的泛化对参数的微小扰动本来就不敏感。
一句话 这一章本质上是在把「学习」拆成两笔账:记住结果(推理,2 字节/参数) 和 记住过程(训练,16 字节/参数)。训练贵的那 8 倍, 买的不是知识,是「能回头改」的能力——反向传播要求你把走过的每一步都留在显存里。
它牺牲了什么 牺牲了数值安全边际:BF16 只有 7 位尾数, FP16 低到 5.96e-8 以下直接归零,8 位优化器可能掉收敛质量。 你省下的每一字节,都在数值范围或收敛速度里还回去(回扣暗线 E)。
🎬 自己验一遍:你现在能指着哪个互动说这句话

回到第 2 节那个「显存账本计算器」。把「序列长度」滑块从 4096 一路往右拖到 32K, 盯着「激活值」那一项(橙色那段)——它会成倍往上翻,而旁边的「权重」一动不动。

那一栏涨的就是「为了能回头」付的钱:权重、梯度、优化器状态是为「记住过程」付的 固定成本,而激活值是为「每一步的中间结果」付的可变成本。 然后把「梯度检查点」从「关」切到「开」——你会看到激活值掉下去一块, 代价写在正文里(慢 20~30%)。那一下的涨和落,就是你刚才拖滑块时在做的事。

它在暗线上站在哪

这一章是账本章,回答集中在「参数账本」和「它假设了什么」两行。

暗线这一章的回答
C 参数账本 7B 模型逐项算:权重 2×7 = 14 GB,梯度 2×7 = 14 GB, FP32 主副本 4×7 = 28 GB,Adam 的一阶 + 二阶动量 4×7 + 4×7 = 56 GB—— 固定成本合计 16 × 7 = 112 GB,单张 80GB 的卡根本放不下,还没算激活值。
激活值(用本章计算器的同一套公式,B=1、L=4096、h=4096、32 层): ≈ 2 × 4096 × 4096 × 32 × 12 = 12.9 GB;把 L 拉到 32K 就是 约 103 GB—— 比模型本身还贵得多。只推理只要 14 GB;LoRA 优化器状态几乎归零,总需求约 27 GB。
E 它假设了什么 假设「能回头」是必要的——也就是默认用反向传播。 页面上那几种省法(检查点、量化、卸载、LoRA)全都是在反向传播这个框架里做优化, 没有一条是「不用回头」。
第二个假设更隐蔽:它假设数值精度的损失是可以容忍的。 这在绝大多数视觉/语言任务上成立,但在数值敏感的场景(某些强化学习、 小规模精确微调)不成立——所以那些场景必须留 FP32 主副本兜底。
🎯 前后钩子

它接住了前面阶段的什么:并行训练那一章的 ZeRO / FSDP 切的就是这里的四份状态; 没有这一章的 16 字节账,ZeRO 的三个 stage 到底在切什么就是背下来的。

它给后面几章留了什么:训练算完了,服务的时候显存又变成了瓶颈—— 只不过换成了另一个东西:《推理与 KV Cache》 和 《服务化与吞吐》。

一句话带走显存与精度

记住 16 倍:Adam 训练一个模型要参数量的 16 倍字节,其中优化器状态占 3/4、 权重只占 1/8,数值格式用 BF16 而不是 FP16——BF16 的指数和 FP32 一样宽,不会下溢。 省显存按 混合精度 → 小 batch / 梯度累积 → 梯度检查点 → LoRA → ZeRO → CPU 卸载 的顺序上, 每一步都在别处付账;公式只给下界,激活值那一项必须上机器实测。

9

拓展阅读

上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。

📄 这一章的说法从哪来

💻 工业界怎么写

∑ 更严格的形式