StarPO 主循环:rollout 与 update
本章讲什么:StarPO(State-Thinking-Action-Reward Policy Optimization)是 RAGEN 的核心算法。看它怎么 把一整条多轮轨迹当成一个 RL 样本来优化。
4.1 一句话:StarPO 是什么
要解决的小问题。 普通 RLHF 只优化“一问一答”。但 agent 任务是多轮的:推理→动作→看新状态→再推理… 怎么把这整条“推理+动作交错”的轨迹当一个整体来优化?
思路。 把交互建模为 MDP:状态和动作都是 token 序列,每轮输出 <think>…</think><answer>…</answer>。
StarPO 用重要性采样优化整条轨迹,可插 PPO(token 级优势 + value function)或 GRPO(整条轨迹拿归一化奖励)。
两个交替阶段。 全在 RayAgentTrainer.fit(agent_trainer.py:942)里:
for step in total_training_steps:
rollout 阶段:agent_proxy.rollout() → 一批轨迹(DataProto)
update 阶段:过滤 → 算 log_prob → 算优势 → update_actor
4.2 rollout 阶段:一批轨迹怎么采出来
核心是 LLMAgentProxy.rollout(agent_proxy.py:225)。它 是个“全部环境齐步走 max_turn 轮”的循环:
# 示意,非源码(改编自 agent_proxy.py:LLMAgentProxy.rollout)
env_outputs = es_manager.reset() # 初始状态
for i in range(max_turn):
if len(env_outputs) == 0: break # 全做完了就停
lm_inputs = ctx_manager.get_lm_inputs(env_outputs, prepare_for_update=False)
lm_inputs.meta_info["mode"] = mode # multiturn-start/middle/end
lm_outputs = self.generate_sequences(lm_inputs) # 调 LLM
env_inputs = ctx_manager.get_env_inputs(lm_outputs) # 解析动作
env_outputs = es_manager.step(env_inputs) # 执行
rollout_states = es_manager.get_rollout_states()
rollouts = ctx_manager.formulate_rollouts(rollout_states) # 打包成训练样本
两个细节值得看。
mode标记轮次位置。 首轮=multiturn-start,末轮=multiturn-end(agent_proxy.py:252)。这个mode会传给下游 vLLM(推测用来控制 KV cache / early stop)。- “finalize”收尾。 若多轮还没到 end 就全部做完,rollout 会带
skip_generation=True补一次multiturn-end(agent_proxy.py:286)——让轨迹有个干净的结尾状态。
实例级熵顺手累加。 rollout 过程中把每轮的熵、token 数、轮数按 env_id 累加,最后除得到“每轨迹平均熵”
(agent_proxy.py:298)。这是诊断指标之一。
4.3 update 阶段:从轨迹到梯度
回到 fit(agent_trainer.py:1046 起的主循环)。一个 step 内,rollout 之后依次是:
1. (V2)collapse_detector.compute_collapse_metrics() # 过滤前算,公平对比 → 第4章
2. rollout_filter.filter() # 按奖励方差筛组 → 第3章
3. adjust_batch() # 补/删样本凑整除
4. actor_rollout_wg.compute_log_prob() # 重算 old_log_prob + 熵
5. compute_advantage() # GRPO / PPO 算优势
6. update_critic() / update_actor() # veRL 做梯度下降
为什么要 adjust_batch。 过滤后样本数不规整,但 mini-batch / GPU 数 / 组数都要整除。adjust_batch
(agent_trainer.py:62)按这三者的最小公倍数,用 copy(复制样本)或 delete(删样本)凑整
(agent_trainer.py:1194)。
关键:uid 就是 group_id。 看 agent_trainer.py:1276:batch.non_tensor_batch["uid"] = group_ids。这一行
决定了 GRPO “按谁分组算基线”——同一个 group_id(同一初始状态)的几条轨迹互为参照。
response_mask 就用 loss_mask。 agent_trainer.py:1279 直接把 loss_mask 赋给 response_mask——因为多轮
场景下“该算损失的 token”就是 loss_mask 决定的。
4.4 GRPO 优势:组内减均值,加 episode 去重
GRPO 的核心思想:一个 prompt 采 N 条轨迹,用这 N 条的组内均值当基线,谁高于均值谁就是正优势。
RAGEN 在 ragen/trainer/core_algos.py:26 重写了 compute_grpo_outcome_advantage,加了一个关键的 episode 去重:
# 示意,非源码(改编自 core_algos.py:compute_grpo_outcome_advantage)
seen_pairs = set()
for i in range(bsz):
if episode_ids is not None:
pair = (index[i], episode_ids[i]) # (group_id, episode)
if pair in seen_pairs: continue # 同 episode 只计一次
seen_pairs.add(pair)
id2score[index[i]].append(scores[i])
# 组内减均值(可选除以方差)
scores[i] = (scores[i] - id2mean[index[i]]) / (id2std[index[i]] + eps)
为什么要去重。 在 single_turn / limited_multi_turn 模式下,一条 episode 被拆成了多个样本(每轮一个)。
如果不去重,长轨迹会因为轮数多而在组内均值里占更大权重,偏差。去重让每条 episode 只贡献一次均值/方差。
norm_adv_by_std_in_grpo 决定是否除以方差(除了就是常规 GRPO,不除就是 Dr.GRPO 风格,core_algos.py:78)。
单样本组(只一条)特判:mean=0, std=1(core_algos.py:68),避免除零。
4.5 PPO / GAE 路径
config/base.yaml:97 默认 adv_estimator: gae(PPO)。compute_advantage(agent_trainer.py:140)按
adv_estimator 分派到 veRL 的 GAE / GRPO / REINFORCE++ / RLOO / REMAX 等。
bi-level GAE(两层 MDP)。 RAGEN 自带一个 compute_bi_level_gae_advantage_return(core_algos.py:94):高层是
“每轮一个 step”的 agentic MDP(用 high_level_gamma),低层是“每个 token”的 token MDP(用 gamma)。它先在
每轮的 eos 位置算高层优势/回报,再把该回报当“该轮的序列奖励”向 token 级下传。注意 train.py:162 断言:
开 bi_level_gae 时 use_turn_scores 必须为 False(轮分数路径还没接好, inferred from assertion comment)。