# EasyR1-V4-Embody 当前实现与两篇论文的完整串讲 本文档把三件事串起来讲清楚: 1. arXiv:2505.22050, **Reinforced Reasoning for Embodied Planning**。 2. arXiv:2605.27140v1, **StepOPSD: Step-Aware Online Preference Distillation for Agent Reinforcement Learning**。 3. 当前仓库 `EasyR1-V4-Embody` 的代码实现,尤其是 `grpo_step`、`rule` step reward、`forward_proxy`、residual gate / prompt gate。 结论先放在前面: ```text 2505.22050 是一个 embodied VLM 训练 recipe: SFT 初始化 + rule-based reward + GRPO/RFT,让 Qwen2.5-VL 学会输出多步可执行计划。 2605.27140 StepOPSD 是一个 agent RL 信用分配算法: 把 OPSD teacher-student logp gap 放到 step/action span 上,做 GRPO advantage shaping。 当前 EasyR1-V4-Embody 介于两者之间: 任务设定、数据和输出形式更像 2505.22050; 算法改动、step-level advantage shaping 和 forward_proxy 更像 StepOPSD; 同时又额外实现了 rule step reward、GT-reference teacher、additive advantage、action_id/action_name span control、residual/prompt gates。 ``` --- ## 1. 三个对象各自解决什么问题 ### 1.1 2505.22050 解决的是“怎么训练 embodied planner” 这篇论文的主问题是:开源 VLM 在具身场景里很难做长程、多步、视觉条件下的 planning。模型要看当前视觉 observation、历史动作、自然语言任务,然后输出后续 action sequence。 它的方案是两阶段: ```text Stage 1: SFT 用强闭源模型 Gemini-2.0-flash 蒸馏出高质量 reasoning + plan 数据。 让 Qwen2.5-VL-7B 先学会任务分解、空间常识、输出格式。 Stage 2: RFT / GRPO 构造 ALFRED 轨迹分解数据。 对模型输出的多步 action plan 设计 rule-based reward。 用 GRPO 做 reinforcement fine-tuning。 ``` 它的 reward 是规则式的: ```text R = R_format + R_accuracy R_format: JSON 结构是否正确; 必需字段是否存在; action_id / action_name 是否有效匹配。 R_accuracy: 按 action sequence 和 gold sequence 做 prefix matching。 若连续匹配 n 步,gold 总长为 k: R(n; k) = n(n + 1) / (k(k + 1)) ``` 所以 2505.22050 的重点不是“OPSD 信号怎么改 advantage”,而是“具身规划任务上,SFT + 规则奖励 + GRPO 这条 pipeline 能不能训动”。 ### 1.2 StepOPSD 解决的是“长轨迹 sparse reward 怎么分给关键 step” StepOPSD 的主问题是 agent RL 的 credit assignment: ```text 一条长 rollout 可能因为一个局部 action 错了而失败。 标准 GRPO 只有一个 terminal reward。 这个 reward 被广播到整条 response 的所有 token。 结果是:模型知道整条轨迹不好,但不知道是哪一步导致不好。 ``` StepOPSD 的核心流程: ```text rollout -> trajectory reward / GRPO advantage -> parse action-centered step spans -> hindsight teacher context rescoring -> compute token-level logp gap Delta -> convert Delta into bounded multiplicative weights -> reshape GRPO advantage -> policy update ``` 它的信号是: ```text Delta_{k,j} = log pi_T(z_{k,j} | hindsight context, prefix) - log pi_S(z_{k,j} | causal context, prefix) ``` 它的 advantage 注入方式是: ```text w_raw = 2 * sigmoid(sign(A) * Delta) w = clip(w_raw, 1 - alpha_clip, 1 + alpha_clip) A_tilde = (1 - lambda_mix) * A + lambda_mix * (w * A) ``` 关键性质: ```text w 始终为正。 因此 StepOPSD 只改变原始 GRPO advantage 的 magnitude,不改变 sign。 它是 sign-preserving 的 credit redistribution。 ``` StepOPSD 的 hindsight 来源主要是同一个 GRPO group 中的 successful peer trajectory;teacher 是 stale reference policy,避免 moving target。 ### 1.3 当前代码解决的是“one-shot embodied JSON plan 内部的 step credit assignment” 当前仓库的任务形态: ```text 输入: 图像 + human instruction + action history + action list 输出: 一次性生成完整 JSON: { "visual_state_description": "...", "reasoning_and_reflection": "...", "language_plan": [...], "executable_plan": [ {"action_id": 133, "action_name": "..."}, ... ] } ``` 注意:当前代码不是多轮在线环境交互。它是一行样本生成一整条后续 trajectory plan。 代码里的核心目标: ```text 原来只有整条 trajectory 的 scalar reward。 现在希望在同一条 response 内部,把不同 step 的 token 赋予不同 advantage。 A_token = A^E + omega * A^S A^E: 原始 GRPO episode advantage。 来自整条轨迹 scalar reward 的组内归一化。 A^S: 新增 step-level per-token advantage。 可以来自 rule action_id 匹配,也可以来自 forward_proxy teacher-student logp contrast。 ``` 因此,当前实现和 2505.22050 的任务设定很近,和 StepOPSD 的算法思想也很近。 --- ## 2. 当前代码端到端数据流 ### 2.1 Dataset:把 EB-ALFRED 样本变成 VLM prompt 主要文件: ```text verl/utils/dataset.py ``` 当前 embodied dataset 的 `__getitem__` 做了这些事: 1. 从 parquet 行里读: ```text instruction history action_id # 可用动作列表 image gt_bbox gt_action gt_input_text gt_action_close / gt_action_open 等字段保留在 row_dict 中 ``` 2. 构造 prompt: ```text You are Embodied-R1 ... Given the current visual state ... Current context: {history} Action Descriptions and Validity Rules ... The available action names are: {actionlist} Output ONLY a valid JSON object: visual_state_description reasoning_and_reflection language_plan executable_plan ``` 3. 把 `` 替换为 Qwen-VL 视觉占位: ```text <|vision_start|><|image_pad|><|vision_end|> ``` 4. 用 processor 编码图像和 prompt,生成: ```text input_ids attention_mask position_ids raw_prompt_ids multi_modal_data = {"images": raw_images} ground_truth = json.dumps(gt) ``` 这里的 `ground_truth` 不是直接的 `executable_plan` list,而是一个包装后的 JSON,内部包含: ```text action gt_bbox input_text gt_open_ans ``` 其中 `input_text` 里还能解析出 GT 的 `executable_plan`。 ### 2.2 Rollout:模型一次性生成 response 训练主循环在: ```text verl/trainer/ray_trainer.py ``` 核心顺序: ```text prepare_rollout_engine -> _make_batch_data -> release_rollout_engine -> _balance_batch -> compute_reward -> compute_old_log_probs -> compute_ref_log_probs -> build step_adv_raw if adv_estimator == grpo_step -> compute_advantage -> update actor ``` 这里的 response 是完整 JSON plan,不是一步环境 action。 ### 2.3 Trajectory-level reward:verify model 给整条计划打分 主要文件: ```text verl/workers/reward/function.py verl/workers/reward/verify.py ``` `compute_reward_batch` 的行为是: 1. decode response。 2. 尝试解析 JSON。 3. 从 response 中提取: ```text executable_plan ``` 4. 从 ground truth 中提取: ```text ground_truth_open = json.loads(ground_truth["input_text"])["executable_plan"] ``` 5. 构造 verify prompt: ```text Sentence 1: {GT executable_plan} Sentence 2: {pred executable_plan} You are judging whether a predicted embodied action trajectory matches the ground-truth trajectory at the intent level. Focus on FUNCTIONAL EQUIVALENCE ... Compare mainly by action_name, not exact action_id. Be lenient about extra/missing find steps ... Only output: True or False. ``` 6. 调用 `ask_llm`,用 verify VLM 生成一个 token,取 True / False 相关概率。 7. 得到: ```text format = 1.0 if JSON parse ok else 0.0 accuracy = verify probability, False 会转成负值 overall = 0.8 * accuracy + 0.2 * format ``` 8. 把 `overall` 写到最后一个 response token: ```text reward_tensor[i, cur_response_length - 1] = score["overall"] ``` 这意味着整条 trajectory 的 reward 在张量里仍然是一个 terminal scalar。 ### 2.4 原始 GRPO:A^E 怎么来 主要文件: ```text verl/trainer/core_algos.py ``` 标准 GRPO advantage 逻辑是: ```text scores = token_level_rewards.sum(-1) 按 uid 分组: 同一个 prompt 的 n 条 rollout 共享 uid。 对每个 uid group: A^E_i = (R_i - mean(R_group)) / (std(R_group) + eps) 然后: A^E_i 广播到该 response 的所有有效 token。 ``` 所以没有 step reward 时,同一条 response 里的每个 token 都拿同一个 advantage。 这正是当前工作要改的地方。 ### 2.5 grpo_step:在 A^E 上加 A^S 主要文件: ```text verl/trainer/core_algos.py verl/trainer/ray_trainer.py verl/trainer/step_reward.py ``` 新增 estimator: ```text AdvantageEstimator.GRPO_STEP = "grpo_step" ``` 核心函数: ```python episode_adv, _ = compute_grpo_outcome_advantage(...) if step_adv_raw is None: return episode_adv, episode_adv step_adv = step_adv_raw * response_mask advantages = episode_adv + step_advantage_w * step_adv return advantages, advantages ``` 公式就是: ```text A_token = A^E + omega * A^S_token ``` 其中: ```text A^E: 仍然是原始 GRPO,完全复用。 A^S: shape = (bs, response_length) response 内不同 token 可以不同。 解析失败、scaffolding、非目标 token 为 0。 ``` 这个点非常关键:当前实现不是额外加一个 KL loss,也不是重写 GRPO,而是在 advantage 估计阶段加一条旁路信号。 --- ## 3. step_adv_raw 的两条来源 当前代码把 `step_adv_raw` 做成可切换的: ```yaml algorithm.adv_estimator: grpo_step algorithm.step_reward_kind: rule | forward_proxy ``` ### 3.1 Rule:硬规则 step reward 文件: ```text verl/trainer/step_reward.py ``` 入口: ```text build_step_adv_raw -> build_step_adv_vector -> compute_step_match_rewards ``` 流程: 1. decode response。 2. 容错解析 `executable_plan`。 3. 解析 GT: ```text gt_steps_from_close(gt_close) ``` 4. 对 predicted steps 和 GT steps 做匹配。 支持三种匹配: ```text exact: pred[i] == gt[i] 才算对。 prefix: 最长正确前缀;第一个错后面全算错。 lcs: longest common subsequence,对顺序扰动更宽松。 ``` 默认匹配键: ```yaml algorithm.step_match_key: action_id ``` reward 转 advantage: ```text 匹配:+1 不匹配:-1 scaffolding / 非 step token:0 解析失败:整条 A^S = 0,退化为纯 GRPO ``` 这条路和 2505.22050 很接近:都是用 GT action sequence 做 rule-based correctness signal。 但也有差别: ```text 2505.22050: rule reward 先变成 response-level scalar reward,再进入 GRPO。 prefix reward R(n;k) 是整条 response 的 reward。 当前代码 rule: rule reward 直接变成 per-token step advantage A^S。 一条 response 内部可以前两步 +1,第三步 -1。 ``` 也就是说,当前 rule 比 2505.22050 更细:不是只给整条轨迹一个分,而是把 step 对错广播到对应 token。 ### 3.2 Forward Proxy:GT-aware teacher-student logp contrast 文件: ```text verl/trainer/teacher_proxy.py verl/workers/reward/function.py verl/workers/reward/verify.py verl/trainer/step_reward.py ``` 配置: ```yaml algorithm.step_reward_kind: forward_proxy algorithm.step_squash: contrast algorithm.step_teacher_path: ... algorithm.step_score_source: action_name | action_id | semantic algorithm.step_credit_target: action_name | action_id | semantic ``` #### 3.2.1 为什么要跑在 reward actor 上 最初如果在 driver 上做 teacher forward,会遇到: ```text driver 进程没有 GPU。 verl/Ray 中 GPU 分给 worker actor。 teacher.to("cuda") 会失败。 ``` 所以当前代码把 forward_proxy 放到 reward actor: ```text ray.get(self.reward_fn.compute_step_adv.remote(...)) ``` reward actor 本来就有 verify model 的 GPU 资源,因此可以 lazy-load teacher scorer。 #### 3.2.2 TeacherLogpScorer 做什么 `TeacherLogpScorer.logp_teacher_student` 对同一段 predicted response tokens 跑两次 forward: ```text teacher context: image + "Here is a reference successful language plan:" + GT reference plan + transition + predicted response tokens student context: image + "Problem: {instruction}" + student instruction + predicted response tokens ``` 返回: ```text teacher_logp[t] = log p_model(response_token_t | image, GT context, response_prefix) student_logp[t] = log p_model(response_token_t | image, problem context, response_prefix) ``` 然后: ```text Delta_t = teacher_logp[t] - student_logp[t] ``` 这个 Delta 的语义是: ```text 如果模型看了 GT 后更支持这个 response token,Delta 为正。 如果模型看了 GT 后更不支持这个 response token,Delta 为负。 ``` #### 3.2.3 当前代码里的一个重要现实:teacher 不一定等于 policy `teacher_proxy.py` 和 `config.py` 的注释里仍然写着: ```text teacher should be same path as policy same tokenizer, true OPSD self-distillation ``` 但当前运行脚本 `examples/qwen25_vl_3b_Domain.sh` 实际是: ```text policy = Qwen2.5-VL-3B-RobotGPT-R1 teacher = Qwen2.5-VL-3B-Instruct ``` 这意味着当前 forward_proxy 的真实语义不是严格的“policy 自蒸馏 OPSD”,而是: ```text 冻结外部 Instruct teacher 在 GT context 和 no-GT context 下的 logp contrast。 ``` 这点要在论文/报告中说准。 如果 teacher 等于 policy: ```text 更像原始 OPSD / self-distillation。 但实测容易因为 policy 对自己生成的 token 过度自信而 logp diff 塌平。 ``` 如果 teacher 是未微调 Instruct: ```text 信号更容易有波动。 但它不再代表 policy 自己的 preference shift; 而是外部 teacher 的 GT-conditioned preference shift。 ``` 当前 `step_adv_heatmaps/_diag.txt` 中能看到 forward_proxy 已有波动,例如: ```text std ~= 0.9 - 2.3 mean 通常为正 min/max 可到约 -15 / +13 ``` 这说明“信号活了”,但不自动说明“信号方向一定奖对惩错”。 #### 3.2.4 Delta 怎么变成 step score 文件: ```text verl/trainer/step_reward.py build_step_adv_vector_from_logp ``` 先选 score tokens: ```yaml step_score_source: action_name # 默认 action_id semantic # action_id + action_name ``` 对这些 token 聚合: ```text contrast 模式: token_signal = teacher_logp - student_logp raw_step = robust_mean(token_signal over score tokens) score = tanh(normalize(raw_step) / score_tau) prob 模式: score = 2 * exp(mean teacher_logp) - 1 ``` 代码和实验笔记都说明:`prob` 容易恒平,因为 GT-conditioned teacher 对很多 token 都很自信;真正有意义的是 `contrast`。 #### 3.2.5 score 写回哪些 token 再选 credit target: ```yaml step_credit_target: action_name action_id semantic ``` 默认 `action_name`,一些 ablation 用: ```yaml step_score_source: semantic step_credit_target: action_id step_action_name_weight: 0.0 ``` 这个设计很重要: ```text score_source 决定“用哪些 token 判断这一步好坏”。 credit_target 决定“训练时把 advantage 写到哪些 token 上”。 ``` 例如: ```text score_source = semantic: 用 action_id + action_name 一起估计这步是否被支持。 credit_target = action_id: 主要更新离散动作选择,而不是训练自然语言措辞。 ``` 这是当前实现相对 StepOPSD 的一个具身 JSON plan 特化点。 --- ## 4. Token span 对齐:当前实现的工程核心 step reward 最终要进入 PPO/GRPO loss,因此必须是 token 级。 当前代码做了以下事情: ```text response_ids -> decode response text -> extract executable_plan -> locate each step's action_id / action_name character span -> map character span back to token indices -> write step score into selected token positions ``` 主要函数: ```text extract_plan_steps pred_step_token_spans _token_char_offsets _find_action_id_value_span _find_action_name_value_span _select_span_tokens ``` ### 4.1 JSON 解析 `extract_plan_steps` 能处理: ```text 正常 JSON dict 带 json 标记的 fenced code block 前后有散文的 response 尾部 <|im_end|> / <|endoftext|> bare list ``` 解析失败返回 `None`,后续整条 A^S 为 0。 ### 4.2 char offset `_token_char_offsets` 先尝试: ```text tokenizer(text, return_offsets_mapping=True) ``` 如果 re-tokenize 后 input_ids 和原始 response_ids 一致,就用 tokenizer offset。 如果不一致,则降级为 prefix decode: ```text 逐 token decode ids[:t+1] 用字符串长度差构造 offsets ``` 这样比“decode 后重编码”更稳,因为 Qwen/VL tokenizer 在 special token、空格清理、JSON 标点附近可能 round-trip 不一致。 ### 4.3 step span 代码先找到 `"executable_plan":` 后面的 list 起点,然后按顺序找每一步: ```text action_id value span action_name value span ``` 每个 step 得到: ```python StepTokenSpans( action_id_tokens=[...], action_name_tokens=[...], ) ``` 然后根据配置选择: ```text action_id tokens action_name tokens semantic tokens = action_id + action_name ``` ### 4.4 当前 span 设计与 StepOPSD 的差别 StepOPSD 面向的是一般 agent trajectory,常见 span 是: ```text ... clean_step_no_observation ``` 当前代码面向的是 embodied JSON plan,span 是: ```text action_id value action_name value ``` 所以当前实现对 `executable_plan` 的结构假设更强,但也更精确:可以只训练 action id,不训练 JSON scaffold 或语言描述。 --- ## 5. Gate 扩展:控制 forward_proxy 信号进来的位置和强度 当前代码不止有 plain forward_proxy,还实现了两类 gate。 ### 5.1 Residual contrast gate 配置: ```yaml algorithm.step_gate: residual_contrast algorithm.step_gate_residual: response_median algorithm.step_gate_threshold: 0.0 algorithm.step_gate_tau: 1.0 algorithm.step_budget_tau: 1.0 algorithm.step_min_score_tokens: 2 ``` 逻辑: 1. 先对 token_signal 去 response-level median: ```text d'_t = d_t - median(d over valid response tokens) ``` 2. 每步算 raw score: ```text raw_k = robust_mean(d'_t over score tokens) soft_score_k = tanh(z_k / score_tau) ``` 3. 再用 unsigned contrast 估计 importance: ```text imp_k = robust_mean(abs(d'_t) over score tokens) ``` 4. 组合三个 gate: ```text decision_gate = sigmoid((imp_z - threshold) / gate_tau) budget_gate = clamp(softmax(imp / budget_tau) * num_steps, 0, 1) confidence = len_gate * var_gate gate_k = decision_gate * budget_gate * confidence ``` 5. 最终: ```text A^S_k = gate_k * soft_score_k ``` 直觉: ```text soft_score 决定方向:支持还是反对。 gate 决定强度:这一步是否值得更新。 ``` ### 5.2 Prompt gate 配置: ```yaml algorithm.step_gate: prompt algorithm.step_prompt_gate_mode: generation algorithm.step_prompt_gate_model_path: ... algorithm.step_prompt_kl_weight: ... algorithm.step_use_prompt_causal_gate: true algorithm.step_use_prompt_redundancy_gate: true ``` `PromptGateScorer` 会让 judge 输出每个 predicted step 的: ```json { "decision_gate": 0.0, "redundancy_gate": 0.0, "causal_gate": 0.0, "prompt_kl_score": 0.0, "reason": "..." } ``` 代码把 gate 合成: ```text gate = decision * (1 - redundancy) * (0.5 + 0.5 * causal) ``` 并可把 prompt score 加进 supervised KL score: ```text supervised_scores = step_supervised_kl_weight * supervised_scores + step_prompt_kl_weight * prompt_scores ``` 这个版本的目标是处理 residual contrast 看不懂的情况: ```text 某步 KL 波动很大,但其实只是冗余 find; 某步看起来错,但由于前一步已经错了,后续动作不该被强烈惩罚; 某步是关键 causal action,应该放大。 ``` 这已经超出 StepOPSD 原始设计,属于当前实现的额外探索。 --- ## 6. 当前实现与 2505.22050 的详细对比 ### 6.1 共同点 两者共同点很多: ```text 都做 embodied planning。 都使用 Qwen2.5-VL 系列。 都要求模型输出结构化 multi-step plan。 都使用 GRPO/RFT。 都重视 action sequence 的正确性。 都有格式约束。 都不直接在真实机器人上训练。 ``` ### 6.2 数据和任务形式 2505.22050: ```text 训练: SFT 数据来自 Gemini-2.0-flash 蒸馏。 RFT 数据来自 ALFRED 轨迹分解。 评测: Embench。 EB-ALFRED seen。 EB-Habitat unseen。 模型: Qwen2.5-VL-7B。 ``` 当前代码: ```text 训练文件: eb_alfred_success_opsd_cot_multimodal_v2_embody.parquet 输入: 当前图像 + instruction + history + action list。 输出: 一次性完整 JSON plan。 policy: Qwen2.5-VL-3B-RobotGPT-R1。 teacher for forward_proxy: Qwen2.5-VL-3B-Instruct。 ``` 因此,当前代码更像把 2505.22050 的 embodied planning setting 改成了 3B policy + EasyR1/verl 风格训练。 ### 6.3 Reward 设计差异 2505.22050 的 reward: ```text R_total = R_format + R_accuracy R_accuracy = prefix curve n(n+1)/k(k+1) ``` 它是 response-level reward。即使 accuracy 来自 step prefix,最后仍然变成一个 scalar reward 给 GRPO。 当前代码的 reward 分两层: ```text trajectory-level reward: verify model 判断 pred plan 与 GT plan 是否 functional equivalent。 overall = 0.8 * verify + 0.2 * format。 写到最后一个 token。 step-level A^S: rule: action_id exact/prefix/lcs -> per-token +/-1。 forward_proxy: teacher-student logp contrast -> per-token [-1,1]。 ``` 也就是说,当前代码比 2505.22050 多了一个显式 step advantage 通道。 ### 6.4 GRPO 使用方式差异 2505.22050: ```text GRPO 直接用 composite reward。 每条 sampled response 得一个 reward。 组内归一化后更新整条 response。 ``` 当前代码: ```text GRPO 仍然用 trajectory reward 得到 A^E。 但在 compute_advantage 之前额外构造 step_adv_raw。 最终 A = A^E + omega * A^S。 ``` 所以当前实现是: ```text GRPO + per-token step advantage shaping ``` 而不是单纯的: ```text GRPO + rule scalar reward ``` ### 6.5 输出格式差异 2505.22050 论文要求输出类似: ```text visual_state_description reasoning_and_reflection language_plan executable_plan ``` 当前代码的 prompt 也要求这些字段,而且 reward JSON parser 优先取 `executable_plan`。 但当前代码的 step credit 只关注 `executable_plan` 中的: ```text action_id action_name ``` 不会给 `visual_state_description`、`reasoning_and_reflection`、`language_plan` 里的 token 直接加 step advantage。 这和当前方法目标一致:训练动作决策,而不是训练解释文本。 --- ## 7. 当前实现与 StepOPSD 的详细对比 ### 7.1 最大共同点 两者都可以概括为: ```text step-aware advantage shaping for GRPO。 ``` 更具体: ```text 都不是把 teacher signal 单独做一个 KL/distillation loss。 都在 rollout 完成后处理 sampled response。 都解析 step/action span。 都把局部 step 信号注入 policy gradient 的 advantage 通道。 都试图修正 trajectory-level sparse reward 的信用分配问题。 ``` 如果当前论文主方法强调 `forward_proxy`,那和 StepOPSD 的相似度很高,不能写成“完全不同路线”。 ### 7.2 Hindsight / teacher 来源 StepOPSD: ```text teacher context = causal prefix + peer-trajectory hindsight teacher model = stale reference policy peer-trajectory hindsight: 同一个 GRPO group 中,如果有成功 rollout, 用第一个成功 peer 给失败 trajectory 提供 hindsight。 ``` 当前 forward_proxy: ```text teacher context = image + GT reference plan + transition student context = image + problem instruction teacher model = step_teacher_path 指定的模型 ``` 当前脚本实际: ```text teacher = 未微调 Qwen2.5-VL-3B-Instruct policy = Qwen2.5-VL-3B-RobotGPT-R1 ``` 差异: ```text StepOPSD 更通用: 不要求数据集中有显式 GT action plan。 只要 group 里有 successful peer。 当前代码监督更强: 直接使用数据集 GT reference plan。 更适合 EB-ALFRED 这种有 reference trajectory 的任务。 ``` ### 7.3 Delta 到 advantage 的变换方式 StepOPSD: ```text Delta -> token weight w w > 0 A_tilde = (1-lambda)A + lambda(wA) ``` 性质: ```text 只改变 A 的大小。 不改变 A 的正负。 如果整条 trajectory 的 GRPO advantage 是负的, 局部 step 也不会被改成正方向,只能少罚或多罚。 ``` 当前代码: ```text Delta -> step scalar A^S_k = tanh(...) A = A^E + omega * A^S_k ``` 性质: ```text 可以改变局部 token 的训练方向。 如果整条 trajectory A^E 为负,但某一步 rule/teacher 认为是对的, 该 step 的 token 可能被加到正方向。 如果整条 trajectory A^E 为正,但某一步是错的, 该 step 的 token 可能被拉成负方向。 ``` 这是当前实现和 StepOPSD 最核心的算法差异: ```text StepOPSD: sign-preserving multiplicative reweighting。 当前代码: additive local step advantage。 ``` ### 7.4 step 粒度 StepOPSD: ```text token-level Delta token-level weight step normalization 用来控制每个 step 的 credit budget ``` 当前代码: ```text 先把 token_signal 聚合成 step scalar。 再把 step scalar 写回 action_id/action_name/semantic tokens。 ``` 当前代码更强调: ```text step 是决策单位。 token 只是承载这个 step 决策的优化位置。 ``` 这和 embodied action plan 更匹配,因为 `{"action_id": 133, "action_name": ...}` 整体是一个动作选择。 ### 7.5 span 定义 StepOPSD: ```text ALFWorld: action_only Search-QA: clean_step_no_observation ``` 当前代码: ```text pred_step_token_spans: action_id_tokens action_name_tokens semantic_tokens = union ``` 这不是泛化的 agent transcript span,而是 embodied JSON plan 的结构化 span。 ### 7.6 normalization / clipping StepOPSD 有两个核心 knob: ```text alpha_clip: 控制局部 weight trust region。 lambda_mix: 控制 shaped signal 与原始 advantage 的混合强度。 ``` 当前代码的 knob: ```text step_advantage_w: A^S 总权重 omega。 step_score_tau: tanh 温度。 step_score_norm: none / per_response。 step_gate: none / residual_contrast / prompt。 step_budget_tau: residual gate 的 per-response budget 控制。 ``` 当前代码没有实现 StepOPSD 同款: ```text w = clip(2 * sigmoid(sign(A) * Delta), 1-alpha, 1+alpha) ``` 也没有完全等价的: ```text equal_step_mean_abs ``` 但 residual gate 里的 budget gate 有类似“不要让所有 step 都满强度更新”的意图。 ### 7.7 对原始 rollout 的干预 两者都不改 rollout: ```text 先采样 rollout。 再做 post-rollout step credit。 最后更新 policy。 ``` 这点是共同点。 --- ## 8. 当前实现的优点、风险和需要说准的地方 ### 8.1 优点 #### 优点 1:把 trajectory reward 和 step credit 分开 当前实现没有破坏原始 verify reward: ```text verify reward 仍然决定整条 trajectory 的 A^E。 step reward 只作为 A^S 叠加。 ``` 因此: ```text omega = 0 可以退回纯 GRPO。 解析失败可以 A^S=0,不影响主训练。 ``` #### 优点 2:rule step reward 对当前任务非常强 因为 action space 是离散的 `action_id`,且 GT 中有 reference trajectory: ```text action_id exact/prefix/lcs 是直接的 correctness signal。 ``` 相比 forward_proxy,它: ```text 不需要额外 teacher。 不占 GPU。 不会自蒸馏塌平。 不会把“像正确动作的错误动作”误判为正。 ``` #### 优点 3:forward_proxy 是一个活的软信号对照 当前诊断显示 teacher-student logp diff 不再全平: ```text std 约 1-2 min/max 有明显波动 ``` 这意味着 forward_proxy 至少具备可训练信号,不是全 0 对照。 #### 优点 4:action_id/action_name 分离很适合 embodied plan 当前代码可以: ```text 用 semantic tokens 算分。 只把 credit 写到 action_id token。 不给 JSON scaffold 梯度。 ``` 这比直接对整段 JSON 做 KL 更贴近“动作选择”。 ### 8.2 风险 #### 风险 1:forward_proxy 的 Delta 不等于动作正确性 Delta 表示: ```text teacher 看 GT 后是否更愿意生成这个 token。 ``` 它不一定表示: ```text 这个 action 是否真的等于 GT action。 ``` 典型问题: ```text GT 是 find DiningTable。 预测是 find Sofa。 teacher 可能因为 "find a furniture" 这个模式合理而给正 Delta。 ``` 所以 forward_proxy 的正确性需要专门验证,不能只看 diff std。 #### 风险 2:外部 teacher 改变了 OPSD 语义 如果 teacher 是 `Qwen2.5-VL-3B-Instruct`,policy 是 `RobotGPT-R1`: ```text Delta = Instruct(GT context) - Instruct(no-GT context) ``` 而不是: ```text Delta = policy_or_stale_ref(GT context) - policy(causal context) ``` 这在论文写法上必须说明,否则会被质疑“这不是 StepOPSD/OPSD 的同模型 rescoring”。 #### 风险 3:additive advantage 可能过强 当前: ```text A = A^E + omega * A^S ``` 如果 `omega=1`,而 A^E 的 group-normalized magnitude 也大约在 0-1 附近,A^S 可能显著改变更新方向。 这既是能力,也是风险: ```text 能力: 能在失败轨迹里保留正确 step。 风险: 如果 A^S 判错,会直接反向更新局部动作。 ``` StepOPSD 的 sign-preserving 设计更保守,当前 additive 设计更激进。 #### 风险 4:rule exact 可能太硬 `action_id` exact 对当前任务很干净,但也可能: ```text 对等价替代路径不宽容。 对多余 find / 顺序轻微变化不宽容。 对 object instance 探索不宽容。 ``` 代码已经提供 `prefix` 和 `lcs`,但默认 `exact`,需要用实验比较。 --- ## 9. 三方总表 | 维度 | 2505.22050 | StepOPSD | 当前 EasyR1-V4-Embody | |---|---|---|---| | 主问题 | 训练 embodied VLM planner | agent RL credit assignment | one-shot embodied JSON plan 的 step credit assignment | | 基础算法 | SFT + GRPO/RFT | GRPO + step-aware OPSD shaping | GRPO + additive step advantage | | 模型 | Qwen2.5-VL-7B | Qwen3-1.7B / Qwen2.5-3B | Qwen2.5-VL-3B RobotGPT-R1 | | 任务 | Embench / EB-ALFRED / EB-Habitat | ALFWorld / Search-QA | EB-ALFRED parquet embodied planning | | 输出 | structured reasoning + executable_plan | agent trajectory / actions / search steps | JSON with executable_plan | | trajectory reward | rule format + prefix accuracy | env/QA reward | verify VLM functional equivalence + format | | step 信号 | prefix correctness 汇总成 scalar | teacher-student logp gap | rule action_id 或 forward_proxy logp gap | | step 信号进入方式 | 作为 response reward 的一部分 | multiplicative reweight A | additive A = A^E + omega A^S | | 是否保留 A 符号 | GRPO scalar reward 决定 | 是 | 否,可能局部翻转 | | teacher / hindsight | Gemini 用于 SFT,不是 RFT teacher | successful peer + stale ref | GT reference + configured teacher | | span | action sequence prefix | action-centered step | action_id/action_name token span | | 是否需要 GT step | 需要 gold sequence | 不需要显式 GT,依赖 peer success | rule 需要;forward_proxy 使用 GT context | | 是否额外 KL loss | 否 | 否 | 否 | | 最大风险 | reward 仍较粗,长程困难 | 与当前方法撞车点强 | forward_proxy 正确性、additive 过强、GT 依赖 | --- ## 10. 应该如何给当前方法定位 ### 10.1 不建议的写法 如果主方法是 forward_proxy,不建议写: ```text We propose to use OPSD-style teacher-student logp gap for step-level GRPO advantage shaping. ``` 这个表述和 StepOPSD 撞得太正。 也不建议写: ```text Our method is fundamentally different from StepOPSD. ``` 因为两者确实同属 step-aware OPSD advantage shaping。 ### 10.2 更准确的写法 可以这样定位: ```text We study step-level credit assignment for one-shot embodied trajectory planning, where a VLM generates a structured JSON executable plan in a single response. Unlike general multi-turn agent trajectories, the decision variables are explicit action_id/action_name fields. We inject step-local advantages into GRPO by mapping either GT action matching or GT-reference-privileged teacher contrast onto these structured action spans. ``` 中文表达: ```text 我们不是提出“OPSD 信号用于 advantage shaping”这个大方向本身; 这个方向 StepOPSD 已经非常接近。 我们的具体切入点是: 在 one-shot embodied JSON trajectory planning 中, 利用显式 action_id/action_name 结构,把 GT-reference 或 rule step signal 映射到动作字段 token,并以 additive local step advantage 的方式注入 GRPO。 ``` ### 10.3 当前实现最清楚的贡献点 可以强调: 1. **任务形态差异**: ```text one-shot embodied trajectory planning,不是一般 multi-turn agent transcript。 ``` 2. **结构化 action span**: ```text 显式区分 action_id 和 action_name。 可以 score semantic tokens,但只 credit action_id。 ``` 3. **additive local advantage**: ```text 允许局部 step 改变更新方向。 这和 StepOPSD 的 sign-preserving multiplicative reweighting 不同。 ``` 4. **rule vs forward_proxy 的系统比较**: ```text 当任务有干净离散 action_id GT 时,hard rule step reward 可能比 KL soft proxy 更可靠。 forward_proxy 作为软信号和无 step verifier 场景的泛化对照。 ``` 5. **gate 机制**: ```text residual gate / prompt gate 用来控制 soft KL 信号进入 action tokens 的强度。 ``` --- ## 11. 建议补的实验和分析 ### 11.1 必做:四个主 baseline 建议最小实验表: ```text 1. GRPO 2. GRPO + rule step reward 3. GRPO + forward_proxy additive 4. GRPO + forward_proxy + residual gate ``` 如果资源允许,再加: ```text 5. GRPO + forward_proxy + prompt gate 6. GRPO + StepOPSD-style multiplicative reweighting ``` ### 11.2 强烈建议实现 StepOPSD-style multiplicative baseline 为了和 StepOPSD 讲清楚,建议在当前代码里加一个模式: ```text step_injection_mode: additive # current multiplicative_stepopsd ``` multiplicative 公式: ```text Delta_step 或 Delta_token 来自 forward_proxy。 w = clip(2 * sigmoid(sign(A^E) * Delta), 1 - alpha_clip, 1 + alpha_clip) A = (1 - lambda_mix) * A^E + lambda_mix * (w * A^E) ``` 然后比较: ```text additive 是否真的优于 sign-preserving? additive 的 sign flip 是否带来收益? ``` 这个实验对论文非常关键,因为它直接回应 StepOPSD。 ### 11.3 sign flip 统计 当前 additive 最大差异是可能局部翻转方向。建议统计: ```text sign_flip_ratio = mean[ sign(A^E + omega*A^S) != sign(A^E) over action tokens ] ``` 并分组看: ```text 成功 trajectory vs 失败 trajectory 正确 step vs 错误 step Heat/Cool/Clean/PickTwo 等长程任务 ``` 如果能证明: ```text 失败轨迹中的正确前缀 step 被 additive 保留下来; 成功轨迹中的错误/冗余 step 被局部压低; ``` 那就是相对 StepOPSD 的强证据。 ### 11.4 forward_proxy 正确性分析 不要只看: ```text teacher-student diff std ``` 还要看: ```text rule match = 1 的 step,forward_proxy A^S 分布。 rule match = 0 的 step,forward_proxy A^S 分布。 ``` 理想情况: ```text correct steps: mean A^S > 0 wrong steps: mean A^S < 0 ``` 如果 wrong steps 也大量为正,说明 forward_proxy 更像 style/trajectory prior,而不是 correctness signal。 ### 11.5 rule mode 消融 比较: ```text exact prefix lcs ``` 可能结论: ```text exact: 信号干净但太硬。 prefix: 更符合 embodied execution,一旦前面错了后面不该继续强判。 lcs: 对顺序和多余 find 更宽容,但可能奖励不可执行的乱序计划。 ``` ### 11.6 credit target 消融 比较: ```text score_source=action_name, credit_target=action_name score_source=semantic, credit_target=action_id score_source=semantic, credit_target=semantic ``` 重点看: ```text 只训练 action_id 是否比训练 action_name 更稳定。 action_name token 是否引入措辞/格式梯度噪声。 ``` --- ## 12. 当前代码路径速查 ### 12.1 数据和 prompt ```text verl/utils/dataset.py embodied prompt 构造 image processor multi_modal_data ground_truth 包装 ``` ### 12.2 trajectory reward ```text verl/workers/reward/function.py compute_reward_batch verify prompt overall = 0.8 * accuracy + 0.2 * format reward 写最后一个 token verl/workers/reward/verify.py ask_llm load_verify / load_verify_qwen3 ``` ### 12.3 step reward / step advantage ```text verl/trainer/step_reward.py extract_plan_steps compute_step_match_rewards pred_step_token_spans build_step_adv_vector build_step_adv_vector_from_logp build_step_adv_raw ``` ### 12.4 forward_proxy teacher ```text verl/trainer/teacher_proxy.py TeacherLogpScorer PromptGateScorer verl/workers/reward/verify.py step_logp_vl verl/workers/reward/function.py AutoRewardManager.compute_step_adv ``` ### 12.5 GRPO 接线 ```text verl/trainer/core_algos.py AdvantageEstimator.GRPO_STEP compute_grpo_step_outcome_advantage verl/trainer/ray_trainer.py compute_advantage _build_step_adv _dump_adv_heatmaps _save_total_adv_heatmaps ``` ### 12.6 配置和脚本 ```text verl/trainer/config.py AlgorithmConfig 中所有 step_* 字段 examples/qwen25_vl_3b_Domain.sh 当前 forward_proxy 主运行脚本 examples/qwen25_vl_3b_residual_gate_ablation.sh residual gate 消融 examples/qwen25_vl_3b_prompt_gate_ablation.sh prompt gate 消融 ``` --- ## 13. 最后总结 如果用一句话串起来: ```text 2505.22050 告诉我们 embodied planning 可以用 SFT + rule reward + GRPO 训; StepOPSD 告诉我们长轨迹 agent RL 的关键是 step-aware advantage shaping; 当前 EasyR1-V4-Embody 把这两个方向接到一起: 在 one-shot embodied JSON plan 上,用 verify model 给整轨 reward, 再用 rule 或 GT-aware forward_proxy 给 executable_plan 的 action spans 加 step-local advantage。 ``` 当前实现最应该讲清楚的不是“我们用了 GRPO”,也不是“我们用了 OPSD”,而是: ```text 我们如何把 embodied JSON plan 中的结构化动作字段变成可训练的 token-level step credit; 以及 additive step advantage 相比 StepOPSD sign-preserving reweighting 的差异、收益和风险。 ``` 当前代码已经具备一套完整实验框架: ```text GRPO GRPO + rule GRPO + forward_proxy GRPO + residual gate GRPO + prompt gate ``` 下一步最有价值的是补: ```text StepOPSD-style multiplicative baseline sign-flip analysis forward_proxy correctness-vs-rule correlation rule exact/prefix/lcs 消融 credit target 消融 ``` 这些实验能把“和两篇论文的关系”从文字解释变成可验证证据。