☰
EDM采样框架解析:sigma参数化、Euler/Heun与Karras调度
2026/10/27 15:46:28 网站建设 项目流程

第一次在采样器下拉列表里看到 EDM 这个词,我以为是又一个新出的求解器,名字缩写难记、面板上除了步数几乎没有别的可调项,扫了两眼就划过去了。后来做一组需要反复复现的对比测试时才发现,EDM 压根不是某一个采样器的名字——它是一整套把 Stable Diffusion 的加噪、去噪、采样全部重新参数化的框架。你在界面里看到的 euler、heun、dpm_2 这些求解器,以及调度器里那个 Karras 选项,追根溯源都能挂到这套框架上。换句话说,很多人天天在用的"EDM 采样器",其实是这套框架被切碎之后散落在界面各处的零件。这篇内容我打算把 EDM 从头拆一遍:它的 sigma 参数化在干什么、Euler 和 Heun 的两个公式怎么写、s_churn 这几个旋钮拧错了会出什么画面、以及在 Stable Diffusion 的实际工作流里怎么把它们用对。适合已经能跑通出图、但对着采样器列表一脑袋问号的中级用户,也适合想搞清楚"换采样器为什么出图完全变样"的调试型玩家。

1. EDM 那篇论文真正改掉的三件事

1.1 把时间步 t 换成噪声强度 sigma

传统扩散模型的写法里,采样过程跟着离散时间步 t = 0,1,2,…,T 走,每一步对应一个"加了多少噪声"的隐含状态。这种写法有个麻烦:t 只是一个序号,它本身不带物理意义,不同模型、不同 schedule 下同一个 t 对应的噪声水平天差地别,你想横向比较两个模型的采样难度都做不到。

EDM 的做法是把这层抽象直接撕掉,用一个连续的噪声标准差 sigma 来描述状态。干净图像对应 sigma = 0,纯噪声对应 sigma 接近 sigma_max。整条采样轨迹就是从 sigma_max 平滑降到 0 的一条曲线,每个点 x 都是"干净图加上 sigma 倍标准高斯噪声"的混合体。

这个换轴带来的直接影响是:步数 N 变成了对这条曲线的离散采点数,而不绑定某个固定的训练时刻表。你可以用 20 步采,也可以用 50 步采,采的始终是同一条连续曲线,只是疏密不同。Stable Diffusion 里那些看起来"步数改了构图就变了"的玄学现象,很大一部分就来自这种离散化误差——采点位置变了,浮点误差累积路径就变了。

论文里给出的三个基准常数值得记一下,它们几乎出现在所有后续实现的默认值里:

参数默认值含义
sigma_data0.5训练数据的标准差,归一化后的经验值
sigma_max80采样起点,纯噪声端的最大噪声强度
sigma_min0.002采样终点,视为"几乎干净"的噪声下限
rho7采点分布曲线的弯曲程度

sigma_max 取 80 而不是 1,是因为这套框架把图像归一化到零均值、单位方差之后,还需要很高的噪声水平才能彻底覆盖数据分布。你在实现里如果看到输入张量被乘了 0.5 之类的系数,大概率就是在对齐 sigma_data。

1.2 预条件:让网络在每个噪声等级下都"手感一致"

这套框架里我认为最值得细看的是预条件(preconditioning)。原始扩散训练有个隐藏问题:在 sigma 极大时,网络输入几乎全是噪声,预测目标和输入的量级差了几十倍;在 sigma 极小时,输入几乎就是干净图,网络只需要学一个接近恒等的映射,梯度又太小。同一个网络要在这两个极端之间切换,训练非常难受。

EDM 用四个系数把输入、输出、训练目标、噪声标签全部重新缩放:

c_skip(sigma) = sigma_data^2 / (sigma^2 + sigma_data^2) c_out(sigma) = sigma * sigma_data / sqrt(sigma^2 + sigma_data^2) c_in(sigma) = 1 / sqrt(sigma^2 + sigma_data^2) c_noise(sigma) = 0.25 * ln(sigma) D(x; sigma) = c_skip * x + c_out * F(c_in * x; c_noise)

这四个式子的作用可以这样理解:c_in 把输入缩放回单位量级,c_noise 把跨度巨大的 sigma 压成对数尺度喂给网络(否则网络对 sigma 的感知分辨率太粗),c_out 把输出缩放回来,而 c_skip 是一个"捷径"——当 sigma 很小时,c_skip 趋近 1,网络输出直接等于输入加上一个小修正,学起来轻松;当 sigma 很大时,c_skip 趋近 0,全部依赖网络 F 自己预测。

