Stable Diffusion:架构与微调


文档摘要

Stable Diffusion:架构与微调 本节摘要:Stable Diffusion 本质上是一个跑到 VAE 隐空间里去的 DDPM——文本通过交叉注意力注入条件,采样用快速确定性 ODE 求解器,方向由无分类器引导(Classifier-Free Guidance)把控。本节把这条流水线拆成五块(VAE、文本编码器、U-Net、调度器、安全检查器),讲清「隐空间扩散」为何能把算力砍掉 48 倍、CFG 的 为什么是文生图能上生产的命门、LoRA 如何只动 1050 MB 就把风格改头换面。读完本节,你能用 跑通文生图、图生图、内绘、ControlNet,并把一个自定义 LoRA 训出来、加载进去。 对应原课程:Phase 4 · Lesson 11 · (原英文 )。

Stable Diffusion:架构与微调

本节摘要:Stable Diffusion 本质上是一个跑到 VAE 隐空间里去的 DDPM——文本通过交叉注意力注入条件,采样用快速确定性 ODE 求解器,方向由无分类器引导(Classifier-Free Guidance)把控。本节把这条流水线拆成五块(VAE、文本编码器、U-Net、调度器、安全检查器),讲清「隐空间扩散」为何能把算力砍掉 48 倍、CFG 的 eps_uncond + w·(eps_cond − eps_uncond) 为什么是文生图能上生产的命门、LoRA 如何只动 10~50 MB 就把风格改头换面。读完本节,你能用 diffusers 跑通文生图、图生图、内绘、ControlNet,并把一个自定义 LoRA 训出来、加载进去。

对应原课程:Phase 4 · Lesson 11 · stable-diffusion(原英文 phases/04-computer-vision/11-stable-diffusion/docs/en.md)。

学习目标

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

  1. 说清 Stable Diffusion 流水线的五个组件——VAE、文本编码器、U-Net、调度器、安全检查器——各自干什么。
  2. 解释隐空间扩散为什么在 4×64×64 上训练比 3×512×512 像素空间省 48 倍算力而不掉画质。
  3. diffusers 跑通文生图、图生图、内绘、ControlNet 引导生成。
  4. 在小型自定义数据集上用 LoRA 微调 Stable Diffusion,并在推理时加载该适配器。

一、问题与直觉

直接在 512×512 RGB 图上训 DDPM 太贵。每个训练步都要把一个看到 3×512×512 = 786432 个输入值的 U-Net 反向传播一次,采样还要在同一个 U-Net 上跑 50+ 次前向。要达到 Stable Diffusion 1.5(2022 年发布)那种画质,像素空间扩散大约需要 256 GPU-月训练,且消费级 GPU 上每张图要 10~30 秒。

让开源文生图变得可行的,是隐空间扩散(Latent Diffusion,Rombach 等,CVPR 2022)。先训一个 VAE,把 3×512×512 的图映到 4×64×64 的隐张量并能还原回来,然后在这个隐空间里做扩散。算力直降 (3·512·512)/(4·64·64) = 48 倍。采样在同一块 GPU 上从几十秒掉到两秒以内。

今天几乎所有图像生成模型——SDXL、SD3、FLUX、HunyuanDiT、Wan-Video——都是隐空间扩散模型,只在自编码器、去噪器(U-Net 或 DiT)和文本条件这三处有变体。学会 Stable Diffusion,就学会了这套模板。

流水线全貌

  • VAE——冻结的自编码器。编码器把图变成隐变量(图生图和训练时用),解码器把隐变量变回图。
  • 文本编码器——CLIP 文本编码器(SD 1.x/2.x)、CLIP-L + CLIP-G(SDXL)或 T5-XXL(SD3/FLUX)。产出一串 token 嵌入。
  • U-Net——去噪器。每个分辨率层级都有交叉注意力层,让隐变量去「看」文本嵌入。
  • 调度器——采样算法(DDIM、Euler、DPM-Solver++)。挑选 sigma,把预测的噪声混回隐变量。
  • 安全检查器——可选,过滤输出图里的 NSFW / 违规内容。

无分类器引导(CFG)

朴素文本条件让网络对每个提示 cepsilon_theta(x_t, t, c)。CFG 在训练时以 10% 概率丢弃 c(换成空嵌入),于是同一个模型既能预测条件噪声也能预测无条件噪声。推理时:

eps = eps_uncond + w * (eps_cond - eps_uncond)

w 是引导尺度。w=0 纯无条件,w=1 朴素条件,w>1 把输出推向「更贴合提示」但牺牲多样性。SD 默认 w=7.5

