首页
看点啥
插画图片
首页 热点时事 Agentic RL / 强化学习 / OPD: OpenClaw-RL 源码阅读笔记 --- (6)--- Ro...

Agentic RL / 强化学习 / OPD: OpenClaw-RL 源码阅读笔记 --- (6)--- Ro...

2026-06-20 0

原创 罗西的思考 2026-06-18 20:16 浙江

【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (6)--- Rollout

0x00 概要

本系列的目的是:借着对 OpenClaw-RL 源码的学习,来梳理强化学习的一些相关概念和思想。所以,会有一些基础知识、扩展和发散,OpenClaw-RL 只是一个切入点。而且,因为整篇系列是一个整体,所以有些概念的解读/学习会在不同的文章中出现,还请大家谅解。

OpenClaw-RL 是一个用于在线强化学习(Online RL)的框架,专门针对智能体工具使用场景。它通过从环境反馈中提取过程奖励信号来训练语言模型,支持三种主要模式:

framework

可以把 RL 训练管道划分为如下5 个阶段(会有重叠,依据不同系统而不同),本篇介绍Rollout。

Stage1 Stage2 Stage3 Stage4 Stage5
────────────────── ───────── ───────── ─────────
PromptRolloutRewardAdvantage Gradient
SelectionGenerationScoringComputationUpdate


"问什么""怎么答" "打几分" "好了多少""往哪走"

0x01 Rollout基础

Rollout = 用策略在环境中执行并产生轨迹 τ = (s₀, a₀, r₀, ..., sₜ, aₜ, rₜ)。

1.1 概念

在 RL 框架中,"rollout" 这个词同时指代:

在Slime代码中,generate_rollout_openclaw()函数名用的是含义 1(执行rollout过程),返回的 RolloutFnTrainOutput(samples=...)是含义2(rollout的结果数据)。

1.1.1 标准 RL

Rollout = 在环境中执行策略,产生一条完整的交互轨迹(trajectory)。

形式化:

给定策略 π 和环境 E
一次 rollout 产生一条轨迹 τ: τ = (s₀, a₀,r₀, s₁, a₁,r₁, ..., sₜ, aₜ,rₜ)


其中:
s₀ ~ ρ₀ (初始状态,从 prompt 分布采样)
aₜ ~ π(・|sₜ) (策略生成 action)
sₜ₊₁ ~ P (・|sₜ,aₜ) (环境转移)
rₜ =R(sₜ, aₜ)(环境给出奖励)

1.1.2 LLM RL

在 LLM RL 中,Rollout = 给定一个 prompt, 模型生成一个完整 response + 记录 log-probs + 打分。当然,也有人这么归纳:一次 rollout = 给定一个 prompt, 模型生成一个完整 response

s₀ = prompt (初始状态)
a₀, a₁, ..., aₜ = response 的每个 token (一系列 action)
r = 对整个 response 的打分 (terminal reward)
轨迹 τ = (prompt, token₁, token₂, ..., tokenₜ, reward)

注意: LLM 的 rollout 通常是 single-step episode (一轮就结束), 不像游戏有多步交互。

1.1.3 GPRO

一个 GRPO rollout batch:

每条轨迹包含:

1.1.4 OpenClaw-RL

OpenClaw 的 "rollout"的特点:

每条轨迹包括:

主动和被动的对比如下。

标准 RL Rollout:
────────────────────────────────────────────────
dataset = load ("math_data.jsonl")
forpromptindataset.sample (batch_size): ← 主动选题
responses = model.generate (prompt, n=4)← 主动生成 N 个
forrespinresponses:
score = reward_model (resp)
submit (prompt, resp, score)


OpenClaw Rollout:
────────────────────────────────────────────────
@openclaw_rollout.py
defgenerate_rollout_openclaw(...):
worker.resume_submission()← 打开阀门
whilelen(data) < rollout_batch_size:
data += queue.get()← 等!等用户发消息
awaitasyncio.sleep(0.05) ← 继续等...
worker.pause_submission ()← 关阀门
returndata
# 数据从哪来?从 API Server 的请求处理流程来
# rollout 函数本身不生成任何数据!

具体可以参见下表

标准

OpenClaw

谁控制prompt?

训练系统

用户

谁控制N?

训练系统(n=4~16)

用户(永远n=1)

数据到达时间

确定的(GPU生成速度)

不确定的(等用户)

--disable-rollout-global-dataset

不需要

必须(没有dataset)

1.2 RL2 对比

