EasyR1-V4-Embody 当前实现与两篇论文的完整串讲
本文档把三件事串起来讲清楚:
- arXiv:2505.22050, Reinforced Reasoning for Embodied Planning。
- arXiv:2605.27140v1, StepOPSD: Step-Aware Online Preference Distillation for Agent Reinforcement Learning。
- 当前仓库
EasyR1-V4-Embody的代码实现,尤其是grpo_step、rulestep reward、forward_proxy、residual gate / prompt gate。
结论先放在前面:
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。
它的方案是两阶段:
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 是规则式的:
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:
一条长 rollout 可能因为一个局部 action 错了而失败。
标准 GRPO 只有一个 terminal reward。
这个 reward 被广播到整条 response 的所有 token。
结果是:模型知道整条轨迹不好,但不知道是哪一步导致不好。
StepOPSD 的核心流程:
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
它的信号是:
Delta_{k,j}
= log pi_T(z_{k,j} | hindsight context, prefix)
- log pi_S(z_{k,j} | causal context, prefix)
它的 advantage 注入方式是:
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)
关键性质:
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”
当前仓库的任务形态:
输入:
图像 + human instruction + action history + action list
输出:
一次性生成完整 JSON:
{
"visual_state_description": "...",
"reasoning_and_reflection": "...",
"language_plan": [...],
"executable_plan": [
{"action_id": 133, "action_name": "..."},
...
]
}
注意:当前代码不是多轮在线环境交互。它是一行样本生成一整条后续 trajectory plan。
代码里的核心目标:
原来只有整条 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
主要文件:
verl/utils/dataset.py
当前 embodied dataset 的 __getitem__ 做了这些事:
- 从 parquet 行里读:
instruction
history
action_id # 可用动作列表
image
gt_bbox
gt_action
gt_input_text
gt_action_close / gt_action_open 等字段保留在 row_dict 中
- 构造 prompt:
You are Embodied-R1 ...
Given the current visual state <image> ...
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
- 把
<image>替换为 Qwen-VL 视觉占位:
<|vision_start|><|image_pad|><|vision_end|>
- 用 processor 编码图像和 prompt,生成:
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,内部包含:
action
gt_bbox
input_text
gt_open_ans
其中 input_text 里还能解析出 GT 的 executable_plan。
2.2 Rollout:模型一次性生成 response
训练主循环在:
verl/trainer/ray_trainer.py
核心顺序:
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 给整条计划打分
主要文件:
verl/workers/reward/function.py
verl/workers/reward/verify.py
compute_reward_batch 的行为是:
- decode response。
- 尝试解析 JSON。
- 从 response 中提取:
executable_plan
- 从 ground truth 中提取:
ground_truth_open = json.loads(ground_truth["input_text"])["executable_plan"]
- 构造 verify prompt:
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.
调用
ask_llm,用 verify VLM 生成一个 token,取 True / False 相关概率。得到:
format = 1.0 if JSON parse ok else 0.0
accuracy = verify probability, False 会转成负值
overall = 0.8 * accuracy + 0.2 * format
- 把
overall写到最后一个 response token:
reward_tensor[i, cur_response_length - 1] = score["overall"]
这意味着整条 trajectory 的 reward 在张量里仍然是一个 terminal scalar。
2.4 原始 GRPO:A^E 怎么来
主要文件:
verl/trainer/core_algos.py
标准 GRPO advantage 逻辑是:
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
主要文件:
verl/trainer/core_algos.py
verl/trainer/ray_trainer.py
verl/trainer/step_reward.py
新增 estimator:
AdvantageEstimator.GRPO_STEP = "grpo_step"
核心函数:
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
公式就是:
A_token = A^E + omega * A^S_token
其中:
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 做成可切换的:
algorithm.adv_estimator: grpo_step
algorithm.step_reward_kind: rule | forward_proxy
3.1 Rule:硬规则 step reward
文件:
verl/trainer/step_reward.py
入口:
build_step_adv_raw
-> build_step_adv_vector
-> compute_step_match_rewards
流程:
- decode response。
- 容错解析
executable_plan。 - 解析 GT:
gt_steps_from_close(gt_close)
- 对 predicted steps 和 GT steps 做匹配。
支持三种匹配:
exact:
pred[i] == gt[i] 才算对。
prefix:
最长正确前缀;第一个错后面全算错。
lcs:
longest common subsequence,对顺序扰动更宽松。
默认匹配键:
algorithm.step_match_key: action_id
reward 转 advantage:
匹配:+1
不匹配:-1
scaffolding / 非 step token:0
解析失败:整条 A^S = 0,退化为纯 GRPO
这条路和 2505.22050 很接近:都是用 GT action sequence 做 rule-based correctness signal。
但也有差别:
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
文件:
verl/trainer/teacher_proxy.py
verl/workers/reward/function.py
verl/workers/reward/verify.py
verl/trainer/step_reward.py
配置:
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,会遇到:
driver 进程没有 GPU。
verl/Ray 中 GPU 分给 worker actor。
teacher.to("cuda") 会失败。
所以当前代码把 forward_proxy 放到 reward actor:
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:
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
返回:
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)
然后:
Delta_t = teacher_logp[t] - student_logp[t]
这个 Delta 的语义是:
如果模型看了 GT 后更支持这个 response token,Delta 为正。
如果模型看了 GT 后更不支持这个 response token,Delta 为负。
3.2.3 当前代码里的一个重要现实:teacher 不一定等于 policy
teacher_proxy.py 和 config.py 的注释里仍然写着:
teacher should be same path as policy
same tokenizer, true OPSD self-distillation
但当前运行脚本 examples/qwen25_vl_3b_Domain.sh 实际是:
policy = Qwen2.5-VL-3B-RobotGPT-R1
teacher = Qwen2.5-VL-3B-Instruct
这意味着当前 forward_proxy 的真实语义不是严格的“policy 自蒸馏 OPSD”,而是:
冻结外部 Instruct teacher 在 GT context 和 no-GT context 下的 logp contrast。
这点要在论文/报告中说准。
如果 teacher 等于 policy:
更像原始 OPSD / self-distillation。
但实测容易因为 policy 对自己生成的 token 过度自信而 logp diff 塌平。
如果 teacher 是未微调 Instruct:
信号更容易有波动。
但它不再代表 policy 自己的 preference shift;
而是外部 teacher 的 GT-conditioned preference shift。
当前 step_adv_heatmaps/_diag.txt 中能看到 forward_proxy 已有波动,例如:
std ~= 0.9 - 2.3
mean 通常为正
min/max 可到约 -15 / +13
这说明“信号活了”,但不自动说明“信号方向一定奖对惩错”。
3.2.4 Delta 怎么变成 step score
文件:
verl/trainer/step_reward.py
build_step_adv_vector_from_logp
先选 score tokens:
step_score_source:
action_name # 默认
action_id
semantic # action_id + action_name
对这些 token 聚合:
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:
step_credit_target:
action_name
action_id
semantic
默认 action_name,一些 ablation 用:
step_score_source: semantic
step_credit_target: action_id
step_action_name_weight: 0.0
这个设计很重要:
score_source 决定“用哪些 token 判断这一步好坏”。
credit_target 决定“训练时把 advantage 写到哪些 token 上”。
例如:
score_source = semantic:
用 action_id + action_name 一起估计这步是否被支持。
credit_target = action_id:
主要更新离散动作选择,而不是训练自然语言措辞。
这是当前实现相对 StepOPSD 的一个具身 JSON plan 特化点。
4. Token span 对齐:当前实现的工程核心
step reward 最终要进入 PPO/GRPO loss,因此必须是 token 级。
当前代码做了以下事情:
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
主要函数:
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 能处理:
正常 JSON dict
带 json 标记的 fenced code block
前后有散文的 response
尾部 <|im_end|> / <|endoftext|>
bare list
解析失败返回 None,后续整条 A^S 为 0。
4.2 char offset
_token_char_offsets 先尝试:
tokenizer(text, return_offsets_mapping=True)
如果 re-tokenize 后 input_ids 和原始 response_ids 一致,就用 tokenizer offset。
如果不一致,则降级为 prefix decode:
逐 token decode ids[:t+1]
用字符串长度差构造 offsets
这样比“decode 后重编码”更稳,因为 Qwen/VL tokenizer 在 special token、空格清理、JSON 标点附近可能 round-trip 不一致。
4.3 step span
代码先找到 "executable_plan": 后面的 list 起点,然后按顺序找每一步:
action_id value span
action_name value span
每个 step 得到:
StepTokenSpans(
action_id_tokens=[...],
action_name_tokens=[...],
)
然后根据配置选择:
action_id tokens
action_name tokens
semantic tokens = action_id + action_name
4.4 当前 span 设计与 StepOPSD 的差别
StepOPSD 面向的是一般 agent trajectory,常见 span 是:
<action>...</action>
clean_step_no_observation
当前代码面向的是 embodied JSON plan,span 是:
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
配置:
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
逻辑:
- 先对 token_signal 去 response-level median:
d'_t = d_t - median(d over valid response tokens)
- 每步算 raw score:
raw_k = robust_mean(d'_t over score tokens)
soft_score_k = tanh(z_k / score_tau)
- 再用 unsigned contrast 估计 importance:
imp_k = robust_mean(abs(d'_t) over score tokens)
- 组合三个 gate:
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
- 最终:
A^S_k = gate_k * soft_score_k
直觉:
soft_score 决定方向:支持还是反对。
gate 决定强度:这一步是否值得更新。
5.2 Prompt gate
配置:
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 的:
{
"decision_gate": 0.0,
"redundancy_gate": 0.0,
"causal_gate": 0.0,
"prompt_kl_score": 0.0,
"reason": "..."
}
代码把 gate 合成:
gate = decision * (1 - redundancy) * (0.5 + 0.5 * causal)
并可把 prompt score 加进 supervised KL score:
supervised_scores =
step_supervised_kl_weight * supervised_scores
+ step_prompt_kl_weight * prompt_scores
这个版本的目标是处理 residual contrast 看不懂的情况:
某步 KL 波动很大,但其实只是冗余 find;
某步看起来错,但由于前一步已经错了,后续动作不该被强烈惩罚;
某步是关键 causal action,应该放大。
这已经超出 StepOPSD 原始设计,属于当前实现的额外探索。
6. 当前实现与 2505.22050 的详细对比
6.1 共同点
两者共同点很多:
都做 embodied planning。
都使用 Qwen2.5-VL 系列。
都要求模型输出结构化 multi-step plan。
都使用 GRPO/RFT。
都重视 action sequence 的正确性。
都有格式约束。
都不直接在真实机器人上训练。
6.2 数据和任务形式
2505.22050:
训练:
SFT 数据来自 Gemini-2.0-flash 蒸馏。
RFT 数据来自 ALFRED 轨迹分解。
评测:
Embench。
EB-ALFRED seen。
EB-Habitat unseen。
模型:
Qwen2.5-VL-7B。
当前代码:
训练文件:
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:
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 分两层:
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:
GRPO 直接用 composite reward。
每条 sampled response 得一个 reward。
组内归一化后更新整条 response。
当前代码:
GRPO 仍然用 trajectory reward 得到 A^E。
但在 compute_advantage 之前额外构造 step_adv_raw。
最终 A = A^E + omega * A^S。
所以当前实现是:
GRPO + per-token step advantage shaping
而不是单纯的:
GRPO + rule scalar reward
6.5 输出格式差异
2505.22050 论文要求输出类似:
visual_state_description
reasoning_and_reflection
language_plan
executable_plan
当前代码的 prompt 也要求这些字段,而且 reward JSON parser 优先取 executable_plan。
但当前代码的 step credit 只关注 executable_plan 中的:
action_id
action_name
不会给 visual_state_description、reasoning_and_reflection、language_plan 里的 token 直接加 step advantage。
这和当前方法目标一致:训练动作决策,而不是训练解释文本。
7. 当前实现与 StepOPSD 的详细对比
7.1 最大共同点
两者都可以概括为:
step-aware advantage shaping for GRPO。
更具体:
都不是把 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:
teacher context = causal prefix + peer-trajectory hindsight
teacher model = stale reference policy
peer-trajectory hindsight:
同一个 GRPO group 中,如果有成功 rollout,
用第一个成功 peer 给失败 trajectory 提供 hindsight。
当前 forward_proxy:
teacher context = image + GT reference plan + transition
student context = image + problem instruction
teacher model = step_teacher_path 指定的模型
当前脚本实际:
teacher = 未微调 Qwen2.5-VL-3B-Instruct
policy = Qwen2.5-VL-3B-RobotGPT-R1
差异:
StepOPSD 更通用:
不要求数据集中有显式 GT action plan。
只要 group 里有 successful peer。
当前代码监督更强:
直接使用数据集 GT reference plan。
更适合 EB-ALFRED 这种有 reference trajectory 的任务。
7.3 Delta 到 advantage 的变换方式
StepOPSD:
Delta -> token weight w
w > 0
A_tilde = (1-lambda)A + lambda(wA)
性质:
只改变 A 的大小。
不改变 A 的正负。
如果整条 trajectory 的 GRPO advantage 是负的,
局部 step 也不会被改成正方向,只能少罚或多罚。
当前代码:
Delta -> step scalar A^S_k = tanh(...)
A = A^E + omega * A^S_k
性质:
可以改变局部 token 的训练方向。
如果整条 trajectory A^E 为负,但某一步 rule/teacher 认为是对的,
该 step 的 token 可能被加到正方向。
如果整条 trajectory A^E 为正,但某一步是错的,
该 step 的 token 可能被拉成负方向。
这是当前实现和 StepOPSD 最核心的算法差异:
StepOPSD:
sign-preserving multiplicative reweighting。
当前代码:
additive local step advantage。
7.4 step 粒度
StepOPSD:
token-level Delta
token-level weight
step normalization 用来控制每个 step 的 credit budget
当前代码:
先把 token_signal 聚合成 step scalar。
再把 step scalar 写回 action_id/action_name/semantic tokens。
当前代码更强调:
step 是决策单位。
token 只是承载这个 step 决策的优化位置。
这和 embodied action plan 更匹配,因为 {"action_id": 133, "action_name": ...} 整体是一个动作选择。
7.5 span 定义
StepOPSD:
ALFWorld:
action_only
Search-QA:
clean_step_no_observation
当前代码:
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:
alpha_clip:
控制局部 weight trust region。
lambda_mix:
控制 shaped signal 与原始 advantage 的混合强度。
当前代码的 knob:
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 同款:
w = clip(2 * sigmoid(sign(A) * Delta), 1-alpha, 1+alpha)
也没有完全等价的:
equal_step_mean_abs
但 residual gate 里的 budget gate 有类似“不要让所有 step 都满强度更新”的意图。
7.7 对原始 rollout 的干预
两者都不改 rollout:
先采样 rollout。
再做 post-rollout step credit。
最后更新 policy。
这点是共同点。
8. 当前实现的优点、风险和需要说准的地方
8.1 优点
优点 1:把 trajectory reward 和 step credit 分开
当前实现没有破坏原始 verify reward:
verify reward 仍然决定整条 trajectory 的 A^E。
step reward 只作为 A^S 叠加。
因此:
omega = 0 可以退回纯 GRPO。
解析失败可以 A^S=0,不影响主训练。
优点 2:rule step reward 对当前任务非常强
因为 action space 是离散的 action_id,且 GT 中有 reference trajectory:
action_id exact/prefix/lcs 是直接的 correctness signal。
相比 forward_proxy,它:
不需要额外 teacher。
不占 GPU。
不会自蒸馏塌平。
不会把“像正确动作的错误动作”误判为正。
优点 3:forward_proxy 是一个活的软信号对照
当前诊断显示 teacher-student logp diff 不再全平:
std 约 1-2
min/max 有明显波动
这意味着 forward_proxy 至少具备可训练信号,不是全 0 对照。
优点 4:action_id/action_name 分离很适合 embodied plan
当前代码可以:
用 semantic tokens 算分。
只把 credit 写到 action_id token。
不给 JSON scaffold 梯度。
这比直接对整段 JSON 做 KL 更贴近“动作选择”。
8.2 风险
风险 1:forward_proxy 的 Delta 不等于动作正确性
Delta 表示:
teacher 看 GT 后是否更愿意生成这个 token。
它不一定表示:
这个 action 是否真的等于 GT action。
典型问题:
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:
Delta = Instruct(GT context) - Instruct(no-GT context)
而不是:
Delta = policy_or_stale_ref(GT context) - policy(causal context)
这在论文写法上必须说明,否则会被质疑“这不是 StepOPSD/OPSD 的同模型 rescoring”。
风险 3:additive advantage 可能过强
当前:
A = A^E + omega * A^S
如果 omega=1,而 A^E 的 group-normalized magnitude 也大约在 0-1 附近,A^S 可能显著改变更新方向。
这既是能力,也是风险:
能力:
能在失败轨迹里保留正确 step。
风险:
如果 A^S 判错,会直接反向更新局部动作。
StepOPSD 的 sign-preserving 设计更保守,当前 additive 设计更激进。
风险 4:rule exact 可能太硬
action_id exact 对当前任务很干净,但也可能:
对等价替代路径不宽容。
对多余 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,不建议写:
We propose to use OPSD-style teacher-student logp gap for step-level GRPO advantage shaping.
这个表述和 StepOPSD 撞得太正。
也不建议写:
Our method is fundamentally different from StepOPSD.
因为两者确实同属 step-aware OPSD advantage shaping。
10.2 更准确的写法
可以这样定位:
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.
中文表达:
我们不是提出“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 当前实现最清楚的贡献点
可以强调:
- 任务形态差异:
one-shot embodied trajectory planning,不是一般 multi-turn agent transcript。
- 结构化 action span:
显式区分 action_id 和 action_name。
可以 score semantic tokens,但只 credit action_id。
- additive local advantage:
允许局部 step 改变更新方向。
这和 StepOPSD 的 sign-preserving multiplicative reweighting 不同。
- rule vs forward_proxy 的系统比较:
当任务有干净离散 action_id GT 时,hard rule step reward 可能比 KL soft proxy 更可靠。
forward_proxy 作为软信号和无 step verifier 场景的泛化对照。
- gate 机制:
residual gate / prompt gate 用来控制 soft KL 信号进入 action tokens 的强度。
11. 建议补的实验和分析
11.1 必做:四个主 baseline
建议最小实验表:
1. GRPO
2. GRPO + rule step reward
3. GRPO + forward_proxy additive
4. GRPO + forward_proxy + residual gate
如果资源允许,再加:
5. GRPO + forward_proxy + prompt gate
6. GRPO + StepOPSD-style multiplicative reweighting
11.2 强烈建议实现 StepOPSD-style multiplicative baseline
为了和 StepOPSD 讲清楚,建议在当前代码里加一个模式:
step_injection_mode:
additive # current
multiplicative_stepopsd
multiplicative 公式:
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)
然后比较:
additive 是否真的优于 sign-preserving?
additive 的 sign flip 是否带来收益?
这个实验对论文非常关键,因为它直接回应 StepOPSD。
11.3 sign flip 统计
当前 additive 最大差异是可能局部翻转方向。建议统计:
sign_flip_ratio =
mean[ sign(A^E + omega*A^S) != sign(A^E) over action tokens ]
并分组看:
成功 trajectory vs 失败 trajectory
正确 step vs 错误 step
Heat/Cool/Clean/PickTwo 等长程任务
如果能证明:
失败轨迹中的正确前缀 step 被 additive 保留下来;
成功轨迹中的错误/冗余 step 被局部压低;
那就是相对 StepOPSD 的强证据。
11.4 forward_proxy 正确性分析
不要只看:
teacher-student diff std
还要看:
rule match = 1 的 step,forward_proxy A^S 分布。
rule match = 0 的 step,forward_proxy A^S 分布。
理想情况:
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 消融
比较:
exact
prefix
lcs
可能结论:
exact:
信号干净但太硬。
prefix:
更符合 embodied execution,一旦前面错了后面不该继续强判。
lcs:
对顺序和多余 find 更宽容,但可能奖励不可执行的乱序计划。
11.6 credit target 消融
比较:
score_source=action_name, credit_target=action_name
score_source=semantic, credit_target=action_id
score_source=semantic, credit_target=semantic
重点看:
只训练 action_id 是否比训练 action_name 更稳定。
action_name token 是否引入措辞/格式梯度噪声。
12. 当前代码路径速查
12.1 数据和 prompt
verl/utils/dataset.py
embodied prompt 构造
image processor
multi_modal_data
ground_truth 包装
12.2 trajectory reward
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
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
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 接线
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 配置和脚本
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. 最后总结
如果用一句话串起来:
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”,而是:
我们如何把 embodied JSON plan 中的结构化动作字段变成可训练的 token-level step credit;
以及 additive step advantage 相比 StepOPSD sign-preserving reweighting 的差异、收益和风险。
当前代码已经具备一套完整实验框架:
GRPO
GRPO + rule
GRPO + forward_proxy
GRPO + residual gate
GRPO + prompt gate
下一步最有价值的是补:
StepOPSD-style multiplicative baseline
sign-flip analysis
forward_proxy correctness-vs-rule correlation
rule exact/prefix/lcs 消融
credit target 消融
这些实验能把“和两篇论文的关系”从文字解释变成可验证证据。