初读 Miles:RL 后训练,难在采样和训练之间
前段时间我在研究 SGLang,后来顺手点进了 RadixArk 的 GitHub 主页。也是在那里,我看到了刚发布没几天的 Miles v0.1。
一开始吸引我的,其实不是 Miles 支持多少种 RL 算法,而是一个更基础的问题:
为什么一个 RL 训练框架,会把 SGLang 这样的推理引擎放在这么重要的位置?
我以前会很自然地把训练和推理分成两个阶段:先把模型训练好,再拿去做推理。可是在 RL 后训练里,事情不是这么走的。模型要先生成回答,系统根据回答算奖励,再拿这些数据更新模型。模型更新以后,又要回去生成下一批回答。
也就是说,推理并不是训练结束以后才发生的事。它本身就在训练循环里。
Miles 做的,就是把这个循环真正跑起来。它基于 slime 开发,SGLang 负责 rollout(采样生成),Megatron-LM 负责训练,Ray 则用来管理进程和 GPU 资源。项目采用 Apache-2.0 许可证。
我现在还没有合适的硬件去跑完整训练,所以这篇不是实测,也不会讨论 Miles 到底能快多少。主要想记一下我读文档和代码时觉得比较有意思的几个地方,以及它们为什么让我重新理解了 RL 训练系统。
那几行 RL 代码,真正难的是中间怎么接
把一轮 RL 训练写成伪代码,大概就是下面这样:
python
trajectories = rollout(policy, prompts)
scores = reward(trajectories)
policy = train(policy, trajectories, scores)
sync(policy, rollout_workers)
这里的 policy,暂时可以理解成正在训练的模型。rollout 则是让模型根据提示词实际生成回答,也就是为后面的训练采样。
只看这几行,好像没有什么特别复杂的地方:生成、打分、训练、同步,循环往复。
但一放到多机多卡环境里,问题就来了。
采样生成的数据怎么交给训练端?模型刚刚更新完,负责生成的进程怎么拿到新权重?一批请求里有的很快结束,有的迟迟跑不完,其他 GPU 要不要一直等?训练端收到的 token,还是不是推理端当时真正生成的那一串?
这些问题在算法公式里基本看不到,却会直接决定这套训练能不能高效、稳定地跑起来。Miles 的大部分设计,也都围绕着这些"步骤之间"的问题展开。

同一个模型,在系统里要干两份完全不同的活
模型做采样时,目标是尽量快地生成更多回答。推理引擎要处理并发请求、维护 KV Cache,还要尽量提高 GPU 利用率。这部分 Miles 交给了 SGLang。
到了训练阶段,重点又变成了反向传播、梯度和优化器状态。这部分主要由 Megatron-LM 负责。
从概念上看,两边当然是同一个策略模型。但在系统里,它们是两套为了不同任务优化的运行形态。训练端每更新一次参数,推理端手里的模型就可能变成旧版本。
读到这里,我才意识到,"把训练好的权重同步给推理端"听起来只是一句话,真正做起来却会碰到显存、通信、进程协调和模型版本等一连串问题。
Miles 支持两种主要的 GPU 布局。
一种是共置模式(colocated),也就是采样和训练共用同一组 GPU。好处是不用准备两套机器,但两边不能同时把显存占满,只能轮流工作。采样结束后让出资源,训练接着跑;训练做完,再切回采样。
另一种是分离模式(disaggregated),采样和训练分别使用不同的 GPU。这样两边可以同时工作,也是全异步模式的前提。代价是权重要在两组 GPU 之间传输;如果两边不在同一台机器上,请求和采样数据还会经过网络。

Ray 在外面负责把这些组件组织起来,包括训练进程、SGLang 服务、路由器和采样管理器。普通的生成请求走 SGLang 的 HTTP 服务和 Miles 路由器,模型权重则有单独的同步方式。
模型一更新,采样端马上就落后了
RL 训练里有一个绕不开的问题:用来生成数据的模型,最好不要和当前正在训练的模型差得太远。
最容易想到的同步方法,是训练端保存一个检查点(checkpoint),再让推理端重新加载。偶尔做一次问题不大,但如果训练过程中频繁这么干,大模型的保存、读取和重新加载很快就会变成瓶颈。
Miles 针对不同的部署方式准备了不同的权重同步路径。
在共置模式下,权重可以通过 CUDA IPC 在设备侧传递。训练和采样分开部署时,默认可以使用 NCCL 广播;文档里还提供了基于 Mooncake 的 P2P RDMA,以及通过共享存储传输权重差量的 disk-delta 方案。
不过,权重也不是说换就能换。SGLang 里可能还有请求生成到一半,KV Cache 也还在用。系统需要在预先设置好的同步点暂停生成,更新权重,再继续处理请求。
所以权重多久同步一次,不只是一个部署参数。
同步得太频繁,通信和暂停本身会影响吞吐;隔得太久,采样端用的模型又会越来越旧。看到这里,我开始理解为什么这类框架会把权重同步当成核心能力,而不是一个顺手补上的功能。
异步能少等一会儿,但也会带来新问题
最容易理解的是同步训练:先生成完整的一批数据,再训练一步,然后同步权重,开始下一轮。
这样做的好处是流程清楚。哪批数据由哪个版本的模型生成,也比较容易对上。
但生成任务有个很现实的问题:每条回答的长度不一样。有的请求很快遇到 EOS,有的会一直生成到上限。只要这一批里还有几个慢请求没结束,其他已经空下来的资源就只能等。
Miles 的全异步模式不再死守这条批次边界。采样进程持续生成,完成的数据先放进一个大小有限的缓冲区;训练端一边从中取数据,一边更新参数。这样采样和训练就能重叠起来。
当然,它不是打开一个开关就能白拿性能。
一条采样轨迹放进缓冲区以后,可能不会立刻被训练端取走。等轮到它时,模型已经更新了几次。于是训练端正在使用的数据,可能来自几个版本以前的策略。
Miles 会记录每轮生成对应的权重版本,也允许设置样本的最大陈旧度(staleness),用来丢掉过旧的数据。这个过滤默认并不会自动开启,需要使用者自己确定阈值。
另外,全异步模式要求采样和训练使用不同的 GPU。到了设置好的权重同步点,生成还是会暂停,等新权重换好以后再继续。
所以异步解决的是"大家互相干等"的问题,但同时也把模型版本和样本新鲜度带进了训练过程。缓冲区设多大、多久同步一次、旧到什么程度的数据还能用,这些已经不只是系统调优,也可能影响最后的训练效果。
如果是第一次跑,我应该还是会从同步模式开始。速度慢一点,但出问题时至少更容易知道是哪一步出了问题。

