LLM 后训练正在从“单机脚本调参”走向“分布式系统工程”。Miles 的定位很明确:用 PyTorch 原生的方式,把 SGLang 的 rollout、NVIDIA Megatron-LM 的训练、Ray 的编排能力组合起来,做大规模 LLM RL post-training,同时保留一个较小、可插拔的扩展面。
这类框架的价值不在于发明一个全新的训练范式,而在于把已经在工程里反复出现的几个环节——采样、奖励、训练、同步、容错——组织成可以替换、可以扩容、可以调试的栈。
它解决的不是“怎么写 PPO”,而是“怎么把 PPO 跑大”
在 LLM RL 后训练里,最容易低估的是系统复杂度。一个典型流程至少包含:
- rollout:让当前策略模型生成回答,通常需要高吞吐推理引擎;
- scoring:用 reward model、规则、工具调用或人工偏好信号打分;
- training:把采样结果喂给训练后端,更新策略模型;
- synchronization:训练权重需要定期回流到推理侧;
- orchestration:多个 GPU、多个节点、失败重试、资源隔离都要被管理。
Miles 摘要里提到的组合很有针对性:SGLang 适合承担 rollout,高效服务生成;Megatron-LM 负责大模型训练的并行能力;Ray 把不同角色组织成分布式任务;PyTorch-native extensibility 则意味着你可以用熟悉的 PyTorch 方式接入 reward、loss、数据处理和自定义模块。
换句话说,Miles 更像一条 RL 后训练流水线的骨架,而不是只提供一个孤立算法实现。
组件边界:把推理、训练和编排分清楚
大规模 RL 后训练最怕“所有逻辑都塞进一个 trainer”。一旦 rollout 变慢、reward 阻塞、训练卡住,很难定位。
Miles 这种组合式设计隐含了几个边界:
| 环节 | 典型职责 | 为什么要独立 |
|---|---|---|
| SGLang rollout | 批量生成、服务化推理、提高 token 吞吐 | rollout 往往是 RL 循环里的最大吞吐瓶颈 |
| Megatron-LM training | 张量并行、流水并行、数据并行下的权重更新 | 训练侧需要成熟的大模型并行能力 |
| Ray orchestration | Actor/Task 编排、资源调度、失败恢复 | RL 循环天然是多角色、多进程系统 |
| PyTorch 插件 | reward、loss、数据转换、监控 hook | 让研究和工程代码不必脱离 PyTorch 生态 |
这也解释了为什么“PyTorch-native”重要:很多团队的奖励函数、数据清洗、指标计算和实验代码本来就在 PyTorch/Python 里。框架如果强迫用户迁移到陌生 DSL,扩展成本会迅速上升。
可以这样实践:先用一个最小 Ray + PyTorch 循环摸清接口
下面的示例不是 Miles 的真实 API,而是一个可运行的“微缩版”工作流,用来帮助团队理解 Miles 这类栈的角色划分:rollout worker 生成样本,reward worker 打分,trainer 更新一个 PyTorch 模型。真实接入 Miles 时,可以把这些角色分别替换成 SGLang rollout、Megatron-LM trainer 和你的 reward 插件。
运行前安装依赖:
python -m venv .venv
source .venv/bin/activate
pip install torch ray
python toy_rl_posttrain_loop.py
保存为 toy_rl_posttrain_loop.py:
import random
from dataclasses import dataclass
import ray
import torch
import torch.nn as nn
import torch.optim as optim
@dataclass
class Sample:
prompt: str
response: str
reward: float = 0.0
@ray.remote
class RolloutWorker:
def __init__(self, worker_id: int):
self.worker_id = worker_id
def generate(self, prompts):
# 在真实系统里,这里可以替换为 SGLang 服务调用。
samples = []
for prompt in prompts:
response = random.choice([
'use step-by-step reasoning',
'give a concise answer',
'refuse unsafe request',
'ask a clarifying question',
])
samples.append(Sample(prompt=prompt, response=response))
return samples
@ray.remote
class RewardWorker:
def score(self, samples):
# 在真实系统里,这里可以替换为 reward model、规则系统或工具评测。
scored = []
for s in samples:
reward = 1.0 if 'concise' in s.response or 'safe' in s.response else 0.2
scored.append(Sample(prompt=s.prompt, response=s.response, reward=reward))
return scored
class TinyPolicy(nn.Module):
def __init__(self):
super().__init__()
self.logit = nn.Parameter(torch.zeros(()))
def forward(self):
return torch.sigmoid(self.logit)
@ray.remote
class Trainer:
def __init__(self):
self.model = TinyPolicy()
self.opt = optim.AdamW(self.model.parameters(), lr=0.1)
def train_step(self, samples):
# 这不是 PPO,只是用 reward 演示“样本进入训练器并更新参数”的形状。
avg_reward = torch.tensor([s.reward for s in samples]).mean()
prob = self.model()
loss = -avg_reward * torch.log(prob + 1e-6)
self.opt.zero_grad()
loss.backward()
self.opt.step()
return {
'loss': round(float(loss.detach()), 4),
'avg_reward': round(float(avg_reward), 4),
'policy_prob': round(float(self.model().detach()), 4),
}
def main():
ray.init(ignore_reinit_error=True)
rollout = RolloutWorker.remote(worker_id=0)
rewarder = RewardWorker.remote()
trainer = Trainer.remote()
prompts = [
'Explain KV cache in one paragraph.',
'How should I debug a failed distributed training job?',
'Summarize RLHF for a backend engineer.',
]
for step in range(5):
samples = ray.get(rollout.generate.remote(prompts))
scored = ray.get(rewarder.score.remote(samples))
metrics = ray.get(trainer.train_step.remote(scored))
print(f'step={step} metrics={metrics}')
ray.shutdown()
if __name__ == '__main__':
main()
这个小例子刻意没有实现复杂算法,重点是三件事:
- rollout、reward、training 是不同角色,可以独立扩容;
- 样本对象需要有稳定 schema,方便跨进程传递;
- trainer 不应该知道 rollout 的内部实现,只消费样本和分数。
这正是大规模后训练框架需要保持清晰的地方。
配置也要可插拔:把“换组件”变成改配置
如果团队要评估 Miles 这类框架,建议从配置边界开始设计。下面是一个可改造的伪配置,字段名仅用于表达结构,真实项目应以 Miles 实际配置为准:
experiment:
name: rl-posttrain-smoke-test
seed: 42
rollout:
engine: sglang
model_path: /models/policy
tensor_parallel_size: 4
max_new_tokens: 512
temperature: 0.7
training:
backend: megatron-lm
global_batch_size: 256
micro_batch_size: 2
tensor_model_parallel_size: 4
pipeline_model_parallel_size: 2
learning_rate: 1.0e-6
orchestration:
backend: ray
rollout_replicas: 8
reward_replicas: 4
trainer_replicas: 1
reward:
type: python_plugin
entrypoint: rewards.helpfulness:score_batch
checkpoint:
save_interval_steps: 50
output_dir: /checkpoints/rl-posttrain-smoke-test
一个好的 post-training 栈,应该允许你在不重写主循环的情况下完成这些替换:
- 把规则 reward 换成 reward model;
- 把单个 rollout worker 扩成多个 SGLang 实例;
- 调整 Megatron-LM 并行策略;
- 在 Ray 层调整资源和失败重试;
- 在 PyTorch 插件里快速试验新的 loss 或指标。
落地时优先检查这些风险
Miles 代表的方向很务实,但大规模 RL 后训练仍然有不少坑:
- 权重同步延迟:rollout 侧使用的策略如果太旧,训练信号会变噪;
- reward 质量:框架能扩容 reward 计算,但不能替你保证 reward 合理;
- 吞吐不均衡:SGLang rollout、reward、Megatron training 任一环节慢,都会让 GPU 空转;
- checkpoint 与恢复:RL 循环跨多个角色,恢复点必须覆盖模型、优化器、采样进度和配置;
- 可观测性:只看 loss 不够,还要看 reward 分布、生成长度、KL、失败率和队列积压。
建议采用时从小规模 smoke test 开始:先跑通 1 个 rollout、1 个 reward、1 个 trainer,再逐步增加并行度。等样本 schema、checkpoint、指标和失败恢复都稳定后,再把资源拉大。Miles 的吸引力正在于这里:它把 LLM RL 后训练拆成熟悉的工程组件,让团队可以按模块扩展,而不是一次性吞下一整个黑盒系统。