VLS 全解析

Vision-Language Steering:训练-free 推理时引导,解决 VLA policy 的分布外(OOD)问题。背景 · 创新 · 代码 · 部署 · 评测结果一站式。

1. 背景与意义

VLA(Vision-Language-Action)policy 如 π-0.5 在训练分布内表现优异,但部署到真实世界会遇到分布外(OOD)问题:物体被挪了位置、任务指令变了、场景换了。此时 policy 会依赖训练时学到的"捷径先验",做出错误动作。

VLS 要解决的核心问题:不重新训练 policy,如何在推理时纠正它在 OOD 下的错误行为?

传统做法是收集 OOD 数据微调,成本高且无法穷举。VLS 提出一条训练-free路线:冻结 policy,在它生成动作的过程中用 VLM 的语义理解实时"掰方向"。

图 A:VLS 整体流程 —— 冻结 policy + VLM reward 推理时引导
图 A:VLS 整体流程 —— 冻结 policy + VLM reward 推理时引导

2. 创新方案

VLS = Vision-Language Steering。一句话:冻结的 policy 照常生成动作,VLS 在它"去噪生成动作"的过程中插一脚,用 VLM 写的 reward 把动作往正确方向推。

2.1 切入点:Flow Matching 的速度场

π-0.5 用 flow matching 生成动作:从噪声出发,沿"速度场 v_t"积分 10 步到干净动作。VLS 的干预就是在这个去噪循环里修改速度场

图 B:去噪循环中的两阶段引导
图 B:去噪循环中的两阶段引导

2.2 两阶段引导

  • 早期(撒开):RBF kernel 多样性梯度把并行粒子互相推开,保证探索。
  • 后期(纠偏,核心):用 VLM 生成的 reward 函数对动作求梯度,v_t -= scale * kp_grad,把速度场掰向 reward 上升方向。这是 classifier guidance 思想,创新在于打分器由 VLM 实时生成、适配 OOD。

2.3 Sigmoid 自适应强度

引导强度随"离目标多近"自适应:远时全力推,近时放手,把精细抓取交还 policy。

图 C:Sigmoid 自适应引导强度
图 C:Sigmoid 自适应引导强度

2.4 FKD 粒子重采样(互补)

梯度引导负责"推"(需 reward 可微),FKD 用 Feynman-Kac 权重做粒子"优胜劣汰"(不需可微)。

图 D:梯度引导 vs FKD 重采样
图 D:梯度引导 vs FKD 重采样

2.5 VLS 完整 5 步管线详解 🟢论文§IV核对

VLS 不是简单的"VLM 检测物体位置然后告诉模型"。它是一个梯度引导管线:

步骤1: 场景理解(Grounding)
  RGB-D图像 → SAM分割出物体mask → DINOv2提取特征 → 深度图反投影3D点云
  → 聚类得到 关键点集合 P = {p1, p2, ..., pn}  (每个pi是一个3D坐标)
  结果: 把"当前场景有什么东西在哪"压缩成一组3D关键点

步骤2: VLM生成可微reward函数
  输入: 图像 + OOD指令 + 关键点P
  VLM(GPT-4)被prompt生成: "把任务分解成S个阶段,每阶段写一个PyTorch函数
    R_s(动作轨迹, 关键点P) → 标量分数"
  输出: S个可微的Python函数(用距离/点积/软约束等可微操作写成)
  
  举例: 对"pick up bowl and place on plate"
    Stage 1 reward: R1 = -||末端位置 - bowl关键点||  (越近分越高)
    Stage 2 reward: R2 = -||末端位置 - plate关键点|| (把碗放到盘子旁)

步骤3: 在去噪循环里注入梯度引导
  冻结的base policy(π0.5)正常做去噪采样: 噪声 → 逐步去噪 → 动作
  VLS在每个去噪步k:
    ① 对当前部分去噪的动作a_k算reward: score = R_s(a_k, P)
    ② 算梯度: g = ∇_{a_k} R_s  (reward对动作的梯度)
    ③ 把梯度注入去噪: v̂ = v(原始速度) + λ·g  (修正动作方向)
  效果: 动作被"拉向"高reward方向(即正确的物体位置)

