【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (4)--- 系统架构
【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 — (4)— 架构
文章目录
0x00 概要
本系列的目的是:借着对 OpenClaw-RL 源码的学习,来梳理强化学习的一些相关概念和思想。所以,会有一些基础知识、扩展和发散,OpenClaw-RL 只是一个切入点。而且,因为整篇系列是一个整体,所以有些概念的解读/学习会在不同的文章中出现,还请大家谅解。
OpenClaw-RL 是一个用于在线强化学习(Online RL)的框架,专门针对智能体工具使用场景。它通过从环境反馈中提取过程奖励信号来训练语言模型,支持三种主要模式:
- openclaw-rl:基于二元奖励的强化学习(Binary RL / GRPO)
- openclaw-opd:基于后见之明提示的在线策略蒸馏(On-Policy Distillation, OPD)
- openclaw-combine:联合方法,在同一 PPO 更新中同时利用 RL reward 和 OPD teacher signal

0x01 架构
OpenClaw 的 RL 训练 = 一套统一的 PPO 框架 + 三种不同的 advantage 注入方式
┌────────────┬──────────────────────────────┬─────────────────────────────────────┐
│ 方法 │ Advantage 来源 │ 适用场景 │
├────────────┼──────────────────────────────┼─────────────────────────────────────┤
│ Binary RL │ A = R (raw broadcast) │ 简单场景,只有 ±1 reward │
│ │ reward 标量广播到全序列 │ │
├────────────┼──────────────────────────────┼─────────────────────────────────────┤
│ OPD │ A_t = teacher_lp_t - old_lp_t│ 有 teacher model 提供 per-token 信号 │
│ │ teacher 的 per-token log-probs │ 需要精细引导,如 hint 机制 │
├────────────┼──────────────────────────────┼─────────────────────────────────────┤
│ Combine │ A_t = w_rl·R + w_opd·(teacher│ 同时需要 reward 和 teacher 信号 │
│ │ _lp_t - old_lp_t) │ 最灵活,可调权重 │
└────────────┴──────────────────────────────┴─────────────────────────────────────┘
关键设计原则:
- 统一 PPO clip 框架:三种方法共享同一套 ratio-based clipped loss
- 数据驱动分流:Combine 通过设置 teacher_lp=rollout_lp 或 reward=0 自动区分 OPD/RL 样本
- 无额外模型:GRPO 替代 Critic,teacher 只做 forward pass(不训练)
- 异步架构:Proxy 实时拦截用户对话,Trainer 后台持续更新
1.1 架构图
OpenClaw-RL 的系统架构图如下:

1.2 File Structure
OpenClaw-RL 的的文件结构如下。
OpenClaw-RL-main/
│
├── 🧠 核心 RL 框架
│ ├── slime/ # Ray + Megatron-LM 分布式训练
│ └── Megatron-LM/ # NVIDIA 模型并行后端
│
├── 🏠 个性化 Agent 优化 (Track 1)
│ ├── openclaw-rl/ # Binary RL (GRPO)
│ ├── openclaw-opd/ # On-Policy Distillation
│ ├── openclaw-combine/ # RL + OPD 联合方法
│ └── openclaw-tinker/ # Tinker 云端零 GPU 方案
│
├── 🌐 通用 Agent RL (Track 2)
│ ├── gui-rl/ # GUI Agent (OSWorld)
│ ├── swe-rl/ # SWE Agent (mini-swe-agent)
│ ├── terminal-rl/ # Terminal Agent (SETA)
│ └── toolcall-rl/ # Tool-Call Agent (ReTool)
│
├── 📊 评估
│ └── openclaw-test/ # GSM8K 多轮对话评估
│
├── 🎨 前端
│ └── openclaw/ # OpenClaw 聊天应用 (TS/Node)
│
└── 📄 配置
├── requirements.txt # 301 个 Python 依赖
└── instructions/ # 环境搭建指南
模块职责划分如下。
OpenClaw-RL/
├─ openclaw-rl/
│ ├─ openclaw_api_server.py ← FastAPI 代理 + PRM 评分 + 样本提交
│ └─ openclaw_rollout.py ← AsyncRolloutWorker: 桥接 API Server ↔ Slime
├─ openclaw-opd/ ← OPD 变体(hint 提取 + teacher log-probs)
├─ openclaw-combine/ ← Combined 变体(RL + OPD 并行)
├─ openclaw-tinker/ ← 无 GPU 云端版(Tinker API)
├─ slime/
│ └─ train_async.py ← 基础 RL 框架(Megatron + SGLang)
│ ← 入口:异步训练主循环
├─ terminal-rl/ gui-rl/
│ swe-rl/ toolcall-rl/ ← Track 2:通用智能体 RL
└─ openclaw/
└─ src/ ← OpenClaw TypeScript 应用
1.3 四大组件
OpenClaw-RL 的系统设计是四个异步解耦的循环——policy serving、environment hosting、reward judging、policy training 同时运行、互不阻塞,因此模型可以一边持续服务,一边从刚刚发生的真实交互中在线学习。

