这篇文章对应视频:【[Agentic RL] [VeRL] 08 MultiTurn Tool Use, Coding Agent SFT 训练,Cold Start for RL】(BV19PmwB5ERd)。
但我不会把它写成“逐句复盘”的笔记,而是把它抽象成一套你能复用的工程方法论:如果你要做一个 multi-turn tool-use 的 coding agent,并且希望后续能接上 PPO/GRPO/DAPO 这类 RL 训练,你在 数据、mask、rollout 结构、reward 形态 上要先把哪些坑填平。
系列导航:
关联阅读(建议顺序):
- SFT trainer 主篇:交叉熵 / loss mask / scheduler
- SFT 补充:teacher forcing / shift / mask 对齐
- RL 算法侧:GRPO/RLOO/REINFORCE++ 与 baseline
- 推理侧:vLLM 参数、显存/吞吐调优
- Tokenizer 非对称性与 Token-in-Token-out(RL 训练崩溃的根因)
配套仓库(你本地已下载)里,这篇最相关的材料(我会在文中引用这些路径):
- ReTool 的整体 pipeline(cold-start SFT + RL + interleaved rollout):
/Users/wangpeng/Downloads/modern_genai_bilibili-main/agentic_rl/verl/retool/ReTool-overall.ipynb
- Multi-turn SFT 数据与
loss_mask可视化:/Users/wangpeng/Downloads/modern_genai_bilibili-main/agentic_rl/verl/retool/ReTool-sft.ipynb
- Agent loop 的架构与异步 rollout:
/Users/wangpeng/Downloads/modern_genai_bilibili-main/agentic_rl/verl/agent/agent_loop_arch.ipynb/Users/wangpeng/Downloads/modern_genai_bilibili-main/agentic_rl/verl/agent/agent_loop_details.ipynb/Users/wangpeng/Downloads/modern_genai_bilibili-main/agentic_rl/verl/agent/agent_loop_code.ipynb/Users/wangpeng/Downloads/modern_genai_bilibili-main/agentic_rl/verl/agent/agent_loop_config.ipynb
- reward shaping(在 tool-use 场景里,经常是 cold start 成败的关键):
/Users/wangpeng/Downloads/modern_genai_bilibili-main/agentic_rl/reward_model/reward-shaping.ipynb
0. 为什么 MultiTurn Tool Use + Coding Agent 比“普通 RLHF”难一个数量级
把“对话 SFT + RL”直接搬到 tool-use agent 上,最常见的失败模式不是“跑不起来”,而是“能跑但学不到”。
原因是 tool-use agent 的交互过程具备这些特征:
- 轨迹是 interleaved 的:LLM 生成 token → 调工具 → 工具返回(非策略生成)→ LLM 继续生成。
- 你需要两套 mask:
- SFT:
loss_mask(哪些 token 参与交叉熵)。 - RL:
response_mask/action_mask(哪些 token 是策略动作,参与 logprob、ratio、KL、entropy 等计算)。
- SFT:
- reward 延迟且稀疏:coding agent 常见 reward 是“测试是否通过 / patch 是否正确”,中间步骤 reward 很弱。
- 环境噪声很大:
- 工具可能失败(网络/超时/权限/依赖缺失)。
- 代码执行的 stderr/stdout 会引入大量 token,污染序列长度与训练稳定性。
一个实用结论是:
“tool-use RL”不是在 PPO/GRPO 上加一个工具就行了,你需要先把 trajectory 的可训练部分定义清楚。
后面这篇文章会围绕这件事展开。
1. ReTool Pipeline 的核心:Cold-Start SFT → RL(带工具)→ Interleaved Rollout
ReTool-overall.ipynb 把训练 pipeline 拆得很清楚,我建议你把它当成一个“工程模板”:
- Phase 1: Cold-Start SFT
- Phase 2: RL with Tool Integration
- Interleaved Rollout / Generation(LLM token 与 tool output 交错)
我用一张图把它画成闭环(不是论文图,是工程视角的“你会踩坑在哪里”图):
1 | flowchart TD |
这里最容易被低估的是 Cold-Start SFT:它不是“可选项”,而是“定义动作空间”的一步。
如果 cold start 不稳,RL 会出现两种典型崩法:
- 策略学会逃避工具:用胡编的自然语言绕开工具调用,骗过弱 reward 或弱 verifier。
- 策略学会滥用工具:疯狂调用工具制造长序列,KL/entropy/ratio 统计彻底失真,训练发散。
2. 数据结构:messages/tools 的“协议”决定你能不能做 RL
以 ReTool-sft.ipynb 的思路,multi-turn tool-use SFT 的样本基本长这样:
messages: 一个列表,元素有role(system/user/assistant/tool)与content(或 tool_call 字段)tools: 可调用函数(或工具)的 schema 列表
你做 coding agent 时,常见工具可以抽象成三类:
- 执行器:
code_interpreter/ sandbox runner(输入 code,输出 stdout/stderr/returncode) - 检索器:search / doc lookup(输入 query,输出结果片段)
- 文件系统:read/write/list(输入 path,输出内容)
工程建议(和模型/框架无关):
- 工具 schema 先冻结再训练:schema 每改一次,你的“协议分布”就变一次,SFT 与 RL 的收敛都会变差。
- tool output 永远不要当作监督目标:它属于环境观测(observation),不是 action。
3. 两套 mask:loss_mask(SFT)与 response_mask(RL)
3.1 SFT 的 loss_mask:只监督 assistant 的输出
这一点在你之前的文章里已经讲过,但它在 coding agent 上更关键:
- system/user/tool 的 token:mask=0
- assistant 的 token:mask=1(包含 tool_call 协议片段 + final answer)
如果你把 tool output 也训了,模型会倾向于“把工具返回当 continuation 生成”,这是 agent 最危险的坏习惯之一。
3.2 RL 的 response_mask:只对“策略生成的 token”算 logprob/ratio/KL
agent_loop_details.ipynb 有一个非常关键的标注(我把它翻成一句话):
- LLM 生成的 token:mask=1
- Tool 返回的 token:mask=0
这件事如果错了,你的 RL 训练会出现非常诡异的现象:
- KL 看起来异常大(因为你把 tool output 当成策略生成,而策略从未“生成过它”)
- ratio 统计爆炸(logp_old/logp_new 对不上)
- clip fraction 长期高或长期为 0(完全失真)
所以我建议你在实现里把 mask 语义写死成两句话(写进注释/日志里):
loss_mask: supervised labels(SFT)response_mask: policy actions(RL)
两者都可能是 [B, T],但语义完全不同。
4. Cold-Start SFT:coding agent “能用”比“聪明”更重要
ReTool-overall.ipynb 把 cold start 放在第一阶段,我非常认同。
对 coding agent 来说,cold-start SFT 的目标不是把它训成“最强写码者”,而是:
- 稳定地产生可执行的工具调用
- 稳定地把工具返回转成下一步行动(多轮闭环)
- 输出结构可评测(比如最终 patch/答案有固定格式)
4.1 你应该盯的 3 个指标(比 loss 更重要)
- tool-call parse rate(可解析率)
- tool-call success rate(工具执行成功率)
- end-to-end success 的 proxy(例如:单测通过率、静态检查通过率、编译成功率)
它们决定了你后续 RL 的“可学习信号”是否存在。
4.2 冷启动数据从哪来:不要迷信人工标注
对于 tool-use / coding agent,人工标注高质量多轮轨迹成本很高。更常见的冷启动方式是混合:
- 少量人工高质量轨迹(定义协议上限)
- 大量合成轨迹(覆盖分布)
- rejection sampling(用 verifier/规则过滤)
注意:合成轨迹不是“越多越好”,而是“越能覆盖错误模式越好”。你需要刻意包含失败样例,让模型学会修复流程,而不是只学会一条成功路径。
5. RL with Tool Integration:reward 形态决定你用不用 shaping
coding agent 的 reward 典型是:
- 稀疏:是否通过 tests(0/1)
- 高噪声:依赖环境、随机种子、外部网络
这时 reward-shaping.ipynb 的思路就很实用:你可以给工具调用增加一些 过程性奖励(shaping),让 credit assignment 不至于完全靠最终成败。
常见 shaping(只要能被稳定评测,就值得):
- 工具调用格式正确(JSON/schema pass)
- 工具执行成功(returncode=0)
- 关键中间产物出现(比如生成了 patch、生成了测试命令并执行)
反例(我不建议):
- 用模型自评打分当 shaping(会被 reward hacking)
- 用“输出更长/更像思考”当 shaping(会被 length hacking)
6. Agent Loop:为什么 async rollout 是必需品(不是优化项)
tool-use agent 的最大吞吐瓶颈往往不是 GPU,而是工具 IO(网络、文件、执行器)。
agent_loop_details.ipynb 用“同步点”解释了一个关键问题:如果你按 batch 同步推进 multi-turn,你会被最慢的那个样本拖死。
工程上更合理的做法是:
- 每个 sample 一个 async task
- 每个 task 跑自己的状态机(LLM → tool → LLM → …)
- 全部完成后再 merge 成 batch(在“回传梯度/做更新”处同步)
你可以把它理解成:
rollout 阶段尽可能异步,update 阶段必须同步。
这也是 agent_loop_arch.ipynb 里反复强调的 server-based async rollout 的意义。
6.1 从代码抽象看架构:Manager / Worker / AgentLoop / ServerManager 各管一件事
如果你读 agent_loop_arch.ipynb,会看到一个非常“工程化”的拆分方式(我把它总结成四个角色):
AgentLoopManager:接收 Trainer 的“生成一批 rollout”请求,把 batch 切块并分发给多个 Worker。AgentLoopWorker:在 CPU 节点上跑并发(asyncio),每个 sample 一个 task,避免 straggler 拖垮整批。AgentLoop(例如 ToolAgentLoop):只负责“怎么聊/怎么调工具”的状态机逻辑(PENDING→GENERATING→TOOLS→…)。AsyncLLMServerManager:只负责“怎么生成”(推理服务网关 + 负载均衡 + sticky session)。
你可以把它画成一条直线:
1 | flowchart LR |
这个拆分的好处是:你后续想改“工具协议/状态机”,只需要改 AgentLoop;想改并发策略/worker 数量,只需要改 Worker/Manager;想换推理引擎,只需要改 ServerManager。
6.2 两个关键点:async task 粒度 + 状态机
在 async rollout 里,“并发粒度”基本决定你能不能把 GPU 吃满。
如果你按 “一个 batch 一个 coroutine” 并发,那你仍然会被最慢样本拖住;真正有效的是:
- 每个 sample 一个 coroutine(task):快样本先结束,慢样本继续跑,GPU 侧的 continuous batching 可以不断接新请求。
状态机这块,你至少要建立这 5 个状态的直觉(工具调用 agent 基本都能套进来):
1 | stateDiagram-v2 |
最重要的一条工程原则是:“环境/工具返回”永远被当成 observation,绝不当成 action。
6.3 response_mask:interleaved trajectory 里怎么只对“策略 token”求梯度
multi-turn tool-use 轨迹是交错的:
- LLM 生成(action)
- tool 返回(observation)
- LLM 继续生成(action)
RL 训练(PPO/GRPO)需要计算 logprob_new/logprob_old、ratio、KL、entropy 等,这些量只能对 LLM 生成出来的 token 有意义。
所以实现里会维护一个 response_mask(或 action_mask):
- LLM 生成 token:mask=1
- tool 输出 token:mask=0
这样做的直接效果是:计算 policy gradient 时只对 mask=1 的 token 回传梯度,tool 输出不会污染 logprob/ratio/KL。
把它写成伪代码(对齐 agent_loop_details.ipynb 的核心片段):
1 | # after LLM generate: |
你要把这条规则当作 debug 的第一优先级:一旦 mask 语义错,后面 KL/ratio/clipfrac 全会“看起来像随机数”。
6.4 Async rollout 里的 reward 计算:为什么会被“前置”到 rollout 阶段
agent_loop_details.ipynb 里还强调了一个容易被忽略的点:
- 在 async rollout 模式下,reward 计算经常被放到 Worker 内部完成(rollout 阶段),并把
rm_scores附着到轨迹上返回 Trainer; - Trainer 看到 batch 已经包含
rm_scores,就会跳过“再算一遍 reward”。
这在 tool-use/coding agent 场景很常见,因为 reward 往往需要:
- 真实执行(跑测试/编译/静态检查)
- 或者调用 verifier(额外模型/规则系统)
它们都是 IO/CPU 重,不适合卡住 GPU 推理进程。
6.5 Sticky session:request_id 为什么重要
一条 tool-use 轨迹会多次调用推理引擎:Prompt→Tokens→ToolResult→Prompt→…
当你有多个推理 server replica 时,如果每次都“随机路由”,你可能会在工程上踩到两类坑:
- 同一条轨迹跨 replica 迁移,KV cache / session 管理变复杂(甚至实现不支持)。
- 负载均衡短期抖动导致 tail latency 变大(多轮交互把延迟放大)。
因此 AsyncLLMServerManager 通常会用 request_id 做 sticky session:同一条轨迹固定路由到同一 replica(配合 LRU 缓存/heap-based 最少请求优先等策略)。
6.6 规模化时怎么数“replica”:num_replicas vs 引擎内部 DP
agent_loop_config.ipynb 里有一段很实用的“数服务实例”公式(把它当作配置 sanity check):
- 一个 rollout server 实例内部可能用了 TP/DP/PP(引擎内部并行),其 world size 为:
$$\text{rollout_world_size}=\text{TP}\times \text{DP}\times \text{PP}$$
- 你总 GPU world size 为:
$$\text{world_size}=\text{n_gpus_per_node}\times \text{nnodes}$$
- 那么可启动的独立推理服务 replica 数为:
$$\text{num_replicas}=\frac{\text{world_size}}{\text{rollout_world_size}}$$
这背后最关键的直觉是:
- num_replicas(服务级 DP):多个完全独立的推理服务,靠 ServerManager 做负载均衡,容错更好,KV cache 各自独立。
- data_parallel_size(引擎内部 DP):同一推理引擎内部做 DP,耦合更紧,可能共享状态,但通信/同步也更复杂。
7. Coding Agent 的“训练闭环”:SFT 里就要内置 debug 接口
coding agent 的训练最怕的是“训练看似稳定,但实际不可用”。我建议你在 SFT 阶段就内置 2 个 debug 钩子:
- 抽样打印(decode + mask 上色)
- 真实执行(把 tool-call 真的跑一遍,记录失败原因)
你可以把它当成 agent 的单元测试:SFT 训练前先跑 50 条样本,确保协议与工具都没问题。
(这部分你可以直接复用 ReTool-sft.ipynb 的 mask 可视化代码。)
8. Cold Start for RL:一个可落地的路线(不要求你一上来就烧很多卡)
最后我给一条“能跑起来且不容易跑歪”的路线。它不追求最少步骤,而追求可控:
- 先做 cold-start SFT,把 tool-call 协议训稳(parse rate、success rate)
- 做一个弱 verifier(规则 + 小模型都行),把“明显不合法的轨迹”筛掉
- RL 阶段先用强 KL anchor(离 SFT 很近),先学会“少犯错”
- reward shaping 从强到弱:先用过程性 shaping 提供密集信号,再逐步退火到 end-to-end reward
- 最终再做“更难任务”的 curriculum(不要一开始就 SWE-bench 级别)
这条路线对应的工程直觉是:
- 先让 agent 变得“可执行”,再让 agent 变得“更优”
- 先让 reward 信号稳定,再让 reward 目标更苛刻
9. 小结
MultiTurn Tool Use / Coding Agent 的核心不是“换个 RL 算法”,而是:
- 你定义的 trajectory 里,哪些 token 属于 action,哪些属于 observation(response_mask)
- 你在 SFT 与 RL 阶段分别监督什么(loss_mask vs response_mask)
- 你是否有一条稳定的 cold-start 数据路线(否则 RL 只会放大坏习惯)
如果你愿意,下一篇我可以继续沿着这条线把“RL 端的实现细节”补全成可直接对照框架的 checklist:
- interleaved rollout 下 logprob 的对齐与缓存
- tool output 的长度爆炸如何处理(truncate/pack/summary)
- reward manager 的工程分层(rule/verifier/RM/ensemble)