我们用 RL2 这个框架来做对比,看看它是怎么做rollout的。

RL2 的本质架构为:在同一组 GPU 上交替做推理和训练。或者说,RL2 = 一个on-policy RL循环,把LLM当policy network,把推理服务器当采样器。

6-RL2

展开核心数据流如下:

6-核心数据流

三个核心子系统及其职责:

注意:

  1. Reward 不是独立模块—它集成在 env_step 内,实现方式完全灵活(规则/外部服务/LLM judge)

  2. PRM 可通过多轮环境实现—每个 step 返回中间 reward,累加到轨迹中

  3. 整个 Rollout 是异步的—SampleGroup 并发、env_step 可调外部网络、SGLang 请求并发

  4. 所有组件共享同一组 GPU—通过 offload + memory occupation 管理实现时分复用

0x02 OpenClaw-RL Rollout基础

在 OpenClaw-RL 中,Rollout 是Policy Serving + Environment 的交叉。

Rollout = 在环境中执行策略,生成完整轨迹的过程 = Policy的推理输出 × Environment的状态转移

Rollout的完整循环如下:

Environment提供 State(t)(用户消息)

Policy Serving 执行推理 → Action(t)(模型回复)

Environment 接收 Action(t) → Environment 提供 State(t+1)(用户下一条消息)

重复,直到 session 结束

2.1 硬件架构

在 OpenClaw-RL 的硬件架构中,GPU 4-5 的名称是 "SGLang Rollout Engine"。但它实际负责的是 rollout 的 Policy Serving 侧:

rollout 的 Environment 侧(用户行为)在 GPU 之外:

┌──────────────────────────────────────────────────────────────┐
│ Rollout(概念上) │
│ │
│ ┌─────────────────┐ ┌───────────────────────┐│
│ │ Policy Serving │ │ Environment││
│ │ GPU4-5│ + │ 真实用户(外部) ││
│ │ LLM 推理生成回复 │ │ 提供state、接收 action ││
│ └─────────────────┘ └───────────────────────┘│
│ │
└──────────────────────────────────────────────────────────────┘

2.2 总体模块交互架构图

OpenClaw-RL 总体模块交互架构图 (Combine 方法)如下,可以从中找到Rollout相关内容。

6-模块交互架构图

2.3 Slime 的 RolloutFunction 封装

在代码层面,Slime用一个函数封装了rollout的全部逻辑:

# openclaw-rl/openclaw_rollout.py
defgenerate_rollout_openclaw(args, rollout_id, data_buffer, evaluation=False):
"""
Slime 的 rollout function:


标准rollout(主动生成):
rollout_engine.generate(prompts) → 直接调LLM生成轨迹
= Policy Serving(GPU4-5)自己完成整个rollout
Environment是静态的(题目数据集)


OpenClaw的被动rollout:
等待_sample_queue.get()→ 从真实用户对话中取已完成的轨迹
= PolicyServing已经完成了(对话已结束)
= Environment已经交互过了(用户消息已收到)
这里只是“收集“已经发生的rollout
"""
whilelen(samples) < batch_size:
sample=_sample_queue.get(block=True)#被动等待
returnsamples

-disable-rollout-global-dataset的含义就是:

具体如下图。

Slime 训练框架调用: generate_rollout_openclaw(args, rollout_id, data_buffer)
|
| passive rollout:
| 不主动生成, 等待真实对话产生数据

+---------------------------------------+
| worker.resume_submission()| <- 开启 submission_enabled Event
|_drain_output_queue() | <- 等待 rollout_batch_size=16组
+---------------------------------------+
|
|

(数据由异步 FastAPI handler 填入)

2.4 被动Rollout

OpenClaw-RL的rollout是被动rollout。generate_rollout_openclaw()等待真实用户发消息,而非主动从prompt池中选择问题生成回答。这意味着系统对rollout allocation(选什么问题训练)几乎没有控制权,由用户决定。

优势:

劣势:

2.5 小结

0x03 OpenClaw-RL Rollout 实现

3.1 Rollout 完整流程

6-Rollout 完整流程

关键设计要点

机制

实现方式

next_state 滞后

turn N 的 next_state = turn N+1 请求里 messages 的最后一条

PRM 异步

asyncio.create_task + done_callback 触发提交

at-least-one

session 全为 score=0 时,首个 turn 强制 loss_mask=1

权重同步暂停

submission_enabled Event 控制,同步中返回 503

3.2 Session 生命周期

假设我们session含有三轮。

