DynaGuide 代码深度学习指南

点击章节标题展开/折叠 | 按顺序阅读理解引导链路

1. 一句话理解 DynaGuide

核心思想:冻结一个已训好的 Diffusion Policy(会多种行为但不知道该做哪个),用一个独立训练的动态模型的梯度,在 DDIM 去噪过程中"推"动作走向期望的视觉目标。

类比:Policy 是一个会弹多首曲子的钢琴家,DynaGuide 是在他耳边轻声说"弹莫扎特"的指挥——不改变他的弹奏能力,只引导他选择弹什么。

关键限制:如果钢琴家从来没学过某首曲子,指挥再怎么说也弹不出来——这就是 DynaGuide 只能做行为选择、不能解决真正 OOD 的原因。

2. 系统架构全景

┌─────────────────────────────────────────────────────────────────┐
│                        推理时(单步)                              │
│                                                                  │
│  ┌──────────┐     ┌───────────────────────────────────────┐     │
│  │ 当前观测  │────▶│  Diffusion Policy (DDIM 10步去噪)      │     │
│  │ 200×200  │     │                                       │     │
│  └──────────┘     │  for k in timesteps:                  │     │
│       │           │    for ss in range(4):                │     │
│       │           │      ε = UNet(a_noisy, k, obs)        │     │
│       ▼           │      grad = guidance(obs, a_noisy)    │◀──┐ │
│  ┌──────────┐     │      ε -= √(1-ᾱ_k) × grad           │   │ │
│  │ Dynamics │     │      a = DDIM_step(ε, k, a)          │   │ │
│  │  Model   │─────│───────────────────────────────────────│───┘ │
│  │ (DINOv2  │     │  输出: action_chunk [16, 7]           │     │
│  │  +Trans) │     └───────────────────────────────────────┘     │
│  └──────────┘                                                    │
│       ▲                                                          │
│       │                                                          │
│  ┌──────────┐  预计算                                            │
│  │ 目标demo │────────▶ good_embeddings [20, D]                   │
│  │ 20条hdf5 │         (DINOv2 latent of goal states)             │
│  └──────────┘                                                    │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                     训练时(完全独立)                              │
│                                                                  │
│  ABCD split ──▶ Dynamics Model 训练 (MSE in DINO latent space)  │
│  D split    ──▶ Base Policy 训练 (standard DP training)          │
└─────────────────────────────────────────────────────────────────┘

3. 引导链路:从 obs 到 guided action

Step 1: 预计算目标 embeddings(一次性)

文件: core/dynaguide.py → calculate_classifier_guidance()

输入: 20条 switch_on demo 的 HDF5
处理: 对每条demo取最后一帧的(obs_image, action_chunk)
      → dynamics_model.state_action_embedding(obs, action)
      → flatten → [1, D]
输出: good_embeddings [20, D] 存在GPU上

注意: 不是简单的"目标图像特征",而是"达到目标时的(状态,动作)联合编码"
      这编码了"什么状态+什么动作→目标结果"的因果信息

Step 2: 每步推理时计算梯度

文件: core/dynaguide.py → guidance() 闭包

输入: 当前 obs + 候选 noisy actions (来自DDIM中间状态)
处理:
  1. predicted_z = model.state_action_embedding(obs, actions)  # 预测未来latent
  2. dist = cdist(good_embeddings, predicted_z)                # [20, 1] 距离矩阵
  3. energy = logsumexp(-dist / alpha)                         # smooth minimum
  4. grad = autograd.grad(energy, actions)                     # 对actions求梯度
  5. grad *= scale                                             # scale=1.5
输出: gradient [1, 16, 7] — 引导方向

直觉: "如果这个动作会导致未来状态接近目标demo的ending → 梯度让actions更朝这个方向走"

Step 3: 注入 DDIM 去噪

文件: robomimic/.../diffusion_policy.py → _get_action_trajectory_guidance()

标准 DDIM:     ε_pred = UNet(a_t, t, obs)
加引导后:      ε_pred = UNet(a_t, t, obs) - √(1-ᾱ_t) × guidance_grad

这就是 Classifier Guidance 的标准公式:
  score(a|goal) = score(a) + s×∇_a log p(goal|a)
  对应到噪声空间: ε_guided = ε - √(1-ᾱ) × ∇_a log p(goal|a)

ss=4: 在同一个 timestep 重复4次(预测→引导→step),相当于更强的引导
     但只对前几个timestep生效(后面的√(1-ᾱ)很小,引导自然衰减)

4. 动态模型 (FinalStatePredictionDino)

文件: core/dynamics_models.py

架构:
  Image → DINOv2 ViT-S/14 (冻结) → 256个patch tokens [B, 256, 384]
  Action chunk [B, 16, 7] → Linear embedding → [B, 16, 384]
  Proprio [B, 15] → Linear → [B, 1, 384]
  
  Concat: [256 + 16 + 1] = 273 tokens
  + Positional Embedding [1, 258, 384]
  → 6层 Transformer Encoder (8头, dim=384)
  → 取前256 tokens → z_hat [B, 256, 384]  (预测的未来DINO patch embedding)

训练:
  Loss = MSE(z_hat, DINOv2(future_image)) + MSE(VQVAE_decode(z_hat), future_image)
  
  第二项(reconstruction)的梯度被detach——只用来监控训练质量,不反传到主loss

关键设计:
  - DINOv2 完全冻结(不微调)
  - 只训 Transformer + ActionEmbedding + ProprioEmbedding
  - 参数量 ≈ 15M(轻量,单3090训24-48h)
  - 输入输出都在 DINO patch token 空间(384维 × 256 patches)

