生成模型 / 采样与蒸馏 原理笔记进阶 30 分钟阅读 查看源文件 ↗

采样器与 ODE Solver#

本页内容4 个部分
查看概览图
FOUNDATIONS · 07

所有采样器
都是同一条 ODE 的数值解

采样器名字很多,可分解的其实只有五层:训练目标、时间参数化、schedule、solver 阶数、是否 ancestral。

训练目标 ε / x₀ / v / score
决定网络学什么
时间参数化 t ∈ [0,T] / σ_t / λ_t
DDPM / EDM / DPM-Solver
Schedule linear / cosine / Karras(ρ)
步长往哪儿密
Solver Euler / Heun / DPM-Solver / UniPC
积分阶数与策略
Ancestral 开 / 关
每步是否再注噪

换一个名字,动的往往只是其中一层——这五层可以自由组合,这就是采样器名字爆炸的原因。

ρKarras schedule (EDM)

把步数堆到低噪端

step i →σ
均匀Karras ρ = 7

低噪端图像细节正在成形,误差最伤质量;Karras 用 ρ 把步长压到那一段。

两个旋钮 步长往哪儿密
要不要再注噪
ηAncestral 旋钮

同一个起点,走不走直路

t →x
ODE · η = 0SDE · η = 1

η 是连续旋钮:0 退化为标准 Euler,1 最接近原始 DDPM,中间是两者的插值。轨迹不同,但边际分布相同。

同样 20 步阶数换精度条形 = 每 NFE 走过的步数
Euler / DDIM · 1 阶1 NFE
Heun · 2 阶2 NFE
≈ 40 步「20 步 Heun」的真实成本
约等于「40 步 Euler」
ODE非 Ancestral

确定、稳、步数少

  • 解 PF-ODE同 seed 同 prompt 同结果
  • 多样性低结果被 seed 完全决定
  • 典型 20–50 步Euler / DDIM / DPM-Solver / DPM++
    稳
SDEAncestral

多样、但要更多步

  • 解反向 SDE每步注入额外噪声
  • 多样性高即使 seed 相同也有变化
  • 典型 30–80 步Euler a / DPM++ SDE / DPM++ 2M SDE
    低步数易失稳
按 NFE 对齐才公平Karras 把步长压到低噪端η 是 ODE↔SDE 的连续旋钮

扩散/Flow 采样器全景 · 一阶 / 二阶 / DPM-Solver · Karras · 面试串讲

1. 生成采样与数值求解#

1.1 为什么要单独讲采样器#

训练和采样是解耦的#

扩散模型的训练只做一件事:给定 (xt,t)(x_t, t),让网络学会预测噪声 ϵ\epsilon(或 x0x_0、vv、score 之一,本质等价)。训练完成后,网络就是一个固定的函数 ϵθ(xt,t)\epsilon_\theta(x_t, t)。

但从纯噪声 xTx_T 一路走到干净图像 x0x_0 的去噪轨迹,完全由"采样器(Sampler / Scheduler)"决定——它把连续时间的 ODE/SDE 离散化成一步一步的更新规则。

核心解耦

网络预测决定了每个位置的"向量场方向";采样器决定了"如何沿着这个向量场积分"。两者是正交的——同一个训练好的模型,换个采样器就能跑得更快或者更稳。

不同采样器差距有多大#

在同样一个 Stable Diffusion 1.5 模型上:

  • 原始 DDPM 采样:1000 步,质量好但慢到不可用。
  • DDIM:50 步,同质量。
  • DPM-Solver++ 2M + Karras schedule:20-25 步,质量和 DDIM 50 步基本持平,甚至更好。

也就是说——仅仅换采样器,就能把推理成本压缩到原来的 1/40。不需要改模型、不需要重新训练,完全免费。这就是为什么工业界对采样器如此重视。

一张图看清全景#

层级 算法 说明
训练目标 ϵ\epsilon / x0x_0 / vv / score 决定网络学什么
时间参数化 t∈[0,T]t \in [0,T] / σt\sigma_t / λt\lambda_t DDPM / EDM / DPM-Solver 的不同选择
Schedule linear / cosine / Karras(ρ\rho) 决定步长分布(哪里密、哪里稀)
Solver Euler / Heun / DPM-Solver / UniPC 决定数值积分的阶数与策略
Ancestral 开 / 关 是否在每步注入额外噪声(SDE vs ODE)

实际使用时,用户选的一个"采样器"其实是上面几层的组合(例如"DPM++ 2M Karras" = ϵ\epsilon-pred + Karras schedule + DPM-Solver++ 2M + 非 Ancestral)。这一篇我们把每一层拆开看。

1.2 统一 ODE 视角:所有采样器都是数值 solver#

从 SDE 到 Probability Flow ODE#

Song et al. 2021("Score-Based Generative Modeling through SDEs")指出:DDPM 的前向加噪可以写成一个 SDE:

dx=f(x,t) dt+g(t) dwdx = f(x, t)\, dt + g(t)\, dw

前向 SDE——ff 是漂移项,gg 是扩散系数,dwdw 是 Wiener 过程(随机项)

反向过程则是一个反向 SDE:

dx=[f(x,t)−g(t)2∇xlog⁡pt(x)]dt+g(t) dwˉdx = \left[f(x, t) - g(t)^2 \nabla_x \log p_t(x)\right] dt + g(t)\, d\bar w

反向 SDE——用到 score function ∇xlog⁡pt(x)\nabla_x \log p_t(x),其中 dwˉd\bar w 是反向时间的 Wiener 过程

Song 证明了一个关键事实:这条 SDE 存在一个和它有相同边际分布的 ODE,叫做 Probability Flow ODE(PF-ODE):

dxdt=f(x,t)−12g(t)2∇xlog⁡pt(x)\frac{dx}{dt} = f(x, t) - \tfrac{1}{2} g(t)^2 \nabla_x \log p_t(x)

PF-ODE——把随机项去掉、系数减半。和原 SDE 在每个时刻 tt 产生同样的分布 pt(x)p_t(x)

**一句话理解:**原来的 SDE 是"带噪声的随机游走",PF-ODE 是"把所有路径的平均行为写成的确定性流"。两者生成的样本分布相同,但 PF-ODE 是一条确定的轨迹,可以用成熟的 ODE solver 来积分。

Flow Matching:原生就是 ODE#

Flow Matching 跳过了 SDE,直接定义 ODE:

dxdt=vθ(x,t)\frac{dx}{dt} = v_\theta(x, t)

Flow ODE——vθv_\theta 是学到的向量场,直接就是 ODE 的右端项

PF-ODE 和 Flow ODE 在连续时间极限下是等价的,只是参数化方式不同:PF-ODE 用 score 和扩散系数拼出来,Flow ODE 直接学向量场。

采样器 = ODE/SDE 数值 solver#

所有采样器的统一本质

任何采样器(DDIM, Euler, Heun, DPM-Solver, UniPC, ...)都是在解下面两种方程之一:

• 如果是 SDE solver(Ancestral、DDPM 原版):每步更新 = 漂移项 + 扩散项(随机噪声)
• 如果是 ODE solver(DDIM、Euler、DPM-Solver、Heun):每步更新只有漂移项,完全确定性

不同之处只有两件事:数值积分的阶数(误差 O(Δtk)O(\Delta t^k) 的 kk 多大),以及对扩散方程结构(半线性、log-SNR 等)的利用程度。

本文约定#

为方便和主流实现对齐,后文统一采用"扩散方向":t=0t=0 是数据,t=Tt=T 是噪声;采样从 t=Tt=T 递减到 t=0t=0。Flow Matching 原生的 t=0t=0 是噪声、t=1t=1 是数据的约定仅在 §07 中使用。

2. 从一阶到高阶方法#

2.1 一阶方法:Euler / DDIM / Euler Ancestral#

Euler:最朴素的一阶方法#

ODE dxdt=v(x,t)\frac{dx}{dt} = v(x, t) 的最简单数值解法。对每一步时间区间 [t,t+Δt][t, t+\Delta t],用当前点的切线外推:

xt+Δt=xt+v(xt,t) Δtx_{t+\Delta t} = x_t + v(x_t, t)\,\Delta t

Euler 前向步进——只用当前点的一次函数求值

