Diffusers 中的 EulerDiscreteScheduler 详解:基于 EDM 的一阶快速采样调度器
2026/9/11 13:43:21 网站建设 项目流程

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。同时,使用指南 中特别提到,EulerDiscreteSchedulerEulerAncestralDiscreteScheduler常用于生成动漫风格图像。

从源码看,该调度器定义在 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 步即可获得良好效果。由于调度器继承自SchedulerMixinConfigMixin,它天然支持from_config/from_pretrained/save_pretrained等通用加载保存方法,配置以 JSON 形式与权重一起存放。

构造参数全解析

EulerDiscreteScheduler.__init__通过@register_to_config将全部参数注册进配置。以下是官方文档与源码共同确认的完整参数清单:

参数类型 / 默认值说明
num_train_timestepsint,默认1000训练阶段的扩散步数
beta_startfloat,默认0.0001推理使用的起始 beta 值
beta_endfloat,默认0.02结束 beta 值
beta_schedule"linear"/"scaled_linear"/"squaredcos_cap_v2",默认"linear"beta 调度策略,将 beta 区间映射为步进序列
trained_betasnp.ndarray,可选直接传入 beta 数组,从而绕过beta_start/beta_end
prediction_type"epsilon"/"sample"/"v_prediction",默认"epsilon"模型输出类型:预测噪声、直接预测去噪样本、或 v 预测
interpolation_type"linear"/"log_linear",默认"linear"计算中间 sigma 的插值方式
use_karras_sigmasbool,默认False是否使用 Karras 噪声水平序列 {σi} 决定步长
use_exponential_sigmasbool,默认False是否使用指数型 sigma 调度
use_beta_sigmasbool,默认False是否使用 Beta 分布采样调度(需安装 scipy)
sigma_minfloat,可选噪声调度最小 sigma,缺省取调度中最后一个 sigma
sigma_maxfloat,可选噪声调度最大 sigma,缺省取调度中第一个 sigma
timestep_spacing"linspace"/"leading"/"trailing",默认"linspace"时间步缩放方式(对应论文 Table 2)
timestep_type"discrete"/"continuous",默认"discrete"时间步类型
steps_offsetint,默认0推理步数偏移量,部分模型家族需要
rescale_betas_zero_snrbool,默认False将 beta 重缩放为终端 SNR 为零,支持生成极亮/极暗样本
final_sigmas_type"zero"/"sigma_min",默认"zero"最终 sigma 取值:"sigma_min"取训练调度最后一项,"zero"置为 0

参数间约束与实现细节

  • 三种 sigma 模式互斥:源码中明确校验use_beta_sigmasuse_exponential_sigmasuse_karras_sigmas三者之和不能大于 1,否则抛出ValueError
  • beta 调度生成"linear"torch.linspace均匀采样;"scaled_linear"先对端点开方再平方(源码注释指出该调度专为 latent diffusion 模型设计);"squaredcos_cap_v2"则调用betas_for_alpha_bar生成 Glide 余弦调度,该辅助函数支持cosineexplaplace三种 alpha 变换类型。
  • 零终端 SNRrescale_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_stepssigmas必须为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_outputtimestepsample以及四个随机性控制参数:

参数默认值作用
s_churn0.0采样过程中的随机性强度,越大越随机
s_tmin0.0施加随机性的最小时间步阈值
s_tmaxinf施加随机性的最大时间步阈值
s_noise1.0注入噪声的缩放系数

算法流程(对应源码第 685~800 行):

  1. 校验timestep必须是scheduler.timesteps中的浮点值,拒绝整数索引(如enumerate(timesteps)产生的整数),并提示先调用scale_model_input
  2. sample提升到float32以避免精度问题;
  3. 计算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 机制);
  4. 根据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)
  5. 构造 ODE 导数:derivative = (sample - pred_original_sample) / sigma_hat
  6. 沿导数走一步:prev_sample = sample + derivative * dt,其中dt = sigmas[step_index + 1] - sigma_hat
  7. 将结果转回模型输出 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内部根据配置自动调用:

  1. Karras sigmasuse_karras_sigmas=True):实现 EDM 论文提出的调度,_convert_to_karras使用论文中的rho = 7.0,将 sigma 按幂律插值。对应源码中的_convert_to_karras,其注释表明该函数直接移植自 k-diffusion 的 sampling.py。
  2. Exponential sigmasuse_exponential_sigmas=True):_convert_to_exponentiallog(sigma_max)log(sigma_min)之间等距取指数,得到对数均匀的噪声水平。
  3. Beta sigmasuse_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_timestepstest_custom_sigmasprediction_type × interpolation_type × final_sigmas_type全组合下断言自定义时间步/sigma 与自动调度的结果差异小于 1e-5;
  • 三种 sigma 模式:test_karras_sigmastest_exponential_sigmastest_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_inputstep之前被调用(管线默认会做),否则调度器会打印警告,去噪结果可能不正确。
  • 极亮/极暗样本:训练时若配合 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询