步骤4: 粒子重采样(Feynman-Kac)
  同时维护B个候选动作(粒子),按reward高低重采样
  高分粒子被复制、低分粒子被丢弃 → 快速收敛到好动作

步骤5: 阶段切换(Schmitt触发器)
  监控reward值: 如果Stage 1完成(碗被抓起) → 自动切换到Stage 2的reward
关键理解
• VLM 的输出不是"碗在右前方"这样的文字描述给模型——而是一段可微的 PyTorch 代码(reward 函数),这段代码用 3D 关键点坐标定义"动作应该往哪里走"
• 这个 reward 函数的梯度被注入到 base policy 的去噪过程中——不是"告诉模型目标在哪",而是在数学上把动作方向往目标拉
• Base policy 的权重完全不修改(冻结)——只是每个去噪步的输出被梯度微调了
• 这就是为什么叫"steering"(转向)而不是"fine-tuning"(微调)
检测结果是什么?怎么给到模型?
• "检测结果"= 一组 3D 关键点坐标 P(通过 SAM 分割 + 深度反投影得到,不是 bounding box)
• "怎么给到模型"= 不是直接给(base policy 冻结,不接受新输入)。而是把关键点写进 reward 函数 → reward 对动作求梯度 → 梯度注入去噪循环修正动作方向。模型本身不知道关键点在哪——它只是"感觉到"去噪时有一个力在把它的动作往某个方向拉。

3. 代码细节

核心代码在 core/pi05_steer.py_sample_actions_guided。去噪循环:

noise = self.model.sample_noise(actions_shape, device)   # 从纯噪声开始
dt = torch.tensor(-1.0 / num_steps)                      # 负步长
x_t = noise; time = torch.tensor(1.0)                    # t=1(噪声)→ t=0(动作)
while time >= -dt / 2:
    v_t = self.model.denoise_step(..., x_t=x_t, timestep=expanded_time)  # 预测速度场
    # 后期:用 VLM reward 梯度掰速度场
    kp_grad, reward = self._compute_keypoint_gradient(x_t, keypoints, guidance_fn, ...)
    normalized_reward = 1 - (reward / self._stage_init_reward)
    guidance_strength = 1/(1+np.exp(sigmoid_k*(normalized_reward - sigmoid_x0)))
    scale = guide_scale * guidance_strength
    v_t[..., :3] -= scale * kp_grad[..., :3]             # 注入引导
    x_t = x_t + dt * v_t                                 # 欧拉法解 ODE
    time = time + dt
关键参数(评测用值):guide_scale=80,start_ratio=0.8(后80%步引导),sigmoid_k=12,sigmoid_x0=0.7,去噪步数=10。复现数字偏低时,这几个是首要调节对象。

3.1 reward 是怎么写的?梯度怎么从 reward 传到动作?

这是 VLS 最核心的一环。上面只写了"用 reward 梯度掰速度场",这里讲清 reward 函数长什么样、loss/梯度怎么反传

① reward 不是一个数值,是 VLM 现场写的一段 Python 函数

每个 episode 开始,VLM(gpt-5.5)会看 agentview 图像 + 读任务指令,直接生成一段 Python 代码 —— 一个 stageN_guidance(keypoints, action_sequence) 函数。下面是一次真实运行中 VLM 为"抓取奶油奶酪"生成的 reward 函数(原样,来自 outputs/.../vlm_agent/stage1_guidance.txt):

def stage1_guidance(keypoints, action_sequence):
    # Guide the gripper to the cream cheese at keypoint 8.
    # 从上往下抓:X-Y 对齐比 Z 更重要,给更大权重
    T = action_sequence.shape[1]
    timesteps = torch.arange(T, device=action_sequence.device)
    final_start = int(0.7 * T)
    # 轨迹后段(接近抓取)权重更高
    weights = torch.where(timesteps >= final_start, 3.0, 1.0)
    weights = weights / weights.sum()

    # VLM 看图后硬编码:目标是 8 号关键点(奶油奶酪)
    target_idx = torch.tensor([8], device=keypoints.device)
    target_pos = keypoints[target_idx][0]

    # reward = 负的"轨迹到目标关键点的加权距离平方"
    xy_dist = torch.norm(action_sequence[..., :2] - target_pos[:2], dim=-1)
    z_dist  = torch.abs(action_sequence[..., 2] - target_pos[2])
    weighted_dist_sq = 3.0 * (xy_dist ** 2) + (z_dist ** 2)
    reward = -(weighted_dist_sq * weights).sum(dim=1)
    return reward.mean()