在这种模块化设计中,各组件既保持功能独立性又实现数据互通。
OpenClaw-RL四大组件 vs 标准RL的区别
标准Online RL OpenClaw-RL 差异级别
────────────── ────────────── ──────────────
Rollout 主动生成 被动等待 完全不同
从dataset采样prompt→生成
Environment 数据集prompt 真实用户 完全不同
PRM/RM 训练好的RM或验证器 zero-shot LLM judge 理念不同
Policy Serving 内部rollout 引擎 面向用户的API服务 架构不同
四个阶段的模块归属
Policy Serving
定义:运行策略模型、生成response、提供推理能力。
功能定位
- 用户交互入口:接收外部环境的请求并返回响应
- 模型推理服务:转发请求至SGLang推理引擎获取模型响应
- 会话状态管理:维护多轮对话的上下文状态
技术实现
- Web 框架:基于 FastAPI 实现OpenAI 兼容的 /v1/chat/completions接口
- 核心文件:openclaw_api_server.py/ openclaw_opd_api_server.py
具体如下:
| 模块 | 角色 | 位置 |
|---|---|---|
| SGLang Rollout Engine | 真正的 Policy(LLM 推理,GPU 4-5) | Slime 启动,Ray PlacementGroup 管理 |
| OpenClawAPIServer._handle_request() | HTTP 转发代理 | openclaw_api_server.py |
| httpx.post(sglang_chat_url) | 向 SGLang 发送推理请求 | _handle_request() 内 |
| _extract_logprobs_from_chat_response() | 采集 rollout log-probs | openclaw_api_server.py |
| FastAPI /v1/chat/completions | 对外暴露的推理接口(PORT=30000) | _build_app() |
| Megatron Actor(GPU 0-3) | 保存最新策略权重,定期同步给 SGLang | Slime 内部 |
Environment
定义:产生观测(next_state)、定义任务边界。
环境托管模块(Environment Hosting Subsystem)构建智能体操作的真实/模拟环境:
- 个人智能体场景:集成用户终端设备(手机/电脑)的会话系统
- 通用智能体场景:支持云端并行终端、图形界面交互、软件工程开发环境及工具调用沙盒
环境模块通过事件驱动机制实时反馈状态变化,包括用户追问、代码执行结果(stdout/stderr)或界面元素变更等信号。
具体如下:
| 模块 | 角色 | 位置 |
|---|---|---|
| 真实用户 | 发消息(动作) + 看 response(观测) | 外部,无代码 |
| HTTP 请求中的 messages | 用户发出的 observation sequence | FastAPI 请求体 |
| messages[-1](新消息) | next_state,上一轮 turn 的环境反馈 | _handle_request() 第 504 行 |
| X-Session-Id header | 标识同一个 environment episode(会话) | FastAPI header |
| X-Turn-Type: main/side | 区分训练轨迹与非训练交互 | FastAPI header |
| X-Session-Done header | episode 终止信号 | FastAPI header |
Environment在OpenClaw中没有代码实体,它就是“真实用户+HTTP协议“本身。
Reward Judging
定义:评估当前response的质量,产生reward 信号 Binary RL 的 Reward Judge。
效果评估模块(Reward Judging Component)采用双轨制评估机制:
- 量化评估层(PRM Judge):基于预设指标体系生成即时评分(+1/-1等)
- 质性指导层(OPD Hint Extractor):通过自然语言理解技术提取改进建议。
该模块将评估结果与指导信号整合为结构化反馈,构建"教师上下文"用于模型微调。评估过程采用异步批处理模式,支持每秒千级交互的评估吞吐量。
功能定位
- 质量评估:对代理响应进行质量评分
- 评估逻辑:基于下一状态判断助手响应质量
- 评分规则:+1(好)/-1(差)/0(中性)
- 多数投票:多次独立评估取多数结果
- 奖励信号生成:生成用于策略优化的奖励信号
- 过程监督:提供细粒度的过程反馈而非仅结果反馈
技术实现
- 过程奖励模型:通过下一状态评估当前响应质量
- 多数投票机制:执行多次独立评估取多数结果提高可靠性
- 异步评估:在后台线程中执行耗时的LLM评估
- 核心文件:集成在openclaw_api_server.py中的PRM相关逻辑
具体如下:
| 模块 | 角色 | 位置 |
|---|---|---|
| _fire_prm_scoring() | 触发 PRM 评分任务(异步) | openclaw_api_server.py |
| _prm_evaluate() | PRM 评估主逻辑(m=3 并行) | openclaw_api_server.py |
| _query_prm_once() | 单次调用 Judge LLM(GPU 6-7) | openclaw_api_server.py |
| _majority_vote() | 多数投票 → final score | openclaw_api_server.py |
| _build_prm_judge_prompt() | 构造 judge prompt | openclaw_api_server.py |
| SGLang PRM Engine(GPU 6-7) | 运行 Judge LLM 推理 | Slime 启动 |
OPD / Combine 额外的 Reward Judge如下:
| 模块 | 角色 | 位置 |
|---|---|---|
| _fire_opd_task() | 触发 OPD 评估任务(异步) | openclaw_opd_api_server.py |
| _opd_evaluate() | Hint Judge + Teacher LP + Eval | openclaw_opd_api_server.py |
| _query_judge_once() | Hint Judge 单次调用(GPU 6-7) | openclaw_opd_api_server.py |
| _select_best_hint() | 选最优 hint | openclaw_opd_api_server.py |
| _compute_teacher_log_probs() | 教师前向传播(max_new_tokens=0) | openclaw_opd_api_server.py |
| _query_prm_eval_once() | Eval Judge(仅 Combine) | openclaw_opd_api_server.py |
| _prm_eval_majority_vote() | Eval 多数投票(仅 Combine) | openclaw_opd_api_server.py |
Policy Training
定义:利用reward信号更新策略参数
策略训练模块(Policy Training Pipeline)基于Megatron等分布式训练框架构建,采用PPO等强化学习算法实现模型优化。其创新点在于:
- 独立数据队列机制:隔离在线服务与训练数据流
- 增量学习架构:支持动态权重更新而不中断服务
功能定位
- 模型优化:基于收集的样本和奖励信号更新策略模型
- 分布式训练:支持多GPU和多节点分布式训练
技术实现
- 训练框架:基于Slime和Megatron-LM实现
- 算法支持:GRPO、PPO、KL正则化等多种RL算法
- 核心文件:slime/train_async.py及相关训练脚本
具体如下:
| 模块 | 角色 | 位置 |
|---|---|---|
| _maybe_submit_ready_samples() | 等 PRM 完成后触发 sample 提交 | 各 api_server.py |
| _submit_turn_sample() | 构造 Sample 对象(loss_mask, reward) | 各 api_server.py |
| _submit_rl_turn_sample() | RL-only 样本(Combine) | openclaw_combine_api_server.py |
| output_queue.put(…) | 跨线程传递 Sample | 各 api_server.py |
Slime训练主循环:
| 模块 | 角色 | 位置 |
|---|---|---|
| generate_rollout_openclaw() | Slime rollout 入口(被动收集) | openclaw_rollout.py |
| _drain_output_queue() | 等待 N 个样本积累 | openclaw_rollout.py |
| AsyncRolloutWorker.pause/resume_submission() | 控制 API 开关(weight sync 期间暂停) | openclaw_rollout.py |
| compute_advantages_and_returns() | 计算 GRPO/OPD advantage | slime/…/loss.py |
| get_grpo_returns() | GRPO advantage(reward 标量广播) | ppo_utils.py |
| compute_policy_loss() | PPO clip 损失 | ppo_utils.py |
| combine_loss_function() | Combine 自定义损失(w_opd + w_rl) | combine_loss.py |
| Megatron Actor(GPU 0-3) | 执行梯度更新、weight sync | Slime / Megatron-LM |
| RolloutFnTrainOutput | rollout 输出格式(返回给 Slime) | openclaw_rollout.py |
GPU分配
4组件架构各自运行在哪些GPU上?具体如下:
| 组件 | GPU | 实际进程 | 核心代码 |
|---|---|---|---|
| Policy Training | GPU 0-3 | Megatron Actor,TP=4 | slime/ + openclaw_rollout.py |
| Policy Serving | GPU 4-5 | SGLang Rollout + FastAPI Proxy | openclaw_api_server.py |
| Reward Judging | GPU 6-7 | SGLang PRM/Judge | 同一个 openclaw_api_server.py 中的评分逻辑 |
| Environment | 无GPU | OpenClawApp+用户 | openclaw/ (TS app) |
即:
GPU 分配 (8卡节点, run_qwen3_4b_openclaw_rl.sh):
GPU 0-3: Megatron Actor (ACTOR_GPUS=4, TP=4) <- Policy Training
GPU 4-5: SGLang Rollout (ROLLOUT_GPUS=2, TP=2) <- Policy Serving
GPU 6-7: SGLang PRM/Judge (PRM_GPUS=2, TP=2) <- Reward Judging
1.4 模型
OpenClaw-RL 优化的是 Qwen3-4B (Actor),它同时也是serve用户的模型(通过SGLang 推理引l擎的权重副本)。三个角色(Actor/Rollout/Judge)用的都是同一个模型,但只有 Actor 被训练更新。
模型详情
具体如下:
① Actor Model (GPU 0-3, Megatron TP=4)
= 被优化的 Student模型
= 做 forward/backward/optimizer step 的那个
→ 就是OpenClaw-RL 正在训练的模型
② Rollout Mode1. (GPU 4-5,SGLang TP=2)
= 为用户服务的推理引擎
= Actor的权重副本(定期同步)
→ 不直接训练,只是Actor 的“镜像“
③ PRM Judge / Teacher (GPU 6-7,·SGLang)
= 评分+生成hint+计算teacher_log_probs
= 可以是同一模型的另一个实例(OpenClawOPD)
→ 不被训练,只是工具
- 被优化的:只有 ① ActorModel
- ② 和 ① 是同一个模型的不同副本(权重周期性同步)
- ③ 是judge/teacher(固定不变)
具体配置
# run_qwen3_4b_openclaw_rl.sh 中:
MODEL_PATH=Qwen/Qwen3-4B #所有角色都用同—个模型
# Actor(训练):Qwen3-4B(Megatron格式,做梯度更新)
# Rollout(推理):Qwen3-4B(SGLang 格式,serve 用户)
# PRM/Teacher (评估):Qwen3-4B(SGLang 格式,评分/hint/teacher scoring)
训练循环
训练循环如下:
User → Rollout (Qwen3-4B) 生成 response
→ PRM Judge(Qwen3-4B)评分/生成hint
→ Teacher (Qwen3-4B+hint) 计算 teacher_log_probs
→ Actor(Qwen3-4B)做梯度更新 ◄─── 这一步优化模型
→ 权重同步回Rollout
→ 下次用户得到更好的response
0x02 Slime 的作用
Slime 在 OpenClaw-RL 中,是核心的 RL 后训练框架,负责高效地组织 rollout、trainer和data buffer等模块,实现异步、解耦的RL训练流程。它连接了模型推理(如SGLang)、训练(如 Megatron)和数据流转,支撑了 OpenClaw-RL 的所有 RL 训练范式(OPD,Binary RL,Combine)。
2.1 集成机制
Slime 通过以下机制与OpenClaw组件集成:
-
插件化架构:通过命令行参数注入自定义函数
-
异步数据流:OpenClawAPIServer异步生产数据,Slime异步消费
-
训练资源隔离:不同组件使用独立的GPU资源,避免相互干扰
-
统一接口:所有OpenClaw变体(RL/OPD/Combine)都遵循相同的集成模式
Slime 启动 SGLang 引擎
└── Slime 启动 SlimeRouter (分配动态端口)
└── Slime 将 ip/port 写入 args
└── OpenClawAPIServer 读取 args
└── 对外暴露 PORT=30000 给 OpenClaw App
(用户流量入口)
✓ SGLang 的启动、GPU 分配、端口分配、Router 注册 全部由 Slime 控制
① 分配 GPU (Ray Placement Group)
② 动态启动 SGLang 推理引擎 + SlimeRouter (port=动态分配 -> args.sglang_router_port)
③ 动态启动 PRM Engine + PRM Router (port=动态分配 -> args.prm_router_port)
④ 启动 Megatron Actor (GPU 0-3, TP=4)
✓ OpenClawAPIServer 只是"寄生"在 Slime 的基础设施上 ◄───────────────────── "此处是关键"
✓ PORT=30000 是外部可见端口, sglang_router_port 是 Slime 内部动态分配的
这种设计使得 OpenClaw-RL能够充分利用 Slime强大的分布式训练能力,同时保持 OpenClaw组件的灵活性和可扩展性。
2.2 Slime 扩展
我们来看看 Slime 扩展的机制。
Slime 设计了插件化的钩子系统,OpenClaw-RL 通过 shell 脚本中的参数注入,不修改 Slime 核心即可完整接管整个训练流程。
插件化架构
RolloutManager是Slime中负责管理rollout数据收集的核心类,它通过以下方式(扩展点)集成OpenClaw组件:
- 自定义生成函数:通过–custom-generate-function-path指定
- 自定义奖励函数:通过–custom-rm-path指定
- 自定义损失函数:通过–custom-loss-function-path指定
- 自定义 rollout 函数:通过 --rollout-function-path 指定
具体如下:
# run_qwen3_4b_openclaw_rl.sh 中的关键参数:
--rollout-function-path openclaw_rollout.generate_rollout_openclaw # 扩展点1
--custom-generate-function-path openclaw_api_server.generate # 扩展点2
--custom-rm-path openclaw_api_server.reward_func # 扩展点3
# 无需 --custom-loss-function-path (RL用标准GRPO)
# run_qwen3_4b_openclaw_combine.sh
--custom-loss-function-path combine_loss.combine_loss_function # 扩展点4
扩展点的职责
每个扩展点的职责如下:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Slime 框架层 │
│ │
│ ┌──────────────────────┐ │
│ │ RolloutWorker │ ① rollout_function_path │
│ │ (slime/ray/ │ ───────────────────────────────► OpenClaw-RL 接管 │
│ │ rollout.py) │ Slime 调用这个函数"产生"训练数据 │
│ │ │ 函数签名: (args, rollout_id, data_buffer) │
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ │
│ │ EvalRollout │ ② custom_generate_function_path │
│ │ (batch 评估用) │ ───────────────────────────────► OpenClaw-RL 接管 │
│ │ │ 函数签名: (args, sample, sampling_params) │
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ │
│ │ Reward Model │ ③ custom_rm_path │
│ │ 接口 │ ───────────────────────────────► OpenClaw-RL 接管 │
│ │ │ 函数签名: (args, sample) → {"score": float} │
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ │
│ │ Megatron Loss │ ④ custom_loss_function_path │
│ │ Computation │ ───────────────────────────────► Combine 接管 │
│ │ │ 函数签名: (args, batch, logits, reducer) │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
2.3 OpenClaw-RL 做了哪些工作
OpenClaw-RL 的核心工作量集中在数据采集层 (openclaw_api_server.py),通过 4 个扩展点精准插入 Slime框架,完全不需要修改 Slime/Megatron 核心代码。
OpenClaw-RL 提供的工作量 Slime 框架提供的工作量
───────────────────────────────────── ─────────────────────────────────────
openclaw_api_server.py train_async.py (异步训练主循环)
- FastAPI 代理 + 会话管理 Megatron-LM (TP/PP/CP 训练)
- PRM 评分 (并发 m 次调用) SGLang (高效推理)
- next_state 检测 Ray (分布式任务调度)
- at-least-one guarantee GRPO 优势计算
- 样本提交到 output_queue PPO 损失函数
权重同步 (mbridge)
openclaw_rollout.py checkpoint 管理
- 被动等待模式 rollout WanDB 日志
- drain_output_queue
- pause/resume submission
openclaw_opd_api_server.py
- hint 提取 judge
- teacher log-probs 计算
- top-K 蒸馏
combine_loss.py
- combined_adv 计算
- 自定义 PPO loss
run_*.sh (5-6个脚本)
- GPU 分配配置
- Slime 参数绑定
- 环境变量设置
扩展点①
扩展点①: rollout_function_path - 最核心的接管。
关键创新:Slime 框架原本假设 rollout 是主动的 (给模型一个 prompt,模型生成 response)。OpenClaw-RL 把它改成被动等待 (等真实用户对话产生样本)。
函数名:generate_rollout_openclaw()
-
作用:被 Slime的 RolloutManager调用,负责协调OpenClawAPIServer 收集训练数据
-
关键操作:
- 获取全局AsyncRolloutWorker 实例
- 启用样本提交(resume_submission())
- 从输出队列收集样本
(_drain_output_queue()) - 禁用样本提交(pause_submission())
具体代码如下:
# openclaw_rollout.py
def generate_rollout_openclaw(args, rollout_id, data_buffer, evaluation=False):
"""
Slime 框架期望: 调用这个函数 → 返回 rollout_batch_size 个 Sample
OpenClaw 实现: 不主动生成! 而是等待真实用户对话产生样本
"""
worker = get_global_worker(args, data_buffer)
if evaluation:
# 标准 eval rollout (用 Slime 自带)
eval_output, _ = run(eval_rollout(args, rollout_id))
return eval_output
worker.resume_submission() # ← 开放 API 接受新会话的样本提交
completed_samples = run(
_drain_output_queue(args, worker) # ← 阻塞等待,直到收集到 rollout_batch_size 个样本
)
worker.pause_submission() # ← 关闭提交 (权重更新期间,503 所有请求)
return RolloutFnTrainOutput(samples=completed_samples, metrics=...)
标准 Slime 模式如下:
训练器 → "给我生成 rollout_batch_size 个样本" → rollout 引擎主动采样
OpenClaw-RL 模式如下:
↓
训练器 → "给我生成 rollout_batch_size 个样本"
↓
generate_rollout_openclaw() 打开阀门,等待...
↓
用户正常使用 OpenClaw (同时 API Server 收集并评分)
↓
output_queue 积满 rollout_batch_size 个样本
↓
关闭阀门 (暂停提交),返回给训练器
扩展点 ②③
扩展点 ②③:custom_generate 和 custom_rm (评估用)。
这两个函数是直通接口,真正的评分逻辑已经在 _prm_evaluate() 异步完成并存入 sample.reward。
- 生成函数:openclaw_api_server.py
- 函数名:generate()
- 作用:被Slime在评估模式下调用,用于生成单个样本的响应
- 注意:generate() 是模块级 async 函数,不是 OpenClawAPIServer 的方法
- 实现:调用 SGLang 的 /generate 端点生成响应
- 奖励函数:openclaw_api_server.py
- 函数名:reward_func()
- 作用:被 Slime调用计算样本的奖励值
- 注意:reward_func() 是模块级函数,不是 OpenClawAPIServer 的方法
- 实现:对于OpenClaw-RL,直接返回样本中已有的PRM评分
# openclaw-rl/openclaw_api_server.py 中的两个模块级函数 (仅用于 eval 阶段)
# Ref: openclaw-rl/openclaw_api_server.py:141,149
async def generate(args, sample, sampling_params, evaluation=False) → Sample:
# 直接调用 SGLang 的 /generate 端点
# 返回带 rollout_log_probs 的 Sample
...
async def reward_func(args, sample_or_samples, **kwargs):
# 把 sample.reward["score"] 透传回去 (实际评分在 API Server 已完成)
return {"score": s.reward.get("score", 0.0)}
扩展点 ④
扩展点 ④:custom_loss_function_path (仅 Combine 使用)。
# combine_loss.py
def combine_loss_function(args, batch, logits, sum_of_sample_mean):
# Slime 框架注入:batch["advantages"] → GRPO 预计算优势
# batch["teacher_log_probs"] → OPD teacher 信号
# logits → Megatron 当前 forward pass 输出
combined_advantages = w_opd * teacher_adv + w_rl * grpo_adv
loss = PPO_clip_loss(combined_advantages) + KL_loss
return loss, metrics
自定义 Advantage 如何接入 Trainer
核心设计思路:API Server 负责"算好所有原材料"(reward、teacher_lp、rollout_lp),Slime 只负责"组装 advantage + 算 loss"。三种方法的 advantage 注入路径各不相同:
- Binary RL 用 Slime 内置 GRPO
- OPD 靠字段劫持
- Combine 用 custom loss
关键区分:不是所有方法都用同一条路径注入 advantage。
Binary RL — 走 Slime 内置 GRPO 路径
reward_func(reward=±1) → Slime 自动计算 GRPO advantage → PPO clip loss
↓
ppo_utils.py L207: advantages[i] = ones_like(kl) * rewards[i] # broadcast
- 不需要 custom-loss-function-path
- Advantage 由 Slime 内部的 get_grpo_returns() 计算
- OpenClaw 只负责提供 reward 值
OPD — 劫持 Slime 的 advantage 字段
API Server 把 teacher_log_probs 塞进 sample 字段
↓
Slime loss.py 读到 teacher_log_probs
↓
advantages[i] = teacher_log_probs[i] - rollout_log_probs[i] # per-token
↓
正常 PPO clip loss
- 也不需要 custom-loss-function-path
- Slime 的 loss.py 内部已有 OPD 分支,由 CLI 参数 `--advantage-estimator on_policy_distillation` 触发,而非自动字段检测
Combine — 唯一需要 custom loss 的
combine_loss.py::combine_loss_function
↓
读取 batch["advantages"](GRPO 已算好的)
读取 batch["teacher_log_probs"] 和 batch["rollout_log_probs"]
↓
combined_adv = w_rl * grpo_adv + w_opd * (teacher_lp - rollout_lp)
↓
用 combined_adv 替换原 advantage → PPO clip loss
数据流全景图
API Server Slime Trainer
────────── ────────────
Binary RL: reward=±1 ─────────────────→ get_grpo_returns() → A=R → loss
(Slime 内置)
OPD: teacher_log_probs ─────────→ --advantage-estimator on_policy_distillation →
A=t_lp-r_lp → loss
+ rollout_log_probs (CLI 参数触发 OPD 分支)
Combine: reward=±1 ─────────────────→ get_grpo_returns() → grpo_adv ─┐
teacher_log_probs ──────────────────────────────────────────┤
rollout_log_probs ──────────────────────────────────────────┤
↓
combine_loss.py (custom) → merged_adv → loss
2.4 集成
入口
主训练入口:slime/train_async.py,这是Slime异步训练的核心文件,主要功能包括:
- 初始化 Ray 集群:创建 placement groups 分配 GPu资源
- 创建 Rollout Manager:初始化包含 SGLang 引l擎的 rollout 管理器
- 创建训练模型:初始化 actor 和 critic模型
- 异步训练循环:
- 调用rollout_manager.generate.remote()获取训练数据
- 调用actor_model.async_train()执行训练
- 定期保存模型和更新权重
关键调用点
- rollout_data_next_future = rollout_manager.generate.remote(args.start_rollout_id)
- rollout_data_next_future = rollout_manager.generate.remote(rollout_id + 1)
- ray.get(actor_model.async_train(rollout_id,rollout_data_curr_ref))
启动
Slime的主要调用是通过 shell脚本启动的,例如:
OpenClaw-RL:openclaw-rl/run_qwen3_4b_openclaw_rl.sh
OpenClaw-OPD:openclaw-opd/run_qwen3_4b_openclaw_opd.sh
OpenClaw-Combine: openclaw-combine/run_qwen3_4b_openclaw_combine.sh
在这些脚本的最后部分,通过 Ray Job Submit调用 Slime 的主训练文件:
ray job submit --address="http://127.0.0.1:8265"
--runtime-env-json="${RUNTIME_ENV_JSON}"\
--python3 train_async.py\--actor-num-nodes 1\
--actor-num-gpus-per-node "${ACToR_GPUS}"\
--rollout-num-gpus "${ROLLoUT_GPUS}"\#...其他参数
--custom-generate-function-path openclaw_api_server.generate\--custom-rm-path openclaw_api_server.reward_func
具体参见如下:
启动: ray job submit -> train_async.py -> Slime RolloutManager
---------------------------------------------------------------------------
┌─────────────────────────────────────────────────────────────────────────┐
│ SGLang 完全由 Slime 控制 - 启动控制链路 │
└─────────────────────────────────────────────────────────────────────────┘
ray job submit → train_async.py
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Slime: RolloutManager (Ray Actor, 运行在 Rollout GPUs 上) │
│ slime/slime/ray/rollout.py │
│ │
│ __init__ 时: │
│ │
│ ① 启动 SGLang Router │
│ _start_router(args, │
│ router_ip_attr="sglang_router_ip", │
│ router_port_attr="sglang_router_port") │
│ → 找一个可用端口 (find_available_port()) │
│ → args.sglang_router_ip / sglang_router_port 被动态设置 │
│ → 启动 SlimeRouter (uvicorn) │
│ │
│ ② 启动 SGLang 推理引擎 │
│ init_rollout_engines(args, pg, all_rollout_engines) │
│ → 为每个引擎分配 GPU (placement group) │
│ → multiprocessing.Process(target=sglang.launch_server, ...) │
│ → 等待 /health 就绪 │
│ → 注册到 SlimeRouter (/add_worker) │
│ │
│ ③ 若 prm_enable: 同样启动 PRM Engine │
│ _start_router(..., router_ip_attr="prm_router_ip", ...) │
│ init_prm_engines(...) │
│ │
│ ④ 启动 OpenClawAPIServer │
│ generate_rollout_openclaw() → get_global_worker(args, ...) │
│ → AsyncRolloutWorker.__init__(args) │
│ → OpenClawAPIServer(args, ...) │
│ sglang_chat_url = f"http://{args.sglang_router_ip}: │
│ {args.sglang_router_port}/..." │
│ (此时 ip/port 已由步骤①填入 args) |
└─────────────────────────────────────────────────────────────────────────┘
循环
Slime 框架的循环如下,可以看到 OpenClaw-RL 如何在其中运作。
- Slime 训练器→调用rollout_manager.generate()
- RolloutManager→调用自定义的generate_rollout_openclaw()函数
- OpenClawRolloutWorker→管理OpenClawAPIServer实例并收集样本
- OpenClawAPIServer→处理用户请求并生成训练样本
Slime 框架 (train_async.py)
│
├── 初始化 Ray cluster + Megatron Actor + SGLang Rollout Engine
│
├── 训练循环 while True:
│ │
│ ├── ① 调用 rollout_function_path (generate_rollout_openclaw)
│ │ │
│ │ ├── 启动 OpenClawAPIServer (FastAPI on :30000)
│ │ ├── resume_submission() <- 开始接受对话
│ │ ├── 等待 output_queue 积满 rollout_batch_size 个 Sample
│ │ │ (每个 Sample 已含 rollout_log_probs + reward["score"])
│ │ └── pause_submission() <- 停止接受对话
│ │
│ ├── ② 调用 reward_func() → 返回已有的 score (直通)
│ │
│ ├── ③ GRPO advantage 计算 (Slime 内置)
│ │ grpo_adv = broadcast(reward) to all tokens
│ │
│ ├── ④ Megatron forward pass (TP=4)
│ │
│ ├── ⑤ 调用 custom_loss_function (combine_loss 或 Slime 内置)
│ │ loss = PPO_clip(combined_adv) + β_KL * KL
│ │
│ ├── ⑥ backward + optimizer step (with CPU offload)
│ │
│ └── ⑦ 权重同步: Megatron → SGLang (mbridge)
│
└── 每 save_interval 步保存 checkpoint
2.5 核心模块
几大组件角色定位
- Slime:核心训练框架,提供分布式训练基础设施
- OpenClawAPIServer:API代理服务器,处理用户请求并生成训练样本
- AsyncRolloutWorker:异步轨迹收集工作者,协调数据收集流程
- Trainer:Slime 训练器,负责模型训练和权重更新
采用生产者一消费者一协调者模式:
-
生产者:OpenClawAPIServer(生成训练样本)
-
协调者:AsyncRolloutWorker(管理收集流程)
-
消费者:Slime Trainer(消费样本进行训练)
依赖关系如下:
- Slime(核心框架) → AsyncRolloutWorker(协调层):通过函数调用接口
- AsyncRolloutWorker → OpenClawAPIServer(数据生产层):通过队列和状态控制
- OpenClawAPIServer → SGLang(推理引擎) : 通过 HTTP API 调用
- Slime → SGLang:直接集成SGLang引擎用于评估
调用逻辑详细分析
Slime Trainer 调用 AsyncRolloutWorker
调用入口点
在 slime/train_async.py 中:
#创建rolloutmanager(包含AsyncRolloutWorker)
rollout_manager, num_rollout_per_epoch = create_rollout_manager(args, pgs["rollout"], pgs.get("prm"))
#异步生成rollout数据
rollout_data_next_future = rollout_manager.generate.remote(rollout_id + 1)
#训练actor模型
ray.get(actor_model.async_train(rollout_id, rollout_data_curr_ref))
关键调用链
- Slime Train Loop → rollout_manager.generate.remote()
- RolloutManager→调用自定义的generate_rollout_openclaw()
- AsyncRolloutWorker→管理 OpenClawAPIServer 实例
AsyncRolloutWorker 调用 OpenClawAPIServer
初始化调用
在AsyncRolloutWorker.init() 中:
#创建OpenClawAPIServer 实例
self._server = OpenClawAPIServer(args=args, output_queue=self.output_queue, submission_enabled=self._submission_enabled)
#启动服务器线程
Self.worker_thread = threading.Thread(target=self._server.run, daemon=True)
self.worker_thread.start()
状态控制调用
- 暂停提交:worker.pause_submission() → self._server.purge_record_files()
- 恢复提交:worker.resume_submission()→启用_submission_enabled 标志_
- 清理记录:self._server.purge_record_files()→清空内部状态
核心数据结构
Sample对象
来源:OpenClawAPIServer._build_sample_for_turn() _
- 字段:
prompt:用户输入文本 - response:助手响应文本
- tokens:完整token序列
- rollout_log_probs:SGLang生成的对数概率
- teacher_log_probs:教师模型对数概率 (OPD/Combine 使用)
- reward:PRM评分结果
- loss_mask:训练有效性掩码
流向:output_queue → Slime Trainer
内部状态字典
_turn_counts:会话回合计数器
_pending_records:待处理回合记录
_pending_turn_data:待评估回合数据(tokens+logprobs)
eval_scores:PRM评估结果缓存
output_queue
类型:queue.Queue()
内容:(group_index,[sample_list])元组
生产者:OpenClawAPIServer
消费者:AsyncRolloutWorker → Slime Trainer
异步任务队列
_prm_tasks:存储PRM评估的 asyncio.Task
作用:跟踪异步评估状态,避免重复评估
0x03 交互