CFG 是文生图能上生产的根本原因。没有它,提示对输出的偏置很弱;有了它,提示说了算。

隐空间的几何

VAE 的 4 通道隐变量不只是压缩图。它是一个流形——上面做算术大致对应语义编辑(提示工程和插值都活在这里),扩散 U-Net 也把全部建模预算花在了这个流形上。解码一个随机的 4×64×64 隐变量不会得到一张随机噪声图,而是垃圾,因为只有某个特定子流形上的隐变量才能解码出有效图像。

两个推论:

  1. 图生图 = 编码图成隐变量,加部分噪声,跑去噪器,解码。因为编码近似可逆,图像结构能保留;内容按提示变化。
  2. 内绘 = 同图生图,但去噪器只更新掩码区域;非掩码区域保持在编码后的隐变量上。

U-Net 架构

SD 的 U-Net 就是第 10 节 TinyUNet 的放大版,加了三样东西:

  • 每个空间分辨率上都放 Transformer 块,内含自注意力 + 对文本嵌入的交叉注意力。
  • 通过正弦编码上的 MLP 注入时间嵌入
  • 编码器与解码器在匹配分辨率处有跳跃连接

SD 1.5 总参数约 8.6 亿,SDXL 约 26 亿,FLUX 约 120 亿。参数暴涨主要在注意力层。

LoRA 微调

全参微调 Stable Diffusion 要 20+ GB 显存,且要更新 8.6 亿参数。LoRA(Low-Rank Adaptation)冻结基础模型,只在注意力层注入小的低秩分解矩阵。SD 的一个 LoRA 适配器通常 1050 MB,单张消费级 GPU 上 1060 分钟训完,推理时即插即用。

原始: W_q : (d_in, d_out) 冻结 LoRA: W_q + alpha * (A @ B) 其中 A : (d_in, r), B : (r, d_out) r 通常取 4~32。

几乎所有社区微调都以 LoRA 形式分发。CivitAI 和 Hugging Face 上有上百万个。

常见调度器

  • DDIM——确定性,约 50 步,简单。
  • Euler ancestral——随机性,30~50 步,样本略带创意。
  • DPM-Solver++ 2M Karras——确定性,20~30 步,生产默认。
  • LCM / TCD / Turbo——一致性模型与蒸馏变体;1~4 步,代价是掉一些画质。

换调度器在 diffusers 里是一行改动,有时无需重训就能修好采样瑕疵。

二、从零实现

本节全程用 diffusers,不从头重建 Stable Diffusion。要重建的那几块(VAE、文本编码器、U-Net、调度器)各有自己的章节;本节目的就是熟练掌握生产 API。

步骤 1:文生图

import torch from diffusers import StableDiffusionPipeline pipe = StableDiffusionPipeline.from_pretrained( "runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16, ).to("cuda") image = pipe( prompt="a dog riding a skateboard in tokyo, studio ghibli style", guidance_scale=7.5, num_inference_steps=25, generator=torch.Generator("cuda").manual_seed(42), ).images[0] image.save("dog.png")

float16 把显存砍半,画质肉眼无损。默认 DPM-Solver++ 下 25 步等同于 DDIM 的 50 步。

步骤 2:换调度器

from diffusers import DPMSolverMultistepScheduler, EulerAncestralDiscreteScheduler pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config) pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)

调度器状态与 U-Net 权重解耦。可以拿 DDPM 训练,用任意调度器采样。

步骤 3:图生图

