Skip to content

필사 모드: Diffusion Model 论文综述:从 DDPM 到 Stable Diffusion、DiT、SDXL,图像生成模型的演进

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
Diffusion Model Survey: DDPM to Stable Diffusion, DiT, SDXL

引言

在图像生成领域,Diffusion Model 已经确立为取代 GAN(Generative Adversarial Network)的新范式。自 2020 年 Ho 等人发表 DDPM(Denoising Diffusion Probabilistic Models) 以来,短短三年内 Stable Diffusion、DALL-E 2、Midjourney 等商用服务相继出现,推动了图像生成的大众化。

Diffusion Model 的核心思路出奇地简单:学习向数据逐步添加噪声的 Forward Process,以及将该噪声反向去除以复原数据的 Reverse Process。在这一过程中,模型会在每个噪声水平上学习"应该朝哪个方向去除噪声"。

本文将按时间顺序综述从 DDPM 的数学基础、DDIM 的加速采样、与 Score-based 模型的关系,到 Latent Diffusion(Stable Diffusion)的架构、Classifier-free Guidance、DiT(Diffusion Transformer)、SDXL、ControlNet 等主要模型的演进。文章将全面涵盖各模型的核心贡献、实现代码、性能对比,以及运营时的注意事项。

DDPM:扩散模型的基础

Forward Process(添加噪声)

DDPM 的 Forward Process 会在 T 个步骤中,向原始数据 x_0 逐步添加高斯噪声。每一步 t 的噪声调度由 beta_t 控制。

q(xtxt1)=N(xt;1βtxt1,βtI)q(x_t | x_{t-1}) = \mathcal{N}(x_t; \sqrt{1-\beta_t} x_{t-1}, \beta_t I)

利用 Reparameterization trick,可以直接计算任意时间步 t 上的噪声图像。

xt=αˉtx0+1αˉtϵ,ϵN(0,I)x_t = \sqrt{\bar{\alpha}_t} x_0 + \sqrt{1 - \bar{\alpha}_t} \epsilon, \quad \epsilon \sim \mathcal{N}(0, I)

其中 alpha_t = 1 - beta_t,alpha_bar_t 是从 alpha_1 到 alpha_t 的累积乘积。

import torch
import torch.nn as nn
import numpy as np

class DDPMScheduler:
    """DDPM Forward Process 调度器"""
    def __init__(self, num_timesteps=1000, beta_start=1e-4, beta_end=0.02):
        self.num_timesteps = num_timesteps
        # 线性噪声调度
        self.betas = torch.linspace(beta_start, beta_end, num_timesteps)
        self.alphas = 1.0 - self.betas
        self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
        self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod)
        self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1.0 - self.alphas_cumprod)

    def add_noise(self, x_0, t, noise=None):
        """生成任意时间步 t 上的噪声图像"""
        if noise is None:
            noise = torch.randn_like(x_0)

        sqrt_alpha_bar = self.sqrt_alphas_cumprod[t].view(-1, 1, 1, 1)
        sqrt_one_minus_alpha_bar = self.sqrt_one_minus_alphas_cumprod[t].view(-1, 1, 1, 1)

        # x_t = sqrt(alpha_bar_t) * x_0 + sqrt(1 - alpha_bar_t) * epsilon
        x_t = sqrt_alpha_bar * x_0 + sqrt_one_minus_alpha_bar * noise
        return x_t

    def sample_timesteps(self, batch_size):
        """用于训练的随机时间步采样"""
        return torch.randint(0, self.num_timesteps, (batch_size,))

Reverse Process(去除噪声)

在 Reverse Process 中,从 x_T ~ N(0, I) 出发,使用训练好的模型 epsilon_theta 逐步去除噪声。

