【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

framework

0x01 架构

OpenClaw 的 RL 训练 = 一套统一的 PPO 框架 + 三种不同的 advantage 注入方式

ini 复制代码
┌────────────┬──────────────────────────────┬─────────────────────────────────────┐
│   方法      │   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)            │ 最灵活,可调权重                       │
└────────────┴──────────────────────────────┴─────────────────────────────────────┘

关键设计原则:

  1. 统一 PPO clip 框架:三种方法共享同一套 ratio-based clipped loss
  2. 数据驱动分流:Combine 通过设置 teacher_lp=rollout_lp 或 reward=0 自动区分 OPD/RL 样本
  3. 无额外模型:GRPO 替代 Critic,teacher 只做 forward pass(不训练)
  4. 异步架构:Proxy 实时拦截用户对话,Trainer 后台持续更新

1.1 架构图

OpenClaw-RL 的系统架构图如下:

整体架构

1.2 File Structure

OpenClaw-RL 的的文件结构如下。

bash 复制代码
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/             # 环境搭建指南

模块职责划分如下。

arduino 复制代码
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的区别

4-OpenClaw-RL四大组件 vs 标准RL的区别

四个阶段的模块归属
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)

即:

ini 复制代码
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 被训练更新。

模型详情

具体如下:

ini 复制代码
① 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(固定不变)
具体配置
ini 复制代码
# 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)
训练循环

训练循环如下:

vbscript 复制代码
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)都遵循相同的集成模式
ini 复制代码
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 指定

具体如下:

r 复制代码
# 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-扩展点

2.3 OpenClaw-RL 做了哪些工作

OpenClaw-RL 的核心工作量集中在数据采集层 (openclaw_api_server.py),通过 4 个扩展点精准插入 Slime框架,完全不需要修改 Slime/Megatron 核心代码。

scss 复制代码
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())

具体代码如下:

python 复制代码
# 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 模式如下:

arduino 复制代码
训练器 → "给我生成 rollout_batch_size 个样本" → rollout 引擎主动采样

OpenClaw-RL 模式如下:

scss 复制代码
    ↓ 
训练器 → "给我生成 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评分
python 复制代码
# 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 使用)。

ini 复制代码
# 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 路径
ini 复制代码
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 字段
css 复制代码
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 的
less 复制代码
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
数据流全景图

数据流全图

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脚本启动的,例如:

ruby 复制代码
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 的主训练文件:

dart 复制代码
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

具体参见如下:

slime-启动

循环

Slime 框架的循环如下,可以看到 OpenClaw-RL 如何在其中运作。

  • Slime 训练器→调用rollout_manager.generate()
  • RolloutManager→调用自定义的generate_rollout_openclaw()函数
  • OpenClawRolloutWorker→管理OpenClawAPIServer实例并收集样本
  • OpenClawAPIServer→处理用户请求并生成训练样本
ini 复制代码
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 中:

ini 复制代码
#创建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() 中:

python 复制代码
#创建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)

关键数据流总结如下:

diff 复制代码
+-----------------+----------------------------+------------------------------------+
| 阶段            | 核心组件                    | 数据                                |
+-----------------+----------------------------+------------------------------------+
| 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    |
+-----------------+----------------------------+------------------------------------+

从正常训练循环时序来看,时间轴如下:

makefile 复制代码
时间轴 →

T0:用户请求到达→OpenClawAPISerVer处理→样本缓冲

T1:下一状态到达→触发PRM评估→异步评分

T2: Slime 请求数据→AsyncRolloutWorker 启用提交

T3:样本提交到队列→AsyncRolloutWorker收集样本 

T4: Slime开始训练→消费样本→更新权重

T5:权重更新完成→AsyncRolloutWorker禁用提交→清理状态

T6:新请求使用新策略→循环继续 

但是,其总体是异步进行的,具体如下:

javascript 复制代码
时间轴 (完全异步, 各组件不相互阻塞):
    SGLang:    服务用户 ─────────────────── 暂停(503) ─ 加载新权重 ─ 服务用户 ──►
    Proxy:     采集数据 ─────────────────── 暂停 ─────── 恢复 ───── 采集数据 ──►
    PRM:       评分 ───── 评分 ───── 评分 ─────────────────────── 评分 ───── 评分 ──►
    Megatron:  ─ 等待数据 ───────────── 训练 ─ 同步 ─ 等待数据 ─── 训练 ─────────►

3.2 模块交互

关键接口 (模块间通信)如下:

bash 复制代码
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)

模块交互流程如下:

模块交互流程

具体展开如下:

openclawRL-模块交互图

3.3 详细流程

Policy Serving

4-Policy Serving

注:

  • next state:OpenClaw-RL 关心一个贴近真实部署的问题:"agent 在线运行时,每一步交互后天然产生的 next-state signal,能不能直接被回收成训练信号?" 。这里的 next state 可以是很多东西:用户下一句回复、工具执行结果、终端 stdout/stderr、GUI 状态变化、SWE 环境中的测试 verdict 或报错 trace。
  • main vs side 区分逻辑:
css 复制代码
┌────────────────────────────────┐
│ main:用户对话的主线 → 产生训练数据 │
│                                │
│ side:辅助性调用,例如:          │
│     ·UI渲染前的pre-flight请求    │
│     ·工具调用前的参数生成         │
│     ·非核心交互(session管理等)  │
│  → 请求被完整转发,但不截取训练样本 │
└────────────────────────────────┘
Environment

Environment在OpenClaw中没有代码实体,它就是"真实用户+HTTP协议"本身。

4-Environment

Reward Judging

具体如下图所示。

4-Reward Judging

Policy Training

具体如下图所示。

4-Policy Training

三种方法的路径选择

  • 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 完整时序

ini 复制代码
=======================================================================
                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 限速了       

0xFF 参考

本文使用 markdown.com.cn 排版

相关推荐
2601_957888561 小时前
从关键词到语义网络:生成式引擎优化(GEO)的技术原理解析与工程实践
人工智能·大模型
QiLinkOS1 小时前
从技术到资产的跃迁:企业专利布局的深层逻辑
c语言·数据结构·c++·单片机·嵌入式硬件·算法·开源
2501_934440231 小时前
简申的服务哲学中,“专业”从来不是冰冷的技术名词,而是一种设身处地的责任担当
人工智能
慧一居士1 小时前
OpenAI API 协议、 Chat Completions API、Responses API 协议 对比和联系,适用场景以及还有哪些其他协议详解
人工智能
aini_lovee1 小时前
FMCW雷达测速测距系统(锯齿波 + CFAR检测)
算法
qq_297574672 小时前
设计模式系列文章(基础篇第 11 篇):模板方法模式——定义算法骨架,实现代码复用与流程统一
算法·设计模式·模板方法模式
TAOCARTS0012 小时前
反向海淘旺季运营技巧,借助独立站快速拉升店铺单量
大数据·人工智能
lqqjuly2 小时前
知识蒸馏:理论、算法与可运行实现
人工智能·深度学习·算法
小丶舟2 小时前
6GB显卡跑Hermes Agent!开源AI自学习编程Agent实测
人工智能·学习·开源