看懂这个 reward:它 = 负的「机械臂轨迹点 到 目标关键点 的加权距离」。轨迹离目标越近 → 距离越小 → reward 越大(越接近 0)。所以"让 reward 变大"就等于"让动作靠近目标物体"。目标物体是哪个?VLM 看图识别(图上关键点标了红色编号),硬编码那个编号(这里是 keypoint 8),不靠算。

② 关键点从哪来?

keypoints 是场景里候选物体的 3D 坐标:SAM 分割出物体 → DINOv2/v3 提特征 → kmeans 聚类出若干候选关键点,每个标一个编号叠在图上给 VLM 看。VLM 的工作就是从这些编号里用眼睛挑出目标物体对应的那个

③ loss/梯度怎么传:reward 对动作直接求导

reward 是纯 torch 运算、可微。VLS 把当前去噪中的动作设为可求导,让 reward 对它求梯度(core/pi05_steer.py: _compute_keypoint_gradient):

with torch.enable_grad():
    sample_grad = sample.detach().requires_grad_(True)      # 让动作可求导
    traj = self._sample_to_trajectory_3d(sample_grad)[..., :3]  # 动作→3D轨迹
    reward = guidance_fn(keypoints_tensor, traj)            # 调 VLM 写的 reward
    grad = torch.autograd.grad(reward, sample_grad)[0]      # reward 对动作求梯度
    normalized_grad = grad / (grad.norm() + 1e-8)           # 归一化
没有传统的"loss.backward()训练" —— 这里是推理时对单步动作做一次 autograd.grad,拿到"动作往哪个方向调能让 reward 上升"的梯度,然后把这个梯度(乘自适应 scale)加到 flow matching 的速度场上:v_t[...,:3] -= scale · normalized_grad。模型权重全程不更新,只调正在生成的这条动作。

④ 完整链路一句话串起来

VLM 看图+读指令 → 写出 reward 函数(硬编码目标关键点)
   → reward = −距离(动作, 目标关键点)
   → autograd.grad(reward, 动作) 得梯度
   → 梯度 × 自适应scale 注入去噪速度场
   → 动作被"推"向目标物体 → 纠正 OOD 下的错误

3.2 两个 VLM 调用点(关键:用不同的 key)

用途代码用哪个 key状态
guidance/reward 生成vlm_query/vlm_agent.pycodex(OpenAI 兼容)✅ 正常
stage recognitioncore/gemini_grounder.pyGoogle Gemini SDK❌ 无 key,已关
codex key 不能当 Gemini key 用。gemini_grounder.pyimport google.generativeai 只认 Google key 与端点,塞 codex key 会认证失败。两者不同厂商、不同协议。我们只有 codex key,所以 stage recognition 被关闭(影响见第 7 节)。

4. 部署记录

🖥️ 本机(RTX 3090, CUDA 11.8)
conda env vls / torch 2.7.1+cu118 / transformers 4.53.3(lerobot fork) / numpy 1.26.4。用途:部署验证 + 小样本 smoke。
🌐 开发机(8×RTX 4090)
装在 czy 共享盘。仅内网:公网走 proxychains4,HF/GitHub 不通用离线模式 + 本机 scp/rsync。用途:8 卡并行全量评测。

4.1 部署踩坑(举一反三)

根因规律
libero import 失败嵌套包未注册嵌套 submodule 用 .pth 注册路径
mujoco EGL 报错PyOpenGL 3.1.0 太旧EGL 渲染需 ≥3.1.10
DINOv3 加载失败HF 不通且未缓存离线环境确认实际生效的 backbone
8 进程视频混写hydra 秒级时间戳撞车并行跑同程序注入唯一输出目录
log 暴涨到 5.7G每步 httpx 请求都打 INFO调 API 的长任务把 httpx 日志压到 WARNING

