异步训练架构(已并入 B.1)
这一页保留为旧链接入口。核心内容已经合并到 B.1 RL 训练系统:采样、异步与分布式 的“异步训练架构”部分。下面保留原文,方便从旧链接进入的读者对照。
LLM RL 训练有一个核心矛盾:生成(推理)极慢,训练极快,两者串行导致 GPU 大量空闲。 异步训练就是来解决这个矛盾的。
本节只回答一个问题:生成和训练怎么并行跑? 先量化问题有多严重,再讲三种部署方式,然后深入两个工程问题——新参数怎么同步、旧数据怎么处理。
生成和训练串行,GPU 99% 时间在空等
用 TRL 跑 GRPO 的时候,每一步训练是这样的:
① 让模型生成 512 条回答 ← 推理,很慢,训练 GPU 在等
② 用这些回答算 loss、更新参数 ← 训练,很快,推理 GPU 在等
③ 拿新模型再生成 512 条回答 ← 又回到第①步①和②是串行的——总有一边的 GPU 在空等。那到底慢多少?
在单张 H100 上(bf16,vLLM 离线模式),一个典型的 GRPO 步(G=8 × 64 prompts = 512 条 rollout):
| 每条输出多长 | 总 token 数 | 7B 模型耗时 | 32B 模型耗时 |
|---|---|---|---|
| 2K token | ~100 万 | 3 分钟 | 14 分钟 |
| 8K token | ~400 万 | 11 分钟 | 56 分钟 |
| 32K token | ~1600 万 | 45 分钟 | 3.7 小时 |
而训练这一步(反向传播 + 参数更新)大概只要 30 秒。
也就是说,训练 GPU 有 99% 的时间在等生成完成。还有"慢人问题":GRPO 要求同一个 prompt 生成 8 条回答,必须等最慢的那条写完才能继续。
三种部署方式
同步模式(Synchronous)
最简单的做法。一组 GPU,先生成,再训练,再生成,再训练。
时间轴: [生成 b0] [训练 b0] [生成 b1] [训练 b1] [生成 b2] ...
↑ 空闲 ↑ 空闲 ↑ 空闲优点:代码简单,GPU 用得少。缺点:训练和生成不能同时跑。
同步模式也能快:Seer
Seer(Moonshot AI / Kimi)走的是另一个方向——不转向异步,而是在同步框架内消灭"慢人问题"。它的核心观察是:同一 prompt 生成的多条回答,长度和模式高度相似。利用这个特性,Seer 引入三项优化:divided rollout(动态负载均衡)、context-aware scheduling(减少长尾等待)、adaptive grouped speculative decoding(加速慢请求)。结果是 rollout 吞吐提升 74–97%,尾延迟降低 75–93%,同时保持严格的 on-policy 保证——不改变 GRPO 算法。Seer 基于 Mooncake 分离式 KV cache + vLLM + Megatron,详见 arXiv:2511.14617。
共置模式(Colocated)
还是一组 GPU,推理和训练轮替使用。生成时把参数重排成推理格式(从 FSDP 分片格式转成 vLLM 张量并行格式),训练时再转回来。格式转换比重新加载模型快很多,但本质还是轮替。
时间轴: [生成 b0] [转换] [训练 b0] [转换] [生成 b1] [转换] ...优点:省 GPU。缺点:生成和训练仍然不能重叠。适合预算有限的团队。
分离模式(Disaggregated / 异步)
真正让两边同时跑。推理和训练各占一组 GPU,中间用一个缓冲区(Buffer)连起来:
推理 GPU 组: [生成 b0] [生成 b1] [生成 b2] [生成 b3] ...
↓ 存入buffer ↓ 存入buffer
训练 GPU 组: [训练 b0] [训练 b1] [训练 b2] ...
↑ 权重同步 ↑ 权重同步推理 GPU 不停地生成数据塞进 buffer,训练 GPU 从 buffer 取数据训练。两边互不等对方。这是大规模 RL 训练的标配。
| 模式 | GPU 数量 | 能同时跑吗 | 适用场景 |
|---|---|---|---|
| 同步 | 一组 | 不能 | 学习、小实验 |
| 共置 | 一组 | 不能(但切换快) | 中等规模、GPU 预算有限 |
| 分离 | 两组或更多 | 能 | 大规模生产训练 |
分离模式带来两个核心工程问题:权重怎么同步和旧数据怎么处理。
工程问题一 与 新参数怎么同步给推理 GPU
训练 GPU 更新了参数,推理 GPU 怎么拿到?涉及两个决策:用什么方式传,传的时候要不要停生成。
用什么方式传?
| 传输方式 | 要多久 | 传多少数据 | 适用场景 |
|---|---|---|---|
| NCCL 一次性广播 | 100-500ms | 全部参数 | 大多数框架 |
| NCCL 打包传输(veRL) | ~20ms | 全部参数 | 优化传输 |
| GPU 显存直传(NeMo-RL) | 极低 | 全部参数 | 高带宽互联 |
| 只传 LoRA adapter | <1ms | ~50MB | 最佳实践 |
| 写文件再读(PRIME-RL) | 较慢 | 全部参数 | 跨节点 |
关键洞察:如果用 LoRA 训练,只需要同步 adapter 部分(~50MB),不到 1 毫秒。这是 LoRA + 异步训练成为最佳实践的重要原因。
传的时候要不要停生成?
推理 GPU 正在生成一条 32K token 的回答,生成到一半,新参数来了。四种处理方式:
- 不停(PipelineRL):在生成两个 token 之间的间隙(~1-10ms)悄悄换参数。最优雅但实现最复杂。
- 等手头做完再换(veRL、AReaL):不再接受新请求,等当前请求完成后同步参数。简单可靠。
- 直接中断(SkyRL、SLIME):正在生成的请求作废,同步完成后用已生成部分作为前缀重新提交。浪费一些 token。
- 等整个 batch 做完(NeMo-RL、Tunix):最保守,等待时间最长。
用 LoRA 的话同步只要 <1ms,这四种方式的区别就不大了。
工程问题二 与 旧模型生成的数据还能用吗
分离模式下,推理 GPU 可能在用旧版本的模型生成数据。
例如:训练 GPU 在第 100 步更新了参数,同步要 20ms,这期间推理 GPU 还在用第 99 步的模型生成数据。这些数据是"旧策略"生成的,用它来更新"新策略",严格来说有偏差——这就是 off-policy 问题(你在本书 第 5 章 学过)。
偏差有多大?取决于新旧策略差了几步。差 1-2 步通常问题不大,差 10 步就可能出问题。三种处理思路:
给数据打版本号,太旧的扔掉
每条数据记录是第几步策略生成的。训练时发现太旧(比如超过 5 步)直接丢掉。简单直接,不引入错误梯度,但浪费算力。
把缓冲区做小,限制过期程度
不让缓冲区存太多数据——比如最多 2 批。这样数据最多"过期" 2 步。靠缓冲区大小物理约束,不需要打版本号。
不扔数据,用数学修正
根据新旧策略的差异给数据加权:差异大的降权,差异小的正常用。这叫"重要性采样校正"(Truncated IS)。不浪费数据,但新旧策略差异很大时权重会变极端,导致训练不稳定。所以实践中会截断权重。
实际选择
大多数框架组合使用。比如 PRIME-RL 三种都用:缓冲区深度限制兜底 + 版本号追踪做安全网 + 重要性采样做精细修正。
| 思路 | 代表框架 | 简单说 |
|---|---|---|
| 扔旧数据 | PipelineRL、TorchForge | 简单粗暴 |
| 限制缓冲区 | AReaL | 队列兜底 |
| 数学修正 | veRL、ROLL | 不扔但降权 |
| 三重保险 | PRIME-RL | 最稳 |
工业界趋势:队列深度兜底 + 可选的数学修正,兼顾效率和稳定性。