潜在扩散与Stable-Diffusion


文档摘要

潜在扩散与Stable-Diffusion 本节摘要:在 512×512 像素空间跑扩散,是「计算上的战争罪行」。Rombach 等(2022)注意到——生成一张图不需要全部 786k 维,只需要够捕捉语义结构的维度,其余的交给一个独立解码器。把扩散搬进 VAE 的潜空间里跑,这一个想法就是 Stable Diffusion。本节讲透两阶段架构:第一阶段的 VAE 把 压到 (FLOPs 砍 64 倍),第二阶段的 U-Net/DiT 在潜空间里去噪;文本如何通过交叉注意力注入每个块( );以及无分类器引导(CFG)如何用 放大条件信号。我们会梳理 SD1.

潜在扩散与Stable-Diffusion

本节摘要:在 512×512 像素空间跑扩散,是「计算上的战争罪行」。Rombach 等(2022)注意到——生成一张图不需要全部 786k 维,只需要够捕捉语义结构的维度,其余的交给一个独立解码器。把扩散搬进 VAE 的潜空间里跑,这一个想法就是 Stable Diffusion。本节讲透两阶段架构:第一阶段的 VAE 把 512×512×3 压到 64×64×4(FLOPs 砍 ~64 倍),第二阶段的 U-Net/DiT 在潜空间里去噪;文本如何通过交叉注意力注入每个块(Q=图像特征,K=V=文本 token);以及无分类器引导(CFG)如何用 (1+w)·ε_cond − w·ε_uncond 放大条件信号。我们会梳理 SD1.5→SDXL→SD3→Flux 的演化(U-Net→DiT、CLIP→T5、4 通道→16 通道),并给出在 8GB 消费级 GPU 上跑 12B Flux 的实战配方。

对应原课程:Phase 08 · Lesson 07 · latent-diffusion-stable-diffusion(原英文 phases/08-generative-ai/07-latent-diffusion-stable-diffusion/docs/en.md)。

学习目标

阅读完本节,你应当能够:

  1. 说清像素空间扩散为何不可行:512²×3 张量让 500M U-Net 单步约 100 GFLOPS,五十步一张图,十亿张训练图的算力账单荒唐。
  2. 解释两阶段架构:第一阶段 VAE(压缩 + 感知损失 + 小权重 KL),第二阶段扩散(把 z = E(x) 当数据,损失与第 06 节完全相同)。
  3. 推导文本条件的注入:冻结文本编码器 + 每个 U-Net 块的交叉注意力 Q=图像,K=V=文本,这是 Stable Diffusion 与类别条件扩散的唯一实质区别。
  4. 无分类器引导(CFG) (1+w)·ε_cond − w·ε_uncond 控制条件强度,并说出 w ≈ 3-7 的甜点区。
  5. 梳理 SD1.5→SDXL→SD3→Flux 的演化,以及在 8GB 消费级 GPU 上跑 12B Flux 的三旋钮配方(分阶段加载 + 4-bit 量化 + CPU 卸载)。

一、问题与直觉

像素空间扩散在 512² 上跑,意味着 U-Net 跑在形状 [B, 3, 512, 512] 的张量上。对 500M 参数的 U-Net,每个采样步约 100 GFLOPS;五十步就是每张图 5 TFLOPS。在十亿张图上训练,算力账单荒唐。

而这些 FLOPs 里,大部分用在把感知上不重要的细节(有损 VAE 本可压掉的高频纹理)推过网络。Rombach 的主意:训一次 VAE(第一阶段),冻结它,把扩散整个跑在 4 通道 64×64 的潜空间(第二阶段)。同一个 U-Net,像素只有 1/16,FLOPs 砍 ~64 倍,质量相当。

这就是 Stable Diffusion 配方。SD 1.x/2.x 用 860M U-Net 跑 64×64×4;SDXL 用 2.6B U-Net 跑 128×128×4;SD3 把 U-Net 换成带流匹配的扩散 Transformer(DiT);Flux.1-dev(Black Forest Labs,2024)交付 12B 参数的 DiT-MMDiT。它们都跑在同一个两阶段底料上。

两阶段,各自训练

第一阶段——VAE。 编码器 E(x) → z、解码器 D(z) → x。目标压缩:每条空间轴 8 倍下采样 + 调整通道,使总潜变量大小约为像素数的 1/16。损失 = 重建(L1 + LPIPS 感知)+ KL(权重小,因为我们不需要从 z 精确采样,所以不必把 z 强行推向高斯)。常配一个对抗损失,让解码出的图像锐利。

第二阶段——在 z 上扩散。z = E(x_real) 当作数据。训一个 U-Net(或 DiT)去去噪 z_t。推理时:用扩散采样出 z_0,再 x = D(z_0)

文本条件:两个额外组件

  1. 冻结的文本编码器:SD 1.x 用 CLIP-L;SD 2/XL 用 CLIP-L+OpenCLIP-G;SD3 和 Flux 用 T5-XXL。
  2. 交叉注意力注入:每个 U-Net 块取 [Q = 图像特征, K = V = 文本 token] 做混合。token 是文本影响图像的唯一通道。