pθ(xt1xt)=N(xt1;μθ(xt,t),σt2I)p_\theta(x_{t-1} | x_t) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \sigma_t^2 I)
class DDPMSampler:
    """DDPM Reverse Process 采样器"""
    def __init__(self, scheduler):
        self.scheduler = scheduler

    @torch.no_grad()
    def sample(self, model, shape, device):
        """DDPM 逆扩散采样"""
        # 从纯噪声开始
        x = torch.randn(shape, device=device)

        for t in reversed(range(self.scheduler.num_timesteps)):
            t_batch = torch.full((shape[0],), t, device=device, dtype=torch.long)

            # 预测噪声
            predicted_noise = model(x, t_batch)

            # 计算均值
            alpha = self.scheduler.alphas[t]
            alpha_bar = self.scheduler.alphas_cumprod[t]
            beta = self.scheduler.betas[t]

            mean = (1 / torch.sqrt(alpha)) * (
                x - (beta / torch.sqrt(1 - alpha_bar)) * predicted_noise
            )

            # 仅当 t > 0 时添加噪声
            if t > 0:
                noise = torch.randn_like(x)
                sigma = torch.sqrt(beta)
                x = mean + sigma * noise
            else:
                x = mean

        return x

训练目标:Simple Loss

DDPM 的训练目标是最小化模型预测噪声与真实噪声之间的 MSE。

Lsimple=Et,x0,ϵ[ϵϵθ(xt,t)2]L_{\text{simple}} = \mathbb{E}_{t, x_0, \epsilon}\left[\|\epsilon - \epsilon_\theta(x_t, t)\|^2\right]
def ddpm_training_step(model, x_0, scheduler, optimizer):
    """DDPM 单步训练"""
    batch_size = x_0.shape[0]
    device = x_0.device

    # 1. 随机采样时间步
    t = scheduler.sample_timesteps(batch_size).to(device)

    # 2. 生成噪声并生成带噪图像
    noise = torch.randn_like(x_0)
    x_t = scheduler.add_noise(x_0, t, noise)

    # 3. 模型预测噪声
    predicted_noise = model(x_t, t)

    # 4. 计算 Simple Loss
    loss = nn.functional.mse_loss(predicted_noise, noise)

    # 5. 反向传播
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    return loss.item()

DDIM:加速采样

DDPM 需要 1000 步的逆扩散过程,生成速度非常缓慢。Song 等人(2020)提出的 DDIM(Denoising Diffusion Implicit Models) 定义了非马尔可夫(non-Markovian)扩散过程,使得用同一个训练好的模型也能实现快 10~50 倍的采样。

DDIM 的核心在于用 eta 参数控制采样是随机的还是确定性的。eta=0 时完全是确定性(deterministic)采样,eta=1 时则等同于 DDPM。

class DDIMSampler:
    """DDIM 加速采样器"""
    def __init__(self, scheduler, ddim_steps=50, eta=0.0):
        self.scheduler = scheduler
        self.ddim_steps = ddim_steps
        self.eta = eta
        # 生成时间步子集(例如:1000 -> 50)
        self.timesteps = np.linspace(
            0, scheduler.num_timesteps - 1, ddim_steps, dtype=int
        )[::-1]

    @torch.no_grad()
    def sample(self, model, shape, device):
        """DDIM 加速采样 - 用 50 步生成高质量结果"""
        x = torch.randn(shape, device=device)

        for i in range(len(self.timesteps)):
            t = self.timesteps[i]
            t_prev = self.timesteps[i + 1] if i + 1 < len(self.timesteps) else 0

            t_batch = torch.full((shape[0],), t, device=device, dtype=torch.long)
            predicted_noise = model(x, t_batch)

            alpha_bar_t = self.scheduler.alphas_cumprod[t]
            alpha_bar_prev = self.scheduler.alphas_cumprod[t_prev]

            # 预测 x_0
            x_0_pred = (x - torch.sqrt(1 - alpha_bar_t) * predicted_noise) / torch.sqrt(alpha_bar_t)
            x_0_pred = torch.clamp(x_0_pred, -1, 1)

            # 计算方向
            sigma = self.eta * torch.sqrt(
                (1 - alpha_bar_prev) / (1 - alpha_bar_t) * (1 - alpha_bar_t / alpha_bar_prev)
            )
            direction = torch.sqrt(1 - alpha_bar_prev - sigma**2) * predicted_noise

            # 计算 x_{t-1}
            x = torch.sqrt(alpha_bar_prev) * x_0_pred + direction

            if self.eta > 0 and t > 0:
                x = x + sigma * torch.randn_like(x)

        return x

