采样器与调度器
ComfyUI 采样器与调度器核心学习笔记
在 Stable Diffusion 以及最新的 Flux 等扩散模型中,图像生成本质上是一个**“从纯噪声中逐步去噪”**的过程。在这个过程中,**调度器(Scheduler)与采样器(Sampler)**是配合最紧密的两个核心组件。
经典比喻:登山与降噪
如果把图像生成比作“在大雾天气中看清远处的风景”:
- 调度器(Scheduler):负责规划 “大雾消散的节奏(里程表)”。它决定在第几步时大雾应该退去多少。
- 采样器(Sampler):负责执行 “下山的步伐(走路技术)”。它根据当前的可见度和罗盘方向,计算具体迈多大步、往哪迈。 ^scheduler
一、调度器(Scheduler)—— 降噪节奏的规划者
调度器的任务是:给定总步数 N,规划出一串 “进度目标”(经典扩散模型里是 Sigma,Flow Matching 模型里是时间戳 t),采样器只负责在相邻两个目标之间做数值计算。
ComfyUI 实践陷阱:Wan2.1 / WanAnimate 节点里 scheduler 下拉框为何全是 euler、dpm++?
部分自定义节点(如 WanAnimate/Wan2.1 的采样节点)UI 上标注的是 scheduler,展开后却是 euler、dpm++、unipc 这些实际上属于采样算法(Sampler)的选项——这是命名错位,不是概念本身变了。
根源在于这类节点底层直接基于 HuggingFace diffusers 库:diffusers 把“采样算法 + 它内部管理的 sigma/timestep 序列”打包成同一个类,并统一命名为 XxxScheduler(如 EulerDiscreteScheduler、UniPCMultistepScheduler)。节点作者直接沿用了这个底层类名/变量名 scheduler,才造成了 UI 用词和 ComfyUI 原生 KSampler 里 sampler_name / scheduler 两个下拉框对不上。
判断方法:别看参数叫什么名字,看下拉框里的选项内容——选项是 euler/dpm++/unipc 这类算法名,它就是本节后面讲的采样器(Sampler);选项是 normal/karras/sgm_uniform 这类曲线名,才是本节讨论的调度器(Scheduler)。
1. 经典扩散模型:调度 Sigma
什么是 Sigma ($\sigma$)?
在统计学中,$\sigma$ 代表标准差。在 AI 绘画中,它代表**“噪声的强度 / 浓度”**。
- $\sigma$ 极大(如 14.6):代表特大浓雾,画面是一片混沌的纯随机噪声。
- $\sigma$ 极小(如 0.1):代表薄雾消散,细节开始显现。
- $\sigma = 0$:代表晴空万里,无噪声,生成最终干净的图像。
注:当我们调整 K-Sampler 里的 Denoise(降噪幅度)时,其实就是在调整初始的 $\sigma$。Denoise = 0.5 时,采样器直接从中等 $\sigma$(中等大雾)开始去噪。
| 调度器名称 | 降噪曲线特点 | 适用场景 |
|---|---|---|
| normal | 线性或平滑曲线递减(如 10 -> 8 -> 6 -> 4 -> 2 -> 0) | 经典模型,表现平稳 |
| karras | 前期降噪极快(粗略吹散大雾),后期降噪极慢(精细微调细节)。 | 极其推荐。配合 DPM++ 能产生极佳的细节 |
| exponential | 指数级下降,给后期保留极多的步数处理微小细节 | 适合超高细节、步数较多的场景 |
| sgm_uniform | 针对 SGM 框架设计的均匀分布 | 常见于 SDXL / SD3 / Cascade 官方推荐工作流 |
| simple | 简单的步长划分,不针对高级算法进行平滑 | 基础测试,或 LCM/Hyper-SD 蒸馏模型 |
为什么 karras 曲线“前快后慢”反而更好?
图像的整体构图在高噪声区间就已经决定,此时噪声浓度的微小变化对最终结果影响很小,所以可以大步跳过;而细节纹理只有在低噪声区间才会显现,此时哪怕很小的 $\sigma$ 变化也会显著改变细节,所以需要放慢脚步、精细采样。Karras 调度正是把“快慢节奏”按这个规律重新分配。
2. Flow Matching 下的调度器:时间戳与 Shift
在 Flux、SD3 这类 Flow Matching(流匹配) 模型中,调度器不再规划 Sigma,而是规划时间戳 $t \in [0,1]$($t=1$ 代表纯噪声,$t=0$ 代表清晰图像)。在最简单的“整流流”(Rectified Flow)参数化下,噪声强度就直接等于 $t$ 本身:$x_t = (1-t) x_0 + t \cdot \varepsilon$。
为什么 Flow Matching 还需要额外的 Shift 参数?
如果直接对 $t$ 做均匀采样,会发现:分辨率越高,图像里像素之间的冗余、关联信息越多,同样的 $t$ 在高分辨率图上“看起来没那么乱”——也就是说,模型需要相对更多的步数停留在高噪声阶段,才能把整体构图确定下来;分辨率越低则相反。Shift 就是用来手动/自动补偿这个差异的调节旋钮。
Shift 的数学本质:对均匀采样出的步进比例做一次单调的重参数化:
$$t_{shift} = \frac{s \cdot t}{1 + (s-1) \cdot t}$$其中 $s$ 就是 Shift 值。这是一个凹函数(当 $s>1$ 时),效果是:
- $s > 1$:把更多的步数“压”在 $t$ 接近 1(高噪声、构图阶段),构图更稳,适合高分辨率图像。
- $s = 1$:退化为恒等映射,等价于不做任何偏移的均匀调度。
- $s < 1$:把更多步数让给低噪声(细节阶段),适合低分辨率或想要精细纹理的场景。
SD3 与 Flux 的两种用法
- SD3:使用静态 Shift,训练/推理时固定一个值(参考值
shift ≈ 3.0,对应 1024px 训练分辨率)。 - Flux:使用动态 Shift(Dynamic Shifting)。根据当前图像的 token 数(约等于 $\frac{H}{16}\times\frac{W}{16}$)在
base_shift(参考值 0.5,对应短序列/低分辨率)与max_shift(参考值 1.15,对应长序列/高分辨率)之间线性插值出 $\mu$,再代入等价形式: $$t_{shift} = \frac{e^{\mu}}{e^{\mu} + \left(\frac{1}{t}-1\right)}$$ 这样分辨率越高,Shift 会自动变大,无需手动调参。
(以上数值为公开实现的参考值,不同版本/微调模型可能略有差异,实际以对应推理代码为准。)
在 ComfyUI 中,这对应 ModelSamplingSD3 / ModelSamplingFlux 节点里的 shift 滑块:画面构图跑偏、崩坏 → 调大 Shift;细节不够、观感发糊 → 适当调小 Shift。
代码复现:验证 Shift 公式与 ComfyUI 后台日志完全对齐
工程实现上(如 Diffusers / ComfyUI 的 FlowMatchEulerDiscreteScheduler),原始时间步通常是从 1000 均匀切到 5(而不是切到 0),最后再手动补一个 0.0 作为终止点——这是为了避免 ODE 求解器在最后一步除以 0 导致数值不稳定。
import numpy as np
def get_sigmas(steps: int = 8, shift: float = 5.0) -> np.ndarray:
timesteps_raw = np.linspace(1000.0, 5.0, steps) # 避免终点落到 0
sigmas_raw = np.append(timesteps_raw / 1000.0, 0.0) # 归一化 + 补终止点
return (shift * sigmas_raw) / (1.0 + (shift - 1.0) * sigmas_raw) # 套用上面的 Shift 公式
print(np.round(get_sigmas(8, 5.0), 4))
# [1. 0.9679 0.9264 0.8706 0.7914 0.6705 0.4631 0.0244 0. ]该输出与 ComfyUI 控制台打印的 Using sigmas: tensor([1.0000, 0.9679, ..., 0.0000]) 逐位对齐,验证了 Shift 公式和实际实现一致。
(不同模型/节点的起止时间步、shift 默认值可能不同,实际以对应推理代码为准。)
3. LCM 专用调度器:一致性模型的“跳步”节奏
LCM(Latent Consistency Model)用的调度器和上面两类完全不是一回事——它并不负责规划一条平滑的 Sigma/时间戳曲线,而是要在**模型蒸馏阶段就已经烘焙好的一小撮“边界时间点”**里做挑选。
背景:一致性蒸馏(Consistency Distillation)
LCM 的教师模型是一个标准扩散模型,其 ODE 轨迹上任意一点理论上都能唯一确定同一条轨迹的终点 $x_0$。LCM 的学生模型被训练成直接学会这个“一致性函数” $f_\theta(x_t, t) \approx x_0$,因此不需要像传统采样器那样一步步小幅去噪,而是可以大跨步跳跃。
训练时通常只在原始 1000 步里跳着选取一小撮锚点(例如 original_inference_steps=50,跳步间隔 k=20),模型只在这些锚点之间的跳跃被“教过”,跳跃间隔以外的位置模型并没有针对性优化。
正因为如此:
- LCM 调度器的职责变成了从这一小撮锚点中,均匀挑出 N 个供当前步数使用,而不是设计一条平滑衰减曲线。
- 这也解释了为什么经验上
simple/sgm_uniform更适合 LCM,而karras反而容易翻车:Karras 曲线会强行在前几步塞入一个不在锚点附近的陡峭下降,这个位置模型并没有被训练过如何跳跃,容易出现结构崩坏或画面发灰。
二、采样器(Sampler)—— 降噪算法的执行者
采样器是一个数学求解器(Solver)。它在每一步拿着调度器给出的“降噪目标”,去问 AI 模型(UNet/DiT):“你觉得当前画面里的噪声/速度长什么样?”。然后用特定的算法计算出下一步的像素更新。
1. 采样器家族分类
- Euler 家族(欧拉法)
- 代表:
euler,euler_ancestral - 特点:最基础的单步求解法。计算极快(每步只需调用 1 次模型)。路径是走直线的。在经典扩散模型里是“够用但欠精确”的基线算法;但在 Flow Matching 模型里反而是最精确的解法(见下文 §2.3.1)。
- 代表:
- Heun 家族(辛恩法)
- 代表:
heun,heunpp2 - 特点:二阶修正算法。走一步、探一步、取平均。精准度极高,但每步耗时翻倍(需要调用 2 次模型)。
- 代表:
- DPM / DPM++ 家族
- 代表:
dpm_2,dpmpp_2m,dpmpp_2m_sde,dpmpp_3m_sde - 特点:基于 DPM-Solver(++) 的指数积分器,专为扩散模型的 ODE/SDE 设计,有“记忆力”,能利用历史路径预测去噪曲线的弧度。能在 15-20 步内获得极佳画质;带
_sde后缀的变体额外引入随机项,细节质感更强(见下文 §2.3.2)。
- 代表:
- LCM 家族(Latent Consistency)
- 代表:
lcm - 特点:配合 LCM 蒸馏模型使用,靠一致性函数直接“跳”向清晰图像,实现 4~8 步超高速出图(见下文 §2.3.3)。
- 代表:
2. 采样器名字中的“后缀”含义
_ancestral或a(祖先采样)- 机制:在每一步计算完后,故意往里面塞回一点新噪声。
- 特点:永远不会完全收敛。步数越多,画面一直在发生细微变化(更有创造力,但不利于微调)。
_sde(随机微分方程)- 机制:利用随机路径求解,本质上是把确定性的概率流 ODE 换成与之边际分布等价的反向 SDE。
- 特点:能生成更多、更自然的质感与细节(如皮肤纹理),但需要略多的步数(25~35 步)才能把额外注入的噪声重新收敛干净。
_cfg_pp:针对高 CFG 时的画面崩溃进行了算法层面的预测与优化。_gpu:部分数学计算移入 GPU 内部加速执行。
3. 深入拆解:三个重点采样器
3.1 Euler —— 从“将就能用”到“流匹配之王”
在经典扩散模型中,Euler 只是对 $\frac{dx}{d\sigma}$ 做一阶泰勒展开的最简单近似,误差随步长线性累积,所以步数少时容易出现细节丢失、画面发闷,通常需要 20 步以上才够用。
但在 Flow Matching 模型(Flux / SD3)中,模型训练时被强制学习了一条**“整流”(Rectified Flow)的速度场 $v_\theta(x_t,t)$——理论上这条轨迹本身就是(近似)直线。既然路径已经是直线的,Euler 这种“沿切线直走”的一阶方法就不再是近似,而是最精确、最省算力**的解法:
$$x_{next} = x_{current} + dt \cdot v_\theta(x_t, t)$$反而是 DPM++ 这类为“弯曲”ODE 设计的高阶纠偏算法,在近乎笔直的轨迹上容易“过度纠偏”,引入不必要的误差,导致画面崩坏。这就是为什么 Flux/SD3 官方工作流几乎清一色用 euler + 一个合适的 shift,而不是 dpmpp_2m。
3.2 DPM++ SDE —— 高阶指数积分器 + 随机修正
DPM-Solver(++)(Lu et al., 2022)的核心思路,是把反向 ODE 换元到 log-SNR 域 $\lambda = \ln(\alpha_t/\sigma_t)$ 下求解。在这个坐标系里,扩散过程的“线性部分”可以被精确积分,只剩下模型预测的非线性项需要用泰勒展开近似——这比 Euler 直接在原始 $\sigma$ 空间里做一阶近似要精确得多,这也是为什么它能在 15-20 步内就逼近上百步 DDIM 的画质。
家族内部的差异
- 2S(单步二阶):类似 Heun,每一步额外多问一次模型求中间点,不依赖历史,每步 2 次模型调用。
- 2M(多步二阶):复用上一步已经算过的模型输出做二阶修正,不额外增加模型调用次数,相当于“免费”获得二阶精度,是最常用的性价比之王(
dpmpp_2m)。 - 3M(多步三阶):额外复用再往前一步的历史,用更高阶多项式拟合去噪曲线,细节更平滑,代价极低。
_sde 后缀在此基础上做的事情,是把纯 ODE 更新和一次校准过的噪声注入混合起来,等价于求解与原 ODE 边际分布一致的反向 SDE(思路上与祖先采样、Karras 等人提出的 churn/restart 技巧一脉相承):每一步在 DPM-Solver 的确定性更新之外,再叠加一份按 $\sigma$ 变化量精确校准过的高斯噪声。
为什么 SDE 变体尤其偏爱 karras 调度?
每一步注入的新噪声,需要后续还有足够多、且间距足够密的步数才能被重新“收敛”干净。如果调度曲线在低 $\sigma$ 区间步长太大(比如 normal),注入的噪声来不及被收干净,画面就会发灰、发糊;而 karras 恰好在低 $\sigma$ 区间步长最密,天然适合承接 SDE 注入的随机项,这也是 dpmpp_2m_sde / dpmpp_3m_sde 几乎总是和 karras 搭配、且普遍需要比 ODE 版本多几步(25~35 步)的原因。
3.3 LCM —— 一致性函数的单步/少步跳跃
LCM 采样器每一步做的事情和上面所有算法都不同:它不是“沿着 ODE/SDE 小步前进”,而是直接调用一致性函数把当前噪声图投影到清晰图像的估计值:
$$\hat{x}_0 = f_\theta(x_{t_n}, t_n)$$如果只需要 1 步,$\hat{x}_0$ 就是最终输出。如果是常见的 4~8 步 LCM 流程,则会把 $\hat{x}_0$ 按前向扩散公式重新加噪到下一个(跳步选出的)锚点 $t_{n-1}$,再重复调用一致性函数:
$$x_{t_{n-1}} = \alpha_{t_{n-1}} \hat{x}_0 + \sigma_{t_{n-1}} \cdot \varepsilon_{new}$$这种“猜测终点 → 重新加噪 → 再猜测”的循环,本质上是在锚点之间反复横跳,而不是像 Euler/DPM++ 那样沿着连续曲线爬行——这也是它能把步数压到个位数、但必须配合专门蒸馏过的 LCM 模型和对应锚点调度(见 §1.3)才能工作的根本原因;普通底模直接套用 lcm 采样器是无效的。
三、协同工作流与经典黄金组合
1. 经典扩散模型的协同逻辑
[开始生成]
│
├──> 1. 读取设置:Steps=20, Scheduler=karras (规划每步的 Sigma 目标)
│
├──> 2. K-Sampler 循环:
│ 每一物理步骤中,采样器(如 dpmpp_2m)询问模型(UNet)噪声方向,
│ 并结合当前的 Sigma 与下步的目标 Sigma,计算出更新后的像素值。
│
└──> 3. 输出去噪完成的 Latent 图像。2. 推荐黄金配对组合
出图黄金搭配速查
- 日常全能、细节丰富(首选):
dpmpp_2m+karras(20~30 步) - 写实人像、极致质感:
dpmpp_2m_sde/dpmpp_3m_sde_gpu+karras(25~35 步) - 经典插画、动漫艺术:
euler_ancestral+normal(20 步) - 低配显卡、秒速出图:
lcm+sgm_uniform/simple(4~8 步,不要配 karras) - Flux / SD3(Flow Matching):
euler+ModelSamplingFlux/SD3的shift(高分辨率调大、细节不足调小),一般 4~20 步即可,无需强上dpmpp_2m
四、前沿拓展:Flow Matching(流匹配)下的变革
在最新的模型(如 Flux, Stable Diffusion 3)中,底层的数学模型从“概率扩散”进化到了 Flow Matching(流匹配)。此时调度器和采样器发生了根本性的变化:
- 模型不再预测“噪声” $\rightarrow$ 而是预测一个速度向量场 $v_{\theta}(x_t, t)$。模型在每一个位置都会告诉你:“这一步像素应该往哪移动,移动多快”。
- 调度器不再规划“噪声 $\sigma$” $\rightarrow$ 而是规划时间戳分布(Timestep $t \in [1, 0]$),并通过 Shift(见 §1.2)把更多步数压缩堆积在变化剧烈的临界阶段。
- 为什么 Euler 成了 Flow Matching 的王者? 详见 §2.3.1。
五、数学原理:$x_{t+1}$ 是如何计算出来的?
以 Flow Matching 下的 Euler(欧拉)采样器 为例,它在 GPU 里的积分计算过程如下:
1. 核心公式 (常微分方程数值积分)
$$\frac{dx_t}{dt} = v_{\theta}(x_t, t)$$根据欧拉积分法,下一个状态的计算公式为:
$$x_{next} = x_{current} + dt \cdot v_{\theta}(x_t, t)$$2. 单步数值演化实例
假设我们要在 GPU 里计算某一个像素点的下一个数值:
单步计算数值拆解
- 当前状态 $x_t$:某个像素当前的 Latent 值为
2.5。 - 当前时间 $t$ 与下一步 $t_{next}$:当前 $t = 0.8$,下一步 $t_{next} = 0.7$(这两个值已经过 Shift 重参数化,见 §1.2)。
- 计算时间差 $dt$:$dt = 0.7 - 0.8 = -0.1$。
- AI 模型预测:Flux 神经网络计算出当前点的速度 $v = 15.0$。
- 采样器积分计算: $$\text{新数值} = 2.5 + (-0.1 \times 15.0) = 2.5 - 1.5 = 1.0$$
最终在 $t=0.7$ 时,该点的数值更新为 1.0。GPU 会对数百万个像素同时并行这一计算。
3. Heun 采样器的修正计算 (对比)
如果使用的是 Heun 采样器,它会为了防止“直线跑偏”,进行一次预测-纠偏:
- 预测步:先用欧拉法盲猜一个临时终点:$x'_{next} = x_t + dt \cdot v(x_t, t)$。
- 纠偏步:询问模型在临时终点的速度,并与起点速度取平均值:$v_{avg} = \frac{v_{起点} + v_{终点}}{2}$。
- 更新步:用平均速度更新:$x_{next} = x_t + dt \cdot v_{avg}$。
(注意:在 Flow Matching 模型中,因为速度场接近直线,$v_{起点} \approx v_{终点}$,因此无需使用 Heun 进行多余的计算——这也正是 §2.3.1 中 Euler 反而更优的数学根源。)
4. DPM++ 2M 的“免费”二阶修正 (对比)
dpmpp_2m 不需要像 Heun 那样额外调用模型,而是复用上一步已经算出的模型输出 $\epsilon_{prev}$,和当前步输出 $\epsilon_{cur}$ 一起,在 log-SNR 域里做线性外推来逼近二阶精度:
其中 $h$ 是相邻两步 log-SNR 的差值,$r$ 是当前步长与上一步长的比值。直觉上:用最近两步的“弧度”去外推下一步该往哪个方向弯,而不是像 Euler 那样假设一直沿切线直走。
记忆
#flashcards
ComfyUI 里 Wan2.1/WanAnimate 节点的 scheduler 下拉框为什么选项全是 euler/dpm++ 这些采样算法名?:: 因为这类节点底层直接基于 diffusers 库,diffusers 把“采样算法+它管理的 sigma/timestep 序列”统一打包命名为 XxxScheduler 类(如 EulerDiscreteScheduler),节点作者沿用了这个底层类名,是命名错位而非概念本身变化;判断真实身份要看下拉框里的选项内容(算法名 vs 曲线名),而不是参数叫什么。
Flow Matching 模型里的 Shift 参数是用来做什么的,$s>1$ 和 $s<1$ 分别对应什么场景?:: 对均匀时间戳做重参数化 $t_{shift}=\frac{s\cdot t}{1+(s-1)t}$;$s>1$ 把更多步数压在高噪声/构图阶段,适合高分辨率图像;$s<1$ 把更多步数让给低噪声/细节阶段,适合低分辨率或追求细节的场景。Flux 会根据图像 token 数自动插值出动态 Shift。
为什么 Euler 在经典扩散模型里“将就能用”,但在 Flux/SD3 这类 Flow Matching 模型里反而是最优解?:: 因为 Flow Matching 训练时把速度场强制整流成近似直线(Rectified Flow),Euler 沿切线直走正好精确匹配这条直线路径;而 DPM++ 等为弯曲 ODE 设计的高阶纠偏算法在近乎笔直的轨迹上反而会“过度纠偏”,引入多余误差。
DPM++ SDE 为什么普遍要配合 karras 调度器、且比 ODE 版本需要更多步数?:: _sde 后缀会在每步的确定性更新之外注入一份校准过的噪声(等价于求解反向 SDE),这些噪声需要足够密的后续步数才能被重新收敛干净;karras 调度恰好在低 $\sigma$ 区间步长最密,能承接这份随机项,因此二者是天然搭档,且通常需要 25~35 步。
LCM 采样器和调度器为什么必须配合专门蒸馏过的模型,而且推荐用 simple/sgm_uniform 而不是 karras?:: LCM 通过一致性蒸馏学会了一致性函数 $f_\theta(x_t,t)\approx x_0$,只在训练时选定的一小撮跳步锚点之间做“猜终点→重新加噪→再猜”的跳跃;调度器职责变成从这些锚点里挑选,而非设计平滑衰减曲线,karras 的陡峭前段会落在模型未学过的位置容易崩坏,simple/sgm_uniform 更贴近锚点分布。