训练时的噪声采样也不再是均匀的。论文用对数正态分布:ln(sigma) ~ N(-1.2, 1.2²),再配一个损失权重 lambda(sigma) = (sigma² + sigma_data²) / (sigma · sigma_data)²,把各个噪声等级的损失拉回同一量级。这两处设计合起来的效果是:训练不再需要"按课程从易到难"这类技巧,一次跑完就在整个 sigma 区间上均匀收敛。

注意:预条件是训练阶段的产物,它和网络权重是绑定的。你在推理时把一套 EDM 采样参数套到用 epsilon 预测训练的模型上,等于让网络在错误的输入尺度下工作,出图必然发灰或过曝。这一点后面第 3 章会专门展开。

1.3 采样器只是这套框架里的一个可替换插槽

框架把整个设计空间拆成三块:训练目标、网络预条件、采样器。前两块决定模型本身长什么样,第三块才是推理时你能换的东西。

采样器这一块又被细化成两个独立组件:一是确定性求解器(Euler、Heun 这类积分方法),决定怎么沿着轨迹走;二是噪声调度(sigma 的离散采点方案),决定每一步踩在哪。这两个组件在实现里是解耦的,所以你才能看到"同一个 euler 求解器,配合 karras 调度和配合 normal 调度,出图差别不小"这种组合玩法。

把采样器当插槽这件事,直接解释了 Stable Diffusion 生态里的一个现象:界面上的采样器列表之所以长到让人不想看,是因为它把"求解器 × 噪声调度 × 是否加入随机性"三个维度摊平成了一个扁平列表。真正需要理解的维度只有三个,剩下都是排列组合。

2. Euler 与 Heun 的推导:每一步到底在往哪走

2.1 漂移项 (x - D)/sigma 的几何含义

先看最朴素的 Euler。给定当前状态 x 和噪声等级 sigma,网络输出 D(x; sigma),也就是对干净图像的估计。定义漂移项:

d = (x - D(x; sigma)) / sigma

这个方向有两个读法。从概率角度看,扩散模型学到的得分函数满足 score = (D - x) / sigma²,所以 d = -sigma · score,它指向"概率密度上升"的方向,也就是往更像真实图像的地方走。从几何角度看更直观:x - D 是"当前带噪图减去估计的干净图",正好是残留在里面的那部分噪声;除以 sigma 做归一化,就得到一个单位尺度的噪声方向。

Euler 一步的更新式是:

x_{next} = x + (sigma_next - sigma) * d

因为 sigma 一直在下降,sigma_next - sigma 是负数,所以实际效果是沿着 d 的反方向减去一部分噪声。整个过程就是标准的欧拉法解一个常微分方程,只不过这个 ODE 的"时间变量"是 sigma。

Euler 是一阶方法,局部截断误差是 O(h²)。落到画面上,步数偏少时你会看到两种情况:一是纹理细节被抹平,毛发、织物这类高频结构先崩;二是大色块边缘出现轻微的方向性偏移,因为一阶方法在一次大步长里没法修正曲率。20 步以内用 euler 出图,鼻子、眼睛这种小结构最容易翻车。

2.2 Heun 的半步修正为什么值一次额外前向

Heun 是二阶方法,思路是先用 Euler 迈出一步拿到预测点,再在预测点上重新问一次网络,把两次的斜率取平均。伪代码如下:

d1 = (x - D(x; sigma)) / sigma x_pred = x + (sigma_next - sigma) * d1 if sigma_next != 0: d2 = (x_pred - D(x_pred; sigma_next)) / sigma_next x_next = x + (sigma_next - sigma) * (0.5 * d1 + 0.5 * d2) else: x_next = x_pred

代价很明确:每一步要跑两次网络前向。所以同样标称 20 步,Heun 的实际计算量约等于 Euler 的 40 次前向,出图时间接近翻倍。收益是局部误差从 O(h²) 降到 O(h³),在步数不足时对结构的保持明显更好。

我实测下来的经验区间:如果你手上只有 12 到 18 步的预算,Heun 相对 Euler 的提升是能看出来的,尤其是人脸和手部的结构稳定性;但一旦步数上到 30 以上,Euler 和 Heun 的差距就缩到需要并排放大看才能分辨的程度,这时候多花的那一倍时间就不太值了。

还有一个容易忽略的点:Heun 在最后一步(sigma_next = 0)会跳过第二次前向。这是必须的,因为 sigma = 0 时漂移项要除以 sigma,会直接炸成 inf。你自己写采样脚本时如果忘了这个判断,出图会满屏噪点或者干脆全黑。