turn1→[buffered, waiting next_state]
turn2→ flush turn1(next_state=turn2.messages[-1]) → PRM(turn1) fire
turn3→ flush turn2(next_state=turn3.messages[-1]) → PRM(turn2) fire
session_done=True → flush last_turn(next_state=None) → force_no_prm

3.2.1 示例

下图展示了 rollout 的 3-turn 示例。

6-3-turn 示例

3.2.2 单个 Turn 的完整处理流程

6-单个 Turn 的完整处理流程

3.2.3 多个 Turn 的机制

关键时序:next_state的"延迟到达“机制

Turn 1发生时:

Turn 2发生时(用户发M2=Turn1的next_state):

PRM评估R1的结果异步返回:

这个设计导致:每个 turn的 reward来自下一个HTTP请求到达的时刻,而非当前请求结束的时刻。这是OpenClaw Rollout 中最独特的工程设计。

3.3 At-Least-One Guarantee

At-Least-One Guarantee 的作用是:防止整个session贡献零梯度,确保即使最"平庸"的session也有一个turn进入训练,=Reinforce-Ada"强制至少一个梯度"的session级版本。

At-Least-One Guarantee 是最直接的零梯度修复。

具体如下:第一个被 PRM 评过(has_next_state=True)但 score=0 的 turn → 强制 loss_mask=[1],参与训练 → 至少每个 session 贡献一个样本。

# _submit_turn_sample() 中的核心逻辑:
exclude =nothas_next_stateorscore ==0.0
# 正常情况:score = 0 → exclude=True → loss_mask=[0,0,...,0]


# 但是!特殊保障:
ifexcludeandhas_next_stateandself._session_effective.get(session_id,0) ==0:
exclude =False# ← 强制参与训练!
# "at-least-one guarantee"


# openclaw_api_server.py:615-622
# 使用 _session_effective 计数器追踪每个 session 的有效样本数
# 首个 has_next_state 但 score=0 的 turn → 强制 exclude=False
ifexcludeandhas_next_stateandself._session_effective.get(session_id,0) ==0:
exclude =False# ← 强制参与训练!
# "at-least-one guarantee"
# 之后 self._session_effective[session_id] += 1

3.3.1 问题情景

情景:整个 session 的所有 turn 都 score=0

详述:用户发了 5 条消息,但每次都是中性反馈(score=0) → 所有 turn loss_mask=[0] → 这个 session 对训练没有任何贡献 → 分母增大但分子不变 → rollout_batch_size 难以填满 → 训练停滞

3.3.2 逻辑分析

3.3.3 直观类比

3.3.4 设计要点

为什么score=0用loss_mask=0而不是advantage=0?

两种方式理论上都产生零梯度(在kl-coef=0时)

实践中loss_mask=0更优:

为什么 Binary RL 需要 at-least-one

Binary RL的具体问题:训练饥饿(training starvation)

设想一个极端场景:

Session A: turn1→ score=0,turn2→ score=0,turn3→ score=0
Session B: turn1→ score=0,turn2→ score=0
- → output_queue中全是loss_mask=[0]*T 的样本
- → Slime收到rollout_batch_size个样本
- → 前向传播正常,但 ∂L/∂θ ≈0(所有token都被mask掉)
- → 实际上没有任何参数更新
- → 占用了一次完整的 rollout+forwardpass+backwardpass,什么也没学

at-least-one 的修复:

# openclaw_api_server.py
ifexclude and has_next_state and self._session_effective.get(session_id,0) ==0:
exclude=False #强制 loss_mask=[1]
# 但reward保持0.0!

注意:被promote的样本reward仍是0.0,所以advantage ≈ 0,梯度实际上接近 0。它解决的不是“学到有用信号”,而是确保:

为什么 OPD/Combine 不需要

根本原因:两种“零贡献“的本质不同。关键区别:Binary RL的零贡献样本会“占据“批次槽位但静默无效;OPD/Combine则完全不产生样本。

Binary RL的零贡献路径:

score=0 → exclude=True → loss_mask=[0]*T → 样本进入output_queue,但不产生梯度

样本在批次中"占位",Slime看得到,但无梯度流动

OPD/Combine的零贡献路径:

hint被拒绝 &eval=0→ 样本根本不进入output_queue(直接丢弃)

样本对Slime来说不存在

OPD 的信号结构

情形

是否进入队列

advantage

hint 接受

teacher_lp - rollout_lp ≠ 0(几乎必然)

hint 拒绝

×(丢弃)

N/A