from diffusers import StableDiffusionImg2ImgPipeline from PIL import Image img2img = StableDiffusionImg2ImgPipeline.from_pretrained( "runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16, ).to("cuda") init_image = Image.open("dog.png").convert("RGB").resize((512, 512)) out = img2img( prompt="a dog riding a skateboard, oil painting", image=init_image, strength=0.6, guidance_scale=7.5, ).images[0]

strength 是去噪前要加多少噪声(0.0 = 不变,1.0 = 完全重生成)。风格迁移通常取 0.5~0.7。

步骤 4:内绘

from diffusers import StableDiffusionInpaintPipeline inpaint = StableDiffusionInpaintPipeline.from_pretrained( "runwayml/stable-diffusion-inpainting", torch_dtype=torch.float16, ).to("cuda") image = Image.open("dog.png").convert("RGB").resize((512, 512)) mask = Image.open("dog_mask.png").convert("L").resize((512, 512)) out = inpaint( prompt="a cat", image=image, mask_image=mask, guidance_scale=7.5, ).images[0]

掩码里白像素是要重生成的区域,黑像素保留。

步骤 5:加载 LoRA

pipe.load_lora_weights("sayakpaul/sd-lora-ghibli") pipe.fuse_lora(lora_scale=0.8) image = pipe(prompt="a village square in ghibli style").images[0]

lora_scale 控制强度;0.0 = 无效果,1.0 = 满效果。fuse_lora 把适配器就地烤进权重以提速,但就不能再换;要换别的适配器前先调 pipe.unfuse_lora()

步骤 6:LoRA 训练(骨架)

真正的 LoRA 训练在 peftdiffusers.training 里。轮廓如下:

# 伪代码 for step, batch in enumerate(dataloader): images, prompts = batch latents = vae.encode(images).latent_dist.sample() * 0.18215 t = torch.randint(0, num_train_timesteps, (batch_size,)) noise = torch.randn_like(latents) noisy_latents = scheduler.add_noise(latents, noise, t) text_emb = text_encoder(tokenizer(prompts)) pred_noise = unet(noisy_latents, t, text_emb) # LoRA 权重在这里注入 loss = F.mse_loss(pred_noise, noise) loss.backward() optimizer.step()

只有 LoRA 矩阵收梯度;基础 U-Net、VAE、文本编码器都冻结。批大小为 1、开梯度检查点,8 GB 显存就够。

三、框架对比

生产里真正要做的决策:

  • 模型族:SD 1.5 适合开源社区微调,SDXL 画质更高,SD3 / FLUX 是当前最优且授权严格。
  • 调度器:DPM-Solver++ 2M Karras 走 20~30 步;延迟要压到 1 秒内就上 LCM-LoRA。
  • 精度:4080/4090 用 float16,A100 及更新用 bfloat16,显存吃紧时上 int8(经 bitsandbytescompel)。
  • 条件:纯文本就够;要更强控制就在基础流水线上叠 ControlNet(canny、深度、姿态)。

批量生成用社区工具 AUTO1111 / ComfyUI;生产 API 用 diffusers + accelerate,或 optimum-nvidia 配 TensorRT 编译。

四、可复用产物

本节产出两个可复用文件(位于原课程 outputs/):

  • prompt-sd-pipeline-planner.md:一个提示词——给定延迟预算、画质目标和授权约束,在 SD 1.5 / SDXL / SD3 / FLUX 之间挑,并配好调度器和精度。
  • skill-lora-training-setup.md:一个技能——为自定义数据集写出完整 LoRA 训练配置,含 caption、秩、批大小、学习率。

五、练习

  1. (简单) 对同一提示,把 guidance_scale[1, 3, 5, 7.5, 10, 15] 各生成一张。描述图像怎么变。伪影在哪个引导值开始出现?
  2. (中等) 取一张真实照片,在 StableDiffusionImg2ImgPipeline 上把 strength[0.2, 0.4, 0.6, 0.8, 1.0] 各跑一遍。哪个 strength 能保构图又改风格?为什么 1.0 会完全无视输入?
  3. (困难) 在 10~20 张同一主体(宠物、logo、角色)的图上训一个 LoRA,生成含该主体的新场景。报告保持身份又不过拟合输入图的最佳 LoRA 秩和训练步数。

本节要点回顾

  1. Stable Diffusion = 隐空间里的 DDPM——VAE 把 3×512×512 压到 4×64×64,扩散在隐空间做,算力省 48 倍。
  2. 五个组件:VAE(冻结)、文本编码器(CLIP/T5)、U-Net(带交叉注意力)、调度器(采样算法)、安全检查器(可选)。
  3. 无分类器引导(CFG)是命门——eps = eps_uncond + w·(eps_cond − eps_uncond),默认 w=7.5;没有它提示就压不住输出。
  4. 隐空间是有结构的流形——随机隐变量解码出垃圾;图生图靠编码近似可逆保结构,内绘靠掩码只改局部。
  5. U-Net 是放大版 TinyUNet——每分辨率加 Transformer(自注意力+交叉注意力)、时间嵌入、跳跃连接;参数主要堆在注意力层。
  6. LoRA 把微调变小——在注意力层注入低秩矩阵 A·B(秩 432),适配器仅 1050 MB,8 GB 显存可训。
  7. 调度器可热插拔——DDIM/Euler/DPM-Solver++/LCM,与 U-Net 权重解耦,换调度器有时能直接修采样瑕疵。
  8. 生产决策四件:模型族(SD1.5/SDXL/SD3/FLUX)、调度器(20~30 步或 LCM)、精度(fp16/bf16/int8)、条件(纯文本或 ControlNet)。

下一节转向视频理解——把静态图像的卷积与注意力扩展到时间维度,理解动作、跟踪事件、做视频分类与时序定位。


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