3.1 数据流
我们来看看真实对话如何从用户流入训练系统。核心设计特点如下:
① 用户无感知 训练在后台异步进行,不影响响应速度
② Zero 标注 next_state 自动成为 reward 信号,无需人工标注
③ 完全私有 对话数据不离开用户服务器
④ 实时持续学习 每次对话都可能成为训练数据,模型持续进化
总体流程如下:
- 训练数据收集路径:用户请求→OpenClawAPIServer(生产样本)→output_queue→ AsyncRolloutWorker (收集样本)→generate_rollout_openclaw()→ RolloutManager → Slime Train Loop → Model Training
- 评估数据生成路径:Slime Evaluation → generate() function → OpenClawAPIServer._forward_to_sglang() → SGLang → Response
- 奖励计算路径:Slime Training → reward_func()→ Sample.reward.score (already computed by OpenClawAPIServer)
关键数据流总结如下:
+-----------------+----------------------------+------------------------------------+
| 阶段 | 核心组件 | 数据 |
+-----------------+----------------------------+------------------------------------+
| Policy Serving | SGLang + FastAPI Proxy | token ids, logprobs, response text |
| Environment | OpenClaw App (真实用户) | conversation turns, teacher hints |
| Reward Judging | LLM Judge (多数投票) | score ∈ {-1, 0, +1} → loss_mask |
| Policy Training | Megatron GRPO/OPD/Combine | advantages → gradient updates |
+-----------------+----------------------------+------------------------------------+
从正常训练循环时序来看,时间轴如下:
时间轴 →
T0:用户请求到达→OpenClawAPISerVer处理→样本缓冲
T1:下一状态到达→触发PRM评估→异步评分
T2: Slime 请求数据→AsyncRolloutWorker 启用提交
T3:样本提交到队列→AsyncRolloutWorker收集样本
T4: Slime开始训练→消费样本→更新权重
T5:权重更新完成→AsyncRolloutWorker禁用提交→清理状态
T6:新请求使用新策略→循环继续
但是,其总体是异步进行的,具体如下:
时间轴 (完全异步, 各组件不相互阻塞):
SGLang: 服务用户 ─────────────────── 暂停(503) ─ 加载新权重 ─ 服务用户 ──►
Proxy: 采集数据 ─────────────────── 暂停 ─────── 恢复 ───── 采集数据 ──►
PRM: 评分 ───── 评分 ───── 评分 ─────────────────────── 评分 ───── 评分 ──►
Megatron: ─ 等待数据 ───────────── 训练 ─ 同步 ─ 等待数据 ─── 训练 ─────────►
3.2 模块交互
关键接口 (模块间通信)如下:
OpenClaw App ──HTTP/JSON──► FastAPI Proxy headers: X-Session-Id/Turn-Type/Done
FastAPI Proxy ──httpx──► SlimeRouter OpenAI-compatible, logprobs=True
SlimeRouter ──httpx──► SGLang Engine /v1/chat/completions
FastAPI Proxy ──httpx──► PRM SlimeRouter /generate (plain text prompt)
OpenClawAPIServer ──Queue──► AsyncRolloutWorker (group_id, [Sample])
AsyncRolloutWorker ──► generate_rollout_fn RolloutFnTrainOutput
generate_rollout_fn ──► Slime → Megatron samples list
submission_enabled Event ──► FastAPI Proxy pause(clear)/resume(set)
模块交互流程如下:
┌─────────────────┐ ┌───────────────────────────────────┐
│ OpenClaw │ OpenAI API │ RL API Server │
│ (TypeScript APP)│ ───────────────► │ (FastAPI proxy) :30000 │
│ │ (X-Session-Id, │ │
│ │ X-Turn-Type headers) │ │
│ │ ◄─────────────────── │ openclaw_api_server │
│ │ Response (Streaming) │ │
│ │ │ │ │
│ │ │ │ │
│ │ │ ▼ forward request │
│ │ │ ┌─────────────────────────────┐ │
│ │ │ │ SGLang 推理引擎 │ │
│ │ │ │ (Policy Model) │ │
│ │ │ │ rollout_log_probs │ │
│ │ │ └─────────────────────────────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ 当 next_state 到达, 发起评分 │
│ │ │ │ │
│ │ │ ┌─────────────────────────────┐ │
│ │ │ │ PRM/Judge │ │
│ │ │ │ majority vote │ │
│ │ │ │ m 次独立评分 │ │
│ │ │ │ │ │
│ │ │ │ (独立 SGLang 实例) │ │
│ │ │ │ — Binary RL → score ±1/0 │ │
│ │ │ │ — OPD → hint 文本 │ │
│ │ │ └─────────────────────────────┘ │
│ │ │ │ │
│ │ │ ▼ Training Sample │
│ │ │ ┌─────────────────────────────┐ │
│ │ │ │ output_queue │ │
│ │ │ │ (queue.Queue) │ │
│ │ │ │ (Slime 管理) │ │
│ │ │ └─────────────────────────────┘ │
│ │ │ │ │
│ │ │ ▼ 每 rollout_batch_size │
│ │ │ 个样本触发 │
│ │ │ ┌─────────────────────────────┐ │
│ │ │ │ Megatron-LM 训练器 │ │
│ │ │ │ GRPO / OPD / Combined loss │ │
│ │ │ │ TP=4, optimizer offload │ │
│ │ │ └─────────────────────────────┘ │
│ │ │ │ │
│ │ │ ▼ 权重同步 │
│ │ │ ┌─────────────────────────────┐ │
│ │ │ │ SGLang 推理引擎 │ │
│ │ │ │ │ │
│ │ │ │ (policy 权重更新) │ │
│ │ │ └─────────────────────────────┘ │
└─────────────────┘ └───────────────────────────────────┘
GPU 分配 (默认 8 卡):
┌────────────────────────────────────────┐
│ GPU 0-3 │ Megatron Actor (TP=4) │
│ GPU 4-5 │ SGLang Rollout Engine │
│ GPU 6-7 │ SGLang PRM/Judge Engine │
└────────────────────────────────────────┘
具体展开如下:

