1. 为什么“万亿参数 + 强化学习”是后训练里最昂贵的组合
先把结论放这儿:万亿参数模型做强化学习后训练,如果按传统全参数方案硬推,卡数需求不是“多一点”,而是直接翻一个数量级。我见过不少团队在预训练阶段省吃俭用,结果到了 RL 阶段一下把家底全填进去。原因不是 RL 本身有多玄,而是它的资源消耗模型和预训练完全不一样。
先说强化学习后训练到底在做什么。简单讲,它是在一个已经学会“预测下一个词”的模型基础上,通过奖励信号的引导,让模型学会“在特定任务上产出更好的结果”。比如代码题要能通过测试用例,数学题要答案和推理路径都对,对话场景要更符合人类偏好。这个阶段数据量不大,但计算开销却非常夸张,因为每一轮策略更新都要先让模型自己去生成一堆候选答案,再根据奖励反馈去调整权重。
这里有一个核心差异:预训练是“读文档”,RL 是“做题加批改”。建模要生成大量文本,而且这些生成结果的好坏直接影响训练信号。一个万亿参数模型,即使只生成几百条样本,每条几百 token,算上 beam search 或多次采样,前向计算量就会把整卡集群拖住。更麻烦的是,生成结束之后还要做策略更新,这意味着模型参数要来回搬动,显存和带宽同时告急。
我们拿显存算一笔账就清楚了。假设模型总参数 1 万亿,BF16 权重也要 2TB 起步。全参数训练时,除了权重本身,还要有 FP32 主权重(4TB)、Adam 的两个动量项(各 4TB)、梯度(2TB),加一起接近 16TB 的额外状态。注意,这 16TB 还不算任何激活值、KV cache 和临时缓冲。也就是说,全参数训练一个万亿模型,光优化器状态就要 200 张 80GB 的卡才勉强装得下,紧接着前向反向的通信开销又会把可用带宽吃光,实际集群规模远不止这个数。
而强化学习还有一个额外的讨厌之处:需要同时维护策略模型和参考模型。参考模型用于计算 KL 散度,防止策略在优化奖励时彻底漂移。参考模型不训练,但推理要跑,所以权重要常驻显存。如果策略和参考模型都按全参维护,显存直接翻倍。一个 1 万亿参数的全参 RL 工程,现实里至少要四五十台以上 8 卡机器才能转起来,而且转起来之后吞吐率还不一定好看。这种模式基本是资源自杀式玩法。
所以我看到“用 10% GPU 跑通万亿参数 RL”这种方案时,第一反应不是质疑,而是想拆开看它到底省在哪。事实证明,它并不是什么魔法,而是把 RL 的各个环节重新拆解了一遍,把“哪些参数必须更新”“哪些计算必须同步”“哪些显存可以省掉”这三个问题想透了。后面几节我就按这个思路展开。
2. 核心思路:把“全参数 RL”拆成并行流水线的三刀
2.1 第一刀:冻结主干,只训练 LoRA 增量
第一个决策,也是整个方案的基石:策略模型主干全部冻结,只训练 LoRA 低秩适配器。LoRA 的数学形式不复杂——对权重矩阵 W 附加一个低秩分解 BA,前向变成 Wx + BAx,训练时只更新 B 和 A 这两个小矩阵。它的可训练参数量通常只有主干的 0.1% 到 1%,也就是万亿模型只更新大约十亿到百亿级别的参数。
这一刀下去,优化器状态直接从 16TB 缩到几十 GB。哪怕 LoRA 参数有 20 亿,优化器状态也就 32GB 左右,一张卡就能放下。训练侧的显存压力瞬间消失,反向传播只需要把梯度传到 LoRA 矩阵就停下,冻结部分根本不用保存梯度。这对显存是降维打击。
有人担心 LoRA 会不会影响效果。我直接说,在后训练阶段,基座模型已经具备很强的语言能力和知识储备,RL 要改变的是输出策略,是“如何组织答案、往哪个方向搜索推理路径”,这种变化是模块化的,低秩适配完全够用。数学推理、代码生成、指令遵循类任务,LoRA 微调的表现已经被大量验证过。当然,如果任务是让模型大规模吸收新知识或改变事实性记忆,那 LoRA 可能不够,但那是预训练或持续预训练的事,不属于 RL 后训练的典型场景。
2.2 第二刀:把 rollout 生成和策略更新拆成异步流水线
全参数 RL 最常见的浪费点在于同步等待。传统 PPO 流程里,模型先生成一批样本,然后立即用这批样本计算 loss,更新一次策略,再等策略更新完继续采样。每一步都被最慢的环节卡住。在万亿参数规模下,生成一段长文本可能要几秒到几十秒,而训练一步可能只需要几百毫秒,两边速度完全不匹配。如果强制同步,GPU 大部分时间都在空转。
方案的做法是把 rollout 生成和策略更新拆成两条独立流水线,中间用一个经验池缓冲。推理侧常驻策略模型的当前权重,持续从任务池里取 prompt,批量生成样本,写入经验池。训练侧从经验池里取最新样本做策略更新,更新完把最新的 LoRA 权重同步给推理侧。两边各跑各的,谁也不用等谁。
这个设计的关键在于“延迟更新”的容错。推理侧拿到的策略版本永远比训练侧落后几步,这样会产生一些 off-policy 偏差。但 RL 本身就有一定的 off-policy 容忍度,而且我们在后面的 KL 修正里会补这部分偏差。实测下来,只要把滞后步数控制在合理范围内,效果反而更稳,因为生成样本的多样性更好了,不会因为策略在某一小步里过拟合而突然产出同质化内容。
2.3 第三刀:激活重计算加优化器状态 offload,再抠一块显存
冻结主干之后,训练侧仍然要跑完整的前向反向,激活值依然会占不少显存。万亿模型的 hidden state 哪怕只有几万维,在长序列、大 batch 下也可能把显存重新顶爆。解决方案是开启激活重计算,也就是前向时只存必要中间量,反向时再重新计算一遍。这是典型的“用计算换显存”,在冻结主干场景下非常划算,因为反向后不需要更新主干权重,重计算出来的梯度只用喂给 LoRA 部分。
优化器状态 offload 是再进一步:把 LoRA 的优化器状态放到 CPU 内存或者 NVMe 缓存里,每步只拷贝需要的部分到 GPU 更新。LoRA 参数本来就小,这个开销很低。不过我们实际跑下来发现,当 LoRA 参数量在 5 亿以下时,offload 收益不明显,直接放显存更省事。只有当 LoRA 做到二三十亿参数时,offload 才值得开。
2.4 一张表看配额:10% 的 GPU 到底省在哪
经常有人问“10% 是卡数少 90% 还是显存少 90%”。我们按卡数算。对比同一个万亿参数基座模型做数学推理 RL,全参数方案和 LoRA 方案的资源大致如下:
| 资源项 | 全参数方案 | LoRA 方案 | 说明 |
|---|---|---|---|
| 训练侧优化器状态 | 约 16TB | 约 50GB | 这是最大的差距 |
| 训练侧梯度 | 约 2TB | 忽略不计 | 冻结部分不回传梯度 |
| 参考模型 | 需要另存一份完整权重 | 与主干共用,只加一层命中统计 | 省掉一整份权重 |
| rollout 阶段 | 与训练共用同一批卡,互相等待 | 独立推理池,持续产出 | 空闲时间被填满 |
| 预计卡数 | 40 台 8 卡起步 | 4 台 8 卡可以跑通 | 这就是 10% 的来源 |
这张表我想强调最后一行。不是说 LoRA 方案只需要 10% 的算力,而是它把资源需求里最不合理的部分——全参优化器状态和同步等待——去掉了。剩余的开销主要是骨干网络的前向计算和 KV cache 存储,这些在推理阶段本来就要花,RL 只是把它复用了一遍。所以“10% GPU”不是压缩出来的,是精算出来的。
3. 10% GPU 资源的真实跑法与关键配置
3.1 资源清单与实际规模
方案复现时我们用的配置大概是:一个总参数 1.2T 的 MoE 基座模型,激活参数约 300B,单卡 80GB,8 卡一台机器。训练侧用 3 台机器,推理侧用 1 台机器,合计 4 台 32 张卡。作为对比,之前同项目做全参数 PPO 预演时,方案规划是 32 台起的,实际因为集群限制根本没跑起来。这个差距就是“10%”的现实写照。
MoE 结构帮了不少忙。万亿参数听起来吓人,但每次生成 token 只激活一部分专家,实际参与计算的参数远小于总量。混合多头注意力和路由部分仍然要全部加载,但计算压力至少缓解了一半。如果改成稠密 1 万亿模型,4 台机器是跑不动的,至少得把训练侧加到 8 台,再靠 4bit 量化主干才能勉强塞下。所以这套方案对 MoE 特别友好,标题里说的“万亿参数”基本默认是 MoE。
3.2 核心配置示例
训练侧的 LoRA 配置和 RL 超参大概长这样。这个配置是我们反复试出来的基线,不同任务可以在此基础上微调:
model: base_dtype: bf16 activation_checkpointing: true load_in_8bit: false lora: r: 32 alpha: 64 dropout: 0.1 target_modules: ["q_proj", "v_proj", "o_proj", "gate_proj"] optimizer: lr: 2e-5 weight_decay: 0.01 lr_scheduler: cosine warmup_steps: 50 rl: kl_coef: 0.01 clip_range: 0.2 batch_size_per_update: 256 rollout_buffer_size: 4096 max_policy_update_lag: 32 reward_shaping: function_validation几个参数我想特别解释一下:
max_policy_update_lag: 32是异步流水线的安全阀。它表示训练侧最多可以比推理侧领先 32 个优化步,超过这个数就暂停训练,等推理侧把新策略权重同步过去。这个值设太小,流水线就卡成同步了;设太大,off-policy 偏差会失控。32 是我们这个吞吐量下的甜点位。
kl_coef: 0.01看起来不小,但对于有明确验证器的任务很合适。奖励信号是稀疏但准确的,KL 系数大一点可以有效防止策略跑到奖励函数的盲区里去“钻空子”。如果任务的奖励是学习出来的 RM,那这个系数可能要降到 0.001 甚至更低,否则策略会变得特别保守,什么都不敢生成。
alpha: 64是 LoRA 的缩放系数。我们试过 alpha 等于 r,也试过两倍 r。alpha 太小,适配器更新幅度不够,RL 信号推不动策略;alpha 太大,几十步之后 KL 就开始飙升。alpha 设为 r 的两倍是我们实测中平衡点最好的选择。
3.3 推理侧怎么同时 serve LoRA 权重
推理侧不能简单拿原始主干权重去生成,必须把最新的 LoRA 适配器合并进去。这里有一个容易被忽略的工程点:如果推理引擎支持动态加载 LoRA,确实方便,但频繁更新适配器会有额外开销。我们实际做法是让推理侧定期拉取训练侧产出的 LoRA checkpoint,先把权重合并进主干副本,再刷新推理引擎。合并一次权重在单机上只需要十几秒,但能保证生成吞吐始终最高。
奖励计算也单独做了优化。数学和代码类任务可以用函数验证器,也就是让模型生成代码或公式,直接用测试用例跑结果,成本几乎为零。只有涉及开放式写作或对话偏好的任务,才需要加载一个单独的奖励模型。奖励模型也绝不能用和策略同规模的模型,我们用的是一类相对小的二分类头部,只在最后一层输出分数,单卡显存占用不到 20GB。
4. 实测中的三个翻车现场与对应修复
这套方案听着清爽,实际跑起来还是踩了不少坑。挑三个最典型的翻车现场说,每个都对应一类容易被忽略的问题。
4.1 翻车一:rollout 吞吐突然崩掉,卡在采样端而非训练端
第一次联调时,预期推理侧每秒生成的 token 数量应该能支撑训练侧持续消费,结果一启动就发现训练侧经常在等数据。查了半天,问题不在显存也不是网络,而是我们把 LoRA 权重以“动态插拔”的方式挂在了推理引擎里。推理引擎每次生成都会动态计算 LoRA 增量,这个操作在引擎内部没有真正优化,导致前向时间比合并权重后慢了将近三倍。
修复很简单:把 LoRA 合并进主干权重再加载。这个动作如果手工做,每步都要重启推理引擎,但实际训练时我们不是每步都刷新推理侧,而是每积累二十到三十个优化步才同步一次权重。这样一次合并的开销会被大量生成任务摊平,吞吐自然就回满了。做这套方案之前,最好先确认一下目标推理引擎对权重更新的支持方式,不要想当然地认为热插拔适配器一定更快。
4.2 翻车二:KL 直接崩了,策略输出比起始模型偏到没法看
中间有一轮实验,我们把 LoRA 学习率调到了 1e-4,KL 系数还维持之前的 0.001。结果跑了不到两百步,模型输出的平均长度明显变短,答案风格也变得极其单一,一看 KL 曲线已经飙到初始值的十倍以上。问题本质是:LoRA 虽然参数量小,但因为只更新一小部分权重,同等学习率下对输出的影响反而会被放大,再加上低秩结构会让某些方向更新特别激进,KL 一失守,策略就开始坍缩。
修复动作有两个。第一,把学习率降到 2e-5,和主干预训练阶段的微调学习率保持一个量级甚至更低;第二,把 KL 系数从 0.001 提回 0.01,并且开启 KL 的实时监控,超过阈值自动报警。这一步的经验是:LoRA + RL 场景,学习率比 KL 系数更敏感,宁可学习率小一点、多跑几步,也别让它推猛了再往回拉。
4.3 翻车三:延迟策略反而救了训练稳定性,但滞后太大也会震荡
异步流水线跑起来之后,我们注意到一个小规律:当训练侧滞后推理侧大约二十步时,训练 loss 曲线很平稳;但当某一轮 rollout 特别长、滞后超过五十步后,指标反而开始振荡。细想其实就是 off-policy 偏差的双向作用。适度滞后时,旧策略生成的样本相当于天然的探索噪声,能帮训练跳出局部震荡;滞后太多时,样本分布和当前策略差异过大,KL 修正已经补不齐了,损失函数就跟着乱跳。
这个坑没有一次性修复方案,只有运行时观察和调参。我们在配置里加上了max_policy_update_lag,并且让推理侧把每个批次生成时的策略版本号也写进经验池,训练侧按版本号过滤样本,超过阈值的直接丢弃。这样即使推理侧偶尔卡顿,训练侧也不会消费过期太久的样本。实测之后,loss 和奖励曲线的稳定性都明显改善。
这三个翻车有一个共同点:都不是模型结构的问题,而是异步训练系统中“状态同步”的问题。跑这套方案时,建议多留几个监控指标,尤其是 rollout 吞吐、策略版本号差距、平均 KL 这三个,它们比 loss 值更能说明系统是否健康。
5. 这套方案能抄到哪里,哪些场景别硬搬
5.1 适合直接复用的条件
先总结适合这套方案的场景特征。第一,基座模型已经足够稳定,语言能力和基础知识都过关,RL 只是定向提升某个能力;第二,奖励信号可以自动化,验证器、测试用例、格式规则都算,不需要大规模人工标注;第三,输出文本长度中等,不至于每轮生成几万 token 把 KV cache 直接打爆;第四,任务目标是能力增强型,比如数学、代码、逻辑推理,而不是价值观对齐或风格大改。
我们的实践里,最顺的是代码生成任务。模型输出一段代码,直接用一组单元测试判断正确率,奖励信号又稀疏又硬,LoRA 更新起来方向感非常强。数学题类似,只要验证器写得好,整个 RL 流程几乎不需要人工介入。这类项目完全可以按刚才的配置抄作业。
5.2 别硬搬的场合
需要明确的是,这套方案不是万能钥匙。如果任务是做安全对齐,比如让模型学会拒绝某些请求,这往往需要让模型整体行为发生改变,低秩适配可能只改动了表面风格,深层价值观并没有真正调整。全参数训练在这种场景下仍然无法替代,哪怕它贵。
如果模型还需要大规模注入新知识,比如某个垂直行业的数据要持续学习,这也别用 RL 加 LoRA。知识性修改适合持续预训练或 SFT 阶段做,硬塞进 RL 流程只会又贵又低效。再一个是超长上下文任务,比如让模型基于几十万字文档做分析,每轮的 KV cache 就可能超过百 GB,推理侧哪怕只有一小撮卡也扛不住,这种情况下与其上异步流水线,不如先把序列长度和生成长度砍下来。
5.3 下一步可以怎么扩展
这套方案的扩展方向其实很明确。一个方向是压缩推理侧资源,把主干权重量化到 8bit 甚至 4bit 再合并 LoRA,前提是接受一点训练稳定性损失;另一个方向是把推理侧做成多机池,用更多小机器扛更长上下文的 rollout;还有一个方向是训练侧多任务并行,同一个推理池同时服务多个 LoRA 任务的样本生成,进一步提升 GPU 利用率。后两个我都在陆续试,后面有结论再拿出来聊。
说到底,用 10% GPU 跑通万亿参数 RL,靠的是把“训练思维”转成“流水线思维”。全参数训练时我们盯着梯度、优化器、并行策略,但 RL 后训练真正烧钱的地方是重复生成和等待,而不是参数的微调。想省资源,先想清楚哪部分参数真正需要更新,再把生成与训练之间的时间差利用起来。这个思路本身,比任何具体配置都值钱。