
如果你在本地跑过 stable diffusion多半遇到过这种场景同一张图同一个提示词在不同人的电脑上出图速度能差出三到五倍。有人归因于显卡显存不够大有人归因于模型版本不够新真正动手一查才发现瓶颈往往藏在采样器配置、推理引擎选择、管线拆分方式这些不起眼的地方。这篇 Diffusion 推理总纲就是想把这条技术线完整捋一遍从扩散模型推理的底层逻辑到主流推理引擎的选型坐标系再到底层加速的核心矛盾最后落到我实际部署中踩过的坑。不管你是刚接触 diffusion model 的算法同学还是准备做 stable diffusion 本地部署、服务化推理的工程同学这份总纲都会先帮你把地图铺开后续系列再逐个点深入。很多人一开始把扩散模型当成普通生成网络处理不就是个 CNN 套个 Transformer 吗拖进 TensorRT 就完事了。结果第一次跑完整管线就傻了眼怎么一张图要好几秒模型转换以后各种报错换了个调度器结果图就完全变了这些问题背后其实指向同一件事——扩散模型的推理逻辑和传统单次前向的模型根本不一样。搞清楚这件事后面所有优化手段才有地方落脚。1. 为什么 Diffusion 推理值得单独拆出来讲1.1 一次前向和五十次前向的差别先看最根本的差异。传统图像模型比如 ResNet、YOLO、GAN 的生成器推理时都是一次前向传播输入进网络输出直接出来整个过程是一个确定性的映射。Diffusion 不是这样它的推理是一个迭代去噪循环。以 stable diffusion 1.5 为例默认用 DDIM 采样器、50 步去噪。每一步都要让 UNet 完成一次完整的前向计算也就是 50 步就是 50 次 UNet 推理。再加上文本编码器跑一遍、VAE 解码器跑一遍整个 text-to-image 管线才算走完。这里有个关键的数据认知UNet 的参数量大约在 1B 左右单步推理的 FLOPs 就已经上亿级。假设单步 UNet 前向耗时 80ms50 步就是 4 秒这还没算 VAE 解码和文本编码。也就是说扩散模型推理的总耗时约等于采样步数与单步前向耗时的乘积。步数这个变量一下就把工作量放大了一个数量级。这也是为什么加速扩散模型推理这件事从来不是单纯把模型转成 TensorRT 就能解决的。你优化的不只是一次前向多快还要考虑能不能用更少的步数达到同样的生成质量每一步能不能扛住更高的并发显存能不能装下更大的 batch。1.2 四层链路采样算法、执行引擎、精度策略、工程外围我自己做了几年推理优化越做越觉得Diffusion 推理不是一个单点技术而是一条四层链路。总纲篇最有价值的就是先把这条链路搭起来。第一层是采样算法。决定你跑 50 步还是 20 步用 DDIM 还是 DPM-Solver以及每一步的数学更新方式是怎样的。采样算法直接决定生成质量和步数之间的关系是天花板层面的东西。第二层是执行引擎。PyTorch Eager、ONNX Runtime、TensorRT、OpenVINO、MLX这些引擎负责把你的模型计算真正落到硬件上。引擎的性能差距可以在同样的模型权重下跑出完全不同的帧率。第三层是精度策略。FP16、BF16、INT8、INT4每一步去噪网络对精度误差的敏感度不同VAE 对溢出又特别敏感。精度设置不当轻则速度没提升多少重则直接出黑图噪点。第四层是工程外围。文本编码缓存、VAE 挪到 CPU、并发调度、连续批处理、显存池化管理——这些不直接改模型计算但决定了你的服务能不能撑住高并发能不能在边缘设备上把延迟压进可接受范围。后面系列要写的所有专题都会落在这四层里。你遇到的具体问题只要先定位它在哪一层解决方向基本就清楚了一半。2. 采样循环的底层逻辑为什么是一步一步去噪而不是一步到位2.1 正向加噪与反向去噪的直觉想理解推理还是得从扩散模型的训练逻辑往回看。扩散模型有两个过程正向过程和反向过程。正向过程是把一张干净图片用预定义的噪声表noise schedule逐步往里面加高斯噪声。加一步、加两步……加到几百步以后图片彻底变成一张纯噪声图跟原图一点关系都没有。这个步骤在训练时是固定的、确定性的。反向过程就是反过来从一个纯噪声出发训练一个神经网络去预测当前这一步加进去的噪声是什么。预测出来以后从当前噪声图中减去这个噪声得到稍微干净一点的图然后再预测下一步、再减。如此循环往复直到走完所有步数得到一张接近真实分布的图像。论文里的更新公式长这样x_{t-1} \frac{1}{\sqrt{\alpha_t}} \left( x_t - \frac{\beta_t}{\sqrt{1-\bar{\alpha}t}} \epsilon\theta(x_t, t) \right) \sigma_t z这里面 \epsilon_\theta 就是我们的神经网络通常是 UNetx_t 是当前带噪声的 latentz 是随机高斯噪声。我用一个生活化的类比这就像照片显影底片泡在显影液里影像不是瞬间出现的而是一点点浮现出来。每一步去噪都是在让轮廓清晰一点点。值得注意最后那项 \sigma_t z。它不是严格只去噪而是会加入一小部分随机噪声让结果不至于陷入确定性。这也是为什么同样的文字提示词、同样的模型权重只要换随机种子生成出来就是不同图像。这种带随机性的迭代过程是 Diffusion 推理独有的节奏。2.2 调度器与采样器的分工两个容易混淆的概念很多同学在代码里看到 scheduler、sampler 两个词就头大这俩确实容易混。我拆开说。**调度器scheduler**管理的是噪声表和时间步曲线。它决定总共多少步、每一步的噪声方差是多少、每一步网络应该处于哪个时间步 t。常用的 noise schedule 有 linear、scaled_linear、cosine 等。调度器更像一个曲谱规定了每一步该用多大力气去噪。**采样器sampler**则是在调度器给定步数的基础上用一种数学方法来逼近去噪过程。不同的采样器对如何从噪声预测结果更新 latent这件事有自己的迭代策略。拿我常用的几个举例DDIM把反向过程当作一个 ODE常微分方程来解能把原来需要 1000 步的马尔可夫链缩减到 2050 步质量损失相对可控。这是最经典、最稳妥的采样器之一。DPM-Solver / DPM-Solver这是高阶 ODE Solver利用扩散 ODE 的半线性结构用更少的步数逼近更好的结果。实际使用中 1525 步就能达到 DDIM 50 步的视觉质量速度优势非常明显。Euler / Heun结构更简单计算开销更小经常配合蒸馏类模型比如 LCM、SD-Turbo用来做 14 步的极速推理。同样一个模型权重用 DDIM 50 步和用 DPM 20 步生成的图可能在细节上略有差异但速度差出两倍以上。这就是采样算法的价值也是总纲里第一层要解决的核心问题在不牺牲质量的前提下尽可能减少步数。2.3 一张图三段式管线把 stable diffusion 的整条推理管线拆开其实是三段式的文本编码阶段文本提示词通过 CLIP或 SDXL 里的 CLIP T5编码成条件向量 encoder_hidden_states形状一般是 [batch, 77, 768] 或类似尺寸。采样循环阶段随机生成一个 [batch, 4, 64, 64] 的 latent对 512x512 输出而言反复调用 UNet 预测噪声更新 latent。解码阶段把去噪完成的 latent 交给 VAE Decoder上采样还原成真正的 512x512 像素图像。写成分步代码大概长这样import torch from diffusers import AutoencoderKL, UNet2DConditionModel # 初始化模型 vae AutoencoderKL.from_pretrained(stabilityai/sd-vae-ft-mse) unet UNet2DConditionModel.from_pretrained( stable-diffusion-v1-5/stable-diffusion-v1-5, subfolderunet ) # 采样循环简化示意 latents torch.randn((1, 4, 64, 64), generatorgenerator) for t in scheduler.timesteps: noise_pred unet(latents, t, encoder_hidden_statestext_embeds).sample latents scheduler.step(noise_pred, t, latents).prev_sample # 解码 image vae.decode(latents / vae.config.scaling_factor).sample我自己的经验数据在一张 NVIDIA 3090 上SD 1.5 用 DDIM 50 步跑 512x512 图整条管线大约 57 秒其中80% 以上的时间都花在 UNet 采样循环里文本编码通常只占 100200msVAE 解码 300500ms。这也解释了后面工程优化里的一个重要思路为什么服务化部署要把文本编码结果缓存起来为什么可以把 VAE 放到 CPU 上跑为什么采样循环才是 GPU 优化的主战场。三段式管线的每一段都有各自的优化空间但它们的重要性完全不一样。3. 性能瓶颈的三条主线计算、访存与顺序依赖3.1 计算主线UNet 每步都在做大规模卷积与注意力既然采样循环占了大头那 UNet 单步前向的计算密度就非常关键。UNet 的核心成分是什么是大量的卷积层加上跨层跳接以及中间若干层 Transformer 的 Attention 模块。随着 SDXL 这类模型引入更大的 DiT 结构Attention 的计算量占比越来越高。这里有一个量级概念分辨率每翻一倍UNet 单步的计算量大约翻四倍采样步数每翻一倍总计算量翻一倍。所以推理优化的第一反应永远是降低总计算量手段包括但不限于算子融合把卷积 激活 归一化熔成一个算子减少 kernel 启动开销使用 TensorRT / AITemplate 这类编译型引擎把图结构优化到极致降低精度FP16 相比 FP32 可以带来接近翻倍的算力吞吐。3.2 访存主线显存带宽往往被低估很多人只盯着算得快不快忽略了 Diffusion 推理其实也是一个访存密集型过程。UNet 的权重动辄 1B 参数每一步前向都要把所有权重从显存搬到计算单元中间特征图也要不断读写。当 batch 增大或分辨率提升时访存压力比计算压力涨得更猛。在 NVIDIA 显卡上这是 HBM 高带宽显存的价值所在到了核显iGPU上显存和系统内存共享带宽直接成为硬瓶颈这就是为什么核显跑 SD 必须优先量化或裁剪而不是上来就堆算力。访存优化的落点通常是用半精度或更低精度把权重尺寸减半等于把访存量减半用算子融合减少中间结果的反复搬运在 Apple Silicon 这类统一内存架构上充分利用 CPU 与 GPU 共享内存的特性减少数据拷贝。3.3 顺序依赖主线x_t 依赖 x_{t-1}没法并行这可能是 Diffusion 推理和大语言模型推理最不一样的地方。LLM 推理虽然也是自回归逐 token 生成但它有 KV Cache 可以缓存历史计算Diffusion 的每一步去噪都需要上一步的完整 latent 作为输入中间没有可以复用的历史缓存一条链直接串到底。这种顺序依赖带来了两个工程结论单样本的延迟优化只能靠降低单步耗时或减少总步数没法通过并行跑多个样本缩短要想提高吞吐就得往多实例并发和batch 内并行方向走。Batch 内并行指什么指的是在同一个采样循环里把多张图的 latent 拼成一个 batch 一起过 UNet。这样可以提高 GPU 利用率显存足够的情况下是很划算的吞吐优化手段。3.4 和 LLM 推理引擎的对照nano-vllm 给我们的启发这里想多说一句因为热词里有个基于 nano-vllm 学习大模型推理关键功能很多人会把 Diffusion 推理和 LLM 推理直接类比其实两者是同域不同路。LLM 推理核心是自回归 decode KV Cache 管理 连续批处理 投机采样。Diffusion 推理核心是迭代式去噪 采样步数控制 单步算子优化 批量并发。但两者共享很多底层基础设施显存池化、算子融合、低精度量化、动态形状处理、服务化调度。所以如果你正在学 nano-vllm 这类大模型推理引擎里面关于显存管理、调度队列、连续批处理的思路完全可以平移过来设计 Diffusion 的服务化层。只是落到模型执行层时Diffusion 不能用 KV Cache 那种思路必须围绕每步全量前向来做文章。这个对照能帮你省下很多走弯路的时间。4. 推理引擎选型坐标系别一上来就干 TensorRT4.1 主流引擎横向对比从平台到性能到集成成本选推理引擎核心看三件事你在什么硬件上跑、你对延迟还是吞吐更敏感、你的工程化预算有多少。我先给一张我自己用下来的横向对比表引擎适用平台优势劣势典型场景TensorRTNVIDIA GPU性能极致算子融合成熟闭源转换易踩坑插件问题多服务端高吞吐、单机单卡优化AITemplateNVIDIA GPU模板编译性能接近 TensorRT社区更新节奏不稳算子覆盖有限可接受编译时间、追求顶级性能ONNX RuntimeCPU / GPU / 核显跨平台跨框架集成速度快极端性能不如编译型引擎快速上线、跨平台适配OpenVINOIntel CPU/GPU/NPUIntel 平台优化充分NVIDIA 上优势不明显Intel 边缘设备、核显推理MLXApple SiliconM 系列芯片适配极好生态集中在 Apple 平台Mac 本地部署与开发调试我自己在服务端的常规建议是如果是 NVIDIA 独显且追求吞吐TensorRT 依然是绕不开的主力如果只是快速验证、或者要同时跑多个框架的模型ONNX Runtime 是更稳妥的起点。AITemplate 在 A100 上的单算子编译效果确实很强但你要做好跟踪它社区更新节奏的心理准备。4.2 核显/集成显卡的选型不要盲目上 TensorRT热词里有一条780m 核显推理用哪个推理工具最合适这个我专门展开说因为核显场景太容易选错工具了。AMD 780M 这类 RDNA3 核显、Intel Arc 核显跑 stable diffusion 能不能跑能。但很多人直接套用 NVIDIA 独显的经验装了 TensorRT 版结果要么构建失败要么推理速度惨不忍睹。原因很简单TensorRT 的 CUDA 生态并不适配核显架构它默认是给 NVIDIA 独显设计的。核显场景我建议两条路ONNX Runtime DirectML微软的 DirectML 后端在 Windows 上能统一调度核显AMD 和 Intel 都支持得不错集成成本低。OpenVINOIntel 平台尤其是 Arc 核显上用 OpenVINO 的 Stable Diffusion 优化非常成熟官方就出过完整的适配教程CPU 和核显都能吃到优化红利。另外核显天然共享系统内存显存带宽远不如独立显存所以量化优先级要排在最前面。先把 UNet 的权重从 FP16 减到 INT8访存压力降一半速度提升通常比换引擎还明显。4.3 从 nano-vllm 借鉴什么推理引擎的最小骨架如果你对推理引擎到底是怎么设计出来的感兴趣nano-vllm 是一个非常合适的学习样本。它代码精简但把大模型推理引擎的关键环节都保留了下来显存池化、请求调度、连续批处理。我自己当初啃它的时候最大的收获就是搞明白了一个道理——推理引擎的大部分复杂度并不在算子本身而在怎么把一堆请求高效地喂给 GPU。这个道理对 Diffusion 同样适用。当你把 diffusion 模型做成一个 Web 服务同时来 10 个用户请求每个请求都要完整的采样循环你不能简单粗暴地按顺序跑那样后面的请求会等死。正确的思路是借鉴 LLM 推理引擎的调度方式把多个请求拼成 batch 一起进采样循环或者对不同采样步数的请求做动态排布让 GPU 始终处于满载状态。Diffusion 的输入不像 LLM 那样是变长 token 序列latent 尺寸相对规整做 batch 调度反而更简单。这也是总纲把 nano-vllm 放进坐标系的原因它解决的问题和 Diffusion 服务化高度重叠。5. 我在实战中反复踩过的坑总纲篇先列一份清单5.1 动态形状导致的优化失效转换成功但不提速我在把 stable diffusion 转 ONNX 的早期做过一个非常蠢的事把输入尺寸设成动态。ONNX Runtime 里动态轴用起来很方便但当我再把模型转到 TensorRT 时动态形状直接让 TensorRT 的图优化退化成保守模式算子融合全部失效推理速度跟 PyTorch Eager 差不多。解决方案很直接固定分辨率或分档。要么固定 512x512要么做 512 和 768 两个档位分别构建引擎宁可多占一点显存也要换取 TensorRT 的激进优化。动态形状确实灵活但灵活性在推理引擎里往往是性能的敌人。5.2 FP16 下的 VAE NaNSDXL 用户的经典黑图SDXL 刚出来那阵我一直用 FP16 跑完整管线结果经常在 VAE decode 阶段出问题要么出图是一整块黑色要么出现大片噪点和色彩异常。排查了半天才发现VAE 在 FP16 下特别容易数值溢出latent 里的某些值稍微大一点decode 直接爆炸。解决方法按优先级排BF16 替换 FP16BF16 的动态范围和 FP32 基本一致不容易溢出只有 FP16 的硬件部分老卡就单独把 VAE 放在 FP32 上跑实在不行对 latent 做 clamp把数值范围限制在安全区间。这个问题说白了就是精度策略这层没做好。谨记UNet 用半精度没问题VAE 不见得撑得住半精度。5.3 调度器不匹配导致的假失败别急着骂引擎还有一次我把 PyTorch 模型切到 ONNX Runtime 之后发现生成结果和原来不一样第一反应是引擎转换有 bug。后来一步一步往前查才发现 ONNX 后端采样时用的是另一种调度器实现连随机噪声生成方式都不一样图自然就变了。这也是做推理迁移时最容易误判的点输出不一致不一定是引擎的问题可能是调度器实现的细节差异。所以做 AB 对比时务必固定随机种子、固定 scheduler、固定采样步数确保控制变量。如果还怀疑就导出中间每一步的 latent 做逐层 diff看到底从哪一步开始偏离。5.4 尺寸对齐与峰值显存边缘部署的隐藏门槛最后两个小坑一个关于尺寸一个关于显存。VAE 的 latent 下采样倍率是 8所以输入宽高必须是 8 的整数倍。很多人从网上随便拉一张图 resize 到 511x511VAE decode 直接报错本地可能没注意服务化以后就成了崩溃来源。我现在的习惯是任何进管线的图像先做 pad 到 8 的倍数再做后续处理。峰值显存方面如果 batch 开大导致 OOM不要急着换小 batch可以先试试把 VAE 挪到 CPU 推理或者把文本编码结果缓存起来用。VAE 解码只在最后跑一次放到 CPU 上慢那么几百毫秒但对显存峰值影响很明显整体吞吐反而可能更高。6. 总纲之后系列路线图总纲篇最后我按四层链路的框架把后续系列的路线画出来方便大家按需取用。先说采样算法这条线我会单独写一篇采样器专题重点拆 DDIM 和 DPM-Solver 的数学直觉与工程实现讲清楚为什么 DPM 用 20 步能顶 DDIM 50 步以及怎么在质量与速度之间找平衡点。这个专题适合所有想从会用进阶到懂调参的读者。第二条线是引擎部署专题以 TensorRT 为主完整走一遍从 PyTorch 模型导出、ONNX 转换、TensorRT builder 构建到插件踩坑的流程把动态形状、算子不支持、构建时间优化这些老问题一次说透。这条线适合服务端部署和技术负责人参考。第三条线是量化与服务化专题从 FP16 到 INT8、INT4 的逐层精度测试再到基于 nano-vllm 思路的连续批处理和显存池化设计。这条线偏向架构层面适合要把 Diffusion 服务正式推向生产的同学。你可以先对照自己当前卡在哪个环节再决定从哪篇切入。我自己当年就是吃了东一榔头西一棒子的亏一会儿调采样器一会儿换引擎最后发现每个方向都懂一点皮毛但哪条线都没打通。后来按这个四层框架把问题归档思路一下就清爽了。总纲篇先把这张地图放在这里后面的每一篇都是往这几个方向里填实料。