Miles:把 SGLang、Megatron-LM 和 Ray 串起来做大规模 LLM 强化学习后训练

2026-07-01 36 预计阅读时间: 1 分钟
来源: pytorch.org AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

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 后训练拆成熟悉的工程组件,让团队可以按模块扩展,而不是一次性吞下一整个黑盒系统。


相关推荐