5. 数据集与评测设定

看结果前先搞懂:我们在测什么、swap/task 是什么、为什么这些数字能验证 VLS。

5.1 LIBERO —— 原始机器人操作基准

LIBERO 是机器人桌面操作的仿真基准(MuJoCo/robosuite)。给一句语言指令(如"打开中间的抽屉"),机器人执行动作,成功率 = 完成的 episode 比例。它有 4 个 task suite,各考察一种能力:

suite含义例子
Goal同物体,不同目标"打开抽屉" vs "关抽屉"
Spatial同物体,不同空间摆放碗在盘子左边 / 右边
Object不同物体拿番茄汤 / 拿字母汤
10 (Long)长程任务,多步骤串联一连串子任务,步数最多

每 suite 10 个 task × 每 task 20 episode = 每 suite 200 episode(这就是 n=200 的来历)。

5.1.1 每个 suite 具体是哪 10 个任务(从 bddl 提取)

不能只说 Object/Goal。下面是每个 suite 的真实任务指令,任务结构直接决定 VLS 是否有效:

Object(单阶段抓放,只换物体)✅ 最适合 VLS
pick up the [alphabet soup / bbq sauce / butter / chocolate pudding / cream cheese / ketchup / milk / orange juice / salad dressing / tomato sauce] and place it in the basket
Spatial(单阶段抓放,只换位置)
pick up the black bowl [between the plate and ramekin / from table center / in the top drawer / next to cookie box / next to plate / next to ramekin / on cookie box / on ramekin / on stove / on wooden cabinet] and place it on the plate
Goal(多样,含多阶段)⚠️
1. open the middle drawer(抓把手→拉开,多阶段)
2. open the top drawer and put the bowl inside(多阶段)
3. push the plate to front of stove
4-6. put the bowl on plate / stove / cabinet
7. put cream cheese in bowl
8-9. put wine bottle on rack / cabinet
10. turn on the stove
10 / Long(全多步骤串联)❌ 能力边界
1. turn on stove + put moka pot on it
2. put bowl in drawer + close it
3. put mug in microwave + close it
4. put both moka pots on stove
5-7. put both [两种食物] in basket
8-9. 双杯分别放左右盘 + 布丁
10. pick book + place in caddy compartment
suite任务结构对 VLS 的意义
Object单阶段抓放,只换物体✅ VLS 最擅长(引导目标单一)
Spatial单阶段抓放,只换位置✅ 理论适合(我因 kmeans bug 未测到)
Goal多样,含多阶段(开抽屉)⚠️ 多阶段需 stage 切换
10/Long全多步骤串联❌ 长程,VLS 能力边界

5.2 问题:VLA 在原始 LIBERO 上"作弊"

主流 VLA(OpenVLA/π0/π0.5)在原始 LIBERO 上都能 >90%。但 LIBERO-PRO 论文发现:高分很大程度是死记硬背训练场景,不是真学会任务——只要对场景加点扰动,成绩就崩到接近 0。

5.3 LIBERO-PRO —— 加扰动测真泛化(我们用的就是它)

在原始任务上施加 5 种扰动,专门戳破"记忆"、逼出真实的分布外(OOD)能力:

澄清:LIBERO-PRO 本身有 5 种扰动,但 VLS 只用了其中 2 种(Position + Task)。
VLS 论文 Table I 表注原话:"The experimental environment consists of LIBERO-PRO's task and position perturbation, applied to LIBERO's four suites."
为什么只选 2 种?论文说:"Among these, the position and task perturbations best align with the description of OOD scenarios in this paper."
• Position(swap) = 观测偏移 o_OOD → 对应 VLS 验证的"空间 grounding 纠偏"
• Task = 语言偏移 l_OOD → 对应 VLS 验证的"指令跟随纠偏"
另外 3 种(Object换外观/Semantic改措辞/Environment换场景) VLS 没测。
下面列出 PRO 全部 5 种(了解完整 benchmark),但记住 VLS 实验只涉及前两种。
扰动我的标签改了什么例子
Position_swap把物体挪到别的合理位置(指令不变)换 cup 和 bowl 的位置
Object_object改物体外观/颜色/大小"红杯"→"黄杯"
Semantic_lan换说法表达同一指令"grasp the mug"→"pick up the cup"
Task_task改任务逻辑/目标(指令彻底变)"拿杯子"→"拿黄油"
Environment_env换工作场景主桌→厨房桌
论文实测:π0.5 在原始 Object 上 98%,一加 Position 扰动掉到 17%,加 Task 扰动掉到 1%。这就是 OOD 问题的量化体现。

