资讯动态

扩散模型中的伪随机流:可学习输入如何影响生成质量

发布时间:2026/8/30 16:27:45 来源:尧图企业网站定制
扩散模型的生成质量并不只由模型权重决定。同一个 checkpoint、同一个提示词只要随机种子不同生成结果就会完全不同。真正参与生成过程的除了文本编码器、UNet 和采样器还有一条容易被忽略的输入链路伪随机流。论文标题《Pseudorandom Streams within Diffusion Models Act as Learnable Inputs That Affect Generation Quality》点出了一个关键视角扩散模型里的伪随机流不应当只被当作采样时的“随机噪声”它可以被当作可学习输入来参与优化并且会直接影响生成质量。把这层关系理解清楚就能解释很多实际现象为什么同一个模型固定 seed 后结果稳定为什么不同 seed 的画质差异大为什么有些人会花大量时间“洗 seed”。下面从概念、原理、最小实验、参数调优、验证方法和排查路径几个部分展开。这里不要求读者已经读过原论文只要熟悉 PyTorch 和 diffusers 的基本用法就能按这套思路做自己的小实验。1. 先理解扩散模型里的伪随机流到底是什么1.1 生成过程中的随机性来自哪里扩散模型的采样过程并不是“模型以固定规则输出图像”。常规的 DDPM 采样过程是从一个标准正态分布张量出发经过一系列去噪步骤最终生成图像。在 PyTorch 或 diffusers 中这个初始张量通常由torch.randn生成import torch g torch.Generator(devicecpu) g.manual_seed(42) initial_latent torch.randn((1, 4, 64, 64), generatorg)这里的initial_latent不是唯一的。选择不同的 seed生成的张量不同选择相同 seed张量完全相同。这就是伪随机流的两个核心特征确定性和可复现性。它由伪随机数生成器产生而不是由物理过程产生因此在同一环境和同一算法下可以重新生成。“伪随机流”指的是这批服从标准正态分布的数字序列。它在扩散模型中的作用是给反向采样过程提供一个起点。后续每一步去噪实际上都在对这个起点进行修正和重排。1.2 随机流不是噪声计划也不是采样器很多人会把三个概念混淆随机流、噪声计划、采样器。噪声计划是scheduler里的alpha_t、beta_t、sigma_t等系数它决定每一步加入多少噪声、去噪时如何缩放。采样器是scheduler.step的去噪规则比如 DDIM、PNDM、Euler、DPM-Solver。随机流是实际采样出来的标准正态张量它是具体的一个样本而不是概率分布本身。可以这样理解概念解决什么问题典型对象噪声计划控制扩散过程加噪和去噪的强度beta_t、alpha_t采样器决定已知噪声预测结果后如何还原上一层DDIMScheduler、EulerDiscreteScheduler伪随机流提供采样的初始张量和随机扰动torch.randn生成的张量如果只看调度器参数无法知道最终生成的图像内容只有给每个位置填入具体的随机数采样过程才会得到一个确定的图像。1.3 种子、torch.randn 与伪随机流的关系在 diffusers 中固定生成结果的常用方式是from diffusers import StableDiffusionPipeline import torch pipe StableDiffusionPipeline.from_pretrained( runwayml/stable-diffusion-v1-5, torch_dtypetorch.float16, ).to(cuda) g torch.Generator(devicecuda) g.manual_seed(2024) image pipe( a red fox standing on snow, num_inference_steps30, guidance_scale7.5, generatorg, ).images[0] image.save(baseline_2024.png)这段代码里g.manual_seed(2024)决定伪随机流。相同的 seed、相同的模型、相同的调度器和相同的推理参数会得到完全相同的图像。这里的伪随机流是torch.randn在 PyTorch 底层 PRNG 算法驱动下产生的数字序列在 CUDA 设备上通常使用 Philox 算法以保证可复现性。如果愿意还可以在StableDiffusionPipeline的__call__里直接传入latents而不使用generatorlatents torch.randn((1, 4, 64, 64), generatorg, devicecuda, dtypetorch.float16) image pipe( a red fox standing on snow, num_inference_steps30, guidance_scale7.5, latentslatents, ).images[0]两种方式最终都产生一个形状为(1, 4, 64, 64)的潜在噪声张量。这个张量就是“伪随机流”在代码层面的实际载体。1.4 可学习输入与普通随机采样的区别普通采样中latents是采样得到的常量梯度不会流过它。模型推理完成后这个张量被丢弃。可学习输入的做法是把它定义成nn.Parameter或普通可导张量让某些损失函数的梯度能反传到它上面。这样做之后伪随机流变成了优化变量。训练或测试时优化的不是 UNet 权重也不是文本编码器而是“从哪个噪声点开始采样”。论文标题里强调的“Learnable Inputs”指的就是这层关系输入侧的噪声流本身可以参与梯度更新并通过反向传播去影响最终生成质量。这里有一个容易误解的地方经过优化后的张量已经不是严格意义上的“随机流”了它是一个由伪随机流初始化、再经过梯度更新得到的输入张量。但在很多实现中仍然把它称为噪声流或 latent 输入。理解这一点后面看代码时就不会对“为什么正则化损失需要约束分布”感到困惑。2. 为什么伪随机流会影响生成质量从分布到样本2.1 训练目标站在分布视角生成阶段落在样本视角扩散模型的训练目标是让去噪网络学会“给定任意一步的噪声图还原真实图像”的分布映射。训练时网络见过无数个随机噪声样本理论上一个训练充分的模型应该对任意合法噪声样本都有不错的处理能力。但实际 inference 时每次只有一个噪声张量真正进入网络。这个具体样本落在概率分布的高概率区域还是低概率区域会直接影响生成结果。不同 seed 下的图像构图、颜色、物体位置都可能不同甚至会出现人体畸形、物体混叠、结构失效等问题。这类现象说明模型虽然整体上学会了分布但对个别噪声样本的生成质量是波动的。伪随机流之所以会影响质量本质上是因为图像生成是从一个具体样本映射到另一个具体样本而不是从分布平均到另一个平均。分布训练和样本推理之间存在一条窄缝网络能容忍大多数噪声却无法保证所有噪声都好。2.2 生成质量波动来自噪声流与文本条件的匹配程度同一个提示词对应很多合理的图像。比如“一只红狐狸站在雪地上”可以是近景特写可以是远景动物也可以是有大片留白的场景。这些不同结果对应不同的噪声流。文本条件通过 cross-attention 参与生成但文本无法把每个空间位置的内容都定位死剩下的自由度由噪声流决定。因此生成质量可以看作一个关于“文本条件 噪声流”的组合函数。文本条件固定时噪声流决定了最终图像在纹理、构图、色彩分布上的走向。如果某条噪声流和当前 prompt 的语义空间匹配度低模型就需要更多去噪步数来弥补步骤不够时容易生成模糊、失真或语义错乱的结果。所以伪随机流不只是“随机性来源”它隐式携带了一部分采样路径的“意图”。把它视为可学习输入其实是把这个隐式自由度显式化。2.3 可学习伪随机流的两个常见实现思路从工程角度看把伪随机流变成可学习输入通常有两种路线。第一种是测试时优化。保持模型权重不变把初始噪声流定义为可导参数用某个损失函数反向传播更新它。损失可以是图像重建误差、CLIP 对齐程度、结构相似度也可以是多目标组合。这种路线适合离线优化单个或一批图片代价是每次生成都要迭代几十到上百步。第二种是元学习或条件生成。训练一个小网络让它根据 prompt、风格标签或参考图直接输出一个合适的噪声流。推理阶段不需要在线优化因为条件网络已经学会了如何为不同输入分配噪声流。这个路线更接近“输入侧先验学习”但需要额外的训练数据和训练过程。论文标题里强调的是“Act as Learnable Inputs”这说明核心不是训练 UNet而是把噪声流纳入可学习输入集合。两种路线都符合这个思想区别只是可学习参数放在推理期优化还是放在训练期生成。2.4 它与 ControlNet、LoRA 的区别ControlNet 通过额外分支引入边缘、深度、姿态等条件改变的是 UNet 的特征融合方式。LoRA 在 attention 层加入低秩矩阵改变的是模型权重。可学习伪随机流不修改权重也不引入新条件分支它只改变推理时的初始噪声点。因此可学习噪声流和 ControlNet、LoRA 不是替代关系而是互补关系。模型可以做 LoRA 微调也可以接 ControlNet 控制结构同时还可以在推理时优化噪声流让最终采样路径更接近预期。这也意味着如果项目里已经对模型做了大量微调仍然可以通过这个思路做“输入侧”的微调而不必重新训练整个模型。3. 最小实验把噪声张量变成可学习参数3.1 环境与依赖版本做这类实验建议准备一张显存不低于 12 GB 的 NVIDIA GPU。显存不足时可以把采样步数调低或使用 CPU 推理但优化速度会明显下降。组件作用示例版本Python运行环境3.10PyTorch张量与自动微分2.1.xdiffusers扩散模型 pipeline 与调度器0.24.xtransformers文本编码器4.35.xaccelerate设备调度工具0.26.xCUDAGPU 运算环境11.8 或更高版本只作为示例。不同模型仓库对 transformers 和 diffusers 的版本要求不同落地前先按实际环境确认版本不要直接照搬。3.2 使用固定种子生成一张基线图先固定 seed 生成一张基线图记录当时的噪声流。只有固定好基线后续才能确认优化是否真的让生成结果发生变化。import torch from diffusers import StableDiffusionPipeline model_id runwayml/stable-diffusion-v1-5 pipe StableDiffusionPipeline.from_pretrained( model_id, torch_dtypetorch.float16, ).to(cuda) g torch.Generator(devicecpu) g.manual_seed(2024) noise_stream torch.randn((1, 4, 64, 64), generatorg, devicecuda, dtypetorch.float16) image pipe( a red fox standing on snow, num_inference_steps20, guidance_scale7.5, latentsnoise_stream, ).images[0] image.save(baseline.png)代码中latents就是一条伪随机流。之后把它封装成可学习参数时初始值应该和这里保持一致。3.3 把初始噪声流封装成 nn.Parameter为了让梯度能流回噪声流需要用nn.Parameter包装import torch import torch.nn as nn class LearnableNoiseStream(nn.Module): def __init__(self, shape, seed, devicecuda, dtypetorch.float16): super().__init__() gen torch.Generator(devicecpu) gen.manual_seed(seed) init_noise torch.randn(shape, generatorgen, dtypetorch.float32) self.noise nn.Parameter(init_noise.to(devicedevice, dtypedtype)) def forward(self): return self.noise这里初始值由指定的 seed 生成保证和基线图在同一起点出发。nn.Parameter会把该张量注册为可训练参数优化器更新时只会改变这个张量的值不会改变 UNet 的权重。3.4 一个面向测试时间优化的最小循环实际优化时不要直接调用pipe(...)因为 diffusers 的许多 pipeline 内部会进入no_grad或inference_mode导致梯度无法传回latents。更稳妥的方式是拆开采样循环手动调用 UNet 和 scheduler。下面是一个示意实现import torch import torch.nn.functional as F from torch.optim import Adam def differentiable_generate(pipe, prompt_embeds, latents, num_inference_steps10): scheduler pipe.scheduler scheduler.set_timesteps(num_inference_steps) x latents * scheduler.init_noise_sigma for t in scheduler.timesteps: noise_pred pipe.unet( x, t, encoder_hidden_statesprompt_embeds, ).sample x scheduler.step(noise_pred, t, x).prev_sample decoded pipe.vae.decode(x / pipe.vae.config.scaling_factor).sample return decoded stream_model LearnableNoiseStream((1, 4, 64, 64), seed2024).to(cuda) prompt_embeds pipe._encode_prompt( a red fox standing on snow, devicecuda, num_images_per_prompt1, do_classifier_free_guidanceTrue, negative_prompt, ) optimizer Adam(stream_model.parameters(), lr1e-2) for step in range(20): optimizer.zero_grad() generated differentiable_generate( pipe, prompt_embeds, stream_model(), num_inference_steps10, ) # 示范损失让生成图接近一张参考图 target load_reference_tensor(target.png).to(generated.device) loss F.mse_loss(generated, target) # 约束噪声流不要偏离标准正态分布太远 reg_loss (stream_model().pow(2).mean() - 1.0).abs() total_loss loss 0.05 * reg_loss total_loss.backward() torch.nn.utils.clip_grad_norm_(stream_model.parameters(), 1.0) optimizer.step() torch.save(stream_model.state_dict(), optimized_noise_stream.pt)这段代码是思路演示不是某个 diffusers 版本的官方 API 完整实现。_encode_prompt的位置、scheduler.step的返回结构、scaling_factor的属性名在不同版本里可能不同。正式复现时建议以当前环境的源码为准把StableDiffusionPipeline.__call__里的关键步骤复制出来再删掉no_grad。光能跑通不算完成。这个实验的核心观察点有三个优化后生成的图是否偏离基线stream_model.noise的数值分布是否还在标准正态附近把优化后的噪声流保存下来后再用它生成同 prompt 图片结果是否稳定。4. 关键参数和优化策略为什么学习率不能照搬4.1 噪声形状、设备与精度不同扩散模型的 latent 形状不同。Stable Diffusion 1.5 的 VAE 下采样倍率是 8所以 512x512 图像对应 latent 尺寸 64x64通道数为 4即(1, 4, 64, 64)。SDXL 的 latent 形状不是这个值如果直接套用会报错或导致生成异常。精度建议先用 float16 尝试显存不足时考虑 gradient checkpointing 或减少采样步数。但要特别小心float16 的数值精度会放大噪声流更新的不稳定性学习率过高时很容易出现 NaN。否则就使用 float32 做优化虽然慢但排查问题更容易。4.2 学习率、迭代次数与损失函数学习率是可学习噪声流实验中最敏感的参数。噪声流是输入层的参数更新它对输出图的影响是全局性的。学习率太大几步之后图像结构就崩了学习率太小几十步迭代后图像没什么变化。参数建议范围设置偏大的表现设置偏小的表现学习率1e-4 到 1e-2图像失真、NaN、颜色溢出优化缓慢图像变化小迭代次数20 到 200可能过拟合参考图欠拟合质量提升不足正则化权重0.01 到 0.1噪声被压得太死失去多样性噪声分布漂移生成异常采样步数8 到 30内存占用大反向传播慢生成结果粗糙梯度信号噪声大梯度裁剪0.5 到 1.0更新受限收敛慢更新过度优化不稳定损失函数不能只用一个 MSE。如果目标是让生成图接近参考图光靠像素损失会让图像往模糊方向走。可以组合使用感知损失、LPIPS 或 CLIP 对齐损失。正式实验时建议至少用一个语义层损失而不是只比较原始像素。4.3 优化器与梯度裁剪Adam 比 SGD 更适合优化噪声流因为 Adam 对每个参数的自适应学习率能缓解噪声流不同通道尺度不一致的问题。优化器只应该包含噪声流参数不要包含 UNet 或 VAE 参数否则会连带更新模型权重破坏原模型能力。每一步反向传播后使用clip_grad_norm_是性价比很高的保护措施。扩散模型采样过程的计算图很深即使只有 10 步梯度也可能在长链路上产生尖峰。裁剪不能保证找到更好的解但能显著降低训练过程中图像突然崩坏的概率。4.4 伪随机流约束分布边界和更新策略优化过程中nn.Parameter的取值会逐渐偏离标准正态分布。这会导致采样时输入分布和模型训练时的输入分布不一致生成质量反而下降。常见处理方式有三种加正则化项让噪声流的均值接近 0、方差接近 1。使用重参数化把参数表示成均值和 log 方差再通过mean std * eps生成噪声流。分层更新只允许噪声流在某个范数范围内移动超出范围就投影回合法区间。正则化系数不要设太大。实验目标不是让噪声流重新变成严格随机变量而是让它成为一个“仍然位于合法分布附近、但更有利于当前任务”的输入。5. 怎么验证生成质量真的被改善了5.1 主观视觉对比最简单的验证方式是把基线图、可学习噪声流优化后的图放在同一张画布里对比。观察点包括主体结构是否更完整、边缘是否更平滑、细节是否更丰富、颜色是否更协调。主观对比时要注意控制变量。prompt、模型、采样器、采样步数、guidance_scale 都必须保持一致只允许噪声流不同。如果这些条件发生了变化就无法把质量变化归因于可学习噪声流。5.2 指标评估FID、CLIP Score、LPIPS视觉对比之外还需要量化指标。CLIP Score 衡量图文匹配程度适合判断“生成结果是否更符合文本语义”。LPIPS 衡量两图之间的感知距离适合评估结构和纹理差异。FID 需要一批真实图像和一批生成图像适合评估整体分布质量但不适合单个样本的优化效果评估。指标适合场景解读方式CLIP Score图文对齐越高表示图像和 prompt 语义越接近LPIPS感知差异越低表示和参考图越相似FID批量生成分布越低表示整体分布越接近真实分布SSIM / PSNR像素级重建适合重建任务不适合纯生成任务单个样本上 FID 没有意义它需要一组生成图像。因此如果论文里强调“生成质量”通常不会只靠一两张图下结论而是统计多个 prompt、多个 seed 下的平均表现。5.3 可复现性验证相同流、相同结果可学习噪声流的核心优势之一是确定性和可复现性。优化完成后把stream_model.state_dict()保存为.pt文件。下一次加载这个文件用同一个采样器、同一组采样步数生成应该得到和优化结束时完全相同的图像。如果两次推理结果不一致问题通常不在噪声流本身而在采样器状态、随机数生成器或 CUDA 非确定性算子。应该检查 scheduler 是否重置了timesteps检查是否在 pipeline 外部传入了新的generator检查是否有 dropout 层在前向时生效。5.4 一个可复用的验证清单每次做可学习噪声流实验建议按下面清单核对模型 checkpoint 是否固定是否有 ema 权重。prompt 和 negative prompt 是否完全一致。采样器类型、采样步数、guidance_scale 是否一致。初始噪声流是否由固定 seed 生成。优化目标是否明确是图文对齐、重建还是结构保持。是否保存了优化前和优化后的噪声流。是否记录了 loss 曲线。是否用多个 prompt 验证泛化而不是只测一个 prompt。是否检查了优化后噪声流的均值和方差。是否在同一环境下重复生成两次。这个清单同样适用于生产中排查“为什么生成结果不稳定”的问题。6. 常见问题与排查路径6.1 梯度没有传回噪声流常见现象是 loss 始终不下降或者修改noise参数后图像没有任何变化。最常见原因是 pipeline 内部使用了torch.no_grad()或torch.inference_mode()。在使用 diffusers 时不要直接对pipe(...)的输出做 backward而要拆开 UNet 和 scheduler 的自定义循环确保前向过程处于梯度开启状态。检查方式是在loss.backward()之后打印print(stream_model.noise.grad)如果输出为None说明从 loss 到noise的路径断裂了。先检查是否用了no_grad再检查是否在generate函数里对latents做了原地操作或detach。6.2 优化后图像发灰或出现大面积噪声如果 loss 在下降但生成图像越来越模糊、结构越来越乱通常是优化后的噪声流已经偏离模型训练的输入分布。可以把优化后的噪声流打印出来noise stream_model.noise.detach().float() print(mean:, noise.mean().item()) print(std:, noise.std().item())如果 std 明显大于 1.5 或明显小于 0.5就需要加强正则化或者改用重参数化表示。另一种处理是降低学习率并增加迭代次数让更新更平滑。6.3 优化过拟合到单个参考图如果优化后的噪声流只在当前 prompt 上有效换一个 prompt 后质量明显下降说明噪声流过拟合了当前文本条件。处理思路是扩展优化目标。可以让同一个噪声流同时优化多个 prompt 的图像或者把 prompt embedding 换成混合样本迫使噪声流学习一个更通用的起点。测试时优化天然偏向单样本因此这类方法更适合局部调整而不是面向所有 prompt 的通用优化。6.4 显存不足或反向传播太慢通过整个扩散过程反向传播非常消耗显存。10 步采样加上 UNet 的中间激活已经比普通推理占用高很多倍。可以采取以下措施减少采样步数比如从 30 步降到 8 到 12 步。使用torch.utils.checkpoint对 UNet 做梯度检查点。只优化最后若干步的噪声流前面步骤使用固定随机噪声。使用更小的模型比如 SD-Turbo 或 LCM或者降低图像分辨率。如果显存仍不足考虑把优化过程变成离线搜索用多次普通生成记录结果再用代理奖励函数筛选。问题现象可能原因检查方式处理建议loss 不下降pipeline 内部 no_grad打印noise.grad拆开采样循环移除 no_grad图像发灰噪声流分布漂移检查 mean/std加正则化或重参数化换 prompt 不适用过拟合单个条件多 prompt 联合优化扩展优化目标显存不足长计算图查看 CUDA memory减少步数、梯度检查点结果不可复现随机算子或未保存噪声流重复生成对比固定 seed保存.pt文件7. 工程化建议与扩展方向7.1 学习环境与生产环境的区别在本地实验时可以对每个噪声流做 100 步测试时优化效果好坏无所谓。但生产环境有延迟、成本和稳定性要求不能每次请求都跑一次反向传播。更合理的方式是离线优化一批“优质噪声流”把它们和 prompt、采样参数一起缓存起来。线上推理时只做前向采样不更新参数。这样既能获得可学习噪声流的质量收益又避免在线优化的时间开销。生产环境还需要额外的降级方案。如果某个噪声流在特定模型版本上生成异常应该能自动回退到固定 seed 的默认噪声流而不是让用户看到一张崩坏的图。7.2 更适合落地的组合路线从工程投入产出比看最值得尝试的不是“从零训练一个噪声流生成网络”而是先用测试时优化验证质量上限再考虑是否要训练一个轻量条件网络。可以分三步走在 3 到 5 个固定 prompt 上做测试时优化保存优化后的噪声流。记录优化前后在 CLIP Score、LPIPS 上的差异判断收益是否明显。如果收益明显用这批优化后的噪声流作为监督数据训练一个以 prompt embedding 为输入的轻量 MLP 或小型 UNet让它推理时直接输出噪声流。这种组合方式把推理成本和生成质量分开考虑适合实际项目稳定落地。7.3 扩展方向面向主题的噪声先验与跨提示复用可学习噪声流不只作用于单张图。它也可以按主题聚簇比如为“人物特写”“产品白底图”“建筑夜景”这类固定场景各学习一组噪声先验。采样时先判断当前 prompt 的场景再加载对应的噪声流相当于在模型权重之外建立了一个“输入侧场景库”。跨提示复用是另一个值得探索的方向。两个语义相近的 prompt 可以共享部分噪声流实现构图和光影的迁移。这类思路在图像编辑、风格迁移和视频生成中尤其有价值。7.4 什么时候不值得用可学习噪声流不是所有项目都需要优化噪声流。如果模型本身的生成质量已经能满足要求且用户不要求固定构图或稳定风格直接用随机 seed 反而更好成本和复杂度都更低。可学习噪声流的优势集中在需要稳定性、可复现性、可控构图或多次生成选优的场景。如果项目主要问题在 prompt 语义理解、模型过拟合或数据集分布不匹配那么优化噪声流只是治标。先修正训练数据、模型微调方式和采样器配置往往比在输入侧找噪声更有效。实际做扩散模型生成质量优化时建议把伪随机流当作一种可被观察和记录的输入资产而不是“随机产生的临时数据”。保存 seed、保存噪声流、记录采样参数这一步成本极低却能给后续排错、优化和复现带来极大便利。很多看似玄学的 seed 问题本质上只是伪随机流的性质没有被充分利用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价