深入解析 diffusers 中的 Latent Diffusion:LDMTextToImagePipeline 与 LDMSuperResolutionPipeline 实战指南
【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers
Latent Diffusion(潜空间扩散模型,LDM)最早由 Rombach 等人于 2021 年提出,核心思想是把扩散过程从像素空间迁移到预训练自编码器(VAE/VQ-VAE)的潜空间中,从而以更低的计算成本实现高质量图像生成与多种条件控制。本文以本仓库(HuggingFace diffusers)中 Latent Diffusion 的官方实现为蓝本,完整讲解两条核心管线——文本生图管线LDMTextToImagePipeline与图像超分辨率管线LDMSuperResolutionPipeline,包括组件构成、加载方式、全部可调参数、底层去噪与分类器自由引导(CFG)的实现细节,并结合源码与测试用例给出可复现的实战示例。读完本文,你将能够独立加载并运行这两条管线,理解其内部调用链,并学会通过调度器与组件复用策略优化推理速度与生成质量。
Latent Diffusion:从像素扩散到潜空间扩散
在传统扩散模型中,去噪自编码器直接作用于像素空间。论文《High-Resolution Image Synthesis with Latent Diffusion Models》(Robin Rombach, Andreas Blattmann, Dominik Lorenz, Patrick Esser, Björn Ommer)指出,直接在像素空间优化强大的扩散模型往往需要数百个 GPU 天的训练成本,且由于去噪过程的顺序执行,推理开销也很大。LDM 的关键创新在于:
- 在潜空间训练扩散模型:借助预训练的强自编码器(如 VQ-VAE、KL-VAE),把图像压缩到低维潜表示后再做扩散,在"复杂度降低"与"细节保留"之间取得近优平衡,显著提升视觉保真度,同时大幅降低训练与推理的计算需求。
- 引入交叉注意力层:使扩散模型可以接受文本、边界框等通用条件输入,成为灵活的条件生成器,并且能以卷积的方式实现高分辨率合成。
论文在图像修复(inpainting)任务上取得当时的最优效果,在无条件生成、语义场景合成、超分辨率等任务上也表现出了很强的竞争力。本仓库对 LDM 的工程化实现主要落在src/diffusers/pipelines/latent_diffusion/目录下,包含两个可直接使用的管线类:
| 管线类 | 功能 | 源码文件 |
|---|---|---|
LDMTextToImagePipeline | 文本条件图像生成(text-to-image) | pipeline_latent_diffusion.py |
LDMSuperResolutionPipeline | 无条件图像超分辨率(4x upscaling) | pipeline_latent_diffusion_superresolution.py |
两者都继承自DiffusionPipeline,因此天然支持from_pretrained/save_pretrained、设备迁移(.to(device))、CPU 卸载(offload)等通用能力。从 latent_diffusion 包的导出定义 可以看出,LDMBertModel、LDMTextToImagePipeline、LDMSuperResolutionPipeline均已公开导出,并可在顶层直接from diffusers import LDMTextToImagePipeline, LDMSuperResolutionPipeline导入(见 src/diffusers/init.py 中的导出声明)。
LDMTextToImagePipeline:文本驱动的潜空间图像生成
组件构成与加载方式
LDMTextToImagePipeline的构造函数接收五个模块(见 pipeline_latent_diffusion.py):
vqvae:VQModel或AutoencoderKL,负责图像与潜表示之间的编解码;bert:基于 BERT 的文本编码器(仓库内实现为LDMBertModel);tokenizer:BertTokenizer风格的文本分词器;unet:UNet2DModel或UNet2DConditionModel,用于对编码后的图像潜变量去噪;scheduler:去噪调度器,支持DDIMScheduler、LMSDiscreteScheduler、PNDMScheduler。
管线还会根据 VQ-VAE 的下采样层数自动计算潜空间缩放因子:self.vae_scale_factor = 2 ** (len(self.vqvae.config.block_out_channels) - 1),该因子决定了最终输出图像的默认分辨率。同时,管线声明了模型卸载顺序model_cpu_offload_seq = "bert->unet->vqvae",在使用enable_model_cpu_offload()时会按此顺序逐模块加载到 GPU。
最简洁的加载方式是直接通过from_pretrained加载官方发布的 LDM 权重:
from diffusers import DiffusionPipeline ldm = DiffusionPipeline.from_pretrained("CompVis/ldm-text2im-large-256") prompt = "A painting of a squirrel eating a burger" images = ldm([prompt], num_inference_steps=50, eta=0.3, guidance_scale=6).images for idx, image in enumerate(images): image.save(f"squirrel-{idx}.png")这段代码同样出自 pipeline_latent_diffusion.py 的 docstring 示例,其中DiffusionPipeline.from_pretrained会自动识别模型仓库的组件结构并组装出正确的管线。CompVis/ldm-text2im-large-256是论文作者发布的 256x256 文本生图权重,集成测试(test_latent_diffusion.py)中即用它验证了 DDIM 调度器下 50 步完整去噪的输出与参考数组一致。
调用参数详解
__call__方法的完整签名与默认值如下:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
prompt | str/list[str] | 必填 | 引导生成的提示词;传入列表即按 batch 生成多张图 |
height/width | int | unet.config.sample_size * vae_scale_factor | 输出图像尺寸,必须能被 8 整除,否则抛出ValueError |
num_inference_steps | int | 50 | 去噪步数,越多通常质量越高、速度越慢 |
guidance_scale | float | 1.0 | 分类器自由引导强度;> 1时启用引导,越大越贴合提示词但可能降低图像质量 |
eta | float | 0.0 | DDIM 论文中的 η 参数(随机性强度),仅对DDIMScheduler生效,其余调度器忽略 |
generator | torch.Generator/ 列表 | None | 随机数生成器,用于可复现生成 |
latents | torch.Tensor | None | 预生成的初始噪声潜变量,可在不同提示词间复用同一噪声 |
output_type | str | "pil" | 输出格式,可选"pil"(PIL.Image)、"np"(numpy 数组)、"pt"(torch.Tensor) |
return_dict | bool | True | 为True返回ImagePipelineOutput,否则返回普通 tuple |
若guidance_scale != 1.0,管线会额外对空字符串[""] * batch_size做一次分词与 BERT 编码,得到无条件嵌入(negative prompt embeddings),见 pipeline_latent_diffusion.py 第 157-162 行。
分类器自由引导(CFG)与去噪循环的源码实现
LDMTextToImagePipeline的去噪主循环(pipeline_latent_diffusion.py 第 194-214 行)实现了论文中的引导机制,其关键逻辑如下:
- 初始噪声:当用户未提供
latents时,按形状(batch_size, unet.config.in_channels, height // 8, width // 8)采样高斯噪声;若提供了latents,则校验形状必须一致,否则报错。注意这里的// 8与 VQ-VAE 的三次下采样(block_out_channels 长度为 4)对应。 - 引导拼接:
guidance_scale == 1.0时不做引导,直接把latents送入 UNet;否则将latents与context(无条件嵌入 + 条件嵌入)各拼接一份,合并为两倍 batch 的单次前向,避免两次独立推理。 - 噪声预测与引导:UNet 输出经
chunk(2)拆分为无条件噪声与条件噪声,然后按公式noise_pred = noise_pred_uncond + guidance_scale * (noise_prediction_text - noise_pred_uncond)合成最终噪声预测。 - 单步去噪:调用
scheduler.step(noise_pred, t, latents, **extra_kwargs)得到上一时刻样本x_{t-1}。由于不同调度器的step签名不同,代码通过inspect.signature检查其是否接受eta参数,仅在支持时才传入。 - XLA 支持:当检测到
torch_xla可用时,每步循环内调用xm.mark_step()以配合 TPU 执行。
去噪结束后,潜变量按latents = 1 / self.vqvae.config.scaling_factor * latents缩放,经vqvae.decode解码为像素图像,再做(image / 2 + 0.5).clamp(0, 1)的归一化,最后按output_type转换为 PIL 或 numpy 数组(pipeline_latent_diffusion.py 第 219-234 行)。
文本编码器 LDMBert 的实现要点
文本编码器是管线在仓库内自带实现的LDMBertModel(与管线同文件定义),它是一套基于 BART/BERT 结构复刻的 Transformer 编码器:
LDMBertConfig默认参数:vocab_size=30522、max_position_embeddings=77(与管线分词时的max_length=77对应)、encoder_layers=32、encoder_ffn_dim=5120、encoder_attention_heads=8、head_dim=64、d_model=1280、激活函数 GELU;- 结构上包含 token embedding 与位置 embedding 相加、32 层
LDMBertEncoderLayer(自注意力 + 前馈网络,均带残差与 LayerNorm)、最终 LayerNorm; - 注意力实现
LDMBertAttention直接由 BART 的BartAttention复制改造而来,支持 KV 缓存与注意力头掩码。
分词器在管线内以padding="max_length", max_length=77, truncation=True的方式调用,保证文本嵌入长度与模型位置编码上限一致。
LDMSuperResolutionPipeline:无条件潜空间超分辨率
组件构成与加载方式
LDMSuperResolutionPipeline是无条件模型,不接收文本提示,只需三个模块(见 pipeline_latent_diffusion_superresolution.py):
vqvae:VQModel,负责潜变量解码;unet:UNet2DModel(无条件 UNet),其in_channels应为 6——3 个通道留给噪声潜变量,3 个通道存放低分辨率输入图像;scheduler:支持范围更广,包括DDIMScheduler、LMSDiscreteScheduler、EulerDiscreteScheduler、EulerAncestralDiscreteScheduler、DPMSolverMultistepScheduler、PNDMScheduler。
官方示例使用CompVis/ldm-super-resolution-4x-openimages权重,测试用例则还验证了duongna/ldm-super-resolution仓库(见 test_latent_diffusion_superresolution.py)。完整调用示例:
import requests from PIL import Image from io import BytesIO from diffusers import LDMSuperResolutionPipeline import torch # 加载模型并迁移到 GPU pipeline = LDMSuperResolutionPipeline.from_pretrained("CompVis/ldm-super-resolution-4x-openimages") pipeline = pipeline.to("cuda") # 下载一张低分辨率图片 url = "https://user-images.githubusercontent.com/38061659/199705896-b48e17b8-b231-47cd-a270-4ffa5a93fa3e.png" response = requests.get(url) low_res_img = Image.open(BytesIO(response.content)).convert("RGB") low_res_img = low_res_img.resize((128, 128)) # 推理:4 倍超分辨率 upscaled_image = pipeline(low_res_img, num_inference_steps=100, eta=1).images[0] upscaled_image.save("ldm_generated_image.png")该示例同样来自 superresolution 管线的 docstring。
参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
image | torch.Tensor/PIL.Image.Image | 必填 | 待放大的低分辨率图像;PIL 输入会被自动预处理 |
batch_size | int | 1 | 生成图像数量;传入 tensor 时自动取image.shape[0] |
num_inference_steps | int | 100 | 去噪步数 |
eta | float | 0.0 | 同文本生图管线,仅对DDIMScheduler有效,取值建议在[0, 1] |
generator | torch.Generator/ 列表 | None | 随机数生成器 |
output_type | str | "pil" | "pil"/"np"/"pt" |
return_dict | bool | True | 返回ImagePipelineOutput或 tuple |
预处理与"通道拼接"式条件注入
该管线没有交叉注意力条件,而是通过通道维拼接把低分辨率图像直接喂给 UNet。其preprocess函数(pipeline_latent_diffusion_superresolution.py 第 29-36 行)完成以下工作:
- 将宽高向下取整为 32 的整数倍;
- 使用 Lanczos 插值缩放到该尺寸;
- 归一化到
[0, 1]并转为(1, C, H, W)的浮点张量; - 映射到
[-1, 1]区间。
推理阶段(pipeline_latent_diffusion_superresolution.py 第 144-180 行)的流程为:
- 依据
unet.config.in_channels // 2计算噪声通道数(即 6 ÷ 2 = 3),采样与低分辨率图同尺寸的高斯噪声,并按scheduler.init_noise_sigma缩放,以适配所选调度器的噪声约定; - 每步把
torch.cat([latents, image], dim=1)拼接成 6 通道输入,经scheduler.scale_model_input缩放后送入 UNet 预测噪声; - 循环结束后
vqvae.decode解码,clamp(-1.0, 1.0)后映射回[0, 1]输出。
集成测试确认该管线输入 64x64 图像可输出 256x256 的结果(test_latent_diffusion_superresolution.py 第 126-144 行),即默认达到 4 倍超分辨率;单元测试则验证了把多张图沿 batch 维堆叠后输入,可一次得到多张放大结果(test_batched_image_input)。
调度器选择与组件复用建议
原文档在 TIP 中给出了两条重要实践建议,这里结合仓库进一步展开:
- 调度器速度与质量的权衡:两条管线都支持多种调度器,且代码通过参数探测自动适配
eta等调度器特有参数,因此更换调度器几乎零成本。完整的调度器对比与选型思路可参考 schedulers 使用指南。一般而言,DDIMScheduler支持通过eta控制随机性,DPMSolverMultistepScheduler/EulerDiscreteScheduler在较少步数下即可获得不错效果,适合追求推理速度的场景。 - 跨管线复用组件:LDM 管线中的 VQ-VAE、BERT 编码器、UNet 等都是独立注册的模块(
self.register_modules(...)),完全可以像其他 diffusers 管线一样,通过DiffusionPipeline.components取出后注入其他管线,避免重复加载同一份权重。组件复用与加载机制的详细说明见 loading 指南。
测试与验证:仓库如何保证管线正确性
仓库为两条管线都配备了单元测试与(标记为@nightly的)集成测试,可作为理解管线行为的参考实现:
- test_latent_diffusion.py:用微型 UNet / AutoencoderKL / CLIP 文本编码器构造 dummy 组件,验证
LDMTextToImagePipeline的输出形状与数值切片;集成测试则加载真实权重CompVis/ldm-text2im-large-256,对比输出与官方参考数组的误差不超过 1e-3。测试还显式说明:该管线先于 prompt embeddings 与 negative prompting 机制出现,__call__只接收原始prompt字符串。 - test_latent_diffusion_superresolution.py:验证超分输出形状
(3, 64, 64)、batch 输入行为,以及真实权重duongna/ldm-super-resolution下 64→256 的超分结果。
这些测试同时印证了一个重要事实:vqvae参数既可以传VQModel也可以传AutoencoderKL(测试中文本生图管线即用AutoencoderKL充当 VQ 模型),因此两条管线对自编码器实现具有一定的兼容弹性。
使用注意事项小结
LDMTextToImagePipeline的height/width必须能被 8 整除,且默认尺寸由 UNet 的sample_size与 VQ-VAE 下采样因子共同决定。guidance_scale默认 1.0(无引导);设置为大于 1 的值才会启用 CFG,代价是每次迭代需要两倍 batch 的前向计算(代码通过一次拼接前向优化了这点)。eta只在DDIMScheduler下有意义,其他调度器会自动忽略;超分管线还要求eta处于[0, 1]。- 若自行传入
latents(文本生图管线),形状必须是(batch_size, in_channels, height//8, width//8),否则会抛出形状不匹配异常。 - 超分管线的 UNet 输入通道数必须是 6(3 通道噪声 + 3 通道低分辨率图像),这是模型权重固有的约束,替换 UNet 时需要特别留意。
总体而言,diffusers 对 Latent Diffusion 的这两条管线实现保持了与原始 CompVis 模型权重的高度兼容,同时统一了调度器接口、设备管理与组件复用机制,是学习潜空间扩散模型工程化落地与条件生成实现的理想参考。
【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考