与 Score-based 模型的关系

Song 与 Ermon(2019)从 Score Matching 的视角解读了扩散模型。Score function 是数据分布对数密度的梯度。

sθ(x)xlogp(x)s_\theta(x) \approx \nabla_x \log p(x)

DDPM 的噪声预测 epsilon_theta 与 Score function 之间具有以下关系。

sθ(xt,t)=ϵθ(xt,t)1αˉts_\theta(x_t, t) = -\frac{\epsilon_\theta(x_t, t)}{\sqrt{1 - \bar{\alpha}_t}}

这一关系被统一到 Score SDE(Stochastic Differential Equation)框架中,用如下方式描述连续时间下的扩散过程。

dx=f(x,t)dt+g(t)dwdx = f(x, t)dt + g(t)dw

Latent Diffusion Model (Stable Diffusion)

架构概览

Rombach 等人(2022)提出的 Latent Diffusion Model(LDM) 不在像素空间、而是在 潜在空间(latent space) 中执行扩散过程,从而大幅降低了计算成本。这正是 Stable Diffusion 的核心架构。

LDM 由三个核心组成部分构成。

组成部分作用详情
VAE Encoder将图像编码到潜在空间把 512x512 图像压缩为 64x64x4 的潜在表示
U-Net(Denoiser)在潜在空间中预测噪声通过 Cross-attention 反映文本条件
VAE Decoder将潜在表示解码为图像把 64x64x4 的潜在表示还原为 512x512 图像
Text Encoder编码文本提示词用 CLIP ViT-L/14 生成 77 个 token 的嵌入

核心代码结构

import torch
from diffusers import StableDiffusionPipeline, DDIMScheduler

class LatentDiffusionInference:
    """Stable Diffusion 推理流水线(简化版)"""

    def __init__(self, model_id="stable-diffusion-v1-5/stable-diffusion-v1-5"):
        self.pipe = StableDiffusionPipeline.from_pretrained(
            model_id,
            torch_dtype=torch.float16,
            safety_checker=None
        ).to("cuda")

        # 替换为 DDIM 调度器(加速到 50 步)
        self.pipe.scheduler = DDIMScheduler.from_config(
            self.pipe.scheduler.config
        )

    def generate(self, prompt, negative_prompt="", num_steps=50, guidance_scale=7.5):
        """文本-图像生成"""
        image = self.pipe(
            prompt=prompt,
            negative_prompt=negative_prompt,
            num_inference_steps=num_steps,
            guidance_scale=guidance_scale,
        ).images[0]
        return image

    def generate_with_latent_control(self, prompt, seed=42):
        """直接控制潜在空间"""
        generator = torch.Generator(device="cuda").manual_seed(seed)

        # 直接生成潜在向量
        latents = torch.randn(
            (1, 4, 64, 64),
            generator=generator,
            device="cuda",
            dtype=torch.float16
        )

        image = self.pipe(
            prompt=prompt,
            latents=latents,
            num_inference_steps=50,
            guidance_scale=7.5,
        ).images[0]
        return image

Cross-Attention 机制

在 Stable Diffusion 的 U-Net 中,通过 Cross-Attention 将文本条件反映到图像生成中。Query 来自图像的潜在表示,Key 与 Value 则来自文本嵌入。

