强化学习

OpenBMB 开源 Meshy:把强化学习训练拆成服务,一条队列同时管数据、节拍和 GPU

yiwan-cat2026-09-08 22:51
OpenBMB 开源 Meshy:把强化学习训练拆成服务,一条队列同时管数据、节拍和 GPU

GitHub:OpenBMB/Meshy
官方中文博客:Meshy:角色驱动下 SPMD 范式的 RL 训练框架

大模型强化学习框架,通常都绕不开一个“总导演”。

它负责依次通知 rollout 生成样本、reward 打分、trainer 更新权重,再把结果收回来,进入下一轮。这种 Single-Controller 架构非常直观,也很适合同步 RLHF:整个系统一次只推进一个明确阶段,程序运行到哪一行,训练就进行到哪一步。

但今天的后训练任务已经变了。

生成越来越长,样本完成时间差异越来越大;训练与推理希望重叠执行;Agent 轨迹可能停下来等待工具返回;同一时刻,队列里甚至可能混有不同权重版本产生的样本。继续让一个中央控制器负责派发、汇总和维护所有状态,它就容易从“总导演”变成系统的交通路口。

OpenBMB 新开源的 Meshy,选择了另一条路线:

不再设置负责串起全流程的中央控制器,而是把 Inference、Training、Rollout、Teacher 都做成独立服务,让数据是否就绪来决定系统下一步做什么。


图 1|Meshy 官方系统架构图

Meshy 到底是什么?

先给它一个准确定位:

Meshy 是一个基于 SGLang、TorchTitan 与 TransferQueue 构建的、角色驱动的 LLM 强化学习训练框架。

它目前不是一个面向普通开发者的聊天模型训练器,也不是“输入一个数据集就自动对齐”的低代码产品。它更接近一套 RL 系统基础设施:帮助研究者组织 rollout、推理、训练、Teacher 等异构角色,并在同步、有限异步、全异步及在线策略蒸馏之间复用同一套服务。

当前官方仓库已经提供以下配方:

配方 模型与数据 典型 GPU 布局 主要用途
grpo_gsm8k Qwen3-1.7B + GSM8K 默认 1 卡 最小端到端 GRPO 闭环
grpo_gsm8k_qwen3_8b Qwen3-8B + GSM8K 8 卡 非对称训推共置
justrl R1-Distill-Qwen-1.5B + DAPO-Math-17k 8 卡 锁步 GRPO
justrl_async 同上 8 卡 有界异步,生成可领先一批
justrl_fully_async 同上 16 卡分离部署 全异步、流式 mini-batch
justrl_minicpm5_* MiniCPM5-1B / 2.6B 4 或 8 卡 MiniCPM5 系列训练
justrl_qwen3_30b_a3b Qwen3-30B-A3B 8 卡 MoE + TP/EP 并行
math_grpo_minicpm5_2_6b* MiniCPM5-2.6B + S9 数学数据 4 或 8 卡 128K 上下文、全异步数学训练

需要注意:“框架支持 1 卡启动”不等于所有 recipe 都能单卡运行。 单卡路径主要是 grpo_gsm8k;JustRL、MoE、128K 长上下文等配方都有明确的多卡要求。

它和常见 RL 框架最不一样的地方

1. 每个角色都是独立服务

在 Meshy 中,Rollout、Inference、Training 和 Teacher 不再是中央训练循环里的几个函数,而是长期运行的独立 Service。

每个 Service 内部又拆成两层:

  • Engine:负责模型计算、进程组、GPU 状态、权重和 checkpoint;
  • Worker:负责业务循环、批处理,以及从 TransferQueue 读取和写入数据。

一次实验则由 recipe 声明:有哪些角色、每个角色多少副本、每个副本占几张 GPU,以及哪些角色需要共用显卡。

这意味着新增一个 Teacher 或 Scoring 角色时,理想路径不是改写整个中央循环,而是:实现新 Service、声明它消费和产生的数据列,再把它放进 recipe。

2. 数据本身就是流程控制

TransferQueue 可以理解成一组分区表:

  • 一行代表一条样本;
  • 一列代表一种数据,如 token、logprob、advantage、reward、weight version;
  • 不同服务可以向同一行补写不同列;
  • 只有消费者要求的列全部到齐,这一行才会变成“可消费”。

