| # T-Rex Track-Force:16-step、20 Hz 动作与 5 Hz 触觉模型 |
|
|
| 本文描述独立的 `trex_track_force` 模型、数据契约、embedding、注意力、两阶段 |
| flow matching、训练和在线执行。它不会修改原 DreamZero 的模型调用路径。 |
|
|
| ## 1. 固定时序契约 |
|
|
| - 每个 action chunk 含 **16 个动作**,动作频率为 **20 Hz**。 |
| - 一个 chunk 覆盖 `16 / 20 = 0.8 s`;动作时间戳跨度为 `0.75 s`。 |
| - 触觉频率为 **5 Hz**,因此每 **4 个动作步**刷新一次。 |
| - 一个 chunk 内的触觉刷新 offset 固定为 `[0, 4, 8, 12]`。 |
| - 每次刷新只重新去噪尚未执行的 action suffix;已经执行的前缀保持不变。 |
| - `max_chunk_size=4` 保留 DreamZero 原有的 autoregressive block memory。 |
| 这里的 `max_chunk_size` 是跨 block 的记忆窗口,不是单个 action chunk 的长度。 |
| - 一个训练 sample 仍由 4 个 autoregressive blocks 组成,因此共有 64 个动作; |
| 只有每 block 的 action 长度从 24 改为 16。 |
| - 三路视频保持 DreamZero 的 33 帧布局:1 帧 clean conditioning observation, |
| 后接每 block 8 帧、共 32 帧 10 Hz future-video targets。 |
| - 每个 block 都有 16 帧过去 track 和 16 步 target track;target 的第 0 步就是 |
| 当前 anchor 帧,与 OpenPI 的 track window 契约一致。 |
|
|
| ## 2. 总体架构 |
|
|
| ```mermaid |
| flowchart LR |
| V["三视角 RGB\nhead + left wrist + right wrist"] --> GRID["三视角拼图\n160 × 320"] |
| GRID --> VAE["Wan2.2 VAE38\n48-channel latent"] |
| GRID --> CLIP["Wan CLIP\n图像条件"] |
| TXT["任务文本"] --> T5["UMT5\n文本条件"] |
| |
| TRK["250 点 track\npast clean + future noisy"] --> TE["TrackEncoder\n几何 + 可见性 + 身份 embedding"] |
| ACT["16 × 62D delta-base action\n补零到 64D"] --> AE["Action encoder"] |
| ST["62D 当前 EEF/hand state"] --> SE["State encoder"] |
| |
| VAE --> WAN["CausalWanTrackForceModel\nWan2.2-TI2V-5B · 30 blocks"] |
| CLIP --> WAN |
| T5 --> WAN |
| TE --> WAN |
| AE --> WAN |
| SE --> WAN |
| |
| WAN --> COARSE["τ: 1 → 0.4\n6-step coarse action"] |
| WAN --> TFLOW["未来 track flow"] |
| WAN --> VFLOW["未来 video flow"] |
| WAN --> MEM["τ=0.4 coarse memory"] |
| |
| RAW["10 fingers × 6D force\n16-sample history"] --> VQ["Per-finger force VQ-VAE\n64 codes · 256D"] |
| FRESH["当前 10 × 6D force"] --> FT["Force-only Transformer\n6 layers · width 768"] |
| VQ --> FT |
| MEM --> FT |
| COARSE --> FT |
| FT --> FINE["τ: 0.4 → 0\n4-step tactile action suffix"] |
| ``` |
|
|
| 粗阶段负责从视觉、状态、语言和 track 中得到动作的大尺度结构;独立的 |
| force-only transformer 只使用力信号和粗阶段 memory 完成剩余去噪,使触觉 |
| 负责接触后的细粒度修正。 |
|
|
| ## 3. 62DoF 动作空间 |
|
|
| 每只手臂占 31 维,两侧共 62 维: |
|
|
| - 手腕/末端位姿:`xyz + rotation-6D`,共 9 维。 |
| - 手部关节目标:22 维。 |
|
|
| 训练动作不是世界坐标绝对位姿。loader 以 chunk 起始状态为 reference,把每个 |
| 目标手腕位姿转换为 **delta-base**: |
|
|
| - 平移在 chunk 起始手腕坐标系中表示。 |
| - 旋转为 `R_reference^-1 × R_target`,再编码为 rotation-6D。 |
| - 手部 22 维仍是绝对目标。 |
|
|
| 模型内部把 62 个物理维补零为 64 维;采样的两个 padding 维在初始化和每个 |
| Euler step 后都会重新置零。输出先按 relative-action 统计量反归一化,再用 |
| chunk 起始 state 恢复绝对 EEF 位姿。 |
|
|
| ## 4. Track 数据和点身份 |
|
|
| 250 个点具有固定、可验证的顺序: |
|
|
| - `0:50`:头部视角左手及手臂。 |
| - `50:100`:头部视角右手及手臂。 |
| - `100:125`:左腕视角 5×5 背景点。 |
| - `125:175`:左腕视角手掌点。 |
| - `175:200`:右腕视角 5×5 背景点。 |
| - `200:250`:右腕视角手掌点。 |
|
|
| SAM2 只在 episode 第 0 帧根据固定 prompt 产生手/臂 mask;点从 mask 中采样, |
| 随后由 CoTracker 跟踪整段视频。保存字段为: |
|
|
| - `observation.track_xy`:`[250, 2]`,坐标归一化到 `[0, 1]`。 |
| - `observation.track_visibility`:`[250]`。 |
| - metadata 中同时记录 view、hand、role、point index 和各 segment 边界。 |
|
|
| ## 5. Embedding 设计 |
|
|
| ### 5.1 Video、语言和图像条件 |
|
|
| - 三视角拼入 2×2 canvas 的三个有效格,再统一缩放到 `160×320`。 |
| - Wan2.2 VAE38 生成 48-channel latent。 |
| - `patch_size=[1,2,2]`,每 latent frame 形成 50 个 video tokens。 |
| - video token 使用 Wan 原生 3D RoPE,编码时间、高度和宽度。 |
| - UMT5 输出 4096D 文本条件。 |
| - Wan CLIP 输出 1280D 图像条件,并经 `img_emb` 投影。 |
| - CLIP/T5 cross-attention 只作用于 observation/action query,不向 track query |
| 泄漏视觉或语言信息。 |
|
|
| ### 5.2 Action 和 state |
|
|
| - action:每个 62D 动作补零到 64D,经线性层投影到 Wan hidden dim 3072。 |
| - 一个 block 有 16 个 action tokens;位置由 1D action RoPE 编码。 |
| - state:62D 当前状态补到 64D,经线性层得到一个 state token。 |
| - flow 时间 `τ` 经 sinusoidal embedding 和 MLP 后参与 Wan modulation。 |
|
|
| ### 5.3 TrackEncoder |
|
|
| 每个点分别产生一个 `past token` 和一个 `future token`。时间序列输入特征为: |
|
|
| ```text |
| [x, y, visibility, Δx, Δy] |
| ``` |
|
|
| 不可见时刻的坐标和 motion 先清零,随后完整的 `16×5` 时序按固定顺序展平,再经 |
| `Linear → SiLU → Linear` 投影。不能在时间维求平均:future flow 的每个时刻含有 |
| 独立噪声,平均会丢失“哪一个噪声属于哪一个 timestep”,使 16-step 重建不可解。 |
|
|
| 最终每个点 token 是下列 embedding 的和,再经 LayerNorm: |
|
|
| ```text |
| trajectory |
| + view(head / left_wrist / right_wrist) |
| + hand(none / left / right) |
| + role(head_hand / wrist_background / wrist_hand) |
| + canonical point id(0...249) |
| + autoregressive block id |
| + temporal role(past / future) |
| ``` |
|
|
| 左右手不只通过点序号区分,还具有显式 hand embedding。背景点使用 |
| `hand=none` 和独立 role embedding。 |
|
|
| 与 OpenPI 一致,track target 始终是 `[0,1]` 内的绝对归一化 XY;target window |
| 从当前帧开始,因此第 0 步等于 GT anchor。`TrackDecoder` 对 250 个 |
| future-track hidden token 分别执行 `LayerNorm → Linear(16×2)`,预测 CFM |
| velocity `noise-clean_xy`。Euler 积分后的结果已经是绝对坐标,不做空间 |
| `cumsum`,也不再额外加 anchor。 |
|
|
| ### 5.4 Force-only VQ-VAE |
|
|
| 输入只包含 force/wrench,不包含 deformation map: |
|
|
| ```text |
| [batch, history=16, fingers=10, wrench=6] |
| ``` |
|
|
| - 两只手共享同一套时序卷积 encoder/decoder。 |
| - 每只手内部加入 5 个 finger identity embeddings。 |
| - 输出每根手指一个 256D latent,共 10 个 tactile history tokens。 |
| - EMA codebook 大小为 64,带 commitment loss、perplexity 统计和 dead-code |
| revival。 |
| - 左右手通过 side embedding 区分,手指位置通过 finger embedding 区分。 |
| - 训练可直接输入 raw 16-step history;推理也可输入预计算的 10 个离散 codes。 |
| - episode 前缀缺失的历史填为归一化中性值,并用 validity mask 从 VQ 重建 |
| loss 中排除 padding 步。 |
|
|
| ### 5.5 Force-only Transformer |
|
|
| 输入 token 包括: |
|
|
| - 16 个 noisy action tokens。 |
| - 10 个当前 force tokens。 |
| - 10 个 VQ history tokens。 |
| - 从 Wan `τ=0.4` hidden state 提取的 coarse memory tokens。 |
|
|
| action token 叠加 action position、5Hz force slot、token type、当前 refresh |
| offset 和 `τ` embedding。force/history token叠加 finger position、token type 和 |
| refresh offset。独立 transformer 为 6 层、12 heads、hidden dim 768。 |
|
|
| ## 6. 非对称 attention 契约 |
|
|
| 每个 autoregressive block 的 packed 顺序为: |
|
|
| ```text |
| [obs, action, state, track_past, track_future] |
| ``` |
|
|
| 其可见性严格为: |
|
|
| - observation query 和 action query 在同一 block 内互相可见。 |
| - observation/action query 可读取同 block 的 state、past track 和 noisy future |
| track,也可读取有限 AR 窗口内的历史 obs/action/track。 |
| - state query 只读取自己的 state token,避免把多模态信息反向带给 track。 |
| - past-track query 读取历史 block 的 track 和当前 past track,但不能读取当前 |
| noisy future track。 |
| - future-track query 只读取当前及历史 track。 |
| - **任何 track query 都不能读取 observation、action、state、CLIP 或文本。** |
| - 任何 query 都不能读取未来 block。 |
|
|
| 因此实现了要求的方向性:`obs/action → track` 表示 obs/action 可以把 track |
| 作为条件;反方向被 mask 禁止。 |
|
|
| ## 7. 两阶段 flow matching |
|
|
| 使用线性插值: |
|
|
| ```text |
| x_τ = τ · noise + (1 - τ) · clean |
| target flow = noise - clean |
| x_next = x_τ + (τ_next - τ) · predicted_flow |
| ``` |
|
|
| ### 粗阶段:Wan,`τ=1 → 0.4` |
|
|
| - 训练时 action/Wan expert 遵循原始 T-Rex,在完整 `(0,1]` 上按 |
| `Beta(1.5,1.0)` 采样;其均值约为 `0.6`,但这不是 split timestep。 |
| - Wan 联合预测 action flow、future-track flow 和 future-video flow。 |
| - 推理按总计 10 个、`Δτ=-0.1` 的 Euler 网格运行前 6 步,到达 `τ=0.4`。 |
| - 在精确的 `τ=0.4` 再执行一次 Wan,生成给触觉 transformer 使用的 detached |
| coarse memory。 |
|
|
| ### 精阶段:触觉,`τ=0.4 → 0` |
|
|
| - 训练时 `τ_tactile = 0.4 × Beta(1.5,1.0)`,覆盖 `(0,0.4]`。 |
| - 每个训练 chunk 对 offset `0/4/8/12` 分别提供对应的 force 与 16-sample |
| history。 |
| - offset 为 `k` 时,loss 只覆盖 action `k:16`。 |
| - 推理运行剩余 4 个 Euler updates;每次 5Hz 刷新只写入未执行 suffix。 |
| - runtime controller 保存之前已经发出的 prefix,确保后续触觉刷新不能改写历史 |
| command。 |
|
|
| ## 8. 训练 loss |
|
|
| 总 loss 由以下部分加权求和: |
|
|
| - `dynamics_loss`:未来视频 latent flow MSE。 |
| - `action_loss`:粗阶段 62 个物理动作维的 flow MSE。 |
| - `track_loss`:可见性 mask 后的绝对 XY CFM velocity MSE。 |
| - `force_loss`:按 refresh offset mask 后的动作 suffix flow MSE。 |
| - `vq_loss`:仅在有效历史步计算的 force history reconstruction loss。 |
| - `commitment_loss`:VQ commitment loss,默认系数 0.25。 |
|
|
| 训练使用 Wan2.2-TI2V-5B 共享权重和 LoRA。LoRA 注入 |
| `q,k,v,o,k_img,v_img,ffn.0,ffn.2`;新建的 action/state/track/force、video token |
| 投影和 decoder 完整训练。checkpoint 保存 LoRA、新模块参数以及 VQ EMA buffers。 |
|
|
| ## 9. 在线执行 |
|
|
| 1. 收集三视角 conditioning observation(在线接口也可编码多帧历史)、当前 |
| 62D state、16 帧 past track、当前 force 和 16-sample force history。 |
| 2. Wan 对 action、future track 和 future video 运行 6 步,从 `τ=1` 到 |
| `τ=0.4`。 |
| 3. offset 0 的触觉运行剩余 4 步,把完整 16-step action 去噪到 `τ=0`。 |
| 4. 以 20 Hz 执行动作。 |
| 5. 执行 4、8、12 步后,各接收一次新的 5Hz 触觉;每次从缓存的 coarse state |
| 重新去噪剩余 suffix,同时保留已执行 prefix。 |
| 6. 输出 delta-base 动作反归一化并恢复为绝对手腕 `xyz + rotation-6D`。 |
|
|
| ## 10. 入口 |
|
|
| 训练: |
|
|
| ```bash |
| cd /scratch1/home/zhicao/dreamzero |
| bash scripts/train/trex_track_force_training_wan22.sh |
| ``` |
|
|
| 训练脚本直接使用已经构建完成的 `data/trex_small`,启动前只执行 schema |
| 完整性校验,不会再次运行 SAM2/CoTracker。 |
|
|
| 日志、checkpoint 与原 T-Rex 训练共用同一套 Trainer 回调。每隔 |
| `wandb_video_reconstruction_steps` 个 global step 会分别保存两个监控视频: |
| 不带标记的原始重建视频位于 |
| `OUTPUT_DIR/eval_videos/train_step_XXXXXX.mp4`,20Hz 预测 track motion |
| 叠加到 10Hz head/left-wrist/right-wrist 三面板后的版本位于 |
| `OUTPUT_DIR/eval_track_videos/train_step_XXXXXX.mp4`。原始预测与目标 |
| track 同时写入 `OUTPUT_DIR/eval_tracks/train_step_XXXXXX.npz`。两个视频 |
| 分别记录到 W&B `eval/predicted_video` 和 `eval/predicted_track_video`; |
| `dynamics/action/track/force/VQ/commitment` loss 和触觉 codebook |
| perplexity/active-code 指标同时写入 W&B 与 `OUTPUT_DIR/loss_log.jsonl`。 |
| 两个视频的首帧都是精确 GT conditioning frame;track target/prediction 的第 0 |
| 步在模型链路内就是当前 GT anchor。可视化不再对整段预测做事后平移。 |
|
|
| 训练可视化默认使用 |
| `wandb_video_reconstruction_inference_steps=1`:先运行一次 coarse WAN |
| Euler update,再在 `tau=0.4` 用一次边界预测恢复 clean video/track;不会直接 |
| 解码半噪声状态,并跳过不会改变这两个输出的 action-only 触觉精修。 |
| 这不会改变训练或正式在线推理的 6+4 两阶段 schedule;如需更高质量的监控视频, |
| 可以单独提高该值,但耗时近似按 inference steps 线性增长。 |
|
|
| 离线 NPZ 推理: |
|
|
| ```bash |
| python scripts/eval/trex_track_force_inference.py \ |
| --checkpoint checkpoints/trex_track_force_wan22_lora/checkpoint-8000 \ |
| --dataset-root data/trex_small \ |
| --input sample_input.npz \ |
| --output prediction.npz |
| ``` |
|
|
| 核心实现位于 `groot/vla/model/trex_track_force/`;独立 Hydra 配置为 |
| `model=trex_track_force/vla` 和 `data=dreamzero/trex_track_force_wan22`。 |
|
|