扩散模型训练与采样加速:从损失加权到可控生成的关键细节 扩散模型这两年的热度不用我多说从图像生成到视频生成、再到语音和科学计算几乎到处都能看到它的影子。上一篇文章我们把扩散模型的核心思想、前向加噪过程和反向去噪的基本数学框架从头到尾捋了一遍这篇接着往下走重点聊训练和采样阶段那些真正决定模型效果和实用性的细节。如果上一篇文章解决的是“扩散模型是什么”这一篇要解决的就是“扩散模型怎么做才能用起来顺手”。目标读者是已经跑通基础训练代码、开始琢磨更高效采样或者想做可控生成的开发者内容偏原理加实操尽量把每一步为什么这么设计也讲清楚。1. 训练阶段那些容易被忽略的细节很多人第一次写扩散模型训练循环照着开源代码抄一遍loss 也能降生成的图片看着也不差但换一个数据集、改一个模型结构问题就全出来了。原因很简单扩散模型的训练看似就是“加噪、预测噪声、计算 MSE”但里面每一个组件都有它自己的脾气单独拿出来都能写一篇深挖的博客。这一节我把训练阶段最容易被忽视的几个关键点拆开讲。1.1 损失函数不是只有“长得像”就够了最常见的训练目标是从变分下界简化出来的均方误差损失也就是让模型预测出的噪声和真实添加的高斯噪声尽可能接近。但这里有一个隐含假设不同时间步的预测误差对最终生成质量的影响是同样重要的。实际训练中这个假设并不成立越靠近高噪声区域模型做预测的难度越大损失值天然偏高而这些区域对应的又恰恰是生成过程中决定图像整体轮廓的阶段。如果简单把所有时间步的损失一视同仁训练就会被高噪声部分的梯度主导。处理办法是引入损失加权策略。经典实现里用的是简化版的加权方式让所有时间步权重相等但实践下来很多人会做一点改进给中间时间步稍微加一点权重或者用信噪比相关的单调函数作为权重。我之前在某个项目里试过给不同时间步按信噪比做加权同等训练步数下生成图的色彩饱和度和纹理细节确实有明显改善。还要注意一个容易踩的坑用混合精度训练时MSE 损失在 FP16 下容易出现数值问题。噪声预测头的输出范围通常在 -1 到 1 之间FP16 的精度够用但反向传播时梯度的量级可能跨越很大建议在损失计算时先把预测和目标转到 FP32 再算 MSE否则 loss 曲线会出现诡异的抖动。注意扩散模型的损失曲线不会像分类任务那样一路平滑下降它在训练初期波动大、后期在某个低位震荡是正常现象不要因为 loss 不平滑就急着调学习率。1.2 噪声调度表不同阶段该学什么噪声调度表决定了每个时间步加的噪声有多大进一步决定了模型在每个时间步“看到”的输入是什么样的。最早的工作里用的是线性调度从很小的噪声均匀增加到很大的噪声。后来大家发现线性调度在低噪声区域分配的时间步太多而真正让图片从纯噪声浮现出结构的阶段集中在中等噪声水平那段于是出现了各种改进方案。最常用的是余弦调度它在中间时间步分配了更多的采样点让模型有更多机会学习中等难度去噪任务。现在主流开源库里默认的也是基于余弦思想的变体。除了这种全局调度还有一种缩放策略就是在把训练图像输入模型前先对整体输入乘一个系数比如常见的 0.1 之类的值让模型在训练时更容易专注于相对干净的图像区域。这种做法在超高分辨率生成任务里很有效因为大尺寸图像的去噪更难。实际使用中调度表的选择要和采样器搭配着看。训练时用的噪声调度表如果和推理时采样器内部假定的噪声分布不匹配生成结果会明显偏灰或者偏暗这也是很多人从网上下载别人训练的模型放到自己采样器里结果翻车的常见原因之一。1.3 EMA 模型权重与其他训练细节EMA指数移动平均几乎是扩散模型训练的标准配置了。它的作用是让最终模型权重更接近训练过程中一段时间的“平均值”而不是最后一次迭代的瞬态值这样能显著提升生成稳定性。实现上就是每个 step 结束后把当前模型参数按照一个衰减系数合并进一份独立的 EMA 权重副本里推理时加载这份副本。衰减系数怎么选有讲究。常用值在 0.999 到 0.9999 之间数据集越大、训练步数越长衰减系数可以设得越大。衰减系数太大EMA 权重更新太慢训练早中期模型表现反而不如当前权重太小平滑效果不明显。我曾经在一个小数据集上把衰减设成 0.9999结果训练结束时的 EMA 模型相当于把最近两万步的变化几乎全平滑掉了生成的图模糊后来调到 0.999 才恢复正常。除了 EMA学习率的设置也值得单独说。扩散模型训练一步的计算开销大很多人喜欢用大批次、高学习率来加速但学习率一旦超过某个阈值loss 会在中后期开始周期性飙升生成结果出现一次性的大面积噪声伪影。我个人的经验是批次大小翻倍学习率不要直接翻倍按 1.5 倍左右手动调一调往往更好。2. 采样加速从几步走到一步的技术取舍扩散模型的生成过程一开始是纯马尔可夫链式的也就是说要从纯噪声出发按时间步一步步去噪步数通常在 1000 步左右。这在当时是可用的但完全不适合应用落地谁也不能忍受生成一张图等上几分钟。后来这个领域有一半以上的研究都在解决同一个问题能不能用更少的采样步数得到差不多的效果。2.1 DDIM——把采样问题变成确定性的路径求解DDIM去噪扩散隐式模型的洞见是扩散模型反向过程要学习的那个随机微分方程在给定起点和终点分布的前提下存在很多条不同的轨迹。原来的 DDPM 采样是走一条随机轨迹每一步都加入随机噪声而 DDIM 通过重写采样方程把随机项消掉让采样过程变成一个完全确定性的迭代过程。这样同样用 50 步DDIM 的质量已经远超同步数的 DDPM 采样甚至 20 步就能产生可以接受的结果。DDIM 还有一个特别实用的特性确定性采样意味着给定同一个初始噪声生成结果完全可复现。这不仅方便调试还让“在生成结果之间做插值”成为可能。想验证模型是不是真的学到了语义连续的特征空间只要对两个不同初始噪声各自采样到中间某个步数然后把中间结果按比例混合再继续采样就能看到生成图像在语义上平滑变化。这种操作用原来的随机采样根本无法稳定复现。提示DDIM 采样器里的“eta”参数控制采样时是否引入随机性。eta 设为 0 就是完全确定性适当增大 eta 可以提高多样性和局部细节但会增加不确定性。按我自己的使用习惯图像生成任务一般直接设 0图生图或者需要多样性的场景才调到 0.3 附近。2.2 高阶 ODE 求解器与蒸馏方案DDIM 本质上是一个一阶求解器从数学角度看它只是用最简单的欧拉法去解扩散反过程对应的常微分方程。既然是一阶的步长一变大截断误差就会快速累积。后来出现的 DPM-Solver 这类方案就是基于这个思路把扩散模型的采样方程改写成一种更便于高阶求解的形式然后用更高阶的方法去积分。在同样步数下高阶求解器能比 DDIM 得到更低的离散化误差这也就是为什么使用 DPM-Solver 等采样器在 15 到 25 步时就能得到相当好的效果。如果想把步数压到 10 步甚至更少就要用到蒸馏方法。常见的思路是先训练出一个 teacher 模型再用 teacher 指导一个 student 模型让 student 模型学会一步或多步直接生成与 teacher 长期采样结果一致的输出。实现上一般通过对抗损失或者感知损失来拉近两者的分布。蒸馏后的模型在 4 到 8 步内就能出图速度达到接近实时的水平。这里要提醒一句蒸馏不是免费的。它能让模型在低步数下表现得很好但通常会牺牲一定的多样性生成结果容易集中在训练数据的典型区域。如果你要的任务对多样性要求很高比如艺术风格探索类产品就要慎重评估蒸馏的收益是否抵得过多样性的损失。2.3 无分类器引导CFG的正确打开方式很多人第一次接触“Classifier-Free Guidance”时容易误解以为它需要一个额外的分类器。其实恰恰相反CFG 的做法是在训练时以一定概率把条件信息置空让模型同时学会带条件和无条件生成。推理时分别用条件输入和无条件输入得到两个预测结果关键的一步是计算两者的差值再乘以一个大于 1 的引导尺度加回到无条件预测上。用公式表达就是epsilon_final epsilon_uncond guidance_scale * (epsilon_cond - epsilon_uncond)这个公式的含义很清楚如果引导尺度为 1结果就是有条件生成如果大于 1就是夸大“条件带来的变化”让生成结果更贴近提示词。实际经验里引导尺度 2 到 8 之间能带来明显提升太大比如超过 15就会导致画面过饱和、对比度过高出现“烧图”现象。CFG 最调皮的坑在于它和采样器之间的交互很微妙同样的引导尺度、不同采样器最优值完全不同。我在实际项目中测过一个模型用 DDIM 采样的 CFG 最优值是 5换成 DPM-Solver 系列以后最优值变成了 3 左右而且 5 已经明显出现过曝。所以调参时不能只调一个维度采样器、采样步数、CFG 尺度三者要放在一起做小规模网格搜索。3. 可控生成条件信息怎么真正“指挥”扩散过程扩散模型能火出圈很大程度上是因为它可以被“指挥”着生成指定内容。文本生成图像的背后是模型学会了把文本语义和视觉结构对齐。但如果你仔细看生成过程会发现条件信息并不仅仅是“开门时看一眼”而是深度参与到了每一轮去噪迭代中。这节讲条件信息注入的方式和实战选型。3.1 分类器引导与无分类器引导的对比最早的可控生成确实是用额外分类器实现的。做法是训练一个分类器在采样过程的每一步计算梯度把梯度方向加到预测结果上让生成过程偏向分类器认为“更符合目标类别”的方向。这套方案是开创性的但工程化很累每做一次引导都要额外计算分类器的梯度采样速度明显减慢而且分类器的鲁棒性直接影响生成效果。CFG 方案放弃了显式分类器把条件信息直接作为模型输入的一部分通过训练时的随机置空策略让模型具备条件预测和无条件预测两种能力。推理时不需要额外计算梯度只需要多跑一次前向传播。这个改动打破了分类器引导的两大限制一是可以在海量图文对数据上训练而不用标注类别二是条件类型不再局限于有限的类别标签可以是任意语言的文本描述。所以现在的开源生态里CFG 已经成了默认标配。但它的代价是多了一次无条件的正向传播推理成本贵了一倍。这也是为什么很多加速采样器的研究都顺便在处理“如何减少 CFG 带来的额外开销”这个问题比如用一次联合网络同时输出条件和无条件预测把额外成本从一倍降到零。3.2 条件注入的位置与架构改造条件信息不能只是和输入做一个简单的相加然后不管了。要让文本语义真正控制生成需要在模型的多个尺度上注入信息。图像生成常用的基础网络结构是 U-Net它的设计是下采样提取语义再上采样恢复细节中间靠跳跃连接拼接纹理。文本信息通常会被送入中层的 attention 模块让每个位置的视觉特征根据文本内容来聚合全局信息。具体到实现就是交叉注意力机制把文本特征序列作为 key 和 value让图像特征作为 query 去查这样每个图像位置都能决定自己更看重文本中的哪些词。在早期的模型结构里这个交叉注意力只在分辨率最低的层出现过。随后的经验研究发现光在最深层注入是不够的最高分辨率的细节层也需要语义指导否则生成的物体形状对了但纹理乱来后续的结构改造就把注入位置往前移、分布到了更多尺度。在实现自己的条件注入结构时最值得注意的一点是不能盲目地在每层都加。层数加得越多可学习参数越多需要的数据量和训练稳定性成本也越高。平衡的做法是选择性地在低分辨率和中分辨率层注入语义条件高分辨率层只做细粒度纹理调整。我在自己的实验中发现中间和底层注入文本相关性最强的层往往在跳跃连接的拼接特征上做一两次 cross-attention 就足够了。3.3 细粒度空间控制文本的引导是全局性的但很多应用要求更精确的控制比如保持某人骨骼姿态不变或者只允许重绘图片中特定的区域。文本指令解决不了这个问题于是就有了更细粒度的空间条件控制。空间条件的做法是把姿态、边缘、深度图等结构化信息作为额外条件引入。具体实现层面一种常见做法是在 U-Net 的特征层级并行引入一个控制分支利用条件图与原始输入合成控制特征在每一层对主模型特征进行调整。这样做的核心优势是空间控制可以被作用在几乎所有语义层级上模型既理解全局语义、也遵循局部约束。实现时控制分支可以锁住主模型的全部参数只训练控制分支这样对显存的占用和训练数据量的要求要友好得多。我个人的体验是这类控制分支的收敛速度比想象中快。用一两千对条件图、目标图数据简单场景下训练几万步就能看到明显效果。真正让效果上高阶的往往是条件图的预处理一致性训练时如果用纯边缘图推理时输入的条件也要尽量纯净不然模型会对陌生输入比较茫然。4. 评估与调试量化指标的坑和训练失败的排查思路模型训练完之后怎么知道好坏很多初学者只看生成图“肉眼看着还行”但面对不同版本的模型很难科学地判断哪个更优。评估指标和调试经验是扩散模型开发中非常实在的部分。4.1 评估指标的使用场景与陷阱最常用的指标是 FIDFréchet Inception Distance它的核心思路是把真实图片集和生成图片集分别用某个视觉特征提取网络转换成高维特征然后计算这两个特征分布之间的 Wasserstein 距离。数值越低说明两个分布越接近生成质量越好。但 FID 有一个先天的偏向它对多样性更敏感一个模型哪怕每张图都画得一般只要风格多样FID 也可能比一个画得精致但风格单一的模型更好看。ISInception Score则是反过来它更偏向单张图的清晰度和分类置信度但也被诟病对模型内部特征分布过度敏感。实践中很多人两个一起看用 FID 判断整体分布对齐程度用 IS 或 CLIP Score 判断单个样本的语义一致性和清晰度。评估生成结果语义是否对齐提示词可以借助 CLIP 模型计算图文匹配度这种方式在文本生成图像领域已经成为标配之一。用这些指标时还要特别注意评估集的大小和构图多样性。FID 在样本量少于 5000 时极不稳定同一个模型多次抽样计算出的 FID 波动可能很大。所以在项目里做模型对比时尽量固定生成种子、固定评估集、固定采样器参数并多次评估取平均值否则结论的置信度也就只能停留在“参考”层面了。4.2 训练不收敛的常见信号扩散模型训练不收敛的现象不是 loss 不下降而是呈现各种“看似正常但实际已经坏了”的状态。最常见的一种是 loss 在某个区间震荡但生成的图片是一坨带噪声的色块混合体这往往意味着模型容量不足或者数据预处理去噪上出了问题。检查数据预处理时要特别注意图像是否被归一化到了正确的范围噪声调度使用的最大噪声水平是否和归一化范围匹配。另一种常见状态是生成图风格化过度即模型陷入某种局部最优生成的图片色彩高度统一、内容单一。这不是模型崩了而是多样性不足。缓解思路包括降低学习率、增大 EMA 衰减系数、增加随机噪声幅度甚至检查你的训练数据是不是本身多样性就不够。梯度异常也经常遇到。出现 NaN loss 或者在第一个 step 梯度值就超过正常范围的时候优先怀疑两点一是学习率设置太高二是混合精度下的损失计算方式不对。把这两处排查完绝大多数数值问题都能定位。4.3 实战调试笔记我的一次“炼丹”复盘前阵子我用一套公开的图像数据集训练一个条件生成模型效果一直差强人意生成图色彩对比度极高背景容易出现一团团奇怪的彩色伪影而前景主体又是糊的。当时第一反应是模型欠拟合于是加大训练步数、调整学习率但效果依旧。后来仔细检查了训练数据发现问题出在数据预处理上原始图像集里包含大量带水印的素材水印区域被模型当成了“高频有效特征”来学习生成时就会表现为局部斑块。我把有问题的数据清洗掉后问题立刻改善伪影大幅减少。这一次经历让我养成习惯训练效果异常的时候先怀疑数据再去调模型。这类经验还有很多。比如当采样步数很少时偶尔会在背景里出现大面积黑白噪点这通常是噪声调度表和高阶采样器不太兼容当我换成多步调度后问题自然消失。还有一个常见问题是某些采样器对 float16 中间张量敏感切换成 float32 后问题即可解除。记住一个顺序先查数据、再查调度、然后查采样器、最后再动模型结构大多数问题都轮不到最后一步。写在最后写到这里扩散模型从训练到采样、从指标评估到问题定位的主要脉络基本都串起来了。再次强调一下我自己的体会扩散模型是一个工程上细节远多于理论的项目理论和公式可以帮你定方向但真正跑通一个效果好且稳定的生成系统靠的是一轮轮实验迭代出来的经验细节。如果你正在尝试搭建自己的扩散模型项目别指望看一遍理论就能一次成功多动手跑几组对比实验把损失函数加权、调度表、采样器、EMA 这些变量分别做一轮小幅调整把效果的差异记录下来慢慢就会形成自己的判断比任何博客教程都有效。