💡 关键洞察:第二阶段的损失函数与第 06 节完全相同——同一个 DDPM/流匹配的噪声 MSE。你只是换了数据域(从像素到潜变量)。这意味着潜在扩散没有引入新的损失数学,它是一个工程上的压缩技巧——但这一个技巧让扩散从「只有大厂能跑」变成「消费级 GPU 可跑」,是整个文生图民主化的转折点。

二、架构变体:从 SD1.5 到 Flux

模型 年份 骨干 潜变量形状 文本编码器 参数
SD 1.5 2022 U-Net 64×64×4 CLIP-L(77 token) 860M
SD 2.1 2022 U-Net 64×64×4 OpenCLIP-H 865M
SDXL 2023 U-Net + refiner 128×128×4 CLIP-L + OpenCLIP-G 2.6B + 6.6B
SDXL-Turbo 2023 蒸馏 128×128×4 同上 1-4 步采样
SD3 2024 MMDiT(多模态 DiT) 128×128×16 T5-XXL + CLIP-L + CLIP-G 2B / 8B
Flux.1-dev 2024 MMDiT 128×128×16 T5-XXL + CLIP-L 12B
Flux.1-schnell 2024 MMDiT 蒸馏 128×128×16 T5-XXL + CLIP-L 12B,1-4 步

趋势:用 DiT(在潜变量 patch 上的 Transformer)替换 U-Net;扩大文本编码器(T5 在提示遵从度上胜过 CLIP);增加潜变量通道(4→16 给更多细节余量)。

为什么 DiT 取代 U-Net?

U-Net 的归纳偏置(卷积 + 跳跃连接)在小数据上有用,但在大数据 + 大算力下,Transformer 的扩展律更平滑。Peebles & Xie(2023)的 DiT 证明:把扩散骨干从 U-Net 换成「在潜变量 patch 上的 Transformer」,在 ImageNet 类条件生成上随参数与算力平滑提升。SD3 和 Flux 都走这条路。SD3 的 MMDiT 进一步让文本流与图像流共享注意力(「联合注意力」),让两种模态深度交互——这是它在提示遵从度上胜过 SDXL 的架构原因。

三、从零实现:一维潜在扩散 + CFG

code/main.py 在第 06 节的 DDPM 上叠一个玩具一维「VAE」(恒等编码器 + 解码器,用于演示;真实 VAE 是卷积网络),再加带 CFG 的类别条件。它展示:同一个扩散损失在原始一维值或编码值上都工作——这是核心洞察。

步骤 1:编码器/解码器

def encode(x): return x * 0.5 # 玩具「压缩」到更小尺度 def decode(z): return z * 2.0

真实 VAE 有训练好的权重。教学上,这个线性映射足以展示扩散在 z 上操作、不关心原始数据空间。

步骤 2:在 z 空间扩散

与第 06 节相同的 DDPM。网络看到的数据是 z = E(x)。采样出 z_0 后,用 D(z_0) 解码。

步骤 3:无分类器引导(CFG)

训练时,10% 概率丢弃类别标签(替换为 null token)。推理时,同时算 ε_condε_uncond,然后:

eps_cfg = (1 + w) * eps_cond - w * eps_uncond

w = 0 = 无引导(满多样性),w = 3 = 默认,w = 7+ = 饱和 / 过锐。

💡 CFG 的直觉:把条件预测远离无条件预测的方向,等于「朝条件方向外推」。w 越大,外推越远,条件信号越强——但代价是多样性下降,且 w > 10 会出现「油画感」过饱和。数学上,CFG 等价于在修改后的分数场上采样:score_cfg = score_uncond + (1+w)·(score_cond − score_uncond)

步骤 4:文本条件(概念,非代码)

把类别标签替换为冻结文本编码器的输出,通过交叉注意力喂给 U-Net:

h = h + CrossAttention(Q=h, K=text_embed, V=text_embed)

这是类别条件扩散与 Stable Diffusion 的唯一实质区别

四、典型陷阱与修复

  • VAE 尺度不匹配:SD 1.x VAE 编码后有一个缩放常数(scaling_factor ≈ 0.18215)。忘了它,U-Net 会在方差严重错误的潜变量上训练。每个 checkpoint 都附带一个。

  • 文本编码器悄悄出错:SD3 需要带 >=128 token 的 T5-XXL,退化到只用 CLIP 是有损的。永远检查 use_t5=True,否则提示保真度塌陷。

  • 混用潜空间:SDXL、SD3、Flux 用不同的 VAE。在 SDXL 潜变量上训的 LoRA 在 SD3 上不工作。Hugging Face diffusers 0.30+ 会拒绝加载不匹配的 checkpoint。

  • CFG 过高:w > 10 产出饱和、油腻的图像,过拟合提示而牺牲多样性。甜点是 w = 3-7

  • 负向提示泄漏:空负向提示变成 null token;有内容的负向提示变成 ε_uncond两者不一样;有些流水线悄悄默认用 null。