5.3.1 我们用的两种扰动,到底改了什么?(看代码搞懂)

Position(swap)= 把两个物体的初始位置对调,语言指令一个字不改。
机制(perturbation.py: SwapPerturbator):在 BDDL 初始状态里,把关注物体 A 和另一物体 B 的 (On A 区域a) / (On B 区域b) 对调成 (On A 区域b) / (On B 区域a)
举例(Spatial suite):原任务"pick up the black bowl next to the plate and place it on the plate"。原本黑碗在盘子旁、饼干盒在别处;swap 后把黑碗和饼干盒的位置对调 —— 现在盘子旁边是饼干盒,黑碗跑到别处去了。指令没变,还是"拿盘子旁边的黑碗",但盘子旁边已经不是黑碗了。
为什么能测 OOD:如果 policy 是死记"移动到某个固定坐标去抓",它就会抓向旧位置(现在那儿是别的物体)→ 失败。真正理解任务的 policy 才会去找"黑碗现在在哪"。
Task = 把整个任务换成另一个:指令、目标、关注物体同时全变。
机制(perturbation.py: TaskPerturbator):同时替换 BDDL 里的 (:language)(语言指令)+ (:goal)(目标完成状态)+ (:obj_of_interest)(关注物体)。
举例(Object suite):原任务"pick up the alphabet soup and place it in the basket",桌上摆着同一批食物;task 扰动后指令变成"pick up the butter..." —— 场景物体没动,但要抓的目标从字母汤换成了黄油。
为什么能测 OOD:policy 不能靠"这个场景我见过、闭眼执行老动作",必须真的读懂新指令去抓新目标。这是最难的 OOD(论文里所有模型几乎都掉到 0-1%)。

5.4 为什么这能验证 VLS?

VLS 的卖点是「训练-free 推理时引导解决 OOD」。验证逻辑是一条清晰的对照链:

原始任务(90%+) ──加扰动──▶ baseline 崩溃(Object-swap 14%) ──加VLS引导──▶ 救回(36%)
                            ↑ 这就是 OOD                      ↑ 这就是 VLS 的价值
  • baseline 在扰动下的低分(swap 14% / task 12%)= OOD 问题真实存在的证据
  • VLS 相对 baseline 的提升(swap 14→36%)= VLS 确实在纠正 OOD,而非碰运气
  • 4 suite × 2 种扰动 交叉测,能看出 VLS 在哪类 OOD 有效、哪类失效
直接回答"OOD 是否被解决":部分解决。Object suite(单阶段抓放)上 VLS 明显把 baseline 从崩溃救回(swap 14→36%、task 12→20%),说明 VLS 能解决"物体被移动/替换"这类 OOD;但多阶段任务(Goal)和长程任务(LIBERO10)上 VLS 无效甚至反降 —— 这类 OOD 还没被解决,是 VLS 的边界(详见第 7 节)。
所以"swap 14%""task 12%"单看是低分,但意义是 baseline 在特定 OOD 下的表现基线,VLS 的价值体现在"比这个基线高多少"。数据来源:LIBERO-PRO(arXiv:2510.03827),我们用 Position(swap)+ Task 两种扰动 × 4 suite,对齐 VLS 论文协议。

6. 评测结果

评测基准:LIBERO-PRO,4 suite(Goal/Spatial/10/Object)× 两种 OOD(Position/swap、Task)。Policy:π-0.5 LeRobot 作者权重。VLM guidance backend:codex gpt-5.5,stage recognition 关闭(无 Gemini key)。每 suite 200 episode。

6.0 📊 完整结论总表(先看这张)