class CrossAttention(nn.Module):
    """Stable Diffusion U-Net 的 Cross-Attention 层"""
    def __init__(self, d_model=320, d_context=768, n_heads=8):
        super().__init__()
        self.n_heads = n_heads
        self.d_head = d_model // n_heads

        self.to_q = nn.Linear(d_model, d_model, bias=False)
        self.to_k = nn.Linear(d_context, d_model, bias=False)
        self.to_v = nn.Linear(d_context, d_model, bias=False)
        self.to_out = nn.Linear(d_model, d_model)

    def forward(self, x, context):
        """
        x: 图像潜在表示 (B, H*W, d_model)
        context: 文本嵌入 (B, seq_len, d_context)
        """
        B, N, C = x.shape

        q = self.to_q(x).view(B, N, self.n_heads, self.d_head).transpose(1, 2)
        k = self.to_k(context).view(B, -1, self.n_heads, self.d_head).transpose(1, 2)
        v = self.to_v(context).view(B, -1, self.n_heads, self.d_head).transpose(1, 2)

        # Scaled Dot-Product Attention
        scale = self.d_head ** -0.5
        attn = torch.matmul(q, k.transpose(-2, -1)) * scale
        attn = torch.softmax(attn, dim=-1)
        out = torch.matmul(attn, v)

        out = out.transpose(1, 2).contiguous().view(B, N, C)
        return self.to_out(out)

Classifier-free Guidance (CFG)

Ho 与 Salimans(2022)提出的 Classifier-free Guidance 是无需独立分类器即可控制生成质量的核心技术。

训练时同时训练条件模型与无条件模型(以一定概率将文本条件替换为空字符串)。推理时则使用两个预测的加权平均。

ϵ~θ(xt,c)=ϵθ(xt,)+w(ϵθ(xt,c)ϵθ(xt,))\tilde{\epsilon}_\theta(x_t, c) = \epsilon_\theta(x_t, \varnothing) + w \cdot (\epsilon_\theta(x_t, c) - \epsilon_\theta(x_t, \varnothing))

其中 w 是 guidance scale。w=1 时为纯条件生成,w 越大则越强烈地服从文本条件(通常取 7.5~15)。

def classifier_free_guidance_step(model, x_t, t, text_embedding, null_embedding, guidance_scale=7.5):
    """Classifier-free Guidance 单步"""

    # 把条件/无条件预测合并为一个批次一次性处理
    x_in = torch.cat([x_t, x_t], dim=0)
    t_in = torch.cat([t, t], dim=0)
    c_in = torch.cat([null_embedding, text_embedding], dim=0)

    # 用一次 forward pass 同时生成两个预测
    noise_pred = model(x_in, t_in, encoder_hidden_states=c_in)
    noise_pred_uncond, noise_pred_cond = noise_pred.chunk(2)

    # 应用 CFG
    noise_pred_guided = noise_pred_uncond + guidance_scale * (
        noise_pred_cond - noise_pred_uncond
    )
    return noise_pred_guided

DiT: Diffusion Transformer

从 U-Net 到 Transformer

Peebles 与 Xie(2023)提出的 DiT(Diffusion Transformer) 把扩散模型的骨干网络从 U-Net 换成了 Transformer。其核心发现是:增大 Transformer 的规模(GFLOPs),生成质量(FID)会随之持续提升。

模型骨干网络参数量FID(ImageNet 256)GFLOPs
ADMU-Net554M10.941120
LDM-4U-Net400M10.56103
DiT-S/2Transformer33M68.406
DiT-B/2Transformer130M43.4723
DiT-L/2Transformer458M9.6280
DiT-XL/2Transformer675M2.27119

adaLN-Zero 模块

DiT 的核心创新是 adaLN-Zero 条件化方式:把时间步与类别嵌入注入到 Adaptive Layer Normalization 的 scale/shift 参数中,但在初始化时把门控参数设为 0,使模型在训练初期表现为残差连接(identity function)。