例如 Trainer 声明自己需要 tokens + logprobs + advantage。当这三列没有全部准备好时,Trainer 不会取走样本;当 Rollout 或其他服务补齐这些列后,样本自然进入 Trainer 的可见范围。

这里没有额外的“请开始训练”RPC。列的就绪状态,本身就是控制信号。

更关键的是,大块 tensor 由消费者直接从 storage 获取,不必先送到中央 driver,再由 driver 转发。Meshy 官方博客认为,这是它与典型 Single-Controller 架构最实质的差异。


图 2|Meshy 单轮训练数据流

图中一轮 GRPO 的逻辑是:Rollout 从数据集取 prompt,通过 SGLang Inference Service 生成 token 和 logprob;完成 reward、advantage 后,把带权重版本的样本写入 TransferQueue;Trainer 只取列已经齐全的行,更新模型、保存新 checkpoint,再发出新的 generation gate。

3. 没有服务发现:每个进程自己算出完整拓扑

服务化系统通常还需要注册中心:哪个服务在哪台机器、监听哪个端口、使用哪组 GPU?

Meshy 的做法很干脆:所有 GPU 进程通过同一条 torchrun 命令启动,每个进程拿到同一份 recipe 和全局 GPU 列表,再执行同一个确定性函数,本地推导出相同拓扑。

因此,Ignitor 的职责主要发生在启动阶段:计算部署、点火服务、检查 GPU 数量和共置关系、守护生命周期;它不是运行时的任务调度中心。

这让“拓扑”变成 recipe 的纯函数结果,也让 GPU 数量不足、端口冲突、共置环错配等问题尽量提前暴露。

4. GPU 使用权也被做成了队列里的令牌

RL 训练里经常需要训推共置:生成时 SGLang 使用 GPU,训练时释放推理显存,让 TorchTitan 接管;训练完成后再加载新权重,把 GPU 交回推理。

Meshy 把这件事抽象成 ColocationRing。

  • Inference 通常是 FALLBACK:没有其他任务申请时持续生成;
  • Training、Teacher 通常是 ON_DEMAND:需要计算时主动申请 GPU;
  • 当前持有者在安全边界停止计算、释放显存并交出令牌;
  • 新持有者恢复模型与状态后开始执行;
  • 新 checkpoint 路径可以随令牌一起传递,避免推理端悄悄使用旧权重。

这套抽象的价值,在加入第三个 GPU 角色时最明显。传统训推共置往往只写死“两方切换”,Teacher 加进来以后又要补一套状态机;Meshy 则允许多个角色加入同一个环,只要求各自实现显存释放与恢复接口。

一处配置,切换同步、有限异步和全异步

Meshy 内置的 justrl、justrl_async 和 justrl_fully_async 使用相同模型和训练超参数,核心差别集中在 rollout 的推进窗口:

模式 pacing_window 含义
同步/锁步 1 rollout 等待当前版本 gate,按批推进
有界异步 2 允许生成领先训练一个批次
全异步 None 不设置固定窗口,Trainer 流式消费 mini-batch

这并不意味着异步训练只有一行代码那么简单。策略滞后、混合权重版本样本、失败恢复和评测公平性仍然是算法与系统问题。Meshy 的贡献更准确地说是:让不同节拍可以复用同一套 Service 与数据通路,而不必维护两套完全分叉的同步/异步框架。

Quick Start:先用单卡跑通最小闭环

运行前提

官方当前要求:

  • NVIDIA GPU,宿主机驱动能够支持 CUDA 12.9 容器;
  • Docker 与 NVIDIA Container Toolkit;
  • 若手动安装,建议 Ubuntu 24.04、Python 3.12+;
  • 首次运行会从 Hugging Face 下载 Qwen/Qwen3-1.7B 与 GSM8K 数据,需要可用网络与磁盘空间。

对于刚开源的 alpha 项目,建议优先使用容器,不要先在已有训练环境里混装 PyTorch、SGLang、TorchTitan 和 TransferQueue。

1. 拉取源码与官方推荐镜像

git clone https://github.com/OpenBMB/Meshy.git
cd Meshy

docker pull ztonyzhao/meshy:0.1.0-alpha

2. 挂载源码和模型缓存进入容器

docker run --gpus all -it --rm \
  --ipc=host \
  -v "$PWD":/workspace/Meshy \
  -v "$HOME/.cache/huggingface":/root/.cache/huggingface \
  -w /workspace/Meshy \
  ztonyzhao/meshy:0.1.0-alpha bash

