原理 + 公式 + 逐行代码 + 关系图谱 · 结合 dp / starVLA / openpi 仓库
| 概念 | 它是什么层面 | 一句话 |
|---|---|---|
| DDPM / Diffusion | 生成范式(怎么训、怎么采样) | 把数据逐步加噪到高斯,再学一个网络一步步去噪还原 |
| Flow Matching | 生成范式(DDPM 的"竞品/升级") | 不绕弯走加噪链,直接学"从噪声到数据的直线速度场",更少步数 |
| DiT | 网络架构(用什么网络去噪) | 用 Transformer(而非 U-Net)当那个"去噪/速度预测网络" |
| Diffusion Policy | 应用(把上面用在机器人) | 把"生成"对象从图片换成机器人动作序列,观测作条件 |
范式 (训练目标 + 采样算法)
┌─────────────┬──────────────────┐
│ 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 任意组合都成立,这正是为什么这三个概念可以独立学、再自由拼装。
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 动作头主流)
给干净数据 x₀ 逐步加高斯噪声,T 步后变纯噪声。单步:
关键技巧——任意步可一次性算出(reparameterization):
DDPM 的洞察:与其学均值 μ,不如学预测噪声 ε。简化后的训练目标:
| DDPM 采样 | DDIM 采样 | |
|---|---|---|
| 性质 | 随机(每步加噪声) | 确定性(无随机项) |
| 步数 | ~100-1000 | 10-50(可跳步) |
| DP 用途 | 训练时 K=100 | 推理时 10-16 步(实时) |
其中 x̂₀ = (xₜ − √(1−ᾱₜ)·ε_θ)/√ᾱₜ 是当前估计的干净数据。下面 1.2b 专门讲透 DDIM。
DDPM 推理要沿反向马尔可夫链一步步走,100~1000 步,每步一次网络前向——太慢,机器人根本没法实时(DP 要 10Hz)。DDIM (Song et al. 2021) 的目标:用同一个已训练好的 ε_θ,不重新训练,把采样压到 10~50 步。
DDPM 的训练 loss ‖ε − ε_θ‖² 只依赖边缘分布 q(xₜ|x₀)=√ᾱₜx₀+√(1−ᾱₜ)ε,并不关心 xₜ 与 xₜ₋₁ 之间具体怎么连。
Song 等人发现:可以构造一族非马尔可夫的前向过程,它们的边缘分布 q(xₜ|x₀) 完全相同(所以同一个 ε_θ 依然有效),但反向过程的随机性可调。用一个参数 σ 控制:
当 σ=0 且步长趋近无穷小时,DDIM 的离散更新收敛到一个常微分方程 (ODE):
也就是说,DDIM 采样 = 用欧拉法解这条 ODE。而 Flow Matching 是直接学一条 ODE 的速度场。两者都在"解 ODE",只是:
这就是 Tab 4 "统一视角"里"DDIM 与 FM 殊途同归"的精确含义——它们解的是同类 ODE,区别在路径直不直。
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)
| 维度 | DDPM | DDIM | Flow Matching |
|---|---|---|---|
| 本质 | 生成范式(训练+采样) | DDPM 的采样器(不改训练) | 生成范式(训练+采样) |
| 训练目标 | 预测噪声 ε | 不训练,复用 DDPM 的 ε_θ | 预测速度 v=x₁−x₀ |
| 路径 | 加噪马尔可夫链(弯) | 同 DDPM 的边缘分布(弯) | 线性插值(直) |
| 采样性质 | 随机 (σ 最大) | 确定性 (σ=0) | 确定性 (解 ODE) |
| 采样步数 | 100~1000 | 10~50 | 4~10 |
| 数学对象 | 反向 SDE 离散化 | 概率流 ODE 离散化 | 直接学 ODE 速度场 |
| 能否跳步 | 否(逐步马尔可夫) | 是 | 是 |
| 方法 | 训练 | 推理采样器 | 仓库 |
|---|---|---|---|
| DP (默认) | DDPM loss, K=100 | DDIM, 16 步 | dp |
| GR00T 动作头 | FM loss (预测 v) | ODE 欧拉, 4 步 | starVLA |
| π0 | FM loss (预测 v) | ODE 欧拉, 10 步 | openpi |
DP 是"DDPM 训练 + DDIM 采样"的典型;π0/GR00T 是"FM 训练 + ODE 采样"。看懂这两行,就理解了当前动作生成的两条主流技术路线。
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) # 就是公式里的 ‖ε - ε_θ‖²
F.mse_loss(pred, noise) 一行。add_noise 帮你做了 √ᾱ 那套加权。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)就切换采样方式,训练代码完全不用动——这是范式与实现解耦的好例子。
| 图像扩散 (DDPM 原版) | Diffusion Policy | |
|---|---|---|
| x₀ (生成对象) | 图片 [B,3,H,W] | 动作序列 [B,Tp,Da] |
| 条件 | 类别/文本 | 视觉观测 obs (FiLM/cross-attn) |
| 网络 | 2D U-Net | 1D U-Net (时间卷积) 或 Transformer |
| 采样输出后 | 直接是图 | 取中间 Ta 步执行 (receding horizon) |
DiT (Diffusion Transformer, Peebles & Xie 2022):把扩散模型里那个"去噪网络"从 U-Net 换成 Transformer。
核心问题:Transformer 怎么吃"扩散步 t"和"条件 c"?答案是 AdaLN(Adaptive Layer Norm)——把 (t, c) 编码成每层 LayerNorm 的 scale/shift。
误解的根源在一句话:DiT ≠ "用了 DDPM"。DiT 只是一个网络结构(Transformer 骨架 + AdaLN 条件注入),它本身不含任何加噪/去噪逻辑,也不含任何范式。DiT 原论文恰好用 DDPM 来训练它,但那只是作者当时的选择,不是 DiT 的一部分。
把一个去噪/预测网络的接口抽象出来,它长这样:
网络不知道、也不关心这个输出张量"是什么含义"。含义完全由外面的训练 loss 赋予:
| 外面用的 loss | 输出被训练成 | 这就是 |
|---|---|---|
| ‖输出 − ε‖² | 噪声 ε | DDPM 范式 |
| ‖输出 − (x₁−x₀)‖² | 速度 v | Flow Matching 范式 |
看 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 输出=噪声
| 角色 | 谁负责 | 具体内容 |
|---|---|---|
| 出题 + 判分 + 采样 | 范式 (DDPM 或 FM) | 怎么构造 xₜ、目标是 ε 还是 v、推理怎么采样 |
| 做题:吃 (xₜ,t,c) 算张量 | 架构 (DiT 或 U-Net) | Transformer+AdaLN 的矩阵运算 |
FM 负责"出题和判分",DiT 负责"做题"。两件事完全不重叠,反而缺一不可——FM 需要一个网络来预测速度场,DiT 正好胜任。"DiT + FM" = 用 Transformer 这个工具,去做 FM 这套题。
普通 LayerNorm:LN(x) = γ·norm(x) + β,γ/β 是固定可学参数。
AdaLN:让 γ/β 由条件动态生成:
这样每个扩散步、每个条件,都会调制出不同的归一化行为。是把条件注入 Transformer 的标准做法,等价于 CNN 里的 FiLM(见 DP 学习课)。
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
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)
| U-Net (DP-CNN) | DiT (Transformer) | |
|---|---|---|
| 骨架 | 卷积下采样+上采样+skip | 堆叠 Transformer block |
| 条件注入 | FiLM (scale/bias 调制卷积) | AdaLN (调制归一化) + cross-attn |
| 扩展性 | 受限 | 随参数/数据平滑提升 |
| 归纳偏置 | 局部性强(适合低频) | 全局注意(适合高频/长程) |
| 超参敏感 | 低(开箱即用) | 高(dropout/wd 需调) |
注意 FiLM 和 AdaLN 是同一思想的两种实现:用条件生成 scale/shift 去调制特征,只是一个调卷积输出、一个调 LayerNorm。
DDPM 走的是一条"加噪马尔可夫链",弯弯曲曲。Flow Matching (FM) 问:能不能让"噪声→数据"走直线?
最常用的是线性插值路径(Rectified Flow)。给数据 x₁ 和噪声 x₀~N(0,I):
这条直线的速度(对 t 求导)是常数:
训练目标——让网络预测这个速度:
推理——从噪声 x₀ 出发解 ODE(欧拉法,几步即可):
| 维度 | DDPM (DP 用) | Flow Matching (π0/GR00T 用) |
|---|---|---|
| 路径 | 加噪马尔可夫链(曲) | 线性插值(直) |
| 预测目标 | 噪声 ε | 速度场 v = x₁−x₀ |
| 训练 loss | ‖ε − ε_θ‖² | ‖v − v_θ‖² |
| 采样 | 反向链/DDIM (10-100步) | ODE 欧拉 (4-10步) |
| 时间方向 | t=0 数据, t=T 噪声 | 因实现而异(见下方坑) |
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 在这里合体。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 的几十步快一个量级。
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 # 反方向积分
DDPM 和 Flow Matching 看似不同,其实是同一个连续过程的两种刻画。生成可以统一写成一条微分方程,从噪声演化到数据:
| 量 | 含义 | 谁在用 |
|---|---|---|
| 分数 ∇ₓlog p(x) | 对数概率密度的梯度,指向高概率区 | Score-based 理论 |
| 噪声 ε | 加进去的高斯噪声 | DDPM / DP |
| 速度 v | 概率路径的瞬时速度 | Flow Matching / π0 |
三者可互相换算(在给定路径下)。例如 DDPM 的 ε_θ 与分数差一个 −1/√(1−ᾱₜ) 因子;线性路径下速度 v 与 ε、x₀ 也有线性关系。它们是同一信息的不同参数化。
| 方法 | 范式 | 预测量 | 架构 | 采样步 | 仓库 |
|---|---|---|---|---|---|
| DP-CNN | DDPM | ε | UNet1D + FiLM | DDIM 10-16 | dp |
| DP-Transformer | DDPM | ε | Transformer + cross-attn | DDIM 10-16 | dp |
| GR00T 动作头 | Flow Matching | v | DiT + AdaLN + cross-attn | ODE 4 | starVLA |
| π0 | Flow Matching | v | 双 Gemma (类DiT) | ODE 10 | openpi |
读到任何一个动作生成方法,都可以用这张表的五栏去拆解它:范式 / 预测量 / 架构 / 采样 / 条件注入。
| 方式 | 怎么做 | 用在 |
|---|---|---|
| FiLM | 条件→scale/bias 调制卷积输出 | DP-CNN |
| AdaLN | 条件→scale/shift 调制 LayerNorm | DiT / GR00T (扩散步t) |
| Cross-Attention | 动作 token 查询观测特征 | DP-Transformer, GR00T (观测) |
| Token 拼接 | 条件作为序列 token 一起进 Transformer | π0 (prefix-suffix) |
FiLM 与 AdaLN 本质相同(生成 scale/shift 调制特征),区别只是调制对象。扩散步 t 常走 AdaLN/FiLM,复杂观测常走 cross-attn 或 token 拼接。
【范式层】
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 大动作模型)
| 衍生方法 | 在哪个底座上改 | 解决什么问题 |
|---|---|---|
| DDIM | DDPM 采样 | 100步→10步,能实时 |
| Flow Matching | 替代 DDPM 范式 | 直线路径,更少采样步、训练更稳 |
| Consistency Model | 蒸馏扩散/FM | 1-2 步生成,极致提速 |
| DiT | 替代 U-Net 架构 | 可扩展性,适配大模型 |
| Diffusion Policy | DDPM 应用到动作 | 多模态动作、训练稳定 |
| π0 / GR00T | FM+DiT 应用到 VLA | 实时动作生成 + 接 VLM 语义 |
选 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 动作专家 |
选完点提交,即时判分 + 解析。