2.3 rho = 7 的采点曲线把步数堆在了低噪声区

噪声调度解决的是"这 N 步踩在哪"的问题。EDM 用的是多项式曲线:

sigma_i = ( sigma_max^(1/rho) + i/(N-1) * (sigma_min^(1/rho) - sigma_max^(1/rho)) )^rho

rho = 7 时,这条曲线的形状是:在高噪声端步子迈得很大,在低噪声端步子迈得很密。为什么会这样设计?因为在 sigma 很大时,图像基本被噪声淹没,网络输出对细节的影响很小,大步走不心疼;而在 sigma 很小时,每一点细微扰动都会直接反映成画面上的细节变化,必须小步慢磨。

界面里那个 "Karras" 调度选项,本质就是这条 rho = 7 曲线。和它并列的 normal、exponential、sgm_uniform 是别的采点方案,区别只在曲线形状:

调度方案采点特征常见表现
karras高噪端稀疏、低噪端密集细节保留好,20 步就能出较锐利的图
exponential按指数衰减分配中低噪段过渡平缓,风格化模型更稳
normal相对均匀通用,没明显偏好
sgm_uniform均匀分配步数少时高噪段太密,容易浪费预算
simple线性只在特定模型上有味道

同一个 euler 求解器配 karras 和配 normal,20 步的成图差异可以大到"换了一个提示词"的程度。这不是玄学,是采点位置变了导致的轨迹偏差。

2.4 s_churn 那四个旋钮:随机性到底加在哪

EDM 的随机采样版本(对应界面里带 ancestral 或 sde 后缀的那些)多了一层加噪操作,用四个参数控制:

if s_tmin <= sigma <= s_tmax: gamma = min(s_churn / N, sqrt(2) - 1) else: gamma = 0 sigma_hat = sigma * (1 + gamma) x_hat = x + sqrt(sigma_hat^2 - sigma^2) * s_noise * epsilon

逐项解释:

  • s_churn 是加噪强度总开关,0 表示完全确定性,数值越大每一步注入的随机量越多。上限被 sqrt(2) - 1 卡住,是防止单步噪声超过信号本身。
  • s_tmin / s_tmax 划定生效的噪声区间。默认 s_tmin = 0.05、s_tmax = 50,意思是只在中间段加噪,极高噪声端和极低噪声端都不碰。低噪声端不加噪是为了避免最后关头把已经成型的细节重新打散。
  • s_noise 是噪声幅度系数,默认 1.003,略大于 1 的取值来自论文的实测调优,不是什么神秘常数。
  • gamma 里除以 N 是因为加噪是按步分摊的,步数越多每步加得越少,总噪声量大致不变。

这几个参数在界面里通常藏在"高级采样器"节点里,用默认值就能跑。但如果你追求某种"微颗粒质感"或者想让画面在多次采样中产生微妙变化,把 s_churn 从 0 提到 10 到 20 是个可玩的方向,代价是同一提示词同一种子出图不再完全一致。

3. 界面里的名字对照表与模型匹配的坑

3.1 求解器名字与 EDM 谱系的对应关系

节点式工作流界面里那串采样器名字,按 EDM 谱系归类大致是这样:

界面名称类型是否随机相对耗时适配步数区间
euler一阶 Euler否1x20-40
euler_ancestral一阶 Euler + 加噪是1x20-40
heun二阶 Heun否2x12-25
heunpp2二阶 Heun 改进否2x12-25
dpm_2二阶多步否1x15-30
dpm_2_ancestral二阶多步 + 加噪是1x15-30
lms线性多步否1x25-50

这里要提醒一句:界面把"求解器"和"调度器"拆成两个下拉框时,上表说的"是否随机"和"阶数"只由第一个下拉框决定,第二个下拉框只管采点位置。有些整合包把它们合并成一个列表,名字里带 Karras 字样的,就是在求解器后面挂了 rho = 7 的调度。

dpm_2 这类多步方法严格说不完全属于 EDM 原始论文的算法清单,它来自另一支 DPM-Solver 的工作,但落地实现时复用了同样的 sigma 离散化思路和预条件形式,所以放在同一张表里比较是合理的。

3.2 模型预条件不匹配:出图发灰、过曝、糊成一团的头号原因

这是我最想强调的一节。EDM 的采样公式里,(x - D)/sigma 这个漂移项的正确性,依赖于模型输出 D 确实是对干净图像的估计。而 Stable Diffusion 主流的模型权重大多是 epsilon 预测或 v 预测训练的,它们输出的东西不是 x0。