OPD 样本要么有真实的 per-token 教师信号(即使 reward=0, advantage 也非零),要么根本不进队列。没有“占位但无梯度"的中间状态。

Combine 的信号结构

情形

进队列?

OPD 项

RL 项

OPD+RL

≠ 0

≠ 0

OPD-only

≠ 0

= 0

RL-only

= 0(数值对消)

≠ 0

丢弃

×

N/A

N/A

进入队列的样本,至少一个信号项非零(这是 dispatch 逻辑保证的)。

设计选择的对称性

Binary RL的 at-least-one是在loss_mask二元门控机制下的补丁,而OPD/Combine 绕开了这个机制(始终 loss_mask=[1],通过 advantage 对消来“关掉“不需要的信号),所以补丁也就不再需要。

0x04 AsyncRolloutWorker

AsyncRolloutWorker = 线程边界 + 开关 + 数据渡口

4.1 功能

AsyncRolloutWorker 是Slime(Policy Training)与 FastAPI Server(Policy Serving)之间的线程边界管理器,它不做推理、不做打分,但控制着Policy Serving的“营业时间",控制着两侧的生命周期和数据流转,并通过output_queue把FastAPI 异步世界里生产的样本,安全地传递给Slime同步训练世界。

具体功能如下:

4.2 示例图

6-示例图

4.3 三个核心职责

4.3.1 线程隔离:让FastAPI跑在独立asyncio事件循环里

#worker_thread_func跑在独立线程
defworker_thread_func(self):
asyncio.run(self.continuous_worker_loop())
# asyncio.run()创建独立事件循环
# FastAPI/httpx异步请求全在这个线程里

continuous_worker_loop()本身只是一个 sleep(1.0)的keepalive 循环—真正的数据生产在 FastAPI的 requesthandler 里,不是在这个loop里。

4.3.2 开关控制:submission_enabled事件同步

defpause_submission(self):
self._submission_enabled.clear()#关闸 →FastAPI 返回 503
self._server.purge_record_files()#清理临时记录


defresume_submission(self):
self._submission_enabled.set()#开闸→FastAPI正常接受请求

4.3.3 数据渡口:output_queue跨线程传递样本

queue.Queue是Python标准库中线程安全的FIFO,是FastAPI线程和 Slime 主线程之间唯一的共享数据结构。

#FastAPI 线程写入(async)
awaitasyncio.to_thread(self.output_queue.put,(sample.group_index,[sample]))
#Slime主线程读取(同步)
defget_completed_groups(self)->list[tuple]:
whileTrue:
completed.append(self.output_queue.get_nowait())

4.4 与 OpenClawAPIServer 的协作机制

AsyncRolloutWorker 是OpenClaw-RL框架中的异步轨迹收集工作者,负责管理整个 rollout数据收集流程的生命

4.4.1 交互架构模式 -- 生产者-消费者模式

4.4.2 层次化控制结构

AsyncRolloutWorker(顶层控制)
↓ 创建并管理
OpenClawAPIServer(数据生产)
↓ 提交到
SampleQueue(数据传输)
↓ 消费于
SlimeTrainer(模型训练)

4.4.3 具体交互机制

队列传递机制
状态同步机制
权重更新协调

4.4.4 两者配合的工作流程

初始化阶段

AsyncRolloutWorker 初始化

OpenClawAPIServer初始化

运行阶段

数据生产流程

批次收集流程

训练阶段

权重更新协调

恢复运行

实际应用场景示例

正常对话流程

用户请求 → OpenClawAPIServer(生产样本) → output_queue → AsyncRolloutWorker (监控队列) → SlimeTrainer (消费训练)

权重更新流程

训练批次完成 → AsyncRolloutWorker.pause_submission() → purge_record_files() → 权重更新 →
AsyncRolloutWorker.resume_submission() → 新策略生效

异常处理流程

unter(line
队列积压警告 → AsyncRolloutWorker发出30秒超时警告 → 管理员介入或自动扩容 → 恢复正常处理

这种设计确保了OpenClaw-RL能够在保证用户体验的同时,高效地收集和处理强化学习训练数据,体现了解耦设计和异步处理的现代系统架构思想。

0xFF 参考

阅读原文

喜欢(0)

上一篇

全球首个 AI 艺术博物馆:谷歌协力打造:生成 12 亿像素超现实画面

全球首个 AI 艺术博物馆:谷歌协力打造:生成 12 亿像素超现实画面

下一篇

Sora编程提示词问题如何筛掉无关主题

Sora编程提示词问题如何筛掉无关主题
猜你喜欢