DP · DiT · Flow Matching 三剑客

原理 + 公式 + 逐行代码 + 关系图谱 · 结合 dp / starVLA / openpi 仓库

0. 关系总览
1. DDPM (DP 的底座)
2. DiT
3. Flow Matching
4. 统一视角
5. 衍生脉络
6. 自测

0.1 一句话区分三者

概念它是什么层面一句话
DDPM / Diffusion生成范式(怎么训、怎么采样)把数据逐步加噪到高斯,再学一个网络一步步去噪还原
Flow Matching生成范式(DDPM 的"竞品/升级")不绕弯走加噪链,直接学"从噪声到数据的直线速度场",更少步数
DiT网络架构(用什么网络去噪)用 Transformer(而非 U-Net)当那个"去噪/速度预测网络"
Diffusion Policy应用(把上面用在机器人)把"生成"对象从图片换成机器人动作序列,观测作条件
关键认知:DDPM 和 Flow Matching 是同一层面的两种"生成范式"(可互相替换);DiT 是正交的一层——它是"去噪网络的架构选择",既能配 DDPM 也能配 Flow Matching。Diffusion Policy 则是把整套东西应用到动作生成。

0.2 三者的正交关系图

          范式 (训练目标 + 采样算法)
          ┌─────────────┬──────────────────┐
          │   DDPM      │  Flow Matching   │
          │ (预测噪声ε) │  (预测速度场v)    │
══════════╪═════════════╪══════════════════╡
架 U-Net  │ DDPM+UNet   │  FM+UNet         │
构 ───────┼─────────────┼──────────────────┤
   Trans- │ DiT(原始)   │  FM+DiT          │
   former │ DDPM+Trans  │ ★主流VLA动作头   │
══════════╧═════════════╧══════════════════╛

应用到机器人动作生成 = Diffusion Policy 家族:
  • DP-CNN     = DDPM + UNet1D     (dp 仓库主力)
  • DP-Transf. = DDPM + Transformer(dp 仓库变体)
  • π0/GR00T   = Flow Matching + DiT (starVLA/openpi)

横轴=范式(DDPM vs FM),纵轴=架构(UNet vs Transformer/DiT)。2×2 任意组合都成立,这正是为什么这三个概念可以独立学、再自由拼装。

0.3 时间线与传承

2020 DDPM ──────────────► 扩散生成范式确立 (预测噪声)
  │
  ├─2021 DDIM ──────────► 确定性快速采样 (100步→10步)
  │
  ├─2022 DiT ───────────► 把 U-Net 换成 Transformer
  │                       (Peebles & Xie, 可扩展性更好)
  │
  ├─2023 DiffusionPolicy► 扩散用于机器人动作 (DDPM+UNet/Transf)
  │
  └─2022~23 FlowMatching► Lipman等; Rectified Flow (刘强等)
        │                 直线路径, 更少采样步
        │
        └─2024~ π0/GR00T► Flow Matching + DiT 做动作专家
                          (当前 VLA 动作头主流)

0.4 本课学习路线

  1. Tab 1 DDPM:先吃透 DP 用的底座(前向加噪、反向去噪、ε 预测),结合 dp 仓库代码
  2. Tab 2 DiT:理解"去噪网络"为什么/怎么换成 Transformer,看 AdaLN 条件注入
  3. Tab 3 Flow Matching:理解它和 DDPM 的本质区别,看 starVLA/openpi 代码
  4. Tab 4 统一视角:用 SDE/ODE 把三者串起来
  5. Tab 5 衍生:从这三者长出的方法树

1.1 DDPM 原理:两个方向

前向过程 q(加噪,固定不学)

给干净数据 x₀ 逐步加高斯噪声,T 步后变纯噪声。单步:

q(xₜ | xₜ₋₁) = N(xₜ; √(1−βₜ)·xₜ₋₁, βₜI)

关键技巧——任意步可一次性算出(reparameterization):

xₜ = √(ᾱₜ)·x₀ + √(1−ᾱₜ)·ε,   ε~N(0,I),  ᾱₜ=∏(1−βᵢ)

反向过程 p_θ(去噪,要学的)

p_θ(xₜ₋₁ | xₜ) = N(xₜ₋₁; μ_θ(xₜ,t), Σ_θ)

DDPM 的洞察:与其学均值 μ,不如学预测噪声 ε。简化后的训练目标:

L = 𝔼x₀,ε,t ‖ ε − ε_θ(√ᾱₜ·x₀ + √(1−ᾱₜ)·ε, t) ‖²
为什么预测 ε 这么神? 它等价于学数据分布的分数函数(score)∇ₓ log p(x) ≈ −ε_θ/√(1−ᾱₜ)。所以 DDPM 本质是 score-based 生成。这也是 Diffusion Policy "学动作分布梯度" 说法的由来。

1.2 采样:DDPM vs DDIM

DDPM 采样DDIM 采样
性质随机(每步加噪声)确定性(无随机项)
步数~100-100010-50(可跳步)
DP 用途训练时 K=100推理时 10-16 步(实时)
DDIM: xₜ₋₁ = √ᾱₜ₋₁·(x̂₀) + √(1−ᾱₜ₋₁)·ε_θ(xₜ,t)

其中 x̂₀ = (xₜ − √(1−ᾱₜ)·ε_θ)/√ᾱₜ 是当前估计的干净数据。下面 1.2b 专门讲透 DDIM。

1.2b 深入 DDIM:DDPM 为什么能从 1000 步压到 10 步

① 它解决什么问题

DDPM 推理要沿反向马尔可夫链一步步走,100~1000 步,每步一次网络前向——太慢,机器人根本没法实时(DP 要 10Hz)。DDIM (Song et al. 2021) 的目标:用同一个已训练好的 ε_θ,不重新训练,把采样压到 10~50 步

关键前提:DDIM 不改训练!它复用 DDPM 训练出来的噪声预测网络 ε_θ。只是换了一套"反向走法"。这就是为什么 dp 仓库里训练代码一行不动、只换 scheduler 就能加速。

② 核心洞察:训练目标其实不绑定"那条马尔可夫链"

DDPM 的训练 loss ‖ε − ε_θ‖² 只依赖边缘分布 q(xₜ|x₀)=√ᾱₜx₀+√(1−ᾱₜ)ε,并不关心 xₜ 与 xₜ₋₁ 之间具体怎么连。

Song 等人发现:可以构造一族非马尔可夫的前向过程,它们的边缘分布 q(xₜ|x₀) 完全相同(所以同一个 ε_θ 依然有效),但反向过程的随机性可调。用一个参数 σ 控制:

xₜ₋₁ = √ᾱₜ₋₁·x̂₀  +  √(1−ᾱₜ₋₁−σ²)·ε_θ(xₜ,t)  +  σ·z,  z~N(0,I)
  • x̂₀ = (xₜ − √(1−ᾱₜ)·ε_θ)/√ᾱₜ  ——用当前预测的噪声反推"干净数据估计"
  • σ 最大 → 退化回 DDPM(随机马尔可夫)
  • σ = 0DDIM:去掉随机项 σ·z,整个过程确定性

③ σ=0 带来两个好处

  1. 确定性:同一噪声起点 → 同一输出(可复现、可插值)
  2. 可跳步:既然不依赖马尔可夫的"逐步"假设,就能在时间步子集 {1000, 900, ..., 0} 上跳着走,10 步直达

④ 为什么 DDIM 是"概率流 ODE"——通往 Flow Matching 的桥

当 σ=0 且步长趋近无穷小时,DDIM 的离散更新收敛到一个常微分方程 (ODE)

dx/dt = f(x,t) − ½g²(t)·∇ₓlog p(x)   (概率流 ODE)

也就是说,DDIM 采样 = 用欧拉法解这条 ODE。而 Flow Matching 是直接学一条 ODE 的速度场。两者都在"解 ODE",只是:

DDIM:先按 DDPM 那条弯曲的加噪路径训练,事后才发现可以当 ODE 解 → 路径弯,欧拉法要多走几步
Flow Matching:训练时就把路径设成直线 → ODE 极好解,4 步就够

这就是 Tab 4 "统一视角"里"DDIM 与 FM 殊途同归"的精确含义——它们解的是同类 ODE,区别在路径直不直。

⑤ 代码:换 scheduler 即可,训练零改动