每步调用一次网络(1 次 NFE)。在向量场足够光滑等条件下,局部截断误差为 O(Δt2)O(\Delta t^2),固定积分区间上的全局误差为 O(Δt)O(\Delta t)。

NFE = Number of Function Evaluations,即"网络调用次数"。在扩散/FM 里,一次"函数求值"特指调一次 ϵθ\epsilon_\theta——网络 forward 是最贵的部分,代数运算可以忽略,所以 NFE 是重要的算法成本指标;实际延迟还取决于模型、分辨率、batch、CFG 实现和 kernel。\

注意:步数 ≠ NFE。Euler 一步 1 次 NFE,Heun 一步 2 次 NFE,DPM-Solver 2M 一步也是 1 次(靠历史缓存)。比较采样器效率时看总 NFE,不是步数——这就是为什么 DPM++ 2M 的"single-step NFE=1"是卖点(二阶精度 + 成本等同 Euler)。

DDIM:扩散版的一阶 solver#

DDIM 的详细推导已在 3_DDPM_DDIM §07 给出,这里只强调它在采样器谱系中的位置:

  • DDIM 本质是扩散 ODE 的一阶离散化——和 Euler 同阶,但基于 DDPM 的 αˉt\bar\alpha_t 参数化,而不是原始的时间 tt。
  • DDIM 的每步公式 xt−Δ=αˉt−Δ x^0+1−αˉt−Δ−σt2 ϵθ+σtzx_{t-\Delta} = \sqrt{\bar\alpha_{t-\Delta}}\,\hat x_0 + \sqrt{1-\bar\alpha_{t-\Delta}-\sigma_t^2}\,\epsilon_\theta + \sigma_t z,当 σt=0\sigma_t = 0 时就是纯 ODE。
  • 确定性 DDIM 在相应的状态缩放与噪声变量变换后可写成 Euler 更新;只把时间变量改名并不足够,也不是任意参数化下的普通 Euler。

同一件事两种说法:"DDIM = 一阶 ODE solver in αˉ\bar\alpha-time" ≡ "Euler = 一阶 ODE solver in σ\sigma-time"。两者的数值结果在同步数下接近但不完全相同,差别来自时间参数化不同(step 分布不同)。

Euler Ancestral:一阶 + 每步注噪#

"Ancestral" 来自统计学里的"祖先采样"(ancestral sampling),在扩散里特指:每步 Euler 之后额外注入一点噪声,让采样过程从纯 ODE 变回 SDE。

核心思路#

**先走得比目标更干净,再注入新鲜噪声把方差补回目标噪声水平。**目标是 σt−1\sigma_{t-1},但 Euler 不直接走到那;而是走到一个更干净的 σdown<σt−1\sigma_{\text{down}} < \sigma_{t-1},然后加独立高斯噪声 σup⋅z\sigma_{\text{up}}\cdot z 把噪声水平补回 σt−1\sigma_{t-1}。方差账要平:σdown2+σup2=σt−12\sigma_{\text{down}}^2 + \sigma_{\text{up}}^2 = \sigma_{t-1}^2。

真正的旋钮:η\eta#

k-diffusion 里有一个超参 η∈[0,1]\eta \in [0, 1]("随机性强度"),它才是用户调的量:

σup=η⋅σt−12(σt2−σt−12)σt2\sigma_{\text{up}} = \eta \cdot \sqrt{\frac{\sigma_{t-1}^2(\sigma_t^2 - \sigma_{t-1}^2)}{\sigma_t^2}}

根号里那一坨是 DDPM 反向后验方差 σpost2\sigma_{\text{post}}^2(见下方"数学背景")

η\eta σup\sigma_{\text{up}} σdown\sigma_{\text{down}} 行为
00 00 σt−1\sigma_{t-1} 纯 ODE,退化为标准 Euler
(0,1)(0,1) 部分 <σt−1< \sigma_{t-1} ODE 和 SDE 之间插值
11 满 最小 最接近原始 DDPM 的 SDE 采样
完整流程(顺序很重要)#
  1. 选 η\eta——决定要多少随机性。
  2. 由上式算 σup\sigma_{\text{up}}——要注入多少新噪声。
  3. 由方差约束反推 σdown=σt−12−σup2\sigma_{\text{down}} = \sqrt{\sigma_{t-1}^2 - \sigma_{\text{up}}^2}——Euler 该走到哪。
  4. Euler 从 σt\sigma_t 走到 σdown\sigma_{\text{down}}。
  5. 加 σup⋅z\sigma_{\text{up}} \cdot z(z∼N(0,I)z \sim \mathcal{N}(0, I))把噪声水平补回 σt−1\sigma_{t-1}。

合并成一行就是:

xt−1=xt+σdown−σtσt(xt−x^0)+σup zx_{t-1} = x_t + \frac{\sigma_{\text{down}} - \sigma_t}{\sigma_t}(x_t - \hat x_0) + \sigma_{\text{up}}\, z

Euler Ancestral 一步更新——η=0\eta=0 时第二项退化为 Euler 到 σt−1\sigma_{t-1}、第三项为 0

数学背景:σpost\sigma_{\text{post}} 是什么#

根号里那一坨 σpost2=σt−12(σt2−σt−12)/σt2\sigma_{\text{post}}^2 = \sigma_{t-1}^2(\sigma_t^2 - \sigma_{t-1}^2)/\sigma_t^2 是 DDPM 反向过程的后验方差——给定起点 x0x_0 和当前点 xtx_t 后,上一时刻 xt−1x_{t-1} 的剩余不确定性。推导(两个高斯的乘积):

q(xt−1∣xt,x0)∝q(xt∣xt−1)⏟N(xt−1, σt2−σt−12)⋅q(xt−1∣x0)⏟N(x0, σt−12)⇒σpost2=σt−12(σt2−σt−12)σt2q(x_{t-1}\mid x_t, x_0) \propto \underbrace{q(x_t\mid x_{t-1})}_{\mathcal{N}(x_{t-1},\,\sigma_t^2 - \sigma_{t-1}^2)} \cdot \underbrace{q(x_{t-1}\mid x_0)}_{\mathcal{N}(x_0,\,\sigma_{t-1}^2)} \quad\Rightarrow\quad \sigma_{\text{post}}^2 = \frac{\sigma_{t-1}^2(\sigma_t^2 - \sigma_{t-1}^2)}{\sigma_t^2}

σpost\sigma_{\text{post}} 是"这一步允许注入的最大噪声预算",η\eta 是"预算使用比例"

只记一句:σpost\sigma_{\text{post}} 是随机性预算,η\eta 是使用比例。η=0\eta=0 不花 → 纯 Euler;η=1\eta=1 花满 → DDPM。

Ancestral 和非 Ancestral 的本质区别#
维度 非 Ancestral(ODE) Ancestral(SDE)
数值结构 解 PF-ODE 解反向 SDE
确定性 完全确定性(同 seed 同 prompt 同结果) 随机——每步注入额外噪声
多样性 低(被 seed 完全决定) 高(即使 seed 相同也有变化)
采样稳定性 高 低步数下容易失稳
典型步数 20-50 30-80(需要更多步压低随机项影响)
常见名称 Euler / DDIM / DPM-Solver / DPM++ Euler a / DPM++ SDE / DPM++ 2M SDE

面试视角

Ancestral 不是"更好"或"更差"——它是另一个 tradeoff 点:牺牲确定性换更高的多样性、有时也换更锐利的纹理(因为噪声注入打破了 ODE 的平滑性)。
工业产品(要可复现)几乎都用非 Ancestral;创作场景里会用 Ancestral 搏一些"惊喜感"。

2.2 二阶方法:Heun 与截断误差分析#

Euler 的问题:单点切线#

Euler 只用区间起点的导数做外推。如果向量场在区间内发生变化(曲率大),这个外推就不准。例如向量场是从 −1-1 渐变到 +1+1 的直线,Euler 用起点 −1-1 外推,整段区间都偏左。

Heun 方法(改进 Euler)#

Heun 方法的思路:用起点和终点的导数平均值做外推。流程:

  1. Predictor:先用 Euler 预测一个粗略的终点 x~t−1=xt+v(xt,t) Δt\tilde x_{t-1} = x_t + v(x_t, t)\,\Delta t。
  2. Corrector:在这个粗略终点处算一次向量场 v(x~t−1,t−1)v(\tilde x_{t-1}, t-1)。
  3. 平均:用两次 vv 的平均重算终点:

xt−1=xt+12[v(xt,t)+v(x~t−1,t−1)]Δtx_{t-1} = x_t + \frac{1}{2}\left[v(x_t, t) + v(\tilde x_{t-1}, t-1)\right] \Delta t

Heun 一步更新——两次网络调用(predictor + corrector),但误差阶降到 O(Δt3)O(\Delta t^3)

每步的 NFE 是 2。最后一步通常跳过 corrector(因为没有下一个时刻),所以 NN 步总 NFE = 2N−12N - 1。

截断误差分析#

数值分析里的核心公式:对 kk 阶方法,局部截断误差 = O(Δtk+1)O(\Delta t^{k+1}),累计 N=T/ΔtN = T/\Delta t 步后,全局误差 = O(Δtk)O(\Delta t^k)。下面列出一阶、二阶、四阶方法的对比(其中 RK4 是数值分析里的经典方法:一步内求 4 次斜率加权平均,四阶精度,"通用 ODE solver"的默认选项):

方法 阶数 单步 NFE 局部截断误差 全局误差
Euler / DDIM 1 1 O(Δt2)O(\Delta t^2) O(Δt)O(\Delta t)
Heun 2 2 O(Δt3)O(\Delta t^3) O(Δt2)O(\Delta t^2)
RK4(扩散不用) 4 4 O(Δt5)O(\Delta t^5) O(Δt4)O(\Delta t^4)

RK4 在通用 ODE 里一骑绝尘,但在扩散里每步 NFE=4 成本太高,详见本节末尾"为什么不用 RK4?"。

为什么二阶比一阶更省步数#

同精度下的步数比较

Heun 每步 NFE 是 Euler 的 2 倍,但全局误差阶从 O(Δt)O(\Delta t) 降到 O(Δt2)O(\Delta t^2)。

要让 Euler 和 Heun 达到相同精度 ϵ\epsilon:
• Euler 需要 Δt∼ϵ\Delta t \sim \epsilon,步数 N∼1/ϵN \sim 1/\epsilon
• Heun 需要 Δt∼ϵ\Delta t \sim \sqrt\epsilon,步数 N∼1/ϵN \sim 1/\sqrt\epsilon

总 NFE 比较:Heun 为 2/ϵ2/\sqrt\epsilon,Euler 为 1/ϵ1/\epsilon。只要 ϵ<1/4\epsilon < 1/4,Heun 就赢。 对生成模型这个早就满足。

为什么不用 RK4?#

先介绍 RK4:Runge-Kutta 四阶方法是数值分析里最经典的通用 ODE solver,一步内采 4 个斜率样本,然后加权平均:

  1. k1=v(xt,t)k_1 = v(x_t, t)——起点斜率
  2. k2=v(xt+Δt2k1, t+Δt2)k_2 = v(x_t + \tfrac{\Delta t}{2} k_1,\ t + \tfrac{\Delta t}{2})——用 k1k_1 走半步后的中点斜率
  3. k3=v(xt+Δt2k2, t+Δt2)k_3 = v(x_t + \tfrac{\Delta t}{2} k_2,\ t + \tfrac{\Delta t}{2})——用 k2k_2 再走半步后的中点斜率(修正一次)
  4. k4=v(xt+Δt k3, t+Δt)k_4 = v(x_t + \Delta t\, k_3,\ t + \Delta t)——终点斜率

xt+Δt=xt+Δt6(k1+2k2+2k3+k4)x_{t+\Delta t} = x_t + \frac{\Delta t}{6}(k_1 + 2 k_2 + 2 k_3 + k_4)

RK4 加权平均——中点权重更大,相当于高阶 Simpson 积分

单步 NFE = 4,局部截断误差 O(Δt5)O(\Delta t^5),全局 O(Δt4)O(\Delta t^4)。通用 ODE 里是默认选项,精度阶数和计算量的性价比极佳。

**那扩散里为啥不用?**两个原因:

  • NFE 成本太高:扩散里一次 vθv_\theta 是一整个神经网络 forward,比数值分析里的普通函数贵几个数量级。RK4 每步 4 次 NFE,和 4 步 Euler 成本相同——除非 RK4 能把步数砍到 ≤1/4\le 1/4,否则不划算。
  • 高阶精度被网络误差淹没:ϵθ\epsilon_\theta 的学习误差远大于 Heun 截断误差,再往上升阶带来的额外精度被网络噪声吞掉,看不到。

实际上 Heun(二阶)已经接近 diminishing return。比 Heun 更聪明的做法是下一节讲的 DPM-Solver——它利用扩散方程的半线性结构,在单步 NFE=1 的情况下就能拿到二阶以上精度,完全绕过了 RK4 的高 NFE 困境。

2.3 DPM-Solver / DPM-Solver++#

核心动机:扩散 ODE 是"半线性"的#

扩散的反向 ODE 写成 α,σ\alpha, \sigma 形式后长这样:

dxtdt=f(t) xt+g(t) ϵθ(xt,t)\frac{dx_t}{dt} = f(t)\, x_t + g(t)\, \epsilon_\theta(x_t, t)

扩散反向 ODE 的半线性结构——左项是 xtx_t 的线性函数,右项是非线性神经网络

其中 f(t)=dlog⁡αtdtf(t) = \frac{d \log \alpha_t}{dt},g(t)g(t) 和 αt,σt\alpha_t, \sigma_t 也都只是时间的函数,跟 xtx_t 无关。

半线性(semi-linear)的关键洞察

一个通用 ODE solver(如 Euler、Heun)把 整个右端 f(t)xt+g(t)ϵθf(t)x_t + g(t)\epsilon_\theta 当成一个黑箱来数值近似,会同时累积两部分的截断误差。

但线性部分 f(t)xtf(t)x_t 可以解析求解——不需要数值近似。那为什么不只对非线性部分做数值近似,让线性部分的误差彻底消失?这就是 DPM-Solver (Lu et al., 2022) 的核心思路。

指数积分子(Exponential Integrator)#

对半线性 ODE,有一个经典的变分常数法(积分因子法)可以得到精确解:

xt=e∫stf(τ)dτ xs+∫ste∫τtf(u)du g(τ) ϵθ(xτ,τ) dτx_t = e^{\int_s^t f(\tau)d\tau}\, x_s + \int_s^t e^{\int_\tau^t f(u)du}\, g(\tau)\, \epsilon_\theta(x_\tau, \tau)\, d\tau

半线性 ODE 的精确解——线性部分被指数因子完全吸收,只剩非线性部分的积分

如果 ϵθ\epsilon_\theta 是已知函数,这个积分可以精确计算。问题是 ϵθ\epsilon_\theta 要沿轨迹求,我们不知道 xτx_\tau。DPM-Solver 的做法:只对这个积分做数值近似(Taylor 展开),而线性部分保持精确。

变量代换:λt=log⁡(αt/σt)\lambda_t = \log(\alpha_t / \sigma_t)#

DPM-Solver 的另一个技术核心是把时间变量换成 log-SNR:

λt=log⁡αtσt,σt=1−αˉt,αt=αˉt\lambda_t = \log \frac{\alpha_t}{\sigma_t}, \qquad \sigma_t = \sqrt{1-\bar\alpha_t}, \quad \alpha_t = \sqrt{\bar\alpha_t}

log-SNR 参数化——半信噪比的对数。t=0t=0 时 λ→+∞\lambda \to +\infty(高 SNR),t=Tt=T 时 λ→−∞\lambda \to -\infty(低 SNR)

上一节那个精确解里积分因子 g(τ)/ατg(\tau)/\alpha_\tau 写起来很丑。换到 λ\lambda 之后,整个解能写成一个干净的指数积分:

xt=αtαs xs−αt∫λsλte−λ ϵθ(λ) dλx_t = \frac{\alpha_t}{\alpha_s}\, x_s - \alpha_t \int_{\lambda_s}^{\lambda_t} e^{-\lambda}\, \epsilon_\theta(\lambda)\, d\lambda

换到 λ\lambda-时间后的精确解——线性部分被 αt/αs\alpha_t/\alpha_s 完全吸收,非线性部分只剩一个干净的 e−λ ϵθe^{-\lambda}\,\epsilon_\theta 积分