数学上两者可以互相转换。如果模型输出 epsilon,那么干净图像的估计是 x0 = x - sigma * epsilon(在信号系数归一化的约定下)。实现里通常会在模型包装层做一次转换,把 epsilon 预测翻译成 x0 预测,再喂给 EDM 的采样循环。转换做对了,一切正常;转换缺失或重复,就会出现:

  • 输出整体偏灰、对比度上不来,因为尺度被压了一次。
  • 高光区域过曝成纯白,因为低噪声段的残差没被正确扣除。
  • 画面糊成一团、纹理完全消失,因为等价于在错误的 sigma 上求值。

现在很多界面提供专门的模型采样类型节点,用来声明"这个权重是 epsilon 预测、那个是 v 预测、这两个是连续 sigma 训练"。这个声明不是形式主义,它直接决定了采样循环里的转换分支。你加载一个社区权重后如果出图明显不对,第一件事就是确认这个声明和权重的实际训练方式是否一致,而不是急着去调 CFG。

注意:EDM 原论文的预条件(c_skip / c_out 那套)是训练时就嵌进权重的。对于没有用这套预条件训练的模型,你只能用"数学等价的转换",而不能指望它自动获得 EDM 的训练收益。有些界面里那个把采样方式切成 EDM 的开关,只对确实用连续 sigma 训练的权重有意义,对普通 epsilon 权重套上去反而会出问题。

3.3 一套可以复现的对比测试流程

想搞清楚这些采样器在自己常用模型上的差别,别靠记忆和感觉,做一次受控对比比看十篇帖子都管用。我自己用的流程是:

  1. 固定提示词、固定种子、固定分辨率(建议 768×768,避开 SDXL 原生的分桶干扰)、固定 CFG(先用 5.0)、固定步数(先用 20)。
  2. 先把模型采样类型节点设成与权重匹配的正确值,跑一张基线图存档。
  3. 只换采样器下拉框,euler → heun → dpm_2 → lms 依次跑,其余全部不动。
  4. 再把调度器固定成 karras,重复第 3 步。
  5. 把四张图拼在一起对比,重点看三处:皮肤和毛发的微纹理、文字或线条的边缘锐度、暗部有没有色块堆积。

这套流程跑下来大概十分钟,你能得到一份只属于自己模型和提示词组合的经验表,比任何通用推荐都准。记录的时候把步数和耗时也写进去,因为 heun 的 20 步实际等于 euler 的 40 次前向,不看耗时对比是不公平的。

4. 步数、CFG 与随机性的联动调参

4.1 步数的收益拐点在哪里

步数不是越多越好,这话说起来像常识,但拐点在哪是有数据可循的。以 karras 调度的 euler 为例,我在几组不同风格的提示词上做过阶梯测试:

步数观察结果
8-12大结构能对,五官和手指容易崩,画面偏软
15-20大部分人像和场景的可用区间,细节基本到位
25-30微小纹理继续改善,改善幅度已经不明显
35-50除非是极精细的材质或大尺寸出图,否则纯属浪费
50 以上可能出现细节过度锐化、皮肤出现不自然的颗粒

二阶方法(heun、dpm_2)的可用步数下限能往前提 5 步左右,因为每次更新用的信息更多。反过来说,二阶方法在 40 步以上的边际收益比一阶方法更早归零。

还有一个反直觉的点:步数增加不总是让画面更"正确"。有些模型在极低步数下会产生一种结构松散但构图有趣的图,加步数之后反而收敛到一个更平庸的解。如果你在做创意探索,8 到 12 步配随机采样器(euler_ancestral)批量出图,然后再挑一张用高步数精修,是效率很高的玩法。

4.2 CFG 和采样器的交互比想象中大

CFG 控制的是条件引导强度,理论上和采样器正交,但实际体验里两者搅在一起。原因在于随机采样器(带 ancestral 的那些)每一步都在注入噪声,而 CFG 的引导是在每次前向里完成的,两者叠加会放大高 CFG 下的过饱和现象。

我的一般建议是:确定性采样器(euler、heun、dpm_2)配 CFG 5.0 到 7.0 比较舒服;随机采样器(euler_ancestral、dpm_2_ancestral)配 4.0 到 6.0,因为它们自带的那点随机扰动本身就起到了轻微的多样性提升作用,不需要太强的引导去硬拉。CFG 超过 9 之后,无论配哪个采样器都会出现明显的边缘发亮和色彩断层,这时候应该去改提示词,而不是继续拧 CFG。