dp: diffusion_policy/workspace/* 配置 + policy 里的 noise_scheduler

# 训练时: DDPMScheduler, num_train_timesteps=100
noise_scheduler = DDPMScheduler(num_train_timesteps=100, ...)

# 推理时想加速: 直接换成 DDIMScheduler, 复用同一个 ε_θ 权重!
noise_scheduler = DDIMScheduler(num_train_timesteps=100, ...)
self.num_inference_steps = 16        # 只走 16 步

# conditional_sample 里的循环逻辑完全不变:
for t in scheduler.timesteps:                 # DDIM 只有 16 个 t
    model_output = model(trajectory, t, ...)  # 同一个网络
    trajectory = scheduler.step(model_output, t, trajectory).prev_sample
    #            ↑ scheduler.step 内部从 DDPM 公式换成 DDIM 公式 (σ=0)
记住这个解耦:ε_θ(网络权重)+ 训练 loss 属于"范式与模型",scheduler 属于"采样器"。DDPM↔DDIM 只换采样器,是即插即用的。这也是 diffusers 库设计的精髓。

1.2c DDPM / DDIM / Flow Matching 三者区别与联系

一张表看清

维度DDPMDDIMFlow Matching
本质生成范式(训练+采样)DDPM 的采样器(不改训练)生成范式(训练+采样)
训练目标预测噪声 ε不训练,复用 DDPM 的 ε_θ预测速度 v=x₁−x₀
路径加噪马尔可夫链(弯)同 DDPM 的边缘分布(弯)线性插值(直)
采样性质随机 (σ 最大)确定性 (σ=0)确定性 (解 ODE)
采样步数100~100010~504~10
数学对象反向 SDE 离散化概率流 ODE 离散化直接学 ODE 速度场
能否跳步否(逐步马尔可夫)

三句话理清关系

  • DDIM ⊂ DDPM 生态:DDIM 不是新范式,是 DDPM 的"快速采样模式"。同一族公式里 σ 从大调到 0,DDPM 平滑变成 DDIM。
  • DDIM 是 DDPM 通往 ODE 的桥:σ=0 让随机 SDE 退化成确定性 ODE,从此可以用 ODE 求解器(欧拉/RK)加速。
  • FM 是"生来就是直路的 ODE":DDIM 是把弯路当 ODE 解(将就),FM 是训练时就修一条直路(根治)。所以 FM 步数能更少、更稳。

用"赶路"打个比方

目标:从噪声地 → 数据地。
DDPM:沿一条蜿蜒山路、每步还随机抖一下,走 1000 步稳妥到达。
DDIM:同一条山路,但不再随机抖(确定性),而且可以大步跨着走,10 步到。
FM:干脆修一条直达高速(训练时就把路拉直),4 步飙到。
注意:DDPM 和 DDIM 走的是同一条路(同一个 ε_θ),只是走法不同;FM 走的是另一条更直的路(不同训练)。

对应到本仓库代码

方法训练推理采样器仓库
DP (默认)DDPM loss, K=100DDIM, 16 步dp
GR00T 动作头FM loss (预测 v)ODE 欧拉, 4 步starVLA
π0FM loss (预测 v)ODE 欧拉, 10 步openpi

DP 是"DDPM 训练 + DDIM 采样"的典型;π0/GR00T 是"FM 训练 + ODE 采样"。看懂这两行,就理解了当前动作生成的两条主流技术路线。

🔗 想看 Flow Matching 在最新工作里怎么用?→ π0.7 精读(FM 动作专家 + 世界模型子目标) · VERA 精读(视频世界模型 + Jacobian IDM) · 世界模型全景

1.3 代码精读①:前向加噪 + 训练 loss

dp: diffusion_policy/policy/diffusion_unet_lowdim_policy.py

# ═══ 采样随机时间步 ═══
timesteps = torch.randint(0, 100, (bsz,)).long()   # t ~ U(0,100)
noise = torch.randn(trajectory.shape)              # ε ~ N(0,I)

# ═══ 前向加噪 (一步到位, 对应公式 xₜ=√ᾱ·x₀+√(1-ᾱ)·ε) ═══
noisy_trajectory = self.noise_scheduler.add_noise(
    trajectory, noise, timesteps)
#   diffusers 内部: √ᾱₜ·trajectory + √(1-ᾱₜ)·noise

# ═══ 预测噪声 ═══
pred = self.model(noisy_trajectory, timesteps, global_cond=obs_cond)

# ═══ 目标 = 噪声本身 (epsilon prediction) ═══
target = noise   # prediction_type == 'epsilon'
loss = F.mse_loss(pred, target)   # 就是公式里的 ‖ε - ε_θ‖²
看,论文公式 L=‖ε−ε_θ‖² 在代码里就是 F.mse_loss(pred, noise) 一行。add_noise 帮你做了 √ᾱ 那套加权。

1.4 代码精读②:反向去噪采样

dp: diffusion_policy/policy/diffusion_unet_lowdim_policy.py · conditional_sample

trajectory = torch.randn(size=shape)          # xₜ = 纯噪声起点
scheduler.set_timesteps(self.num_inference_steps)  # 推理 10-16 步

for t in scheduler.timesteps:                 # t 从大到小
    model_output = model(trajectory, t, global_cond=obs_cond)  # 预测 ε
    trajectory = scheduler.step(              # xₜ → xₜ₋₁
        model_output, t, trajectory).prev_sample
# 循环结束 → trajectory 是干净动作 x₀

scheduler.step() 内部就是 DDPM/DDIM 的那条反向公式。换 scheduler(DDPMScheduler→DDIMScheduler)就切换采样方式,训练代码完全不用动——这是范式与实现解耦的好例子。

1.5 DP 如何把"生成图片"变成"生成动作"

图像扩散 (DDPM 原版)Diffusion Policy
x₀ (生成对象)图片 [B,3,H,W]动作序列 [B,Tp,Da]
条件类别/文本视觉观测 obs (FiLM/cross-attn)
网络2D U-Net1D U-Net (时间卷积) 或 Transformer
采样输出后直接是图取中间 Ta 步执行 (receding horizon)
本质完全一样,只是把"空间维度的图片"换成"时间维度的动作序列",把"文本条件"换成"观测条件"。DP 的全部创新都在怎么用,而非改动 DDPM 本身。

2.1 DiT 是什么 & 为什么

DiT (Diffusion Transformer, Peebles & Xie 2022):把扩散模型里那个"去噪网络"从 U-Net 换成 Transformer。

动机:U-Net 是卷积结构,扩展性受限;Transformer 随参数/数据扩展(scaling law)表现更好。DiT 证明纯 Transformer 也能做顶级图像生成,并随计算量平滑提升。

核心问题:Transformer 怎么吃"扩散步 t"和"条件 c"?答案是 AdaLN(Adaptive Layer Norm)——把 (t, c) 编码成每层 LayerNorm 的 scale/shift。

2.1b 重要澄清:DiT + Flow Matching 为什么不重复?

常见误解:"你说 DDPM 和 FM 是同一层面、二选一;又说 DiT 来自配 DDPM 的论文。那 DiT 不就自带 DDPM 了吗?再叠一个 FM 岂不是用了两套范式、重复了?"

误解的根源在一句话:DiT ≠ "用了 DDPM"。DiT 只是一个网络结构(Transformer 骨架 + AdaLN 条件注入),它本身不含任何加噪/去噪逻辑,也不含任何范式。DiT 原论文恰好用 DDPM 来训练它,但那只是作者当时的选择,不是 DiT 的一部分。

关键:网络 ≠ 范式,是两层独立的东西

把一个去噪/预测网络的接口抽象出来,它长这样:

输入 (带噪样本 xₜ, 时间步 t, 条件 c)  →  输出 一个与 x 同形状的张量

网络不知道、也不关心这个输出张量"是什么含义"。含义完全由外面的训练 loss 赋予:

外面用的 loss输出被训练成这就是
‖输出 − ε‖²噪声 εDDPM 范式
‖输出 − (x₁−x₀)‖²速度 vFlow Matching 范式
同一个 DiT、同样的前向计算,输出张量的物理含义由 loss 决定,网络结构里没有任何一行代码规定"我预测 ε 还是 v"。

用代码看最清楚

看 starVLA 的 DiT.forward(Tab 2.4 的代码)——它只是:编码时间步 → 过 N 层 Transformer → 投影输出。全程没有"加噪/去噪/ε/速度"任何字样,就是个"吃 (xₜ,t,c) 吐一个张量"的纯函数:

class DiT(...):
    def forward(self, hidden_states, encoder_hidden_states, timestep):
        temb = self.timestep_encoder(timestep)
        for block in self.transformer_blocks:
            hidden_states = block(hidden_states, ..., temb=temb)
        return self.proj_out_2(...)   # 只是输出张量, 不含任何范式语义

"含义"在外面的 action head 里才被赋予。同一个 self.model(DiT),换一行 loss 就切换范式:

# ── FM 写法 (starVLA 现状) ──
velocity = actions - noise                    # 目标定义成"速度"
pred = self.action_decoder(self.model(...))   # self.model 就是 DiT
loss = ((pred - velocity) ** 2).mean()        # ← 这行让 DiT 输出=速度

# ── 改成 DDPM 写法 (DiT 类一字不改) ──
target = noise                                # 目标改成"噪声"
pred = self.action_decoder(self.model(...))   # 同一个 DiT
loss = ((pred - target) ** 2).mean()          # ← 这行让 DiT 输出=噪声

所以 "DiT + FM" 没有重复——两者干的是不同的活

角色谁负责具体内容
出题 + 判分 + 采样范式 (DDPM 或 FM)怎么构造 xₜ、目标是 ε 还是 v、推理怎么采样
做题:吃 (xₜ,t,c) 算张量架构 (DiT 或 U-Net)Transformer+AdaLN 的矩阵运算

FM 负责"出题和判分",DiT 负责"做题"。两件事完全不重叠,反而缺一不可——FM 需要一个网络来预测速度场,DiT 正好胜任。"DiT + FM" = 用 Transformer 这个工具,去做 FM 这套题。

一个类比:菜谱 vs 厨具

范式 = 菜谱(红烧 / 清蒸) | 架构 = 厨具(炒锅 / 蒸锅)
  • DDPM 原论文 = 炒锅做红烧(U-Net + DDPM)
  • DiT 原论文 = 换口锅做红烧(Transformer + DDPM)——换厨具,菜谱没变
  • π0 / GR00T = 用 DiT 这口锅做清蒸(Transformer + FM)——厨具和菜谱各自独立换
你不会说"用蒸锅就一定是清蒸"。锅(DiT)和菜谱(FM/DDPM)本来就是两个正交的选择——这正是 Tab 0 那张 2×2 表的含义。

2.2 AdaLN 公式

普通 LayerNorm:LN(x) = γ·norm(x) + β,γ/β 是固定可学参数。

AdaLN:让 γ/β 由条件动态生成

scale, shift = MLP(emb(t) + emb(c))
AdaLN(x) = (1 + scale)·norm(x) + shift

这样每个扩散步、每个条件,都会调制出不同的归一化行为。是把条件注入 Transformer 的标准做法,等价于 CNN 里的 FiLM(见 DP 学习课)。

2.3 代码精读①:DP 仓库的 Transformer 去噪器

dp: diffusion_policy/model/diffusion/transformer_for_diffusion.py

DP 的 Transformer 变体用 encoder-decoder 结构(条件做 memory,动作做 target):

def forward(self, sample, timestep, cond=None):
    # sample: [B,Tp,Da] 噪声动作; timestep: 扩散步; cond: [B,To,cond_dim] 观测
    time_emb = self.time_emb(timesteps).unsqueeze(1)  # [B,1,n_emb] 扩散步嵌入

    input_emb = self.input_emb(sample)                # 动作 → token

    # ═══ encoder: 把 (时间步 + 观测) 编码成 memory ═══
    cond_embeddings = time_emb
    if self.obs_as_cond:
        cond_obs_emb = self.cond_obs_emb(cond)        # 观测 → token
        cond_embeddings = torch.cat([time_emb, cond_obs_emb], dim=1)
    x = cond_embeddings + self.cond_pos_emb
    memory = self.encoder(x)                          # [B, T_cond, n_emb]

    # ═══ decoder: 动作 token 交叉注意 memory ═══
    x = input_emb + self.pos_emb
    x = self.decoder(
        tgt=x, memory=memory,
        tgt_mask=self.mask,          # 因果掩码: 动作只看自己和之前
        memory_mask=self.memory_mask
    )
    x = self.head(x)                 # → [B,Tp,Da] 预测噪声
    return x
这里条件通过交叉注意力注入(decoder 的 memory),不是 AdaLN。DP-Transformer 是 minGPT 风格。下面看 starVLA 里更"标准 DiT"的 AdaLN 写法。

2.4 代码精读②:starVLA 的标准 DiT(AdaLN 版)

starVLA: starVLA/model/modules/action_model/flow_matching_head/cross_attention_dit.py

class AdaLayerNorm(nn.Module):
    def forward(self, x, temb):
        temb = self.linear(self.silu(temb))      # 条件 → 2×dim
        scale, shift = temb.chunk(2, dim=1)       # 拆成 scale / shift
        x = self.norm(x) * (1 + scale[:, None]) + shift[:, None]  # AdaLN!
        return x

class DiT(...):
    def forward(self, hidden_states, encoder_hidden_states, timestep, ...):
        temb = self.timestep_encoder(timestep)    # 扩散步 → 嵌入

        for idx, block in enumerate(self.transformer_blocks):
            if idx % 2 == 1 and self.config.interleave_self_attention:
                # 奇数层: self-attn (动作 token 互相看)
                hidden_states = block(hidden_states, encoder_hidden_states=None, temb=temb)
            else:
                # 偶数层: cross-attn (动作 token 看 VLM/观测特征)
                hidden_states = block(hidden_states,
                    encoder_hidden_states=encoder_hidden_states, temb=temb)

        # 输出层也用 AdaLN 调制
        shift, scale = self.proj_out_1(F.silu(temb)).chunk(2, dim=1)
        hidden_states = self.norm_out(hidden_states)*(1+scale[:,None])+shift[:,None]
        return self.proj_out_2(hidden_states)
两种条件注入并存:扩散步 t 走 AdaLN(每层归一化调制),观测/VLM 特征走 cross-attention(每隔一层)。这是当前 VLA 动作头(π0/GR00T)的标准结构。

2.5 DiT vs U-Net 对比

U-Net (DP-CNN)DiT (Transformer)
骨架卷积下采样+上采样+skip堆叠 Transformer block
条件注入FiLM (scale/bias 调制卷积)AdaLN (调制归一化) + cross-attn
扩展性受限随参数/数据平滑提升
归纳偏置局部性强(适合低频)全局注意(适合高频/长程)
超参敏感低(开箱即用)高(dropout/wd 需调)

注意 FiLM 和 AdaLN 是同一思想的两种实现:用条件生成 scale/shift 去调制特征,只是一个调卷积输出、一个调 LayerNorm。

3.1 Flow Matching 的核心思想

DDPM 走的是一条"加噪马尔可夫链",弯弯曲曲。Flow Matching (FM) 问:能不能让"噪声→数据"走直线

直觉:在噪声分布和数据分布之间定义一条概率路径,学一个速度场 v(x,t),告诉每个点"此刻该往哪个方向走、走多快"。推理时就是解一个 ODE:从噪声出发,沿速度场积分到数据。

3.2 公式:条件 FM / Rectified Flow

最常用的是线性插值路径(Rectified Flow)。给数据 x₁ 和噪声 x₀~N(0,I):

xₜ = (1−t)·x₀ + t·x₁,   t∈[0,1]

这条直线的速度(对 t 求导)是常数:

真实速度 u = dxₜ/dt = x₁ − x₀

训练目标——让网络预测这个速度:

L = 𝔼t,x₀,x₁ ‖ v_θ(xₜ, t, c) − (x₁ − x₀) ‖²

推理——从噪声 x₀ 出发解 ODE(欧拉法,几步即可):

x ← x + Δt · v_θ(x, t, c)
对比 DDPM:DDPM 预测噪声 ε、采样要几十上百步;FM 预测速度 v、路径是直线、采样 4-10 步就够。这就是 π0/GR00T 选 FM 的原因——动作生成要实时。

3.3 DDPM vs Flow Matching 对照

维度DDPM (DP 用)Flow Matching (π0/GR00T 用)
路径加噪马尔可夫链(曲)线性插值(直)
预测目标噪声 ε速度场 v = x₁−x₀
训练 loss‖ε − ε_θ‖²‖v − v_θ‖²
采样反向链/DDIM (10-100步)ODE 欧拉 (4-10步)
时间方向t=0 数据, t=T 噪声因实现而异(见下方坑)
易混坑:t 的方向。starVLA 里 t=1 是干净动作;openpi 里 t=1 是噪声(代码注释明确吐槽"和论文相反,抱歉")。读代码时务必先确认插值方向,否则推理积分方向会反。

3.4 代码精读①:starVLA 的 FM 训练

starVLA: starVLA/model/modules/action_model/GR00T_ActionHeader.py · forward

def forward(self, vl_embs, actions, state=None, ...):
    # ═══ 采样噪声 + 时间步 ═══
    noise = torch.randn(actions.shape)            # x₀ ~ N(0,I)
    t = self.sample_time(...)                     # t ~ Beta(1.5,1.0), 偏向小t
    t = t[:, None, None]

    # ═══ 线性插值构造 noisy action (FM 路径) ═══
    noisy_trajectory = (1 - t) * noise + t * actions   # xₜ=(1-t)x₀+t·x₁
    velocity = actions - noise                          # 真实速度 v=x₁-x₀

    # ═══ DiT 预测速度 ═══
    t_discretized = (t[:,0,0] * self.num_timestep_buckets).long()
    action_features = self.action_encoder(noisy_trajectory, t_discretized)
    ... # 拼接 state / future tokens
    model_output = self.model(                    # 这个 model 就是 DiT!
        hidden_states=sa_embs,
        encoder_hidden_states=vl_embs,            # 观测/VLM 特征做 cross-attn
        timestep=t_discretized)
    pred = self.action_decoder(model_output)

    # ═══ FM loss: ‖v - v_θ‖² ═══
    loss = ((pred[:, -actions.shape[1]:] - velocity) ** 2).mean()
    return loss
对照公式:noisy_trajectory = (1-t)*noise + t*actions 就是 xₜ;velocity = actions - noise 就是真实速度 u;loss 就是 ‖v−v_θ‖²。而那个 self.model 正是 Tab 2 讲的 DiT——FM 和 DiT 在这里合体。

3.5 代码精读②:starVLA 的 FM 推理(ODE 欧拉)

starVLA: GR00T_ActionHeader.py · predict_action

actions = torch.randn(B, action_horizon, action_dim)  # x₀ 噪声起点
num_steps = self.num_inference_timesteps              # 如 4 步
dt = 1.0 / num_steps

for t in range(num_steps):
    t_cont = t / num_steps                            # 0, 0.25, 0.5, 0.75
    t_disc = int(t_cont * self.num_timestep_buckets)
    action_features = self.action_encoder(actions, t_disc)
    ...
    model_output = self.model(hidden_states=sa_embs,
        encoder_hidden_states=vl_embs, timestep=t_disc)
    pred_velocity = self.action_decoder(model_output)[:, -action_horizon:]
    actions = actions + dt * pred_velocity            # 欧拉积分! x += Δt·v
return actions                                        # 干净动作

对照公式 x ← x + Δt·v,4 步就从噪声积分到动作。比 DDPM 的几十步快一个量级。

3.6 openpi 里的 FM(注意 t 方向相反)

openpi: src/openpi/models/pi0.py · compute_loss / sample_actions

# 训练 (注意 t=1 是噪声, 与 starVLA 相反!)
time = beta(1.5, 1) * 0.999 + 0.001
x_t = time * noise + (1 - time) * actions   # t=1→noise, t=0→action
u_t = noise - actions                        # 速度方向也跟着反
v_t = model(x_t, time, ...)
loss = mean((v_t - u_t)²)

# 推理: dt 为负, 从 t=1 积分到 t=0
dt = -1.0 / num_steps
x = noise
for step: x = x + dt * v_t   # 反方向积分
同样是 Flow Matching,starVLA 和 openpi 的 t 方向、速度符号都相反,但数学等价。读任何 FM 代码先三件事:①插值公式看 t=1 是数据还是噪声 ②速度定义 ③推理 dt 正负。

4.1 统一视角:都是 SDE/ODE 的离散化

DDPM 和 Flow Matching 看似不同,其实是同一个连续过程的两种刻画。生成可以统一写成一条微分方程,从噪声演化到数据:

dx = [漂移项 f(x,t)] dt + [扩散项 g(t)] dW
  • DDPM = 这个 SDE(随机微分方程) 的特定离散化,含随机项 dW,预测噪声 ε(≈ 分数 ∇log p)
  • DDIM = 去掉随机项后对应的 概率流 ODE,所以确定性、可跳步
  • Flow Matching = 直接学这条 ODE 的速度场 v,并选最简单的直线路径
所以 DDIM(DDPM 的快速采样)和 Flow Matching 在"解 ODE"这点上殊途同归。FM 的优势是训练时就让路径变直,使 ODE 更好解(更少步数)。

4.2 score(分数)、噪声 ε、速度 v 的换算

含义谁在用
分数 ∇ₓlog p(x)对数概率密度的梯度,指向高概率区Score-based 理论
噪声 ε加进去的高斯噪声DDPM / DP
速度 v概率路径的瞬时速度Flow Matching / π0

三者可互相换算(在给定路径下)。例如 DDPM 的 ε_θ 与分数差一个 −1/√(1−ᾱₜ) 因子;线性路径下速度 v 与 ε、x₀ 也有线性关系。它们是同一信息的不同参数化

4.3 把四个概念拼回去:每个方法是怎么组合的

方法范式预测量架构采样步仓库
DP-CNNDDPMεUNet1D + FiLMDDIM 10-16dp
DP-TransformerDDPMεTransformer + cross-attnDDIM 10-16dp
GR00T 动作头Flow MatchingvDiT + AdaLN + cross-attnODE 4starVLA
π0Flow Matchingv双 Gemma (类DiT)ODE 10openpi

读到任何一个动作生成方法,都可以用这张表的五栏去拆解它:范式 / 预测量 / 架构 / 采样 / 条件注入

4.4 条件注入方式横向对比

方式怎么做用在
FiLM条件→scale/bias 调制卷积输出DP-CNN
AdaLN条件→scale/shift 调制 LayerNormDiT / GR00T (扩散步t)
Cross-Attention动作 token 查询观测特征DP-Transformer, GR00T (观测)
Token 拼接条件作为序列 token 一起进 Transformerπ0 (prefix-suffix)

FiLM 与 AdaLN 本质相同(生成 scale/shift 调制特征),区别只是调制对象。扩散步 t 常走 AdaLN/FiLM,复杂观测常走 cross-attn 或 token 拼接。

5.1 方法树:从三个底座长出的家族

【范式层】
DDPM(2020) ──┬── DDIM(2021) 快速采样
             ├── Score SDE(2021) 统一理论
             └── Flow Matching / Rectified Flow(2022-23) 直线路径
                      │
                      └── Consistency Models / 蒸馏 → 1步生成

【架构层】
U-Net ──► DiT(2022) ──┬── PixArt, SD3 (图像)
                       ├── 视频 DiT (Sora类)
                       └── 动作 DiT (GR00T, π0 动作头)

【机器人应用层】
Diffusion Policy(2023, DDPM+UNet/Transf)
   │
   ├── 3D Diffusion Policy (点云观测)
   ├── Diffusion Policy + Flow Matching (换范式提速)
   ├── π0 / π0.5 (Flow Matching + 双Gemma, VLA)
   ├── GR00T N1.x (Flow Matching + DiT 动作专家)
   └── RDT, MDT 等 (扩散/DiT 大动作模型)

5.2 关键衍生及其动机

衍生方法在哪个底座上改解决什么问题
DDIMDDPM 采样100步→10步,能实时
Flow Matching替代 DDPM 范式直线路径,更少采样步、训练更稳
Consistency Model蒸馏扩散/FM1-2 步生成,极致提速
DiT替代 U-Net 架构可扩展性,适配大模型
Diffusion PolicyDDPM 应用到动作多模态动作、训练稳定
π0 / GR00TFM+DiT 应用到 VLA实时动作生成 + 接 VLM 语义

5.3 学习建议:怎么读一个新动作生成论文

  1. 先定范式:它用 DDPM 还是 Flow Matching?看训练 loss 预测 ε 还是 v。
  2. 再看架构:去噪网络是 U-Net 还是 DiT/Transformer?
  3. 查条件注入:观测/语言怎么进网络(FiLM/AdaLN/cross-attn/token)?
  4. 看采样:推理几步?DDIM 还是 ODE 欧拉?
  5. 找创新点:多数论文只动其中 1-2 项,其余沿用经典。
掌握 DDPM + DiT + Flow Matching 这三块,几乎所有"扩散类机器人策略"论文都能秒拆。它们就是这个领域的"乐高基础件"。

这三块对算力的影响 🩷整理

选 DDPM / DiT / Flow Matching 不只是精度问题,也直接影响训练与推理的算力开销。下面是从"这三块怎么选"角度看资源的对照(概念性,非某一篇的具体数字)。

选择训练开销推理开销典型场景
去噪网络: U-Net较轻,参数可小到百 M 级单步快Diffusion Policy 原版(单卡 3080 推理 0.1s)
去噪网络: DiT/Transformer随层数/宽度上升,可到 B 级(如 GR00T/PI 的 action DiT ~100M–300M;视频 DiT 到 14B)attention 随序列长度增长VLA action head、视频世界模型
采样: DDPM(多步):几十~上千步训练稳,推理慢
采样: DDIM/少步中:可压到 4–20 步DP 常用少步
采样: Flow Matching(ODE)训练目标更简单(直线插值):直路径,少步即可(常 4–10 步)π0/GR00T/ImageWAM 动作专家
要点训练侧算力主要由"去噪网络多大"决定(U-Net 百 M vs 视频 DiT 14B,差两个数量级);推理侧算力主要由"采样几步"决定(多步 DDPM 慢,Flow Matching / 少步 DDIM 快)。这解释了为什么机器人实时控制里 Flow Matching + 少步 越来越主流——它同时压低了训练目标复杂度和推理步数。具体某模型的 GPU 配置见各专页(DP/π0.7/VERA/ImageWAM 的"资源配置")。

6. 自测:15 题检验你是否真懂四者关系

选完点提交,即时判分 + 解析。