这就是为什么所有 DPM-Solver 公式里都出现 e−λe^{-\lambda}、eλe^\lambda 这样的因子——不是人为凑的,是换元后自然长出来的。

Taylor 展开:近似剩下的非线性积分#

现在整个数值问题只剩一个积分要近似:

∫λsλte−λ ϵθ(λ) dλ\int_{\lambda_s}^{\lambda_t} e^{-\lambda}\, \epsilon_\theta(\lambda)\, d\lambda

唯一的数值近似发生在这里,线性部分零误差

DPM-Solver-kk 的做法是对被积函数里的 ϵθ(λ)\epsilon_\theta(\lambda) 做 kk 阶 Taylor 展开,积分就能解析算出:

  • k=1k=1(一阶):ϵθ\epsilon_\theta 视为常数——结果正好是 DDIM 的精确重写,所以 DDIM = DPM-Solver-1。
  • k=2k=2(二阶):加一个差分项(ϵθ\epsilon_\theta 的一阶导数,用有限差分估计)→ DPM-Solver-2。
  • k=3k=3(三阶):再加二阶差分项 → DPM-Solver-3。

高阶项的"差分"怎么算——用多个 ϵθ\epsilon_\theta 采样点的有限差分。这就引出 single-step vs multi-step 的区别:

  • Single-step(2S):在当前步内部多采一个中间点的 ϵθ\epsilon_\theta,单步 NFE = 2
  • Multi-step(2M):复用上一步已经算过的 ϵθ\epsilon_\theta 做差分,单步 NFE = 1

一阶 / 二阶 / 三阶 Taylor 展开推导#

把上面的 Taylor 展开和换元后的积分合起来,得到 DPM-Solver 所有阶数共用的骨架:

xt−1=αt−1αtxt−αt−1(eh−1) ϵθ(xt,t)+高阶修正项x_{t-1} = \frac{\alpha_{t-1}}{\alpha_t} x_t - \alpha_{t-1}(e^{h}-1)\, \epsilon_\theta(x_t, t) + \text{高阶修正项}

DPM-Solver 统一形式——h=λt−1−λth = \lambda_{t-1} - \lambda_t 是 log-SNR 步长,eh−1e^h - 1 是积分因子的闭式结果,高阶项用额外的 ϵθ\epsilon_\theta 差分

下面具体看每一阶怎么从积分 I=∫λsλte−λ ϵθ(λ) dλI = \int_{\lambda_s}^{\lambda_t} e^{-\lambda}\, \epsilon_\theta(\lambda)\, d\lambda 里算出来。

Order 1:ϵθ\epsilon_\theta 视为常数#

最简单的近似:ϵθ(λ)≈ϵθ(λs)\epsilon_\theta(\lambda) \approx \epsilon_\theta(\lambda_s),提出积分号外:

I≈ϵθ(λs)∫λsλte−λ dλ=ϵθ(λs) (e−λs−e−λt)I \approx \epsilon_\theta(\lambda_s) \int_{\lambda_s}^{\lambda_t} e^{-\lambda}\, d\lambda = \epsilon_\theta(\lambda_s)\,(e^{-\lambda_s} - e^{-\lambda_t})

k=1k=1:闭式解,单步只要 1 次 ϵθ\epsilon_\theta 调用 → NFE = 1,结果恰好是 DDIM

Order 2:加一阶导数,中点有限差分估计#

展开到线性项:ϵθ(λ)≈ϵθ(λs)+ϵθ′(λs)(λ−λs)\epsilon_\theta(\lambda) \approx \epsilon_\theta(\lambda_s) + \epsilon_\theta'(\lambda_s)(\lambda - \lambda_s)。积分可解析算出,但需要 ϵθ′(λs)\epsilon_\theta'(\lambda_s)——模型不直接给导数,用有限差分估计:

ϵθ′(λs)≈ϵθ(λu)−ϵθ(λs)h/2,λu=λs+h/2\epsilon_\theta'(\lambda_s) \approx \frac{\epsilon_\theta(\lambda_u) - \epsilon_\theta(\lambda_s)}{h/2}, \qquad \lambda_u = \lambda_s + h/2

在步长中点 λu\lambda_u 再采一次 ϵθ\epsilon_\theta,做预测-修正 → NFE = 2 per step

算法结构:先用一阶走到中点 λu\lambda_u 得到 xux_u,调用 ϵθ(xu,λu)\epsilon_\theta(x_u, \lambda_u),再合成两次 ϵ\epsilon 走完整步。这就是 DPM-Solver-2S。

Order 3:再加二阶导数,需要 3 个采样点#

展开到二次项,多出一个 ϵθ′′(λs)\epsilon_\theta''(\lambda_s),需要 3 个不同 λ\lambda 上的 ϵθ\epsilon_\theta 值才能做二阶有限差分 → 单步 NFE = 3。

对比一览#
阶数 Taylor 保留项 每步 NFE 局部截断误差 典型总步数
1 常数(零阶) 1 O(h2)O(h^2) 50
2 + 一阶导 2(2S)/ 1(2M) O(h3)O(h^3) 20(40 NFE)
3 + 二阶导 3(3S)/ 1(3M) O(h4)O(h^4) 12(36 NFE)

**核心直觉:**阶数越高,单步代价越大,但每步能跨越的 log-SNR 距离更长 → 总 NFE 反而下降(50 → 40 → 36)。所以 DPM-Solver 不是"追求单步精确",而是"用更贵的单步换更少的总步数"。

为什么不继续往上做 4 阶、5 阶?

高阶有限差分会放大 ϵθ\epsilon_\theta 的神经网络噪声——ϵθ\epsilon_\theta 本身有拟合误差,对它做二阶、三阶差商相当于对噪声求高阶导,信噪比会急剧下降。所以 3 阶左右就是收益拐点,再往上收益递减甚至倒挂。

Multi-step(Adams 风格)→ DPM-Solver++

2S/3S 每步多算的那几次 ϵθ\epsilon_\theta,其实可以复用上一步已经算过的值——这就是 2M/3M,借鉴了 ODE 数值求解里的 Adams 多步法。单步 NFE 降到 1,精度不变。DPM-Solver++ 2M/3M 就是这条路线的工业级版本。

Single-step vs Multi-step:2S 和 2M 的区别#

同一阶数的两种实现

DPM-Solver-2S(single-step):每一步内部,除了起点 ϵθ(xt,t)\epsilon_\theta(x_t, t) 外,再额外算一个中间点的 ϵθ\epsilon_\theta 做二阶差分。单步 NFE = 2。

DPM-Solver-2M(multi-step):利用前一步已经算过的 ϵθ(xt+1,t+1)\epsilon_\theta(x_{t+1}, t+1) 做差分。单步 NFE = 1(和 Euler 相同!),靠历史信息拿到二阶精度。

2M 更常用——同质量下 NFE 只有 2S 的一半。代价是第一步没有历史,需要用一阶启动。

类比:开车预测 5 分钟后的位置#

你在开车,要预测「5 分钟后车在哪」。每次"抬头看速度表"相当于一次 ϵθ\epsilon_\theta 调用(贵操作)。

  • 一阶(DDIM / Euler):看一眼速度表 60 km/h,假设 5 分钟都是这个速度 → 向前 5 km。NFE = 1,但忽略了加速度。
  • 二阶单步(2S)——往前侦察:先按 60 推 2.5 分钟到中间点,在中间点再看一眼速度表(70 km/h),发现在加速 → 用两次读数估出加速度,修正成约 5.4 km。NFE = 2。
  • 二阶多步(2M)——回头看记录:5 分钟前我已经看过一次速度表(55 km/h),缓存还在!直接对比现在的 60 → 过去 5 分钟加速了 5 km/h,外推未来同样 → 约 5.4 km。NFE = 1,精度和 2S 相当。

一个"往前侦察",一个"回头看记录",都能估出一阶导数(加速度),但 2M 免费。

M 不是白嫖:隐含假设和失效场景#

为什么 2M 有时候不靠谱

2S 用区间内信息:中间点就在 [t,t+h][t, t+h] 里,差分估计的是这一步内的实际变化。

2M 用区间外信息:上一步的读数在 [t−hprev,t][t-h_{\text{prev}}, t],是过去的趋势——默认假设"趋势会延续"。