class DiTBlock(nn.Module):
    """DiT 的 adaLN-Zero Transformer Block"""
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.norm1 = nn.LayerNorm(d_model, elementwise_affine=False)
        self.attn = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
        self.norm2 = nn.LayerNorm(d_model, elementwise_affine=False)
        self.mlp = nn.Sequential(
            nn.Linear(d_model, d_model * 4),
            nn.GELU(),
            nn.Linear(d_model * 4, d_model),
        )
        # adaLN modulation:6 个参数(gamma1, beta1, alpha1, gamma2, beta2, alpha2)
        self.adaLN_modulation = nn.Sequential(
            nn.SiLU(),
            nn.Linear(d_model, 6 * d_model),
        )
        # Zero 初始化 - 使模型在训练初期表现为 identity
        nn.init.zeros_(self.adaLN_modulation[-1].weight)
        nn.init.zeros_(self.adaLN_modulation[-1].bias)

    def forward(self, x, c):
        """
        x: patch token (B, N, D)
        c: 条件嵌入 - 时间步 + 类别 (B, D)
        """
        # 生成 adaLN 参数
        shift1, scale1, gate1, shift2, scale2, gate2 = (
            self.adaLN_modulation(c).chunk(6, dim=-1)
        )

        # Self-Attention with adaLN
        h = self.norm1(x)
        h = h * (1 + scale1.unsqueeze(1)) + shift1.unsqueeze(1)
        h, _ = self.attn(h, h, h)
        x = x + gate1.unsqueeze(1) * h

        # FFN with adaLN
        h = self.norm2(x)
        h = h * (1 + scale2.unsqueeze(1)) + shift2.unsqueeze(1)
        h = self.mlp(h)
        x = x + gate2.unsqueeze(1) * h

        return x

Patchify 策略

DiT 把潜在表示切分成 p x p 的 patch,作为 Transformer 的输入 token。patch 尺寸越小,token 数量越多,性能会提升,但计算成本也随之增加。

class PatchEmbed(nn.Module):
    """DiT 的 Patchify 层"""
    def __init__(self, patch_size=2, in_channels=4, embed_dim=1152):
        super().__init__()
        self.patch_size = patch_size
        self.proj = nn.Conv2d(
            in_channels, embed_dim,
            kernel_size=patch_size, stride=patch_size
        )

    def forward(self, x):
        """(B, C, H, W) -> (B, N, D) patch token 序列"""
        x = self.proj(x)  # (B, D, H/p, W/p)
        x = x.flatten(2).transpose(1, 2)  # (B, N, D)
        return x

SDXL:Stable Diffusion 的进化

主要改进

Podell 等人(2023)提出的 SDXL 相较于 Stable Diffusion v1.5,引入了以下核心改进。

特性SD v1.5SDXL Base
U-Net 参数量860M2.6B(增加 3 倍)
文本编码器CLIP ViT-L/14OpenCLIP ViT-bigG + CLIP ViT-L
文本嵌入维度7682048
默认分辨率512x5121024x1024
Attention 模块数1670
Refiner 模型内置专用 Refiner

双文本编码器

SDXL 最大的创新之一,是使用 两个文本编码器。它结合了 OpenCLIP ViT-bigG 丰富的语义表示与 CLIP ViT-L 的互补特征,大幅提升了文本理解能力。

from diffusers import StableDiffusionXLPipeline
import torch

class SDXLInference:
    """SDXL 推理流水线"""

    def __init__(self):
        self.pipe = StableDiffusionXLPipeline.from_pretrained(
            "stabilityai/stable-diffusion-xl-base-1.0",
            torch_dtype=torch.float16,
            variant="fp16",
            use_safetensors=True,
        ).to("cuda")

        # 内存优化
        self.pipe.enable_model_cpu_offload()
        self.pipe.enable_vae_tiling()

    def generate(self, prompt, negative_prompt="", steps=30):
        """SDXL 基础生成"""
        image = self.pipe(
            prompt=prompt,
            negative_prompt=negative_prompt,
            num_inference_steps=steps,
            guidance_scale=7.5,
            height=1024,
            width=1024,
        ).images[0]
        return image

    def generate_with_refiner(self, prompt, base_pipe, refiner_pipe):
        """Base + Refiner 两阶段流水线"""
        # Base 模型:占全部步数的 80%
        high_noise_frac = 0.8
        image = base_pipe(
            prompt=prompt,
            num_inference_steps=40,
            denoising_end=high_noise_frac,
            output_type="latent",
        ).images

        # Refiner:剩余的 20%(提升细节质量)
        image = refiner_pipe(
            prompt=prompt,
            num_inference_steps=40,
            denoising_start=high_noise_frac,
            image=image,
        ).images[0]
        return image