5. 超参数的实际意义

参数默认值实际控制什么过大后果过小后果
scale1.5梯度的整体缩放倍数动作跳出policy可执行范围→乱动引导力度不够→行为仍分散
ss4每个DDIM step重复引导的次数计算量翻倍+后期过度干扰精修引导不够持久
alpha30logsumexp温度:多少demo贡献引导所有demo等权→信号被平均稀释只看最近demo→对异常demo敏感
horizon400单rollout最大步数任务没完成就停了
n_rollouts100统计样本量统计量波动大

6. 训练动态模型

文件: train_dynaguide.py

命令:
  python train_dynaguide.py --exp_dir <output> \
    --train_hdf5 <calvin_ABCD_dynamics.hdf5> \
    --test_hdf5 <calvin_val.hdf5> \
    --cameras third_person --action_dim 7 \
    --proprio_key proprio --proprio_dim 15 \
    --num_epochs 6000 --action_chunk_length 16 --batch_size 16

数据格式 (MultiviewDataset):
  HDF5: data/demo_X/obs/{third_person, proprio, states}, actions
  每条demo被切成 (obs_t, action_chunk[t:t+16], obs_{t+16}) 三元组
  action_chunk 不够长时用最后一步 padding

训练trick:
  - 对 action_chunk 加高斯噪声 (--noise_augmentation)
    模拟推理时DDIM中间步的noisy actions
    让dynamics model对噪声actions也能给出合理预测
  - 学习率: 1e-4 (Adam)
  - 每40个epoch做一次validation + filmstrip可视化

6.5 训练/评测资源配置 🟢论文核对

数据来源:DynaGuide 论文附录(Compute Hardware 小节)。DynaGuide 的最大特点是门槛极低——单张消费级显卡即可完成全部训练与实验。

配置
训练/推理 GPU单张 RTX 3090(24GB VRAM)——所有 policy 与 dynamics model 都在单卡上跑
Dynamics model 规模15M 可训练参数,训练/推理时约占 4GB 显存
训练时长训练至收敛约 24–48 小时(单 3090)
评测每 seed 每 task 约 10–20 分钟(单 3090)
base policy无需重训——DynaGuide 引导 off-the-shelf 冻结的 diffusion policy,只训练外部 dynamics model

门槛解读:DynaGuide 是本站所有工作里资源门槛最低的之一——单张 24GB 消费卡即可完整复现(训练+评测)。原因:它不训练庞大的 policy 或 VLM,只训练一个 ~15M 的轻量 dynamics model 来"引导"已有的冻结策略。这与需要 8× A100 的 VLA、8× H200 的视频世界模型形成鲜明对比,是"推理时引导/外部小模型引导"路线省算力的典型代表。

7. 数据处理流程 (生成 guidance conditions)

文件: data_processing_calvin/

Step 1: calvin_to_labeled_hdf5.py
  输入: CALVIN 原始 npz (episode_XXXXXXX.npz)
  处理: 按 task annotations 切段,标注行为类别
  输出: 分段后的 HDF5 (每段一个demo,标注行为标签)

Step 2: split_behavioral_validation_datasets_calvin.py
  输入: Step 1 的 validation HDF5
  处理: 按行为类别拆分成单独文件
  输出: switch_on_20.hdf5, button_off_20.hdf5, ... (每类取20条)

Step 3: hdf5_combiner.py
  输入: 单行为 HDF5 文件
  处理: 合并/筛选/设定每行为条数
  输出: 最终的 guidance conditions HDF5 (用于推理)

我们当前只有作者提供的 switch_on guidance (20条)
其余7个任务需要等 CALVIN 数据集下完后自行生成

8. VLMGuide 改造切入点

改造内容改哪个文件改什么难度
VLM生成g+run_dynaguide.py:323替换 good_dataset 来源:HDF5 → VLM生成图像
闭环目标演化run_dynaguide.py:rollout()在step循环中加失败检测→重新算good_embeddings
负目标累积core/dynaguide.py:guidance()每次失败后append到bad_embeddings
不确定性自适应core/dynaguide.py:guidance()加ensemble/MC-dropout估计→动态调scale
迁移flow matchingdiffusion_policy.py替换DDIM去噪为flow ODE + 修改注入公式

9. 关键问答

Q: DynaGuide 能解决 OOD 吗?

不能。它只能在 base policy 已有的动作分布内做选择。如果 policy 从未见过某个动作模式,梯度推不出分布外的动作。

Q: alpha=30 是"annealing"吗?

不是时间维度的衰减。alpha 是 logsumexp 的温度参数,控制"对 20 条目标 demo 的距离做 smooth minimum 的程度"。时间衰减是通过 √(1-ᾱ_k) 自然实现的(DDIM 后期这个值趋近 0)。

Q: 为什么 ss=4 比 ss=1 好?

ss=1 只在每个 timestep 引导一次就 step。ss=4 在同一 timestep 反复"引导→step"4次,相当于更深入地优化这步的动作方向。但 ss>4 收益递减(后期 DDIM step 本身变化小了)。

Q: guidance 是对整个 action chunk 施加的?

是。梯度 shape = [1, 16, 7],16步中的每一步动作都被引导。dynamics model 看到完整 16步 action chunk 后预测未来,所以梯度反传到每一步。

Q: 为什么预训练权重 Success_Rate=0 但实际有 80% 触发 switch?

robomimic 的 env.is_success() 不知道当前任务是 switch_on(DynaGuide 通过 env_setup 设置初始条件但没定义 task)。真正的成功判据是事后从 states 数据中算 switch delta。