File size: 13,085 Bytes
fbd9366 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | # 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`。
|