第一部分:基础概念
第1章 Diffusers 简介与基础概念
1.1 什么是 Diffusers 库
| 概念名称 | 说明 | 注意事项 |
|---|
| Hugging Face Diffusers | 一个开源库,提供预构建的扩散模型(如 Stable Diffusion)及其组件,支持文本到图像、图像到图像、视频生成等任务 | 是 Hugging Face 生态的一部分,强调易用性和模块化设计 |
| 开源与社区驱动 | 库由 Hugging Face 团队维护,但接受社区贡献,模型可在 Hugging Face Hub 上共享 | 使用时应检查模型许可证,部分模型不可商用 |
| 模块化架构 | 将扩散模型拆分为独立组件(如 UNet、VAE、Scheduler),便于替换与自定义 | 初学者可直接使用 Pipeline 快速上手,无需理解底层细节 |
| 与 Transformers 集成 | 共享相同的模型加载接口(from_pretrained),可无缝调用 CLIP、T5 等文本编码器 | 需安装 transformers 库才能使用文本条件生成 |
| 支持多种后端 | 原生基于 PyTorch,也支持 JAX 和 TensorFlow(实验性) | 推荐使用 PyTorch 版本以获得最佳兼容性与文档支持 |
1.2 扩散模型的基本原理(DDPM、DDIM、Stable Diffusion 等)
| 概念名称 | 说明 | 注意事项 |
|---|
| 正向扩散过程(Forward Process) | 逐步向图像添加高斯噪声,经过 T 步后将清晰图像变为纯噪声 | 噪声调度通常固定,不参与训练 |
| 反向去噪过程(Reverse Process) | 模型学习从噪声中逐步恢复原始图像,每一步预测噪声或图像本身 | 训练目标是最小化预测噪声与真实噪声的 MSE |
| DDPM (Denoising Diffusion Probabilistic Models) | 最早的扩散模型框架,使用马尔可夫链和变分推断 | 采样速度慢(需数百步),但生成质量稳定 |
| DDIM (Denoising Diffusion Implicit Models) | 非马尔可夫采样方法,允许更少步数生成高质量图像 | 支持确定性生成,可用于图像插值 |
| Latent Diffusion (Stable Diffusion) | 在低维潜在空间(latent space)进行扩散,大幅提升效率 | 使用 VAE 编码图像,UNet 在 latent 上操作,节省显存 |
| Score-based Generative Modeling | 通过估计数据分布的梯度(score function)引导生成 | 与扩散模型数学等价,视角不同 |
| Classifier-Free Guidance | 在无分类器条件下增强生成方向,通过调节 guidance_scale 控制保真度与多样性 | 常用于文本到图像生成,提升提示词相关性 |
1.3 Diffusers 的核心组件概览(Pipeline、UNet、VAE、Scheduler 等)
| 组件名称 | 说明 | 注意事项 |
|---|
| Pipeline | 高层接口,封装完整生成流程(文本编码 → 噪声生成 → 去噪 → 解码) | 推荐初学者使用,如 StableDiffusionPipeline |
| UNet | 主干网络,负责在每一步预测噪声 | 输入包括噪声图像、timestep、条件嵌入;输出为噪声残差 |
| VAE (Variational Autoencoder) | 编码器将图像压缩至 latent 空间,解码器还原为图像 | 解码时可能出现数值溢出,需使用 clamp 或 sample 方法处理 |
| Scheduler | 控制噪声添加与去除的策略,决定采样步骤和噪声调度 | 不同 scheduler 影响生成速度与质量,可热替换 |
| Text Encoder (e.g., CLIP) | 将输入文本转换为嵌入向量,作为生成条件 | 通常来自 transformers 库,如 CLIPTextModel |
| Tokenizer | 将文本字符串转换为模型可处理的 token ID 序列 | 必须与 text encoder 匹配使用 |
| AutoencoderKL | Diffusers 中 VAE 的实现类,使用 KL 正则化 | 解码前需调用 .decode() 并后处理为像素范围 [0,1] 或 [0,255] |
| 对比项 | Diffusers | PyTorch Lightning | Keras | Transformers |
|---|
| 主要用途 | 扩散模型推理与训练 | 简化 PyTorch 训练流程 | 高级神经网络 API(TensorFlow 后端) | 自然语言处理模型 |
| 模型类型支持 | 扩散模型为主(图像、音频、视频) | 通用深度学习模型 | 通用模型,侧重 CNN/RNN | Transformer 架构(BERT、GPT、T5 等) |
| 易用性 | 高层 Pipeline 简单易用,底层组件灵活 | 封装训练循环,降低工程复杂度 | API 简洁,适合快速原型 | 提供 from_pretrained 标准接口 |
| 与 Diffusers 关系 | 核心库 | 可用于训练 Diffusers 模型 | 不直接兼容 | 深度集成,共享 tokenizer 和 text encoder |
| 是否需要手动写训练循环 | 否(Pipeline 自动完成) | 是(但结构清晰) | 是 | 否(提供 Trainer 类) |
| 典型应用场景 | 文本生成图像、图像编辑 | 训练扩散模型 | 快速搭建 CNN 分类器 | 文本生成、翻译、摘要 |
| 注意事项 | 依赖 transformers 处理文本 | 可与 Diffusers 结合实现分布式训练 | 不推荐用于扩散模型开发 | 必须安装以使用 Stable Diffusion |
第二部分:环境搭建与快速上手
第2章 环境搭建与快速上手
2.1 安装 Diffusers 与依赖库
| 操作名称 | 操作细节 | 注意事项 |
|---|
| 安装 diffusers | pip install diffusers | 默认安装最新稳定版 |
| 安装 transformers | pip install transformers | 必需,用于文本编码 |
| 安装 torch | pip install torch torchvision torchaudio | 推荐使用 GPU 版本(见官网) |
| 安装 xformers(可选) | pip install xformers | 加速注意力计算,节省显存,但可能不稳定 |
| 从源码安装最新版 | pip install git+https://github.com/huggingface/diffusers | 获取未发布的新功能或修复 |
| 安装特定版本 | pip install diffusers==0.26.0 | 用于复现论文或项目依赖 |
| 安装依赖(开发模式) | git clone https://github.com/huggingface/diffusers
cd diffusers
pip install -e ".[dev]" | 贡献代码或调试源码时使用 |
2.2 使用预训练模型进行图像生成(文本到图像)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
from_pretrained | StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5") | 加载预训练 pipeline | from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
pipe = pipe.to("cuda") | 首次运行会下载模型(约 4-7GB) |
call (pipe()) | pipe(prompt, num_inference_steps=50, guidance_scale=7.5) | 生成图像 | image = pipe("a photo of a cat", num_inference_steps=30).images[0]
image.save("cat.png") | 输出为 PIL.Image 对象列表,取 [0] 获取第一张 |
to | pipe.to(device) | 移动模型到指定设备 | pipe = pipe.to("cuda") # 使用 GPU
pipe = pipe.to("cpu") # 使用 CPU | GPU 显存不足时可尝试 fp16 |
enable_attention_slicing | pipe.enable_attention_slicing() | 降低显存占用 | pipe.enable_attention_slicing()
image = pipe("a dog").images[0] | 会轻微降低速度,但可运行更大 batch |
disable_attention_slicing | pipe.disable_attention_slicing() | 恢复默认注意力计算 | pipe.disable_attention_slicing() | 在不需要节省显存时关闭 |
2.3 使用预训练模型进行图像到图像生成
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
StableDiffusionImg2ImgPipeline.from_pretrained | from_pretrained(model_id) | 加载图像到图像 pipeline | from diffusers import StableDiffusionImg2ImgPipeline
pipe = StableDiffusionImg2ImgPipeline.from_pretrained("runwayml/stable-diffusion-v1-5").to("cuda") | 与文本到图像 pipeline 不同 |
| call | pipe(prompt, image, strength, guidance_scale) | 执行图像到图像生成 | from PIL import Image
init_image = Image.open("cat.jpg")
result = pipe(prompt="a sketched cat", image=init_image, strength=0.7, guidance_scale=9.0)
result.images[0].save("sketch.png") | strength 控制变化程度(0~1),越大越偏离原图 |
strength 参数 | float in [0, 1] | 控制噪声添加强度,决定输出与输入的相似度 | strength=0.3:轻微修改
strength=0.8:大幅重构 | 高 strength 可能丢失原图结构 |
image 输入格式 | PIL.Image 或 torch.Tensor | 提供初始图像 | 图像应为 RGB,尺寸建议 512x512 | 自动调整大小可能导致变形 |
2.4 使用不同调度器(Scheduler)生成图像
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
from_pretrained | DPMSolverMultistepScheduler.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="scheduler") | 加载调度器 | from diffusers import DPMSolverMultistepScheduler
scheduler = DPMSolverMultistepScheduler.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="scheduler") | 需指定 subfolder="scheduler" |
set_scheduler | pipe.scheduler = scheduler | 替换 pipeline 中的调度器 | pipe.scheduler = scheduler | 可动态切换,无需重新加载模型 |
| 常用调度器 | DDIMScheduler, LMSDiscreteScheduler, EulerDiscreteScheduler, DPMSolverMultistepScheduler | 不同采样策略 | DPM++ 和 Euler 在 20-30 步内效果好 | DPMSolver 速度快,适合交互式应用 |
num_inference_steps | pipe(prompt, num_inference_steps=N) | 设置采样步数 | 较新 scheduler 如 DPM 可用 20-25 步 | 传统 DDPM 需 1000 步,不实用 |
guidance_scale | pipe(guidance_scale=7.5) | 控制条件引导强度 | 值越高越贴近提示词,过高会导致过饱和 | 一般 7~12 为合理范围 |
2.5 快速推理的硬件加速(CPU/GPU/TPU)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
to("cuda") | pipe.to("cuda") | 将模型移动到 GPU | pipe = pipe.to("cuda") | 需安装 CUDA 版 PyTorch |
to("cpu") | pipe.to("cpu") | 强制使用 CPU 推理 | pipe = pipe.to("cpu") | 速度慢,显存不足时备用 |
half() | pipe.unet.half(), pipe.vae.half(), etc. | 转为 FP16 半精度 | pipe.unet = pipe.unet.half()
pipe.vae = pipe.vae.half()
pipe.to("cuda") | 可减少显存占用近半,几乎无质量损失 |
enable_xformers_memory_efficient_attention | pipe.enable_xformers_memory_efficient_attention() | 使用 xformers 优化注意力 | pipe.enable_xformers_memory_efficient_attention() | 需安装 xformers,某些平台可能编译失败 |
| 混合精度 (AMP) | with torch.autocast("cuda"): ... | 自动混合精度推理 | with torch.autocast("cuda"): image = pipe(prompt).images[0] | PyTorch 原生支持,推荐与 half() 结合 |
| TPU 支持 | 使用 JAX 版本 Diffusers | 在 Google Cloud TPU 上运行 | 需使用 diffusers[pipeline-jax] | 配置复杂,适合大规模部署 |
第三部分:Diffusion Pipeline 详解
第3章 Diffusion Pipeline 详解
3.1 Pipeline 的基本结构与工作流程
| 组件/阶段 | 说明 | 注意事项 |
|---|
| Tokenizer | 将输入文本转换为 token ID 序列 | 必须与 text_encoder 匹配,如 CLIP tokenizer |
| Text Encoder | 将 tokens 编码为嵌入向量(text embeddings) | 输出为 [batch_size, sequence_length, hidden_size] |
| VAE Encoder | 将输入图像编码为 latent 表示(用于 img2img/inpainting) | latent 形状通常为 [batch_size, 4, 64, 64](Stable Diffusion) |
| Latent 初始化 | 随机生成噪声 latent(形状同上) | 在文生图任务中使用 |
| UNet + Scheduler | 在每个 timestep 上,UNet 预测噪声,Scheduler 执行去噪步骤 | 核心循环,执行 num_inference_steps 次 |
| VAE Decoder | 将去噪后的 latent 解码为像素图像 | 输出为 [0, 1] 范围的 RGB 图像 |
| 后处理 | 将 tensor 转换为 PIL.Image 对象 | 自动完成,可通过 output_type 控制 |
| 工作流程顺序 | 文本 → Tokenizer → Text Encoder → Context 随机噪声 → Latent → UNet+Scheduler(T 步)→ Decoded Image | 整个过程由 Pipeline 封装 |
3.2 常用 Pipeline 类型对比(StableDiffusionPipeline、DDPMPipeline、LatentConsistencyModelPipeline 等)
| Pipeline 类名 | 用途 | 输入要求 | 典型应用场景 | 注意事项 |
|---|
StableDiffusionPipeline | 文本到图像生成 | prompt (str/List) | 通用图像生成 | 最常用,支持 fp16 和 xformers |
StableDiffusionImg2ImgPipeline | 图像到图像生成 | prompt, image, strength | 风格迁移、图像增强 | strength 控制变化程度 |
StableDiffusionInpaintPipeline | 图像修复(inpainting) | prompt, image, mask | 局部编辑、物体移除 | mask 指定需重绘区域 |
DDPMPipeline | 基础 DDPM 模型采样 | num_inference_steps | 教学、研究基础扩散模型 | 不依赖文本条件,仅生成随机图像 |
DDIMPipeline | 使用 DDIM 采样的非马尔可夫模型 | num_inference_steps, eta | 快速采样、图像插值 | eta=0 为确定性生成 |
PNDMPipeline | 使用 PNDM(Pseudo Numerical Methods)调度 | num_inference_steps | 加速 DDPM 采样 | 兼容旧版模型 |
KarrasVePipeline | 基于能量函数的扩散模型 | num_inference_steps | 特定研究模型 | 使用不同的数学框架 |
LDMPipeline | Latent Diffusion Model 基础 pipeline | latent noise | 早期潜在扩散模型 | 现已被 StableDiffusion 替代 |
LatentConsistencyModelPipeline | 一致性模型,极快采样(1-4 步) | prompt, num_inference_steps=1~4 | 实时生成、低延迟应用 | 新兴技术,质量略低于标准 diffusion |
AudioLDM2Pipeline | 文本到音频生成 | prompt | 音频合成 | 支持音乐、语音等生成 |
CycleDiffusionPipeline | 图像间双向变换 | source_prompt, target_prompt, image | 图像风格渐变 | 实验性功能 |
3.3 Pipeline 的输入输出参数详解
| 参数名 | 类型 | 用途 | 默认值 | 注意事项 |
|---|
prompt | str 或 List[str] | 输入的文本提示 | "a photo of a cat" | 支持多提示批量生成 |
negative_prompt | str 或 List[str] | 不希望出现的内容 | "" | 提升生成质量,避免不相关内容 |
num_inference_steps | int | 去噪迭代次数 | 50 | 较新 scheduler 可减少至 20~30 |
guidance_scale | float | 分类器无关引导强度 | 7.5 | >1.0 增强提示相关性,过高导致过饱和 |
height, width | int | 输出图像尺寸 | 512 | 应为 8 的倍数,否则可能报错 |
batch_size | int | 批量生成数量 | 1 | 受显存限制,fp16 下可增大 |
output_type | str | 输出格式 | "pil" | 可选 "numpy", "latent" |
return_dict | bool | 是否返回字典 | True | 设为 False 可只返回图像列表 |
generator | torch.Generator | 随机种子控制 | None | 用于复现结果,如 torch.Generator("cuda").manual_seed(42) |
latents | torch.Tensor | 自定义初始噪声 | 自动生成 | 形状应为 (batch_size, 4, height//8, width//8) |
callback | function | 每步调用的回调函数 | None | 用于进度监控或中断 |
callback_steps | int | 回调触发间隔 | 1 | 每 N 步调用一次 callback |
cross_attention_kwargs | dict | 传递给注意力层的参数 | None | 如用于 ControlNet |
3.4 自定义 Pipeline 的加载与调用方式
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
from_pretrained | MyPipeline.from_pretrained(model_id) | 加载自定义 pipeline 类 | class MyPipeline(DiffusionPipeline): ...
pipe = MyPipeline.from_pretrained("runwayml/stable-diffusion-v1-5") | 需继承 DiffusionPipeline |
register_modules | pipe.register_modules(**kwargs) | 动态注册组件 | unet = UNet2DConditionModel.from_pretrained(...)
scheduler = DDIMScheduler.from_pretrained(...)
pipe.register_modules(unet=unet, scheduler=scheduler) | 用于组合不同来源的模块 |
save_pretrained | pipe.save_pretrained(save_dir) | 保存自定义 pipeline | pipe.save_pretrained("./my_pipeline") | 可重新加载 |
| 加载 LoRA 权重 | pipe.load_lora_weights(lora_model_id) | 加载 LoRA 微调权重 | pipe.load_lora_weights("latent-consistency/lcm-lora-sdv1-5")
image = pipe(prompt, num_inference_steps=4).images[0] | 支持热插拔 |
| 使用子文件夹加载 | SomePipeline.from_pretrained("model_id", subfolder="unet") | 从特定子目录加载组件 | from diffusers import UNet2DConditionModel
unet = UNet2DConditionModel.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="unet") | 适用于分体式模型结构 |
3.5 Pipeline 的保存与加载
| 操作名称 | 操作细节 | 代码示例 | 注意事项 |
|---|
| 保存完整 pipeline | pipe.save_pretrained(save_directory) | pipe.save_pretrained("./my_sd_model") | 会保存所有组件(tokenizer, text_encoder, unet, vae, scheduler) |
| 加载保存的 pipeline | SomePipeline.from_pretrained(save_directory) | from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained("./my_sd_model") | 自动识别组件类型 |
| 仅保存部分组件 | unet.save_pretrained("./unet_only") | pipe.unet.save_pretrained("./unet_weights") | 可单独保存 UNet 或 VAE |
| 加载部分权重 | pipe.unet = UNet2DConditionModel.from_pretrained("./unet_weights") | pipe.unet = UNet2DConditionModel.from_pretrained("./unet_weights") | 用于微调后替换 |
| 保存 FP16 模型 | 先转换再保存 | pipe.unet.half()
pipe.save_pretrained("./sd_fp16") | 减少存储空间和加载时间 |
| 安全保存(无 pickle) | 使用 safetensors 格式 | 确保安装 safetensors,自动使用安全格式保存 | 更安全,防止恶意代码执行 |
| 跨设备加载 | 支持 CPU/GPU 互转 | pipe = pipe.to("cuda") # 从 CPU 加载后移到 GPU | 权重自动转移,无需额外操作 |
第四部分:噪声调度器(Scheduler)深入解析
第4章 噪声调度器(Scheduler)深入解析
4.1 调度器的作用与分类(DDPM、DDIM、PNDM、K-LMS、Euler 等)
| 调度器名称 | 类名 | 作用 | 采样速度 | 注意事项 |
|---|
| DDPM | DDPMScheduler | 基础马尔可夫扩散过程 | 慢(需 1000 步) | 用于教学,实际生成不推荐 |
| DDIM | DDIMScheduler | 非马尔可夫采样,允许少步生成 | 快(20~50 步) | 支持确定性生成(eta=0) |
| PNDM | PNDMScheduler | 伪数值方法加速 DDPM | 中等(50~100 步) | 旧版兼容,现较少使用 |
| K-LMS (LMS) | LMSDiscreteScheduler | 基于 Langevin 动力学 | 快(20~50 步) | 对噪声敏感,可能不稳定 |
| Euler | EulerDiscreteScheduler | 一阶欧拉积分 | 快(30~50 步) | 简单稳定,适合初学者 |
| Euler Ancestral | EulerAncestralDiscreteScheduler | 添加随机性的 Euler | 中等 | 引入随机性,增加多样性 |
| DPMSolver++ | DPMSolverMultistepScheduler | 高阶求解器,支持半隐式 | 极快(10~25 步) | 当前主流,推荐使用 |
| DPMSolverSinglestep | DPMSolverSinglestepScheduler | 单步高阶求解 | 极快(10~15 步) | 适合极低步数场景 |
| UniPC | UniPCMultistepScheduler | 统一预测器-校正器 | 极快(10~15 步) | 新兴算法,性能优异 |
| KDPM2 | KDPM2DiscreteScheduler | 二阶 Karras 算法 | 快 | 少见,特定模型使用 |
4.2 各调度器的数学原理与采样策略
| 调度器 | 数学原理 | 采样策略 | 注意事项 |
|---|
| DDPM | 基于变分推断,学习反向过程的均值和方差 | 每步添加噪声再预测去除 | 需大量步数,采样效率低 |
| DDIM | 将扩散过程视为常微分方程(ODE) | 使用 ODE 求解器跳步采样 | 支持任意步数,可插值 |
| PNDM | 将扩散视为数值微分方程求解 | 使用多步法(如 Adams-Bashforth) | 近似方法,可能偏离真实路径 |
| K-LMS | 基于 Langevin dynamics,引入梯度指导 | 使用离散化 Langevin 方程 | 对梯度敏感,需调节 |
| Euler | 一阶显式欧拉法求解 ODE | x_{t-1} = x_t - Δt · f(x_t, t) | 简单但精度较低 |
| Euler Ancestral | 在 Euler 基础上添加随机扰动 | 引入祖先采样机制 | 增加多样性,但不可复现 |
| DPMSolver | 使用高阶 ODE 求解器(如 Runge-Kutta) | 多步预测与校正 | 收敛快,适合低步数 |
| DPMSolver++ | 半隐式求解,结合预测与历史信息 | 结合 backward 和 forward 求解 | 稳定性优于显式方法 |
| UniPC | 统一预测器-校正器框架 | 通用框架,适配多种扩散形式 | 理论统一,实现复杂 |
| KDPM2 | 二阶 Karras 采样器 | 使用二阶导数信息 | 精度高,但计算开销略大 |
4.3 调度器的通用接口与参数配置
| 方法/属性 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
config | scheduler.config | 获取调度器配置字典 | print(scheduler.config) | 包含 beta_schedule, steps_offset 等 |
set_timesteps | scheduler.set_timesteps(num_inference_steps) | 设置总采样步数 | scheduler.set_timesteps(30) | 必须在采样前调用 |
timesteps | scheduler.timesteps | 获取离散的时间步序列 | print(scheduler.timesteps) | 长度等于 num_inference_steps |
step | scheduler.step(model_output, t, sample, **kwargs) | 执行单步去噪 | latents = scheduler.step(noise_pred, t, latents).prev_sample | 核心方法,返回下一步样本 |
add_noise | scheduler.add_noise(original_samples, noise, timesteps) | 正向过程加噪声 | noisy_latents = scheduler.add_noise(latents, noise, t) | 用于训练或 img2img |
init_noise_sigma | scheduler.init_noise_sigma | 初始噪声标准差 | sigma = scheduler.init_noise_sigma | 用于 img2img 中调整噪声强度 |
scale_model_input | scheduler.scale_model_input(sample, t) | 缩放输入以适应模型 | input_tensor = scheduler.scale_model_input(latents, t) | 某些 scheduler 需要此操作 |
betas, alphas_cumprod | scheduler.betas, scheduler.alphas_cumprod | 噪声调度参数 | 用于调试或自定义逻辑 | 一般不直接修改 |
4.4 不同调度器的生成效果对比
| 调度器 | 推荐步数 | 生成质量 | 速度 | 确定性 | 适用场景 | 注意事项 |
|---|
| DDPM | 1000 | 高 | 慢 | 否 | 教学演示 | 不实用 |
| DDIM | 50 | 高 | 快 | 是(eta=0) | 通用生成、插值 | 平衡好 |
| PNDM | 50 | 中 | 中 | 否 | 兼容旧模型 | 已过时 |
| K-LMS | 50 | 高 | 快 | 否 | 快速生成 | 可能不稳定 |
| Euler | 50 | 高 | 快 | 是 | 稳定生成 | 推荐初学者 |
| Euler Ancestral | 50 | 中高 | 快 | 否 | 多样性生成 | 每次结果不同 |
| DPM++ 2M | 20~25 | 高 | 极快 | 否 | 主流应用 | 当前最佳选择之一 |
| DPM++ SDE | 20~25 | 高(更自然) | 快 | 否 | 艺术生成 | 引入随机性 |
| UniPC | 10~15 | 高 | 极快 | 否 | 实时生成 | 新兴优秀方案 |
| LCM | 4~8 | 中高 | 极快 | 是/否 | 超快推理 | 配合 LoRA 使用 |
4.5 自定义调度器的实现与替换
| 操作名称 | 操作细节 | 代码示例 | 注意事项 |
|---|
继承 SchedulerMixin | 创建类继承 SchedulerMixin | from diffusers import SchedulerMixin
class MyScheduler(SchedulerMixin): def step(...): ... | 必须实现核心方法 |
实现 step 方法 | 定义单步去噪逻辑 | def step(self, model_output, timestep, sample, **kwargs): # 自定义去噪公式 prev_sample = ... return {"prev_sample": prev_sample} | 返回字典格式 |
实现 add_noise | 定义正向加噪过程 | def add_noise(self, original_samples, noise, timesteps): coeff = self.alphas_cumprod[timesteps] ** 0.5 return coeff * original_samples + (1 - coeff) ** 0.5 * noise | 保持与反向一致 |
| 注册为配置文件 | 创建 scheduler_config.json | {"_class_name": "MyScheduler", "beta_schedule": "linear"} | 放入模型目录 |
| 加载自定义调度器 | 使用 from_config | sched = MyScheduler.from_config("./my_scheduler/scheduler_config.json") | 需正确配置 |
| 替换 pipeline 中调度器 | 直接赋值 | pipe.scheduler = my_scheduler | 无需重新加载模型 |
| 测试调度器 | 在 pipeline 中运行 | image = pipe("test", num_inference_steps=20).images[0] | 验证是否正常工作 |
第五部分:UNet 模型结构与使用
第5章 UNet 模型结构与使用
5.1 UNet 在扩散模型中的角色
| 概念名称 | 说明 | 注意事项 |
|---|
| 噪声预测器 | UNet 的核心任务是预测当前 latent 状态中的噪声成分 | 输入为加噪后的 latent 和 timestep,输出为噪声残差 |
| 时间步嵌入(timestep embedding) | 将离散 timestep 编码为向量,作为条件输入 | 使用正弦位置编码或 MLP 实现 |
| 条件生成支持 | 接收文本嵌入(text embeddings)作为条件,实现文本到图像控制 | 通过 cross-attention 层融合文本信息 |
| 多尺度特征处理 | 利用编码器-解码器结构捕获局部与全局图像特征 | 中间通过 skip-connections 保留细节 |
| 可替换性 | UNet 是 Diffusers Pipeline 中可独立替换的模块 | 可加载微调后的 UNet 或不同架构(如 3D UNet) |
| 训练与推理一致性 | 训练目标为最小化预测噪声与真实噪声的 MSE | 推理时通过 scheduler 多次调用完成去噪 |
5.2 UNet 的网络结构与前向传播机制
| 结构组件 | 说明 | 注意事项 |
|---|
| Encoder(下采样路径) | 多个 ResNet 块 + Attention 层 + Downsample | 逐步降低空间分辨率,增加通道数 |
| Bottleneck | 中间连接层,包含 ResNet 和 Attention 模块 | 处理最抽象的特征表示 |
| Decoder(上采样路径) | 多个 ResNet 块 + Attention 层 + Upsample | 逐步恢复空间分辨率 |
| Skip Connections | 将 encoder 的输出直接传给 decoder 对应层 | 保留细节信息,缓解梯度消失 |
| Cross-Attention 层 | 在 decoder 中融合文本条件(text embeddings) | 每个 attention head 关注不同文本 token |
| Timestep Embedding | 将 timestep 映射为向量,加入到每个 ResNet 块 | 实现时间感知的去噪 |
| ResNet Block with Attention | 基本构建单元,包含卷积、归一化、注意力 | 支持条件输入 |
| GroupNorm & SiLU | 常用归一化和激活函数 | 稳定训练,适合扩散模型 |
| 前向传播流程 | latent → encoder → bottleneck → decoder(融合 skip 和 text cond)→ noise prediction | 每一步调用一次 UNet |
| 输入拼接方式 | timestep 和 text embeddings 通过 modulation 或 attention 注入 | 不同实现方式影响性能 |
5.3 UNet 的输入输出格式(噪声、timestep、条件嵌入等)
| 输入/输出 | 类型 | 形状 | 用途 | 注意事项 |
|---|
sample (noisy latent) | torch.Tensor | (B, C, H//8, W//8) | 当前加噪的 latent 状态 | Stable Diffusion 中 C=4, H=W=512 → (B,4,64,64) |
timestep | torch.Tensor 或 int | scalar 或 (B,) | 当前去噪步数 | 自动广播为 batch 维度 |
encoder_hidden_states | torch.Tensor | (B, Seq_Len, Hidden_Size) | 文本编码器输出(如 CLIP) | Seq_Len=77, Hidden_Size=768 |
return_dict | bool | - | 是否返回字典 | 默认 True,返回 {"sample": pred_noise} |
| 输出:预测噪声 | torch.Tensor | 同输入 sample | 模型预测的噪声残差 | 用于 scheduler 计算下一步 |
可选输入:class_labels | torch.Tensor | (B,) | 类条件扩散模型使用 | 如 ImageNet 类别 |
可选输入:cross_attention_kwargs | dict | - | 传递额外注意力参数 | 如用于 ControlNet 的控制信号 |
| 设备一致性 | 所有输入必须在同一设备(CPU/GPU) | - | - | 否则会报错 |
| 数据类型 | 推荐 torch.float16(GPU)或 torch.float32 | - | - | 混合精度训练需注意 |
5.4 冻结与微调 UNet 层
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
freeze() | module.requires_grad_(False) | 冻结指定层 | # 冻结 attention 层
for attn in pipe.unet.attn_processors: attn.requires_grad_(False) | 减少训练参数量 |
unfreeze() | module.requires_grad_(True) | 解冻层用于训练 | pipe.unet.conv_out.requires_grad_(True) | 通常只训练部分层 |
| 只训练 attention | 使用 PEFT 或手动选择 | 微调 adapter | for name, param in pipe.unet.named_parameters(): if "attn" in name: param.requires_grad = True | LoRA 常用策略 |
| 使用 LoRA 微调 | 配合 peft 库插入低秩矩阵 | 高效微调 | from peft import LoraConfig, get_peft_model
config = LoraConfig(r=4, lora_alpha=32, target_modules=["to_q", "to_v"])
unet_lora = get_peft_model(pipe.unet, config) | 显存占用低,适合消费级 GPU |
| optimizer 设置 | 只传入 requires_grad 的参数 | 节省内存 | optimizer = torch.optim.AdamW(filter(lambda p: p.requires_grad, unet.parameters()), lr=1e-4) | 避免优化冻结层 |
| 查看可训练参数 | sum(p.numel() for p in model.parameters() if p.requires_grad) | 监控训练规模 | print(f"Trainable params: {num_trainable:,}") | 确保只训练目标层 |
| gradient checkpointing | pipe.unet.enable_gradient_checkpointing() | 降低显存占用 | pipe.unet.enable_gradient_checkpointing() | 会轻微降低速度 |
5.5 使用自定义 UNet 替换预训练模型中的 UNet
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
from_pretrained | UNet2DConditionModel.from_pretrained(...) | 加载 UNet 权重 | from diffusers import UNet2DConditionModel
custom_unet = UNet2DConditionModel.from_pretrained("path/to/unet") | 必须匹配架构 |
| 替换 pipeline 中的 UNet | pipe.unet = new_unet | 更新模型组件 | pipe.unet = custom_unet
pipe.unet.to("cuda") | 替换后需移动到相同设备 |
| 修改架构后加载部分权重 | 使用 ignore_mismatched_sizes | 兼容尺寸变化 | unet = UNet2DConditionModel.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="unet", ignore_mismatched_sizes=True) | 仅加载匹配层 |
| 保存自定义 UNet | unet.save_pretrained(save_dir) | 持久化模型 | custom_unet.save_pretrained("./my_unet") | 可重新加载 |
| 从 checkpoint 加载 | torch.load + load_state_dict | 加载非标准格式 | state_dict = torch.load("unet.ckpt")
pipe.unet.load_state_dict(state_dict) | 需匹配 key 名称 |
| 验证替换后功能 | 运行一次推理 | 确保无报错 | image = pipe("test", num_inference_steps=20).images[0] | 检查输出是否合理 |
| 架构兼容性要求 | 输入输出通道、条件接口必须一致 | - | - | 否则无法集成到 pipeline |
第六部分:变分自编码器(VAE)详解
第6章 变分自编码器(VAE)详解
6.1 VAE 的编码与解码过程
| 过程 | 说明 | 注意事项 |
|---|
| 编码(Encode) | 将 RGB 图像压缩为低维 latent 表示 | 输入为 [0,1] 归一化的 tensor |
| 解码(Decode) | 将 latent 表示还原为 RGB 图像 | 输出为 [0,1] 范围的图像 |
| Encoder 网络结构 | 多个卷积层 + Downsampling + 激活函数 | 输出 latent shape 通常为 (4,64,64) |
| Decoder 网络结构 | 多个卷积层 + Upsampling + 激活函数 | 对称结构,逐步恢复分辨率 |
| Latent Normalization | latent 值通常分布在 [-3,3] | 非标准正态,注意调度器适配 |
| 输入预处理 | 图像需缩放到 512x512 并归一化到 [0,1] | 使用 torchvision.transforms |
| 输出后处理 | 解码后使用 clamp 或 sigmoid 确保范围 | 防止数值溢出 |
| 推理模式使用 | 通常只使用 decode 方法生成图像 | encode 多用于 img2img 或训练 |
| KL-Divergence 层 | 在 latent 空间引入正则化(训练时) | 推理时忽略,直接使用均值 |
6.2 Latent Space 的理解与操作
| 概念名称 | 说明 | 注意事项 |
|---|
| Latent Vector | 图像在 VAE 编码后的低维表示 | 通常为 (B,4,64,64),比原图小 64 倍 |
| 潜在空间维度 | 通道数代表特征维度,空间尺寸为压缩后分辨率 | Stable Diffusion 中为 4x64x64 |
| 连续性 | 相似图像在 latent 空间中距离较近 | 支持插值和语义编辑 |
| 线性可分性 | 不同语义概念可能在 latent 空间中线性可分 | Textual Inversion 利用此特性 |
| 操作方式 | 可对 latent 进行加减、插值、掩码等操作 | 如 latents += direction * alpha |
| 插值(Interpolation) | 两个 latent 向量之间线性或球面插值 | 生成过渡图像序列 |
| 潜在空间正则化 | 训练时通过 KL loss 约束 latent 分布 | 推理时可忽略 |
| 潜在空间攻击 | 对 latent 添加扰动可改变生成结果 | 用于对抗样本研究 |
| 可视化 | 无法直接可视化,需通过 VAE 解码 | 所有操作最终需解码观察效果 |
6.3 VAE 的推理与训练模式差异
| 模式 | 行为差异 | 代码控制 | 注意事项 |
|---|
| 训练模式(train) | 启用 dropout、batchnorm 更新、输出均值和方差 | vae.train() | 用于微调 VAE |
| 推理模式(eval) | 固定归一化统计量,不使用 dropout | vae.eval() | 生成时必须使用 |
| 编码输出 | 训练时输出 latent_mean 和 latent_logvar | z = vae.encode(x).latent_dist.sample() | 推理时直接采样 |
| 重参数化技巧 | 仅在训练时使用 reparameterize | z = mean + eps * exp(0.5 * logvar) | 推理时可直接用均值 |
| KL Loss | 仅在训练时计算并加入总损失 | loss = recon_loss + kl_weight * kl_loss | 推理时不计算 |
| 数值稳定性 | 训练时需防止 logvar 过小 | logvar = torch.clamp(logvar, -30.0, 20.0) | 避免 exp(0.5*logvar) 溢出 |
| 梯度计算 | 训练时保留梯度 | requires_grad=True | 推理时可禁用 torch.no_grad() |
| 批量处理 | 训练时使用大 batch | dataloader + loop | 推理时通常 batch=1 |
6.4 使用 VAE 进行图像压缩与重建
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
encode | vae.encode(image).latent_dist.sample() | 图像压缩为 latent | from torchvision import transforms
transform = transforms.ToTensor()
image_tensor = transform(image).unsqueeze(0).to("cuda")
image_tensor = 2 * image_tensor - 1 # 归一化到 [-1,1]
latents = vae.encode(image_tensor).latent_dist.sample()
latents = latents * 0.18215 # 缩放因子 | 必须归一化并缩放 |
decode | vae.decode(latents / 0.18215).sample | latent 解码为图像 | latents = torch.randn(1, 4, 64, 64).to("cuda")
latents = latents / 0.18215
image_tensor = vae.decode(latents).sample
image_tensor = (image_tensor / 2 + 0.5).clamp(0, 1) # 转回 [0,1]
image = transforms.ToPILImage()(image_tensor[0]) | 注意缩放和 clamp |
| 缩放因子 | 0.18215 | Stable Diffusion VAE 特有 | 来自训练时的统计值 | 必须匹配,否则图像失真 |
| 重建误差 | MSE 或 L1 损失 | 评估 VAE 质量 | recon_loss = F.mse_loss(decoded, original) | 通常存在细节丢失 |
| 批量编码 | 支持 batch 处理 | vae.encode(img_batch).latent_dist.sample() | 提高处理效率 | |
| 设备一致性 | 图像和 VAE 必须在同一设备 | .to("cuda") | 否则报错 | |
| 输入范围 | 必须为 [-1,1] | 使用 (x * 2 - 1) 转换 | 原始图像为 [0,1] | |
6.5 处理 VAE 的数值溢出与后处理技巧
| 技巧/方法 | 操作细节 | 代码示例 | 注意事项 |
|---|
| 解码后 clamp | 限制像素值在 [0,1] 范围 | decoded = torch.clamp(decoded, 0, 1) | 防止生成无效颜色 |
使用 sample 而非 latent_dist.mode | 推荐采样而非取众数 | latents = vae.encode(x).latent_dist.sample() | 更稳定 |
输出后处理(+0.5) | 将 [-1,1] 转为 [0,1] | image = (decoded / 2 + 0.5).clamp(0, 1) | 标准做法 |
| 处理 NaN/Inf | 检查并替换异常值 | if torch.isnan(decoded).any(): decoded = torch.nan_to_num(decoded) | 避免图像全黑或花屏 |
| 缩放 latent 输入 | 使用预定义缩放因子 | latents = latents / 0.18215 # for SD | 不同模型因子不同 |
启用 vae.slicing | 降低显存占用 | pipe.enable_vae_slicing() | 适用于大 batch 或高分辨率 |
启用 vae.tiling | 分块处理大图像 | pipe.enable_vae_tiling() | 用于 >1024x1024 图像 |
| 使用 half 精度 | 减少显存 | vae.decoder = vae.decoder.half()
latents = latents.half() | 需 GPU 支持 FP16 |
| 异常捕获 | 包裹解码过程 | try: image = vae.decode(latents).sample
except RuntimeError as e: print("VAE error:", e) | 提高鲁棒性 |
第七部分:文本编码器与条件生成
第7章 文本编码器与条件生成
7.1 CLIP 文本编码器的作用
| 概念名称 | 说明 | 注意事项 |
|---|
| 多模态对齐 | CLIP(Contrastive Language-Image Pretraining)通过对比学习将文本和图像映射到同一语义空间 | 是 Stable Diffusion 实现文本控制的关键 |
| 文本编码功能 | 将输入提示词(prompt)转换为固定维度的嵌入向量(text embeddings) | 输出维度通常为 768 或 1024 |
| 双塔结构 | 包含独立的图像编码器和文本编码器,在训练时进行对比损失优化 | 推理时仅使用文本编码器 |
| Tokenization | 使用 BPE(Byte Pair Encoding)分词,支持开放词汇 | 最大长度为 77 tokens(含起止符) |
| Embedding Pooling | 对 token embeddings 进行池化或直接使用序列输出 | Diffusers 中通常保留完整序列 |
| 与 UNet 集成 | text embeddings 通过 cross-attention 注入 UNet 的 decoder 层 | 实现文本条件去噪 |
| 模型来源 | 通常来自 transformers 库,如 CLIPTextModel | 需与 tokenizer 配套使用 |
| 开源实现 | Hugging Face 提供多种 CLIP 模型(openai/clip-vit-base-patch32 等) | 可替换不同版本以优化效果 |
7.2 文本嵌入(text embeddings)的生成与使用
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| Tokenizer 编码 | tokenizer(prompt, return_tensors="pt", padding=True, truncation=True) | 将文本转为 token IDs | from transformers import CLIPTokenizer
tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
inputs = tokenizer("a cat", max_length=77, return_tensors="pt", padding="max_length", truncation=True) | 必须 padding="max_length" 以支持 batch |
| Text Encoder 推理 | text_encoder(inputs.input_ids.to(device)) | 生成文本嵌入 | from transformers import CLIPTextModel
text_encoder = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14").to(device)
embeddings = text_encoder(inputs.input_ids.to(device))[0] # [last_hidden_state] | 输出为 [batch, seq_len, hidden_size] |
| 负提示嵌入 | 分别编码正负提示 | 实现 classifier-free guidance | pos_inputs = tokenizer("cat", ...)
neg_inputs = tokenizer("", ...) # 空或"low quality"
pos_emb = text_encoder(pos_inputs.input_ids)
neg_emb = text_encoder(neg_inputs.input_ids) | 负提示常为空或负面描述 |
| 嵌入拼接 | torch.cat([neg_emb, pos_emb]) | 用于 CFG | cond_inputs = torch.cat([neg_emb, pos_emb])
noise_pred = unet(latents, t, encoder_hidden_states=cond_inputs) | batch 维度拼接 |
| 自定义嵌入输入 | 直接传入 pipeline | 绕过内置 tokenizer | pipe(prompt=None, text_embeddings=custom_emb, ...) | 需手动实现 |
| 截断与填充 | max_length=77 | 适配模型输入 | 超长文本会被截断 | 建议控制在 75 词以内 |
| 设备一致性 | 所有张量需在相同设备 | .to("cuda") | 否则报错 | |
7.3 支持长文本与负提示(negative prompt)的机制
| 机制 | 说明 | 注意事项 |
|---|
| Classifier-Free Guidance (CFG) | 同时预测有条件和无条件的噪声,通过加权差值增强提示相关性 | 公式:$\epsilon_{guided} = \epsilon_{uncond} + w \cdot (\epsilon_{cond} - \epsilon_{uncond})$ |
| Guidance Scale (w) | 控制条件引导强度 | 通常 7~12,过高导致过饱和或伪影 |
| Negative Prompt | 用户指定不希望出现的内容 | 如 “blurry, low quality, text” |
| 内部实现 | pipeline 自动编码正负提示并拼接 | 调用 UNet 时 batch size 翻倍 |
| 空字符串作为负条件 | 若未提供 negative_prompt,常使用空字符串 "" | 代表”无文本条件” |
| 批量处理 | 支持 per-sample 正负提示 | 可为每张图像设置不同负提示 |
| 长文本截断 | 超过 77 tokens 的文本会被截断 | 可使用文本重写或分块策略缓解 |
| 组合提示 | 使用逗号分隔多个描述 | 如 “a red cat, sitting on a chair, sunlight” |
7.4 使用自定义文本编码器
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 加载自定义模型 | CLIPTextModel.from_pretrained("path/to/model") | 使用微调后的文本编码器 | custom_text_encoder = CLIPTextModel.from_pretrained("./my_clip") | 架构需兼容 |
| 替换 pipeline 组件 | pipe.text_encoder = new_encoder | 集成到 pipeline | pipe.text_encoder = custom_text_encoder
pipe.text_encoder.to("cuda") | 需移动到相同设备 |
| 使用不同 tokenizer | CLIPTokenizer.from_pretrained("new_tokenizer") | 支持新词汇 | custom_tokenizer = CLIPTokenizer.from_pretrained("./my_tokenizer")
pipe.tokenizer = custom_tokenizer | 必须匹配 |
| 注册新模块 | pipe.register_modules(text_encoder=encoder, tokenizer=tokenizer) | 动态替换多个组件 | pipe.register_modules( text_encoder=custom_text_encoder, tokenizer=custom_tokenizer
) | 推荐方式 |
| 保存自定义 pipeline | pipe.save_pretrained("./custom_pipe") | 持久化 | pipe.save_pretrained("./custom_pipe") | 可重新加载 |
| 验证替换效果 | 生成图像测试 | 确保功能正常 | image = pipe("test").images[0] | 检查是否崩溃或输出异常 |
| 架构兼容性 | 输入输出维度必须匹配 | hidden_size=768, max_length=77 | 否则无法集成 | |
7.5 多条件生成(如文本+图像)
| 条件类型 | 实现方式 | 用途 | 注意事项 |
|---|
| 文本 + 图像(img2img) | 使用 StableDiffusionImg2ImgPipeline | 基于原图生成新图 | 通过 strength 控制变化程度 |
| 文本 + 深度图 | 使用 StableDiffusionDepth2ImgPipeline | 保持几何结构 | 需预估深度图 |
| 文本 + 边缘图 | 使用 Canny Edge + ControlNet | 精确控制轮廓 | 见 8.5 节 |
| 文本 + 姿势图 | 使用 OpenPose + ControlNet | 控制人物姿态 | 适用于角色生成 |
| 文本 + 语义分割图 | 使用 segmentation mask | 控制布局 | 如建筑物、天空位置 |
| 多模态嵌入拼接 | 将图像特征与文本嵌入拼接 | 实验性方法 | 需对齐维度 |
| Cross-Attention 注入 | 在 UNet 中添加额外 attention 层 | 融合多条件 | ControlNet 的实现方式 |
| Timestep 调制 | 根据条件调整时间步 | 动态控制生成过程 | 高级技巧 |
| 条件权重控制 | 为不同条件设置权重 | 平衡影响 | 如 controlnet_conditioning_scale |
| Pipeline 选择 | 根据任务选择专用 pipeline | 如 TextToImagePipeline, ImageToImagePipeline | 官方支持有限,需扩展 |
第八部分:图像生成高级技巧
第8章 图像生成高级技巧
8.1 提示词工程(Prompt Engineering)最佳实践
| 技巧 | 说明 | 示例 | 注意事项 |
|---|
| 明确主体 | 清晰描述主体对象 | ”a golden retriever” | 避免模糊词汇 |
| 添加风格 | 指定艺术风格或媒介 | ”oil painting”, “photorealistic”, “anime style” | 显著影响视觉效果 |
| 描述环境 | 包含场景和背景 | ”in a sunny park”, “on Mars” | 增强画面丰富度 |
| 光照条件 | 指定光源类型 | ”soft lighting”, “dramatic shadows”, “backlit” | 影响氛围 |
| 构图与视角 | 描述镜头和角度 | ”wide-angle shot”, “close-up”, “from above” | 控制画面布局 |
| 质量词 | 提升图像质量 | ”high resolution”, “8K”, “sharp focus” | 减少模糊或伪影 |
| 避免负面词 | 显式排除不希望的内容 | negative prompt: “blurry, deformed, text” | 提升整体质量 |
| 使用逗号分隔 | 结构化提示 | ”a cat, sitting on a windowsill, sunlight, detailed fur” | 更易解析 |
| 权重语法 | 强调关键元素 | (cat:1.2), [cat](降低) | Diffusers 支持 (word:weight) |
| 迭代优化 | 多次生成并调整提示 | 逐步细化描述 | 实践中最重要的技巧 |
8.2 使用引导(guidance scale)控制生成质量
| 参数名 | 说明 | 推荐值 | 注意事项 |
|---|
guidance_scale | 控制文本条件引导强度 | 7.0 ~ 12.0 | 默认 7.5 |
| 低值(<5) | 生成更随机,多样性高,但可能偏离提示 | 3~5 | 适合创意探索 |
| 中值(7~9) | 平衡保真度与多样性 | 7.5(默认) | 大多数场景适用 |
| 高值(10~15) | 更贴近提示,细节更丰富 | 10~12 | 可能出现过饱和或伪影 |
| 极高值(>15) | 可能导致图像失真或崩溃 | 不推荐 | 特别在低步数时 |
| 与步数关系 | 低步数采样需降低 guidance_scale | 如 DPM++ 25 步 → scale=7~9 | 避免过度引导 |
| 动态调节 | 在生成过程中变化 scale | 实验性 | 可结合 callback 实现 |
| 负面提示协同 | 高 guidance_scale 配合负面提示效果更好 | 如 “low quality, blurry” | 提升整体质量 |
| 数值稳定性 | 过高可能导致 latent 溢出 | 观察解码是否报错 | 可尝试降低或使用 clamp |
8.3 多步生成与图像插值(image interpolation)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 固定初始噪声 | 设置 generator 和 seed | 复现生成结果 | g = torch.Generator("cuda").manual_seed(42)
image = pipe("cat", generator=g).images[0] | 相同 seed 产生相同图像 |
| 图像插值(Latent Space) | 线性插值两个 latent 向量 | 生成过渡图像 | latents_a = torch.randn(1,4,64,64)
latents_b = torch.randn(1,4,64,64)
for i in range(5): alpha = i / 4.0 inter_latents = latents_a * (1-alpha) + latents_b * alpha image = pipe(prompt, latents=inter_latents).images[0] | 需禁用随机噪声 |
| 球面插值(Slerp) | 更自然的插值方式 | 避免中间模糊 | def slerp(t, v0, v1): ... # 实现球面插值
inter = slerp(alpha, latents_a, latents_b) | 数学上更合理 |
| Prompt Interpolation | 插值两个文本嵌入 | 语义过渡 | emb1 = encode("cat")
emb2 = encode("dog")
inter_emb = emb1 * (1-alpha) + emb2 * alpha
image = pipe(prompt=None, text_embeddings=inter_emb).images[0] | 需手动编码 |
| 多阶段生成 | 分步生成,逐步细化 | 如先生成草图再细化 | 先用低分辨率生成,再用 upscaler | 需多 pipeline 协作 |
| 使用 DDIM 进行确定性插值 | DDIM 支持任意起点终点 | 图像混合 | 使用 DDIMScheduler 和固定噪声 | 可实现图像 morphing |
8.4 图像修复(inpainting)与局部编辑
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
StableDiffusionInpaintPipeline | from_pretrained("runwayml/stable-diffusion-inpainting") | 专用修复 pipeline | from diffusers import StableDiffusionInpaintPipeline
pipe = StableDiffusionInpaintPipeline.from_pretrained("runwayml/stable-diffusion-inpainting").to("cuda") | 模型专门训练 |
| mask 输入 | PIL.Image 或 tensor | 指定需编辑区域 | 白色(255)为重绘区,黑色为保留区 | 形状与图像相同 |
image + mask 输入 | pipe(image=image, mask_image=mask, prompt=new_prompt) | 局部重绘 | result = pipe( prompt="a man with sunglasses", image=init_image, mask_image=mask, num_inference_steps=50
) | 保持上下文一致性 |
| 边缘对齐 | mask 边缘应柔和或扩展 | 避免明显边界 | 可用高斯模糊处理 mask | 减少接缝 |
| 多次修复 | 分区域逐步编辑 | 复杂场景修改 | 每次修复一小块 | 避免全局失真 |
| prompt 设计 | 新提示应与原图协调 | 如只改衣服颜色 | ”red shirt” 而非完整描述 | 保持其他元素不变 |
| 尺寸要求 | 图像和 mask 必须为 512x512 或 8 的倍数 | 否则自动 resize 可能变形 | 建议预处理 | |
8.5 控制生成(ControlNet 集成)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 加载 ControlNet | ControlNetModel.from_pretrained("lllyasviel/control_v11p_sd15_canny") | 加载控制模块 | from diffusers import ControlNetModel
controlnet = ControlNetModel.from_pretrained("lllyasviel/control_v11p_sd15_canny", torch_dtype=torch.float16) | 多种类型可选 |
| 使用 ControlNetPipeline | StableDiffusionControlNetPipeline | 集成 ControlNet | from diffusers import StableDiffusionControlNetPipeline
pipe = StableDiffusionControlNetPipeline.from_pretrained(..., controlnet=controlnet).to("cuda") | 需传入 controlnet |
| 条件图像输入 | canny_map, depth_map, pose_map 等 | 提供结构控制 | pipe(image=canny_map, prompt="house", controlnet_conditioning_scale=0.8) | 图像需预处理 |
| 多 ControlNet | 列表传入多个 controlnet | 组合控制 | pipe = StableDiffusionControlNetPipeline(..., controlnet=[canny, depth])
images = pipe(image=[canny_img, depth_img], ...).images | 支持多条件 |
conditioning_scale | float 或 List[float] | 控制影响强度 | scale=0.5~1.0 | 过高导致僵硬 |
| 预处理工具 | 使用 OpenCV、Hough 等生成控制图 | 如 Canny 边缘 | import cv2
edges = cv2.Canny(image, 100, 200) | 需转换为 PIL |
| 支持的 ControlNet 类型 | canny, depth, hed, normal, pose, scribble 等 | 不同控制信号 | 根据任务选择 | 模型需匹配 |
| 训练自己的 ControlNet | 微调 controlnet 权重 | 适配特定数据 | 使用 train_controlnet.py 脚本 | 需大量标注数据 |
第九部分:模型微调(Fine-tuning)技术
第9章 模型微调(Fine-tuning)技术
9.1 DreamBooth 微调原理与实现
| 概念/方法 | 说明 | 代码示例 | 注意事项 |
|---|
| 核心思想 | 通过少量图像(3~5 张)微调整个扩散模型,将新概念(如特定人物或物体)注入模型 | 使用唯一标识符(如 sks)绑定新概念 | - |
| 唯一标识符(Identifier) | 一个罕见词(如 sks)用于代表新概念 | 在提示中使用 sks dog 表示微调后的对象 | 避免常用词,防止语义冲突 |
| 先验保留损失(Prior Preservation Loss) | 防止语言漂移,同时训练新概念和类别先验(如 “dog”) | 鼓励模型保留原始语义 | 必须使用 class_prompt 和 num_class_images |
| 训练流程 | 冻结 VAE 和 UNet 大部分层,仅微调 UNet,可选使用 LoRA | 通常训练 800~2000 步 | 需要较高显存 |
| 输入数据 | 每张图像需对应一个文本描述,格式为 "a [identifier] [class]" | 如 "a sks dog" | 描述应一致 |
| 超参数设置 | 学习率 1e-6 ~ 2e-6,AdamW 优化器,batch size 1~2 | 使用梯度累积 | 显存不足时降低 batch |
| 代码实现(Hugging Face) | 使用 train_dreambooth.py 脚本 | 官方提供训练脚本 | accelerate launch train_dreambooth.py --pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" ... |
| 输出结果 | 微调后的模型可生成 sks dog in space 等新场景 | 概念泛化能力强 | 可能过拟合若数据太少 |
9.2 Textual Inversion 概念与训练流程
| 概念/方法 | 说明 | 代码示例 | 注意事项 |
|---|
| 核心思想 | 不修改模型权重,而是学习一个新词的嵌入向量(embedding)来表示新概念 | 将新概念编码为 token embedding | - |
| 嵌入空间学习 | 在 CLIP 文本编码器的 token embedding 空间中优化一个新向量 | 新 token 如 <my-concept> | 维度为 768 |
| 训练目标 | 最小化重建损失,使生成图像匹配输入图像 | 使用先验保留损失防止漂移 | 与 DreamBooth 类似 |
| Token 初始化 | 可随机初始化,或基于类别词(如 “dog”)的 embedding 微调 | initializer_token="dog" | 更快收敛 |
| 训练流程 | 固定扩散模型,仅优化一个 embedding 向量 | 显存占用极低(<5GB) | 适合消费级 GPU |
| 代码实现 | 使用 textual_inversion.py 脚本 | accelerate launch textual_inversion.py --learnable_property="object" ... | 支持 object/style 学习 |
| 使用方式 | 训练后保存 .bin 文件,在推理时加载 | pipe.load_textual_inversion("my_concept.bin") | 可组合多个概念 |
| 局限性 | 表达能力弱于 DreamBooth 或 LoRA | 仅学习 embedding,不调整模型结构 | 复杂概念可能学不准 |
9.3 LoRA(Low-Rank Adaptation)微调方法
| 概念/方法 | 说明 | 代码示例 | 注意事项 |
|---|
| 核心思想 | 在原始权重旁添加低秩矩阵($A \cdot B$),仅训练 A 和 B | 参数量减少 90%+,高效微调 | - |
| 数学原理 | $\Delta W = A \times B$,其中 $A \in \mathbb{R}^{d \times r}, B \in \mathbb{R}^{r \times k}, r \ll d,k$ | $r$ 为秩(rank),通常 4~64 | 显著减少训练参数 |
| 注入位置 | 通常应用于 UNet 中的 to_q, to_v, to_k, to_out 等 attention 层 | 也可用于 FFN 层 | 可配置 target_modules |
| 训练过程 | 冻结原始模型,仅训练 LoRA 参数 | 显存占用低,支持消费级 GPU | 可结合梯度检查点 |
| 保存与加载 | 仅保存 LoRA 权重(几 MB~100MB),而非完整模型 | lora.save_pretrained("my_lora") | 可跨基础模型使用 |
| 推理集成 | 加载 LoRA 权重后,自动注入到 UNet | pipe.unet.load_attn_procs("my_lora") | 无需修改 pipeline |
| 多 LoRA 组合 | 可叠加多个 LoRA 模块 | 如风格 + 人物 | 使用 alpha 控制权重 |
| 代码实现 | 使用 peft 库 | from peft import LoraConfig, get_peft_model | Hugging Face 原生支持 |
9.4 使用 PEFT 进行高效微调
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
LoraConfig | LoraConfig(..., r=4, lora_alpha=32, target_modules=["to_q", "to_v"]) | 定义 LoRA 配置 | from peft import LoraConfig
config = LoraConfig( r=4, lora_alpha=32, target_modules=["to_q", "to_v"], lora_dropout=0.0, bias="none"
) | r 越大表达能力越强 |
get_peft_model | get_peft_model(model, config) | 包装模型以支持 PEFT | unet_lora = get_peft_model(pipe.unet, config) | 返回可训练的 PEFT 模型 |
print_trainable_parameters | 打印可训练参数数量 | 监控训练规模 | def print_trainable_parameters(model): trainable = sum(p.numel() for p in model.parameters() if p.requires_grad) total = sum(p.numel() for p in model.parameters()) print(f"Trainable: {trainable}, %: {100 * trainable / total:.2f}") | 确保只训练 LoRA |
save_pretrained | model.save_pretrained(save_dir) | 保存 PEFT 模型 | unet_lora.save_pretrained("./lora_weights") | 仅保存适配器 |
load_peft_model | PeftModel.from_pretrained(model, "./lora_weights") | 加载 PEFT 模型 | unet_loaded = PeftModel.from_pretrained(pipe.unet, "./lora_weights") | 需原始模型 |
enable_gradient_checkpointing | model.enable_gradient_checkpointing() | 降低显存 | unet_lora.enable_gradient_checkpointing() | 训练时使用 |
| 支持的 PEFT 方法 | LoRA, IA³, AdaLora, Prefix Tuning 等 | 不同高效微调方法 | PEFT 支持多种 | LoRA 最常用 |
9.5 微调过程中的数据准备与训练配置
| 项目 | 说明 | 推荐配置 | 注意事项 |
|---|
| 图像数量 | DreamBooth: 310 张;Textual Inversion: 35 张;LoRA: 10~50 张 | 质量优于数量 | 避免重复或低质图像 |
| 图像质量 | 高分辨率(512x512),清晰,主体居中 | JPG/PNG 格式 | 可缩放但避免模糊 |
| 文本标注 | 每张图像对应一个 prompt,格式 "a [identifier] [class]" | 如 "a sks dog" | 保持一致性 |
| 类别先验图像 | 用于 prior preservation loss | 自动生成 200~300 张同类图像 | 如 “dog” 的通用图像 |
| 训练轮数(Steps) | DreamBooth: 8002000;LoRA: 10005000 | 根据数据量调整 | 过多导致过拟合 |
| 学习率 | AdamW: 1e-6 (DreamBooth), 1e-4 (LoRA) | 使用线性衰减或 cosine | 初始值敏感 |
| Batch Size | 1~4,取决于显存 | 使用梯度累积模拟大 batch | 如 gradient_accumulation_steps=4 |
| 优化器 | AdamW 或 AdamW8bit(节省显存) | bitsandbytes 支持 8-bit 优化器 | 推荐使用 |
| 数据增强 | 一般不推荐 | 可能破坏概念一致性 | 保持原始视角和光照 |
| 验证方式 | 定期生成图像检查效果 | 如每 100 步生成 sks dog in space | 观察是否过拟合或漂移 |
第十部分:模型部署与优化
第10章 模型部署与优化
10.1 将 Diffusers 模型导出为 ONNX 或 TorchScript
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| ONNX Export | diffusers.onnx_export | 导出为 ONNX 格式 | from diffusers import OnnxStableDiffusionPipeline
OnnxStableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", provider="CPUExecutionProvider").save_pretrained("onnx/") | 支持 CPU/GPU 推理 |
| TorchScript | torch.jit.trace 或 script | 导出为 TorchScript | traced_model = torch.jit.trace(unet, example_inputs)
torch.jit.save(traced_model, "unet.pt") | 需固定输入形状 |
| 动态轴处理 | ONNX 中设置 dynamic_axes | 支持变长输入 | dynamic_axes={"sample": {0: "batch", 2: "height", 3: "width"}} | 否则输入固定 |
| 子模块导出 | 可分别导出 VAE、UNet、Text Encoder | 灵活部署 | 逐个模块导出并组合 | 注意接口对齐 |
| 依赖安装 | onnx, onnxruntime | 运行 ONNX 模型 | pip install onnx onnxruntime | GPU 需 onnxruntime-gpu |
| 精度问题 | FP16 可能导致数值误差 | 导出时指定 torch.float16 | 测试输出一致性 | |
| 不支持操作 | 某些 PyTorch 操作无法导出 | 如部分 control flow | 需重写或替换 | |
| 验证导出模型 | 加载并运行推理 | 确保输出合理 | 与原始模型对比 | |
10.2 使用 Diffusers + Gradio 构建 Web 演示
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| Gradio Interface | gr.Interface(fn, inputs, outputs) | 创建交互界面 | import gradio as gr
def generate(prompt): image = pipe(prompt).images[0] return image
gr.Interface(fn=generate, inputs="text", outputs="image").launch() | 快速原型 |
| 自定义组件 | gr.Textbox, gr.Slider, gr.Image | 精细化控制 | inputs = [ gr.Textbox(label="Prompt"), gr.Slider(minimum=1, maximum=50, value=20, label="Steps")
] | 提升用户体验 |
| 并发控制 | concurrency_count | 限制同时请求数 | .launch(concurrency_count=2) | 防止显存溢出 |
| 队列机制 | queue() | 异步处理请求 | .launch().queue() | 适合高延迟模型 |
| 加载模型缓存 | 全局加载一次 | 避免重复加载 | pipe = DiffusionPipeline.from_pretrained(...).to("cuda") | 放在函数外 |
| 错误处理 | try-except 包裹生成函数 | 提升鲁棒性 | try: ... except Exception as e: return str(e) | 避免崩溃 |
| 部署到 Spaces | 推送到 Hugging Face | 免费托管 | git push 到 Spaces 仓库 | 支持 Docker |
| 性能优化 | 启用 fp16, xformers | 加快生成速度 | pipe.enable_attention_slicing()
pipe.enable_xformers_memory_efficient_attention() | 必要时使用 |
10.3 模型量化与推理加速(FP16, INT8)
| 技术 | 说明 | 代码示例 | 注意事项 |
|---|
| FP16 推理 | 使用半精度浮点数 | 显存减半,速度提升 | pipe = pipe.to(torch.float16)
pipe.unet.half() # 确保所有模块为 fp16 |
| xFormers | 内存高效注意力 | 降低显存占用 | pipe.enable_xformers_memory_efficient_attention() |
| Attention Slicing | 分块计算注意力 | 降低峰值显存 | pipe.enable_attention_slicing() |
| VAE Slicing | 分块解码 | 处理大 batch | pipe.enable_vae_slicing() |
| VAE Tiling | 分块处理超大图像 | 支持 >1024x1024 | pipe.enable_vae_tiling() |
| INT8 量化 | 使用 8 位整数 | 极大降低显存 | 使用 transformers 的 load_in_8bit=True |
| 模型剪枝 | 移除冗余权重 | 实验性 | 需重新训练或微调 |
| TensorRT | NVIDIA 高性能推理 | 极致优化 | 使用 torch2trt 或官方工具 |
10.4 分布式推理与批量生成优化
| 方法 | 说明 | 代码示例 | 注意事项 |
|---|
| 批量生成 | 一次生成多张图像 | 提高吞吐量 | prompts = ["cat", "dog", "bird"]
images = pipe(prompts).images |
| 动态批处理 | 服务器端合并请求 | 提升 GPU 利用率 | 需自定义服务框架 |
| 模型并行 | 将模型分片到多 GPU | 处理大模型 | pipe.to("cuda:0"), unet.to("cuda:1") |
| 数据并行 | 多 GPU 同时处理不同 batch | 常用于训练 | 推理中较少用 |
| 异步生成 | 使用线程或协程 | 提高响应速度 | async def generate(...) |
| 内存优化 | 启用 slicing, tiling, fp16 | 降低单次消耗 | 组合使用 |
| 缓存机制 | 缓存常用 prompt 的 latent | 加快响应 | 适用于固定内容生成 |
| 负载均衡 | 多实例部署 + 负载均衡器 | 支持高并发 | 如 Nginx + 多个 Gradio 实例 |
10.5 部署到 Hugging Face Spaces 或云平台
| 平台 | 部署方式 | 优点 | 注意事项 |
|---|
| Hugging Face Spaces | Git 推送代码(Gradio/Streamlit) | 免费,集成模型仓库 | 免费实例有休眠限制 |
| GitHub + Docker | 编写 Dockerfile 并部署 | 灵活控制环境 | 需自行托管 |
| AWS SageMaker | 使用 Hugging Face DLC 镜像 | 企业级,可扩展 | 成本较高 |
| Google Cloud Run | 容器化部署 | 自动扩缩容 | 适合轻量服务 |
| Azure ML | 集成 MLOps | 企业支持 | 复杂配置 |
| Replicate | 上传模型即服务 | 简单,按调用付费 | 平台抽成 |
| 配置文件 | app.py, requirements.txt, Dockerfile, README.md | 标准化部署 | 必须包含依赖 |
| 环境变量 | 设置 HF_TOKEN 等 | 用于下载私有模型 | 安全存储 |
| 冷启动优化 | 预加载模型 | 减少首次延迟 | 在 app.py 中全局加载 |
| 监控与日志 | 添加日志输出 | 便于调试 | 记录生成时间、错误等 |
第十一部分:自定义扩散模型开发
第11章 自定义扩散模型开发
11.1 从零构建一个简单的 DDPM 模型
| 概念/组件 | 说明 | 注意事项 |
|---|
| UNet 去噪网络 | 主干网络,预测噪声 | 输入:$x_t$, $t$;输出:噪声残差 |
| 时间步嵌入(Timestep Embedding) | 将时间步 $t$ 编码为向量 | 使用正弦位置编码或 MLP |
| 正向扩散过程 | 固定过程:逐步添加高斯噪声 | $x_t = \sqrt{\bar{\alpha}_t} x_0 + \sqrt{1 - \bar{\alpha}_t} \epsilon$,$\alpha_t$ 预定义 |
| 反向去噪过程 | 学习从 $x_t$ 恢复 $x_{t-1}$ | UNet 预测噪声 $\epsilon_\theta(x_t, t)$ |
| 模型定义 | 自定义 DDPMUNet 类 | 继承 nn.Module,支持时间步输入 |
| 输入输出 | $x$: [B,C,H,W], $t$: [B],输出预测噪声 | 归一化输入到 [-1,1] |
| 损失函数 | 均方误差(MSE) | $F.mse_loss(noise_pred, noise_true)$ |
简单 DDPM UNet 示例:
import torch
import torch.nn as nn
class DDPMUNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 64, 3, padding=1)
self.time_embed = nn.Embedding(1000, 64)
self.relu = nn.ReLU()
self.conv2 = nn.Conv2d(64, 3, 3, padding=1)
def forward(self, x, t):
h = self.relu(self.conv1(x))
t_emb = self.time_embed(t).view(-1, 64, 1, 1)
h = h + t_emb # 简单融合
return self.conv2(h)
11.2 自定义训练循环与损失函数
| 方法名称 | 语法 | 用途 | 注意事项 |
|---|
| 训练循环结构 | for epoch in range(epochs): for batch in dataloader: | 控制训练流程 | 实现完整训练流程 |
| 噪声调度 | 预计算 $\alpha_t$, $\bar{\alpha}_t$ | 用于正向扩散 | betas = torch.linspace(1e-4, 0.02, T) |
| 损失变体 | L1、Huber、VLB | 替代 MSE | loss = F.smooth_l1_loss(...),Huber 更鲁棒 |
| 梯度裁剪 | torch.nn.utils.clip_grad_norm_ | 稳定训练 | 防止梯度爆炸 |
| 学习率调度 | torch.optim.lr_scheduler | 动态调整 LR | scheduler = StepLR(optimizer, step_size=10, gamma=0.9) |
| 模型保存 | torch.save(model.state_dict(), path) | 持久化训练结果 | 包含 model, optimizer, epoch |
| 验证与可视化 | 定期生成图像 | 监控训练效果 | 使用 no_grad() |
训练循环示例:
betas = torch.linspace(1e-4, 0.02, T) # 常用线性或余弦调度
alphas = 1 - betas
alpha_bars = torch.cumprod(alphas, dim=0)
model.train()
for x, _ in train_loader:
x = x.to(device) * 2.0 - 1.0 # [0,1] -> [-1,1]
t = torch.randint(0, T, (x.size(0),), device=device)
noise = torch.randn_like(x)
x_t = sqrt_alpha_bar[t] * x + sqrt_one_minus_alpha_bar[t] * noise
noise_pred = model(x_t, t)
loss = F.mse_loss(noise_pred, noise)
optimizer.zero_grad()
loss.backward()
optimizer.step()
11.3 使用 Diffusers 训练自定义数据集
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 数据加载 | torchvision.datasets.ImageFolder | 加载本地图像 | from torchvision import transforms
dataset = ImageFolder("path/to/data", transform=transforms.Compose([ transforms.Resize((512, 512)), transforms.ToTensor() ])
)
dataloader = DataLoader(dataset, batch_size=4, shuffle=True) | 图像需为 .jpg 或 .png |
| Diffusers 训练脚本 | train_text_to_image.py | 官方训练脚本 | accelerate launch train_text_to_image.py --dataset_name "my_dataset" ... | 支持多种任务 |
| 文本标注 | 使用 caption.txt 或 metadata.jsonl | 提供 prompt | 每张图像对应一行文本 | 格式必须正确 |
| Tokenizer 编码 | tokenizer(prompt, padding="max_length") | 批量编码文本 | input_ids = tokenizer(prompts, max_length=77, padding="max_length", return_tensors="pt").input_ids | 与模型匹配 |
| 训练配置 | TrainingArguments | 设置超参数 | args = TrainingArguments( output_dir="output", per_device_train_batch_size=4, num_train_epochs=10, learning_rate=1e-4, report_to="tensorboard"
) | 使用 accelerate 配置分布式 |
| 模型初始化 | UNet2DModel 或 UNet2DConditionModel | 根据任务选择 | unet = UNet2DConditionModel.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="unet") | 可加载预训练权重 |
| 分布式训练 | Accelerator | 多 GPU/TPU 训练 | from accelerate import Accelerator
accelerator = Accelerator(mixed_precision="fp16")
unet, optimizer, dataloader = accelerator.prepare(unet, optimizer, dataloader) | 推荐使用 |
| 日志与监控 | TensorBoard, WandB | 跟踪训练过程 | accelerator.log({"loss": loss}) | 便于调试 |
11.4 实现新的调度算法
| 调度算法 | 说明 | 代码示例(关键逻辑) | 注意事项 |
|---|
继承 SchedulerMixin | 所有调度器需继承该类 | from diffusers import SchedulerMixin | 实现必要接口 |
| 关键方法 | step(), add_noise(), set_timesteps() | 定义扩散行为 | - |
| 自定义调度器 | 如 MyCustomScheduler | 实现所有抽象方法 | - |
| 注册调度器 | 添加到 configuration.json | 使其可被 from_pretrained 加载 | 或手动传入 pipeline |
| 数值稳定性 | 避免除零或溢出 | 使用 clamp 或 log 技巧 | 特别在计算 $\sigma$ 时 |
| 测试新调度器 | 在 pipeline 中替换 | pipe.scheduler = MyCustomScheduler() | 验证生成效果 |
| 支持动态步数 | set_timesteps 应支持任意 num_inference_steps | 用于不同采样步数 | 提升灵活性 |
自定义调度器示例:
class MyCustomScheduler(SchedulerMixin):
def __init__(self, num_train_timesteps=1000):
super().__init__()
self.num_train_timesteps = num_train_timesteps
# 定义 beta, alpha, etc.
def add_noise(self, original_samples, noise, timesteps):
# 实现正向扩散
return noisy_samples
def step(self, model_output, timestep, sample):
# 实现单步去噪
prev_sample = ...
return prev_sample
def set_timesteps(self, num_inference_steps):
# 设置推理时间步
self.timesteps = torch.linspace(0, self.num_train_timesteps - 1, num_inference_steps).long()
11.5 扩展 Pipeline 支持新任务(如超分、去噪)
| 任务类型 | 实现方式 | 代码示例 | 注意事项 |
|---|
| 超分辨率(Super-Resolution) | 使用 DDPMSuperResPipeline 或自定义 | from diffusers import DDPMSuperResPipeline
pipe = DDPMSuperResPipeline.from_pretrained("google/ddpm-cifar10-32")
low_res_img = Image.open("low_res.png")
high_res_img = pipe(image=low_res_img).images[0] | 需训练超分扩散模型 |
| 图像去噪(Denoising) | 将噪声图作为输入,反向生成干净图 | 类似 DDPM 推理 | 假设噪声水平已知 |
| 自定义 Pipeline 类 | 继承 DiffusionPipeline | 必须调用 register_modules | - |
| 注册模块 | register_modules(**kwargs) | 管理可序列化组件 | 便于 save_pretrained |
| 保存与加载 | save_pretrained(), from_pretrained() | 持久化 pipeline | 所有模块需支持 |
| 输入输出设计 | 明确定义 __call__ 接口 | 如 image, mask, condition | 提升易用性 |
| 与现有组件集成 | 复用 UNet、VAE、Scheduler | 避免重复造轮子 | 推荐方式 |
自定义 Pipeline 类示例:
from diffusers import DiffusionPipeline
class MyTaskPipeline(DiffusionPipeline):
def __init__(self, unet, vae, scheduler):
super().__init__()
self.register_modules(unet=unet, vae=vae, scheduler=scheduler)
def __call__(self, input_data, num_inference_steps=50):
self.scheduler.set_timesteps(num_inference_steps)
sample = input_data
for t in self.scheduler.timesteps:
... # 去噪循环
return sample
第十二部分:生态集成与扩展
第12章 生态集成与扩展
| 集成方式 | 说明 | 代码示例 | 注意事项 |
|---|
| 共享 tokenizer | 使用 CLIPTokenizer | from transformers import CLIPTokenizer | Diffusers 内部使用相同实现 |
| 文本编码器 | CLIPTextModel | text_encoder = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14") | 可替换为其他文本模型 |
| 跨库模型加载 | 直接传入 pipeline | pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", text_encoder=text_encoder) | 架构需兼容 |
| 多模态任务 | 结合 NLP 模型生成 prompt | 如使用 T5 或 BART 生成描述 | 实现文本到图像的端到端 |
| 特征提取 | 使用 transformers 模型提取图像/文本特征 | 用于损失计算或条件输入 | 如 CLIP score |
| 分词一致性 | 确保 tokenizer 配对 | clip-vit 配 clip tokenizer | 避免编码错误 |
| 设备同步 | 所有模型在同一设备 | .to("cuda") | 否则报错 |
| 保存统一模型 | save_pretrained 保存整个 pipeline | 包含所有组件 | 便于共享 |
12.2 与 Accelerate 库协同进行分布式训练
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 初始化 Accelerator | Accelerator() | 管理分布式状态 | from accelerate import Accelerator
accelerator = Accelerator(mixed_precision="fp16", gradient_accumulation_steps=2) | 自动处理设备、精度、分布式 |
| 准备模型与优化器 | accelerator.prepare() | 包装并分发 | model, optimizer, dataloader = accelerator.prepare(model, optimizer, dataloader) | 必须使用返回对象 |
| 梯度累积 | 内置支持 | 模拟大 batch | 设置 gradient_accumulation_steps | 减少 optimizer.step() 频率 |
| 混合精度训练 | mixed_precision="fp16" | 加速并省显存 | 在 Accelerator 中启用 | 自动处理缩放 |
| 模型保存 | accelerator.save() | 正确保存模型 | accelerator.save(model.state_dict(), "model.pth") | 避免多卡重复保存 |
| 日志记录 | accelerator.log() | 统一记录 | accelerator.log({"loss": loss}, step=global_step) | 支持 TensorBoard/WandB |
| 分布式数据加载 | accelerator.prepare(dataloader) | 自动分片数据 | 每个进程获取子集 | 无需手动 DistributedSampler |
| 多节点训练 | 使用 accelerate launch | 跨多台机器 | accelerate launch --num_machines=2 train.py | 需配置网络 |
12.3 使用 Diffusers Hub 模型与上传自定义模型
| 操作 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 下载模型 | from_pretrained() | 加载 Hub 模型 | pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5") | 需登录 huggingface-cli login |
| 上传模型 | push_to_hub() | 发布模型 | pipe.push_to_hub("my-sd-model", commit_message="Initial release") | 需有仓库写权限 |
| 创建仓库 | create_repo() | 初始化远程仓库 | from huggingface_hub import create_repo
create_repo("my-sd-model", private=False) | 可设为私有 |
| 上传配置文件 | save_pretrained() | 保存并上传 | pipe.save_pretrained("./my_model")
pipe.push_to_hub("my-sd-model") | 自动上传 config.json 等 |
| 私有模型 | private=True | 限制访问 | pipe.push_to_hub("my-secret-model", private=True) | 仅自己可见 |
| 模型版本控制 | Git 提交历史 | 管理迭代 | 每次 push 生成新 commit | 支持 revert |
| 缓存管理 | --local_files_only | 强制使用本地缓存 | from_pretrained(..., local_files_only=True) | 离线环境使用 |
| 安全令牌 | use_auth_token | 访问私有模型 | from_pretrained(..., use_auth_token=True) | 或设置 HF_TOKEN 环境变量 |
12.4 社区模型与第三方扩展(如 ControlNet, T2I-Adapter)
| 扩展名称 | 用途 | 加载方式 | 注意事项 |
|---|
| ControlNet | 条件控制(边缘、深度、姿态等) | ControlNetModel.from_pretrained("lllyasviel/control_v11p_sd15_canny") | 需与 pipeline 集成 |
| T2I-Adapter | 轻量级条件适配器 | T2IAdapter.from_pretrained("TencentARC/t2i-adapter-style-sketch-sd15v1") | 参数量小,易训练 |
| IP-Adapter | 图像提示控制 | IPAdapter.from_pretrained(pipe, "h94/IP-Adapter", subfolder="models") | 实现图像到图像的语义控制 |
| DeepFloyd IF | 分阶段文本到图像 | 使用 IFPipeline | 支持超高分辨率 |
| Kandinsky | 多模态扩散模型 | KandinskyPipeline | 支持潜在插值 |
| AnimateDiff | 生成视频 | 结合 UNet 时序层 | 用于动画生成 |
| LCM-LoRA | 轻量级蒸馏 LoRA | 加速推理至 4 步内 | load_lora_weights |
| 社区模型来源 | Hugging Face Hub | 搜索 “stable-diffusion”, “controlnet” 等 | 注意许可证(如 NSFW) |
| 兼容性 | 检查基础模型版本 | 如 SD 1.5 vs SDXL | 权重不通用 |
12.5 最佳实践与常见问题汇总
| 类别 | 最佳实践 / 常见问题 | 解决方案 | 注意事项 |
|---|
| 显存不足 | OOM 错误 | 使用 fp16, xformers, attention_slicing, batch_size=1 | 组合使用更有效 |
| 生成质量差 | 图像模糊、畸形 | 检查 prompt、增加步数、调整 guidance_scale、使用 better checkpoint | 数据质量是关键 |
| 文本不相关 | 忽略 prompt | 提高 guidance_scale(7~12),优化 prompt 描述 | 避免模糊词汇 |
| 训练过拟合 | 生成图像与训练集几乎相同 | 减少训练步数,增加先验保留损失,使用 LoRA 而非 DreamBooth | 监控验证集 |
| 负面提示无效 | 不期望内容仍出现 | 显式添加负面词,提高 guidance_scale | 如 “blurry, text, deformed” |
| 模型加载慢 | from_pretrained 耗时 | 使用 local_files_only,或缓存到本地 | Hub 下载受网络影响 |
| ONNX 导出失败 | 不支持操作 | 使用 diffusers 官方导出脚本,或改用 TorchScript | 检查动态轴设置 |
| 分布式训练错误 | 进程通信失败 | 使用 accelerate launch 而非直接运行 | 正确配置 config.yaml |
| 推理速度慢 | 生成时间过长 | 使用 fp16, xformers, DDIM 或 DPM-Solver++, LoRA | 避免 PNDM |
| 模型上传失败 | 权限或网络问题 | 检查 huggingface-cli login,使用 HF_TOKEN,重试 | 大文件建议使用 git lfs |