⚠️ VAE 缩放因子的来历:扩散在「单位方差空间」上训练最稳。但 SD 的 VAE 编码出的潜变量方差不是 1,而是约 0.18215^{-2} ≈ 30。所以编码后乘 scaling_factor ≈ 0.18215,把方差拉回 1,再喂给 U-Net。解码时除回去。这个「魔数」是 VAE 训练后统计出来的,每个 VAE 不同——混用 VAE 而忘了换 scaling_factor,训练立刻崩。

五、框架对比:2026 年生产栈

目标 推荐骨干
窄领域、有配对数据、从零训 SDXL 微调(LoRA / 全量)——最快交付
开放领域文生图、开源权重 Flux.1-dev(12B,Apache / 非商用)或 SD3.5-Large
最快推理、开源权重 Flux.1-schnell(1-4 步,Apache)或 SDXL-Lightning
最佳提示遵从度、托管 GPT-Image / DALL-E 3、Midjourney v7、Imagen 4
编辑工作流 Flux.1-Kontex(2024.12)——原生接受图像 + 文本
研究、基线 SD 1.5——古老但研究透彻

六、可复用产物

本节产出一个提示技能文件(位于原课程 outputs/skill-sd-prompter.md)。

  • skill-sd-prompter.md:输入「文本提示 + 目标风格」,输出:模型 + checkpoint、CFG 比例、采样器、负向提示、分辨率、可选的 ControlNet/IP-Adapter 组合、逐步 QA 清单。

七、生产推理:在 8GB 消费级 GPU 上跑 12B Flux

参考 Flux 集成是「我只有消费级 GPU,能交付吗」的标准配方。技巧就是生产推理文献列出的、应用到扩散 DiT 上的同一个三旋钮配方:

  1. 分阶段加载:Flux 有三个永不需要同时驻留 VRAM 的网络——T5-XXL 文本编码器(fp32 约 10 GB)、CLIP-L(小)、12B MMDiT、VAE。先编码提示,删掉编码器,加载 DiT,去噪,删掉 DiT,加载 VAE,解码。8GB 消费 GPU 一次只能装一个阶段。

  2. bitsandbytes 的 4-bit 量化:BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16) 同时用在 T5 编码器和 DiT 上。内存砍 8 倍,文生图的质量下降按 Aritra 的基准几乎不可察觉。

  3. CPU 卸载:pipe.enable_model_cpu_offload() 在每次前向推进时自动在 CPU/GPU 间换模块。多 10-20% 延迟,但让流水线能跑起来

内存核算:10 GB T5 / 8 = 1.25 GB 量化、12B × 0.5 字节 ≈ 6 GB 量化 DiT,加激活。用 stas00 的术语,这是 TP=1 推理的极端端——无模型并行、最大量化。生产里你会在 H100 上跑 TP=2 或 TP=4;对单个开发笔记本,这就是配方。

八、练习

  1. 简单。 用引导 w ∈ {0, 1, 3, 7, 15}code/main.py。记录每类的样本均值。在哪个 w 下,类均值越过真实数据均值?

  2. 中等。 把玩具线性编码器换成「tanh-MLP 编码器/解码器 + 重建损失」。在新潜变量上重训扩散。样本质量变了吗?

  3. 困难。 用 diffusers 搭真实 Stable Diffusion 推理:加载 sdxl-base,跑 30 步 Euler + CFG=7,计时。然后切到 sdxl-turbo 跑 4 步 CFG=0。同一主题,不同质量——描述变了什么、为什么。

本节要点回顾

  1. 像素空间扩散不可行:512²×3 让单步 ~100 GFLOPS;潜在扩散把扩散搬进 VAE 的 64×64×4 潜空间,FLOPs 砍 ~64 倍。
  2. 两阶段架构:第一阶段 VAE(压缩 + LPIPS + 小权重 KL),第二阶段扩散(损失与第 06 节完全相同,只换数据域)。
  3. 文本条件 = 交叉注意力:冻结文本编码器 + 每个 U-Net 块 Q=图像,K=V=文本;这是 SD 与类别条件扩散的唯一实质区别。
  4. CFG:(1+w)·ε_cond − w·ε_uncond,w≈3-7 甜点;>10 过饱和;空负向提示(null)≠ 有内容负向提示。
  5. 演化趋势:U-Net→DiT(扩展律更平滑)、CLIP→T5(提示遵从度更好)、4→16 通道(细节余量);SD3 的 MMDiT 让文本/图像流联合注意力。
  6. VAE 缩放因子:scaling_factor≈0.18215 把潜变量方差拉回 1;每个 VAE 不同,混用必崩。
  7. 8GB 跑 12B Flux 的三旋钮:分阶段加载(三个网络无需共存)+ 4-bit 量化(砍 8 倍内存)+ CPU 卸载(多 10-20% 延迟但能跑)。

下一节,我们进入对预训练扩散的精细控制——ControlNet 与 LoRA:前者用「零卷积副本」注入结构条件(边缘、深度、姿态),后者用低秩矩阵让一张消费级 GPU 就能微调一个十亿参数模型。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U