图 N:VLS 完整评测结论总表 —— 两种 OOD × 4 suite 的 baseline vs VLS(n=200)
图 N:VLS 完整评测结论总表 —— 两种 OOD × 4 suite 的 baseline vs VLS(n=200)
三条核心结论:① VLS 对 Object suite 的 OOD 有效(swap 14→36%、task 12→20%);② 多阶段任务缺 stage recognition 会反降(goal swap 28→16%);③ 长程任务 LIBERO10 是能力边界(swap 1%、task 0.5%)。

6.0.1 符合性对比:我的结果 vs 论文

π-0.5 LeRobot,n=200。论文值取自 VLS 论文 Table I / LIBERO-PRO leaderboard。

Position(swap)扰动:

suite论文 base→VLS我的 base→VLS符合?说明
Object—*14%→36.4%✅ 趋势符合论文按 4-suite 平均报告(Position 24.25→35.13),Object 单列数未从原表核实,故不列具体值
Goal有提升27.5%→16.5%❌ 反降多阶段(开抽屉)缺 stage rec.,VLS 卡错阶段
Spatial41%→42%41%→10.7%(n=75)⚠️ 不可靠kmeans NaN 死循环卡死,样本不足
1011%→15.5%10.5%→1.0%❌ 偏低长程任务,引导难救多步序列

Task 扰动:

suite论文 base→VLS我的 base→VLS符合?说明
Object10.5%→41%12%→20.5%⚠️ 方向对提升到一半,因关 stage rec.(task 多为多阶段)
Spatial→25.0%仅我方数据
Goal→13.0%多阶段受限
10→0.5%长程边界
总体判断:部分符合,偏差有一致解释。① 核心 claim 成立:Object suite(单阶段)的 VLS 提升方向明确、量级与论文一致(论文 Position 扰动 4-suite 平均 24.25%→35.13%),验证了 VLM reward 梯度纠偏机制。② 偏差几乎全源于同一个已知差距——我没有 Gemini key,关闭了 stage recognition,而它对 Goal(多阶段)、10(长程)、所有 Task 扰动伤害最大,论文是开着的所以不会有 Goal 反降。③ Spatial 因 kmeans bug 卡死无结论。结论:能对齐论文的地方(Object/单阶段/开引导)基本符合,偏离处均可归因于受控差异,不是 VLS 方法失效。

5.1 🖥️ 本机 object 四种 OOD(n=10)

图 1:本机 object 四种 OOD —— 只有 swap 时 VLS 显著提升(10%→50%)
图 1:本机 object 四种 OOD —— 只有 swap 时 VLS 显著提升(10%→50%)

5.2 🌐 开发机 Swap 全量(n=200,最终数据)

图 E:开发机 swap 全量最终结果 —— Object 14→36% 有效,Goal 反降,Spatial 因 kmeans 卡死不可靠
图 E:开发机 swap 全量最终结果 —— Object 14→36% 有效,Goal 反降,Spatial 因 kmeans 卡死不可靠
Object-Swap 14.0%→36.4%:VLS 有效纠偏,与论文 Position 扰动平均提升(24.25%→35.13%)方向一致。
Goal-Swap 27.5%→16.5%(反降):多阶段任务 + 关 stage recognition 所致,见第 7 节。

5.3 🌐 开发机 Task 扰动(方案 A,n=200 完成)

图 H:Task 扰动 baseline vs VLS(关 stage recognition)
图 H:Task 扰动 baseline vs VLS(关 stage recognition)
图 I:同 suite 下 Task 比 Swap 更难
图 I:同 suite 下 Task 比 Swap 更难
object_task:VLS 20.5% > baseline 12.0%(方向正确,但 < 论文 41%,因关 stage recognition)。10_task 仅 0.5%,再证长程边界。

6.4 📹 实验结果视频画廊(成功 / 失败,真实 rollout)

每个 rollout 评测时都会存一段视频,开发机已累积近千段。下面按 suite 分组展示成功(绿框)与失败(红框)的真实案例。绿框=VLS 完成任务,红框=超时/抓错失败。

① Position(swap)扰动:baseline vs VLS 同任务对比

最直观的证据:同一个被挪了位置的任务,baseline 抓向旧位置失败,VLS 被 VLM reward 拉向新位置成功。

