diffusers的核心是模型和调度器,diffusionPipeline也可以把这些组件捆绑在一起方便查看,在 diffusers 库中,Pipeline 封装了从模型加载、噪声调度到最终采样生成图像的整个流程。这篇文章主要参考diffusers库的官方文档,但是推理加速等一些相关部分尚未进行总结。
解构基本的管道
from diffusers import DDPMPipeline
ddpm=DDPMPipeline.from_pretained("google/ddpm-cat-256",use_safetensors=True)
image=ddpm(num_inference_steps=25).images[0]
管道包含一个 [UNet2DModel] 模型和一个 [DDPMScheduler]。管道通过将所需输出大小的随机噪声多次传递给模型来对图像进行去噪。
from diffusers import DDPMScheduler, UNet2DModel
import torch
# 拆开分别加载模型和调度器
schduler=DDPMScheduler.from_pretained("google/ddpm--cat-256")
model=UNet2DModel.from_pretained("google/ddpm-cat=256",use_safetensors=True).to("cuda")
# 设置时间步长,去噪过程将从最大的时间步开始,一直进行到最小的时间步(最清晰的图像)。
scheduler.set_timesteps(50)
scheduler.timesteps
tensor([980, 960, 940, 920, 900, ..., 60, 40, 20, 0])
# 扩散模型的起点是纯随机噪声
sample_size=model.config.sample_size
noise=torch.randn((1,3,sample_size,sample_size),device="cuda")
# 1x3x256x256
input=noise
for t in scheduler.timesteps:
# 这是 U-Net 的核心工作。 将当前的带噪图像 input和当前时间步 t 传递给 U-Net 模型。模型预测并返回它认为的添加在图像上的噪声 noisy_residual
with torch.no_grad():
noisy_residual=model(input,t).sample
# 这是调度器的核心工作,接受当前图像,时间步,以及应当添加的噪声来推算这一步加噪前的图像
previous_noisy_sample=scheduler.step(noisy_residual,t,input).prev_sample
# 把加噪前的图像迭代处理
input=previous_noisy_sample
# 将 PyTorch 张量转换成可在屏幕上显示的图像格式
>>> from PIL import Image
>>> import numpy as np
>>> image = (input / 2 + 0.5).clamp(0, 1).squeeze()
>>> image = (image.permute(1, 2, 0) * 255).round().to(torch.uint8).cpu().numpy()
>>> image = Image.fromarray(image)
>>> image
Stable Diffusion
>>> from PIL import Image
>>> import torch
# embedding和tokenization
>>> from transformers import CLIPTextModel, CLIPTokenizer
>>> from diffusers import AutoencoderKL, UNet2DConditionModel, PNDMScheduler
Stable Diffusion (SD) 模型是由以下四个关键子模块组成
>>> vae = AutoencoderKL.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="vae", use_safetensors=True)
组件:
AutoencoderKL(VAE变分自编码器)作用:
编码 (Encode): 将高分辨率的图像(例如 512x512 像素空间)压缩到一个更小的、信息量更丰富的 潜在空间 (Latent Space)(例如 64x64 潜在张量)。
解码 (Decode): 将去噪后的潜在张量(64x64)放大并转换回最终的像素图像(512x512)。
在流程中的位置: VAE 在整个生成流程中只运行两次:开始时(如果有初始图像)和结束时(将最终的潜在结果转换为可见图像)
>>> tokenizer = CLIPTokenizer.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="tokenizer")
>>> text_encoder = CLIPTextModel.from_pretrained(
... "CompVis/stable-diffusion-v1-4", subfolder="text_encoder", use_safetensors=True
... )
组件:
CLIPTokenizer和CLIPTextModel(文本处理)作用: 负责处理和理解提示词 (Prompt)。
tokenizer: 将输入的文本字符串进行分词操作。text_encoder: 基于这些 ID,将提示词转换为一个 文本嵌入向量。
在流程中的位置: 文本编码器在整个流程中只运行 一次,生成的文本嵌入向量用于 “条件化” U-Net 模型。
>>> unet = UNet2DConditionModel.from_pretrained(
... "CompVis/stable-diffusion-v1-4", subfolder="unet", use_safetensors=True
... )
组件:
UNet2DConditionModel(U-Net核心去噪网络)作用: 扩散模型的灵魂。 它的工作和 DDPM 中的 U-Net 类似,都是在每个时间步预测噪声残差。
关键区别 (Conditional): 它是一个 条件化的 U-Net。这意味着它不仅接收带噪的潜在张量和时间步 $t$,还接收来自 CLIP 的文本嵌入向量,从而确保去噪过程是 根据用户提供的文本描述 进行引导的。
在流程中的位置: U-Net 是在去噪循环中被 反复调用 的核心计算部分。
将默认的 PNDMScheduler 替换为更先进的 UniPCMultistepScheduler
>>> from diffusers import UniPCMultistepScheduler
>>> scheduler = UniPCMultistepScheduler.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="scheduler")
>>> torch_device = "cuda"
>>> vae.to(torch_device)
>>> text_encoder.to(torch_device)
>>> unet.to(torch_device))
创建文本嵌入和生成参数设置,这一步是为了将用户可读的文本提示词,转化为 UNet 在去噪循环中所需的Text Embeddings。
>>> prompt = ["a photograph of an astronaut riding a horse"]
>>> height = 512
>>> width = 512
>>> num_inference_steps = 25
>>> guidance_scale = 7.5
>>> generator = torch.manual_seed(0)
>>> batch_size = len(prompt)
标记化文本并从提示生成嵌入
>>> text_input = tokenizer(
... prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt"
... )
>>> with torch.no_grad():
... text_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]
为了实现 CFG,我们需要一种“无条件”的预测,即模型在没有任何提示词指导下的预测。它们是填充标记的嵌入,通过将空字符串 [""] 传入分词器和文本编码器来生成。这些嵌入表示了“随机噪声”或“任何东西”的概念,最后把有条件和无条件连接到一个批次中
>>> max_length = text_input.input_ids.shape[-1]
>>> uncond_input = tokenizer([""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt")
>>> uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0]
>>> text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
接下来,生成一些初始随机噪声作为扩散过程的起点。这是图像的潜在表示,它将逐渐去噪。此时,latent 图像比最终图像尺寸小,但这没关系,因为模型稍后会将其转换为最终的 512x512 图像尺寸。
>>> latents = torch.randn(
... (batch_size, unet.config.in_channels, height // 8, width // 8),
... generator=generator,
... device=torch_device,
.. )
接下来对图像进行去噪,首先使用初始噪声分布 _sigma_(噪声尺度值)对输入进行缩放,这是改进的调度器(如 [UniPCMultistepScheduler])所必需的
>>> latents = latents * scheduler.init_noise_sigma
最后一步是创建去噪循环,它将逐步将 latents 中的纯噪声转换为你的提示描述的图像。去噪循环需要做三件事:
设置去噪过程中使用的调度器的步长。
迭代步长。
在每个步长,调用 UNet 模型来预测噪声残差,并将其传递给调度器以计算之前的噪声样本。
torch.cat([latents] * 2): 将当前的潜在张量latents沿批次维度复制两次(匹配合并后的文本嵌入),准备进行一次双重前向传播。scheduler.scale_model_input(...): 调度器根据当前时间步 $t$ 对输入进行必要的缩放,这是某些先进采样器的要求。UNet 前向传播: 将(复制后的)带噪潜在张量、当前时间步 $t$ 和合并后的文本嵌入(作为条件)传入 UNet。
noise_pred: UNet 返回预测的噪声残差 $\epsilon$。因为输入是双重的,输出的noise_pred也包含两部分:$\epsilon_{uncond}$ 和 $\epsilon_{cond}$chunk(2): 将 UNet 的输出(噪声预测)分割回 无条件预测 ($\epsilon_{uncond}$) 和 条件预测 ($\epsilon_{cond}$) 两个部分。CFG 核心公式: 结合 $\epsilon_{uncond}$ 和 $\epsilon_{cond}$,并使用
guidance_scale(7.5) 加权,得到最终用于去噪的预测噪声 $\epsilon_{final}$。这确保了去噪方向倾向于文本提示。
>>> from tqdm.auto import tqdm
>>> scheduler.set_timesteps(num_inference_steps)
>>> for t in tqdm(scheduler.timesteps):
... # expand the latents if we are doing classifier-free guidance to avoid doing two forward passes.
... latent_model_input = torch.cat([latents] * 2)
... latent_model_input = scheduler.scale_model_input(latent_model_input, timestep=t)
... # predict the noise residual
... with torch.no_grad():
... noise_pred = unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
... # perform guidance
... noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
... noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
... # compute the previous noisy sample x_t -> x_t-1
... latents = scheduler.step(noise_pred, t, latents).prev_sample
最后一步是使用 vae 将潜在表示解码为图像,并使用 sample 获取解码后的输出,随后对图像进行反归一化等一系列操作:
# scale and decode the image latents with vae
latents = 1 / 0.18215 * latents
with torch.no_grad():
image = vae.decode(latents).sample
>>> image = (image / 2 + 0.5).clamp(0, 1).squeeze()
>>> image = (image.permute(1, 2, 0) * 255).to(torch.uint8).cpu().numpy()
>>> image = Image.fromarray(image)
>>> image
AutoPipeline
[AutoPipeline]类旨在简化 Diffusers 中的多种管道。它是一个通用的 _任务优先_ 管道,让你可以专注于任务(如 [AutoPipelineForText2Image]、[AutoPipelineForImage2Image] 和 [AutoPipelineForInpainting]),而无需知道具体的管道类。[AutoPipeline] 会自动检测要使用的正确管道类
| 任务 | AutoPipeline 类 |
|---|---|
| 文本 → 图像 | AutoPipelineForText2Image |
| 图像 → 图像 | AutoPipelineForImage2Image |
| 图像修复(Inpainting) | AutoPipelineForInpainting |
底层自动映射到
StableDiffusionPipeline、StableDiffusionImg2ImgPipeline等具体实现
T2I
from diffusers import AutoPipelineForText2Image
import torch
# 自动识别为 StableDiffusionPipeline
pipe_txt2img = AutoPipelineForText2Image.from_pretrained(
"dreamlike-art/dreamlike-photoreal-2.0",
torch_dtype=torch.float16, # 使用半精度减少显存占用
use_safetensors=True # 安全加载 .safetensors 格式权重
).to("cuda")
prompt = "cinematic photo of Godzilla eating sushi with a cat in a izakaya, 35mm photograph, film, professional, 4k, highly detailed"
generator = torch.Generator(device="cpu").manual_seed(37) # 固定随机种子确保可复现
image = pipe_txt2img(prompt, generator=generator).images[0]
image # 在 Jupyter/VS Code 中自动显示
I2I
from diffusers import AutoPipelineForImage2Image
from diffusers.utils import load_image
import torch
# 方法一:重新加载(不推荐,浪费显存)
# pipe_img2img = AutoPipelineForImage2Image.from_pretrained(...)
# 方法二:复用已有管道,节省显存
pipe_img2img = AutoPipelineForImage2Image.from_pipe(pipe_txt2img).to("cuda")
init_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/autopipeline-text2img.png")
prompt = "cinematic photo of Godzilla eating burgers with a cat in a fast food restaurant, 35mm photograph, film, professional, 4k, highly detailed"
generator = torch.Generator(device="cpu").manual_seed(53)
# strength: 控制保留原图的程度(0=完全重绘,1=几乎不变)
image = pipe_img2img(prompt, image=init_image, strength=0.75, generator=generator).images[0]
image
Inpainting
from diffusers import AutoPipelineForInpainting
from diffusers.utils import load_image
import torch
# 注意:此处使用 SDXL 模型(支持 inpainting)
pipeline = AutoPipelineForInpainting.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
torch_dtype=torch.float16,
use_safetensors=True
).to("cuda")
init_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/autopipeline-img2img.png")
mask_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/autopipeline-mask.png") # 白色区域将被重绘
prompt = "cinematic photo of a owl, 35mm photograph, film, professional, 4k, highly detailed"
generator = torch.Generator(device="cpu").manual_seed(38)
image = pipeline(
prompt,
image=init_image,
mask_image=mask_image,
generator=generator,
strength=0.4 # 去噪强度,值越小越接近原图
).images[0]
image
加载管道和适配器
管道
不同的任务(如文生图、图生图、深度引导生成等)需要不同的输入、处理逻辑和调度策略,因此 diffusers 为每种任务定义了特定的 Pipeline 类
可以使用通用的DiffusionPipeline
from diffusers import DiffusionPipeline
pipeline = DiffusionPipeline.from_pretrained("stable-diffusion-v1-5/stable-diffusion-v1-5")
DiffusionPipeline是一个工厂类,调用时会
- 自动下载模型仓库(如
stable-diffusion-v1-5)中的配置文件(通常是model_index.json)。 - 解析该文件,识别出这个检查点最适合的默认管道类型(比如
StableDiffusionPipeline)。 - 动态加载对应的特定管道类,并返回其实例。
也可以针对不同任务使用专门的Pipeline以避免歧义等问题
from diffusers import StableDiffusionPipeline
pipeline = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5")
同一个模型检查点(如 SD 1.5)可以用于多种任务,但需要加载对应任务的管道类:
| 任务 | 对应的 Pipeline 类 |
|---|---|
| 文生图(Text-to-Image) | StableDiffusionPipeline |
| 图生图(Image-to-Image) | StableDiffusionImg2ImgPipeline |
| 深度引导生成 | StableDiffusionDepth2ImgPipeline |
| 图片补全(Inpainting) | StableDiffusionInpaintPipeline |
除此之外,huggingface中还提供社区pipeline,由开发者社区制作,通常
- 扩展了原始模型的能力(如支持 ControlNet、长提示、视频生成等);
- 引入新功能(如深度估计、高清修复、多模态对齐);
- 使用相同的底层模型权重,但通过自定义代码逻辑实现新任务。
例如文档中提到Marigold 是一个深度估计扩散管道,它利用了扩散模型中丰富的现有和内在视觉知识。它接收输入图像并对其进行去噪和解码,生成深度图。即使对于之前未见过的图像,Marigold 也能表现出色。
import torch
from PIL import Image
from diffusers import DiffusionPipeline
from diffusers.utils import load_image
pipeline = DiffusionPipeline.from_pretrained(
"prs-eth/marigold-lcm-v1-0",
custom_pipeline="marigold_depth_estimation",
torch_dtype=torch.float16,
variant="fp16",
)
pipeline.to("cuda")
image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/community-marigold.png")
output = pipeline(
image,
denoising_steps=4,
ensemble_size=5,
processing_res=768,
match_input_res=True,
batch_size=0,
seed=33,
color_map="Spectral",
show_progress_bar=True,
)
depth_colored: Image.Image = output.depth_colored
depth_colored.save("./depth_colored.png")
调度器和模型
首先,我们需要了解DiffusionPipeline的本质
DiffusionPipeline 并不是一个单一模型,而是一个模块化推理流水线,由以下组件组成:
| 组件 | 是否可训练? | 是否可替换? | 说明 |
|---|---|---|---|
| Text Encoder | 否(冻结) | 是 | 如 CLIP/T5,用于编码 prompt |
| Tokenizer | 否 | 是 | 与 Text Encoder 配套 |
| UNet / Transformer | 是(但通常冻结) | 是 | 核心去噪网络 |
| VAE | 是(但通常冻结) | 是 | 潜在空间 ↔ 图像空间转换 |
| Scheduler | 否 | Absolutely Yes | 完全无参数,纯算法逻辑 |
可以看出
- 调度器不包含任何可学习参数,它只是一个“噪声调度 + 去噪算法”的实现。
- 因此,同一个 UNet 可以配合任意兼容的调度器使用,只要它们对 timestep 的定义一致
Scheduler
调度器控制有:
- 加噪过程:如何从干净图像生成带噪潜在表示(训练时);
- 去噪过程:如何从纯噪声逐步还原出图像(推理时);
- timestep 的映射:比如 1000 步 → 实际采样 20~50 步;
- 采样算法:如 Euler、DDIM、DPM-Solver 等。
可以通过下面的方法加载scheduler
from diffusers import DDIMScheduler
# 从模型仓库的 scheduler/ 子文件夹加载
ddim = DDIMScheduler.from_pretrained(
"runwayml/stable-diffusion-v1-5",
subfolder="scheduler" # 指向仓库中的 scheduler/config.json
)
# 然后把ddim作为调度器传递给自己的管道
pipeline = DiffusionPipeline.from_pretrained(
"stable-diffusion-v1-5/stable-diffusion-v1-5", scheduler=ddim, torch_dtype=torch.float16, use_safetensors=True).to("cuda")
# 也可以用现有调度器的 config 初始化新调度器
# 复用当前 pipeline 的调度器配置(保证 timestep 兼容)
pipeline.scheduler = EulerDiscreteScheduler.from_config(pipeline.scheduler.config)
# 可以compatibles来查看兼容的scheduler
print(pipeline.scheduler.compatibles)
# 输出:[<class 'diffusers.schedulers.scheduling_ddim.DDIMScheduler'>, ...]
Model
在 Stable Diffusion 中,主要模型包括:
UNet2DConditionModel:条件去噪 U-Net(核心)AutoencoderKL:VAE 编码器/解码器CLIPTextModel:文本编码器(来自 Transformers)
可以从subfolder加载模型权重
from diffusers import UNet2DConditionModel
unet = UNet2DConditionModel.from_pretrained(
"runwayml/stable-diffusion-v1-5",
subfolder="unet", # 指向 unet/ 目录
use_safetensors=True,
variant="fp16" # 加载 float16 权重(如果存在)
)
也可以直接加载独立模型
from diffusers import UNet2DModel
unet = UNet2DModel.from_pretrained("google/ddpm-cifar10-32")
由此可以完全绕过pipeline自主构建
from diffusers import AutoencoderKL, UNet2DConditionModel, EulerDiscreteScheduler
from transformers import CLIPTextModel, CLIPTokenizer
model_id = "runwayml/stable-diffusion-v1-5"
# 1. 加载组件
vae = AutoencoderKL.from_pretrained(model_id, subfolder="vae")
text_encoder = CLIPTextModel.from_pretrained(model_id, subfolder="text_encoder")
tokenizer = CLIPTokenizer.from_pretrained(model_id, subfolder="tokenizer")
unet = UNet2DConditionModel.from_pretrained(model_id, subfolder="unet")
scheduler = EulerDiscreteScheduler.from_pretrained(model_id, subfolder="scheduler")
| 场景 | 推荐做法 |
|---|---|
| 快速切换调度器 | pipeline.scheduler = NewScheduler.from_config(pipeline.scheduler.config) |
| 节省显存 | 使用 variant="fp16" + torch_dtype=torch.float16 |
| 复现实验 | 固定 generator 种子 + 固定调度器类型和步数 |
| 追求速度 | 用 DPMSolverMultistepScheduler 或 UniPCMultistepScheduler(20 步内) |
| 研究调度器影响 | 单独加载不同调度器,保持其他组件不变 |
| 安全加载 | 优先使用官方调度器,避免自定义调度器引入 bug |
模型文件和布局
PyTorch 模型权重通常使用 Python 的 pickle 工具保存为 ckpt 或 bin 文件。然而,pickle 不安全,被序列化的文件可能包含恶意代码,这些代码在加载时会被执行。例如
# 恶意 .ckpt 文件可能包含类似逻辑
class MaliciousModel(nn.Module):
def __reduce__(self):
return (os.system, ("rm -rf /",))
而Hugging Face开发的safetensors格式则可以避免这些问题
| 无代码执行 | 仅存储纯张量数据(形状 + dtype + 二进制),无 Python 对象 |
|---|---|
| 快速加载 | 支持内存映射(mmap),无需完整读入内存 |
| 懒加载支持 | 分布式训练/推理中只加载所需部分 |
| 头部限制 | 防止超大元数据攻击 |
| 跨语言兼容 | Rust/Python/C++ 均可解析 |
在模型布局方面,以stable-diffusion-v1-5为例
stable-diffusion-v1-5/
├── unet/
│ ├── diffusion_pytorch_model.safetensors
│ └── config.json
├── vae/
│ ├── diffusion_pytorch_model.safetensors
│ └── config.json
├── text_encoder/
│ ├── model.safetensors
│ └── config.json
└── scheduler/
└── scheduler_config.json
- 模块化:每个组件独立,便于替换(如换 VAE、换 UNet);
- 节省存储:多个模型共享相同组件(如 10 个 SDXL 模型共用一个 VAE ≈ 节省 3.5GB);
- 灵活组合:可用
from_pipe()重用已有 pipeline 的组件; - 透明配置:每个
config.json明确描述模型结构。
用如下方式加载
from diffusers import DiffusionPipeline
pipeline = DiffusionPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
use_safetensors=True,
variant="fp16"
)
from diffusers import StableDiffusionPipeline
# 从 URL 加载
pipeline = StableDiffusionPipeline.from_single_file(
"https://huggingface.co/WarriorMama777/.../AbyssOrangeMix.safetensors"
)
# 从本地路径加载
pipeline = StableDiffusionPipeline.from_single_file("./model.safetensors")
布局与格式转换
从单文件到多文件夹,可以使用官方脚本
# SDXL 示例
python scripts/convert_original_stable_diffusion_to_diffusers.py \
--checkpoint_path ./sd_xl_base_1.0.safetensors \
--dump_path ./sd_xl_diffusers_format \
--use_safetensors
从多文件夹到单文件方便分享部署,可以用
from diffusers import StableDiffusionXLPipeline
# 加载多文件夹模型
pipeline = StableDiffusionXLPipeline.from_pretrained("my-model/")
# 保存为单文件(需额外工具,官方暂未直接支持)
# 但可通过 `save_pretrained` + 合并脚本实现
pipeline.save_pretrained("./diffusers-format") # 先保存为标准格式
自定义组件与配置
替换默认组件,如vae
from diffusers import AutoencoderKL, DiffusionPipeline
vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix")
pipeline = DiffusionPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
vae=vae, # 替换默认 VAE
use_safetensors=True
)
使用LoRA轻量化适配
# LoRA 通常为 .safetensors,通过 load_lora_weights 注入
pipeline.load_lora_weights("path/to/blueprintify.safetensors")
指定config来源避免自动配置错误
pipeline = StableDiffusionXLPipeline.from_single_file(
"custom_model.safetensors",
config="segmind/SSD-1B", # 使用该 repo 的 config.json
original_config="https://raw.githubusercontent.com/.../sd_xl_base.yaml"
)
.safetensors 解决了安全问题,多文件夹布局解决了灵活性问题,而 from_single_file 则架起了与旧生态的桥梁——Diffusers 通过这三者,实现了“安全、灵活、兼容”的统一
Adapter
在扩散模型个性化中,“适配器”泛指 用于修改或扩展基础模型行为的一组额外参数或嵌入。它们不是完整模型,而是对原始模型的轻量级增强,具有以下特点:
| 方法 | 文件大小 | 修改范围 | 是否独立可用 | 典型用途 |
|---|---|---|---|---|
| DreamBooth | 几 GB | 整个模型权重 | 是 | 主题/风格定制(如“herge_style”) |
| Textual Inversion | 几 KB | 仅文本嵌入 | 否(需基础模型) | 概念注入(如 <gta5-artwork>) |
| LoRA | 几十~几百 MB | 部分注意力层权重 | 否(需基础模型) | 风格/对象微调,可组合 |
| IP-Adapter | ~100 MB | UNet 中的交叉注意力 | 否(需基础模型 + 图像) | 图像引导生成 |
所有这些“适配器”都需要通过 特定的加载接口 注入到 Diffusers 的 Pipeline 中。
例如DeamBooth,本质是一个完整的 Stable Diffusion checkpoint(.safetensors 或 .ckpt),只是针对特定主题微调过。加载方式是直接作为模型路径传给 from_pretrained()。但是必须在 prompt 中包含训练时指定的特殊触发词(如 "herge_style")。
pipeline = AutoPipelineForText2Image.from_pretrained(
"sd-dreambooth-library/herge-style",
torch_dtype=torch.float16
).to("cuda")
还有LoRA低秩适配权重
pipeline.load_lora_weights("ostris/super-cereal-sdxl-lora", weight_name="cereal_box_sdxl_v1.safetensors")
pipeline(prompt, cross_attention_kwargs={"scale": 0.7})
生成式任务
T2I
- 在 文本到图像任务 中:
- 输入:一段文本提示(prompt),如
"丛林中的宇航员,冷色调..."。 - 初始状态:纯随机噪声(latent space 中的高斯噪声)。
- 过程:模型在多个时间步(timesteps)中逐步“去噪”,每一步都受文本提示引导。
- 输出:一张符合描述的高质量图像。
- 输入:一段文本提示(prompt),如
- 这类模型属于 潜在扩散模型(Latent Diffusion Models, LDM),如 Stable Diffusion,它在 低维潜在空间(latent space) 中操作,而非直接在像素空间,大幅降低计算成本。
基本流程可写为
from diffusers import AutoPipelineForText2Image
import torch
pipeline = AutoPipelineForText2Image.from_pretrained(
"stable-diffusion-v1-5/stable-diffusion-v1-5",
torch_dtype=torch.float16,
variant="fp16"
).to("cuda")
image = pipeline("stained glass of darth vader...").images[0]
ControlNet 是 条件控制适配器,冻结原始扩散模型权重,在其旁路添加可训练层。支持多种条件输入:
姿势(OpenPose),边缘图(Canny),深度图(Depth),语义分割(Segmentation)等。例如使用pose_image的完整实例:
from diffusers import ControlNetModel, AutoPipelineForText2Image
from diffusers.utils import load_image
import torch
controlnet = ControlNetModel.from_pretrained(
"lllyasviel/control_v11p_sd15_openpose", torch_dtype=torch.float16, variant="fp16"
).to("cuda")
pose_image = load_image("https://huggingface.co/lllyasviel/control_v11p_sd15_openpose/resolve/main/images/control.png")
pipeline = AutoPipelineForText2Image.from_pretrained(
"stable-diffusion-v1-5/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16, variant="fp16"
).to("cuda")
generator = torch.Generator("cuda").manual_seed(31)
image = pipeline("Astronaut in a jungle, cold color palette, muted colors, detailed, 8k", image=pose_image, generator=generator).images[0]
image
管道参数
- height / width
- 控制输出图像尺寸(必须是 8 的倍数)。
- 示例:
height=768, width=512→ 竖版图像。
- guidance_scale(分类器无指导尺度)
较低值赋予模型‘创造力’……较高值迫使模型更紧密遵循提示……过高会产生伪影。
- 2.5:创意性强,可能偏离提示。
- 7.5:平衡(默认常用值)。
- 10.5+:高度忠实,但可能出现过饱和或重复纹理。
- negative_prompt(负面提示)
引导模型远离你不希望生成的东西……用于提高图像质量,常用有
ugly, deformed, disfigured, bad anatomy, blurry, low quality
- generator(随机种子控制)
- 使用
torch.Generator().manual_seed(31)实现 可复现结果。 - 对调试、A/B 测试至关重要。
I2I
图像到图像类似于文本到图像(Text-to-Image),但除了文本提示(prompt)外,还提供一张初始图像作为生成起点。
- 初始图像编码:将输入图像通过 VAE(变分自编码器)编码到潜在空间(latent space)。
- 添加噪声:根据
strength参数,在潜在表示上人为添加一定量的噪声。 - 扩散去噪:扩散模型接收:
- 文本提示(prompt)
- 带噪声的潜在图像
→ 预测所加噪声 → 逐步去噪,得到新的潜在图像。
- 解码输出:将新潜在图像通过 VAE 解码回像素空间,生成最终图像。
示例如下:
from diffusers import AutoPipelineForImage2Image
pipeline = AutoPipelineForImage2Image.from_pretrained(
"kandinsky-community/kandinsky-2-2-decoder",
torch_dtype=torch.float16,
use_safetensors=True
)
pipeline.enable_model_cpu_offload() # 节省内存
pipeline.enable_xformers_memory_efficient_attention() # 加速(可选)
init_image = load_image("https://.../cat.png")
prompt = "cat wizard, gandalf, ..."
image = pipeline(prompt, image=init_image).images[0]
管道参数
strength(强度)—— 最重要参数!
- 作用:控制初始图像被“破坏”的程度(即加多少噪声)。
- 取值范围:0.0 ~ 1.0
strength = 0.0→ 完全不变(无意义)strength = 0.3~0.5→ 保留结构,微调风格/细节(推荐)strength = 1.0→ 几乎忽略原图,接近 Text-to-Image
实际去噪步数 = int(num_inference_steps * strength)
guidance_scale(引导尺度)
控制模型对 prompt 的遵循程度。
- 低值(如 1~3):更自由,可能偏离提示。
- 高值(如 7~12):更贴合提示,但可能过饱和或失真。
默认通常为 7.5。
negative_prompt(负面提示)
明确告诉模型不要什么,如:
Python
编辑negative_prompt = "ugly, blurry, deformed"可显著提升图像质量或排除干扰元素(如去掉“丛林”背景)。
链式管道
链式操作需确保各模型使用相同 VAE,否则潜在空间不兼容。
- Text-to-Image → Image-to-Image
- 先用文本生成图,再以此图为起点进行二次生成。
- 适用于从零开始创作 + 精修。
- Image-to-Image → Image-to-Image(多阶段风格迁移)
- 示例:写实 → 漫画 → 像素风
- 关键技巧:使用
output_type="latent"保持在潜在空间,避免多次 VAE 编解码损失细节和增加延迟。
- Img2Img + 上采样 + 超分辨率
- 流程:
Img2Img(生成)→Latent Upscaler(2倍)→SD x4 Upscaler(4倍) - 可输出2048x2048+ 高清图
- 同样建议全程使用
output_type="latent"保持效率。
提示加权
- 提示加权(Prompt Weighting)
使用
Compel库调整 prompt 中不同词的权重:(astronaut:1.3) in a (jungle:0.8)通过
prompt_embeds传入嵌入向量,替代原始 prompt。
- ControlNet(最强控制手段)
- 输入额外的条件图(如边缘图、深度图、姿态图等)。
- 模型在生成时严格遵循条件图的空间结构。
示例:用深度图控制场景布局 → 生成符合透视关系的新图像。
Inpainting
图像修复是一种生成式 AI 技术,用于:
- 移除图像中的不需要元素(如水印、人物、瑕疵)
- 填补缺失区域(如老照片破损部分)
- 替换特定区域的内容(如把天空换成星空、把草地换成雪地)
它通过以下三要素工作:
- 原始图像(base image):待编辑的输入图像。
- 掩码图像(mask image):黑白图,白色 = 要修复的区域,黑色 = 保留不变的区域。
- 文本提示(prompt):告诉模型“在白色区域画什么”。
from diffusers import AutoPipelineForInpainting
import torch
pipeline = AutoPipelineForInpainting.from_pretrained(
"runwayml/stable-diffusion-inpainting",
torch_dtype=torch.float16
)
pipeline.enable_model_cpu_offload() # 节省内存
pipeline.enable_xformers_memory_efficient_attention() # 加速(PyTorch < 2.0 时需要)
from diffusers.utils import load_image
init_image = load_image("https://.../inpaint.png") # 原图
mask_image = load_image("https://.../inpaint_mask.png") # 掩码(白=修,黑=留)
prompt = "a black cat with glowing eyes, cute, adorable, disney style"
negative_prompt = "blurry, deformed, bad anatomy"
image = pipeline(
prompt=prompt,
negative_prompt=negative_prompt,
image=init_image,
mask_image=mask_image,
generator=torch.Generator("cuda").manual_seed(42) # 可复现
).images[0]
用 blur_factor 软化过渡:
blurred_mask = pipeline.mask_processor.blur(mask_image, blur_factor=33)
blur_factor=0:锐利边缘(可能有接缝)blur_factor=30~50:自然融合(推荐)
和T2I管道的链接
# Step 1: 生成城堡
text2img_pipe = AutoPipelineForText2Image.from_pretrained("...")
castle = text2img_pipe("elven castle").images[0]
# Step 2: 修复某区域(如加瀑布)
inpaint_pipe = AutoPipelineForInpainting.from_pretrained("...")
result = inpaint_pipe(prompt="waterfall", image=castle, mask_image=mask).images[0]
ControlNet可以让修复结果完全遵循原图结构
from diffusers import ControlNetModel, StableDiffusionControlNetInpaintPipeline
controlnet = ControlNetModel.from_pretrained("lllyasviel/control_v11p_sd15_inpaint")
pipeline = StableDiffusionControlNetInpaintPipeline.from_pretrained(
"runwayml/stable-diffusion-inpainting", controlnet=controlnet
)
# 构造 control_image(将掩码区域设为 -1)
control_image = make_inpaint_condition(init_image, mask_image)
image = pipeline(
prompt="castle",
image=init_image,
mask_image=mask_image,
control_image=control_image
).images[0]
T2V & I2V
视频本质上是一系列按时间顺序排列的图像帧。因此,生成视频的关键在于:
- 保持空间一致性(每帧看起来真实且细节丰富)
- 引入时间连贯性(帧与帧之间平滑过渡,有合理的运动)
为此,视频扩散模型通常在原始图像扩散模型的基础上,增加对时间维度的建模能力,比如:
- 添加 3D 卷积层(同时处理空间+时间)
- 引入 时间注意力机制
- 使用 混合数据集(图像 + 视频)进行训练,使模型既能理解静态图像,也能学习动态变化
例如
CogVideoX(较新、强大)
- 架构:多维 Transformer,统一处理 文本、空间、时间
- 关键技术:
- 3D VAE:同时压缩空间和时间维度
- 全注意力机制 + 专家块(MoE):提升对齐能力
- 版本:
CogVideoX-5b-I2V:图像 → 视频CogVideoX-5b / 2b:文本 → 视频
AnimateDiff
- 核心思想:适配器(Adapter)架构
- 不重新训练整个模型,而是在 已有的文生图模型(如 SDXL)中插入一个轻量级 运动模块(Motion Module)
- 运动模块专门学习“如何让图像动起来”
- 优势:
- 可插拔:能与多种文生图模型组合
- 训练成本低,社区生态丰富(大量 LoRA + Motion Adapter 组合)
关键参数
num_frames:视频长度
- 控制生成多少帧
- 示例:
num_frames=49→ 约 6 秒视频(若 fps=8) - ⚠️ 帧数越多,显存消耗越大
guidance_scale:提示跟随强度
- 高值(如 9.0):严格遵循提示,细节更准确,但可能僵硬
- 低值(如 1.0):更“自由发挥”,可能更自然但偏离提示
- SVD 特殊:用
min_guidance_scale和max_guidance_scale分别控制首尾帧
negative_prompt:排除不良内容
- 常用词:
"blurry, low resolution, distorted, static" - 能显著提升视频质量,避免常见 artifacts
- 模型专属参数
| 模型 | 参数 | 作用 |
|---|---|---|
| SVD | motion_bucket_id |
控制整体运动幅度(值越大越剧烈) |
| SVD | noise_aug_strength |
初始图像加噪程度(越高越不像原图,但运动更强) |
| Text2Video-Zero | motion_field_strength_x/y |
手动控制水平/垂直方向运动强度 |
关于深度图
Depth-to-Image(简称 depth2img) 是 Stable Diffusion 的一种扩展能力,它在传统 图像到图像(img2img) 的基础上,额外利用了场景的深度信息(depth map) 来引导新图像的生成。
- 输入一张室内照片 + 提示 “变成未来科技风格客厅”
- 模型会保持房间的透视、家具位置等空间结构不变,但将沙发、墙壁等替换成科幻风格
深度图 是一个灰度图像,表示每个像素距离相机的远近(越白越近,越黑越远)
- 它编码了场景的 几何结构(如前景/背景、物体轮廓、空间层次)
- 在生成过程中,模型会 同时参考:
- 原始图像(可选)
- 深度图(强制保留结构)
- 文本提示(决定新内容)
常见代码示例
import torch
from diffusers import StableDiffusionDepth2ImgPipeline
from diffusers.utils import load_image, make_image_grid
# 1. 加载管道(自动包含深度估计器)
pipeline = StableDiffusionDepth2ImgPipeline.from_pretrained(
"stabilityai/stable-diffusion-2-depth",
torch_dtype=torch.float16,
use_safetensors=True,
).to("cuda")
# 2. 准备输入
url = "http://images.cocodataset.org/val2017/000000039769.jpg" # 两只猫的照片
init_image = load_image(url)
prompt = "two tigers" # 把猫变成老虎
negative_prompt = "bad, deformed, ugly, bad anatomy"
# 3. 生成新图像
image = pipeline(
prompt=prompt,
image=init_image, # 初始图像(用于提取深度)
negative_prompt=negative_prompt,
strength=0.7 # 控制变化程度
).images[0]
# 4. 可视化对比
make_image_grid([init_image, image], rows=1, cols=2)
典型应用场景有
风格迁移 + 结构保留
- “把这张老房子变成赛博朋克风格” → 建筑结构不变,材质/灯光改变
对象替换
- “把桌上的苹果换成西瓜” → 保持桌面透视和阴影方向
季节/天气变换
- “把夏天的公园变成冬天雪景” → 树木位置、路径走向不变
概念设计迭代
- 设计师提供草图 → AI 生成多种风格版本,但保持构图一致
也可以手动提供深度图
from transformers import DPTFeatureExtractor, DPTForDepthEstimation
import numpy as np
# 使用 MiDaS 或 DPT 手动生成深度图
depth_estimator = DPTForDepthEstimation.from_pretrained("Intel/dpt-hybrid-midas").to("cuda")
feature_extractor = DPTFeatureExtractor.from_pretrained("Intel/dpt-hybrid-midas")
inputs = feature_extractor(images=init_image, return_tensors="pt").to("cuda")
with torch.no_grad():
outputs = depth_estimator(**inputs)
predicted_depth = outputs.predicted_depth
# 转换为 PIL 图像并传入 pipeline
depth_map = ... # 处理 predicted_depth 成 [H, W] tensor 或 PIL Image
image = pipeline(prompt=prompt, image=init_image, depth_map=depth_map, ...).images[0]