另外,带 s_churn 的那套随机机制和 ancestral 系列并不是一回事。前者是在 sigma 上加扰动再减回去,后者是在每一步注入新的噪声样本。功能上有重叠,但实现路径不同,同一个界面里一般不会同时开启。

4.3 高分辨率和局部重绘时的参数修正

出图尺寸一上去,EDM 系采样器的表现会变。1024 以上的分辨率,同样的提示词和步数,画面容易出现"整体锐、局部平"的情况,因为采点在像素空间上的相对密度被稀释了。

我的做法是:分辨率翻倍时,步数增加 20% 到 30%,或者干脆换成二阶方法。如果做局部重绘(inpaint),把重绘区域当成独立的一张图来处理,步数按区域尺寸重新算,而不是沿用整图步数。局部重绘时用确定性采样器更稳,随机采样器会让重绘区和原图之间出现接缝处的质感差异。

还有一个实用技巧:局部重绘的最后几步如果关掉 s_churn(或者干脆换 euler),接缝会明显更干净。原理和第 2.4 节说的一样,低噪声段不该再引入扰动。

5. 踩坑记录:从画面症状反推参数问题

5.1 三种典型画面症状的归因路径

我把自己遇到过和帮别人排查过的问题整理成一张对照表,按症状倒推原因,比盲调参数快得多:

画面症状优先怀疑验证方法
整体发灰、对比度低模型输出转换缺失,尺度错位换回常规采样器看是否恢复
高光死白、暗部死黑CFG 过高叠加随机采样器CFG 降到 5 试一次
满屏细密噪点步数过少 + ancestral 系列步数翻倍或换确定性采样器
规则网格状纹理低噪声段采点太稀换 karras 调度
边缘方向性拖影一阶方法步长过大换 heun 或增加步数
同一组参数两次出图不同s_churn 或 ancestral 的随机性检查 seed 与采样器类型

这张表里最容易被误判的是第一行。很多人看到图发灰,第一反应是"模型坏了"或者"这个采样器不行",其实是模型采样类型的声明没对上,导致 EDM 循环里多做或少做了一次尺度转换。换回他平时用的采样器立刻就正常,反过来验证了这个判断。

5.2 一次完整的排查链路实录

说个具体的。某次我在一个新品类的权重上试 euler + karras,20 步,出图整体偏灰、细节全糊,第一眼像是噪声没降干净。当时的排查顺序是这样的:

第一步,把采样器换成平时最常用的那个(非 EDM 谱系的),其余不动。结果依然偏灰。这一步排除了采样器本身的问题,把矛头指向模型或参数。

第二步,检查模型采样类型节点的声明值,发现默认停在了一个和该权重训练方式不匹配的选项上。改成匹配的值之后重跑,灰度消失,但细节仍然偏软。

第三步,怀疑步数。把 20 步提到 30 步,细节有改善但没到满意程度。再看调度器,当前是 normal。

第四步,把调度器换成 karras,步数回落 22 步,细节锐度明显上来了。到这里问题基本解决。

第五步,为了确认不是运气,把种子换三个重复跑,结果稳定。结论:根因是模型声明不匹配 + 调度器选错,两个问题叠加,只改一个都只能解决一半。

这个链路里我特意把"换采样器"放在第一步,因为它是最快的排除手段,成本只有一次前向。很多人喜欢从调 CFG 开始,那是效率最低的路径,因为 CFG 对灰度这种系统性偏差几乎没有作用。

5.3 参数改动的记录习惯

EDM 系采样器的参数互相纠缠(步数影响 gamma、gamma 影响加噪量、加噪量影响最终纹理),单靠脑子记不住哪次改动起了作用。我的习惯是每次实验都存一份节点图快照,文件名里带上采样器名、步数、调度器和一句提示词摘要,比如euler_22_karras_人像A。一周之后回头看,哪些组合稳定好用一目了然。

另一个小技巧是,把常用的几组采样器配置做成独立的预设节点组,用的时候整体切换而不是逐个改下拉框。这样做能避免"改了采样器忘了改调度器"这类低级错误,我在早期没少因为这种疏忽得出错误结论。

最后分享一个我自己反复验证过的结论:在这套框架里,调度器(采点位置)对出图的影响,往往比求解器的阶数更大。与其纠结选 euler 还是 heun,不如先把调度器在 karras 和 normal 之间对比一遍,那一步的收益通常更直接。

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

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

立即咨询