尺寸/裁剪条件化

SDXL 在训练时把图像的原始尺寸与裁剪坐标作为条件提供,从而能够有效学习各种宽高比的图像。这一机制是通过 Fourier Feature Encoding 实现的。

def get_sdxl_conditioning(original_size, crop_coords, target_size):
    """生成 SDXL 的尺寸/裁剪条件"""
    # 原始尺寸 (height, width)
    original_size = torch.tensor(original_size, dtype=torch.float32)
    # 裁剪坐标 (top, left)
    crop_coords = torch.tensor(crop_coords, dtype=torch.float32)
    # 目标尺寸 (height, width)
    target_size = torch.tensor(target_size, dtype=torch.float32)

    # Fourier Feature Encoding
    conditioning = torch.cat([original_size, crop_coords, target_size])

    # Sinusoidal embedding
    freqs = torch.exp(
        -torch.arange(0, 128) * np.log(10000) / 128
    )
    emb = conditioning.unsqueeze(-1) * freqs.unsqueeze(0)
    emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1)

    return emb.flatten()

ControlNet:条件生成控制

Zhang 等人(2023)提出的 ControlNet,向预训练扩散模型添加边缘、深度、姿态等空间条件。它通过 Zero Convolution 技术,在训练初期保留模型原有能力的同时,逐步学习新的条件。

from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
from controlnet_aux import CannyDetector
from PIL import Image
import torch

def controlnet_canny_generation(input_image_path, prompt):
    """基于 ControlNet Canny Edge 的图像生成"""
    # 加载 ControlNet 模型
    controlnet = ControlNetModel.from_pretrained(
        "lllyasviel/control_v11p_sd15_canny",
        torch_dtype=torch.float16,
    )

    pipe = StableDiffusionControlNetPipeline.from_pretrained(
        "stable-diffusion-v1-5/stable-diffusion-v1-5",
        controlnet=controlnet,
        torch_dtype=torch.float16,
    ).to("cuda")

    # 提取 Canny Edge
    canny_detector = CannyDetector()
    input_image = Image.open(input_image_path)
    canny_image = canny_detector(input_image, low_threshold=100, high_threshold=200)

    # 基于 ControlNet 生成
    output = pipe(
        prompt=prompt,
        image=canny_image,
        num_inference_steps=30,
        guidance_scale=7.5,
        controlnet_conditioning_scale=1.0,
    ).images[0]

    return output

训练流水线与数据准备

数据集构成

以下是大规模扩散模型训练中常用的主要数据集对比。

数据集规模分辨率用途
LAION-5B58 亿图像-文本对多样Stable Diffusion 训练
LAION-Aesthetics1.2 亿(经过筛选)多样高质量微调
ImageNet130 万256/512DiT 训练(类别条件)
COYO-700M7 亿多样含韩语的多语言训练

微调策略

# LoRA 微调(Stable Diffusion)
accelerate launch train_text_to_image_lora.py \
    --pretrained_model_name_or_path="stable-diffusion-v1-5/stable-diffusion-v1-5" \
    --dataset_name="custom_dataset" \
    --resolution=512 \
    --train_batch_size=4 \
    --gradient_accumulation_steps=4 \
    --learning_rate=1e-4 \
    --lr_scheduler="cosine" \
    --lr_warmup_steps=500 \
    --max_train_steps=10000 \
    --rank=64 \
    --output_dir="./lora_output" \
    --mixed_precision="fp16" \
    --enable_xformers_memory_efficient_attention

# DreamBooth 微调(特定对象/风格学习)
accelerate launch train_dreambooth.py \
    --pretrained_model_name_or_path="stable-diffusion-v1-5/stable-diffusion-v1-5" \
    --instance_data_dir="./my_images" \
    --instance_prompt="a photo of sks dog" \
    --class_data_dir="./class_images" \
    --class_prompt="a photo of dog" \
    --with_prior_preservation \
    --prior_loss_weight=1.0 \
    --num_class_images=200 \
    --resolution=512 \
    --train_batch_size=1 \
    --learning_rate=5e-6 \
    --max_train_steps=800