这个假设在三种情况下会崩:

  • 步长突变:上一步很短、这一步很长,外推失真
  • ϵθ\epsilon_\theta 变化剧烈:采样两头(高噪声起点 / 低噪声终点),ϵ\epsilon 可能急剧变化
  • 冷启动:第一步根本没有历史,必须先用单步法启动

实践中的混合策略:

  • 2M 在中间阶段(步长均匀、ϵθ\epsilon_\theta 变化平缓)效率极高
  • 2S 在边界和剧变区更鲁棒
  • 真实实现通常是「启动阶段用 2S,稳定后切 2M」

采样中 ϵθ\epsilon_\theta 随 λ\lambda 变化通常平滑(神经网络输出的连续函数),所以 2M 的"复用历史"几乎总是成立——这是 SD 默认用 DPM++ 2M 的根本原因。

DPM-Solver 和 DPM-Solver++:一个字符之差#

DPM-Solver++ (Lu et al., 2022, v2) 的核心改动:把 Taylor 展开的目标从 ϵθ\epsilon_\theta 换成 x^0=(xt−σtϵθ)/αt\hat x_0 = (x_t - \sigma_t\epsilon_\theta)/\alpha_t,并基于新参数化重新推导更新公式。

版本 参数化 适用场景
DPM-Solver ϵ\epsilon-prediction 低 CFG(< 3)下数值稳定
DPM-Solver++ x0x_0-prediction 高 CFG(5~15)下不发散
为什么高 CFG 下更稳?三条独立理由#

① 避免大数相消

ϵ\epsilon-解 = 线性项 − 积分项(减法),高噪声区两项都很大、差值对误差敏感;x0x_0-解 = 线性项 + 积分项(加法),数值稳健。

② 积分核衰减方向对了

ϵ\epsilon-形式被积函数带 e−λe^{-\lambda},高噪声区(λ\lambda 负)这项爆炸放大 ϵθ\epsilon_\theta 的误差;x0x_0-形式带 eλe^{\lambda},同一区域反而衰减。

③ x^0\hat x_0 可以被裁剪

预测的干净图像有天然值域约束,可以配合 dynamic thresholding 截断异常值;而 ϵ\epsilon 是噪声,没有自然的值域可裁。

数学上两种参数化等价,但数值条件数差异显著——这就是为什么 CFG scale = 5~15 时 DPM-Solver++ 几乎是唯一选择。

**面试结论:**DPM-Solver++ 2M 是 SD 1.x/2.x 时代的黄金采样器——单步 NFE=1、二阶精度、高 CFG 不崩,同质量下只要 Euler 一半步数(15-25 步 vs 30-50 步)。

3. 日程与采样选择#

3.1 Karras schedule(EDM)#

Schedule 是什么?和 solver 的区别#

一个采样流程需要两样东西:

  • Solver:决定每一步从 σi\sigma_i 走到 σi−1\sigma_{i-1} 怎么走(Euler / Heun / DPM++)
  • Schedule:决定这 NN 个离散点 σ0,σ1,…,σN\sigma_0, \sigma_1, \dots, \sigma_N 在哪(也叫 step distribution)

两者完全正交——可以任意组合。Karras schedule 只管 schedule,不管 solver。

DDPM 原始 schedule 的问题#

DDPM 用的整数步 t=1,2,…,1000t = 1, 2, \dots, 1000 在 tt 轴上是等距的,但 β-linear schedule 换算到实际噪声水平 σt\sigma_t 后,σ 分布极度不均匀:

  • tt 小时,σt\sigma_t 几乎不动——tt 轴均匀采样到 σ 轴上就变得密到夸张。
  • t→Tt \to T 时,σt\sigma_t 才开始加速上冲到几十甚至上百。

也就是说:大量 tt 步都挤在 σ<1 的低噪声端。真跑 1000 步问题不大,但实际采样只跑 20–50 步需要从 1000 步里子采样——等距子采样会把太多步浪费在 σ→0 的"无信息"极端区(网络已近恒等映射),而 ODE 真正敏感的中低 σ 区又不够密。结果:步数分布和 ODE 实际难度不匹配,solver 的精度优势被低效的 schedule 抵消。

Karras 的目标:误差均摊#

在看公式之前,先讲清楚动机。Karras et al. 2022(EDM,"Elucidating the Design Space of Diffusion Models")的核心想法是:让每一步的截断误差大小相近。

为什么?因为最大的那一步误差会主宰整条轨迹的质量——其他步再精确也白搭。所以最优策略是把误差摊平:

  • 哪里误差大 → 点要密(步长小)
  • 哪里误差小 → 点可以稀(步长大)

那问题来了——哪里误差大?这里有一个反直觉的点需要澄清。"低 σ 接近干净图"听起来应该更简单(网络确实在这里预测更准,SNR 高),但扩散 ODE 的数值积分恰恰在低 σ 区最脆弱:

低 σ 区的双重身份

概率流 ODE 的形式是 dxdσ=x−Dθ(x,σ)σ\dfrac{dx}{d\sigma} = \dfrac{x - D_\theta(x, \sigma)}{\sigma},右端分母有个 1/σ1/\sigma 放大项。

• 网络角度:低 σ → 网络预测很准 ✓
• 数值角度:低 σ → 1/σ1/\sigma 把微小预测误差放大、轨迹曲率飙升 ✗

结论:低 σ 区不是"任务难",而是"ODE 对步长敏感"。准得来不易的网络精度必须用小步长才能守住,一步跨太大立刻把小误差放大成大偏差。

综合下来的分区结论:

  • 极低 σ(σ<σmin⁡≈0.002\sigma < \sigma_{\min} \approx 0.002):网络已接近恒等映射,不需要点——Karras 直接截断。
  • 低-中 σ(约 0.01 – 10):ODE 敏感区,点要密。
  • 高 σ:网络预测不准,但轨迹平缓,中等密度即可。

所以目标很明确:点要偏向低-中 σ。实现上,Karras 同时彻底抛弃 βt,αˉt\beta_t, \bar\alpha_t 等复杂记号,直接用 σ\sigma 作为时间变量(VE parametrization)。

公式的由来:变量替换 + 均匀采样#

Karras 公式看起来很奇怪(1/ρ1/\rho 在里面、ρ\rho 在外面),但其实是一个经典技巧的三步式:

  1. 定义新变量 u=σ1/ρu = \sigma^{1/\rho}(进入"变换空间")
  2. 在 uu 空间均匀取 NN 个点:ui=umax⁡+iN−1(umin⁡−umax⁡)u_i = u_{\max} + \frac{i}{N-1}(u_{\min} - u_{\max})
  3. 映射回 σ:σi=uiρ\sigma_i = u_i^{\rho}(离开"变换空间")

三步合并就是原公式:

σi=(σmax⁡1/ρ+iN−1(σmin⁡1/ρ−σmax⁡1/ρ))ρ,i=0,1,…,N−1\sigma_i = \left(\sigma_{\max}^{1/\rho} + \frac{i}{N-1}\left(\sigma_{\min}^{1/\rho} - \sigma_{\max}^{1/\rho}\right)\right)^\rho, \quad i = 0, 1, \dots, N-1

1/ρ1/\rho 和 ρ\rho 不是两个独立参数,而是"进入变换空间"和"离开变换空间"的一对逆运算

ρ\rho 到底在控制什么#

ρ\rho 控制 uu 空间和 σ 空间之间的形变强度。在 uu 空间均匀取点,映射回 σ 就会变成"小 σ 密、大 σ 稀",ρ\rho 越大越极端:

ρ\rho u=σ1/ρu = \sigma^{1/\rho} σ 轴上点的分布
ρ=1\rho = 1 u=σu = \sigma σ 上均匀(= 没变换)
ρ=2\rho = 2 u=σu = \sqrt{\sigma} 轻微偏向低 σ
ρ=7\rho = 7 u=σ1/7u = \sigma^{1/7} 明显偏向低 σ
ρ→∞\rho \to \infty u→log⁡σu \to \log \sigma log⁡σ\log \sigma 上均匀

光看表格还是抽象,两张图直观感受一下 ρ\rho 在干嘛。下图参数都一样:N=20N=20,σmax⁡=80\sigma_{\max}=80,σmin⁡=0.002\sigma_{\min}=0.002,只改变 ρ\rho:

7_Samplers_and_ODE_Solver 图1

