ConsisVLA-4D 论文阅读笔记

8,501 字 · English 1,155 词 · 约 30 分钟阅读

论文标题: ConsisVLA-4D: Advancing Spatiotemporal Consistency in Efficient 3D-Perception and 4D-Reasoning for Robotic Manipulation arXiv ID: 2605.05126v1 作者: Wei Li, Jizhihui Liu, Li Yixing, Junwen Tong, Rui Shao, Liqiang Nie — 哈工大(深圳)、中兴、深圳环湾研究院 投稿方向: CVPR 2026 代码*: github.com/JiuTian-VL/ConsisVLA-4D


一、核心问题

当前 VLA(Vision-Language-Action)模型主要存在两大缺陷:

  1. 3D 空间理解不足:多数模型止步于 2D 观测→动作的映射;引入点云/深度图的方法依赖额外传感器,计算开销大;2D→3D 投影的方法存在投影偏差、遮挡误差,缺乏跨视角和跨物体的几何一致性。
  2. 4D 时空推理薄弱:现有方法最多做到预测未来帧图像,缺少与指令相关场景的对齐,导致时空一致性差,动作生成不稳定。

intro:与现有范式对比

图1:ConsisVLA-4D 与现有 VLA 范式对比总览。左侧展示了四种 VLA 范式从 2D 观测到动作生成的演进路径,右侧展示了 ConsisVLA-4D 在四个长时序真机任务上的执行效果。

左侧——四种 VLA 范式的演进:

右侧——四个真机长时序任务示例: 展示了 ConsisVLA-4D 在微波炉操作(放面包→加热→关门)、剥香蕉(抓起→剥皮→放盘)、抽屉整理(拉开→放入→推回)、T恤折叠等任务上的执行效果,印证了时空一致性对长时序操作的关键支撑作用。*

二、核心思路

受人类操控行为启发(双眼视觉保持空间感知一致性 + 在稳定空间感知基础上预测未来状态),提出 ConsisVLA-4D,在一个统一框架内实现高效的 3D 感知 + 4D 推理,包含三个紧密耦合的模块:

pipline:从 3D 感知到 4D 推理的机制

图2:3D 感知→4D 推理完整流水线。该图从机制层面揭示 CV-Aligner 和 CO-Fuser 如何将各自的 3D 一致性拓展到 4D 时空域。

上半部分(CV-Aligner → 4D 推理)—— 从跨视角物体到动态物体预测:

CV-Aligner 在多视角(Main/Left/Right)图像中通过 ES-Selection + Single-Fusion 识别并匹配同一物体在不同视角下的身份,产生跨视角一致的物体表示 $\mathbf{z}_{\{M,L,R\}}^{\text{obj-3D}}$。进入 4D 推理阶段后,CS-Thinker 从多视角物体 token 出发,预测动作执行后固定视角(如 Main)中的动态物体——即回答”这个物体在动作后会变成什么样、出现在什么位置”。例如,从 Main 视角看到碗在桌上、Left 视角看到碗在微波炉旁,4D 推理预测碗放入微波炉后的状态。这里的关键是从”当前多视角静态物体”到”未来单视角动态物体”的推理跨越。

下半部分(CO-Fuser → 4D 推理)—— 从聚合几何关系到全局深度预测:

CO-Fuser 通过 Group-Fusion + IG-Aggregation 聚合多视角的空间几何关系,消除单视角的尺度歧义和遮挡误差,产生紧凑的聚合几何表示 $\mathbf{z}_{\mathcal{L}'}^{\text{agg-3D}}$。进入 4D 推理阶段后,CS-Thinker 从同一组几何表示出发,解码出所有视角的全局深度图——即回答”动作后整个场景的 3D 结构是什么样的”。这里的关键是从”抽象几何关系”到”具体深度图”的推理跨越。

整体意义: 3D 感知确保了空间一致性(物体身份跨视角一致 + 几何关系跨物体一致),4D 推理在此基础上扩展为时空一致性——既预测局部物体动态,又预测全局场景深度,两者共同作为动作生成的中间视觉推理支撑。*

2.1 CV-Aligner(跨视角对齐器)—— 保证跨视角物体语义一致性

2.2 CO-Fuser(跨物体融合器)—— 保证跨物体空间几何一致性

3d:高效 3D 感知

图3:高效 3D 感知保证空间一致性。该图详细展示 CV-Aligner(红色)和 CO-Fuser(橙色)的内部结构,以及三个视觉编码器(SigLIP/DINOv2/VGGT)的分工协作。

子图 (a) CV-Aligner(红色)—— 跨视角物体语义一致性:

CV-Aligner 的输入来自 SigLIP(语义编码器,输出 256 个 patch token/视角)和 VGGT(空间编码器,输出 3D 特征)。流程分为三步:

  1. FiLM 调制:在 SigLIP 的每一层 Transformer Block 中,利用指令 embedding 生成 scale $\gamma(\mathbf{t})$ 和 shift $\beta(\mathbf{t})$ 参数,对视觉 token 做通道级调制 $\tilde{\mathbf{z}} = (1+\gamma) \odot \mathbf{z} + \beta$。这使得指令语义渗透到每个视觉 token 中,增强视觉特征与指令的相关性。
  2. ES-Selection(显式语义物体选择):计算每个视觉 token 与指令 embedding 的余弦相似度,保留 Top-K(默认 K=32)最相关的 token。这步将 256 个 patch token 压缩为 32 个,压缩比 1/8,同时确保保留的是与当前任务指令最相关的物体 token。
  3. Single-Fusion(逐帧融合):以筛选后的 32 个语义 token 为 Q,VGGT 的 3D 特征为 K、V,通过 4 层 cross-attention 逐帧(逐视角独立)融合。利用 VGGT 的点跟踪预训练能力,隐式建立同一物体在不同视角之间的身份关联。

最终输出 $\mathbf{z}_{i}^{\text{obj-3D}}$(每个视角 32 token),实现了跨视角物体语义一致性。

子图 (b) CO-Fuser(橙色)—— 跨物体空间几何一致性:

CO-Fuser 的输入来自 DINOv2(几何编码器)和 VGGT,所有视角的图像和 VGGT 特征拼接后一起处理。流程分为两步:

  1. Group-Fusion(分组融合):在 DINOv2 的每个 Transformer Block 中,将 DINOv2 几何特征与 VGGT 空间特征按可学习的余弦衰减权重 $\alpha_l$ 做逐元素加权求和:$\mathbf{z}_{l}^{\text{geo-3D}} = (1-\alpha_l)\mathbf{z}_{l}^{\text{geo}} + \alpha_l\mathbf{z}_l^{\text{3D}}$。α_l 从前 12 层的 0.2 余弦衰减到后 12 层的 0——浅层充分注入 VGGT 的 3D 空间先验,深层由 DINOv2 自身几何特征主导。
  2. IG-Aggregation(隐式几何关系聚合):引入 64 个可学习的聚合 token $\mathbf{z}^{\text{agg-3D}}$,与融合后的几何 token 一起通过 BC-Attn(块级因果注意力)处理——aggregator token 可以看到所有 patch token 以聚合全局几何信息,patch token 之间保持双向注意力。

最终仅保留 $\mathbf{z}_{\mathcal{L}'}^{\text{agg-3D}}$(64 token),压缩比 1/12–1/8,消除了单视角的几何歧义。

三个编码器的分工:

supp_1:BC-Attn 块级因果注意力

图4:BC-Attn(块级因果自注意力)机制示意图,展示 CO-Fuser 中 IG-Aggregation 的核心注意力设计。

注意力 mask 设计: 图中展示三视角(Main/Left/Right)的 patch token(蓝色方块)与可学习的 Aggregation Token(橙色方块,用 A 标记)之间的注意力模式。关键设计是:

设计意图: 这种非对称的注意力设计确保 Aggregation Token 成为跨视角几何信息的唯一汇聚点——它”看”到了所有视角的空间信息但不能”告诉”任何 patch token,迫使模型将所有跨视角的空间几何关系压缩到这 64 个 token 中。推理时只使用这 64 个 Aggregation Token,丢弃所有原始 patch token,实现从 768→64 的高效压缩。*

2.3 CS-Thinker(跨场景思考器)—— 保证跨场景时空一致性

核心创新:将 3D 感知拓展到 4D 推理,训练时学习隐式知识,推理时不显式生成中间表示。

4d:高效 4D 推理

图5:高效 4D 推理。CS-Thinker 配合 SC-Attn 在统一上下文窗口内并行实现三件事:动态物体预测、全局深度预测、动作生成。训练时三者同时学习(动态物体和深度有监督信号),推理时仅执行动作生成(依赖隐式知识 IK)。

子图 (a) 整体结构 —— SC-Attn 统一上下文窗口:

CS-Thinker 的输入 token 序列被组织为:[语义token(64) | 几何token(64) | 文本token | Dynamic Queries(2N) | Depth Queries(N) | Action Queries]。所有 token 在 SC-Attn 的统一上下文窗口内并行处理,不同类型的 token 遵循不同的注意力模式(组内双向 + 组间因果)。其中 Dynamic Queries 和 Depth Queries 是可学习的隐式知识 token,占序列不到 10%。

子图 (b) 多视角物体 → 单视角动态物体预测(CoTracker 监督):

为每个视角 $i$ 初始化一组可学习的 dynamic token $\mathbf{0}_{i}^{\text{dyn-4D}}$(3 视角 × 4 token/组 = 12 token)。在 SC-Attn 中,每个 $\mathbf{0}_{i}^{\text{dyn-4D}}$ 由对应视角的物体特征 $\mathbf{z}_{i}^{\text{obj-3D}}$ 和指令 $\mathbf{t}$ 独立引导。经过 LLM 处理后,dynamic token 的 hidden states 被送入 DynamicFeatDecoder(8 层 ViT Block,1024 维),解码出固定视角 $i^*$ 中动作后的动态物体图像 $\hat{\mathbf{z}}_{i^*}^{\text{dyn-4D}}$。

损失函数 $\mathcal{L}_{\text{dyn-4D}}$:仅对 CoTracker 离线标注的运动区域($\mathbf{m}_{i^*}^{\text{obj-3D}}$)计算 MSE,避免背景干扰。这确保模型学到的是”物体因动作发生了怎样的变化”,而非”整个画面变成了什么样”。

子图 (c) 抽象关系 → 具体全局深度预测(Depth-Anything 监督):

初始化一组可学习的 depth token $\mathbf{0}^{\text{dep-4D}}$(1 组 × 4 token = 4 token)。在 SC-Attn 中,这些 token 由 CO-Fuser 的聚合几何关系 $\mathbf{z}_{\mathcal{L}'}^{\text{agg-3D}}$ 和指令 $\mathbf{t}$ 共同引导,但与 dynamic token 的学习过程隔离。经过 LLM 处理后,depth token 的 hidden states 被送入另一组 DynamicFeatDecoder(img_channel=1, pred_num=视角数),解码出所有视角的全局深度图。

损失函数 $\mathcal{L}_{\text{dep-4D}}$:逐视角计算预测深度与 Depth-Anything 生成的真实深度的 MSE。注意 dynamic 和 depth 的解码器结构相同但参数独立——dynamic decoder 每个视角一个,depth decoder 单个解码器同时输出所有视角。

子图 (d) 并行动作解码:

动作 token $\mathbf{0}^A$ 在 SC-Attn 中与上述所有 visual reasoning token 及指令交互。训练时,LLM 最后一层的动作 token hidden states 通过 L1RegressionActionHead 预测连续动作,与 GT 动作计算 L1 损失 $\mathcal{L}_{\text{action}}$。三组损失联合优化:总损失 $= \mathcal{L}_{\text{action}} + \mathcal{L}_{\text{dyn-4D}} + \mathcal{L}_{\text{dep-4D}}$。

推理时(关键设计): DynamicFeatDecoder 和 DepthFeatDecoder 不执行,不生成中间表示。模型仅依靠训练中学会的隐式知识(dynamic/depth dream token 在 LLM 中的 hidden states)辅助动作生成——这解释了为什么引入 2B 参数后推理反而更快 2.3 倍。*


三、训练目标

$$\mathcal{L}_{\text{total}} = \mathcal{L}_{\text{action}} + \mathcal{L}_{\text{dyn-4D}} + \mathcal{L}_{\text{dep-4D}}$$


四、实验与结果

4.1 仿真实验(LIBERO 基准)

方法 Spatial Object Goal Long Avg.
OpenVLA 84.7 88.4 79.2 83.7 76.5
OpenVLA-OFT 97.6 98.4 97.9 94.5 97.1
π₀.₅ 98.8 98.2 98.0 92.4 96.9
SpatialVLA 88.2 89.9 78.6 55.5 78.1
CoT-VLA 87.5 91.6 87.6 69.0 83.9
ConsisVLA-4D 98.8 99.8 98.0 95.6 98.1

vis2:LIBERO 观测可视化与注意力

图6:LIBERO 观测示例与 CV-Aligner/CO-Fuser 注意力可视化。该图直观展示 ConsisVLA-4D 的两个核心模块在实际任务中分别关注什么,以及这种分工如何支撑仅用 1/8 token 就达到 SOTA 性能。

第 1 行(观测视图)—— 原始输入: 展示四个 LIBERO 任务的 Main View(主视角,M)和 Wrist View(腕部视角,W)观测图像。四个任务分别为:将碗放到饼干盒上/旁、从木柜顶层抽屉取出碗、打开微波炉(stove 场景)、将盘子放到托盘上。M 视角提供全局场景上下文(物体相对位置、桌面布局),W 视角提供近距离操作细节(夹爪与物体的精确空间关系)。

第 2 行(橙色标记 M/W)—— CV-Aligner 提取的指令相关物体 token: 橙色标记显示 CV-Aligner 选出的 Top-K(K=32)语义 token 在原图中的对应位置。可以看到,CV-Aligner 精准定位了任务指令涉及的关键物体——饼干盒上的碗、抽屉中的碗、炉灶、盘子——而过滤掉了背景、桌面纹理、无关物体。注意 M 和 W 视角下同一物体的标记位置不同但语义一致,验证了跨视角物体身份关联的有效性。

第 3 行(蓝色标记 R)—— CO-Fuser 捕获的聚合几何关系 token: 蓝色标记显示 CO-Fuser 的 64 个 aggregator token 如何分布注意力到原始 patch token 上。与 CV-Aligner 的"点状聚焦"(只关注指令相关物体)不同,CO-Fuser 呈现"分布式注意力"——同时覆盖任务相关的多个空间节点(物体、容器、桌面边缘)。这反映了 CO-Fuser 关注的是物体之间的空间几何关系("碗在饼干盒上方""抽屉的几何结构")而非物体语义本身。

关键洞察: CV-Aligner 回答"关注什么物体"(语义选择),CO-Fuser 回答"物体在哪里、它们之间的空间关系如何"(几何聚合)。两者功能互补,分别在语义和几何维度实现高效压缩,合计将视觉 token 压缩到 1/8,却保留了比原始 256 token 更精准的任务相关信息。*

ManiSkill2 结果:

方法 PickCube StackCube PushCube Avg.
Octo 86% 76% - 81.0%
OpenVLA† 67% 64% 71% 67.3%
CogACT 95% 90% - 92.5%
GeoVLA 90% 90% - 90.0%
Dita 79% 80% - 79.5%
OpenVLA-OFT† 85% 93% 88% 88.7%
ConsisVLA-4D 93% 95% 95% 94.3%

† 表示在与 ConsisVLA-4D 相同条件下复现的结果。ManiSkill2 的三个任务侧重测试精细空间感知能力(PickCube 考察抓取精度、StackCube 考察堆叠空间推理、PushCube 考察推动轨迹规划),ConsisVLA-4D 在三个任务上均达到或接近最优。

RoboTwin 2.0 结果(7 个 ALOHA 双臂任务):

vis0:RoboTwin 2.0 仿真结果

图7:RoboTwin 2.0 基准上的双臂仿真结果。7 个任务涵盖点击闹钟、翻转开关、放瓶子进垃圾桶、打开笔记本、按压订书机、放空杯到杯垫、按 RGB 顺序排列方块等多样化场景,每个任务进行 100 次试验。ConsisVLA-4D(蓝色柱)在所有任务上均取得最优或接近最优的成功率,验证了时空一致性设计对双臂协调操作的普适优势。RoboTwin 2.0 基于 ALOHA 双臂平台,任务需要双臂精确配合,对 3D 空间感知(物体相对位置)和 4D 推理(双臂动作协调的时序同步)都有很高要求。

4.2 效率对比

场景 方法 延迟 ↓ 吞吐 ↑ FLOPs ↓ 训练成本 ↓
仿真 OpenVLA 0.254s 3.9Hz 8.48T 11.7h
仿真 OpenVLA-OFT 0.137s 58.4Hz 8.45T 12.3h
仿真 ConsisVLA-4D 0.110s 72.7Hz 4.59T 8.6h
真机 OpenVLA 0.552s 1.8Hz 16.30T 12.8h
真机 OpenVLA-OFT 0.334s 74.8Hz 14.95T 13.7h
真机 ConsisVLA-4D 0.231s 108.2Hz 9.68T 10.1h

4.3 真机实验

在 Galaxea R1 Lite 和 AgileX Cobot Magic 两个平台,4 个长时序双臂任务(微波炉操作、剥香蕉、抽屉整理、T恤折叠):

方法 Avg. Success(Galaxea) Avg. Success(AgileX)
OpenVLA 28.5% 30.0%
OpenVLA-OFT 51.8% 50.3%
ConsisVLA-4D 70.0% 68.3%

vis1v2:真机长时序任务执行

图8:ConsisVLA-4D 在 Galaxea R1 Lite 平台上执行四个长时序双臂任务的完整过程。每个任务展示 4–5 个关键执行阶段,红色圆圈标注精细夹爪操作。该图验证了时空一致性设计对长时序操作稳定性的关键作用。

任务 1 —— 微波炉操作(3 阶段): 指令:"Put the bread into a bowl and heat it in the microwave."

任务 2 —— 剥香蕉(3 阶段): 指令:"Peel the banana and place it on the plate."

任务 3 —— 抽屉整理(3 阶段): 指令:"Open the drawer, put the toy inside, and then close the drawer."

任务 4 —— T恤折叠(3 阶段): 指令:"Fold the T-shirt."

与数值结果的对应: OpenVLA 在此类长时序任务上成功率仅 28.5%(Galaxea)/ 30.0%(AgileX),OpenVLA-OFT 为 51.8%/50.3%,而 ConsisVLA-4D 达到 70.0%/68.3%。该图直观展示了性能提升的来源——时空一致性使得模型在场景持续变化的长时序操作中能稳定追踪物体状态并生成准确动作。*

4.4 消融实验

消融实验在仿真(LIBERO 四套件)和真机(Galaxea R1 Lite,微波炉操作 + T恤折叠两个任务,每个任务 30 次试验)上进行。

4.4.1 CV-Aligner 与 CO-Fuser 消融

ES-Sel. S-Fus. G-Fus. IG-Agg. LIBERO SR ↑ Real-World SR ↑
✗ ✓ ✓ ✓ 93.9 (−4.2) 71.7 (−6.6)
✓ ✗ ✓ ✓ 95.5 (−2.6) 73.3 (−5.0)
✓ ✓ ✗ ✓ 95.6 (−2.5) 70.0 (−8.3)
✓ ✓ ✓ ✗ 91.7 (−6.4) 68.3 (−10.0)
✓ ✓ ✓ ✓ 98.1 78.3

解读:

4.4.2 CS-Thinker(4D 推理)消融

Dyn.O. Glob.D. Attention LIBERO SR ↑ Real-World SR ↑
✗ ✗ SC-Attn 93.3 (−4.8) 66.7 (−11.6)
✗ ✓ SC-Attn 95.4 (−2.7) 73.3 (−5.0)
✓ ✗ SC-Attn 94.7 (−3.4) 71.6 (−6.7)
✓ ✓ Causal 90.9 (−7.2) 66.7 (−11.6)
✓ ✓ Bidirectional 92.2 (−5.9) 68.3 (−10.0)
✓ ✓ SC-Attn 98.1 78.3

解读:

4.4.3 稀疏比与压缩方法消融

稀疏比 $\mathbf{z}^{\text{obj-3D}}$ $\mathbf{z}^{\text{agg-3D}}$ $\mathbf{0}^{\text{4D}}$ LIBERO SR ↑ Real-World SR ↑
≈1/4 128 128 30 98.0 (−0.1) 80.0 (+0.7)
≈1/8 64 64 18 98.1 78.3
≈1/16 32 32 12 94.9 (−3.2) 68.3 (−10.0)
≈1/8 (FastV†) — — — 88.8 (−9.3) 50.0 (−28.3)
≈1/8 (SliME†) — — — 85.6 (−12.5) 46.7 (−31.6)

解读:


五、关键洞察与技术亮点

  1. 从 3D 到 4D 的范式升级:不仅构建 3D 空间表示,还将时间维度纳入,预测动作后的动态场景变化,实现完整的 4D 时空推理闭环。
  2. 训练-推理解耦的隐式知识设计:CS-Thinker 在训练时学习动态物体和深度的显式预测(有监督信号),推理时只依赖隐式知识,不增加额外推理开销。
  3. 多编码器协同分工:
    • SigLIP → 语义特征(与指令对齐)
    • DINOv2 → 几何特征(自监督的几何一致性)
    • VGGT → 空间特征(深度、点云、点跟踪先验)
  4. 极致的 token 压缩:通过指令相关性筛选 + 几何关系聚合,将视觉 token 压缩到 1/8–1/12,使得即使加了 2B 参数的 VGGT,整体反而更快。
  5. SC-Attn 的定制化注意力:不是简单的 causal 或 bidirectional,而是针对不同类型 token(语义/几何/动态/深度/动作)设计组间因果 + 组内双向的混合注意力。

六、代码实现解读

6.1 整体代码结构

CVPR26-ConsisVLA-4D/
├── prismatic/                    # 核心模型库(基于 OpenVLA 架构进行改造)
│   ├── models/
│   │   ├── vit_wrapper.py        # ★ PrismaticHybridCompressionVisionBackbone — 整个视觉端的主入口
│   │   ├── vggt_dino.py          # ★ VGGTVisionTransformer + VGGTDinoBlock — 自定义 DINOv2 ViT 块
│   │   ├── feats_decoders.py     # ★ DynamicFeatDecoder — 动态物体/深度的解码器(Conv 风格)
│   │   ├── frame_fuser.py        # FrameFuser — SigLIP token 与 VGGT 特征的 cross-attention 融合
│   │   ├── mm_sampler.py         # TextGuidedSampler — ES-Selection 的代码实现
│   │   ├── action_heads.py       # L1RegressionActionHead / DiffusionActionHead
│   │   ├── projectors.py         # ProprioProjector(本体感知)/ NoisyActionProjector(扩散)
│   │   ├── film_vit_wrapper.py   # FiLM 调制的 VisionTransformer Block 包装器
│   │   └── vlms/prismatic.py     # PrismaticVLM — 基座 VLM 的 forward + generate
│   ├── extern/hf/
│   │   └── modeling_prismatic.py # ★ OpenVLAForActionPrediction — HF 风格的完整 forward 逻辑
│   ├── conf/
│   │   ├── vla.py                # VLAConfig — 训练配置注册
│   │   └── models.py             # 模型架构配置
│   └── vla/
│       ├── action_tokenizer.py   # ActionTokenizer — 连续动作离散化/反离散化
│       └── constants.py          # 常量定义(ACTION_DIM, NUM_ACTIONS_CHUNK 等)
├── vggt/                         # ★ VGGT-1B 模型(来自 Meta)
│   ├── models/aggregator.py      # Aggregator — 交替注意力的多帧处理核心
│   ├── models/vggt.py            # VGGT — 顶层模型,组合 Aggregator + Camera/Depth/Point/Track Heads
│   ├── layers/block.py           # Block — VGGT 的基础 Transformer 块(支持 RoPE)
│   ├── heads/dpt_head.py         # DPT 头 — 深度/点云预测
│   └── heads/track_head.py       # TrackHead — 点跟踪预测
├── vla-scripts/finetune.py       # ★ 微调主脚本(训练入口)
└── experiments/robot/libero/     # LIBERO 评估相关脚本

6.2 论文模块 → 代码映射

论文概念 核心代码文件 关键类/函数
CV-Aligner: ES-Selection mm_sampler.py TextGuidedSampler.forward() — 计算 vision-text 余弦相似度 + Top-K 选取
CV-Aligner: FiLM film_vit_wrapper.py, vggt_dino.py FiLMedVisionTransformerBlock + VGGTDinoBlock.scale/shift
CV-Aligner: Single-Fusion frame_fuser.py FrameFuser — Cross-Attn: Q=语义token, K/V=VGGT特征
CO-Fuser: Group-Fusion vggt_dino.py:154-157 VGGTDinoBlock.forward() 中的 (1-α)*x_img + α*vggt_features
CO-Fuser: IG-Aggregation vggt_dino.py:404-407 VGGTVisionTransformer._get_attn_mask() — BC-Attn mask 构造
CO-Fuser: 余弦衰减权重 α_l vggt_dino.py:320-321 VGGTVisionTransformer.__post_init__() 中的 vggt_proportion
CS-Thinker: Dream Queries modeling_prismatic.py:370-376 init_dream_queries() — 可学习的 dynamic/depth token
CS-Thinker: Dyn-4D Decoder feats_decoders.py DynamicFeatDecoder — 从 dream token 解码动态物体图像
CS-Thinker: Dep-4D Decoder feats_decoders.py DynamicFeatDecoder — 从 dream token 解码深度图(img_channel=1)
CS-Thinker: SC-Attn modeling_llama.py, modeling_prismatic.py:546-559 LLM 内部按 token_slices 进行分组注意力
Action Head action_heads.py L1RegressionActionHead.predict_action()
整体 forward modeling_prismatic.py:583-791 OpenVLAForActionPrediction.forward()

6.3 视觉端架构详解

6.3.1 PrismaticHybridCompressionVisionBackbone(vit_wrapper.py:265-474)

这是整个视觉处理的主入口,它在 finetune.py:822-826 被初始化并替换原始 VLA 的 vision_backbone。

初始化流程(__init__):

1. _wrap_dino()    → 将 DINOv2 ViT 的 Block 替换为 VGGTDinoBlock(支持 VGGT 特征融合 + FiLM)
                   → 将 DINOv2 ViT 的 class 替换为 VGGTVisionTransformer(支持 BC-Attn)
                   → 添加 64 个可学习的 aggregator token(aggrator_num=64)


2. _wrap_siglip()  → 将 SigLIP ViT 的 Block 替换为 FiLMedVisionTransformerBlock(支持 FiLM)
                   → 将 SigLIP ViT 的 class 替换为 VisionTransformerRegister


3. 初始化:
      - dino_projector:  Linear(1024→1024+1152→4096)
      - siglip_mm_sampler: TextGuidedSampler(ES-Selection)
      - siglip_frame_fuser: FrameFuser(Single-Fusion: Cross-Attn)
      - siglip_projector: Linear(1152→1024+1152→4096)
      - vggt_encoder: Aggregator(从 VGGT-1B 权重加载)
      - cross_vit_model: CrossVisionTransformerInteractionWrapper(可选,多 ViT 间交互)

关键维度:

6.3.2 VGGT 特征融合过程(forward 中的核心路径)

论文中多视角(M, L, R = 3 视角)的处理流程在代码中对应 num_images_in_input > 1 的分支(vit_wrapper.py:436-474):

# 伪代码:多图像 forward 路径
for each viewpoint (M, L, R):
    # 1) SigLIP 分支
    siglip_patches = siglip_vit(image_siglip, language_embedding)    # 提取语义特征
    siglip_patches = TextGuidedSampler(siglip_patches, instruction)  # ES-Selection(Top-K=32)

    # 2) VGGT 分支(冻结,无梯度)
    with torch.inference_mode():
        vggt_patches_list, _, vggt_output = vggt_encoder(vggt_image)

    # 3) Single-Fusion:语义 token + VGGT 3D 特征
    siglip_patches = FrameFuser(siglip_patches, vggt_output)
    siglip_patches = siglip_projector(siglip_patches)  # → 4096 维

# 4) DINOv2 + VGGT Group-Fusion(跨视角批量处理)
dino_features = dino_vit(all_images_dinov2, 
                         vggt_features=all_vggt_patches,    # Group-Fusion
                         language_embedding)                 # FiLM
dino_features = dino_projector(dino_features)                # → 4096 维

# 5) 拼接输出
output = concat([siglip_patches_all_views, dino_features])

实际 token 数量(以 2 视角为例,论文中仿真使用 Main + Wrist):

6.3.3 论文中的「1/8 压缩比」在代码中的体现

论文说的 1/8 压缩来源于:

代码中 siglip_cfg.num_vision_queries = 32 对应 ES-Selection 的 K=32,dino_cfg.num_aggregator = 64 对应 Aggregator token 数量。

6.4 VGGT 集成细节

6.4.1 VGGT-1B 模型简介(vggt/models/vggt.py)

VGGT(Visual Geometry Grounded Transformer)是一个前馈式大型 Transformer,输入同一场景的多张 RGB 图像,一次性预测:

核心:Aggregator(vggt/models/aggregator.py)

交替注意力机制:在处理多帧图像时,交替执行:

# aggregator.py:238-249 — 交替注意力调度
for _ in range(aa_block_num):
    for attn_type in aa_order:  # ["frame", "global"]
        if attn_type == "frame":
            tokens = process_frame_attention(tokens, ...)   # 帧内
        elif attn_type == "global":
            tokens = process_global_attention(tokens, ...)  # 帧间

输出:每一层交替注意力后的拼接特征 [B, S, P, 2C](frame 特征 + global 特征拼接)。

6.4.2 在 ConsisVLA-4D 中的使用方式

  1. VGGT 仅在训练和推理时作为特征提取器,不参与梯度更新(inference_mode)
  2. 从 HuggingFace 加载预训练权重:ckpts/model.pt(vit_wrapper.py:322)
  3. 从 VGGT 的 aggregator 输出中提取:
    • 多层 patch token 列表:用于 DINOv2 每层的 Group-Fusion(vggt_patches_list,shape: [num_layers, B, num_patches, 1024])
    • 最后一层全局输出:用于 SigLIP 的 Single-Fusion(vggt_output_patches)
# vit_wrapper.py:456-459
with torch.inference_mode():
    vggt_patches_list, token_start_idx, vggt_output_patches = vggt_encoder(vggt_img)
    # vggt_patches_list: [num_layers] × [B*S, P, 2C] → 提取后变为 [num_layers, B, num_patches, D]
    vggt_patches_list = torch.stack(vggt_patches_list, dim=0).squeeze(2)[:, :, token_start_idx:]

6.5 CV-Aligner 实现细节

6.5.1 TextGuidedSampler(mm_sampler.py)—— ES-Selection 的代码实现

class TextGuidedSampler:
    def __init__(self, config):
        self.vision_topk = config.get("num_vision_queries", 32)  # K=32
        self.text_encoder = SiglipTextModel / CLIPTextModel     # 用 SigLIP 的 text encoder
        self.text_post_projector = MLP(text_dim → vision_dim → vision_dim)

    def forward(self, vision_embedding, text_ids):
        # 1) 用 SigLIP Text Encoder 编码指令
        text_embedding = self.text_encoder(text_ids).last_hidden_state
        text_embedding = self.text_post_projector(text_embedding)  # 投影到视觉空间

        # 2) 计算余弦相似度矩阵 [B, N_vision, M_text]
        similarity = cosine_sim(norm(vision_embedding), norm(text_embedding))

        # 3) 训练时加 Gumbel 噪声
        if training:
            similarity += Gumbel_noise(0.1)

        # 4) 按文本维度取平均 → Top-K 选取
        probs = similarity.mean(dim=2)           # [B, N]
        selected_indices = topk(probs, K=32)      # 选取相似度最高的 32 个视觉 token
        return gather(vision_embedding, selected_indices)

与论文公式的对应:

6.5.2 FrameFuser(frame_fuser.py)—— Single-Fusion 的代码实现

class FrameFuser:
    """
    逐帧融合:Q = 语义 token(SigLIP 输出),K/V = VGGT 3D 特征
    config: hidden_size=1152, vggt_hidden_size=1024, num_hidden_layers=4, num_heads=16
    """
    def forward(self, input_features, vggt_features):
        # Q: input_features [B, N_siglip, 1152]
        # K, V: vggt_features [B, N_vggt, 1024]
        for layer in self.layers:  # 4 层 cross-attention
            hidden = self.self_attn(input_features=Q, vggt_features=KV)  # Q 来自语义, KV 来自 VGGT
            hidden = self.mlp(hidden)
            input_features = residual_add(input_features, hidden)
        return self.norm(input_features)

这与论文的描述一致:论文 eq. (9) 描述 Single-Fusion 为逐帧地将 VGGT 的 3D 特征与语义 token 通过 cross-attention 融合,利用 VGGT 预训练的点跟踪能力建立跨视角物体身份关联。

6.5.3 FiLM 调制(论文 eq. 5)

论文公式:$\tilde{\mathbf{z}}_{i,l}^{\text{sem}} = (\mathbf{1}+\gamma(\mathbf{t})) \odot \text{Self-Attn}(\mathbf{z}_{i,l}^{\text{sem}}) + \beta(\mathbf{t})$

在代码中分为两部分实现:

SigLIP 端(film_vit_wrapper.py):

class FiLMedVisionTransformerBlock:
    def forward(self, x, language_embeddings):
        gamma = self.scale(language_embeddings)  # Linear(llm_dim → vision_dim)
        beta = self.shift(language_embeddings)
        x = x + self.attn(self.norm1(x))
        x = x * (1 + gamma) + beta                # FiLM modulation
        x = x + self.mlp(self.norm2(x))

DINOv2 端(vggt_dino.py:159-164):

class VGGTDinoBlock:
    def forward(self, x, vggt_features, vggt_proportion, attn_mask, average_language_embedding):
        # 先融合 VGGT 特征(Group-Fusion)
        x_img = (1 - vggt_proportion) * x_img + vggt_proportion * vggt_features
        x = concat([x_img, x_agg])

        # 再应用 FiLM
        gamma = self.scale(average_language_embedding)
        beta = self.shift(average_language_embedding)
        x = x + self.drop_path(self.ls1(self.attn(self.norm1(x), attn_mask)))
        x = x * (1 + gamma) + beta                 # FiLM modulation
        x = x + self.mlp(self.norm2(x))

6.6 CO-Fuser 实现细节

6.6.1 Group-Fusion —— 所有视角"成组"做逐元素特征混合(论文 eq. 10-11)

Group-Fusion 是什么:将多个视角(M, L, R)的 DINOv2 图像和 VGGT 特征各自拼接成"一组"(Group),然后在 DINOv2 编码器的每一层执行 DINOv2 特征与 VGGT 特征的逐元素加权求和。与 CV-Aligner 的"Single-Fusion"(每视角独立做 Cross-Attn)形成对比——一个成组联合处理(Group),一个逐帧单独处理(Single)。

论文公式:

其中 $\psi=0.2$, $\delta=0.01/0.2=0.05$, $\mathcal{L}'=24$(附录 sec.A)。

"成组"的体现——多视角特征拼接(vit_wrapper.py:465-466):

# 将所有视角的 DINOv2 图像沿 patch 维度拼接成一个"组"
cached_img_regular = torch.cat([img_M, img_L, img_R], dim=1)
# 将所有视角的 VGGT 特征也沿 patch 维度拼接
cached_vggt_patches = torch.cat([vggt_M, vggt_L, vggt_R], dim=-2)
# 拼接后一起送入 DINOv2
patches = dino_vit(cached_img_regular, vggt_features=cached_vggt_patches, ...)

每一层的加权求和(vggt_dino.py:154-156):

# VGGTDinoBlock.forward() 中的核心操作
x_img = (1 - vggt_proportion) * x_img + vggt_proportion * vggt_features
#        ↑ DINOv2 几何特征             ↑ VGGT 3D 空间特征

α_l 的余弦衰减(vggt_dino.py:320-321):

def __post_init__(self, aggrator_num=64):
    # 24 层 DINOv2 ViT,α_l = vggt_proportion[l]
    # 前 12 层:余弦衰减,α 从 0.2 降到 0
    # 后 12 层:α = 0,VGGT 不再参与
    self.vggt_proportion = np.concatenate([
        0.2 - 0.01 * (1 - np.cos(np.linspace(0, np.pi, len(blocks) // 2))) / 2,
        np.zeros(len(blocks) // 2)
    ])

为什么这样设计(附录 sec.A $\alpha_l$ Setting):

Single-Fusion vs Group-Fusion 对比:

Single-Fusion(CV-Aligner) Group-Fusion(CO-Fuser)
处理对象 SigLIP 语义 token + VGGT 3D 特征 DINOv2 几何特征 + VGGT 3D 特征
视角处理 逐帧/逐视角独立 所有视角拼接后一起处理
操作 Cross-Attn(Q=语义, K/V=VGGT) 逐元素加权求和
融合位置 SigLIP 输出后(后融合,4 层额外 Transformer) DINOv2 编码器内部(深层融合,每一层都做)
论文公式 eq. 9 eq. 10-11

6.6.2 IG-Aggregation —— BC-Attn mask(论文 eq. 12-13)

论文描述:$\text{BC-Attn}(\cdot)$ = Block-wise causal self-attention,在 $\mathbf{z}^{\text{geo-3D}}$ 和 $\mathbf{z}^{\text{agg-3D}}$ 之间用 causal attention,各自内部用 bidirectional attention。

代码实现(vggt_dino.py:404-407):

def _get_attn_mask(self, x):
    # x 是 DINOv2 的 patch token
    # aggregator token 与 patch token 之间是 causal(aggregator 不能看 patch)
    mask = torch.zeros([B, N_patch + N_agg, N_patch + N_agg])
    mask[:, :N_patch, N_patch:] = -inf  # patch token 不能 attend aggregator token
    return mask

注意:这里的 BC-Attn mask 设计是 patch token 不能看到 aggregator token,而 aggregator token 可以看到 patch token。这确保 aggregator 可以聚合全局几何信息,而其自身的因果关系(论文中的"causal attention between groups")则通过 LLM 内部的序列排序来保证。

6.7 CS-Thinker 实现细节

6.7.1 Dream Queries 初始化(modeling_prismatic.py:370-376)

def init_dream_queries(self, num_images_in_input):
    # dynamic queries: 每个视角一组,共 num_images_in_input 组
    # depth queries: 所有视角共用一组
    self.dream_queries = nn.Parameter(torch.rand([
        num_dream_queries_per_image * (num_images_in_input + 1),  # +1 是 depth queries
        llm_dim  # 4096
    ]))

这些 dream queries 是论文中 $\mathbf{0}_{i}^{\text{dyn-4D}}$(每视角独立初始化)和 $\mathbf{0}^{\text{dep-4D}}$(所有视角共用)的代码实现。

在 forward 时,dream queries 被拼接到 multimodal embedding 序列的特定位置(靠近 action token),由 LLM 的 SC-Attn 进行处理。

6.7.2 DynamicFeatDecoder(feats_decoders.py)—— 动态物体和深度解码

class DynamicFeatDecoder:
    """
    将 LLM 处理后的 dream token 解码为图像/深度图

    配置(动态物体预测):

        - num_tokens_per_img = num_dream_queries_per_image (输入 Latent token 数)
        - input_dim = 4096 (LLM hidden dim)
        - decoder_hidden_dim = 1024
        - decoder_depth = 8 (8 层 Transformer Block)
        - pred_num = 1 (只预测固定视角)
        - img_channel = 3 (输出 RGB)
        - patch_size = 16 (与 ViT 一致)

    配置(深度预测):

        - pred_num = num_images_in_input (预测所有视角)
        - img_channel = 1 (输出单通道深度)
    """

    def forward(self, latent):
        # latent: LLM 最后一层的 dream token hidden states [B, num_tokens, 4096]
        # 1) 投影到 decoder 空间
        x = self.projector(latent)  # [B, num_tokens, 1024]

        # 2) 与可学习的 mask_token + position embedding 拼接
        mask_tokens = self.mask_token.repeat(B, num_mask_token, 1)
        x = concat([x, mask_tokens]) + position_embedding

        # 3) 经过 8 层 ViT Block 解码
        for blk in self.decoder:  # 8 个 Block(dim=1024, heads=16)
            x = blk(x)

        # 4) 预测输出
        pred = self.pred_head(x[:, -num_mask_token:])  # [B, num_mask_token, patch_size²*C*pred_num]
        pred = pred.reshape(B, 1, H/patch, W/patch, ...)  # 重排为图像空间
        return pred

与论文的对应:

6.7.3 SC-Attn 的 token 排列

在 modeling_prismatic.py:546-559 中,token 被整齐地切分为:

[<BOS> | SigLIP tokens (64) | DINOv2 tokens (64) | text tokens | dynamic queries (2*N) | depth queries (N) | action tokens | <EOS>]

其中 token_slices 记录了每个分段的起止位置,LLM 内部可以根据这些 slice 应用不同的注意力模式(组间 causal / 组内 bidirectional)。

6.8 训练流程详解

6.8.1 训练入口(vla-scripts/finetune.py)

# 关键训练参数(scripts/finetune_libero.sh)
batch_size = 16          # 每卡 batch size,4 卡共 64
learning_rate = 5e-4
max_steps = 80000
lora_rank = 32
num_images_in_input = 2  # Main + Wrist 双视角
use_proprio = True        # 使用本体感知
use_l1_regression = True  # L1 回归预测动作
image_aug = True

6.8.2 完整 forward 流程

Input → vision_backbone (PrismaticHybridCompressionVisionBackbone)
      │
      ├─ SigLIP 分支(每个视角独立)
      │   ├─ SigLIP ViT + FiLM → 语义 patch tokens
      │   ├─ TextGuidedSampler → ES-Selection(256→32 token)
      │   ├─ FrameFuser (Cross-Attn) → Single-Fusion with VGGT
      │   └─ siglip_projector → 4096 维
      │
      ├─ VGGT 分支(冻结,无梯度)
      │   └─ Aggregator → 多层 3D patch tokens + final output
      │
      ├─ DINOv2 分支(所有视角合并处理)
      │   ├─ VGGTVisionTransformer → 每层 Group-Fusion + FiLM + BC-Attn
      │   └─ dino_projector → 4096 维
      │
      └─ 拼接:SigLIP + DINOv2 → projected_patch_embeddings
            │
            ↓
      LLM forward (LlamaForCausalLM with SC-Attn)
            │
            ├─ Dream Queries (dynamic + depth) 参与 attention
            │
            ├─ Action tokens → L1RegressionActionHead → 动作预测
            ├─ Dynamic dream tokens → DynamicFeatDecoder → 动态物体预测
            └─ Depth dream tokens → DynamicFeatDecoder → 深度预测
            │
            ↓
      Loss = L1(action) + MSE(dynamic) + MSE(depth)

6.8.3 训练策略

# finetune.py:675-708 — set_full_trainable()
# 训练时冻结 VGGT encoder,只训练以下组件:

# 1) LoRA 微调 LLM(target_modules 排除 projector/frame_fuser/text_encoder/vggt/decoder)
# 2) 全参数训练:
#    - siglip_frame_fuser(4 层 Cross-Attn)
#    - siglip_projector(MLP: 1152 → 4096)
#    - dino_projector(MLP: 1024 → 4096)
#    - future_dynamic_decoder(8 层 ViT Decoder)
#    - future_depth_decoder(8 层 ViT Decoder)
# 3) DINOv2 ViT 的 FiLM scale/shift 参数
# 4) SigLIP ViT 的 FiLM scale/shift 参数
# 5) DINOv2 ViT 的 aggregator token(可学习的聚合 token)
# 6) dream_queries(可学习的 dynamic/depth token)

6.8.4 训练 vs 推理的差异

维度 训练 推理
VGGT inference_mode,冻结 inference_mode,冻结
Dynamic/Depth Decoder 前向 + 计算损失(用 CoTracker/Depth-Anything 监督) 不执行,仅依赖隐式知识
Dream Queries 更新参数 前向传播但不解码
FiLM scale/shift 更新 冻结(LoRA)
视觉 token 压缩 ES-Selection + IG-Aggregation 同训练
动作预测 L1 回归(L1RegressionActionHead) 同训练方式

6.9 关键技术细节补充

6.9.1 Action Tokenizer(prismatic/vla/action_tokenizer.py)

与 OpenVLA 一致,将连续动作离散化为 256 个 bin,使用 LLM 词汇表末尾的 256 个 token:

class ActionTokenizer:
    def __init__(self, tokenizer, bins=256, min_action=-1, max_action=1):
        self.bins = np.linspace(-1, 1, 256)
        self.bin_centers = (self.bins[:-1] + self.bins[1:]) / 2.0
        self.action_token_begin_idx = tokenizer.vocab_size - 257

当使用 L1 回归时(use_l1_regression=True),该 tokenizer 在训练时不直接使用;动作通过 L1RegressionActionHead 直接从 LLM 最后一层的 hidden states 预测,不使用离散化损失。

6.9.2 动作预测头

class L1RegressionActionHead:
    """
    input_dim = 4096 (LLM hidden dim)
    hidden_dim = 4096
    action_dim = 7 (LIBERO: 6DoF pose + 1 gripper)
    NUM_ACTIONS_CHUNK = 25 (真机) / 8 (仿真)
    """
    def predict_action(self, actions_hidden_states):
        # actions_hidden_states: [B, chunk_len * action_dim, 4096]
        # → reshape → [B, chunk_len, action_dim * 4096]
        # → MLPResNet(2 blocks) → [B, action_dim]
        return self.model(rearranged_hidden_states)

6.9.3 多编码器的像素空间对齐

三个编码器的输入图像分辨率可能不同,在 vit_wrapper.py 的 forward 中处理:

代码中 pixel_values 被组织为 [B, 6 * num_images, H, W],其中每 6 个 channel 对应一个视角(前 3 个 channel → SigLIP,后 3 个 channel → DINOv2)。

6.9.4 各模型的 Embedding 维度汇总

组件 维度 说明
SigLIP ViT 1152 So400m 模型,224×224
DINOv2 ViT-L 1024 14×14 patch,224×224
VGGT-1B Aggregator 1024 与 DINOv2 对齐
Llama-2 7B 4096 LLM hidden dim
SigLIP → LLM Projector 1152 → 2176 → 4096 2 层 MLP
DINOv2 → LLM Projector 1024 → 2176 → 4096 2 层 MLP
FrameFuser Q:1152, K/V:1024 4 层 Cross-Attn
DynamicFeatDecoder 4096 → 1024 8 层 ViT Block
ProprioProjector 14 → 4096 → 4096 2 层 MLP
L1RegressionActionHead 4096*7=28672 → 4096 → 7 MLPResNet (2 blocks)

6.10 数据集构造与监督信号构建

6.10.1 数据管线总览

整个数据管线基于 OpenVLA 的 RLDS(Reinforcement Learning Datasets)框架,分为以下几个阶段:

原始 HDF5 数据 → HDF5 再生(消除 no-op) → RLDS 格式转换 → TFDS 数据加载 → 轨迹变换 → 帧变换 → PyTorch Batch

核心代码路径:

6.10.2 LIBERO 原始数据再生(regenerate_libero_dataset.py)

目的:LIBERO 官方提供的 HDF5 演示数据有以下问题——

  1. 包含大量 "no-op" 动作(机器人不移动,仅维持状态),对训练无益
  2. 图像分辨率为 128×128,需要升采样到 256×256
  3. 部分演示不成功(任务未完成),需要过滤

处理流程:

# 1) 在 LIBERO 仿真环境中重放原始动作序列
for action in orig_actions:
    # 2) 过滤 no-op 动作(动作幅度 < 1e-4 且 gripper 不变)
    if is_noop(action, prev_action):
        continue  # 跳过
    # 3) 执行有效动作,以 256×256 分辨率记录观测
    obs, reward, done, info = env.step(action.tolist())
    agentview_images.append(obs["agentview_image"])      # 主视角
    eye_in_hand_images.append(obs["robot0_eye_in_hand_image"])  # 腕部视角

# 4) 仅保存成功(done=True)的轨迹
if done:
    save_to_hdf5(states, actions, agentview_images, eye_in_hand_images, ...)

no-op 判定逻辑(regenerate_libero_dataset.py:46-68):

def is_noop(action, prev_action=None, threshold=1e-4):
    # 条件1:除 gripper 外的所有动作维度接近零
    # 条件2:gripper 动作与上一时间步相同
    return np.linalg.norm(action[:-1]) < threshold and gripper_action == prev_gripper_action

6.10.3 LIBERO 标准化变换(transforms.py:827-841)

在 RLDS 加载时,通过 libero_dataset_transform 对每条轨迹进行标准化处理:

def libero_dataset_transform(trajectory):
    # 1) Gripper 动作处理:原为 -1(open)...1(close) → clip 到 0...1 → flip 为 +1=open, 0=close
    gripper_action = trajectory["action"][:, -1:]
    gripper_action = invert_gripper_actions(tf.clip_by_value(gripper_action, 0, 1))

    # 2) 拼接:前 6 维(EEF delta XYZ + RPY)+ 后 1 维(gripper)
    trajectory["action"] = tf.concat([trajectory["action"][:, :6], gripper_action], axis=1)

    # 3) 从 state 中分离 EEF 状态和 gripper 状态
    trajectory["observation"]["EEF_state"] = trajectory["observation"]["state"][:, :6]
    trajectory["observation"]["gripper_state"] = trajectory["observation"]["state"][:, -2:]

6.10.4 动作归一化与统计信息计算(data_utils.py)

在 RLDS 加载过程中,自动计算或加载数据集的统计信息,用于动作归一化:

# LIBERO 使用 BOUNDS_Q99 归一化方式
# 对每个动作维度,计算 [q01, q99] 分位数,归一化到 [-1, 1]
normalized_action = 2 * (action - q01) / (q99 - q01 + 1e-8) - 1
# 最终 clip 到 [-1, 1]
normalized_action = tf.clip_by_value(normalized_action, -1, 1)

统计信息内容(get_dataset_statistics 函数):

统计信息会被缓存到 dataset_statistics_{hash}.json,便于后续复用。

6.10.5 RLDSBatchTransform:RLDS → PyTorch Batch(datasets.py:33-197)

这是 RLDS 管线和 PyTorch 训练之间的桥梁函数,__call__ 方法将单个 RLDS batch 转换为训练所需的 PyTorch 张量:

def __call__(self, rlds_batch):
    # === 1) Prompt 构造与 Tokenization ===
    instruction = "What action should the robot take to {language_instruction}?"
    # 将当前动作 + 未来动作 chunk 用 ActionTokenizer 离散化
    action_chunk_string = action_tokenizer(current_action) + action_tokenizer(future_actions)
    # 构造对话:Human: instruction, GPT: action_chunk_string
    prompt = f"Human: {instruction}\nAssistant: {action_chunk_string}"
    input_ids = tokenizer(prompt)

    # === 2) 图像预处理 ===
    pixel_values = image_transform(primary_image)       # SigLIP + DINOv2 共享
    vggt_pixel_values = preprocess_vggt(primary_image)   # VGGT 专用(224×224)

    # === 3) 标签构造(关键:控制损失计算范围)===
    labels = list(input_ids)
    labels[: -(action_chunk_len + 1)] = IGNORE_INDEX     # ❗ 仅动作 token 计算损失

    # === 4) 腕部图像(多视角)===
    if use_wrist_image:
        pixel_values_wrist = image_transform(wrist_image)
        vggt_pixel_values_wrist = preprocess_vggt(wrist_image)

    # === 5) 未来动态物体监督信号 ===
    if use_dynamic:
        future_image = rlds_batch["observation"]["image_future"]       # 动作后的场景图像
        future_dynamic_mask = interpolate(rlds_batch["future_dynamic_mask"], 224×224)  # 动态物体 mask

    # === 6) 未来深度监督信号 ===
    if use_depth:
        future_depth = interpolate(rlds_batch["observation"]["future_depth"], 224×224)
        future_wrist_depth = interpolate(rlds_batch["observation"]["future_wrist_depth"], 224×224)

    # === 7) 原始图像(用于 Decoder 监督)===
    image_primary = rlds_batch["observation"]["image_primary"]  # 当前主视角图像
    image_wrist = rlds_batch["observation"]["image_wrist"]      # 当前腕部图像

    # === 8) 损失权重 ===
    compute_future_loss = rlds_batch["observation"]["compute_future_loss"]  # 是否计算未来损失

6.10.6 三条监督信号的 GT 来源与构建

ConsisVLA-4D 的三条损失中,后两条($\mathcal{L}_{\text{dyn-4D}}$ 和 $\mathcal{L}_{\text{dep-4D}}$)的 GT 标签是离线预计算并直接存储在 RLDS 数据集中的。训练时 dataloader 只是读取这些预存的标签,不涉及 CoTracker 或 Depth-Anything 的在线推理。

(1)$\mathcal{L}_{\text{action}}$ —— 动作预测损失(L1 回归)

GT 来源:人类遥操作采集的专家动作序列,存储在 RLDS 数据的 action 字段。

论文公式(eq. 17):$\mathcal{L}_{\text{total}} = \mathcal{L}_{\text{action}} + \mathcal{L}_{\text{dyn-4D}} + \mathcal{L}_{\text{dep-4D}}$

代码中的 $\mathcal{L}_{\text{action}}$ 计算(finetune.py:420-428):

# 从 LLM 最后一层 hidden states 提取动作 token 对应的 hidden states
actions_hidden_states = text_hidden_states[current_action_mask | next_actions_mask]
actions_hidden_states = actions_hidden_states.reshape(B, NUM_ACTIONS_CHUNK * ACTION_DIM, -1)

# 通过 L1RegressionActionHead 预测动作 → 与 GT action 计算 L1
predicted_actions = action_head.predict_action(actions_hidden_states)
action_loss = L1Loss(ground_truth_actions, predicted_actions)
(2)$\mathcal{L}_{\text{dyn-4D}}$ —— 动态物体重建损失(CoTracker 监督)

论文描述(sec. 4.3, eq. 14 + Fig. 4d caption):

$$\mathcal{L}_{\text{dyn-4D}} = \| (\hat{\mathbf{z}}_{i^*}^{\text{dyn-4D}} \odot \mathbf{m}_{i^*}^{\text{obj-3D}}) - ({\mathbf{z}}_{i^*}^{\text{dyn-4D}} \odot \mathbf{m}_{i^*}^{\text{obj-3D}}) \|_2^2$$

GT 如何生成(离线预处理流水线,不在开源代码中):

  1. 对于轨迹中的每一帧 $t$,取当前观测 $O_t$ 和动作执行后的未来帧 $O_{t+k}$(间隔 $k$ 由动作 chunk 长度决定)
  2. 用 CoTracker 在 $(O_t, O_{t+k})$ 图像对上运行点跟踪,识别哪些像素/物体发生了运动
  3. CoTracker 输出的运动区域被二值化为 future_dynamic_mask(1 = 该像素属于运动物体,0 = 背景/静止区域)
  4. 未来帧 $O_{t+k}$ 本身存储为 future_image
  5. 这些标签写入 RLDS 数据集的每条轨迹中(作为 observation 的额外字段)

训练时的数据流(代码中只是读取,不运行 CoTracker):

# datasets.py:120-124 — 从 RLDS 直接读取预存的 GT
future_image = rlds_batch["observation"]["image_future"]         # 动作后真实图像
future_dynamic_mask = rlds_batch["observation"]["future_dynamic_mask"]  # CoTracker 生成的运动 mask
# modeling_prismatic.py:729-741 + feats_decoders.py:231-255 — 计算损失
dynamic_dream_hidden = last_hidden_states[:, dynamic_dream_start:dynamic_dream_end, :]
# 每个视角的 dynamic dream token 独立解码 → 预测固定视角的动作后动态物体图像
for each viewpoint:
    predicted_image = DynamicFeatDecoder(latent)  # 从 dream token 解码图像
    # Masked MSE:只对 CoTracker 标注的动态区域计算损失
    loss_dyn += MSE(predicted_image * future_dynamic_mask, 
                    future_image * future_dynamic_mask)

与论文公式的对应关系:

(3)$\mathcal{L}_{\text{dep-4D}}$ —— 全局深度重建损失(Depth-Anything 监督)

论文描述(sec. 4.3, eq. 15-16 + Fig. 4d caption):

$$\mathcal{L}_{\text{dep-4D}} = \sum_{i=1}^{N_i} \| \hat{\mathbf{z}}_{i}^{\text{dep-4D}} - {\mathbf{z}}_{i}^{\text{dep-4D}} \|_2^2$$

GT 如何生成(离线预处理流水线,不在开源代码中):

  1. 对于轨迹中的每一帧 $t$,取动作执行后的未来帧 $O_{t+k}$
  2. 用 Depth-Anything 模型对 $O_{t+k}$ 进行单目深度估计,生成深度图
  3. 对每个视角(Main、Wrist 等)各自运行 Depth-Anything
  4. 生成的深度图存储为 future_depth、future_wrist_depth 等字段
  5. 写入 RLDS 数据集的每条轨迹中

训练时的数据流(代码中只是读取,不运行 Depth-Anything):

# datasets.py:126 — 从 RLDS 直接读取预存的 GT
future_depth = rlds_batch["observation"]["future_depth"]  # Depth-Anything 生成的深度图
# modeling_prismatic.py:743-756 + feats_decoders.py:209-229 — 计算损失
depth_dream_hidden = last_hidden_states[:, depth_dream_start:depth_dream_end, :]
# 一组 depth dream token 同时解码所有视角的深度图
predicted_depths = DynamicFeatDecoder(depth_dream_hidden)  # pred_num=num_views, img_channel=1
# 逐视角计算 MSE
for each viewpoint:
    loss_dep += MSE(predicted_depth[viewpoint], future_depth[viewpoint])

与论文公式的对应关系:

三条损失的 GT 来源总结
损失 论文公式 模型预测 GT 标签 GT 来源
$\mathcal{L}_{\text{action}}$ eq. 17 L1RegressionActionHead(dream token) actions(7 维动作向量) 人类遥操作采集的专家数据
$\mathcal{L}_{\text{dyn-4D}}$ eq. 14 DynamicFeatDecoder(dynamic dream token) → 动作后图像 future_image + future_dynamic_mask CoTracker 离线处理:在 (当前帧, 未来帧) 对上运行点跟踪,产生运动物体 mask
$\mathcal{L}_{\text{dep-4D}}$ eq. 15 DynamicFeatDecoder(depth dream token) → 深度图 future_depth, future_wrist_depth Depth-Anything 离线处理:对动作后的未来帧进行单目深度估计

关键理解:CoTracker 和 Depth-Anything 不是训练循环的一部分,它们只在数据预处理阶段运行一次,将生成的 GT 标签写入 RLDS 数据集。训练时,dataloader 直接从 RLDS 数据中读取这些预存的 GT 标签。在目前已开源的代码中,这个离线预处理脚本并未包含在内(future_* 字段被假定为已存在于 RLDS 数据中)。

6.10.7 数据增强策略

在 RLDSDataset.__init__ 中配置(datasets.py:254-268):

if image_aug:
    image_augment_kwargs = dict(
        random_resized_crop=dict(scale=[0.9, 0.9], ratio=[1.0, 1.0]),  # 轻微缩放裁剪
        random_brightness=[0.2],
        random_contrast=[0.8, 1.2],
        random_saturation=[0.8, 1.2],
        random_hue=[0.05],
        augment_order=["random_resized_crop", "random_brightness", 
                       "random_contrast", "random_saturation", "random_hue"],
    )

增强在 TFDS 管线中通过 dlimp.transforms.augment_image 执行,使用相同种子保证不同视角的图像增强一致。

6.10.8 轨迹变换管线的完整流程

apply_trajectory_transforms(dataset.py:269-366)按顺序执行以下变换:

1. skip_unlabeled         → 过滤无语言标签的轨迹
2. max_action / max_proprio → 过滤异常值轨迹
3. add_pad_mask_dict      → 标记各观测的填充状态
4. goal_relabeling        → 目标重标记(LIBERO 使用 "uniform" 策略)
5. task_augmentation      → 任务增强(训练时可选,如随机丢弃部分观测键)
6. chunk_act_obs          → 动作分块(window_size=1, future_action_window_size=NUM_ACTIONS_CHUNK-1)
7. subsample              → 轨迹下采样(若轨迹过长)

动作分块(chunk_act_obs):以当前帧为中心,取前 window_size-1 帧到后 future_action_window_size 帧的动作组成动作块:

6.10.9 完整的数据流总结

LIBERO 仿真器/HDF5 原始数据
        │
        ▼
regenerate_libero_dataset.py  ← 重放演示,过滤 no-op 和失败轨迹
        │
        ▼
RLDS TFDS 格式(含 image, depth, track, future 等键)
        │
        ▼
make_dataset_from_rlds()  ← 加载 + libero_dataset_transform + 归一化
        │
        ▼
apply_trajectory_transforms()  ← 过滤、分块、下采样
        │
        ▼
apply_frame_transforms()  ← 解码、resize、数据增强
        │
        ▼
RLDSBatchTransform.__call__()  ← 构造 prompt、tokenize、组织 PyTorch 张量
        │
        ▼
PaddedCollatorForActionPrediction  ← Padding 到 batch 内统一长度
        │
        ▼
Training Loop(finetune.py)
        │
        ├── LLM forward → hidden states
        ├── Action Head → L1(action, gt_action)          [L_action]
        ├── Dynamic Decoder → MSE(pred_image, future_image * mask)   [L_dyn-4D]
        └── Depth Decoder → MSE(pred_depth, future_depth)  [L_dep-4D]

七、局限性与思考


八、总结

ConsisVLA-4D 是一篇高质量的 VLA 改进工作。核心贡献在于:

  1. 范式升级:通过 CV-Aligner、CO-Fuser、CS-Thinker 三个模块的协同设计,在统一框架内实现了从 2D 观测 → 3D 感知 → 4D 推理的完整闭环
  2. 反直觉的效率:通过极致 token 压缩(1/8)实现"参数更多但速度更快"——推理延迟 2.3× 提升,吞吐量 2.4× 提升
  3. 训练-推理解耦:CS-Thinker 在训练时学习显式的动态物体和深度预测(隐式知识),推理时不增加额外开销
  4. SOTA 性能:在 LIBERO(98.1% avg.)、ManiSkill2(94.3% avg.)、RoboTwin 2.0 以及真机平台(~69% avg.)上均取得最优结果