推理优化技术

主要优化技术对比

技法速度提升质量影响内存节省
DDIM(50 steps)20x轻微-
DPM-Solver++(20 steps)50x轻微-
xFormers Memory Efficient Attention1.5x30-40%
torch.compile1.2-1.5x-
VAE Tiling-轻微70%+
FP16/BF161.5-2x轻微50%
TensorRT2-4x-

实战优化代码

import torch
from diffusers import StableDiffusionXLPipeline, DPMSolverMultistepScheduler

def optimized_sdxl_pipeline():
    """经过生产环境优化的 SDXL 流水线"""
    pipe = StableDiffusionXLPipeline.from_pretrained(
        "stabilityai/stable-diffusion-xl-base-1.0",
        torch_dtype=torch.float16,
        variant="fp16",
        use_safetensors=True,
    ).to("cuda")

    # 1. 应用高速调度器
    pipe.scheduler = DPMSolverMultistepScheduler.from_config(
        pipe.scheduler.config,
        algorithm_type="dpmsolver++",
        use_karras_sigmas=True,
    )

    # 2. VAE Tiling(高分辨率生成时节省内存)
    pipe.enable_vae_tiling()

    # 3. Attention Slicing(VRAM 不足时)
    pipe.enable_attention_slicing()

    # 4. torch.compile(PyTorch 2.0+)
    pipe.unet = torch.compile(pipe.unet, mode="reduce-overhead", fullgraph=True)

    return pipe

# GPU 内存监控
def monitor_gpu_memory():
    """监控 GPU 内存使用量"""
    allocated = torch.cuda.memory_allocated() / 1024**3
    reserved = torch.cuda.memory_reserved() / 1024**3
    max_allocated = torch.cuda.max_memory_allocated() / 1024**3
    print(f"Allocated: {allocated:.2f} GB")
    print(f"Reserved:  {reserved:.2f} GB")
    print(f"Peak:      {max_allocated:.2f} GB")

模型综合对比

模型年份核心贡献骨干网络条件化方式分辨率
DDPM2020扩散模型的实用化U-Net无(无条件)256
DDIM2020加速采样U-Net256
LDM(SD)2022潜在空间扩散U-Net + VAECross-Attention512
DiT2023Transformer 骨干网络TransformeradaLN-Zero256/512
SDXL2023大规模 U-Net + 双编码器U-Net + VAECross-Attention + CFG1024
ControlNet2023空间条件控制Zero Conv + U-Net边缘/深度/姿态512
SD32024MMDiT(多模态 DiT)TransformerFlow Matching1024

运营注意事项

GPU 内存管理

运营基于 Stable Diffusion 的服务时,最常见的问题是 GPU OOM(Out of Memory)。需要检查以下事项。

  1. 限制批次大小:生成 1024x1024 的 SDXL 图像时,单张图像在 A100 80GB 上约占用 12GB,在 V100 16GB 上则会发生 OOM
  2. 限制并发请求:必须应用 Rate limiter,防止 GPU 内存超限
  3. 启用 VAE Tiling:生成高分辨率(2048x2048+)图像时是必需的
  4. 内存分析:通过定期监控 GPU 内存来发现内存泄漏

故障案例:GPU OOM 恢复

# 检查 GPU 内存状态
nvidia-smi --query-gpu=memory.used,memory.total --format=csv

# 检查 Python 进程的 GPU 内存泄漏
fuser -v /dev/nvidia*

# 强制释放 GPU 内存(无需重启进程)
python -c "
import torch
import gc
gc.collect()
torch.cuda.empty_cache()
print('GPU memory cleared')
print(f'Allocated: {torch.cuda.memory_allocated()/1024**3:.2f} GB')
"

# 发生 OOM 时的服务恢复流程
# 1. 对相应 worker 进程执行 graceful shutdown
# 2. 确认 GPU 内存已释放
# 3. 调整批次大小/并发请求数
# 4. 重启 worker 进程
# 5. 确认健康检查通过后恢复流量