横轴=采样第 i 步,纵轴=此刻的 σi\sigma_i。ρ\rho 越大,σ 前期下降越陡——更多步数集中在低 σ 的 ODE 敏感区。ρ=1\rho=1 是等幅下降的基线。

这张图看"σ 随 i 怎么掉"。换个角度:把这 20 个 σi\sigma_i 直接投影到一条 [0,80][0, 80] 的 σ 数轴上,看它们最终落在哪——密度分布就一目了然:

7_Samplers_and_ODE_Solver 图2

同样 20 个点,但密度分布完全不同:ρ=1\rho=1 均匀铺;ρ=7\rho=7 几乎所有点都挤在 σ<5 的区间,高 σ 只留少数几个"路标";ρ=20\rho=20 则过度极端,高 σ 几乎只剩一个端点。

两张图联起来看:

  • ρ=1\rho=1:步长 Δσ\Delta\sigma 恒定 ≈4.2\approx 4.2。没有任何对低 σ 的偏向——高 σ 浪费太多步数,低 σ 分辨率又不够。
  • ρ=3\rho=3:开始偏向低 σ,但前几步步长仍然很大(一步从 80 跨到 68),NFE 没用在刀刃上。
  • ρ=7\rho=7(EDM 默认):前 5 步用相对大的步快速跨过"不敏感"的高 σ 区(80→60→44→32→2380 \to 60 \to 44 \to 32 \to 23),然后 step 快速收窄,剩下 10+ 步全留给低-中 σ 的敏感区精修。
  • ρ=20\rho=20:太激进,几乎所有点都堆在 σ→0,高 σ 分辨率不足,反而丢失大尺度结构。

为什么是 ρ=7\rho = 7#

没有漂亮推导,是数值实验结果。Karras 算了 Heun solver 在不同 σ 位置的单步截断误差表达式,对 ρ\rho 做扫描,看哪个让"最大步误差"最小。结论是 ρ∈[5,10]\rho \in [5, 10] 都差不多好,ρ=7\rho = 7 当默认值。

所以 7 并不神圣——它是针对一类网络 / 一类 σ 范围的经验最优。换个模型族(比如 Rectified Flow),最优 ρ\rho 会不同。直觉上也好理解:ρ\rho 越大节点越往低噪声区集中,但又不能太极端(高噪声区也要有足够步数),7 是这两者之间的平衡点。

一个反直觉的发现:log-uniform 并不是最优

Karras 之前的"共识"是 log-uniform schedule(即 ρ→∞\rho \to \infty)足够好——比线性强、已经是"高级做法"。但 Karras 的扫参显示:log-uniform 并不是最优,ρ≈7\rho \approx 7 反而更好。

这意味着最优 schedule 位于线性与 log-uniform 之间——比 log-uniform 更偏向低 σ,但没走到那一端。

一句话串起逻辑链:目标是误差均摊 → 需要点偏向低 σ → 用 u=σ1/ρu = \sigma^{1/\rho} 做变量替换、在 uu 空间均匀采样再映射回来 → ρ\rho 控制偏向低 σ 的强度 → 扫参数发现 ρ=7\rho = 7 效果好。

为什么和 DPM-Solver++ 2M 是"黄金搭档"#

正交优化的叠加收益

• DPM-Solver++ 2M 优化了"每一步的数值精度"——给定 step 分布,单步误差最小化。
• Karras schedule 优化了"step 分布本身"——让步长和误差更匹配。

两者解决的是正交的问题,可以叠加使用。实际效果:
DPM++ 2M + 线性 schedule 30 步 ≈ DPM++ 2M + Karras 20 步——同质量下步数再降 1/3。

Karras 参数化的其他优点#

  • 时间连续化:σ∈[σmin⁡,σmax⁡]\sigma \in [\sigma_{\min}, \sigma_{\max}] 是连续实数,不再绑死 T=1000T=1000。这让同一个训练好的模型可以任意选择采样步数。
  • 和训练无关:只改变采样时的节点分布,训练好的模型不需要任何改动。
  • 容易实现:只是一行 schedule 生成代码,没有额外开销。

3.2 Rectified Flow 下的采样(速览)#

FM 的采样和扩散的关键差别#

Flow Matching 的训练让向量场接近常数方向(直线路径)。路径几乎是直线意味着:

  • Euler 的截断误差极小——直线上 Euler 精确,曲线上才有误差。
  • DPM-Solver 几乎没用——它的优势来自"处理扩散的半线性结构",而 FM 不是半线性结构。
  • Karras schedule 意义变小——FM 没有 log-SNR 意义上的"两端误差不对称"。

结论

Flow Matching(SD3 / Flux / LTX / Wan)几乎都用最简单的 Euler solver,配合一个均匀或 shift 过的线性 tt schedule。20-50 步即可得到高质量结果。

Timestep Shift:FM 唯一的 schedule 技巧#

SD3 论文里提出一个叫 timestep shift 的小 trick:在均匀采样 t∈[0,1]t \in [0,1] 后,对 tt 做一个非线性变换 t′=f(t)t' = f(t),把更多步数放在生成过程的某一段(例如低噪声端,让细节更好)。

常用形式:t′=s⋅t1+(s−1)tt' = \dfrac{s \cdot t}{1 + (s-1) t},ss 是 shift 参数(s>1s > 1 把步数往 t′=1t'=1 方向推)。

7_Samplers_and_ODE_Solver 图3

横轴是均匀输入 tt,纵轴是 shift 后的 t′t'。s=1s=1 是恒等映射(灰色虚线,直接等于 tt);ss 越大,曲线越往左上弯——输入 tt 还在前段,输出 t′t' 就已经冲到高位。配合"把 t′t' 当实际采样点"使用,相当于把步数挤到 t′→1t'\to1 一端。

方向:shift 往哪边推?#

SD3/FM 的约定是 t=1t=1 是纯噪声、t=0t=0 是干净图。所以 shift 往 t′=1t'=1 压 = 把更多步数塞到高噪声端(构图阶段)——方向和 Karras 偏向低 σ 的精修端恰好相反。

为什么要偏向高噪声端?高分辨率的"噪声不够"问题#

动机是一个和数值稳定性完全无关的问题:高分辨率图像下,"纯噪声"并不纯。核心是中心极限定理:

独立高斯噪声:杀高频、放过低频

• 高频(边缘、纹理——相邻像素的差):独立噪声相减仍是 ∼σ\sim \sigma 量级,全额破坏。
• 低频(整体构图、大块色域——区域内像素的平均):NN 个独立噪声平均后标准差衰减到 ∼σ/N\sim \sigma/\sqrt{N},被稀释。

分辨率越高 NN 越大,稀释越狠。1024×10241024 \times 1024 下 σ=1\sigma=1 的噪声几乎不伤大尺度构图——所以 t=1t=1 的"纯噪声"网络仍能眯眼看出"上蓝下绿中间暗"这种 gist。FM"从真正无信息起点出发"的假设被破坏。

Shift 把步数挤到高噪声端,给网络更多步数去决定整体构图,再进入细节——正好补上这个缺口。

ss 的最优值随分辨率缩放(SD3 论文经验值):

  • 256×256256 \times 256:s≈1s \approx 1(几乎不 shift)
  • 512×512512 \times 512:s≈1.5s \approx 1.5
  • 1024×10241024 \times 1024:s≈3s \approx 3
  • 更高分辨率:ss 继续增大

与 Karras schedule 的本质区别#

Karras ρ=7\rho=7(diffusion) SD3 shift(FM)
方向 偏向低噪声(精修) 偏向高噪声(构图)
在补救什么 ODE 数值稳定性:低 σ 端 1/σ1/\sigma 放大误差 数据 vs 噪声的匹配:高分辨率下低频结构抗噪
根源 ODE 数学性质 图像统计性质 + 中心极限定理
是否随分辨率调 否(ρ=7\rho=7 通用) 是(ss 随分辨率缩放)

所以"FM 不需要 Karras 式 schedule"指的是 ODE 层面——FM 两端良态,不需要为数值稳定性倾斜。但 SD3 shift 解决的是另一个问题(数据分布 vs 噪声扰动的匹配),两者并不矛盾。

关于公式形式#