人看起来是同一句话,对模型来说却不一定
Miles 里另一个让我印象很深的设计叫 TITO,也就是 token-in-token-out:推理端输入和输出都保留为 token。
最省事的数据传递方式,似乎是让推理端把 token 解码成文字,再把文字交给训练端重新分词:
python
reconstructed_ids = encode(decode(token_ids))
# 这次往返不保证保留策略模型真正采样的动作
assert reconstructed_ids == token_ids # 可能失败
问题在于,解码再编码以后,不一定还能得到原来的 token 序列。分词器可能把同一段文字切成另一种形式,解码时也可能移除特殊 token,或者对文本做一些规范化处理。
多轮智能体的情况更麻烦。每次调用工具以后,系统可能重新套用对话模板,也可能把工具调用解析以后再拼回 JSON。哪怕人眼看着内容没变,空格、字段顺序或边界位置的变化,都可能让下一轮输入变成另一串 token。
为什么这件事值得单独处理?
因为采样生成的 token ID,就是策略当时实际选出的动作。训练端如果重新构造出另一条 token 序列,优化的可能已经不是产生那份采样对数概率的动作了。文字看起来一样,不代表这还是同一条训练数据。
在 Miles 的文本智能体会话里,TITO 会以 SGLang 实际生成的 token 为准,并保存对应的对数概率和路由信息。到了下一轮,已经存在的 token 前缀会直接复用,只对后来追加的内容重新分词。

我原本会把这种问题归到"分词器的边角细节"里。读完这一段才发现,它其实是在保证一件很基础的事:训练端学到的,必须是推理端当时真正做过的选择。
没有八张数据中心 GPU,还能怎么看这个项目
Miles 目前支持 GRPO、GSPO、PPO、REINFORCE++、SFT 和 OPD,也提供多轮智能体采样、低精度训练和多种模型配置。
不过它显然不是给普通笔记本准备的入门项目。
官方快速入门从一台八卡机器起步,要求 H100、H200 或 B 系列 GPU,至少 500 GB 可用磁盘空间,以及能够使用 GPU 的 Docker 环境。示例跑的是 Qwen3-4B:四个双卡 SGLang 推理实例和 Megatron 训练端共用这八张卡,轮流工作。
如果暂时没有这样的硬件,我觉得仍然可以先从代码和配置入手。比如看它怎样启动各个组件、怎样分配 GPU、一次采样保存了哪些字段,以及权重同步从哪里触发。这些内容本身就是一份挺完整的 RL 训练系统案例。
但吞吐、稳定性和扩展能力就不能只靠读文档下结论了。那些还是得等真正跑过以后再聊。
写在最后
在看 Miles 之前,我对 RL 后训练框架的关注点还是偏算法:支持哪些损失函数、奖励怎么计算、有没有 GRPO。
看完它的整体设计以后,我更关心的是另外一些问题:采样和训练怎么同时使用同一个模型?新权重怎样送回推理端?异步以后,训练数据到底旧了多少?传给训练端的 token,还是不是模型当时真正生成的 token?
这些东西单独看都不像"RL 算法",但少了任何一个,前面那几行简单的训练伪代码都很难在真实集群里顺利跑起来。
这也是 Miles 最吸引我的地方。它让我看到,RL 后训练的难点不只在每一步怎么算,还在这些步骤之间到底怎么接。
接下来我想挑一个具体入口继续往下读。TITO 会是一个不错的方向,权重同步也很值得单独拆开。等有条件跑起官方示例以后,再补一篇真正的上手记录。
参考资料
- Miles GitHub 仓库 --- 源代码、示例配置和版本记录。
- Miles v0.1 发布说明 --- v0.1 的主要能力与版本记录。
- Miles 架构文档 --- 核心组件、控制流程和数据流。
- 训练后端与资源布局 --- Megatron-LM 后端、GPU 布局和权重同步方式。
- 全异步训练 --- 异步执行、采样缓冲区和样本陈旧度控制。
- 智能体采样与 TITO --- 多轮智能体采样中的 token 轨迹保持机制。
- Miles 快速入门 --- 官方硬件要求与 Qwen3 示例启动流程。