← 返回全部思考

训练 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 只有两条路:

  1. 时分复用 + offload:rollout 时把训练态(权重/梯度/优化器共 38.1GB)offload 到 CPU 内存,rollout 完再搬回来。每步搬 38GB 上下,PCIe Gen5 约 64 GB/s → 单向 0.6 秒,双向 1.2 秒。这个开销其实可以接受,但实现复杂(要处理 mcore 的分片状态),且 CPU 内存要够(每节点 8 卡 × 38GB = 304GB)。
  2. 分离部署:拿出若干节点专做 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 节点)

配套要做两件事:

  1. 把显存余量找回来。第 6 节的优化路径在这里从"提速"变成"生存必需":fused CE 省下的 ~13GB 直接把峰值从 88.3 拉回 75 左右,余量就舒服了。
  2. 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. 一切决策都能还原成"数据在哪一层硬件之间流动"(第 1 节的带宽地图)。并行策略的本质就是让高频通信待在高带宽层级。

  2. 能算的东西不要猜。显存峰值可以算(第 2 节)、并行配置可以由约束推出来(第 3 节)、通信占比可以算(第 4 节)、MFU 可以算(第 6 节)、checkpoint 间隔可以算(第 7 节)、RL 的分卡方案可以算(第 8 节)。这套课里几乎每一个"应该怎么配"的问题,都有一个算式而不是一个经验值。

  3. 稳定性的收益高于性能。一次 5 小时的静默死锁,等于损失 150 个训练步——而这套课里所有性能优化加起来大概能提速 2 倍。先让它别停,再让它变快。

课程索引和六次事故档案在这里。