t′=st1+(s−1)tt' = \dfrac{s t}{1 + (s-1) t} 不是随便选的——它是分数变换(Möbius 的特例),同时满足三个性质:

  • 端点 t=0,1t=0, 1 不动(干净/噪声两端固定,只动中间)
  • 单调(不破坏时间顺序)
  • 在 SNR 空间里作用是一次乘法平移(log⁡SNR\log\text{SNR} 加常数)——等价于"把 SNR 曲线整体平移到目标分辨率该在的位置",这正是"ss 随分辨率缩放"的数学根据

更深入的推导#

详细的 FM 采样推导、Rectified Flow reflow 迭代、PF-ODE 等价性,参考 6_Flow_Matching §06-§07。

3.3 采样器选型速查表#

主流采样器总览#

采样器 阶数 单步 NFE ODE/SDE 典型步数 特点与适用
DDPM 1 1 SDE 1000 原始马尔可夫链,历史意义
DDIM 1 1 ODE 30-50 扩散模型默认 baseline
Euler 1 1 ODE 30-50 FM / SD3 / Flux 标配
Euler Ancestral 1 1 SDE 30-80 更多样性、更创作向
Heun 2 2 ODE 15-25 二阶 baseline,简单稳定
DPM-Solver 2M 2 1 ODE 15-25 低 CFG 精度高
DPM-Solver++ 2M 2 1 ODE 15-25 SD 黄金采样器
DPM-Solver++ 2M Karras 2 1 ODE 15-20 再提高质量的首选
DPM-Solver++ SDE 2 1 SDE 20-30 二阶 + Ancestral,细节锐利

怎么选?三个场景#

场景 A:产品级图像生成(SDXL / SD3)

推荐:DPM++ 2M Karras(扩散)/ Euler(FM)
• 步数:扩散 20-25 步,FM 30-50 步
• 非 Ancestral 在确定性实现、模型、初始噪声和网格固定时可复现
• 高 CFG scale(5-10)场景下务必用 DPM++ 而不是 DPM

场景 B:视频生成(Wan / LTX / CogVideoX)

推荐:Euler(FM 训练的模型)/ DPM++ 2M(扩散训练的模型)
• 视频模型内存压力大,步数越少越好
• FM 训练的视频模型(SD3 系)Euler 20 步足够
• 如果做蒸馏版(LCM / 4 步模型),换专用 solver(LCM Scheduler / DPM Multistep SDE)

场景 C:创作 / 探索

推荐:Euler a / DPM++ SDE Karras
• Ancestral 引入随机性,同 seed 多次出图有变化
• 更锐利的纹理(但有时也更容易 overcook)
• 步数适当加到 30-40

不兼容组合(踩坑预警)#

  • FM 训练的模型 + DDIM:DDIM 用的是 αˉt\bar\alpha_t 参数化,FM 没有这套参数;虽然 HF diffusers 的一些接口能跑,但数值上并非最优。
  • LCM 蒸馏模型 + 普通 DPM-Solver:LCM 的 4 步目标是被蒸馏固定的,换 solver 会破坏蒸馏假设。必须用 LCMScheduler。
  • v-pred 模型 + 只支持 ε-pred 的 solver 接口:参数化不匹配,网络输出解释错误,结果全是雪花。务必检查 scheduler 的 prediction_type。

4. 理解检查与实现#

4.1 理解检查(8 道)#

Q1 训练好一个 DDPM,可以直接拿去用 DDIM 采样吗?为什么?#

参考回答

可以,不需要重训练。

关键理由:DDPM 训练时只用了边际分布 q(xt∣x0)q(x_t|x_0),从来没有用过"xtx_t 是从 xt−1x_{t-1} 一步加噪来的"这个马尔可夫结构。

DDIM 构造的反向过程边际分布和 DDPM 完全一致——采样轨迹上每一步的 xtx_t 都在网络的训练分布上,预测仍然有效。

同样地,这个模型也能用 Euler、DPM-Solver 等 ODE solver,只要参数化对得上(ϵ\epsilon-pred / x0x_0-pred / v-pred)。

Q2 Ancestral 和非 Ancestral 采样的本质区别是什么?#

参考回答

Ancestral = 解反向 SDE,每步用 Euler 之后额外加一个高斯噪声;非 Ancestral = 解 Probability Flow ODE,完全确定性。

数学上:SDE 和 PF-ODE 有相同的边际分布 pt(x)p_t(x),所以最终样本分布相同;但具体到某个 seed 的轨迹,两者完全不同。

实践上:非 Ancestral 可复现、低步数更稳;Ancestral 多样性高、纹理更锐、但低步数容易不稳。工业产品默认非 Ancestral。

Q3 DPM-Solver 的"半线性分解"是什么意思?为什么它重要?#

参考回答

扩散的反向 ODE 是 dx/dt=f(t)x+g(t)ϵθ(x,t)dx/dt = f(t) x + g(t)\epsilon_\theta(x, t),其中 f(t)xf(t) x 是关于 xx 的线性项(可解析求解),g(t)ϵθg(t)\epsilon_\theta 是非线性项(依赖神经网络)。

通用 solver(Euler、Heun)把整个 RHS 当黑箱近似,线性部分也被数值化,引入不必要的截断误差;DPM-Solver 用"指数积分子 / 变分常数法"让线性部分保持精确,只对非线性部分做 Taylor 展开。

好处:同样阶数下实际精度更高 → 同质量下步数更少(15-25 vs 30-50)。这就是 DPM-Solver 相比 Heun 的根本优势。

Q4 DPM-Solver 和 DPM-Solver++ 差在哪?为什么高 CFG 下 ++ 更稳?#

参考回答

对应连续 ODE 可在适当参数化下表示同一动力学,但有限步数值算法一般不同:DPM-Solver 用 ϵ\epsilon 做差分,DPM-Solver++ 用 x^0=(xt−σtϵθ)/αt\hat x_0 = (x_t - \sigma_t \epsilon_\theta) / \alpha_t 做差分。\

高 CFG 场景:ϵcfg=ϵuncond+w(ϵcond−ϵuncond)\epsilon_{\text{cfg}} = \epsilon_{\text{uncond}} + w(\epsilon_{\text{cond}} - \epsilon_{\text{uncond}}),w∈[5,15]w \in [5, 15] 把 ϵ\epsilon 的数值放大。大的引导强度可能增加求解难度,高阶更新也未必更稳定;但 x^0\hat x_0 预测本身不天然有界。像素空间可使用 thresholding;latent 一般不在 [−1,1][-1,1],不能直接套用像素裁剪。\

结论:DPM-Solver++ 在论文所测 guided sampling 中表现较好,但不是所有文生图模型的必选项;应按具体参数化、网格和 NFE 比较质量。

Q5 为什么 DPM-Solver-2M 比 2S 常用得多?#

参考回答

2M(multi-step)利用前一步已经算过的 ϵθ\epsilon_\theta 做二阶差分,单步 NFE = 1;2S(single-step)在每一步内部额外求一个中间点的 ϵθ\epsilon_\theta,单步 NFE = 2。

同样 NN 个时间节点下,2S 总 NFE = 2N,2M 总 NFE = N + 1(第一步没有历史,需要额外一次启动)。2M 在同质量下成本几乎减半。

唯一的代价是需要额外一次前一步的信息缓存,内存开销可忽略。

Q6 Karras schedule 解决了什么问题?为什么 ρ=7\rho = 7?#

参考回答

解决的问题:DDPM 的 tt 步在 tt 轴上是等距的,但 β-linear schedule 换算到 σ 轴后极度不均匀——大量 tt 步挤在 σ<1 的低噪声区,高 σ 区反而稀疏。实际只跑 20–50 步需要从 1000 步里子采样,等距子采样会把太多步浪费在 σ→0 的"无信息"极端区(网络已近恒等映射),而真正关键的中低 σ 区又不够密。

Karras 的做法:彻底以 σ 为时间变量,用 σi=(σmax⁡1/ρ+iN−1(σmin⁡1/ρ−σmax⁡1/ρ))ρ\sigma_i = (\sigma_{\max}^{1/\rho} + \frac{i}{N-1}(\sigma_{\min}^{1/\rho} - \sigma_{\max}^{1/\rho}))^\rho 生成节点,ρ\rho 控制向低 σ 倾斜的强度;再加上 σmin⁡≈0.002\sigma_{\min} \approx 0.002 的截断,避免堆在"无意义"的极低 σ 端。

