想象你是一位话剧导演,要排练一出大型舞台剧。舞台上需要四位演员同台:主角(Actor)负责念台词、做动作;导演助理(Critic)站在侧台,给主角的每一句表现打分、指出改进方向;一位“标准答案”演员(Reference)拿着一册原著,随时提醒主角“你离原版台词偏了多远”;还有一位观众代表(Reward)竖着大拇指,告诉你观众到底爱吃哪一套。 这出戏的“舞台”就是 GPU 显存。舞台就这么大,四名演员连同他们的道具、提词卡、排练笔记都要挤在上面,一不留神就“舞台超载”——在 AI 训练里,这就是我们常说的 OOM(显存耗尽)崩溃。为什么 PPO 训练大模型特别费显存?因为一次训练要同时养活四个模型!本文就用“排练舞台剧”这条线,带你算清楚每个“演员”各占多少“舞台面积”,再教你用混合精度和 ZeRO 等“压缩道具”的办法,把显存省到能安全演出。
对 AI 从业者而言,掌握四模型显存估算是 RLHF 工程的“预算基本功”。能提前算清显存、规划 GPU 资源,就能避免训练到一半 OOM 崩溃,是决定大模型项目能否交付的硬技能。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
显存分解: PPO-RLHF训练中Actor/Critic/Reference/Reward四模型的显存占用分析与优化策略
四模型概览
你走进排练厅,看到四位演员已经就位:主角手里攥着厚厚一沓新台词(Actor 模型),导演助理拿着记分板准备对每个动作打分(Critic 模型),一位“原著复读机”捧着原版剧本随时比对(Reference 模型),还有一位观众代表按着脉搏感受“这届观众买不买账”(Reward 模型)。四者同台,各司其职,这就是 PPO(近端策略优化)训练大语言模型的标准配置。
在 PPO 的 RLHF(基于人类反馈的强化学习)流程里,Actor 是唯一被持续更新的“学生”,它每产出一句回答,Critic 就立刻评估“这句值多少分”,Reward 模型给出奖励信号,Reference 模型则负责计算 KL 散度,防止主角跑调跑得太远。四个模型协同完成“采样—评估—更新—再采样”的循环。
显存占用由参数量、精度和训练状态三因素决定。通常 Actor 和 Reference 是同一个尺寸的底座模型(如 7B),Critic 是较小的价值网络(如 0.4B),Reward 则是更小的奖励模型(如 0.3B)。规模差距直接决定了显存占比:主角和原著复读机是“大块头”,而评论家和观众代表是“轻量级”。
Actor显存
主角演员是全场最“耗空间”的角色。他不仅要站在台上(存储权重),还要在排练中一遍遍对台词(计算梯度),更要把每一次改进心得记成厚厚的排练笔记(优化器状态)。这三样东西,在深度学习里统称“模型状态”。如果我们用 N 表示模型参数量,那么纯 FP32 训练时,仅凭参数本身就要占 4N 字节,梯度同样要占 4N 字节,而 Adam 优化器还要为一阶矩和二阶矩各存一份 4N 字节——加起来就是 16N 字节。
明明生活中我们每天都要做注意力管理,AI 却是在“背”每一个词的笔记——对每个参数,Adam 都要记录“上一次往哪个方向改、改了多少”(一阶矩),以及“这个参数到底多难学”(二阶矩)。这也是思维导图里“Adam 状态占用”最扎心的一点:它比权重本身还要大 2 倍。
1. 权重占位:参数本身以 4 字节单精度存储,占 4N 字节。
2. 梯度占位:反向传播要算出每个参数的修正量,同样 4N 字节。
3. 一阶矩笔记:Adam 记录梯度指数移动平均,即“惯性方向”,再占 4N。
4. 二阶矩笔记:记录梯度平方的移动平均,即“对每个参数的学习节奏”,又占 4N。
如果把这个模型换成 7B 参数,16N 就是 112GB——一块 A100-80GB 根本放不下!好在实际训练中我们几乎不会用纯 FP32 硬扛,而是用“轻装版剧本”(BF16 混合精度),把权重和梯度各减半到 2N,这样模型状态就降到 14+14+42=70GB,勉强挤进 80GB 的舞台。正因优化器状态占了大头,显存优化的首要目标就是“压缩排练笔记”。
Critic显存
导演助理(Critic)的任务是给主角每一句台词打分。他也要“揣摩演技”,也就是说 Critic 模型同样需要反向传播更新参数,所以它同样要准备权重、梯度和优化器状态的“三件套”。但好在助理体型小(通常只有 Actor 的 1/10 到 1/20),参数存储本身不算惊人。
然而 Critic 有一个隐藏的大胃口:前向激活值。在计算“这句台词值几分”的时候,每一层的中间结果都要先缓存下来,等反向传播时再取用。激活值大小和批次大小、序列长度、层数都成正比。当输入序列很长时,这部分的量级可能超过权重本身。这也是思维导图“前向激活开销”这一节点的要害:模型小 ≠ 显存一定小,关键在于激活值这座“临时道具山”堆得多高。
1. 层数堆叠:每层 Transformer 都要保存一份中间激活,层数 L 越大,叠加越多。
2. 序列长度放大:序列越长,注意力矩阵越庞大,激活值与长度 S 成线性甚至平方关系。
3. 批次数倍:批次越大,同时处理的样本越多,显存按 B 倍增长。
在工程实践中,我们经常会发现 Critic 虽小,但激活值却让它“站得比预想的宽”。应对办法是使用梯度检查点(用时间换空间):不缓存全部激活,反向传播时重算一次前向。这样激活显存能从 O(L × S × B × D) 降到 O(L × S × B),非常适合长序列任务。
Ref与Reward
两位“裁判”就好演了。Reference 模型(原著复读机)和 Reward 模型(观众代表)全程只需“品评”,不需“练习”——它们不做反向传播、不更新参数。相比之下,它们只需要权重和前向过程中的激活缓存,不需要梯度,更不需要优化器状态。这一下子就省掉了约 70% 的训练显存开销。
RLHF 的奖励构造依赖一个关键公式:Reward = Reward 模型打分 − β × KL(策略 || Ref),KL 散度正是由 Ref 和 Actor 的输出分布共同算出的。由于 Ref 和 Reward 都只需要前向推理,它们甚至可以用 FP16/BF16 加载并冻结参数,进一步压低显存。唯一要注意的是:它们的前向激活仍然需要保留,用于计算 Reward 和 KL 信号。
1. Ref 权重:以半精度存储,约 2N(N 为 Ref 参数量)。
2. Reward 权重:同样按半精度存储,约 2N。
3. 无梯度项:两者都不计算梯度,所以没有 M_grads 和 M_optimizer。
在以 7B Actor 为基准的例子里,Ref 约占 14GB(按 BF16),Reward 只有不到 1GB,而同时刻的 Actor 光是优化器状态就扛着 42GB。所以聪明的主播(工程师)往往会想:既然裁判不训练,能否把裁判换一张更小的推理卡?这样就能给主角腾出宝贵的训练舞台。
显存公式
把整个舞台画成一张总平面图:总显存 = 模型状态 + 激活值 + 临时缓冲区。后者包括通信 buffer、CUDA context 等“杂项道具”。理解总公式,是资源规划的第一步。
1. 模型状态项:权重 + 梯度 + 优化器状态,是静态显存主体。
2. 激活值项:随批次 × 序列长度 × 层数动态变化。
3. 缓冲区项:包括 AllReduce 通信缓冲和临时张量,通常预留 10%~20%。
拿 7B、BF16、Adam 优化器举例:模型状态 = 14 + 14 + 42 = 70GB;激活值按常见 batch=1、seq=1024 大约 5~15GB;再加上通信缓冲约 10%。总显存 = 70 + 10 + 8 ≈ 88GB,已经超过单卡 80GB。这就是为什么单卡训练 7B 级别的 PPO 非常局促,工程师必须启动混合精度 + ZeRO + 检查点等“减负三板斧”。
混合精度
为了把“16N“压缩成”10N“,道具组想出一个妙招:剧本用大字版(FP32)珍藏在保险柜里(Master 权重),排练时演员只带轻便的小抄(BF16 副本)。这就是混合精度的核心思路:权重、梯度和激活都用 16 位半精度参与计算,但维护一份 FP32 的”权威主权重”用于更新。
1. BF16 权重:模型加载半精度副本,占 2N 字节。
2. BF16 梯度:反向传播算出的梯度也以半精度存储,占 2N。
3. FP32 主权重:每个参数仍保留一份 4N 的高精度主副本,保证收敛稳定。
4. 优化器状态:Adam 的一阶、二阶矩仍建议 FP32 保存,占 8N。
为什么不用纯 FP16?因为梯度太小会“溢出”变成零,就像音量旋钮扭到最低就听不见了。这时梯度缩放器(Grad Scaler)会在反向传播前把梯度放大,更新后再缩回去,确保小梯度也能被听见。BF16 因为保留了 8 位指数位,动态范围更宽,在 LLM 训练中比 FP16 更受欢迎。总之,混合精度相当于把“16 系行李”减少到“10~12 系”,省出的空间立竿见影。
ZeRO策略
当单张舞台实在挤不下,聪明的导演会租用多间排练室,把剧本、道具和笔记分散存放,排练时再“隔空取物”。这就是 ZeRO(零冗余优化器)的思想:把优化器状态、梯度乃至参数切分成碎片,分给 P 张卡各自保管,从而把单卡显存压到原来的 1/P。
1. 每卡必存权重副本:前向计算时需要完整权重,因此 2N 字节是保底的。
2. 切分模型状态:梯度 + 优化器状态共 12N 字节被切成 P 片,每卡只拿 1/P。
3. P 越大越省:卡数从 1 增至 8,12N/P 从 12N 降到 1.5N,单卡显存趋于 2N 下限。
在 DeepSpeed 中,ZeRO-1 只切优化器状态,ZeRO-2 再加切梯度,ZeRO-3 连参数都切开(此时权重从“每卡一份”变成“全局仅一份”)。ZeRO-3 省得最多,但通信开销也最大,因为每次前向都要 AllGather 权重。结合思维导图的“道具分仓”比喻:组员越多,每人带的道具越少,但互相借道具的跑来跑去也更频繁。
显存规划
掌握了上述知识,最后一步就是“排班表”:先算总量,再留缓冲,最后设警报。工程上建议至少预留 20%~30% 的余量,因为激活值、临时张量和 CUDA context 常在训练中途突然膨胀。假设你计划用 8×A100-80GB 训练 7B 模型,先用 10~12N 的混合精度公式粗算:模型状态 70GB,再加激活和缓冲约 88GB——单卡不够,但 ZeRO-3 + 梯度检查点能把它压到 20GB 以下,8 卡绰绰有余。
还有几个省钱利器:梯度检查点用重计算换显存,可将激活显存减少约一半以上;Flash Attention 优化注意力计算,让长序列不再“爆心跳”;KV Cache 也要算进推理环节的显存,尤其在 PPO 采样时。规划时建议在训练脚本里加上显存监控,一旦超过阈值就自动减小 batch 或触发 offload。
1. 安全系数:显存余量取 1.2~1.3,防止临时峰值击穿。
2. 总显存乘系数:把前文三项相加后再乘 k,得到推荐显存预算。
OOM 一旦发生,训练进程会直接崩溃,几小时的心血瞬间归零。所以“先算后训、留足余量、实时监控”是 RLHF 工程师的保命三原则。好消息是,各家框架(DeepSpeed、HF TRL、Megatron)都已内置显存预估工具,团队排卡前跑一遍估算脚本,就能把崩溃风险挡在上线之前。
AI 应用场景
RLHF 训练排卡与资源预估
阶段:模型训练 / 资源规划
解决的问题:传统做 RLHF 训练时,工程师常直接凭经验开卡,结果训练到一半 OOM 崩溃,浪费数小时算力。利用本文的显存公式,在启动前就能按模型参数量、Batch Size、序列长度估算出四个模型的总显存,精确决定需要几张 A100/H100,以及要不要开混合精度和 ZeRO。
具体案例:某团队要在 4×A100-80GB 上训练 7B 模型的 PPO。先用公式估算:Actor 模型状态 70GB + Critic 激活 10GB + Ref 占 14GB + Reward 占 1GB ≈ 95GB,单卡必然 OOM。于是启动 ZeRO-3 + BF16 混合精度,把单卡压到 25GB 以下,同时开启梯度检查点,训练 3 天未崩一次。
PPO 采样阶段的显存调度
阶段:数据采样 / 推理缓存
解决的问题:PPO 训练中需要反复“让主角上台表演”(采样),此时 KV Cache 瞬时挤占显存。传统做法只盯训练显存,采样一多就现 OOM。通过显存监测与动态 batch 缩放,可以让采样阶段自动降低 batch size,保证四个模型“同台不打架”。
具体案例:某大模型平台在 PPO 采样循环中加入显存水位监控,当总显存超过 75% 时自动把采样 batch 从 16 降到 8,并把 Ref 模型临时 offload 到 CPU;训练结束后再恢复,整体吞吐仅下降 5%,但 OOM 次数降为零。
Python 演示
import torchimport torch.nn as nnimport torch.optim as optim class PolicyNet(nn.Module): def __init__(self, dim=64): super().__init__() self.fc = nn.Sequential(nn.Linear(dim, 128), nn.Tanh(), nn.Linear(128, dim)) def forward(self, x): return self.fc(x) class ValueNet(nn.Module): def __init__(self, dim=64): super().__init__() self.fc = nn.Sequential(nn.Linear(dim, 64), nn.Tanh(), nn.Linear(64, 1)) def forward(self, x): return self.fc(x) def estimate_gb(model, dtype_bytes=2): total = sum(p.numel() for p in model.parameters()) return { "params": total, "weights_gb": total * dtype_bytes / 1e9, "optimizer_gb": total * 8 / 1e9, } def ppo_step(actor, critic, ref_model, reward_model, states, actions, returns, old_logp, beta=0.05): logits = actor(states) logp = nn.functional.log_softmax(logits, dim=-1).gather(1, actions.unsqueeze(-1)).squeeze(-1) ref_logp = nn.functional.log_softmax(ref_model(states), dim=-1).gather(1, actions.unsqueeze(-1)).squeeze(-1) rewards = reward_model(states).squeeze(-1).detach() kl = torch.mean(logp - ref_logp) adv = rewards + beta * kl ratio = torch.exp(logp - old_logp) loss = -torch.mean(torch.clamp(ratio, 0.8, 1.2) * adv.detach()) + 0.1 * torch.mean((returns - critic(states).squeeze(-1)) ** 2) return loss def main(): actor = PolicyNet() critic = ValueNet() ref = PolicyNet() reward = ValueNet() ref.eval() reward.eval() for m in (ref, reward): for p in m.parameters(): p.requires_grad = False opt = optim.Adam(actor.parameters(), lr=3e-4) states = torch.randn(32, 64) actions = torch.randint(0, 64, (32,)) returns = torch.randn(32) old_logp = torch.randn(32) for _ in range(20): opt.zero_grad() loss = ppo_step(actor, critic, ref, reward, states, actions, returns, old_logp) loss.backward() opt.step() for name, model in (("Actor", actor), ("Critic", critic), ("Ref", ref), ("Reward", reward)): info = estimate_gb(model) print(name, "params:", info["params"], "weights(GB):", round(info["weights_gb"], 4), "optimizer(GB):", round(info["optimizer_gb"], 4)) if __name__ == "__main__": main()
class PolicyNet(nn.Module)定义 Actor / Ref 共用的策略网络,模拟生成动作的“主角”。class ValueNet(nn.Module)定义 Critic / Reward 共用的网络,模拟打分和给奖励的“裁判”。def estimate_gb按 BF16 权重(2 字节)+ Adam 优化器(8 字节)估算显存。beta * klKL 散度系数,限制 Actor 偏离 Ref 太远,对应“别把原著改得面目全非”。torch.clamp(ratio, 0.8, 1.2)PPO 的截断(clip)核心,防止策略一步跨太大导致训练崩溃。for m in (ref, reward)冻结 Ref 与 Reward,正是“裁判不训练”的工程实现。
常见误区
误区我刚学 PPO 时以为 7B 模型就是 7GB 或 14GB(按 FP16),那 80GB 显卡不是随便跑吗?为什么大家总说显存不够?
正解权重只是冰山一角。训练 7B 模型时,BF16 权重占 14GB,梯度占 14GB,Adam 的优化器状态却要占 42GB——光模型状态就 70GB,再算上激活值很可能直接 OOM。权重之外还有梯度和优化器笔记,后者才是“隐形的显存杀手”。
为何常见:很多人用“推理显存”的直觉来推算“训练显存”,没把反向传播和优化器考虑进账。推理只要一前一后,训练却要“三份账本”同时摊开。
误区开了 AMP 半精度训练,是不是所有显存自动减半?为什么有时候省下来的数量很有限?
正解混合精度会让权重和梯度减半,但 Adam 优化器状态仍以 FP32 保存(8N 字节),主权重也要保留 FP32 副本(4N 字节)。所以模型状态从 16N 降到 12N 左右,而不是 8N。节省约 25%~30%,并非 50%,如果用了 offload 或 ZeRO 才会进一步压缩。
为何常见:误以为“全精度换半精度”是一刀切。实际上为保证收敛稳定,工程框架通常保留高精度主权重与优化器状态。
误区既然 ZeRO 把状态切成 P 片,那我从 1 卡加到 2 卡,每卡显存就该是原来一半吧?
正解ZeRO 只切“12N/P“的优化器状态与梯度部分,而”2N 的权重副本”每卡仍要保留(ZeRO-3 才进一步切权重)。所以单卡显存是 2N + 12N/P,从 P=1 到 P=2 时显存从 14N 降到 8N,接近减半;但 P 越大,减幅越缓,最终逼近 2N 的下限。
为何常见:忽视 ZeRO 阶段的本质差异,把“全员无冗余”当成了“全员无限比例下降”。
误区Reward 模型才 0.3B,参数量连 Actor 的零头都不到,显存应该不重要吧?
正解Reward 模型的权重显存虽小,但它在 RLHF 中要执行大量前向推理,激活值与 KV Cache 会随序列长度和采样 batch 显著增长。若同时开多个采样器,Reward 的激活缓存可能高达数 GB,不能完全无视。更关键的是 Ref 模型若也是 7B,它的 14GB 权重是必须纳入预算的。
为何常见:只按参数量估算,忽略了“推理激活和并发采样”的放大效应。裁判一动不动地坐着,但观众席的“饮料零食”(KV Cache)也在堆。
面试题
Q1. PPO 训练 LLM 时,以下哪个模型需要同时保存权重、梯度和优化器状态?
- A.Actor 模型← 正确答案
- B.Reference 模型
- C.Reward 模型
- D.全部四个模型
答案:A。只有 Actor 作为策略网络参与训练更新,同时保存权重、梯度和优化器状态。Reference 和 Reward 只做前向推理,不计算梯度,不更新参数,因此没有优化器状态。想象一下,只有主角在“背台词练动作”,两位裁判只负责观看打分,不需要排练笔记。
Q2. 纯 FP32 训练一个参数为 N 的模型,仅模型状态(权重+梯度+Adam优化器)至少需要多少字节?
- A.4N
- B.8N
- C.12N
- D.16N← 正确答案
答案:D。纯 FP32 下,权重 4N、梯度 4N、Adam 一阶矩 4N、二阶矩 4N,合计 16N。这就是“舞台道具四件套”:剧本、批注、排练笔记一、排练笔记二,每样都要 4N。8N 只算了权重+梯度,缺少优化器两项。
Q3. 7B 模型使用 BF16 混合精度 + Adam 优化器训练,其模型状态约多少 GB?(按 1e9 近似)
- A.14GB
- B.28GB
- C.70GB← 正确答案
- D.112GB
答案:C。BF16 混合精度下,权重=2N=14GB,梯度=2N=14GB,优化器状态=8N=56GB(FP32 保存),合计 70GB。14GB 只算了权重,28GB 漏掉优化器,112GB 是纯 FP32 的数值。优化器状态占了 56GB,说明“排练笔记”才是最大包袱。
Q4. 以下哪些做法可以降低 PPO 训练中的显存占用?
- A.使用 BF16 混合精度← 正确答案
- B.开启 ZeRO 状态切分← 正确答案
- C.增加 Batch Size
- D.使用梯度检查点← 正确答案
答案:ABD。混合精度把权重和梯度减半;ZeRO 切分优化器状态与梯度;梯度检查点重算前向,减少激活缓存。增大 Batch Size 反而让激活和梯度更大,是显存压力上升的主因之一,应避免。
Q5. 在 RLHF 中,Reference(Ref)模型与 Reward 模型最大的显存优势是?
- A.参数很小,只有 0.1B
- B.不需要反向传播和优化器状态← 正确答案
- C.可以全部放在 CPU 上
- D.不需要前向传播
答案:B。两个裁判模型只需做前向推断,不参与梯度计算,因此没有梯度和优化器状态。它们通常与 Actor 同尺寸(Ref 可能 7B),但显存占用远小于 Actor,正是因为“站着看戏不排练”。CPU offload 也可行,但不是其固有优势。
Q6. ZeRO-3 相比 ZeRO-1 多切分了哪部分状态?
- A.模型参数← 正确答案
- B.激活值
- C.CUDA context
- D.数据集
答案:A。ZeRO-1 只切优化器状态,ZeRO-2 切优化器+梯度,ZeRO-3 连模型参数也切分开来。激活值属于动态显存,不被 ZeRO 的“状态切分”机制处理;数据集更不在切分范围。
Q7. 7B 模型,BF16 混合精度 + Adam,使用 ZeRO-3 在 8 卡上训练,每卡模型状态理论约为多少?(不计激活)
- A.约 2.75GB
- B.约 12.5GB← 正确答案
- C.约 70GB
- D.约 112GB
答案:B。ZeRO-3 下模型状态接近 14N/P=14×7/8=12.25GB,再加少量冗余约 12.5GB。若记权重也被切分,各卡只需在需要时做 AllGather 取回。70GB 是单卡无 ZeRO 的数值,112GB 是纯 FP32 无优化。8 卡分摊后,单卡压力从 70GB 骤降到约 12.5GB。
Q8. PPO 训练中 Critic 模型的显存开销与哪些因素直接相关?
- A.Critic 模型的参数量← 正确答案
- B.采样序列的长度← 正确答案
- C.训练时 Batch Size 大小← 正确答案
- D.Reward 模型的参数量
答案:ABC。Critic 需要更新参数,权重、梯度和优化器状态由参数量决定;其前向激活随序列长度和 Batch Size 增长。Reward 模型的参数量影响的是奖励模型的显存,不影响 Critic。
Q9. 工程上为了防止训练中途 OOM,建议预留多少显存余量?
- A.0%~5%
- B.10% 以内
- C.20%~30%← 正确答案
- D.50% 以上
答案:C。CUDA context、临时缓冲和激活峰值可能中途膨胀,预留 20%~30% 是公认安全线。0~5% 毫无缓冲,50% 则让 GPU 利用率过低,浪费算力。好的导演总在舞台四周留安全通道。
Q10. 关于混合精度训练(BF16 + FP32 Master 权重),下列说法正确的有?
- A.梯度以 BF16 存储,反向传播时可能精度受限← 正确答案
- B.FP32 Master 权重的存在是为了保证数值更新稳定← 正确答案
- C.优化器一阶矩和二阶矩通常仍以 FP32 保存← 正确答案
- D.使用混合精度后显存一定降为原来的四分之一
答案:ABC。混合精度把权重和梯度以 16 位存取,精度低于 FP32,因此保留 FP32 Master 权重在每步更新时使用;Adam 矩估计也一般保留 FP32。显存从 16N 降到约 12N,即原来的 75%,并非四分之一。A 的前半句是真实存在的权衡,也是工程师做 loss scaling 的原因之一。