✗ 失败 Baseline:抓向 swap 前的旧位置,超时
✓ 成功 VLS:被 reward 拉向新位置,完成
图 G:Swap OOD 关键帧对比(逐帧看动作差异)
图 G:Swap OOD 关键帧对比(逐帧看动作差异)

② Object-Task 扰动(单阶段抓放,VLS 最擅长)

任务:桌上摆着一排食物(字母汤/黄油/番茄酱/牛奶…),指令形如"pick up the X and place it in the basket"。task 扰动把要抓的目标 X 换成另一个物体(如 字母汤→黄油),场景不变但目标变了。VLS 20.5% > baseline 12.0%,下面 3 成功 + 3 失败 + 1 组同任务对照。

✓ 成功 VLS 成功:正确抓到新目标物体放入篮子
✓ 成功 VLS 成功:机械臂移向正确食物并抓取
✓ 成功 VLS 成功:完成抓取-放置
✗ 失败 VLS 失败:未抓到目标/超时
✗ 失败 VLS 失败:抓取偏差
✗ 失败 VLS 失败:未完成
✗ 失败 对照-Baseline(无引导):抓向旧目标,失败
✓ 成功 对照-VLS(有引导):同一任务被救回,成功

③ Spatial-Task 扰动(单阶段抓放,只换空间关系)

任务:指令形如"pick up the black bowl [在某处] and place it on the plate",10 个任务是黑碗放在不同位置(桌心/抽屉里/炉子上/饼干盒旁…)。VLS 25.0%,是 task 扰动里表现最好的 suite。

✓ 成功 VLS 成功:定位到黑碗并放上盘子
✓ 成功 VLS 成功:正确抓取黑碗
✓ 成功 VLS 成功:完成放置
✗ 失败 VLS 失败:抓错位置/超时
✗ 失败 VLS 失败:未完成
✗ 失败 VLS 失败:抓取偏差

④ Goal-Task 扰动(含多阶段任务,成功率低)

任务:goal suite 目标多样(开抽屉、把碗放到盘子/炉子/柜顶、开灯…),其中"open the middle drawer"这类是多阶段(先抓把手→再拉开)。VLS 仅 13% —— 缺 stage recognition 时,VLS 容易卡在第一阶段的 reward 上出不来,成功案例稀少(只有 2 个)。

✓ 成功 VLS 成功:少数完成的案例之一
✓ 成功 VLS 成功:少数完成的案例之一
✗ 失败 VLS 失败:卡在错误阶段/超时
✗ 失败 VLS 失败:多阶段未推进
✗ 失败 VLS 失败:未完成

⑤ LIBERO10 / Long-Task 扰动(长程任务,能力边界,全失败)

任务:全是多步骤串联的长程任务(如"开炉子+把摩卡壶放上去"、"把碗放进抽屉+关上抽屉"、"把两种食物都放进篮子")。VLS 仅 0.5%,几乎无成功。长 horizon 下引导信号被稀释,VLS 救不回。下面 3 段全是失败。

✗ 失败 VLS 失败:长程任务中途停滞
✗ 失败 VLS 失败:多步骤未完成
✗ 失败 VLS 失败:超时

6.5 📹 4 suite 视频墙(关键帧汇总)

把代表 rollout 抽帧拼成静态对比图,一屏看全 VLS 在 4 个 suite 的行为差异。

图 L:VLS 在 4 个 Task suite 的行为 —— object/spatial/goal 成功,LIBERO10 仍失败
图 L:VLS 在 4 个 Task suite 的行为 —— object/spatial/goal 成功,LIBERO10 仍失败
图 M:Object-Task VLS 多个成功 rollout 行为一致
图 M:Object-Task VLS 多个成功 rollout 行为一致

6.6 与论文对齐

图 2:论文 Table I 基准(π-0.5 LeRobot)
图 2:论文 Table I 基准(π-0.5 LeRobot)
图 3:本机复现 vs 论文趋势一致
图 3:本机复现 vs 论文趋势一致
图 F:开发机全量 vs 论文
图 F:开发机全量 vs 论文
图 J:Object-Task 我们 vs 论文 —— 差距主因是关闭了 stage recognition
图 J:Object-Task 我们 vs 论文 —— 差距主因是关闭了 stage recognition