这里主动挂载了仓库源码。原因是截至本文核验的 Dockerfile 主要负责准备 CUDA、PyTorch、SGLang、TorchTitan 与 TransferQueue 依赖,并没有把当前仓库源码 COPY 到镜像中。挂载源码后,版本关系也更清楚。

3. 安装当前源码并启动最小 recipe

进入容器后执行:

uv pip install -e .
python scripts/launch.py --recipe recipe.grpo_gsm8k

它默认使用 1 张 GPU,在 Qwen3-1.7B 与 GSM8K 上跑通 rollout → reward/advantage → training → checkpoint/gate 的完整链路。

启动成功时,首先应看到类似信息:

[launch] recipe=recipe.grpo_gsm8k cards=1 nproc-per-node=1 colocate=True runtime=...

运行产物位于:

.xrl_runtime/<timestamp>/
├── logs/
├── tensorboard/
├── weights/
└── trajectory JSONL

这里的成功判据不是“终端没有立即报错”,而是:Inference、Rollout、Training 和 TransferQueue 相关进程均已就绪;队列持续产生和消费样本;训练日志出现 step;运行目录中出现日志、轨迹和权重产物。

最常见的三个问题

  1. 容器看不到 GPU:先用 docker run --rm --gpus all nvidia/cuda:12.9.1-base-ubuntu24.04 nvidia-smi 检查宿主机驱动与 NVIDIA Container Toolkit。
  2. recipe 找不到或 import meshy 失败:确认当前目录是仓库根目录,并已执行 uv pip install -e .。
  3. 首次运行长时间没有进入训练:先检查 Hugging Face 模型与数据是否仍在下载,再查看 .xrl_runtime/<timestamp>/logs/;Meshy 是多进程框架,真正的 traceback 往往在具体 Service 日志里。

怎么接入自己的数据和奖励函数?

对大多数 GRPO 实验,第一步通常不需要写新 Service,只要增加 Dataset adapter 和 reward。

官方约定很小:

  • 构造函数接收 dataset_kwargs;
  • next_batch(builder) 返回一组 prompt Sample;
  • 返回空列表表示本轮数据结束;
  • reward 接收完成生成的 Sample,返回标量分数。

然后在 recipe 里通过 module:attr 字符串接入:

RolloutServiceConfig(
    model_path="Qwen/Qwen3-1.7B",
    dataset="recipe.my_dataset:MyDataset",
    dataset_kwargs={
        "path": "/data/train.jsonl",
        "batch_size": 4,
        "seed": 42,
    },
    reward="recipe.my_dataset:MyDataset.reward",
    group_size=4,
    pacing_window=1,
)

工程上建议先让 reward 保持纯函数、可重复测试,并对异常样本显式返回或记录错误。不要一开始就把远程 Judge、工具执行、沙箱交互和多个奖励模型全部塞进同一条链路,否则排障时很难判断是数据、生成、评分还是队列出了问题。

怎么写自己的训练拓扑?

一个 recipe 至少导出三部分:

SERVICE_GROUPS = [...]   # 有哪些角色、多少副本、各用几张卡
COLOCATIONS = [...]      # 哪些 GPU 角色共享同一组卡,可选

def main():
    Ignitor(SERVICE_GROUPS, COLOCATIONS).run()

如果只是从共置切换到分离部署,可以移除 colocate_with 和 COLOCATIONS,各个 Service 的业务代码不用改变。

如果要加入新角色,则需要明确三件事:

  1. 它消费 TransferQueue 中的哪些列;
  2. 它会新增或改写哪些列;
  3. 它是中间处理者还是最终消费者——只有最终消费者才应清理样本行。

第三点很容易踩坑。如果中间 Teacher 在写回分数后就把行清掉,下游 Trainer 会永远等不到完整样本。

生产环境怎么上?

如果是我,我不会直接把官方 demo 扩成十几张卡长期训练,而会分五步:

  1. 固定版本:锁定 Meshy commit、Docker image digest、模型 revision 和数据集快照。当前仓库没有正式 Release/Tag,不能长期跟随 main。
  2. 单卡闭环:先用 grpo_gsm8k 验证模型下载、SGLang 生成、TQ 数据流、TorchTitan 更新和 checkpoint 全链路。
  3. 换自己的最小数据:只替换 Dataset 和 reward,保持同步 pacing_window=1,先验证训练曲线和样本格式。
  4. 再改资源拓扑:先做 2 卡或小规模 disaggregate,对比共置与分离部署的 GPU 利用率、权重同步时间和峰值显存。
  5. 最后打开异步:逐步从窗口 1 调到 2,再考虑完全异步;同时监控策略版本滞后、样本等待时间、队列积压和训练稳定性。

