采样器与 ODE Solver#
本页内容4 个部分
查看概览图
所有采样器
都是同一条 ODE 的数值解
采样器名字很多,可分解的其实只有五层:训练目标、时间参数化、schedule、solver 阶数、是否 ancestral。
决定网络学什么
DDPM / EDM / DPM-Solver
步长往哪儿密
积分阶数与策略
每步是否再注噪
换一个名字,动的往往只是其中一层——这五层可以自由组合,这就是采样器名字爆炸的原因。
把步数堆到低噪端
低噪端图像细节正在成形,误差最伤质量;Karras 用 ρ 把步长压到那一段。
要不要再注噪
同一个起点,走不走直路
η 是连续旋钮:0 退化为标准 Euler,1 最接近原始 DDPM,中间是两者的插值。轨迹不同,但边际分布相同。
确定、稳、步数少
-
解 PF-ODE同 seed 同 prompt 同结果 -
多样性低结果被 seed 完全决定 -
稳
典型 20–50 步Euler / DDIM / DPM-Solver / DPM++
多样、但要更多步
-
解反向 SDE每步注入额外噪声 -
多样性高即使 seed 相同也有变化 -
低步数易失稳
典型 30–80 步Euler a / DPM++ SDE / DPM++ 2M SDE
扩散/Flow 采样器全景 · 一阶 / 二阶 / DPM-Solver · Karras · 面试串讲
1. 生成采样与数值求解#
1.1 为什么要单独讲采样器#
训练和采样是解耦的#
扩散模型的训练只做一件事:给定 ,让网络学会预测噪声 (或 、、score 之一,本质等价)。训练完成后,网络就是一个固定的函数 。
但从纯噪声 一路走到干净图像 的去噪轨迹,完全由"采样器(Sampler / Scheduler)"决定——它把连续时间的 ODE/SDE 离散化成一步一步的更新规则。
核心解耦
网络预测决定了每个位置的"向量场方向";采样器决定了"如何沿着这个向量场积分"。两者是正交的——同一个训练好的模型,换个采样器就能跑得更快或者更稳。
不同采样器差距有多大#
在同样一个 Stable Diffusion 1.5 模型上:
- 原始 DDPM 采样:1000 步,质量好但慢到不可用。
- DDIM:50 步,同质量。
- DPM-Solver++ 2M + Karras schedule:20-25 步,质量和 DDIM 50 步基本持平,甚至更好。
也就是说——仅仅换采样器,就能把推理成本压缩到原来的 1/40。不需要改模型、不需要重新训练,完全免费。这就是为什么工业界对采样器如此重视。
一张图看清全景#
| 层级 | 算法 | 说明 |
|---|---|---|
| 训练目标 | / / / score | 决定网络学什么 |
| 时间参数化 | / / | DDPM / EDM / DPM-Solver 的不同选择 |
| Schedule | linear / cosine / Karras() | 决定步长分布(哪里密、哪里稀) |
| Solver | Euler / Heun / DPM-Solver / UniPC | 决定数值积分的阶数与策略 |
| Ancestral | 开 / 关 | 是否在每步注入额外噪声(SDE vs ODE) |
实际使用时,用户选的一个"采样器"其实是上面几层的组合(例如"DPM++ 2M Karras" = -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:
前向 SDE—— 是漂移项, 是扩散系数, 是 Wiener 过程(随机项)
反向过程则是一个反向 SDE:
反向 SDE——用到 score function ,其中 是反向时间的 Wiener 过程
Song 证明了一个关键事实:这条 SDE 存在一个和它有相同边际分布的 ODE,叫做 Probability Flow ODE(PF-ODE):
PF-ODE——把随机项去掉、系数减半。和原 SDE 在每个时刻 产生同样的分布
**一句话理解:**原来的 SDE 是"带噪声的随机游走",PF-ODE 是"把所有路径的平均行为写成的确定性流"。两者生成的样本分布相同,但 PF-ODE 是一条确定的轨迹,可以用成熟的 ODE solver 来积分。
Flow Matching:原生就是 ODE#
Flow Matching 跳过了 SDE,直接定义 ODE:
Flow ODE—— 是学到的向量场,直接就是 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):每步更新只有漂移项,完全确定性
不同之处只有两件事:数值积分的阶数(误差 的 多大),以及对扩散方程结构(半线性、log-SNR 等)的利用程度。
本文约定#
为方便和主流实现对齐,后文统一采用"扩散方向": 是数据, 是噪声;采样从 递减到 。Flow Matching 原生的 是噪声、 是数据的约定仅在 §07 中使用。
2. 从一阶到高阶方法#
2.1 一阶方法:Euler / DDIM / Euler Ancestral#
Euler:最朴素的一阶方法#
ODE 的最简单数值解法。对每一步时间区间 ,用当前点的切线外推:
Euler 前向步进——只用当前点的一次函数求值
每步调用一次网络(1 次 NFE)。在向量场足够光滑等条件下,局部截断误差为 ,固定积分区间上的全局误差为 。
NFE = Number of Function Evaluations,即"网络调用次数"。在扩散/FM 里,一次"函数求值"特指调一次 ——网络 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 的 参数化,而不是原始的时间 。
- DDIM 的每步公式 ,当 时就是纯 ODE。
- 确定性 DDIM 在相应的状态缩放与噪声变量变换后可写成 Euler 更新;只把时间变量改名并不足够,也不是任意参数化下的普通 Euler。
同一件事两种说法:"DDIM = 一阶 ODE solver in -time" ≡ "Euler = 一阶 ODE solver in -time"。两者的数值结果在同步数下接近但不完全相同,差别来自时间参数化不同(step 分布不同)。
Euler Ancestral:一阶 + 每步注噪#
"Ancestral" 来自统计学里的"祖先采样"(ancestral sampling),在扩散里特指:每步 Euler 之后额外注入一点噪声,让采样过程从纯 ODE 变回 SDE。
核心思路#
**先走得比目标更干净,再注入新鲜噪声把方差补回目标噪声水平。**目标是 ,但 Euler 不直接走到那;而是走到一个更干净的 ,然后加独立高斯噪声 把噪声水平补回 。方差账要平:。
真正的旋钮:#
k-diffusion 里有一个超参 ("随机性强度"),它才是用户调的量:
根号里那一坨是 DDPM 反向后验方差 (见下方"数学背景")
| 行为 | |||
|---|---|---|---|
| 纯 ODE,退化为标准 Euler | |||
| 部分 | ODE 和 SDE 之间插值 | ||
| 满 | 最小 | 最接近原始 DDPM 的 SDE 采样 |
完整流程(顺序很重要)#
- 选 ——决定要多少随机性。
- 由上式算 ——要注入多少新噪声。
- 由方差约束反推 ——Euler 该走到哪。
- Euler 从 走到 。
- 加 ()把噪声水平补回 。
合并成一行就是:
Euler Ancestral 一步更新—— 时第二项退化为 Euler 到 、第三项为 0
数学背景: 是什么#
根号里那一坨 是 DDPM 反向过程的后验方差——给定起点 和当前点 后,上一时刻 的剩余不确定性。推导(两个高斯的乘积):
是"这一步允许注入的最大噪声预算", 是"预算使用比例"
只记一句: 是随机性预算, 是使用比例。 不花 → 纯 Euler; 花满 → 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 只用区间起点的导数做外推。如果向量场在区间内发生变化(曲率大),这个外推就不准。例如向量场是从 渐变到 的直线,Euler 用起点 外推,整段区间都偏左。
Heun 方法(改进 Euler)#
Heun 方法的思路:用起点和终点的导数平均值做外推。流程:
- Predictor:先用 Euler 预测一个粗略的终点 。
- Corrector:在这个粗略终点处算一次向量场 。
- 平均:用两次 的平均重算终点:
Heun 一步更新——两次网络调用(predictor + corrector),但误差阶降到
每步的 NFE 是 2。最后一步通常跳过 corrector(因为没有下一个时刻),所以 步总 NFE = 。
截断误差分析#
数值分析里的核心公式:对 阶方法,局部截断误差 = ,累计 步后,全局误差 = 。下面列出一阶、二阶、四阶方法的对比(其中 RK4 是数值分析里的经典方法:一步内求 4 次斜率加权平均,四阶精度,"通用 ODE solver"的默认选项):
| 方法 | 阶数 | 单步 NFE | 局部截断误差 | 全局误差 |
|---|---|---|---|---|
| Euler / DDIM | 1 | 1 | ||
| Heun | 2 | 2 | ||
| RK4(扩散不用) | 4 | 4 |
RK4 在通用 ODE 里一骑绝尘,但在扩散里每步 NFE=4 成本太高,详见本节末尾"为什么不用 RK4?"。
为什么二阶比一阶更省步数#
同精度下的步数比较
Heun 每步 NFE 是 Euler 的 2 倍,但全局误差阶从 降到 。
要让 Euler 和 Heun 达到相同精度 :
• Euler 需要 ,步数
• Heun 需要 ,步数
总 NFE 比较:Heun 为 ,Euler 为 。只要 ,Heun 就赢。 对生成模型这个早就满足。
为什么不用 RK4?#
先介绍 RK4:Runge-Kutta 四阶方法是数值分析里最经典的通用 ODE solver,一步内采 4 个斜率样本,然后加权平均:
- ——起点斜率
- ——用 走半步后的中点斜率
- ——用 再走半步后的中点斜率(修正一次)
- ——终点斜率
RK4 加权平均——中点权重更大,相当于高阶 Simpson 积分
单步 NFE = 4,局部截断误差 ,全局 。通用 ODE 里是默认选项,精度阶数和计算量的性价比极佳。
**那扩散里为啥不用?**两个原因:
- NFE 成本太高:扩散里一次 是一整个神经网络 forward,比数值分析里的普通函数贵几个数量级。RK4 每步 4 次 NFE,和 4 步 Euler 成本相同——除非 RK4 能把步数砍到 ,否则不划算。
- 高阶精度被网络误差淹没: 的学习误差远大于 Heun 截断误差,再往上升阶带来的额外精度被网络噪声吞掉,看不到。
实际上 Heun(二阶)已经接近 diminishing return。比 Heun 更聪明的做法是下一节讲的 DPM-Solver——它利用扩散方程的半线性结构,在单步 NFE=1 的情况下就能拿到二阶以上精度,完全绕过了 RK4 的高 NFE 困境。
2.3 DPM-Solver / DPM-Solver++#
核心动机:扩散 ODE 是"半线性"的#
扩散的反向 ODE 写成 形式后长这样:
扩散反向 ODE 的半线性结构——左项是 的线性函数,右项是非线性神经网络
其中 , 和 也都只是时间的函数,跟 无关。
半线性(semi-linear)的关键洞察
一个通用 ODE solver(如 Euler、Heun)把 整个右端 当成一个黑箱来数值近似,会同时累积两部分的截断误差。
但线性部分 可以解析求解——不需要数值近似。那为什么不只对非线性部分做数值近似,让线性部分的误差彻底消失?这就是 DPM-Solver (Lu et al., 2022) 的核心思路。
指数积分子(Exponential Integrator)#
对半线性 ODE,有一个经典的变分常数法(积分因子法)可以得到精确解:
半线性 ODE 的精确解——线性部分被指数因子完全吸收,只剩非线性部分的积分
如果 是已知函数,这个积分可以精确计算。问题是 要沿轨迹求,我们不知道 。DPM-Solver 的做法:只对这个积分做数值近似(Taylor 展开),而线性部分保持精确。
变量代换:#
DPM-Solver 的另一个技术核心是把时间变量换成 log-SNR:
log-SNR 参数化——半信噪比的对数。 时 (高 SNR), 时 (低 SNR)
上一节那个精确解里积分因子 写起来很丑。换到 之后,整个解能写成一个干净的指数积分:
换到 -时间后的精确解——线性部分被 完全吸收,非线性部分只剩一个干净的 积分
这就是为什么所有 DPM-Solver 公式里都出现 、 这样的因子——不是人为凑的,是换元后自然长出来的。
Taylor 展开:近似剩下的非线性积分#
现在整个数值问题只剩一个积分要近似:
唯一的数值近似发生在这里,线性部分零误差
DPM-Solver- 的做法是对被积函数里的 做 阶 Taylor 展开,积分就能解析算出:
- (一阶): 视为常数——结果正好是 DDIM 的精确重写,所以 DDIM = DPM-Solver-1。
- (二阶):加一个差分项( 的一阶导数,用有限差分估计)→ DPM-Solver-2。
- (三阶):再加二阶差分项 → DPM-Solver-3。
高阶项的"差分"怎么算——用多个 采样点的有限差分。这就引出 single-step vs multi-step 的区别:
- Single-step(2S):在当前步内部多采一个中间点的 ,单步 NFE = 2
- Multi-step(2M):复用上一步已经算过的 做差分,单步 NFE = 1
一阶 / 二阶 / 三阶 Taylor 展开推导#
把上面的 Taylor 展开和换元后的积分合起来,得到 DPM-Solver 所有阶数共用的骨架:
DPM-Solver 统一形式—— 是 log-SNR 步长, 是积分因子的闭式结果,高阶项用额外的 差分
下面具体看每一阶怎么从积分 里算出来。
Order 1: 视为常数#
最简单的近似:,提出积分号外:
:闭式解,单步只要 1 次 调用 → NFE = 1,结果恰好是 DDIM
Order 2:加一阶导数,中点有限差分估计#
展开到线性项:。积分可解析算出,但需要 ——模型不直接给导数,用有限差分估计:
在步长中点 再采一次 ,做预测-修正 → NFE = 2 per step
算法结构:先用一阶走到中点 得到 ,调用 ,再合成两次 走完整步。这就是 DPM-Solver-2S。
Order 3:再加二阶导数,需要 3 个采样点#
展开到二次项,多出一个 ,需要 3 个不同 上的 值才能做二阶有限差分 → 单步 NFE = 3。
对比一览#
| 阶数 | Taylor 保留项 | 每步 NFE | 局部截断误差 | 典型总步数 |
|---|---|---|---|---|
| 1 | 常数(零阶) | 1 | 50 | |
| 2 | + 一阶导 | 2(2S)/ 1(2M) | 20(40 NFE) | |
| 3 | + 二阶导 | 3(3S)/ 1(3M) | 12(36 NFE) |
**核心直觉:**阶数越高,单步代价越大,但每步能跨越的 log-SNR 距离更长 → 总 NFE 反而下降(50 → 40 → 36)。所以 DPM-Solver 不是"追求单步精确",而是"用更贵的单步换更少的总步数"。
为什么不继续往上做 4 阶、5 阶?
高阶有限差分会放大 的神经网络噪声—— 本身有拟合误差,对它做二阶、三阶差商相当于对噪声求高阶导,信噪比会急剧下降。所以 3 阶左右就是收益拐点,再往上收益递减甚至倒挂。
Multi-step(Adams 风格)→ DPM-Solver++
2S/3S 每步多算的那几次 ,其实可以复用上一步已经算过的值——这就是 2M/3M,借鉴了 ODE 数值求解里的 Adams 多步法。单步 NFE 降到 1,精度不变。DPM-Solver++ 2M/3M 就是这条路线的工业级版本。
Single-step vs Multi-step:2S 和 2M 的区别#
同一阶数的两种实现
DPM-Solver-2S(single-step):每一步内部,除了起点 外,再额外算一个中间点的 做二阶差分。单步 NFE = 2。
DPM-Solver-2M(multi-step):利用前一步已经算过的 做差分。单步 NFE = 1(和 Euler 相同!),靠历史信息拿到二阶精度。
2M 更常用——同质量下 NFE 只有 2S 的一半。代价是第一步没有历史,需要用一阶启动。
类比:开车预测 5 分钟后的位置#
你在开车,要预测「5 分钟后车在哪」。每次"抬头看速度表"相当于一次 调用(贵操作)。
- 一阶(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 用区间内信息:中间点就在 里,差分估计的是这一步内的实际变化。
2M 用区间外信息:上一步的读数在 ,是过去的趋势——默认假设"趋势会延续"。
这个假设在三种情况下会崩:
- 步长突变:上一步很短、这一步很长,外推失真
- 变化剧烈:采样两头(高噪声起点 / 低噪声终点), 可能急剧变化
- 冷启动:第一步根本没有历史,必须先用单步法启动
实践中的混合策略:
- 2M 在中间阶段(步长均匀、 变化平缓)效率极高
- 2S 在边界和剧变区更鲁棒
- 真实实现通常是「启动阶段用 2S,稳定后切 2M」
采样中 随 变化通常平滑(神经网络输出的连续函数),所以 2M 的"复用历史"几乎总是成立——这是 SD 默认用 DPM++ 2M 的根本原因。
DPM-Solver 和 DPM-Solver++:一个字符之差#
DPM-Solver++ (Lu et al., 2022, v2) 的核心改动:把 Taylor 展开的目标从 换成 ,并基于新参数化重新推导更新公式。
| 版本 | 参数化 | 适用场景 |
|---|---|---|
| DPM-Solver | -prediction | 低 CFG(< 3)下数值稳定 |
| DPM-Solver++ | -prediction | 高 CFG(5~15)下不发散 |
为什么高 CFG 下更稳?三条独立理由#
① 避免大数相消
-解 = 线性项 − 积分项(减法),高噪声区两项都很大、差值对误差敏感;-解 = 线性项 + 积分项(加法),数值稳健。
② 积分核衰减方向对了
-形式被积函数带 ,高噪声区( 负)这项爆炸放大 的误差;-形式带 ,同一区域反而衰减。
③ 可以被裁剪
预测的干净图像有天然值域约束,可以配合 dynamic thresholding 截断异常值;而 是噪声,没有自然的值域可裁。
数学上两种参数化等价,但数值条件数差异显著——这就是为什么 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:决定每一步从 走到 怎么走(Euler / Heun / DPM++)
- Schedule:决定这 个离散点 在哪(也叫 step distribution)
两者完全正交——可以任意组合。Karras schedule 只管 schedule,不管 solver。
DDPM 原始 schedule 的问题#
DDPM 用的整数步 在 轴上是等距的,但 β-linear schedule 换算到实际噪声水平 后,σ 分布极度不均匀:
- 小时, 几乎不动—— 轴均匀采样到 σ 轴上就变得密到夸张。
- 时, 才开始加速上冲到几十甚至上百。
也就是说:大量 步都挤在 σ<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 的形式是 ,右端分母有个 放大项。
• 网络角度:低 σ → 网络预测很准 ✓
• 数值角度:低 σ → 把微小预测误差放大、轨迹曲率飙升 ✗
结论:低 σ 区不是"任务难",而是"ODE 对步长敏感"。准得来不易的网络精度必须用小步长才能守住,一步跨太大立刻把小误差放大成大偏差。
综合下来的分区结论:
- 极低 σ():网络已接近恒等映射,不需要点——Karras 直接截断。
- 低-中 σ(约 0.01 – 10):ODE 敏感区,点要密。
- 高 σ:网络预测不准,但轨迹平缓,中等密度即可。
所以目标很明确:点要偏向低-中 σ。实现上,Karras 同时彻底抛弃 等复杂记号,直接用 作为时间变量(VE parametrization)。
公式的由来:变量替换 + 均匀采样#
Karras 公式看起来很奇怪( 在里面、 在外面),但其实是一个经典技巧的三步式:
- 定义新变量 (进入"变换空间")
- 在 空间均匀取 个点:
- 映射回 σ:(离开"变换空间")
三步合并就是原公式:
和 不是两个独立参数,而是"进入变换空间"和"离开变换空间"的一对逆运算
到底在控制什么#
控制 空间和 σ 空间之间的形变强度。在 空间均匀取点,映射回 σ 就会变成"小 σ 密、大 σ 稀", 越大越极端:
| σ 轴上点的分布 | ||
|---|---|---|
| σ 上均匀(= 没变换) | ||
| 轻微偏向低 σ | ||
| 明显偏向低 σ | ||
| 上均匀 |
光看表格还是抽象,两张图直观感受一下 在干嘛。下图参数都一样:,,,只改变 :
横轴=采样第 i 步,纵轴=此刻的 。 越大,σ 前期下降越陡——更多步数集中在低 σ 的 ODE 敏感区。 是等幅下降的基线。
这张图看"σ 随 i 怎么掉"。换个角度:把这 20 个 直接投影到一条 的 σ 数轴上,看它们最终落在哪——密度分布就一目了然:
同样 20 个点,但密度分布完全不同: 均匀铺; 几乎所有点都挤在 σ<5 的区间,高 σ 只留少数几个"路标"; 则过度极端,高 σ 几乎只剩一个端点。
两张图联起来看:
- :步长 恒定 。没有任何对低 σ 的偏向——高 σ 浪费太多步数,低 σ 分辨率又不够。
- :开始偏向低 σ,但前几步步长仍然很大(一步从 80 跨到 68),NFE 没用在刀刃上。
- (EDM 默认):前 5 步用相对大的步快速跨过"不敏感"的高 σ 区(),然后 step 快速收窄,剩下 10+ 步全留给低-中 σ 的敏感区精修。
- :太激进,几乎所有点都堆在 σ→0,高 σ 分辨率不足,反而丢失大尺度结构。
为什么是 #
没有漂亮推导,是数值实验结果。Karras 算了 Heun solver 在不同 σ 位置的单步截断误差表达式,对 做扫描,看哪个让"最大步误差"最小。结论是 都差不多好, 当默认值。
所以 7 并不神圣——它是针对一类网络 / 一类 σ 范围的经验最优。换个模型族(比如 Rectified Flow),最优 会不同。直觉上也好理解: 越大节点越往低噪声区集中,但又不能太极端(高噪声区也要有足够步数),7 是这两者之间的平衡点。
一个反直觉的发现:log-uniform 并不是最优
Karras 之前的"共识"是 log-uniform schedule(即 )足够好——比线性强、已经是"高级做法"。但 Karras 的扫参显示:log-uniform 并不是最优, 反而更好。
这意味着最优 schedule 位于线性与 log-uniform 之间——比 log-uniform 更偏向低 σ,但没走到那一端。
一句话串起逻辑链:目标是误差均摊 → 需要点偏向低 σ → 用 做变量替换、在 空间均匀采样再映射回来 → 控制偏向低 σ 的强度 → 扫参数发现 效果好。
为什么和 DPM-Solver++ 2M 是"黄金搭档"#
正交优化的叠加收益
• DPM-Solver++ 2M 优化了"每一步的数值精度"——给定 step 分布,单步误差最小化。
• Karras schedule 优化了"step 分布本身"——让步长和误差更匹配。
两者解决的是正交的问题,可以叠加使用。实际效果:
DPM++ 2M + 线性 schedule 30 步 ≈ DPM++ 2M + Karras 20 步——同质量下步数再降 1/3。
Karras 参数化的其他优点#
- 时间连续化: 是连续实数,不再绑死 。这让同一个训练好的模型可以任意选择采样步数。
- 和训练无关:只改变采样时的节点分布,训练好的模型不需要任何改动。
- 容易实现:只是一行 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 过的线性 schedule。20-50 步即可得到高质量结果。
Timestep Shift:FM 唯一的 schedule 技巧#
SD3 论文里提出一个叫 timestep shift 的小 trick:在均匀采样 后,对 做一个非线性变换 ,把更多步数放在生成过程的某一段(例如低噪声端,让细节更好)。
常用形式:, 是 shift 参数( 把步数往 方向推)。
横轴是均匀输入 ,纵轴是 shift 后的 。 是恒等映射(灰色虚线,直接等于 ); 越大,曲线越往左上弯——输入 还在前段,输出 就已经冲到高位。配合"把 当实际采样点"使用,相当于把步数挤到 一端。
方向:shift 往哪边推?#
SD3/FM 的约定是 是纯噪声、 是干净图。所以 shift 往 压 = 把更多步数塞到高噪声端(构图阶段)——方向和 Karras 偏向低 σ 的精修端恰好相反。
为什么要偏向高噪声端?高分辨率的"噪声不够"问题#
动机是一个和数值稳定性完全无关的问题:高分辨率图像下,"纯噪声"并不纯。核心是中心极限定理:
独立高斯噪声:杀高频、放过低频
• 高频(边缘、纹理——相邻像素的差):独立噪声相减仍是 量级,全额破坏。
• 低频(整体构图、大块色域——区域内像素的平均): 个独立噪声平均后标准差衰减到 ,被稀释。
分辨率越高 越大,稀释越狠。 下 的噪声几乎不伤大尺度构图——所以 的"纯噪声"网络仍能眯眼看出"上蓝下绿中间暗"这种 gist。FM"从真正无信息起点出发"的假设被破坏。
Shift 把步数挤到高噪声端,给网络更多步数去决定整体构图,再进入细节——正好补上这个缺口。
的最优值随分辨率缩放(SD3 论文经验值):
- :(几乎不 shift)
- :
- :
- 更高分辨率: 继续增大
与 Karras schedule 的本质区别#
| Karras (diffusion) | SD3 shift(FM) | |
|---|---|---|
| 方向 | 偏向低噪声(精修) | 偏向高噪声(构图) |
| 在补救什么 | ODE 数值稳定性:低 σ 端 放大误差 | 数据 vs 噪声的匹配:高分辨率下低频结构抗噪 |
| 根源 | ODE 数学性质 | 图像统计性质 + 中心极限定理 |
| 是否随分辨率调 | 否( 通用) | 是( 随分辨率缩放) |
所以"FM 不需要 Karras 式 schedule"指的是 ODE 层面——FM 两端良态,不需要为数值稳定性倾斜。但 SD3 shift 解决的是另一个问题(数据分布 vs 噪声扰动的匹配),两者并不矛盾。
关于公式形式#
不是随便选的——它是分数变换(Möbius 的特例),同时满足三个性质:
- 端点 不动(干净/噪声两端固定,只动中间)
- 单调(不破坏时间顺序)
- 在 SNR 空间里作用是一次乘法平移( 加常数)——等价于"把 SNR 曲线整体平移到目标分辨率该在的位置",这正是" 随分辨率缩放"的数学根据
更深入的推导#
详细的 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 用的是 参数化,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 训练时只用了边际分布 ,从来没有用过" 是从 一步加噪来的"这个马尔可夫结构。
DDIM 构造的反向过程边际分布和 DDPM 完全一致——采样轨迹上每一步的 都在网络的训练分布上,预测仍然有效。
同样地,这个模型也能用 Euler、DPM-Solver 等 ODE solver,只要参数化对得上(-pred / -pred / v-pred)。
Q2 Ancestral 和非 Ancestral 采样的本质区别是什么?#
参考回答
Ancestral = 解反向 SDE,每步用 Euler 之后额外加一个高斯噪声;非 Ancestral = 解 Probability Flow ODE,完全确定性。
数学上:SDE 和 PF-ODE 有相同的边际分布 ,所以最终样本分布相同;但具体到某个 seed 的轨迹,两者完全不同。
实践上:非 Ancestral 可复现、低步数更稳;Ancestral 多样性高、纹理更锐、但低步数容易不稳。工业产品默认非 Ancestral。
Q3 DPM-Solver 的"半线性分解"是什么意思?为什么它重要?#
参考回答
扩散的反向 ODE 是 ,其中 是关于 的线性项(可解析求解), 是非线性项(依赖神经网络)。
通用 solver(Euler、Heun)把整个 RHS 当黑箱近似,线性部分也被数值化,引入不必要的截断误差;DPM-Solver 用"指数积分子 / 变分常数法"让线性部分保持精确,只对非线性部分做 Taylor 展开。
好处:同样阶数下实际精度更高 → 同质量下步数更少(15-25 vs 30-50)。这就是 DPM-Solver 相比 Heun 的根本优势。
Q4 DPM-Solver 和 DPM-Solver++ 差在哪?为什么高 CFG 下 ++ 更稳?#
参考回答
对应连续 ODE 可在适当参数化下表示同一动力学,但有限步数值算法一般不同:DPM-Solver 用 做差分,DPM-Solver++ 用 做差分。\
高 CFG 场景:, 把 的数值放大。大的引导强度可能增加求解难度,高阶更新也未必更稳定;但 预测本身不天然有界。像素空间可使用 thresholding;latent 一般不在 ,不能直接套用像素裁剪。\
结论:DPM-Solver++ 在论文所测 guided sampling 中表现较好,但不是所有文生图模型的必选项;应按具体参数化、网格和 NFE 比较质量。
Q5 为什么 DPM-Solver-2M 比 2S 常用得多?#
参考回答
2M(multi-step)利用前一步已经算过的 做二阶差分,单步 NFE = 1;2S(single-step)在每一步内部额外求一个中间点的 ,单步 NFE = 2。
同样 个时间节点下,2S 总 NFE = 2N,2M 总 NFE = N + 1(第一步没有历史,需要额外一次启动)。2M 在同质量下成本几乎减半。
唯一的代价是需要额外一次前一步的信息缓存,内存开销可忽略。
Q6 Karras schedule 解决了什么问题?为什么 ?#
参考回答
解决的问题:DDPM 的 步在 轴上是等距的,但 β-linear schedule 换算到 σ 轴后极度不均匀——大量 步挤在 σ<1 的低噪声区,高 σ 区反而稀疏。实际只跑 20–50 步需要从 1000 步里子采样,等距子采样会把太多步浪费在 σ→0 的"无信息"极端区(网络已近恒等映射),而真正关键的中低 σ 区又不够密。
Karras 的做法:彻底以 σ 为时间变量,用 生成节点, 控制向低 σ 倾斜的强度;再加上 的截断,避免堆在"无意义"的极低 σ 端。
为什么要向低 σ 倾斜:概率流 ODE 在低 σ 端有 放大项,轨迹 stiff,单步截断误差影响大;高 σ 区 ODE 平缓,大步可容。理由是 ODE 数值稳定性,不是"网络在高 σ 误差大所以要密"——那种说法把因果弄反了。
:扫参最优。(σ 线性)完全没有向低 σ 倾斜;(log σ 线性)倾斜但不够狠; 是 Heun solver 下"最大步截断误差最小化"的经验平衡点。和训练、solver 都解耦,纯采样侧改进。
Q7 Flow Matching 为什么不需要 DPM-Solver?#
参考回答
DPM-Solver 的核心优势来自"利用扩散 ODE 的半线性结构"。但 Flow Matching 的 ODE 是 ,没有半线性结构—— 整体就是一个非线性函数。指数积分子那一套用不上。
更重要的是: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(线性 → ):步数分布和误差分布更匹配
这就是为什么"DPM++ 2M Karras 20 步"在 2023 年前后成了 SD 社区事实上的默认配置。
4.2 进阶讨论:DPM-Solver++ 2M 伪代码#
核心公式回顾#
在 log-SNR 时间 下,记 (单步步长)。DPM-Solver++ 2M 的一步更新(-prediction 版本):
其中 是二阶修正的 估计
用当前步和前一步的 估计做二阶外推——这就是"multi-step"的体现
其中 是当前步的 估计。第一步(没有 )退化为一阶:。
完整采样伪代码#
# 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 的干净样本
需要注意的数值细节#
- 的定义:VP schedule(DDPM 默认)下 ;VE schedule(EDM 默认)下 。两套公式有轻微差别。
- 最后一步 :,公式里的 要用
expm1(-h)避免数值下溢。 - 第一步启动: 不存在,直接用一阶()。部分实现会做一次 Euler 预热。
- CFG 的放置:CFG 在网络输出之后、 计算之前——确保 ++ 参数化接收的是 CFG 后的 。
和 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;不能只改时间变量便推断任意实现逐步相同。