7. 关键发现

发现 1:VLS 对 Position(swap)OOD 有效。位置先验被打破时,baseline 大幅下降,VLS 用 VLM reward 把动作拉回正确目标(本机 object_swap 10%→50%,开发机 object_swap_vls 已达 50%+ 量级)。
发现 2:多阶段任务缺 stage recognition 时,VLS 反而拖累。goal 任务是 stage1 抓把手 → stage2 往 +Y 拉开,两阶段 reward 方向冲突。关了 stage recognition 后 VLS 全程用 stage1 reward 把动作钉在把手处,反而阻碍拉开 → VLS(16.5%)< baseline(27.5%)。对比 object 任务 stage2"无需引导",所以 object 不受影响。这说明 stage recognition 对多阶段任务是刚需,不是锦上添花。
发现 3:LIBERO10-Task 是 VLS 的能力边界。长程任务(10+ 步序列)即使有引导也几乎无成功(视频墙最后一行全失败)。
发现 4:codex key ≠ Gemini key。不同厂商不同协议不可混用。缺 Gemini key → 关闭 stage recognition → task 扰动指标低于论文,这是实验设置决定的差距,不是 bug。

8. 结论

  • 方法成立:训练-free 推理时引导能在不重训 policy 的前提下提升 Position OOD 表现,本机/开发机均复现出与论文一致的趋势。
  • 有边界:多阶段任务依赖 stage recognition;长程任务(LIBERO10)仍是难点。
  • 工程要点:离线部署需处理 HF/GitHub 不通(scp/rsync + 离线模式);多进程评测需隔离输出目录;长任务需压制第三方日志。
  • 复现条件:完整对齐论文 Task 指标需 Google Gemini key 开启 stage recognition。

本页所有图表来自真实评测,视频为开发机真实 rollout。详细记录见仓库 midocx/vls_完整记录.md

9. 对 Steering 课题的启示

把 VLS 复现中暴露的问题,提炼成对后续「推理时引导 / steering」课题的可操作指导。

8.1 VLS 的三个软肋 = 课题突破口

VLS 局限暴露证据课题切入
依赖 stage recognition 且绑死 Geminigoal_swap 反降 28→16%stage 识别改走开源/自建 VLM,或用 policy 内部信号自动切 stage
多阶段 reward 方向冲突goal 的 stage1/stage2 目标相反多阶段引导的 reward 编排:自动判断当前该用哪个子目标
长程任务几乎无效LIBERO10 swap 1% / task 0.5%分层引导 / 关键步引导 / 与 replanning 结合

8.2 引导机制的通用洞见

引导不是越强越好,方向对才关键。goal 案例证明:reward 方向错时,强引导把动作钉死在错误目标,比不引导更差。steering 课题必须先保证「引导目标与当前真实子目标对齐」。
自适应强度(Sigmoid)不够。VLS 用 reward 值调强度(远强近弱),但解决不了「reward 本身指错方向」。可研究引导方向的置信度估计,方向不确定时降级。
任务结构决定引导难度。单关键阶段(object)最适合引导,多阶段/长程是难点。选题先想清目标任务是哪种结构。

8.3 做 steering 实验的方法论

  • 推理时引导 = 每步调外部模型,极慢且脆弱。VLS 单 episode 慢约 10×,还因 kmeans NaN 卡死近 2 天。课题设计要预留:超时看门狗、NaN 防护、日志降噪。
  • 评测要区分 OOD 类型(Position/Task/长程),不同类型对引导响应完全不同,混看平均会掩盖结论。
  • 判断实验是否推进,看业务里程碑(episode 完成数),不是进程存活或 GPU 占用。这是最贵的教训(白等近 2 天)。
  • 多个 LLM 调用点别假设共用一个 key,逐个确认 SDK 与凭证。
一句话总结:VLS 证明了「训练-free 推理时引导」在单阶段 Position OOD 上有效(object 14→36%),但多阶段 reward 编排长程任务是软肋,且强依赖闭源 stage 识别——这三点正是 steering 课题最有价值的突破口。