3.3 详细流程
Policy Serving
【1. POLICY SERVING】 GPU 4-5 - SGLang Rollout Engine
用户 (Telegram/Discord/Signal/iMessage/Slack/WhatsApp/Web)
(即用户端 – 通过各消息渠道与 AI 对话)
|
▼
OpenClaw App (TypeScript) 部署在用户自己的服务器上,完全私有 / self-hosted
|
| +--------------------------------------------------------------------+
| | POST /v1/chat/completions (到 FastAPI Proxy, port 30000) |
| | |
| | Headers: |
| | Authorization: Bearer {SGLANG_API_KEY} |
| | X-Session-Id: "user_abc_20260419" <- 按对话分组 (同一 session) |
| | X-Turn-Type: "main" <- 训练用途 |
| | "side" <- 跳过, 不产生训练数据 |
| | X-Session-Done: "true" / "false" <- 对话是否结束 |
| | |
| | Body: |
| | { |
| | "model": "qwen3-4b", |
| | "messages": [ |
| | {"role": "system", "content": "..."}, |
| | {"role": "user", "content": "帮我写个排序算法"}, |
| | {"role": "assistant", "content": "..."}, <- 历史轮次 |
| | {"role": "user", "content": "能用快排吗?"} <- 当前请求 |
| | ], |
| | "tools": [...], <- 工具定义 (agentic 场景) |
| | "stream": true/false |
| | } |
| +--------------------------------------------------------------------+
|
|
▼
┌───────────────────────────────────────────────────────────────────────┐
│ FastAPI Proxy (openclaw_api_server.py) │
│ | │
│ ├─ 接收请求 │
│ | ├─ 权限验证 (Bearer Token) │
│ | ├─ 检查 submission_enabled (503 if 权重同步中) │
│ | └─ 解析 session_id / turn_type / session_done │
│ | │
│ ├─ 转发到 SGLang (port 30001) │
│ | └─ 强制追加 logprobs=True, top_logprobs=1 │
│ | │
│ ├─ 拦截响应 (turn_type == "main") │
│ | ├─ 提取 response_text / reasoning_content / tool_calls │
│ | ├─ 提取 per-token logprobs -> rollout_log_probs (old policy π_old)│
│ | ├─ tokenize(messages) -> prompt_ids │
│ | ├─ tokenize(messages+resp) -> prompt_ids + response_ids │
│ | └─ 存入 _pending_turn_data[session_id][turn_num] │
│ | │
│ ├─ next_state 处理 │
│ | 当 turn N+1 的请求到来: │
│ | messages[-1] == turn N 的 next_state (user/tool 回复) │
│ | -> _flush_pending_record(session_id, next_state) │
│ | -> 触发 PRM 异步评分 │
│ | │
│ └─ 返回响应 (turns)给 OpenClaw App (支持 streaming) │
└───────────────────────────────────────────────────────────────────────┘
|
| 实时推理流
▼
┌────────────────────┐
│ SGLang vLLM Engine │ (--rollout-num-gpus-per-engine 2)
│ Qwen3-4B Actor │ ← 当前 policy 权重
└────────────────────┘
|
| 真实用户消息 + 模型响应 (turns)
▼
注:
-
next state:OpenClaw-RL 关心一个贴近真实部署的问题:”agent 在线运行时,每一步交互后天然产生的 next-state signal,能不能直接被回收成训练信号?” 。这里的 next state 可以是很多东西:用户下一句回复、工具执行结果、终端 stdout/stderr、GUI 状态变化、SWE 环境中的测试 verdict 或报错 trace。
-
main vs side 区分逻辑:
┌────────────────────────────────┐
│ main:用户对话的主线 → 产生训练数据 │
│ │
│ side:辅助性调用,例如: │
│ ·UI渲染前的pre-flight请求 │
│ ·工具调用前的参数生成 │
│ ·非核心交互(session管理等) │
│ → 请求被完整转发,但不截取训练样本 │
└────────────────────────────────┘
Environment
Environment在OpenClaw中没有代码实体,它就是“真实用户+HTTP协议“本身,下面代码只是展示。
【2. ENVIRONMENT】 - OpenClaw App (TypeScript, openclaw/)
真实用户 -- → OpenClaw App -- → FastAPI Proxy
每个 turn 记录 :
• prompt_ids / response_ids
• rollout_log_probs (old-policy log-probs)
• response_text / prompt_text
• session_id (按对话分组)
Turn N+1 请求到来时:
messages[-1] = turn N 的 next_state (用户回复 or 工具返回值)
-> _flush_pending_record(session_id, next_state)
-> 触发 Judge 异步评分
[OPD/Combine 额外操作]
• 调用 Teacher 模型生成 hint (\boxed{1}[HINT_START]...[HINT_END])
• 将 hint 追加到 messages 最后一条 user 消息末尾,构成enhanced_messages
• 用 Teacher 前向计算 teacher_log_probs (max_new_tokens=0)
• 支持 Top-K: teacher_topk_log_probs [T,K] + teacher_topk_indices [T,K]
|
| turn data (含 response + next_state)
▼
Reward Judging
具体如下图所示。
【3. REWARD JUDGING】 GPU 6-7 - SGLang PRM/Judge Engine
Zero-shot LLM Judge (同族 Qwen3 模型, 非训练 RM)
输入: assistant_output + next_state (user/tool)
输出: \boxed{1} | \boxed{0} | \boxed{-1}
--------------------- Binary RL: PRM Judge (m=3 majority vote) ---------------
输入: response_text + next_state (role: user|tool)
\boxed{1}=好 / \boxed{0}=中性 / \boxed{-1}=差 -> final_score
at-least-one: session全为0时首个turn强制 loss_mask=[1]
----------------------------- OPD/Combine: 双 Judge 并发 -----------------------
+-------------------------------+ +-------------------------------------+
| Hint Judge (m=3 votes) | | PRM Eval Judge (m=3 votes) |
| \boxed{1}: 有 hint | | \boxed{1}/\boxed{0}/\boxed{-1} |
| [HINT_START]...[HINT_END] | | -> eval_score |
| \boxed{-1}: 无 hint | +-------------------------------------+
| _select_best_hint: 最长有效 |
| hint (>10字符) |
+-------------------------------+
+-------------------------------------------+
| Majority Vote (m=3, async) |
| +1: positive → loss_mask=[1] |
| 0: neutral → loss_mask=[0] |
| -1: negative → loss_mask=[1] |
+-------------------------------------------+
(at-least-one guarantee: 若全0,
首个turn强制升为 loss_mask=[1])
-------------------------------- Combine 三路分发 --------------------------------
[Combine 3-way dispatch]
hint✓ + eval±1 → OPD+RL sample (teacher_log_probs + reward=eval)
hint✓ + eval=0 → OPD-only sample (teacher_log_probs + reward=0)
hint✗ + eval±1 → RL-only sample (teacher_lp=rollout_lp + reward=eval)
hint✗ + eval=0 → drop
---------------------------------------------------------------------------------
|
| scored samples → output_queue
▼
Policy Training
具体如下图所示。
【4. POLICY TRAINING】 GPU 0-3 - Megatron Actor (TP=4)
generate_rollout_openclaw(): [passive rollout, --disable-rollout-global-dataset, 等待真实对话而非生成]
|
|
▼ drain output_queue (rollout_batch_size=16),即等待 rollout_batch_size=16 组 sample
+-----------------------------------------------------------------------------+
| |
| Advantage 计算: |
| |
| Binary RL (GRPO): |
| adv[t] = reward (scalar broadcast) --disable-rewards-normalization |
| = reward[i] * ones(scalar broadcast) --disable-rewards-normalization |
| OPD: |
| adv[t] = teacher_log_probs[t] - rollout_log_probs[t] (per-token) |
| |
| Combine (combine_loss.py): |
| teacher_adv[t] = teacher_lp[t] - rollout_lp[t] |
| grpo_adv[t] = reward (broadcast) |
| combined_adv = w_opd * teacher_adv + w_rl * grpo_adv |
| (OPENCLAW_COMBINE_W_OPD=1.0, OPENCLAW_COMBINE_W_RL=1.0) |
| |
| PPO clipped loss: |
| ppo_kl = old_lp - new_lp |
| pg_loss = -min(exp(-ppo_kl)*adv, clip(exp(-ppo_kl),1-ε,1+ε_h)*adv) |
| ε=0.2, ε_high=0.28 (asymmetric) |
| loss = pg_loss - entropy_coef*entropy + kl_coef*KL(new||ref) |
| |
| Adam (lr=1e-5, β1=0.9, β2=0.98, CPU offload) -> 梯度更新 |
+-----------------------------------------------------------------------------+
|
|
▼
权重更新后 - → [权重同步] - → SGLang 暂停(503) - → sync - → 恢复
+------------------------------------------------------------------------+
| Megatron -> SGLang 权重同步 |
| 1. Megatron checkpoint -> HF 格式 |
| 2. SGLang Engine 热加载新权重 |
| 3. submission_enabled.set() -> 恢复接受请求 |
+------------------------------------------------------------------------+
│ ▲
│updated weights │
└─────────────────┘
循环 (async loop)
三种方法的路径选择
Binary RL: Proxy→PRM评分→Sample(reward)→GRPO adv→policy_loss→更新
OPD: Proxy → PRM+hint+teacher_lp → Sample(teacher_lp)→OPD adv →policy_loss →更新
Combine: Proxy →PRM+hint+teacher_lp→Sample(reward+teacher_lp)→GRPO adv→custom_loss(混合)→更新
3.4 完整时序
=======================================================================
Phase 1: 用户对话 → Rollout
=======================================================================
用户手机 → HTTP POST → FastAPI Proxy (GPU 4-5 SGLang)
Request:
messages: [{"role": "user", "content": "北京有什么好玩的?"}]
headers: X-Session-Id: sess_abc, X-Turn-Type: main
SGLang 生成:
response: "北京 有 很多 好玩 的 地方,比如 故宫 、 长城 ..."
response_logprobs: [-1.2, -0.8, -2.1, -1.5, -0.3, -1.8, -0.9, -0.5, -1.1, ...]
北京 有 很多 好玩 的 地方 , 比如 故宫
Proxy 拦截并记录:
prompt_ids = tokenize ("北京有什么好玩的?")
response_ids = tokenize ("北京有很多好玩的地方,比如故宫、长城...")
response_logprobs = [-1.2, -0.8, -2.1, ...] ← π_old 的 log probs
=======================================================================
Phase 2: PRM 评分 (GPU 6-7)
=======================================================================
3 次独立调用 (majority vote, m=3):
Call 1: PRM prompt + response → "这个回答准确全面" → \boxed {1}
Call 2: PRM prompt + response → "信息丰富" → \boxed {1}
Call 3: PRM prompt + response → "还行但可以更好" → \boxed {0}
majority_vote([1, 1, 0]) = 1 ← reward = +1
=======================================================================
Phase 2b: (仅 OPD/Combine) Hint + Teacher log-probs
=======================================================================
Hint judge 调用:
"给这个回答提供改进建议" → \boxed{1}[HINT_START]可以加上交通建议[HINT_END]
hint 注入后的用户消息:"北京有什么好玩的?\n[HINT: 可以加上交通建议]"
Teacher forward pass (max_new_tokens=0, 不生成):
输入:prompt(含hint) + response
输出:teacher_log_probs = [-0.9, -0.6, -1.8, -1.0, -0.2, -1.5, -0.7, -0.3, -0.8, ...]
北京 有 很多 好玩 的 地方 , 比如 故宫
=======================================================================
Phase 3: 构建 Sample 对象
=======================================================================
sample = Sample()
sample.tokens = prompt_ids + response_ids
sample.response_length = 10 (假设 10 个 token)
sample.rollout_log_probs = [-1.2, -0.8, -2.1, -1.5, -0.3, -1.8, -0.9, -0.5, -1.1, ...]
sample.teacher_log_probs = [-0.9, -0.6, -1.8, -1.0, -0.2, -1.5, -0.7, -0.3, -0.8, ...]
sample.reward = {"score": 1.0}
sample.loss_mask = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
→ 放入 output_queue → Slime 的 rollout 函数取走
=======================================================================
Phase 4: Advantage 计算 (Slime Trainer)
=======================================================================
Binary RL (--advantage-estimator grpo):
advantages = [+1, +1, +1, +1, +1, +1, +1, +1, +1, +1] ← reward broadcast
OPD (--advantage-estimator on_policy_distillation):
token: 北京 有 很多 好玩 的 地方 , 比如 故宫
teacher: -0.9 -0.6 -1.8 -1.0 -0.2 -1.5 -0.7 -0.3 -0.8
student: -1.1 -0.7 -2.0 -1.3 -0.3 -1.7 -0.8 -0.4 -1.0
advantage: +0.2 +0.1 +0.2 +0.3 +0.1 +0.2 +0.1 +0.1 +0.2
↑ teacher 都比 student 更自信 → 全部正向 (向 teacher 靠拢)
Combine:
grpo_adv: [+1, +1, +1, +1, +1, +1, +1, +1, +1 ]
teacher_adv: [+0.2, +0.1, +0.2, +0.3, +0.1, +0.2, +0.1, +0.1, +0.2]
combined: [+1.2, +1.1, +1.2, +1.3, +1.1, +1.2, +1.1, +1.1, +1.2]
↑ "好玩" 最受 teacher 青睐
=======================================================================
Phase 5: PPO 更新 (以 Combine 为例)
=======================================================================
当前 forward pass (π_new):
new_log_probs = [-1.0, -0.7, -1.9, -1.2, -0.25, -1.6, -0.85, -0.45, -0.95]
token: 北京 有 很多 好玩 的 地方 , 比如 故宫
old_lp: -1.2 -0.8 -2.1 -1.5 -0.3 -1.8 -0.9 -0.5 -1.1
new_lp: -1.0 -0.7 -1.9 -1.2 -0.25 -1.6 -0.85 -0.45 -0.95
kl: -0.2 -0.1 -0.2 -0.3 -0.05 -0.2 -0.05 -0.05 -0.15
# ratio = exp(new_lp - old_lp) ← per-token
ratio: 1.22 1.11 1.22 1.35 1.05 1.22 1.05 1.05 1.16
↑ 1.35 > 1.28 → 会被 clip!
advantage: [+1.2, +1.1, +1.2, +1.3, +1.1, +1.2, +1.1, +1.1, +1.2]
# Binary RL / OPD: --loss-type policy_loss
# → Slime 内置policy_loss_function
# Combine:--loss-type custom_loss
# → combine_loss.combine_loss_function
# → combined_adv = w_rl*grpo + w_opd*(teacher-old)
pg_loss1 = -A * ratio:
[-1.46, -1.22, -1.46, -1.76, -1.16, -1.46, -1.16, -1.16, -1.39]
pg_loss2 = -A * clamp(ratio, 0.8, 1.28):
[-1.46, -1.22, -1.46, -1.66, -1.16, -1.46, -1.16, -1.16, -1.39]
↑ clamp(1.35→1.28) × 1.3 = 1.66 (被限速!)
pg_loss = max(loss1, loss2):
[-1.46, -1.22, -1.46, -1.66, -1.16, -1.46, -1.16, -1.16, -1.39]
↑ "好玩"被 clip,防止步子太大
sample_mean_loss = mean(pg_loss) = -1.30
=======================================================================
Phase 6: 参数更新
=======================================================================
total_loss = -1.30 (这个样本的贡献)
total_loss += 其他样本的贡献...
total_loss /= global_batch_size
total_loss.backward() → ∇θ
optimizer.step() → θ_new = θ_old - lr × Adam(∇θ)
效果:"北京"、"好玩"、"故宫" 等 token 的概率都增大了.
"好玩" 想增大更多但被 clip 限速了
0xEE 个人信息
★★★★★★关于生活和技术的思考★★★★★★
微信公众账号:罗西的思考

0xFF 参考
更多推荐



所有评论(0)