建议把以下内容纳入最低可观测性:

  • 每个 Service 独立日志与退出码;
  • TQ 每个分区的待生产、已就绪、待消费行数;
  • 样本的 weight_version 与训练消费版本差;
  • GPU 令牌当前持有者、等待者及每次切换耗时;
  • checkpoint 保存、推理端加载和验证结果;
  • rollout 吞吐、trainer step time、失败样本和截断比例。

目前有哪些限制?

Meshy 的设计很有意思,但它仍然是一个刚开源的 alpha 工程,选型时要把边界看清楚。

  • 当前没有官方性能 Benchmark。 官方材料重点解释架构,没有给出与 verl、slime、Relax 在同硬件、同模型、同精度和同算法下的吞吐对比。因此现在可以说它减少了中央数据转发与调度路径,不能据此直接宣称“训练更快多少”。
  • TQ 从可选组件变成必需基础设施。 数据、gate 和 GPU token 都依赖 TransferQueue;架构更统一,但团队也必须理解并维护它。
  • 调试从一条调用栈变成多服务日志。 队列状态更可观察,却无法再靠中央 controller 的一个断点看完整世界。
  • 环境版本较新且绑定较紧。 当前手动安装文档要求 Python 3.12、CUDA 12.9,并固定了特定 PyTorch、SGLang、TorchTitan 和 TransferQueue 版本,直接混入旧集群环境有较高冲突风险。
  • 分布式不是“多写一个副本数”那么简单。 官方文档明确提醒,简单增加 Rollout/Trainer 副本并不会自动带来数据分片和独立 gate stream。
  • 许可证状态需要确认。 截至本文核验的 commit,仓库根目录没有 LICENSE 文件。研究试用可以继续关注项目更新,商业使用或二次分发前应先向维护者确认授权边界。

Meshy 适合谁?不适合谁?

比较适合

  • 正在研究同步与异步 LLM RL、需要频繁改训练节拍的团队;
  • 希望在 SGLang 推理与 TorchTitan 训练之间做共置或分离部署的人;
  • 需要加入 Teacher、Scoring、在线蒸馏等新角色,不想反复改中央训练循环的系统研究者;
  • 愿意通过 recipe、数据列契约和多进程日志管理复杂系统的基础设施团队。

暂时不太适合

  • 只想单卡 LoRA/SFT、希望开箱即用完成训练的个人开发者;
  • 现有环境无法支持官方 CUDA/Python 组合,且不方便使用容器的集群;
  • 需要成熟 Release、稳定兼容矩阵、完整 Benchmark 和明确商业许可证的生产团队;
  • 流程固定、规模不大、同步 controller 已经足够稳定的项目。对这类任务,换架构未必能收回迁移成本。

最后怎么看 Meshy?

Meshy 最值得关注的,不是它又提供了一份 GRPO recipe,而是它重新回答了一个系统问题:

当 RL 的进度单位从“全局训练轮次”变成“样本、权重版本和独立服务”时,是否还需要一个中央控制器替所有角色推进流程?

它给出的答案是:让角色独立运行,让数据列决定何时可计算,让 topology 从 recipe 确定性地产生,再把 GPU 使用权也变成可以传递的 token。

这条路线未必会替代所有 Single-Controller 框架。对于小规模、固定流程的同步训练,中央循环依然简单、成熟、好调试。但在全异步 RL、多轮 Agent、Teacher/Student 在线蒸馏和多角色共置这些场景里,Meshy 的“服务 + 队列 + 数据契约”确实提供了一种更自然的表达方式。

目前最合理的态度不是急着比较谁“碾压”谁,而是先用单卡 recipe 跑通,再用自己的数据验证三件事:队列是否真的更容易观测,角色是否真的更容易扩展,异步化是否真的减少了等待而没有引入不可接受的策略滞后。

如果这三点成立,Meshy 才不只是架构图漂亮,而是真正值得进入后训练基础设施栈。


项目链接

点赞收藏
// 评论0
0 / 500
还没有评论,快来抢沙发