Diffusers 中的 EulerDiscreteScheduler 详解:基于 EDM 的一阶快速采样调度器
【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers
导读
本文围绕 🤗 Diffusers 仓库中的 EulerDiscreteScheduler 官方文档 展开,深入剖析这一基于 EDM(Elucidating the Design Space of Diffusion-Based Generative Models)论文 Algorithm 2 的一阶 Euler 采样调度器。它是 Diffusers 生态中被广泛使用的快速调度器,通常仅需 20~30 步去噪即可获得良好输出。读完本文,你将掌握 EulerDiscreteScheduler 的算法原理、全部构造参数的含义、核心 API 的调用方式,以及如何在 Stable Diffusion 等管线中替换默认调度器,并理解其在仓库各 Pipeline 中的实际应用形态。
EulerDiscreteScheduler 是什么
根据官方文档的定义,EulerDiscreteScheduler实现了 EDM 论文中的 Euler 采样算法(Algorithm 2),是一个快速调度器,通常 20~30 步就能生成质量不错的图像。它的实现源自 Katherine Crowson 的 k-diffusion 开源项目中的原始采样实现。
在 Diffusers 的调度器体系中,它属于KarrasDiffusionSchedulers家族。在 调度器概览文档 的对照表中,EulerDiscreteScheduler与 "Euler" 采样器一一对应,而 "Euler a"(祖先采样变体)则对应EulerAncestralDiscreteScheduler。同时,使用指南 中特别提到,EulerDiscreteScheduler或EulerAncestralDiscreteScheduler常用于生成动漫风格图像。
从源码看,该调度器定义在 scheduling_euler_discrete.py 中,类声明为class EulerDiscreteScheduler(SchedulerMixin, ConfigMixin),其order = 1,即一阶求解器——这是它"快"的根本原因:每一步只做一次模型前向传播,用一阶欧拉步近似求解概率流 ODE。
快速上手:在 Stable Diffusion 管线中使用
虽然StableDiffusionPipeline默认使用PNDMScheduler,但 Diffusers 提供了大量兼容的调度器可供替换。在 Stable Diffusion 管线文档 中给出了标准替换方式:
from diffusers import StableDiffusionPipeline, EulerDiscreteScheduler pipeline = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4") # 直接基于当前调度器配置构造 Euler 调度器 pipeline.scheduler = EulerDiscreteScheduler.from_config(pipeline.scheduler.config) # 或者从模型仓库的 scheduler 子文件夹独立加载 euler_scheduler = EulerDiscreteScheduler.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="scheduler")替换后即可在pipeline(prompt, num_inference_steps=30)中直接使用,通常 20~30 步即可获得良好效果。由于调度器继承自SchedulerMixin与ConfigMixin,它天然支持from_config/from_pretrained/save_pretrained等通用加载保存方法,配置以 JSON 形式与权重一起存放。
构造参数全解析
EulerDiscreteScheduler.__init__通过@register_to_config将全部参数注册进配置。以下是官方文档与源码共同确认的完整参数清单:
| 参数 | 类型 / 默认值 | 说明 |
|---|---|---|
num_train_timesteps | int,默认1000 | 训练阶段的扩散步数 |
beta_start | float,默认0.0001 | 推理使用的起始 beta 值 |
beta_end | float,默认0.02 | 结束 beta 值 |
beta_schedule | "linear"/"scaled_linear"/"squaredcos_cap_v2",默认"linear" | beta 调度策略,将 beta 区间映射为步进序列 |
trained_betas | np.ndarray,可选 | 直接传入 beta 数组,从而绕过beta_start/beta_end |
prediction_type | "epsilon"/"sample"/"v_prediction",默认"epsilon" | 模型输出类型:预测噪声、直接预测去噪样本、或 v 预测 |
interpolation_type | "linear"/"log_linear",默认"linear" | 计算中间 sigma 的插值方式 |
use_karras_sigmas | bool,默认False | 是否使用 Karras 噪声水平序列 {σi} 决定步长 |
use_exponential_sigmas | bool,默认False | 是否使用指数型 sigma 调度 |
use_beta_sigmas | bool,默认False | 是否使用 Beta 分布采样调度(需安装 scipy) |
sigma_min | float,可选 | 噪声调度最小 sigma,缺省取调度中最后一个 sigma |
sigma_max | float,可选 | 噪声调度最大 sigma,缺省取调度中第一个 sigma |
timestep_spacing | "linspace"/"leading"/"trailing",默认"linspace" | 时间步缩放方式(对应论文 Table 2) |
timestep_type | "discrete"/"continuous",默认"discrete" | 时间步类型 |
steps_offset | int,默认0 | 推理步数偏移量,部分模型家族需要 |
rescale_betas_zero_snr | bool,默认False | 将 beta 重缩放为终端 SNR 为零,支持生成极亮/极暗样本 |
final_sigmas_type | "zero"/"sigma_min",默认"zero" | 最终 sigma 取值:"sigma_min"取训练调度最后一项,"zero"置为 0 |
参数间约束与实现细节
- 三种 sigma 模式互斥:源码中明确校验
use_beta_sigmas、use_exponential_sigmas、use_karras_sigmas三者之和不能大于 1,否则抛出ValueError。 - beta 调度生成:
"linear"用torch.linspace均匀采样;"scaled_linear"先对端点开方再平方(源码注释指出该调度专为 latent diffusion 模型设计);"squaredcos_cap_v2"则调用betas_for_alpha_bar生成 Glide 余弦调度,该辅助函数支持cosine、exp、laplace三种 alpha 变换类型。 - 零终端 SNR:
rescale_betas_zero_snr=True时调用rescale_zero_terminal_snr重缩放 betas,并把alphas_cumprod[-1]设为2**-24——源码注释解释这是为了在避免第一个 sigma 变成无穷大的同时,使其尽可能接近 0。 - 初始化校验:
use_beta_sigmas=True时要求环境中安装 scipy,否则直接抛出ImportError。 - 连续时间步特例:当
timestep_type == "continuous"且prediction_type == "v_prediction"时,时间步被定义为0.25 * log(sigma),这是 EDM 风格的对数噪声参数化。
核心 API 与采样循环
set_timesteps:生成去噪时间步与 sigma 序列
set_timesteps是采样前的必调方法,三种传参方式互斥:
- 传入
num_inference_steps:按timestep_spacing策略生成时间步; - 传入
timesteps(自定义时间步列表):跳过自动生成,此时num_inference_steps与sigmas必须为None; - 传入
sigmas(自定义 sigma 序列):通过_sigma_to_t反向插值得到对应时间步。
源码对非法组合(同时传两个、一个都不传、与 Karras/exponential/beta sigma 冲突等)都做了显式ValueError拦截。timestep_spacing的三种策略实现如下:
"linspace":在[0, num_train_timesteps - 1]上等间距取点后倒序;"leading":按step_ratio = num_train_timesteps // num_inference_steps取整索引并加上steps_offset;"trailing":从num_train_timesteps以-step_ratio递减取整。
生成的 sigma 序列末尾会追加一个sigma_last(由final_sigmas_type决定为 0 或sigma_min),保证最后一步能真正收敛到干净样本。时间步与 sigma 会移动到指定device,但self.sigmas随后被移回 CPU,源码注释说明这是为了避免过多的 CPU/GPU 通信。
scale_model_input:输入缩放
sigma = self.sigmas[self.step_index] sample = sample / ((sigma**2 + 1) ** 0.5)在去噪循环中,模型输入必须按(sigma² + 1)^0.5归一化,以匹配 Euler 算法对"缩放输入"的约定(EDM 框架中的c_in缩放)。该方法还会惰性初始化step_index。
step:一阶 Euler 去噪步
step是调度器的核心,参数包括model_output、timestep、sample以及四个随机性控制参数:
| 参数 | 默认值 | 作用 |
|---|---|---|
s_churn | 0.0 | 采样过程中的随机性强度,越大越随机 |
s_tmin | 0.0 | 施加随机性的最小时间步阈值 |
s_tmax | inf | 施加随机性的最大时间步阈值 |
s_noise | 1.0 | 注入噪声的缩放系数 |
算法流程(对应源码第 685~800 行):
- 校验
timestep必须是scheduler.timesteps中的浮点值,拒绝整数索引(如enumerate(timesteps)产生的整数),并提示先调用scale_model_input; - 将
sample提升到float32以避免精度问题; - 计算
gamma = min(s_churn / (len(sigmas) - 1), sqrt(2) - 1)(当s_tmin <= sigma <= s_tmax时,否则为 0),得到sigma_hat = sigma * (gamma + 1);若gamma > 0,则按sample + eps * (sigma_hat² - sigma²)^0.5注入随机噪声(Churn 机制); - 根据
prediction_type计算预测的原始样本pred_original_sample:"epsilon":pred_original_sample = sample - sigma_hat * model_output;"sample"(兼容别名"original_sample"):直接取model_output;"v_prediction":pred_original_sample = model_output * (-sigma / (sigma²+1)^0.5) + sample / (sigma²+1);
- 构造 ODE 导数:
derivative = (sample - pred_original_sample) / sigma_hat; - 沿导数走一步:
prev_sample = sample + derivative * dt,其中dt = sigmas[step_index + 1] - sigma_hat; - 将结果转回模型输出 dtype,
step_index自增 1。
返回值是EulerDiscreteSchedulerOutput,包含prev_sample(作为下一步模型输入)和pred_original_sample(可用于预览进度或 guidance);return_dict=False时返回二元组。
add_noise 与 get_velocity:训练与 img2img 场景
add_noise(original_samples, noise, timesteps):按当前时间步对应的 sigma 向干净样本添加噪声,用于 img2img 构造初始潜在变量等场景。源码对begin_index的三种状态做了区分处理(训练时无 begin_index、img2img 首次加噪、inpainting 中途加噪)。get_velocity(sample, noise, timesteps):实现 v-prediction 模型的速度目标velocity = sqrt(alpha_prod) * noise - sqrt(1 - alpha_prod) * sample,同样拒绝整数时间步输入。
三种 sigma 调度变体
EulerDiscreteScheduler内置三套替代噪声调度,在set_timesteps内部根据配置自动调用:
- Karras sigmas(
use_karras_sigmas=True):实现 EDM 论文提出的调度,_convert_to_karras使用论文中的rho = 7.0,将 sigma 按幂律插值。对应源码中的_convert_to_karras,其注释表明该函数直接移植自 k-diffusion 的 sampling.py。 - Exponential sigmas(
use_exponential_sigmas=True):_convert_to_exponential在log(sigma_max)到log(sigma_min)之间等距取指数,得到对数均匀的噪声水平。 - Beta sigmas(
use_beta_sigmas=True):_convert_to_beta依据 Beta Sampling 论文,用scipy.stats.beta.ppf在 alpha=beta=0.6 的 Beta 分布分位数上构造调度(依赖 scipy)。
这三个方法都会回退使用sigma_min/sigma_max配置,未配置时取输入 sigmas 的首尾值。注意:使用这三种调度时,set_timesteps不允许再传自定义timesteps(源码有显式校验)。
在仓库 Pipeline 中的广泛应用
从源码检索看,EulerDiscreteScheduler被仓库内大量管线直接引用,覆盖文生图、图生视频、音频等多种模态,例如:
- 图像生成:ace_step、cogview4、chroma、dreamlite、ernie_image 等;
- 动画/视频:animatediff、anyflow、easyanimate、cosmos 等;
- 音频:stable_audio_3 等。
在 StableDiffusionPipeline 源码 中可以看到典型调用链:初始化时latents = latents * self.scheduler.init_noise_sigma(第 713 行);去噪循环里每步先scheduler.scale_model_input(latent_model_input, t)(第 1039 行),再送入 UNet 预测噪声,最后scheduler.step(...)得到下一步潜在变量。管线层面的retrieve_timesteps辅助函数(同文件第 95 行起)通过反射检查scheduler.set_timesteps是否支持timesteps/sigmas参数,从而把自定义时间步能力暴露给用户。
测试验证
仓库测试 test_scheduler_euler.py 对 EulerDiscreteScheduler 做了系统性验证:
- 参数扫描:不同
num_train_timesteps(10/50/100/1000)、beta_start/beta_end组合、beta_schedule(linear/scaled_linear)、prediction_type(epsilon/v_prediction)、timestep_type(discrete/continuous)、rescale_betas_zero_snr(True/False); - 全循环数值断言:
test_full_loop_no_noise断言 10 步全循环后样本绝对值和约为 10.0807、均值约为 0.0131(误差容限 1e-2/1e-3),v_prediction与 Karras sigmas 变体也有各自的数值基准,可据此回归验证算法实现的一致性; - 自定义调度一致性:
test_custom_timesteps与test_custom_sigmas在prediction_type × interpolation_type × final_sigmas_type全组合下断言自定义时间步/sigma 与自动调度的结果差异小于 1e-5; - 三种 sigma 模式:
test_karras_sigmas、test_exponential_sigmas、test_beta_sigmas分别覆盖。
实践建议与总结
- 步数选择:Euler 是一阶求解器,20~30 步通常是质量与速度的平衡点;若追求更快,可配合 Karras sigmas 在低步数下获得更平滑的噪声过渡。
- v_prediction 模型:使用 Imagen Video 类 v 预测模型时,记得设置
prediction_type="v_prediction";若同时配合timestep_type="continuous",时间步会自动映射为0.25 * log(sigma)。 - img2img 场景:替换调度器后需保证
scale_model_input在step之前被调用(管线默认会做),否则调度器会打印警告,去噪结果可能不正确。 - 极亮/极暗样本:训练时若配合 offset noise,可设置
rescale_betas_zero_snr=True,避免输出被限制在中等亮度。 - 可复现性:向
step传入generator参数可固定随机性(churn 噪声),配合torch.manual_seed实现可复现采样。
综上,EulerDiscreteScheduler以简洁的一阶 ODE 求解为核心,配合丰富的 sigma 调度、时间步间距与预测类型配置,成为 Diffusers 生态中兼顾速度与质量的高频选择。理解其 源码实现 与 官方文档,可以帮助你在不同模型与场景下做出更合理的调度器配置决策。
【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考