训练 Infra 08:从 SFT 到 RL——一套 infra 两个入口
RL 相比 SFT 新增的三块部件(rollout 引擎、reward 服务、权重同步),以及一个反直觉的约束:把卡分给 rollout 会让训练侧的专家 DP 组变小,16 卡训练直接 OOM——所以这 40 张卡最多只能让出一个节点。
训练 Infra 08:从 SFT infra 到 RL infra
本节是大模型训练 Infra 实战课的最后一节。前七节全部围绕 SFT,这一节讲 RL 阶段会变什么、不变什么。
好消息先说:前七节学的东西,在 RL 阶段有八成直接复用。 显存账的算法、并行的整除约束、NCCL 的死锁诊断、checkpoint 的 Young/Daly、脏环境治理——一条都不浪费。
坏消息是那两成新增的部分,恰好是整个训练 infra 里最脏的地方。
1. RL 相比 SFT 多了什么
SFT 的一步
读一批数据 → 前向 → 反向 → 优化器更新
数据是离线的、固定的。这是前七节的全部假设。
RL 的一步
1. 从 prompt 集采样一批 prompt
2. rollout : 用当前策略生成 k 个候选响应 ← 新
3. reward : 给每个响应打分(规则/模型/人类偏好) ← 新
4. 计算优势(advantage)、构造训练样本
5. train : 前向 → 反向 → 优化器更新
6. 权重同步: 把新权重推回 rollout 引擎 ← 新
数据变成在线生成的,而且生成器就是被训练的模型本身。 这一个改动引入了三块新部件:
| 新增 | 性质 | 最难的地方 |
|---|---|---|
| rollout 引擎 | 自回归 decode,memory-bound | 和训练抢显存 |
| reward 服务 | 常驻推理服务或规则计算 | 吞吐匹配、稳定性 |
| 权重同步 | 每步一次的大规模数据搬运 | 格式转换 + 时间开销 |
2. rollout:一个完全不同的计算形状
训练和推理的性格是相反的
回到第 1 节的 Roofline。同一个模型,两种用法:
| 训练(train) | rollout(decode) | |
|---|---|---|
| 运算强度 | 几百 ~ 上千 FLOPs/byte | 约 1 FLOPs/byte |
| 瓶颈 | 算力(compute-bound) | 带宽(memory-bound) |
| 关键资源 | tensor core | HBM 带宽 + KV cache 显存 |
| batch | 固定,越大越好 | 动态,受 KV cache 限制 |
| 优化方向 | 大 GEMM、并行切分 | continuous batching、PagedAttention |
这就是为什么不能用一个引擎干两件事(第 5 节讲过 verl 的混合引擎设计)。
H20 在 rollout 上是优势卡
第 1 节的思考题在这里得到回报。decode 时每生成 1 个 token 要把激活参数读一遍:
每 token 读取 = 3e9 参数 × 2 字节 = 6 GB
理论上限 = 显存带宽 / 6 GB
H20 : 4.00 TB/s / 6 GB = 667 tok/s/卡
H100 : 3.35 TB/s / 6 GB = 558 tok/s/卡
H20 的 rollout 理论上限比 H100 高 19%。(实际值会低不少——受 batch 大小、KV cache、attention 开销影响——但相对关系成立。)
这是个值得高兴的结论:这块"训练弱"的卡,在 RL 的 rollout 阶段是强卡。 而 RL 里 rollout 常常占掉一半以上的墙钟时间,所以 H20 在 RL 上的整体性价比,比在纯 SFT 上要好。
3. 显存冲突:colocate 还是分离
这是 RL infra 的第一个架构决策,而在这套集群上,它是被显存算出来的,不是拍出来的。
为什么不能简单 colocate
训练态峰值已经 85.6GB / 96GB(第 2 节)。rollout 引擎需要:
模型权重(按它自己的并行切分) + KV cache + 激活
剩下的 10.4GB 连权重都放不下。 所以 colocate 只有两条路:
- 时分复用 + offload:rollout 时把训练态(权重/梯度/优化器共 38.1GB)offload 到 CPU 内存,rollout 完再搬回来。每步搬 38GB 上下,PCIe Gen5 约 64 GB/s → 单向 0.6 秒,双向 1.2 秒。这个开销其实可以接受,但实现复杂(要处理 mcore 的分片状态),且 CPU 内存要够(每节点 8 卡 × 38GB = 304GB)。
- 分离部署:拿出若干节点专做 rollout。
分离部署的显存账(关键发现)
直觉上"分几张卡出去做 rollout"很简单。但第 3 节的整除约束会反咬一口:训练卡数减少 → DP 减少 → 专家的 DP 组变小 → 优化器状态分片变差 → 显存反而涨。
专家优化器状态 = (E/EP) × 12 B / (DP/EP)
= 4.01e9 × 12 / 专家DP组
代入不同的训练卡数(EP=8 固定,激活+临时沿用第 2 节的 47.5GB 估):
| 训练卡数 | DP | 专家 DP 组 | 优化器状态 | 常驻 | 估算峰值 | 单步耗时 |
|---|---|---|---|---|---|---|
| 40(全部) | 40 | 5 | 10.50 GB | 38.1 GB | 85.7 GB | 120 s |
| 32(让出 1 节点) | 32 | 4 | 13.12 GB | 40.8 GB | 88.3 GB | 150 s |
| 24(让出 2 节点) | 24 | 3 | 17.50 GB | 45.1 GB | 92.7 GB | 200 s |
| 16(让出 3 节点) | 16 | 2 | 26.25 GB | 53.9 GB | 101.4 GB | OOM |
结论
这 40 张卡最多只能让出一个节点(8 卡)给 rollout。
- 让出 1 节点:峰值 88.3GB,占 92%,紧但可行
- 让出 2 节点:峰值 92.7GB,占 97%,几乎没有余量,不建议——多模态数据里一个偶发的长样本就会 OOM,然后又是一次静默死锁
- 让出 3 节点:直接 OOM
注意这个约束的来源不是"卡少了算不动",而是"卡少了显存反而不够"——完全反直觉,而且只有把第 2、3 节的账算清楚才能提前发现。如果不算这一步,你会在部署 RL 的第一天撞上它。
推荐方案
32 卡训练(4 节点,DP=32,EP=8,专家DP组=4) + 8 卡 rollout(1 节点)
配套要做两件事:
- 把显存余量找回来。第 6 节的优化路径在这里从"提速"变成"生存必需":fused CE 省下的 ~13GB 直接把峰值从 88.3 拉回 75 左右,余量就舒服了。
- rollout 节点用 TP=8:单节点 8 卡,权重 70GB/8 = 8.75GB/卡,剩下的 80GB+ 全给 KV cache——这对长序列 rollout 很重要。
4. 权重同步:RL infra 最脏的一块
问题
每个 RL step 结束后,训练侧产出了新权重,rollout 引擎必须用上它。否则你就是在用旧策略采样、用新策略更新——off-policy 程度失控,训练会崩。
难点在于两边的格式和切分完全不同:
训练侧: mcore 的分片布局(TP=1, EP=8, DP=32),bf16 权重 + fp32 主参数
rollout侧: vllm 的布局(TP=8),只要 bf16 权重
所以每步都要做一次跨进程、跨格式、跨切分的 70GB 级数据搬运。
三种做法
| 方案 | 机制 | 每步耗时 | 优点 | 缺点 |
|---|---|---|---|---|
| 落盘 | 训练侧存 safetensors,rollout 侧重新加载 | NFS 1GB/s → 70 秒 | 简单、鲁棒、易调试 | 慢;NFS 抖动直接卡住训练 |
| NCCL 直传 | 建一个跨 train/rollout 的 process group,broadcast 权重 | RoCE 25GB/s → 约 3 秒 | 快 | 实现复杂;两边必须同一个 torch/NCCL;一方崩会拖死另一方 |
| CUDA IPC | 同机 colocate 时用显存句柄共享 | 亚秒 | 最快 | 仅限同机;和 offload 方案绑定 |
代入项目
落盘方案: 70 GB / 1 GB/s = 70 秒
占单步(150 秒训练 + rollout 时间)的比例可观
NCCL 直传: 70 GB / 25 GB/s ≈ 3 秒
但要求 train 和 rollout 用同一个 torch ——
这正是第 5 节 cu128 约束链的现实后果:
torch ≤ 2.11 且 vllm ≤ 0.19 必须是同一个 torch
这就是为什么第 5 节要那么强调"rollout 和 train 必须共享 torch 版本"。 不是洁癖,是 NCCL 直传的前提条件。如果版本对不上,你就只能退回落盘方案,每步多付 70 秒。
一个常被忽略的坑:同步的原子性
权重同步过程中,rollout 引擎处于"半新半旧"状态。如果这时有请求进来,它会用混合权重生成——这种数据污染极难发现,因为生成的东西看起来完全正常。
正确做法:
1. rollout 引擎进入 "draining" 状态,拒绝新请求,等在途请求完成
2. 执行权重更新
3. 校验(比如比对一个 layer 的 checksum)
4. 恢复接受请求
第 3 步的校验值得做。权重同步静默失败(比如某个 layer 没更新到)是 RL 训练"莫名不收敛"的经典原因之一。
5. reward 服务
三种 reward 来源
| 类型 | 例子 | infra 要求 |
|---|---|---|
| 规则/可验证 | 代码跑单测、数学答案比对、格式检查 | 沙箱、超时控制、并发 |
| reward model | 训练好的打分模型 | 常驻推理服务,吞吐匹配 rollout |
| LLM judge | 用更强的模型打分 | 同上,且可能是外部 API |
吞吐必须匹配
这是最容易翻车的地方。假设一个 RL step:
batch 64 prompt × 每 prompt 8 个候选 = 512 个响应
如果 judge 单个响应要 2 秒,串行 → 1024 秒
而训练部分只要 150 秒
→ judge 成为瓶颈,GPU 大部分时间空着
所以 reward 服务的并发能力必须按 rollout 的产出速率来设计,而且要能水平扩展。规则类 reward 尤其要注意:跑单测的沙箱如果是串行的,会直接把整条流水线变成串行。
稳定性要求更高
reward 服务挂掉,整个 RL 训练就停了。而它通常是一堆"辅助服务",运维关注度低。建议:
- reward 服务必须有健康检查和自动重启
- 训练侧对 reward 调用要有超时和重试
- 超时的样本要有明确的降级策略(丢弃?给中性分?),且要记录比例。这个比例悄悄上升,就是训练质量在悄悄下滑
6. RL 里哪些账要重算
前七节的方法全都能用,但有几个数字要重新代入:
| 内容 | SFT 阶段 | RL 阶段的变化 |
|---|---|---|
| 显存账(第 2 节) | 85.6GB @ 40卡 | 88.3GB @ 32卡 —— 专家 DP 组从 5 变 4 |
| 并行整除(第 3 节) | EP=8 | DP=40 ✓ |
EP=8 | DP=32 ✓(但 DP=24 时 EP 只能是 8 或更小,DP=16 时 OOM) |
| 通信(第 4 节) | 占比 <3% | 加上每步 70GB 的权重同步 —— 可能从 3% 涨到 30% |
| MFU(第 6 节) | 单步 120 秒 | 分母要包含 rollout 和 reward 时间,MFU 定义本身要重新讨论 |
| checkpoint(第 7 节) | C=490s, save_steps≈77 | 还要额外存 rollout 引擎状态、经验缓冲、reward 统计 |
| 失败模式(第 7 节) | 5 类 | 新增:rollout 引擎崩溃、reward 服务不可用、权重同步失败 |
特别注意 MFU 的定义会失效
SFT 里 MFU 是个清晰的指标。RL 里一步的时间分成 rollout / reward / train 三段,其中:
- rollout 是 memory-bound,它的"MFU"天然很低(1 FLOPs/byte 的运算强度)
- reward 可能根本不在 GPU 上
所以 RL 阶段不要看 MFU,要看的是"单位时间产出多少有效经验样本"和"三段时间的占比"。 优化目标从"把 tensor core 喂饱"变成"让三段流水起来、别互相等"。
7. "一套 infra 两个入口"的设计思想
这个架构长什么样
┌─────────────────────────────┐
│ 共享基础设施 │
│ · megatron-core 0.16 │
│ · 并行配置与显存账 │
│ · NCCL 环境统一注入 │
│ · 排空闸门 + 心跳看门狗 │
│ · 日志/监控/诊断包 │
│ · safetensors 交换格式 │
└──────────┬──────────────────┘
│
┌────────────────┴────────────────┐
▼ ▼
┌──────────────────┐ ┌────────────────────┐
│ 入口 A: SFT │ │ 入口 B: RL │
│ Megatron-SWIFT │─safetensors│ verl │
│ │───────────▶│ + vllm rollout │
│ 离线数据 │ │ + reward 服务 │
└──────────────────┘ └────────────────────┘
为什么这个设计值钱
(1) checkpoint 直通
第 5 节讲过:同一个 mcore 底座 + safetensors,SFT 产出直接是 RL 的输入。省掉的不只是转换代码,更是"转换过程中悄悄出错"这一整类事故。
(2) 运维资产复用
排空闸门、环境 diff、心跳看门狗、py-spy 扫栈脚本、日志汇总——这些东西写一遍,两个入口都用。它们占了训练 infra 工程量的很大一块,而且和训练目标无关。
(3) 认知复用
这是最被低估的。团队里的人学一次显存账、学一次并行整除约束、学一次死锁诊断流程,两个阶段都够用。换框架的隐性成本主要是人的成本,不是代码的成本。
(4) 故障归因更快
RL 出问题时,你能先问"这个现象在 SFT 阶段见过吗"。如果见过,那是共享层的问题;如果没见过,那就是 RL 特有的三块新部件之一。这个二分法能省掉大量排查时间。
代价
诚实地说也有代价:
- 被 mcore 的版本绑住。第 5 节那条 cu128 约束链现在锁死了两个入口,任何升级都要两边同时验证。
- mcore 的侵入性。模型结构非标准时改造成本高。
- 不能各自选最优。如果某天出现一个专门为 RL 优化的框架但底座不同,切换成本很高。
但在当前这个具体场景下(同一个模型、同一个集群、同一个团队、连续的 SFT→RL 流程),共享底座的收益远大于灵活性的损失。
8. 从 SFT 到 RL 的迁移清单
[ ] 1. 重算显存:训练卡数变化后的专家 DP 组和峰值 (本节表格)
[ ] 2. 重验整除:EP | DP 在新卡数下是否成立
[ ] 3. 定 colocate 还是分离,给出显存依据而不是直觉
[ ] 4. 打通权重同步,优先 NCCL 直传;确认 train/rollout 同 torch
[ ] 5. 权重同步加校验(checksum)和 draining 保护
[ ] 6. reward 服务的并发按 rollout 产出速率设计,加健康检查
[ ] 7. reward 超时的降级策略明确,且比例上报
[ ] 8. 把 SFT 的运维资产接过来:排空闸门/环境diff/心跳/诊断包
[ ] 9. 新增三类失败模式的监控:rollout崩/reward挂/同步失败
[ ] 10. 指标口径换掉:不看 MFU,看有效经验样本产出率和三段时间占比
[ ] 11. 先跑通小规模(1节点train + 1节点rollout)再上全量
[ ] 12. 优先做第 6 节的 fused CE —— 在 RL 阶段它从"提速"变成"生存必需"
9. 常见坑
- 按直觉分卡。"让出 3 个节点做 rollout"会直接 OOM,因为专家 DP 组塌到 2。
- 权重同步走落盘就不管了。每步 70 秒,占比可能超过训练本身。
- 不校验权重同步。半新半旧的权重生成的数据看起来完全正常。
- reward 服务是串行的。整条流水线降级成串行。
- 在 RL 阶段继续盯 MFU。口径已经失效,会导致优化方向错误。
- train 和 rollout 用不同 torch。NCCL 直传直接不可能。
- 直接上全量规模。RL 的部件多、耦合紧,先在 2 个节点上跑通再放大。
- 以为 SFT 的经验不适用。八成适用,别重新发明。
思考题
1. 你决定用 32 卡训练 + 8 卡 rollout,并且已经做完 fused CE(省 13GB)。
- (a) 训练侧峰值变成多少?占 96GB 的百分比?
- (b) 这时候能不能把 full recompute 换成 selective(省 25% 算力)?单步耗时变成多少?
- © 如果还想让出第 2 个节点(24 卡训练),峰值是多少?可行吗?
2. 权重同步你有两个方案:落盘(70 秒)和 NCCL 直传(3 秒)。假设一个 RL step 是 rollout 200 秒 + reward 60 秒 + train 150 秒。
- (a) 两种方案下,单步总时间和"权重同步占比"分别是多少?
- (b) 假设实现 NCCL 直传要两周工程量,而项目要跑 2000 个 RL step。这笔投入划算吗?
- © 如果 rollout 和 train 能流水并行(用稍旧的权重做 rollout,即轻度 off-policy),单步时间理论上能降到多少?这引入了什么算法风险?
全课程回顾
八节课,压成三句话:
-
一切决策都能还原成"数据在哪一层硬件之间流动"(第 1 节的带宽地图)。并行策略的本质就是让高频通信待在高带宽层级。
-
能算的东西不要猜。显存峰值可以算(第 2 节)、并行配置可以由约束推出来(第 3 节)、通信占比可以算(第 4 节)、MFU 可以算(第 6 节)、checkpoint 间隔可以算(第 7 节)、RL 的分卡方案可以算(第 8 节)。这套课里几乎每一个"应该怎么配"的问题,都有一个算式而不是一个经验值。
-
稳定性的收益高于性能。一次 5 小时的静默死锁,等于损失 150 个训练步——而这套课里所有性能优化加起来大概能提速 2 倍。先让它别停,再让它变快。
课程索引和六次事故档案在这里。