NSFW 过滤

商用服务中必须启用 Safety Checker。如果禁用 Safety Checker,可能会生成 NSFW 内容,从而引发法律问题。

# Safety Checker 设置(生产环境必需)
pipe = StableDiffusionPipeline.from_pretrained(
    "stable-diffusion-v1-5/stable-diffusion-v1-5",
    safety_checker=None,  # 仅在开发环境中禁用
)

# 生产环境中必须启用
from diffusers.pipelines.stable_diffusion import StableDiffusionSafetyChecker
from transformers import CLIPImageProcessor

safety_checker = StableDiffusionSafetyChecker.from_pretrained(
    "CompVis/stable-diffusion-safety-checker"
)
feature_extractor = CLIPImageProcessor.from_pretrained(
    "openai/clip-vit-base-patch32"
)

故障案例与恢复流程

案例 1:模型加载失败

加载大规模模型时,可能会发生磁盘 I/O 超时或 checkpoint 损坏。

import os
from diffusers import StableDiffusionXLPipeline

def robust_model_loading(model_id, max_retries=3):
    """稳健的模型加载(含重试)"""
    for attempt in range(max_retries):
        try:
            pipe = StableDiffusionXLPipeline.from_pretrained(
                model_id,
                torch_dtype=torch.float16,
                use_safetensors=True,
                local_files_only=os.path.exists(
                    os.path.join(model_id, "model_index.json")
                ),
            )
            pipe = pipe.to("cuda")
            # 执行 warmup
            _ = pipe("test", num_inference_steps=1)
            return pipe
        except Exception as e:
            print(f"Attempt {attempt + 1} failed: {e}")
            if attempt < max_retries - 1:
                import time
                time.sleep(10)
                # 清除缓存后重试
                torch.cuda.empty_cache()
            else:
                raise RuntimeError(f"Model loading failed after {max_retries} attempts")

案例 2:图像质量下降(CFG Scale 设置不当)

# CFG Scale 指南
guidance_scale_guidelines:
  1.0: '几乎忽略条件 - 接近随机生成'
  3.0-5.0: '富有创意且多样的生成'
  7.0-8.5: '常规推荐范围 - 质量/多样性平衡'
  10.0-15.0: '文本一致性高 - 存在过饱和风险'
  20.0+: '过度引导 - 产生伪影'

# 问题诊断清单
troubleshooting:
  blurry_output:
    - 'num_inference_steps 增加(至少 30 以上)'
    - '将调度器切换为 DPM-Solver++'
  oversaturated:
    - '将 guidance_scale 降低到 7.0 以下'
    - "在 negative_prompt 中添加 'oversaturated, vivid'"
  wrong_composition:
    - '改进提示词结构(明确主语-动词-宾语)'
    - '用 ControlNet 控制构图'

结语

Diffusion Model 在 DDPM 的理论基础之上,叠加了 DDIM 的加速采样、Latent Diffusion 的高效架构、Classifier-free Guidance 的质量控制、DiT 的可扩展性、SDXL 的规模化,以及 ControlNet 的精细控制,得以迅速发展。

如今,随着 SD3 的 MMDiT(Multi-Modal Diffusion Transformer)、Flow Matching、Consistency Models 等新范式的出现,更快、更高质量的图像生成正在成为现实。尤其是 DiT 架构已成为 Sora(OpenAI)等视频生成模型的基础,Diffusion Model 的应用范围正从图像扩展到视频、3D、音频等领域。

从工程师的角度来看,理解模型的理论背景是优化与调试的关键。只有准确把握噪声调度、CFG Scale、调度器选择、内存管理等各组成部分的作用,才能在生产环境中稳定地运营服务。

参考资料

현재 단락 (1/512)

在图像生成领域,Diffusion Model 已经确立为取代 GAN(Generative Adversarial Network)的新范式。自 2020 年 Ho 等人发表 **DDPM(Deno...

작성 글자: 0원문 글자: 19,780작성 단락: 0/512