为什么要向低 σ 倾斜:概率流 ODE dx/dσ=(x−Dθ)/σdx/d\sigma = (x - D_\theta)/\sigma 在低 σ 端有 1/σ1/\sigma 放大项,轨迹 stiff,单步截断误差影响大;高 σ 区 ODE 平缓,大步可容。理由是 ODE 数值稳定性,不是"网络在高 σ 误差大所以要密"——那种说法把因果弄反了。

ρ=7\rho = 7:扫参最优。ρ=1\rho=1(σ 线性)完全没有向低 σ 倾斜;ρ→∞\rho \to \infty(log σ 线性)倾斜但不够狠;ρ=7\rho=7 是 Heun solver 下"最大步截断误差最小化"的经验平衡点。和训练、solver 都解耦,纯采样侧改进。

Q7 Flow Matching 为什么不需要 DPM-Solver?#

参考回答

DPM-Solver 的核心优势来自"利用扩散 ODE 的半线性结构"。但 Flow Matching 的 ODE 是 dx/dt=vθ(x,t)dx/dt = v_\theta(x, t),没有半线性结构——vθv_\theta 整体就是一个非线性函数。指数积分子那一套用不上。

更重要的是:FM 训练时就已经让路径接近直线(常数向量场),Euler 的截断误差本身就很小。升到二阶/三阶 solver 带来的增益和额外 NFE 成本相比,不划算。

结论:FM 用 Euler 就够了,SD3 / Flux / LTX 的默认配置都是 Euler。如果真的要更少步数,走 Rectified Flow reflow 或 Consistency Distillation 比换 solver 更有效。

Q8 同一个模型,20 步 DPM++ 2M Karras 和 50 步 DDIM 谁质量好?#

参考回答

在 SD 1.x / SDXL 这类扩散模型上,20 步 DPM++ 2M Karras 通常持平甚至略优于 50 步 DDIM,但推理成本只有 40%。

原因是三个正交优化叠加:

• 阶数从 1 升到 2(DDIM → DPM++ 2M):同精度下步数折半
• 半线性解法(通用 Euler → 指数积分子):同阶数下单步精度更高
• Karras schedule(线性 → ρ=7\rho=7):步数分布和误差分布更匹配

这就是为什么"DPM++ 2M Karras 20 步"在 2023 年前后成了 SD 社区事实上的默认配置。

4.2 进阶讨论:DPM-Solver++ 2M 伪代码#

核心公式回顾#

在 log-SNR 时间 λt\lambda_t 下,记 hi=λi−1−λih_i = \lambda_{i-1} - \lambda_i(单步步长)。DPM-Solver++ 2M 的一步更新(x0x_0-prediction 版本):

xi−1=σi−1σixi−αi−1(e−hi−1)Dix_{i-1} = \frac{\sigma_{i-1}}{\sigma_i} x_i - \alpha_{i-1}\left(e^{-h_i} - 1\right) D_i

其中 DiD_i 是二阶修正的 x^0\hat x_0 估计

Di=(1+hi−12hi)x^0(i)−hi−12hix^0(i+1)D_i = \left(1 + \frac{h_{i-1}}{2 h_i}\right) \hat x_0^{(i)} - \frac{h_{i-1}}{2 h_i} \hat x_0^{(i+1)}

用当前步和前一步的 x^0\hat x_0 估计做二阶外推——这就是"multi-step"的体现

其中 x^0(i)=(xi−σiϵθ(xi,ti))/αi\hat x_0^{(i)} = (x_i - \sigma_i \epsilon_\theta(x_i, t_i)) / \alpha_i 是当前步的 x0x_0 估计。第一步(没有 x^0(i+1)\hat x_0^{(i+1)})退化为一阶:Di=x^0(i)D_i = \hat x_0^{(i)}。

完整采样伪代码#

# DPM-Solver++ 2M with Karras schedule
# 输入: model (epsilon-pred), 步数 N, sigma_min, sigma_max, rho=7.0, cfg_scale
# 输出: 生成样本 x_0

# ----- Step 1: 构造 Karras schedule -----
def karras_sigmas(N, sigma_min, sigma_max, rho=7.0):
    ramp = torch.linspace(0, 1, N)
    min_inv_rho = sigma_min ** (1 / rho)
    max_inv_rho = sigma_max ** (1 / rho)
    sigmas = (max_inv_rho + ramp * (min_inv_rho - max_inv_rho)) ** rho
    return torch.cat([sigmas, torch.zeros(1)])  # 最后一个 sigma=0

sigmas = karras_sigmas(N, 0.02, 14.6, rho=7.0)  # 形状 [N+1]
lambdas = torch.log(alpha(sigmas) / sigmas)       # 转到 log-SNR

# ----- Step 2: 初始化噪声 -----
x = torch.randn(batch, 4, H, W) * sigmas[0]   # Karras 习惯:x_T 乘 sigma_max
x0_prev = None                                  # 前一步的 hat_x0 缓存

# ----- Step 3: 采样循环 -----
for i in range(N):
    sigma_i   = sigmas[i]
    sigma_ip1 = sigmas[i + 1]

    # 3.1 网络预测 + CFG
    eps_uncond = model(x, sigma_i, null_text)
    eps_cond   = model(x, sigma_i, prompt)
    eps = eps_uncond + cfg_scale * (eps_cond - eps_uncond)

    # 3.2 反推 hat_x0(++ 参数化的关键)
    hat_x0 = (x - sigma_i * eps) / alpha(sigma_i)

    # 3.3 构造 D_i(二阶外推)
    if x0_prev is None:
        # 第一步退化为一阶
        D = hat_x0
    else:
        h      = lambdas[i + 1] - lambdas[i]
        h_prev = lambdas[i]     - lambdas[i - 1]
        r = h_prev / h
        D = (1 + 1 / (2 * r)) * hat_x0 - (1 / (2 * r)) * x0_prev

    # 3.4 DPM-Solver++ 2M 更新
    h = lambdas[i + 1] - lambdas[i]
    x = (sigma_ip1 / sigma_i) * x - alpha(sigma_ip1) * (torch.exp(-h) - 1) * D

    # 3.5 缓存供下一步使用
    x0_prev = hat_x0

# ----- Step 4: 最后一步返回 -----
return x  # 对应 sigma ~ 0 的干净样本

需要注意的数值细节#

  • α(σ)\alpha(\sigma) 的定义:VP schedule(DDPM 默认)下 α(σ)=1/1+σ2\alpha(\sigma) = 1/\sqrt{1+\sigma^2};VE schedule(EDM 默认)下 α(σ)=1\alpha(\sigma) = 1。两套公式有轻微差别。
  • 最后一步 σ→0\sigma \to 0:λ→+∞\lambda \to +\infty,公式里的 e−h−1e^{-h} - 1 要用 expm1(-h) 避免数值下溢。
  • 第一步启动:x0prevx0_{\text{prev}} 不存在,直接用一阶(D=x^0D = \hat x_0)。部分实现会做一次 Euler 预热。
  • CFG 的放置:CFG 在网络输出之后、x^0\hat x_0 计算之前——确保 ++ 参数化接收的是 CFG 后的 ϵ\epsilon。

和 HF diffusers 对应关系#

概念 diffusers 类名
DPM-Solver++ 2M DPMSolverMultistepScheduler(algorithm_type="dpmsolver++", solver_order=2)
Karras schedule use_karras_sigmas=True
DPM++ 2M SDE 同上 + algorithm_type="sde-dpmsolver++"
Euler EulerDiscreteScheduler
Euler Ancestral EulerAncestralDiscreteScheduler
Flow Matching Euler FlowMatchEulerDiscreteScheduler

**工程要点:**diffusers 的 Scheduler 负责 sigma 生成、时间参数化、单步更新三件事。切换采样器时要同时检查 prediction_type(ε-pred / v-pred / sample)和 beta_schedule(linear / scaled_linear / squaredcos_cap_v2),搞错任何一个结果都会是雪花图。

原始资料:DPM-Solver、DPM-Solver++、EDM。确定性 DDIM 与 Euler 的对应要求同时转换状态变量与噪声时间、使用一致网格和 denoiser;不能只改时间变量便推断任意实现逐步相同。

相关阅读路线

理解生成模型 ↗