diffusers-源码解析-五十二-
diffusers 源码解析(五十二)
.\diffusers\pipelines\stable_diffusion_xl\pipeline_stable_diffusion_xl_instruct_pix2pix.py
# 版权声明,标识此代码的版权归属
# Copyright 2024 Harutatsu Akiyama and The HuggingFace Team. All rights reserved.
#
# 根据 Apache 2.0 许可证进行许可
# Licensed under the Apache License, Version 2.0 (the "License");
# 本文件只能在遵守许可证的情况下使用
# you may not use this file except in compliance with the License.
# 可以在以下网址获取许可证副本
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面协议另有规定,否则本软件以“原样”方式分发,不提供任何形式的保证
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 查看许可证以了解有关许可和限制的具体信息
# See the License for the specific language governing permissions and
# limitations under the License.
# 导入 inspect 模块,用于检查活跃的对象
import inspect
# 导入类型提示相关的类和函数
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
# 导入 PIL 图像处理库
import PIL.Image
# 导入 PyTorch 库
import torch
# 导入 CLIP 模型和分词器
from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer
# 从本地模块导入图像处理和加载器类
from ...image_processor import PipelineImageInput, VaeImageProcessor
from ...loaders import FromSingleFileMixin, StableDiffusionXLLoraLoaderMixin, TextualInversionLoaderMixin
# 从本地模型导入相关类
from ...models import AutoencoderKL, UNet2DConditionModel
# 导入注意力处理器
from ...models.attention_processor import (
AttnProcessor2_0,
FusedAttnProcessor2_0,
XFormersAttnProcessor,
)
# 导入 Lora 相关调整函数
from ...models.lora import adjust_lora_scale_text_encoder
# 导入扩散调度器
from ...schedulers import KarrasDiffusionSchedulers
# 从本地工具库导入常用工具函数
from ...utils import (
USE_PEFT_BACKEND,
deprecate,
is_invisible_watermark_available,
is_torch_xla_available,
logging,
replace_example_docstring,
scale_lora_layers,
)
# 从 torch_utils 导入随机张量生成函数
from ...utils.torch_utils import randn_tensor
# 导入扩散管道及其混合类
from ..pipeline_utils import DiffusionPipeline, StableDiffusionMixin
# 导入管道输出类
from .pipeline_output import StableDiffusionXLPipelineOutput
# 检查是否可以使用隐形水印功能
if is_invisible_watermark_available():
# 如果可用,导入水印类
from .watermark import StableDiffusionXLWatermarker
# 检查是否可以使用 Torch XLA
if is_torch_xla_available():
# 如果可用,导入 XLA 核心模型
import torch_xla.core.xla_model as xm
# 设置标志,指示 XLA 可用
XLA_AVAILABLE = True
else:
# 设置标志,指示 XLA 不可用
XLA_AVAILABLE = False
# 创建一个日志记录器,用于记录当前模块的信息
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
# 示例文档字符串,包含代码示例
EXAMPLE_DOC_STRING = """
Examples:
```py
>>> import torch
>>> from diffusers import StableDiffusionXLInstructPix2PixPipeline
>>> from diffusers.utils import load_image
>>> resolution = 768 # 设置图像分辨率
>>> image = load_image(
... "https://hf.co/datasets/diffusers/diffusers-images-docs/resolve/main/mountain.png"
... ).resize((resolution, resolution)) # 加载并调整图像大小
>>> edit_instruction = "Turn sky into a cloudy one" # 编辑指令
>>> pipe = StableDiffusionXLInstructPix2PixPipeline.from_pretrained(
... "diffusers/sdxl-instructpix2pix-768", torch_dtype=torch.float16
... ).to("cuda") # 加载预训练管道并移动到 GPU
>>> edited_image = pipe(
... prompt=edit_instruction, # 传入编辑指令
... image=image, # 传入待编辑的图像
... height=resolution, # 设置图像高度
... width=resolution, # 设置图像宽度
... guidance_scale=3.0, # 设置引导比例
... image_guidance_scale=1.5, # 设置图像引导比例
... num_inference_steps=30, # 设置推理步数
... ).images[0] # 获取编辑后的图像
>>> edited_image # 输出编辑后的图像
```py
"""
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img 中复制
def retrieve_latents(
# 输入参数:编码器输出的张量,生成器(可选),采样模式(默认为 "sample")
encoder_output: torch.Tensor, generator: Optional[torch.Generator] = None, sample_mode: str = "sample"
):
# 如果编码器输出具有 latent_dist 属性且采样模式为 "sample"
if hasattr(encoder_output, "latent_dist") and sample_mode == "sample":
# 返回从 latent_dist 中采样的结果
return encoder_output.latent_dist.sample(generator)
# 如果编码器输出具有 latent_dist 属性且采样模式为 "argmax"
elif hasattr(encoder_output, "latent_dist") and sample_mode == "argmax":
# 返回 latent_dist 的模式值
return encoder_output.latent_dist.mode()
# 如果编码器输出具有 latents 属性
elif hasattr(encoder_output, "latents"):
# 返回 latents 属性的值
return encoder_output.latents
# 如果都没有,抛出 AttributeError
else:
raise AttributeError("Could not access latents of provided encoder_output")
def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0):
"""
根据 guidance_rescale 重新缩放 `noise_cfg`。基于 [Common Diffusion Noise Schedules and
Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf) 的发现。见第 3.4 节
"""
# 计算噪声预测文本的标准差,保持维度
std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True)
# 计算噪声配置的标准差,保持维度
std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True)
# 根据文本的标准差与配置的标准差的比例,重新缩放噪声预测(修正过曝)
noise_pred_rescaled = noise_cfg * (std_text / std_cfg)
# 通过引导缩放因子将重新缩放的结果与原始结果混合,以避免图像 "过于简单"
noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg
# 返回重新缩放后的噪声配置
return noise_cfg
class StableDiffusionXLInstructPix2PixPipeline(
# 继承自 DiffusionPipeline 和其他混合类
DiffusionPipeline,
StableDiffusionMixin,
TextualInversionLoaderMixin,
FromSingleFileMixin,
StableDiffusionXLLoraLoaderMixin,
):
r"""
基于文本指令的像素级图像编辑管道。基于 Stable Diffusion XL。
该模型继承自 [`DiffusionPipeline`]。查看超类文档以了解库为所有管道实现的通用方法
(例如下载或保存、在特定设备上运行等)。
该管道还继承以下加载方法:
- [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] 用于加载文本反转嵌入
- [`~loaders.FromSingleFileMixin.from_single_file`] 用于加载 `.ckpt` 文件
- [`~loaders.StableDiffusionXLLoraLoaderMixin.load_lora_weights`] 用于加载 LoRA 权重
- [`~loaders.StableDiffusionXLLoraLoaderMixin.save_lora_weights`] 用于保存 LoRA 权重
# 文档字符串,描述函数的参数
Args:
vae ([`AutoencoderKL`]):
变分自编码器(VAE)模型,用于将图像编码和解码为潜在表示。
text_encoder ([`CLIPTextModel`]):
冻结的文本编码器。Stable Diffusion XL 使用
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel) 的文本部分,
特别是 [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) 变体。
text_encoder_2 ([` CLIPTextModelWithProjection`]):
第二个冻结文本编码器。Stable Diffusion XL 使用
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModelWithProjection) 的文本和池部分,
特别是
[laion/CLIP-ViT-bigG-14-laion2B-39B-b160k](https://huggingface.co/laion/CLIP-ViT-bigG-14-laion2B-39B-b160k)
变体。
tokenizer (`CLIPTokenizer`):
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer) 类的分词器。
tokenizer_2 (`CLIPTokenizer`):
第二个 [CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer) 类的分词器。
unet ([`UNet2DConditionModel`]): 条件 U-Net 架构,用于去噪编码后的图像潜在表示。
scheduler ([`SchedulerMixin`]):
与 `unet` 结合使用的调度器,用于去噪编码的图像潜在表示。可以是
[`DDIMScheduler`], [`LMSDiscreteScheduler`] 或 [`PNDMScheduler`] 之一。
requires_aesthetics_score (`bool`, *optional*, defaults to `"False"`):
`unet` 是否需要在推理期间传递审美评分条件。另见
`stabilityai/stable-diffusion-xl-refiner-1-0` 的配置。
force_zeros_for_empty_prompt (`bool`, *optional*, defaults to `"True"`):
是否强制负提示嵌入始终设置为 0。另见
`stabilityai/stable-diffusion-xl-base-1-0` 的配置。
add_watermarker (`bool`, *optional*):
是否使用 [invisible_watermark 库](https://github.com/ShieldMnt/invisible-watermark/) 对输出图像进行水印处理。
如果未定义,且包已安装,则默认为 True,否则不使用水印。
is_cosxl_edit (`bool`, *optional*):
当设置时,图像潜在表示被缩放。
"""
# 定义模型 CPU 卸载顺序的字符串
model_cpu_offload_seq = "text_encoder->text_encoder_2->unet->vae"
# 定义可选组件的列表
_optional_components = ["tokenizer", "tokenizer_2", "text_encoder", "text_encoder_2"]
# 初始化方法,用于创建类的实例
def __init__(
self,
# 自动编码器模型
vae: AutoencoderKL,
# 文本编码器模型
text_encoder: CLIPTextModel,
# 第二个文本编码器模型,带投影
text_encoder_2: CLIPTextModelWithProjection,
# 第一个分词器
tokenizer: CLIPTokenizer,
# 第二个分词器
tokenizer_2: CLIPTokenizer,
# 条件生成的 UNet 模型
unet: UNet2DConditionModel,
# Karras 扩散调度器
scheduler: KarrasDiffusionSchedulers,
# 是否在空提示时强制使用零
force_zeros_for_empty_prompt: bool = True,
# 是否添加水印,默认为 None
add_watermarker: Optional[bool] = None,
# 是否为 cosxl 编辑,默认为 False
is_cosxl_edit: Optional[bool] = False,
):
# 调用父类的初始化方法
super().__init__()
# 注册模型模块
self.register_modules(
vae=vae,
text_encoder=text_encoder,
text_encoder_2=text_encoder_2,
tokenizer=tokenizer,
tokenizer_2=tokenizer_2,
unet=unet,
scheduler=scheduler,
)
# 将参数注册到配置中
self.register_to_config(force_zeros_for_empty_prompt=force_zeros_for_empty_prompt)
# 计算 VAE 的缩放因子
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
# 创建图像处理器
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
# 设置默认样本大小
self.default_sample_size = self.unet.config.sample_size
# 记录是否为 cosxl 编辑
self.is_cosxl_edit = is_cosxl_edit
# 判断是否添加水印,如果未提供,则根据可用性设置
add_watermarker = add_watermarker if add_watermarker is not None else is_invisible_watermark_available()
# 如果需要添加水印,创建水印对象
if add_watermarker:
self.watermark = StableDiffusionXLWatermarker()
else:
# 否则设置为 None
self.watermark = None
# 编码提示的方法
def encode_prompt(
self,
# 输入的提示字符串
prompt: str,
# 可选的第二个提示字符串
prompt_2: Optional[str] = None,
# 可选的设备
device: Optional[torch.device] = None,
# 每个提示生成的图像数量
num_images_per_prompt: int = 1,
# 是否进行无分类器自由引导
do_classifier_free_guidance: bool = True,
# 可选的负面提示字符串
negative_prompt: Optional[str] = None,
# 可选的第二个负面提示字符串
negative_prompt_2: Optional[str] = None,
# 可选的提示嵌入
prompt_embeds: Optional[torch.Tensor] = None,
# 可选的负面提示嵌入
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 可选的池化提示嵌入
pooled_prompt_embeds: Optional[torch.Tensor] = None,
# 可选的负面池化提示嵌入
negative_pooled_prompt_embeds: Optional[torch.Tensor] = None,
# 可选的 LoRA 缩放因子
lora_scale: Optional[float] = None,
# 从 diffusers 中复制的额外步骤参数准备方法
def prepare_extra_step_kwargs(self, generator, eta):
# 准备调度器步骤的额外参数,因为并非所有调度器的签名相同
# eta(η)仅用于 DDIMScheduler,对于其他调度器将被忽略。
# eta 对应于 DDIM 论文中的 η: https://arxiv.org/abs/2010.02502
# 应在 [0, 1] 之间
# 检查调度器是否接受 eta 参数
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
extra_step_kwargs = {}
if accepts_eta:
# 如果接受,添加 eta 参数
extra_step_kwargs["eta"] = eta
# 检查调度器是否接受 generator 参数
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
if accepts_generator:
# 如果接受,添加 generator 参数
extra_step_kwargs["generator"] = generator
# 返回准备好的额外参数
return extra_step_kwargs
# 定义一个检查输入参数的函数
def check_inputs(
self, # 当前对象实例
prompt, # 正向提示词
callback_steps, # 回调步骤的频率
negative_prompt=None, # 负向提示词,可选
prompt_embeds=None, # 正向提示词的嵌入表示,可选
negative_prompt_embeds=None, # 负向提示词的嵌入表示,可选
callback_on_step_end_tensor_inputs=None, # 回调时的张量输入,可选
):
# 检查 callback_steps 是否为正整数
if callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0):
# 如果不是,则抛出值错误
raise ValueError(
f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
f" {type(callback_steps)}."
)
# 检查 callback_on_step_end_tensor_inputs 是否在预定义的输入中
if callback_on_step_end_tensor_inputs is not None and not all(
k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
):
# 如果有不在其中的,则抛出值错误
raise ValueError(
f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
)
# 检查 prompt 和 prompt_embeds 是否同时提供
if prompt is not None and prompt_embeds is not None:
# 如果同时提供,则抛出值错误
raise ValueError(
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
" only forward one of the two."
)
# 检查 prompt 和 prompt_embeds 是否都未提供
elif prompt is None and prompt_embeds is None:
# 如果都未提供,则抛出值错误
raise ValueError(
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
)
# 检查 prompt 的类型是否正确
elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
# 如果类型不正确,则抛出值错误
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
# 检查 negative_prompt 和 negative_prompt_embeds 是否同时提供
if negative_prompt is not None and negative_prompt_embeds is not None:
# 如果同时提供,则抛出值错误
raise ValueError(
f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
)
# 检查 prompt_embeds 和 negative_prompt_embeds 是否同时提供
if prompt_embeds is not None and negative_prompt_embeds is not None:
# 如果它们的形状不同,则抛出值错误
if prompt_embeds.shape != negative_prompt_embeds.shape:
raise ValueError(
"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
f" {negative_prompt_embeds.shape}."
)
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents 复制的代码
# 准备潜在变量的函数,接受批量大小、通道数、高度、宽度等参数
def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None):
# 定义潜在变量的形状,计算后高和宽根据 VAE 的缩放因子调整
shape = (
batch_size,
num_channels_latents,
int(height) // self.vae_scale_factor,
int(width) // self.vae_scale_factor,
)
# 检查生成器是否为列表,并验证其长度是否与批量大小匹配
if isinstance(generator, list) and len(generator) != batch_size:
raise ValueError(
# 抛出错误,说明生成器列表长度与请求的批量大小不一致
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
# 如果没有提供潜在变量,则生成随机潜在变量
if latents is None:
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
else:
# 如果提供了潜在变量,则将其移动到指定的设备上
latents = latents.to(device)
# 按照调度器所需的标准差缩放初始噪声
latents = latents * self.scheduler.init_noise_sigma
# 返回准备好的潜在变量
return latents
# 准备图像潜在变量的函数,接受图像及其他参数
def prepare_image_latents(
self, image, batch_size, num_images_per_prompt, dtype, device, do_classifier_free_guidance, generator=None
# 该代码块的最后一部分开始于这个方法的结束
):
# 检查传入的 image 是否为有效类型,如果不是则抛出错误
if not isinstance(image, (torch.Tensor, PIL.Image.Image, list)):
raise ValueError(
# 报告 image 的实际类型
f"`image` has to be of type `torch.Tensor`, `PIL.Image.Image` or list but is {type(image)}"
)
# 将 image 移动到指定设备并转换为指定数据类型
image = image.to(device=device, dtype=dtype)
# 根据每个提示的图像数量计算批大小
batch_size = batch_size * num_images_per_prompt
# 如果 image 的通道数为 4,则直接使用它作为潜在表示
if image.shape[1] == 4:
image_latents = image
else:
# 确保 VAE 在 float32 模式下,以避免在 float16 中溢出
needs_upcasting = self.vae.dtype == torch.float16 and self.vae.config.force_upcast
if needs_upcasting:
# 将图像转换为 float32
image = image.float()
# 执行 VAE 的上采样
self.upcast_vae()
# 编码图像并获取潜在表示
image_latents = retrieve_latents(self.vae.encode(image), sample_mode="argmax")
# 如果需要,将 VAE 数据类型转换回 fp16
if needs_upcasting:
self.vae.to(dtype=torch.float16)
# 如果批大小大于潜在表示的数量且可以整除
if batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] == 0:
# 扩展潜在表示以匹配批大小
deprecation_message = (
# 提示用户传入的提示数量与图像数量不匹配
f"You have passed {batch_size} text prompts (`prompt`), but only {image_latents.shape[0]} initial"
" images (`image`). Initial images are now duplicating to match the number of text prompts. Note"
" that this behavior is deprecated and will be removed in a version 1.0.0. Please make sure to update"
" your script to pass as many initial images as text prompts to suppress this warning."
)
# 发出弃用警告
deprecate("len(prompt) != len(image)", "1.0.0", deprecation_message, standard_warn=False)
# 计算每个提示所需的额外图像数量
additional_image_per_prompt = batch_size // image_latents.shape[0]
# 复制潜在表示以匹配批大小
image_latents = torch.cat([image_latents] * additional_image_per_prompt, dim=0)
# 如果批大小大于潜在表示的数量但不能整除
elif batch_size > image_latents.shape[0] and batch_size % image_latents.shape[0] != 0:
# 抛出错误,说明无法复制图像
raise ValueError(
# 报告无法复制的批大小
f"Cannot duplicate `image` of batch size {image_latents.shape[0]} to {batch_size} text prompts."
)
else:
# 将潜在表示扩展到批次维度
image_latents = torch.cat([image_latents], dim=0)
# 如果使用无分类器自由引导
if do_classifier_free_guidance:
# 创建与潜在表示相同形状的零张量
uncond_image_latents = torch.zeros_like(image_latents)
# 将潜在表示与无条件潜在表示合并
image_latents = torch.cat([image_latents, image_latents, uncond_image_latents], dim=0)
# 如果潜在表示的数据类型与 VAE 不匹配,则进行转换
if image_latents.dtype != self.vae.dtype:
image_latents = image_latents.to(dtype=self.vae.dtype)
# 如果为 COSXL 编辑模式,则应用缩放因子
if self.is_cosxl_edit:
image_latents = image_latents * self.vae.config.scaling_factor
# 返回最终的潜在表示
return image_latents
# 该方法用于获取附加时间 ID
# 复制自 diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl.StableDiffusionXLPipeline._get_add_time_ids
def _get_add_time_ids(
# 定义方法所需的参数
self, original_size, crops_coords_top_left, target_size, dtype, text_encoder_projection_dim=None
# 定义函数的结束部分
):
# 创建包含原始大小、裁剪坐标和目标大小的列表
add_time_ids = list(original_size + crops_coords_top_left + target_size)
# 计算传入的附加时间嵌入维度
passed_add_embed_dim = (
self.unet.config.addition_time_embed_dim * len(add_time_ids) + text_encoder_projection_dim
)
# 获取模型预期的附加嵌入维度
expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features
# 检查预期和实际的嵌入维度是否一致
if expected_add_embed_dim != passed_add_embed_dim:
# 如果不一致,抛出错误并提供相关信息
raise ValueError(
f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. The model has an incorrect config. Please check `unet.config.time_embedding_type` and `text_encoder_2.config.projection_dim`."
)
# 将时间 ID 转换为张量,指定数据类型
add_time_ids = torch.tensor([add_time_ids], dtype=dtype)
# 返回添加的时间 ID 张量
return add_time_ids
# 从 diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl.StableDiffusionXLPipeline.upcast_vae 复制
def upcast_vae(self):
# 获取 VAE 的数据类型
dtype = self.vae.dtype
# 将 VAE 转换为 float32 类型
self.vae.to(dtype=torch.float32)
# 检查是否使用了 Torch 2.0 或 Xformers
use_torch_2_0_or_xformers = isinstance(
self.vae.decoder.mid_block.attentions[0].processor,
(
AttnProcessor2_0,
XFormersAttnProcessor,
FusedAttnProcessor2_0,
),
)
# 如果使用 xformers 或 torch_2_0,注意力模块不需要使用 float32,以节省内存
if use_torch_2_0_or_xformers:
# 将后量化卷积转换为指定数据类型
self.vae.post_quant_conv.to(dtype)
# 将输入卷积转换为指定数据类型
self.vae.decoder.conv_in.to(dtype)
# 将中间块转换为指定数据类型
self.vae.decoder.mid_block.to(dtype)
# 关闭梯度计算
@torch.no_grad()
# 替换示例文档字符串
@replace_example_docstring(EXAMPLE_DOC_STRING)
# 定义可调用对象的方法,接受多个参数进行处理
def __call__(
# 输入提示,可以是字符串或字符串列表
self,
prompt: Union[str, List[str]] = None,
# 第二个提示,默认为 None
prompt_2: Optional[Union[str, List[str]]] = None,
# 输入图像,类型为 PipelineImageInput,默认为 None
image: PipelineImageInput = None,
# 图像高度,默认为 None
height: Optional[int] = None,
# 图像宽度,默认为 None
width: Optional[int] = None,
# 推理步骤数,默认为 100
num_inference_steps: int = 100,
# 去噪结束的浮点值,默认为 None
denoising_end: Optional[float] = None,
# 引导比例,默认为 5.0
guidance_scale: float = 5.0,
# 图像引导比例,默认为 1.5
image_guidance_scale: float = 1.5,
# 负面提示,可以是字符串或字符串列表,默认为 None
negative_prompt: Optional[Union[str, List[str]]] = None,
# 第二个负面提示,默认为 None
negative_prompt_2: Optional[Union[str, List[str]]] = None,
# 每个提示生成的图像数量,默认为 1
num_images_per_prompt: Optional[int] = 1,
# ETA 参数,默认为 0.0
eta: float = 0.0,
# 随机生成器,可以是 torch.Generator 或其列表,默认为 None
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 潜在张量,默认为 None
latents: Optional[torch.Tensor] = None,
# 提示嵌入,默认为 None
prompt_embeds: Optional[torch.Tensor] = None,
# 负面提示嵌入,默认为 None
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 池化的提示嵌入,默认为 None
pooled_prompt_embeds: Optional[torch.Tensor] = None,
# 负面的池化提示嵌入,默认为 None
negative_pooled_prompt_embeds: Optional[torch.Tensor] = None,
# 输出类型,默认为 "pil"
output_type: Optional[str] = "pil",
# 返回字典的标志,默认为 True
return_dict: bool = True,
# 回调函数,默认为 None
callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
# 回调步骤,默认为 1
callback_steps: int = 1,
# 交叉注意力的关键字参数,默认为 None
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 引导重标定,默认为 0.0
guidance_rescale: float = 0.0,
# 原始图像大小,默认为 None
original_size: Tuple[int, int] = None,
# 左上角裁剪坐标,默认为 (0, 0)
crops_coords_top_left: Tuple[int, int] = (0, 0),
# 目标图像大小,默认为 None
target_size: Tuple[int, int] = None,
.\diffusers\pipelines\stable_diffusion_xl\watermark.py
# 导入 numpy 库,用于数值计算
import numpy as np
# 导入 torch 库,用于深度学习操作
import torch
# 从上级目录导入 is_invisible_watermark_available 函数,用于检查水印功能是否可用
from ...utils import is_invisible_watermark_available
# 如果水印功能可用,则导入 WatermarkEncoder 类
if is_invisible_watermark_available():
from imwatermark import WatermarkEncoder
# 定义水印信息的二进制表示
WATERMARK_MESSAGE = 0b101100111110110010010000011110111011000110011110
# 将水印信息转换为二进制字符串,并将每个比特转换为 0/1 的整数列表
WATERMARK_BITS = [int(bit) for bit in bin(WATERMARK_MESSAGE)[2:]]
# 创建一个用于水印的类
class StableDiffusionXLWatermarker:
# 初始化方法
def __init__(self):
# 设置水印比特
self.watermark = WATERMARK_BITS
# 创建水印编码器实例
self.encoder = WatermarkEncoder()
# 使用比特设置水印
self.encoder.set_watermark("bits", self.watermark)
# 应用水印的方法,接收图像张量作为输入
def apply_watermark(self, images: torch.Tensor):
# 如果图像尺寸小于 256,则不能编码,直接返回原图像
if images.shape[-1] < 256:
return images
# 将图像标准化到 0-255,并调整维度顺序
images = (255 * (images / 2 + 0.5)).cpu().permute(0, 2, 3, 1).float().numpy()
# 将 RGB 图像转换为 BGR,以符合水印编码器的通道顺序
images = images[:, :, :, ::-1]
# 添加水印并将 BGR 图像转换回 RGB
images = [self.encoder.encode(image, "dwtDct")[:, :, ::-1] for image in images]
# 将列表转换为 numpy 数组
images = np.array(images)
# 将 numpy 数组转换回 torch 张量,并调整维度顺序
images = torch.from_numpy(images).permute(0, 3, 1, 2)
# 将图像数值重新标准化到 [-1, 1] 范围
images = torch.clamp(2 * (images / 255 - 0.5), min=-1.0, max=1.0)
# 返回处理后的图像张量
return images
.\diffusers\pipelines\stable_diffusion_xl\__init__.py
# 导入类型检查相关的模块
from typing import TYPE_CHECKING
# 从上层目录导入工具函数和常量
from ...utils import (
# 慢导入的常量
DIFFUSERS_SLOW_IMPORT,
# 可选依赖不可用时的异常类
OptionalDependencyNotAvailable,
# 懒加载模块的工具
_LazyModule,
# 从模块中获取对象的工具函数
get_objects_from_module,
# 检查 Flax 库是否可用的工具
is_flax_available,
# 检查 Torch 库是否可用的工具
is_torch_available,
# 检查 Transformers 库是否可用的工具
is_transformers_available,
)
# 存储虚拟对象的字典
_dummy_objects = {}
# 存储额外导入的字典
_additional_imports = {}
# 初始化导入结构,定义模块的导入内容
_import_structure = {"pipeline_output": ["StableDiffusionXLPipelineOutput"]}
# 检查 Transformers 和 Flax 库是否可用
if is_transformers_available() and is_flax_available():
# 如果都可用,向导入结构中添加 Flax 的输出类
_import_structure["pipeline_output"].extend(["FlaxStableDiffusionXLPipelineOutput"])
try:
# 检查 Transformers 和 Torch 库是否可用
if not (is_transformers_available() and is_torch_available()):
# 如果不可用,抛出异常
raise OptionalDependencyNotAvailable()
# 捕获可选依赖不可用的异常
except OptionalDependencyNotAvailable:
# 从工具模块导入虚拟的 Torch 和 Transformers 对象
from ...utils import dummy_torch_and_transformers_objects # noqa F403
# 更新虚拟对象字典,获取并添加虚拟对象
_dummy_objects.update(get_objects_from_module(dummy_torch_and_transformers_objects))
else:
# 如果可用,向导入结构中添加 Stable Diffusion XL 的管道
_import_structure["pipeline_stable_diffusion_xl"] = ["StableDiffusionXLPipeline"]
_import_structure["pipeline_stable_diffusion_xl_img2img"] = ["StableDiffusionXLImg2ImgPipeline"]
_import_structure["pipeline_stable_diffusion_xl_inpaint"] = ["StableDiffusionXLInpaintPipeline"]
_import_structure["pipeline_stable_diffusion_xl_instruct_pix2pix"] = ["StableDiffusionXLInstructPix2PixPipeline"]
# 检查 Transformers 和 Flax 库是否可用
if is_transformers_available() and is_flax_available():
# 从 Flax 的调度模块导入 PNDM 调度器的状态
from ...schedulers.scheduling_pndm_flax import PNDMSchedulerState
# 更新额外导入字典,添加调度器状态
_additional_imports.update({"PNDMSchedulerState": PNDMSchedulerState})
# 向导入结构中添加 Flax 的 Stable Diffusion XL 管道
_import_structure["pipeline_flax_stable_diffusion_xl"] = ["FlaxStableDiffusionXLPipeline"]
# 如果正在进行类型检查或慢导入
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
try:
# 检查 Transformers 和 Torch 库是否可用
if not (is_transformers_available() and is_torch_available()):
# 如果不可用,抛出异常
raise OptionalDependencyNotAvailable()
# 捕获可选依赖不可用的异常
except OptionalDependencyNotAvailable:
# 从工具模块导入虚拟的 Torch 和 Transformers 对象
from ...utils.dummy_torch_and_transformers_objects import * # noqa F403
else:
# 如果可用,导入 Stable Diffusion XL 的管道
from .pipeline_stable_diffusion_xl import StableDiffusionXLPipeline
from .pipeline_stable_diffusion_xl_img2img import StableDiffusionXLImg2ImgPipeline
from .pipeline_stable_diffusion_xl_inpaint import StableDiffusionXLInpaintPipeline
from .pipeline_stable_diffusion_xl_instruct_pix2pix import StableDiffusionXLInstructPix2PixPipeline
try:
# 检查 Transformers 和 Flax 库是否可用
if not (is_transformers_available() and is_flax_available()):
# 如果不可用,抛出异常
raise OptionalDependencyNotAvailable()
# 捕获可选依赖不可用的异常
except OptionalDependencyNotAvailable:
# 从工具模块导入虚拟的 Flax 对象
from ...utils.dummy_flax_objects import *
else:
# 如果可用,导入 Flax 的 Stable Diffusion XL 管道
from .pipeline_flax_stable_diffusion_xl import (
FlaxStableDiffusionXLPipeline,
)
# 从输出模块导入 Flax 的 Stable Diffusion XL 输出类
from .pipeline_output import FlaxStableDiffusionXLPipelineOutput
# 如果不是进行类型检查或慢导入
else:
# 导入系统模块
import sys
# 用懒加载模块替换当前模块
sys.modules[__name__] = _LazyModule(
__name__,
globals()["__file__"],
_import_structure,
module_spec=__spec__,
)
# 将虚拟对象字典中的对象添加到当前模块
for name, value in _dummy_objects.items():
setattr(sys.modules[__name__], name, value)
# 遍历额外导入的字典,获取每个名称和值
for name, value in _additional_imports.items():
# 将值设置为当前模块中的属性,使用名称作为属性名
setattr(sys.modules[__name__], name, value)
.\diffusers\pipelines\stable_video_diffusion\pipeline_stable_video_diffusion.py
# 版权声明,声明此文件的版权属于 HuggingFace 团队
#
# 根据 Apache 许可证第 2.0 版("许可证")授权;
# 除非遵循许可证,否则您不能使用此文件。
# 您可以在以下地址获取许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面同意,按照许可证分发的软件
# 是按 "原样" 基础分发,不提供任何形式的保证或条件。
# 有关许可证所涵盖的特定权限和限制,请参见许可证。
import inspect # 导入 inspect 模块以检查对象的信息
from dataclasses import dataclass # 从 dataclasses 模块导入 dataclass 装饰器,用于创建数据类
from typing import Callable, Dict, List, Optional, Union # 导入类型提示相关的类型
import numpy as np # 导入 numpy 库,用于数组和数值计算
import PIL.Image # 导入 PIL.Image 模块,用于处理图像
import torch # 导入 PyTorch 库,用于深度学习
from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection # 从 transformers 导入 CLIP 相关的处理器和模型
from ...image_processor import PipelineImageInput # 从当前包导入 PipelineImageInput 类
from ...models import AutoencoderKLTemporalDecoder, UNetSpatioTemporalConditionModel # 导入特定模型类
from ...schedulers import EulerDiscreteScheduler # 导入调度器类
from ...utils import BaseOutput, logging, replace_example_docstring # 导入工具类和函数
from ...utils.torch_utils import is_compiled_module, randn_tensor # 导入与 PyTorch 相关的工具函数
from ...video_processor import VideoProcessor # 导入视频处理器类
from ..pipeline_utils import DiffusionPipeline # 导入扩散管道类
logger = logging.get_logger(__name__) # 获取日志记录器,使用当前模块的名称
EXAMPLE_DOC_STRING = """ # 定义示例文档字符串
Examples: # 示例部分
```py
>>> from diffusers import StableVideoDiffusionPipeline # 从 diffusers 导入管道
>>> from diffusers.utils import load_image, export_to_video # 导入加载图像和导出视频的工具
>>> pipe = StableVideoDiffusionPipeline.from_pretrained( # 从预训练模型创建管道
... "stabilityai/stable-video-diffusion-img2vid-xt", torch_dtype=torch.float16, variant="fp16" # 指定模型名称和类型
... )
>>> pipe.to("cuda") # 将管道移到 GPU
>>> image = load_image( # 加载图像
... "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/svd-docstring-example.jpeg" # 指定图像 URL
... )
>>> image = image.resize((1024, 576)) # 调整图像大小
>>> frames = pipe(image, num_frames=25, decode_chunk_size=8).frames[0] # 生成视频帧
>>> export_to_video(frames, "generated.mp4", fps=7) # 导出帧为视频文件
```py
""" # 结束示例文档字符串
def _append_dims(x, target_dims): # 定义一个私有函数,用于向张量添加维度
"""Appends dimensions to the end of a tensor until it has target_dims dimensions.""" # 函数说明,描述其功能
dims_to_append = target_dims - x.ndim # 计算需要添加的维度数量
if dims_to_append < 0: # 如果目标维度小于当前维度
raise ValueError(f"input has {x.ndim} dims but target_dims is {target_dims}, which is less") # 抛出值错误
return x[(...,) + (None,) * dims_to_append] # 在张量末尾添加所需数量的维度
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion 导入的函数
def retrieve_timesteps( # 定义一个函数,用于检索时间步
scheduler, # 调度器对象
num_inference_steps: Optional[int] = None, # 推理步骤的可选参数
device: Optional[Union[str, torch.device]] = None, # 可选的设备参数
timesteps: Optional[List[int]] = None, # 可选的时间步列表
sigmas: Optional[List[float]] = None, # 可选的 sigma 值列表
**kwargs, # 接收其他关键字参数
):
""" # 函数说明,描述其功能
Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles # 调用调度器的 `set_timesteps` 方法并检索时间步
custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. # 处理自定义时间步,其他关键字参数传递给 `scheduler.set_timesteps`
# 参数说明
Args:
scheduler (`SchedulerMixin`): # 调度器,用于获取时间步
The scheduler to get timesteps from.
num_inference_steps (`int`): # 生成样本时使用的扩散步骤数
The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`
must be `None`.
device (`str` or `torch.device`, *optional*): # 指定时间步移动的设备
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
timesteps (`List[int]`, *optional*): # 自定义时间步以覆盖调度器的时间步间距策略
Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,
`num_inference_steps` and `sigmas` must be `None`.
sigmas (`List[float]`, *optional*): # 自定义sigmas以覆盖调度器的时间步间距策略
Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,
`num_inference_steps` and `timesteps` must be `None`.
Returns:
`Tuple[torch.Tensor, int]`: 返回一个元组,包含调度器的时间步调度和推理步骤数
"""
# 检查是否同时传入了时间步和sigmas
if timesteps is not None and sigmas is not None:
raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
# 如果提供了时间步
if timesteps is not None:
# 检查当前调度器是否支持自定义时间步
accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
if not accepts_timesteps:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" timestep schedules. Please check whether you are using the correct scheduler."
)
# 设置时间步
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
# 获取设置后的时间步
timesteps = scheduler.timesteps
# 计算推理步骤数
num_inference_steps = len(timesteps)
# 如果提供了sigmas
elif sigmas is not None:
# 检查当前调度器是否支持自定义sigmas
accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
if not accept_sigmas:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" sigmas schedules. Please check whether you are using the correct scheduler."
)
# 设置sigmas
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
# 获取设置后的时间步
timesteps = scheduler.timesteps
# 计算推理步骤数
num_inference_steps = len(timesteps)
# 如果没有提供时间步和sigmas
else:
# 根据推理步骤数设置时间步
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
# 获取设置后的时间步
timesteps = scheduler.timesteps
# 返回时间步和推理步骤数
return timesteps, num_inference_steps
# 定义 StableVideoDiffusionPipelineOutput 类,继承自 BaseOutput
@dataclass
class StableVideoDiffusionPipelineOutput(BaseOutput):
r"""
Output class for Stable Video Diffusion pipeline.
Args:
frames (`[List[List[PIL.Image.Image]]`, `np.ndarray`, `torch.Tensor`]):
List of denoised PIL images of length `batch_size` or numpy array or torch tensor of shape `(batch_size,
num_frames, height, width, num_channels)`.
"""
# 定义输出属性 frames,可以是多种类型:嵌套 PIL 图像列表、numpy 数组或 torch 张量
frames: Union[List[List[PIL.Image.Image]], np.ndarray, torch.Tensor]
# 定义 StableVideoDiffusionPipeline 类,继承自 DiffusionPipeline
class StableVideoDiffusionPipeline(DiffusionPipeline):
r"""
Pipeline to generate video from an input image using Stable Video Diffusion.
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods
implemented for all pipelines (downloading, saving, running on a particular device, etc.).
Args:
vae ([`AutoencoderKLTemporalDecoder`]):
Variational Auto-Encoder (VAE) model to encode and decode images to and from latent representations.
image_encoder ([`~transformers.CLIPVisionModelWithProjection`]):
Frozen CLIP image-encoder
([laion/CLIP-ViT-H-14-laion2B-s32B-b79K](https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K)).
unet ([`UNetSpatioTemporalConditionModel`]):
A `UNetSpatioTemporalConditionModel` to denoise the encoded image latents.
scheduler ([`EulerDiscreteScheduler`]):
A scheduler to be used in combination with `unet` to denoise the encoded image latents.
feature_extractor ([`~transformers.CLIPImageProcessor`]):
A `CLIPImageProcessor` to extract features from generated images.
"""
# 定义模型的 CPU 卸载顺序,用于优化内存使用
model_cpu_offload_seq = "image_encoder->unet->vae"
# 定义需要回调的张量输入
_callback_tensor_inputs = ["latents"]
# 初始化方法,定义模型所需的组件
def __init__(
self,
# VAE 模型,用于编码和解码图像
vae: AutoencoderKLTemporalDecoder,
# CLIP 图像编码器,被冻结以提取图像特征
image_encoder: CLIPVisionModelWithProjection,
# 用于去噪的 UNet 模型
unet: UNetSpatioTemporalConditionModel,
# 用于调度的 Euler 离散调度器
scheduler: EulerDiscreteScheduler,
# 图像特征提取器
feature_extractor: CLIPImageProcessor,
):
# 调用父类初始化方法
super().__init__()
# 注册模型组件
self.register_modules(
vae=vae,
image_encoder=image_encoder,
unet=unet,
scheduler=scheduler,
feature_extractor=feature_extractor,
)
# 计算 VAE 的缩放因子
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
# 创建视频处理器实例,启用图像缩放
self.video_processor = VideoProcessor(do_resize=True, vae_scale_factor=self.vae_scale_factor)
# 定义图像编码方法
def _encode_image(
self,
# 输入图像,类型为 PipelineImageInput
image: PipelineImageInput,
# 设备类型,字符串或 torch.device
device: Union[str, torch.device],
# 每个提示生成的视频数量
num_videos_per_prompt: int,
# 是否进行无分类器自由引导
do_classifier_free_guidance: bool,
# 返回类型为 torch.Tensor 的函数
) -> torch.Tensor:
# 获取图像编码器参数的 dtype
dtype = next(self.image_encoder.parameters()).dtype
# 如果输入的图像不是 torch.Tensor 类型
if not isinstance(image, torch.Tensor):
# 将 PIL 图像转换为 NumPy 数组
image = self.video_processor.pil_to_numpy(image)
# 将 NumPy 数组转换为 PyTorch 张量
image = self.video_processor.numpy_to_pt(image)
# 在调整大小之前对图像进行归一化,以匹配原始实现
# 然后在调整大小后进行反归一化
image = image * 2.0 - 1.0
# 使用抗锯齿算法调整图像大小到 (224, 224)
image = _resize_with_antialiasing(image, (224, 224))
# 反归一化图像
image = (image + 1.0) / 2.0
# 使用 CLIP 输入对图像进行归一化
image = self.feature_extractor(
# 输入图像
images=image,
# 是否进行归一化
do_normalize=True,
# 是否进行中心裁剪
do_center_crop=False,
# 是否调整大小
do_resize=False,
# 是否重新缩放
do_rescale=False,
# 返回的张量类型
return_tensors="pt",
).pixel_values
# 将图像移动到指定设备并设置数据类型
image = image.to(device=device, dtype=dtype)
# 使用图像编码器生成图像嵌入
image_embeddings = self.image_encoder(image).image_embeds
# 在第 1 维上增加一个维度
image_embeddings = image_embeddings.unsqueeze(1)
# 针对每个提示的生成复制图像嵌入,使用适合 MPS 的方法
bs_embed, seq_len, _ = image_embeddings.shape
# 重复图像嵌入,针对每个提示的生成
image_embeddings = image_embeddings.repeat(1, num_videos_per_prompt, 1)
# 将嵌入形状调整为适合的格式
image_embeddings = image_embeddings.view(bs_embed * num_videos_per_prompt, seq_len, -1)
# 如果需要分类器自由引导
if do_classifier_free_guidance:
# 创建与图像嵌入相同形状的零张量
negative_image_embeddings = torch.zeros_like(image_embeddings)
# 对于分类器自由引导,我们需要进行两次前向传播
# 在这里,我们将无条件和文本嵌入拼接成一个批次
# 以避免进行两次前向传播
image_embeddings = torch.cat([negative_image_embeddings, image_embeddings])
# 返回图像嵌入
return image_embeddings
# 定义一个编码 VAE 图像的函数
def _encode_vae_image(
self,
# 输入图像张量
image: torch.Tensor,
# 设备类型
device: Union[str, torch.device],
# 每个提示的视频数量
num_videos_per_prompt: int,
# 是否进行分类器自由引导
do_classifier_free_guidance: bool,
):
# 将图像移动到指定设备
image = image.to(device=device)
# 使用 VAE 编码器对图像进行编码,获取潜在分布的模式
image_latents = self.vae.encode(image).latent_dist.mode()
# 针对每个提示的生成复制图像潜在,使用适合 MPS 的方法
image_latents = image_latents.repeat(num_videos_per_prompt, 1, 1, 1)
# 如果需要分类器自由引导
if do_classifier_free_guidance:
# 创建与图像潜在相同形状的零张量
negative_image_latents = torch.zeros_like(image_latents)
# 对于分类器自由引导,我们需要进行两次前向传播
# 在这里,我们将无条件和文本嵌入拼接成一个批次
# 以避免进行两次前向传播
image_latents = torch.cat([negative_image_latents, image_latents])
# 返回图像潜在
return image_latents
# 定义一个获取附加时间 ID 的函数
def _get_add_time_ids(
# 帧率
fps: int,
# 动作桶 ID
motion_bucket_id: int,
# 噪声增强强度
noise_aug_strength: float,
# 数据类型
dtype: torch.dtype,
# 批大小
batch_size: int,
# 每个提示的视频数量
num_videos_per_prompt: int,
# 是否进行分类器自由引导
do_classifier_free_guidance: bool,
)
):
# 将 FPS、运动桶 ID 和噪声增强强度放入列表
add_time_ids = [fps, motion_bucket_id, noise_aug_strength]
# 计算传入的时间嵌入维度(乘以添加的时间 ID 数量)
passed_add_embed_dim = self.unet.config.addition_time_embed_dim * len(add_time_ids)
# 获取模型期望的时间嵌入维度
expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features
# 检查期望的和实际的时间嵌入维度是否匹配
if expected_add_embed_dim != passed_add_embed_dim:
# 如果不匹配,则抛出错误,提示配置不正确
raise ValueError(
f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. The model has an incorrect config. Please check `unet.config.time_embedding_type` and `text_encoder_2.config.projection_dim`."
)
# 将添加的时间 ID 转换为张量,指定数据类型
add_time_ids = torch.tensor([add_time_ids], dtype=dtype)
# 重复添加时间 ID,生成与批次大小和每个提示的视频数相同的数量
add_time_ids = add_time_ids.repeat(batch_size * num_videos_per_prompt, 1)
# 如果进行无分类器自由引导
if do_classifier_free_guidance:
# 将添加的时间 ID 与自身连接以形成双倍数量
add_time_ids = torch.cat([add_time_ids, add_time_ids])
# 返回最终的添加时间 ID 张量
return add_time_ids
def decode_latents(self, latents: torch.Tensor, num_frames: int, decode_chunk_size: int = 14):
# 将输入的潜在张量形状从 [batch, frames, channels, height, width] 转换为 [batch*frames, channels, height, width]
latents = latents.flatten(0, 1)
# 使用 VAE 的缩放因子缩放潜在张量
latents = 1 / self.vae.config.scaling_factor * latents
# 确定前向 VAE 函数,如果 VAE 是编译模块,则使用其原始模块的前向方法
forward_vae_fn = self.vae._orig_mod.forward if is_compiled_module(self.vae) else self.vae.forward
# 检查前向函数是否接受 num_frames 参数
accepts_num_frames = "num_frames" in set(inspect.signature(forward_vae_fn).parameters.keys())
# 每次解码 decode_chunk_size 帧以避免内存不足(OOM)
frames = []
# 按照 decode_chunk_size 步长遍历潜在张量
for i in range(0, latents.shape[0], decode_chunk_size):
# 获取当前块中的帧数
num_frames_in = latents[i : i + decode_chunk_size].shape[0]
decode_kwargs = {}
# 如果前向函数接受帧数参数
if accepts_num_frames:
# 仅在期望时传递当前帧数
decode_kwargs["num_frames"] = num_frames_in
# 解码当前潜在块并获取采样结果
frame = self.vae.decode(latents[i : i + decode_chunk_size], **decode_kwargs).sample
# 将解码的帧添加到列表中
frames.append(frame)
# 将所有帧合并为一个张量
frames = torch.cat(frames, dim=0)
# 将帧的形状从 [batch*frames, channels, height, width] 转换为 [batch, channels, frames, height, width]
frames = frames.reshape(-1, num_frames, *frames.shape[1:]).permute(0, 2, 1, 3, 4)
# 将帧转换为 float32 类型,以确保兼容性并避免显著开销
frames = frames.float()
# 返回最终解码的帧
return frames
def check_inputs(self, image, height, width):
# 检查输入图像类型是否有效
if (
not isinstance(image, torch.Tensor)
and not isinstance(image, PIL.Image.Image)
and not isinstance(image, list)
):
# 如果无效,则抛出错误,说明类型不匹配
raise ValueError(
"`image` has to be of type `torch.Tensor` or `PIL.Image.Image` or `List[PIL.Image.Image]` but is"
f" {type(image)}"
)
# 检查高度和宽度是否为8的倍数
if height % 8 != 0 or width % 8 != 0:
# 如果不是,则抛出错误
raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
# 准备潜在变量,返回适合输入的张量
def prepare_latents(
self,
# 批次大小
batch_size: int,
# 帧数
num_frames: int,
# 潜在变量的通道数
num_channels_latents: int,
# 图像高度
height: int,
# 图像宽度
width: int,
# 数据类型
dtype: torch.dtype,
# 设备类型(CPU或GPU)
device: Union[str, torch.device],
# 随机数生成器
generator: torch.Generator,
# 可选的潜在变量张量
latents: Optional[torch.Tensor] = None,
):
# 定义潜在变量的形状
shape = (
batch_size,
num_frames,
num_channels_latents // 2,
height // self.vae_scale_factor,
width // self.vae_scale_factor,
)
# 检查生成器列表的长度是否与批次大小匹配
if isinstance(generator, list) and len(generator) != batch_size:
raise ValueError(
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
# 如果潜在变量为空,生成随机张量
if latents is None:
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
else:
# 将给定的潜在变量移动到指定设备
latents = latents.to(device)
# 根据调度器所需的标准差缩放初始噪声
latents = latents * self.scheduler.init_noise_sigma
# 返回准备好的潜在变量
return latents
# 属性:获取引导比例
@property
def guidance_scale(self):
return self._guidance_scale
# `guidance_scale` 属性定义与Imagen论文中公式(2)的引导权重`w`类似
# `guidance_scale = 1`表示不进行分类器自由引导
@property
def do_classifier_free_guidance(self):
# 检查引导比例是否为整数或浮点数
if isinstance(self.guidance_scale, (int, float)):
return self.guidance_scale > 1
# 如果是张量,检查最大值是否大于1
return self.guidance_scale.max() > 1
# 属性:获取时间步数
@property
def num_timesteps(self):
return self._num_timesteps
# 不计算梯度的函数,用于调用模型
@torch.no_grad()
@replace_example_docstring(EXAMPLE_DOC_STRING)
def __call__(
# 输入图像,可以是单个图像、图像列表或张量
image: Union[PIL.Image.Image, List[PIL.Image.Image], torch.Tensor],
# 图像高度(默认为576)
height: int = 576,
# 图像宽度(默认为1024)
width: int = 1024,
# 可选的帧数
num_frames: Optional[int] = None,
# 推理步骤数(默认为25)
num_inference_steps: int = 25,
# 可选的噪声参数列表
sigmas: Optional[List[float]] = None,
# 最小引导比例(默认为1.0)
min_guidance_scale: float = 1.0,
# 最大引导比例(默认为3.0)
max_guidance_scale: float = 3.0,
# 帧率(默认为7)
fps: int = 7,
# 运动桶的ID(默认为127)
motion_bucket_id: int = 127,
# 噪声增强强度(默认为0.02)
noise_aug_strength: float = 0.02,
# 可选的解码块大小
decode_chunk_size: Optional[int] = None,
# 每个提示生成的视频数量(默认为1)
num_videos_per_prompt: Optional[int] = 1,
# 可选的随机数生成器
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 可选的潜在变量张量
latents: Optional[torch.Tensor] = None,
# 输出类型(默认为"pil")
output_type: Optional[str] = "pil",
# 每个步骤结束时的回调函数
callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
# 回调函数输入张量的列表(默认为["latents"])
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
# 返回字典格式的标志(默认为True)
return_dict: bool = True,
# 图像缩放工具
# TODO: 稍后清理
def _resize_with_antialiasing(input, size, interpolation="bicubic", align_corners=True):
# 获取输入图像的高度和宽度
h, w = input.shape[-2:]
# 计算高度和宽度的缩放因子
factors = (h / size[0], w / size[1])
# 首先,我们必须确定 sigma
# 取自 skimage: https://github.com/scikit-image/scikit-image/blob/v0.19.2/skimage/transform/_warps.py#L171
sigmas = (
max((factors[0] - 1.0) / 2.0, 0.001), # 计算高度方向的 sigma,确保不小于 0.001
max((factors[1] - 1.0) / 2.0, 0.001), # 计算宽度方向的 sigma,确保不小于 0.001
)
# 现在计算卷积核大小。对于 3 sigma 得到较好的结果,但速度较慢。Pillow 使用 1 sigma
# https://github.com/python-pillow/Pillow/blob/master/src/libImaging/Resample.c#L206
# 但他们用两次传递,得到更好的结果。暂时尝试 2 sigmas
ks = int(max(2.0 * 2 * sigmas[0], 3)), int(max(2.0 * 2 * sigmas[1], 3))
# 确保卷积核大小是奇数
if (ks[0] % 2) == 0:
ks = ks[0] + 1, ks[1] # 如果高度为偶数,增加 1 使其为奇数
if (ks[1] % 2) == 0:
ks = ks[0], ks[1] + 1 # 如果宽度为偶数,增加 1 使其为奇数
# 对输入图像应用高斯模糊
input = _gaussian_blur2d(input, ks, sigmas)
# 使用插值方法调整图像大小
output = torch.nn.functional.interpolate(input, size=size, mode=interpolation, align_corners=align_corners)
return output # 返回调整大小后的图像
def _compute_padding(kernel_size):
"""计算填充元组。"""
# 4 或 6 个整数: (左填充, 右填充, 上填充, 下填充)
# https://pytorch.org/docs/stable/nn.html#torch.nn.functional.pad
if len(kernel_size) < 2:
raise AssertionError(kernel_size) # 如果卷积核大小小于 2,抛出异常
computed = [k - 1 for k in kernel_size] # 计算每个维度的填充量
# 对于偶数大小的卷积核,我们需要不对称填充 :(
out_padding = 2 * len(kernel_size) * [0] # 初始化填充数组
for i in range(len(kernel_size)):
computed_tmp = computed[-(i + 1)] # 取最后一个计算的填充量
pad_front = computed_tmp // 2 # 计算前填充
pad_rear = computed_tmp - pad_front # 计算后填充
out_padding[2 * i + 0] = pad_front # 设置前填充
out_padding[2 * i + 1] = pad_rear # 设置后填充
return out_padding # 返回填充元组
def _filter2d(input, kernel):
# 准备卷积核
b, c, h, w = input.shape # 获取输入张量的形状
tmp_kernel = kernel[:, None, ...].to(device=input.device, dtype=input.dtype) # 转换卷积核到正确的设备和数据类型
tmp_kernel = tmp_kernel.expand(-1, c, -1, -1) # 扩展卷积核以匹配输入通道数
height, width = tmp_kernel.shape[-2:] # 获取卷积核的高度和宽度
padding_shape: List[int] = _compute_padding([height, width]) # 计算所需的填充
input = torch.nn.functional.pad(input, padding_shape, mode="reflect") # 对输入进行填充
# 将卷积核和输入张量重塑以对齐逐元素或批处理参数
tmp_kernel = tmp_kernel.reshape(-1, 1, height, width) # 重塑卷积核
input = input.view(-1, tmp_kernel.size(0), input.size(-2), input.size(-1)) # 重塑输入
# 用卷积核对张量进行卷积
output = torch.nn.functional.conv2d(input, tmp_kernel, groups=tmp_kernel.size(0), padding=0, stride=1)
out = output.view(b, c, h, w) # 重塑输出为原始形状
return out # 返回卷积结果
def _gaussian(window_size: int, sigma):
# 如果 sigma 是浮点数,转化为张量
if isinstance(sigma, float):
sigma = torch.tensor([[sigma]])
batch_size = sigma.shape[0] # 获取 sigma 的批大小
# 创建一个 x 张量,用于计算高斯窗口
x = (torch.arange(window_size, device=sigma.device, dtype=sigma.dtype) - window_size // 2).expand(batch_size, -1)
# 如果窗口大小为偶数,调整 x 使其中心偏移
if window_size % 2 == 0:
x = x + 0.5 # 对于偶数大小,增加 0.5 以使其居中
# 计算高斯函数值,使用输入 x 和标准差 sigma
gauss = torch.exp(-x.pow(2.0) / (2 * sigma.pow(2.0)))
# 归一化高斯函数值,确保总和为 1,保持维度不变
return gauss / gauss.sum(-1, keepdim=True)
# 定义一个二次元高斯模糊函数,接受输入图像、卷积核大小和标准差
def _gaussian_blur2d(input, kernel_size, sigma):
# 检查 sigma 是否为元组,如果是,则将其转换为张量
if isinstance(sigma, tuple):
sigma = torch.tensor([sigma], dtype=input.dtype)
# 如果 sigma 不是元组,则将其转换为与输入数据相同的数据类型
else:
sigma = sigma.to(dtype=input.dtype)
# 从 kernel_size 中提取出高和宽,转换为整数
ky, kx = int(kernel_size[0]), int(kernel_size[1])
# 获取 sigma 的第一个维度,表示批量大小
bs = sigma.shape[0]
# 计算 x 方向的高斯核
kernel_x = _gaussian(kx, sigma[:, 1].view(bs, 1))
# 计算 y 方向的高斯核
kernel_y = _gaussian(ky, sigma[:, 0].view(bs, 1))
# 在 x 方向上应用滤波
out_x = _filter2d(input, kernel_x[..., None, :])
# 在 y 方向上应用滤波
out = _filter2d(out_x, kernel_y[..., None])
# 返回模糊处理后的输出
return out
.\diffusers\pipelines\stable_video_diffusion\__init__.py
# 导入类型检查的相关支持
from typing import TYPE_CHECKING
# 从 utils 模块导入多个工具和常量
from ...utils import (
DIFFUSERS_SLOW_IMPORT, # 慢导入标识
BaseOutput, # 基础输出类
OptionalDependencyNotAvailable, # 可选依赖不可用异常
_LazyModule, # 延迟加载模块的工具
get_objects_from_module, # 从模块获取对象的工具
is_torch_available, # 检查 PyTorch 是否可用的函数
is_transformers_available, # 检查 Transformers 是否可用的函数
)
# 初始化一个空字典用于存储虚拟对象
_dummy_objects = {}
# 初始化一个空字典用于存储导入结构
_import_structure = {}
# 尝试检查依赖项的可用性
try:
# 如果 Transformers 和 Torch 都不可用,则抛出异常
if not (is_transformers_available() and is_torch_available()):
raise OptionalDependencyNotAvailable()
# 捕获可选依赖不可用的异常
except OptionalDependencyNotAvailable:
# 导入虚拟的 Torch 和 Transformers 对象
from ...utils import dummy_torch_and_transformers_objects
# 更新虚拟对象字典,填充从虚拟模块获取的对象
_dummy_objects.update(get_objects_from_module(dummy_torch_and_transformers_objects))
# 如果依赖可用,更新导入结构
else:
_import_structure.update(
{
# 添加稳定视频扩散管道及其输出类到导入结构
"pipeline_stable_video_diffusion": [
"StableVideoDiffusionPipeline",
"StableVideoDiffusionPipelineOutput",
],
}
)
# 根据类型检查或慢导入标识执行不同的逻辑
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
# 尝试再次检查依赖项的可用性
try:
# 如果 Transformers 和 Torch 都不可用,则抛出异常
if not (is_transformers_available() and is_torch_available()):
raise OptionalDependencyNotAvailable()
# 捕获可选依赖不可用的异常
except OptionalDependencyNotAvailable:
# 从虚拟对象导入必要的内容
from ...utils.dummy_torch_and_transformers_objects import *
# 如果依赖可用,导入所需的类
else:
from .pipeline_stable_video_diffusion import (
StableVideoDiffusionPipeline, # 导入稳定视频扩散管道类
StableVideoDiffusionPipelineOutput, # 导入稳定视频扩散输出类
)
# 如果不进行类型检查,执行以下逻辑
else:
import sys
# 使用延迟加载模块的工具创建当前模块的替代版本
sys.modules[__name__] = _LazyModule(
__name__,
globals()["__file__"],
_import_structure, # 使用已定义的导入结构
module_spec=__spec__, # 模块规格
)
# 将虚拟对象字典中的对象添加到当前模块
for name, value in _dummy_objects.items():
setattr(sys.modules[__name__], name, value)
.\diffusers\pipelines\t2i_adapter\pipeline_stable_diffusion_adapter.py
# 版权所有声明,包含版权信息和许可证详情
# Copyright 2024 TencentARC and The HuggingFace Team. All rights reserved.
#
# 根据 Apache 许可证第 2.0 版("许可证")授权;
# 除非遵守许可证,否则您不得使用此文件。
# 您可以在以下网址获取许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有约定,软件在许可证下分发时按“原样”提供,
# 不附带任何明示或暗示的保证或条件。
# 有关许可证下的特定权限和限制,请参阅许可证。
import inspect # 导入 inspect 模块以获取对象的内部信息
from dataclasses import dataclass # 从 dataclasses 导入 dataclass 装饰器以简化类定义
from typing import Any, Callable, Dict, List, Optional, Union # 导入类型提示
import numpy as np # 导入 numpy 作为 np,用于数值计算
import PIL.Image # 导入 PIL 的 Image 模块以处理图像
import torch # 导入 PyTorch 库,用于深度学习
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer # 从 transformers 导入相关模型和处理器
from ...image_processor import VaeImageProcessor # 从本地模块导入 VaeImageProcessor
from ...loaders import StableDiffusionLoraLoaderMixin, TextualInversionLoaderMixin # 导入混合类用于加载
from ...models import AutoencoderKL, MultiAdapter, T2IAdapter, UNet2DConditionModel # 导入模型
from ...models.lora import adjust_lora_scale_text_encoder # 导入函数用于调整 LORA 模型的缩放
from ...schedulers import KarrasDiffusionSchedulers # 导入调度器
from ...utils import ( # 从 utils 导入多个工具函数和常量
PIL_INTERPOLATION,
USE_PEFT_BACKEND,
BaseOutput,
deprecate,
logging,
replace_example_docstring,
scale_lora_layers,
unscale_lora_layers,
)
from ...utils.torch_utils import randn_tensor # 从工具模块导入随机张量生成函数
from ..pipeline_utils import DiffusionPipeline, StableDiffusionMixin # 导入扩散管道和混合类
from ..stable_diffusion.safety_checker import StableDiffusionSafetyChecker # 导入安全检查器
@dataclass # 使用 dataclass 装饰器简化数据类定义
class StableDiffusionAdapterPipelineOutput(BaseOutput): # 定义 StableDiffusionAdapterPipelineOutput 类,继承自 BaseOutput
"""
Args:
images (`List[PIL.Image.Image]` or `np.ndarray`)
图像列表,包含去噪后的 PIL 图像,长度为 `batch_size`,或形状为 `(batch_size, height, width,
num_channels)` 的 numpy 数组。PIL 图像或 numpy 数组表示扩散管道生成的去噪图像。
nsfw_content_detected (`List[bool]`)
布尔值标志列表,表示对应生成图像是否可能包含“不安全内容”
(nsfw),如果无法执行安全检查则为 `None`。
"""
images: Union[List[PIL.Image.Image], np.ndarray] # 定义 images 属性,类型为图像列表或 numpy 数组
nsfw_content_detected: Optional[List[bool]] # 定义 nsfw_content_detected 属性,类型为可选布尔列表
logger = logging.get_logger(__name__) # 获取当前模块的日志记录器
# pylint: disable=invalid-name # 禁用 pylint 对无效名称的警告
EXAMPLE_DOC_STRING = """ # 定义示例文档字符串的开始
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
```py # 文档字符串的结束标记
``` # 文档字符串的结束标记
# 示例代码,展示如何使用图像和适配器进行稳定扩散处理
Examples:
```py
# 导入必要的库
>>> from PIL import Image # 导入图像处理库 PIL
>>> from diffusers.utils import load_image # 从 diffusers 库导入加载图像的函数
>>> import torch # 导入 PyTorch 库
>>> from diffusers import StableDiffusionAdapterPipeline, T2IAdapter # 导入稳定扩散适配器和管道
# 加载图像,使用指定的 URL
>>> image = load_image(
... "https://huggingface.co/datasets/diffusers/docs-images/resolve/main/t2i-adapter/color_ref.png" # 指定图像的 URL
... )
# 将加载的图像调整为 8x8 的大小,创建色彩调色板
>>> color_palette = image.resize((8, 8))
# 将色彩调色板调整为 512x512 的大小,使用最近邻插值
>>> color_palette = color_palette.resize((512, 512), resample=Image.Resampling.NEAREST)
# 从预训练的模型中加载 T2IAdapter,指定数据类型为 float16
>>> adapter = T2IAdapter.from_pretrained("TencentARC/t2iadapter_color_sd14v1", torch_dtype=torch.float16)
# 从预训练的稳定扩散模型中加载管道,指定适配器和数据类型
>>> pipe = StableDiffusionAdapterPipeline.from_pretrained(
... "CompVis/stable-diffusion-v1-4", # 指定稳定扩散模型的名称
... adapter=adapter, # 使用上面加载的适配器
... torch_dtype=torch.float16, # 指定数据类型为 float16
... )
# 将管道转移到 GPU
>>> pipe.to("cuda")
# 使用管道生成图像,提供提示和调色板
>>> out_image = pipe(
... "At night, glowing cubes in front of the beach", # 提供生成图像的文本提示
... image=color_palette, # 使用之前调整的色彩调色板
... ).images[0] # 获取生成图像的第一个元素
"""
# 文档字符串,描述该函数的功能和参数
def _preprocess_adapter_image(image, height, width):
# 检查输入是否为 PyTorch 张量,如果是则直接返回
if isinstance(image, torch.Tensor):
return image
# 如果输入是 PIL 图像,则将其放入列表中
elif isinstance(image, PIL.Image.Image):
image = [image]
# 检查列表中的第一个元素是否为 PIL 图像
if isinstance(image[0], PIL.Image.Image):
# 将每个图像调整为指定的高度和宽度,并转换为 NumPy 数组
image = [np.array(i.resize((width, height), resample=PIL_INTERPOLATION["lanczos"])) for i in image]
# 扩展图像维度:如果是 [h, w],则变为 [b, h, w, 1];如果是 [h, w, c],则变为 [b, h, w, c]
image = [
i[None, ..., None] if i.ndim == 2 else i[None, ...] for i in image
] # expand [h, w] or [h, w, c] to [b, h, w, c]
# 沿第一个维度(批次维度)连接所有图像
image = np.concatenate(image, axis=0)
# 转换为浮点数并归一化到 [0, 1]
image = np.array(image).astype(np.float32) / 255.0
# 调整维度顺序为 [b, c, h, w]
image = image.transpose(0, 3, 1, 2)
# 将 NumPy 数组转换为 PyTorch 张量
image = torch.from_numpy(image)
# 如果列表中的第一个元素是 PyTorch 张量
elif isinstance(image[0], torch.Tensor):
# 如果张量的维度是 3,沿第一个维度堆叠
if image[0].ndim == 3:
image = torch.stack(image, dim=0)
# 如果张量的维度是 4,沿第一个维度连接
elif image[0].ndim == 4:
image = torch.cat(image, dim=0)
# 如果维度不正确,则抛出错误
else:
raise ValueError(
f"Invalid image tensor! Expecting image tensor with 3 or 4 dimension, but recive: {image[0].ndim}"
)
# 返回处理后的图像
return image
# 文档字符串,描述该函数的功能和参数
# 从调度器中检索时间步
def retrieve_timesteps(
# 调度器对象,用于获取时间步
scheduler,
# 推理步骤数,指定生成样本时使用的扩散步骤
num_inference_steps: Optional[int] = None,
# 指定将时间步移动到的设备
device: Optional[Union[str, torch.device]] = None,
# 自定义时间步的列表
timesteps: Optional[List[int]] = None,
# 自定义 sigma 的列表
sigmas: Optional[List[float]] = None,
**kwargs,
):
"""
调用调度器的 `set_timesteps` 方法,并在调用后从调度器检索时间步。处理自定义时间步。
所有额外参数将传递给 `scheduler.set_timesteps`。
参数:
scheduler (`SchedulerMixin`):
从中获取时间步的调度器。
num_inference_steps (`int`):
生成样本时使用的扩散步骤数。如果使用,则 `timesteps` 必须为 `None`。
device (`str` 或 `torch.device`, *可选*):
将时间步移动到的设备。如果为 `None`,则不移动时间步。
timesteps (`List[int]`, *可选*):
自定义时间步,覆盖调度器的时间步间隔策略。如果传递 `timesteps`,`num_inference_steps` 和 `sigmas` 必须为 `None`。
sigmas (`List[float]`, *可选*):
自定义 sigma,覆盖调度器的时间步间隔策略。如果传递 `sigmas`,`num_inference_steps` 和 `timesteps` 必须为 `None`。
返回:
`Tuple[torch.Tensor, int]`: 一个元组,第一个元素是来自调度器的时间步安排,第二个元素是推理步骤数。
"""
# 如果同时传递了自定义时间步和 sigma,则抛出错误
if timesteps is not None and sigmas is not None:
raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
# 检查 timesteps 是否不为 None
if timesteps is not None:
# 检查当前调度器的 set_timesteps 方法是否接受 timesteps 参数
accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
# 如果不接受 timesteps,则抛出错误
if not accepts_timesteps:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" timestep schedules. Please check whether you are using the correct scheduler."
)
# 调用调度器的 set_timesteps 方法,设置 timesteps、设备和其他参数
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
# 从调度器中获取当前的 timesteps
timesteps = scheduler.timesteps
# 计算推理步骤的数量
num_inference_steps = len(timesteps)
# 检查 sigmas 是否不为 None
elif sigmas is not None:
# 检查当前调度器的 set_timesteps 方法是否接受 sigmas 参数
accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
# 如果不接受 sigmas,则抛出错误
if not accept_sigmas:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" sigmas schedules. Please check whether you are using the correct scheduler."
)
# 调用调度器的 set_timesteps 方法,设置 sigmas、设备和其他参数
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
# 从调度器中获取当前的 timesteps
timesteps = scheduler.timesteps
# 计算推理步骤的数量
num_inference_steps = len(timesteps)
# 如果 timesteps 和 sigmas 都为 None
else:
# 调用调度器的 set_timesteps 方法,设置推理步骤数量、设备和其他参数
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
# 从调度器中获取当前的 timesteps
timesteps = scheduler.timesteps
# 返回 timesteps 和推理步骤数量
return timesteps, num_inference_steps
# 继承自 DiffusionPipeline 和 StableDiffusionMixin 的稳定扩散适配器管道类
class StableDiffusionAdapterPipeline(DiffusionPipeline, StableDiffusionMixin):
r"""
用于使用增强 T2I-Adapter 的文本到图像生成的管道
https://arxiv.org/abs/2302.08453
此模型继承自 [`DiffusionPipeline`]。请查看超类文档,以了解库为所有管道实现的通用方法
(例如下载或保存、在特定设备上运行等)。
参数:
adapter ([`T2IAdapter`] 或 [`MultiAdapter`] 或 `List[T2IAdapter]`):
在去噪过程中为 unet 提供额外的条件。如果将多个适配器设置为列表,
每个适配器的输出将相加以创建一个组合的额外条件。
adapter_weights (`List[float]`, *可选*, 默认值为 None):
表示每个适配器的输出相加之前所乘的权重的浮点数列表。
vae ([`AutoencoderKL`]):
变分自编码器 (VAE) 模型,用于将图像编码和解码为潜在表示。
text_encoder ([`CLIPTextModel`]):
冻结的文本编码器。稳定扩散使用
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel) 的文本部分,
特别是 [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) 变体。
tokenizer (`CLIPTokenizer`):
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer) 类的标记器。
unet ([`UNet2DConditionModel`]): 条件 U-Net 架构,用于去噪编码图像潜在表示。
scheduler ([`SchedulerMixin`]):
用于与 `unet` 结合使用的调度器,以去噪编码图像潜在表示。可以是
[`DDIMScheduler`], [`LMSDiscreteScheduler`] 或 [`PNDMScheduler`] 之一。
safety_checker ([`StableDiffusionSafetyChecker`]):
分类模块,用于评估生成的图像是否可能被认为是冒犯性或有害的。
请参阅 [模型卡](https://huggingface.co/runwayml/stable-diffusion-v1-5) 以获取详细信息。
feature_extractor ([`CLIPImageProcessor`]):
从生成的图像中提取特征的模型,用作 `safety_checker` 的输入。
"""
# 定义模型 CPU 卸载顺序
model_cpu_offload_seq = "text_encoder->adapter->unet->vae"
# 定义可选组件
_optional_components = ["safety_checker", "feature_extractor"]
# 初始化类的构造函数,接受多个参数以配置模型
def __init__(
self,
# 变分自编码器,用于生成图像
vae: AutoencoderKL,
# 文本编码器,用于将文本转换为嵌入
text_encoder: CLIPTextModel,
# 标记器,用于文本的标记化处理
tokenizer: CLIPTokenizer,
# 条件生成模型,用于生成图像
unet: UNet2DConditionModel,
# 适配器,用于不同类型的输入适配
adapter: Union[T2IAdapter, MultiAdapter, List[T2IAdapter]],
# 调度器,用于控制生成过程的时间步骤
scheduler: KarrasDiffusionSchedulers,
# 安全检查器,用于过滤不安全内容
safety_checker: StableDiffusionSafetyChecker,
# 特征提取器,用于处理输入图像
feature_extractor: CLIPImageProcessor,
# 是否需要安全检查器的标志,默认为 True
requires_safety_checker: bool = True,
):
# 调用父类的构造函数
super().__init__()
# 检查安全检查器是否为 None,并且要求安全检查器
if safety_checker is None and requires_safety_checker:
# 记录警告信息,提醒用户安全检查器已被禁用
logger.warning(
f"You have disabled the safety checker for {self.__class__} by passing `safety_checker=None`. Ensure"
" that you abide to the conditions of the Stable Diffusion license and do not expose unfiltered"
" results in services or applications open to the public. Both the diffusers team and Hugging Face"
" strongly recommend to keep the safety filter enabled in all public facing circumstances, disabling"
" it only for use-cases that involve analyzing network behavior or auditing its results. For more"
" information, please have a look at https://github.com/huggingface/diffusers/pull/254 ."
)
# 检查安全检查器存在,但特征提取器为 None
if safety_checker is not None and feature_extractor is None:
# 抛出错误,要求提供特征提取器
raise ValueError(
"Make sure to define a feature extractor when loading {self.__class__} if you want to use the safety"
" checker. If you do not want to use the safety checker, you can pass `'safety_checker=None'` instead."
)
# 如果适配器是列表或元组,则将其转换为 MultiAdapter 实例
if isinstance(adapter, (list, tuple)):
adapter = MultiAdapter(adapter)
# 注册模型的各个模块
self.register_modules(
vae=vae,
text_encoder=text_encoder,
tokenizer=tokenizer,
unet=unet,
adapter=adapter,
scheduler=scheduler,
safety_checker=safety_checker,
feature_extractor=feature_extractor,
)
# 计算 VAE 的缩放因子
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
# 创建图像处理器实例,用于 VAE 缩放
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
# 将是否需要安全检查器的配置注册到类中
self.register_to_config(requires_safety_checker=requires_safety_checker)
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline._encode_prompt 复制的方法
def _encode_prompt(
# 提示文本,用于生成图像的输入
prompt,
# 设备类型(如 GPU 或 CPU)
device,
# 每个提示生成的图像数量
num_images_per_prompt,
# 是否执行无分类器自由引导
do_classifier_free_guidance,
# 可选的负提示文本,用于反向指导生成
negative_prompt=None,
# 可选的提示嵌入,预先计算的提示表示
prompt_embeds: Optional[torch.Tensor] = None,
# 可选的负提示嵌入,预先计算的负提示表示
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 可选的 LoRA 缩放因子,用于微调
lora_scale: Optional[float] = None,
# 其他关键字参数
**kwargs,
# 声明一个过时警告信息,提醒用户此方法将在未来版本中删除
):
deprecation_message = "`_encode_prompt()` is deprecated and it will be removed in a future version. Use `encode_prompt()` instead. Also, be aware that the output format changed from a concatenated tensor to a tuple."
# 调用 deprecate 函数发出过时警告,指定版本和消息
deprecate("_encode_prompt()", "1.0.0", deprecation_message, standard_warn=False)
# 调用 encode_prompt 方法生成提示嵌入的元组
prompt_embeds_tuple = self.encode_prompt(
# 设置提示内容
prompt=prompt,
# 指定设备类型
device=device,
# 每个提示生成的图像数量
num_images_per_prompt=num_images_per_prompt,
# 是否进行分类自由引导
do_classifier_free_guidance=do_classifier_free_guidance,
# 设置负提示内容
negative_prompt=negative_prompt,
# 设置已有的提示嵌入
prompt_embeds=prompt_embeds,
# 设置已有的负提示嵌入
negative_prompt_embeds=negative_prompt_embeds,
# 设置 LoRA 缩放因子
lora_scale=lora_scale,
# 接收额外的关键字参数
**kwargs,
)
# 为了向后兼容,将元组中的两个元素连接成一个张量
prompt_embeds = torch.cat([prompt_embeds_tuple[1], prompt_embeds_tuple[0]])
# 返回合并后的提示嵌入
return prompt_embeds
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.encode_prompt 复制
def encode_prompt(
# 定义 encode_prompt 方法的参数
self,
prompt,
device,
num_images_per_prompt,
do_classifier_free_guidance,
# 负提示内容的可选参数
negative_prompt=None,
# 提示嵌入的可选参数
prompt_embeds: Optional[torch.Tensor] = None,
# 负提示嵌入的可选参数
negative_prompt_embeds: Optional[torch.Tensor] = None,
# LoRA 缩放因子的可选参数
lora_scale: Optional[float] = None,
# 可选的跳过剪辑参数
clip_skip: Optional[int] = None,
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.run_safety_checker 复制
def run_safety_checker(self, image, device, dtype):
# 检查安全检查器是否为 None
if self.safety_checker is None:
# 如果没有安全检查器,标记为 None
has_nsfw_concept = None
else:
# 如果输入图像是张量格式
if torch.is_tensor(image):
# 后处理图像以便特征提取
feature_extractor_input = self.image_processor.postprocess(image, output_type="pil")
else:
# 如果是其他格式,转换为 PIL 格式
feature_extractor_input = self.image_processor.numpy_to_pil(image)
# 生成安全检查器输入,并将其移动到指定设备
safety_checker_input = self.feature_extractor(feature_extractor_input, return_tensors="pt").to(device)
# 调用安全检查器检查图像和特征输入
image, has_nsfw_concept = self.safety_checker(
images=image, clip_input=safety_checker_input.pixel_values.to(dtype)
)
# 返回处理后的图像和 NSFW 概念标记
return image, has_nsfw_concept
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.decode_latents 复制
# 解码潜在变量并生成图像
def decode_latents(self, latents):
# 定义弃用警告信息,提示用户此方法将在1.0.0中移除
deprecation_message = "The decode_latents method is deprecated and will be removed in 1.0.0. Please use VaeImageProcessor.postprocess(...) instead"
# 调用弃用警告函数,标记此方法已弃用
deprecate("decode_latents", "1.0.0", deprecation_message, standard_warn=False)
# 调整潜在变量的尺度以便解码
latents = 1 / self.vae.config.scaling_factor * latents
# 解码潜在变量,返回图像数据
image = self.vae.decode(latents, return_dict=False)[0]
# 将图像数据归一化到[0, 1]范围
image = (image / 2 + 0.5).clamp(0, 1)
# 将图像数据转换为float32,保证兼容性
image = image.cpu().permute(0, 2, 3, 1).float().numpy()
# 返回生成的图像
return image
# 准备额外步骤的参数,用于调度器
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs
def prepare_extra_step_kwargs(self, generator, eta):
# 准备调度器步骤的额外参数,不同调度器的参数签名可能不同
# eta (η) 仅在DDIMScheduler中使用,其他调度器将忽略
# eta对应于DDIM论文中的η,取值应在[0, 1]之间
# 检查调度器是否接受eta参数
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 初始化额外参数字典
extra_step_kwargs = {}
# 如果接受eta,则将其添加到字典中
if accepts_eta:
extra_step_kwargs["eta"] = eta
# 检查调度器是否接受generator参数
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 如果接受generator,则将其添加到字典中
if accepts_generator:
extra_step_kwargs["generator"] = generator
# 返回包含额外参数的字典
return extra_step_kwargs
# 检查输入参数的有效性
def check_inputs(
self,
prompt,
height,
width,
callback_steps,
image,
negative_prompt=None,
prompt_embeds=None,
negative_prompt_embeds=None,
):
# 检查高度和宽度是否都能被8整除,不符合则抛出错误
if height % 8 != 0 or width % 8 != 0:
raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
# 检查回调步骤是否有效,必须为正整数
if (callback_steps is None) or (
callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)
):
raise ValueError(
f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
f" {type(callback_steps)}."
)
# 检查是否同时提供了提示和提示嵌入,不能同时存在
if prompt is not None and prompt_embeds is not None:
raise ValueError(
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
" only forward one of the two."
)
# 检查是否未提供任何提示或提示嵌入,必须至少提供一个
elif prompt is None and prompt_embeds is None:
raise ValueError(
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
)
# 检查提示的类型,必须为字符串或列表
elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
# 检查是否同时提供了负提示和负提示嵌入,不能同时存在
if negative_prompt is not None and negative_prompt_embeds is not None:
raise ValueError(
f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
)
# 检查如果提供了提示嵌入,负提示嵌入也被提供,二者形状必须一致
if prompt_embeds is not None and negative_prompt_embeds is not None:
if prompt_embeds.shape != negative_prompt_embeds.shape:
raise ValueError(
"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
f" {negative_prompt_embeds.shape}."
)
# 检查适配器类型是否为多适配器,且图像参数必须为列表
if isinstance(self.adapter, MultiAdapter):
if not isinstance(image, list):
raise ValueError(
"MultiAdapter is enabled, but `image` is not a list. Please pass a list of images to `image`."
)
# 检查传入的图像数量与适配器数量是否匹配
if len(image) != len(self.adapter.adapters):
raise ValueError(
f"MultiAdapter requires passing the same number of images as adapters. Given {len(image)} images and {len(self.adapter.adapters)} adapters."
)
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents 复制的代码
# 准备潜在变量(latents),根据给定的参数设置形状
def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None):
# 计算潜在变量的形状,基于输入的批大小和通道数,以及经过 VAE 缩放因子的高度和宽度
shape = (
batch_size,
num_channels_latents,
int(height) // self.vae_scale_factor,
int(width) // self.vae_scale_factor,
)
# 检查生成器是否为列表且其长度与批大小不匹配
if isinstance(generator, list) and len(generator) != batch_size:
# 抛出错误,提示生成器的长度必须与请求的批大小一致
raise ValueError(
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
# 如果潜在变量为 None,生成随机的潜在变量
if latents is None:
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
else:
# 如果提供了潜在变量,将其转移到指定的设备上
latents = latents.to(device)
# 将初始噪声按调度器要求的标准差进行缩放
latents = latents * self.scheduler.init_noise_sigma
# 返回准备好的潜在变量
return latents
# 默认高度和宽度的设置,根据给定的高度、宽度和图像进行调整
def _default_height_width(self, height, width, image):
# 注意:可能传入的图像列表中每个图像的维度不同,
# 所以仅检查第一个图像并不完全准确,但这样处理较为简单
while isinstance(image, list):
# 如果图像是列表,取第一个图像进行处理
image = image[0]
# 如果高度未指定,进行计算
if height is None:
# 如果图像是 PIL 图像,获取其高度
if isinstance(image, PIL.Image.Image):
height = image.height
# 如果图像是张量,获取其形状中的高度
elif isinstance(image, torch.Tensor):
height = image.shape[-2]
# 向下取整至最近的 `self.adapter.downscale_factor` 的倍数
height = (height // self.adapter.downscale_factor) * self.adapter.downscale_factor
# 如果宽度未指定,进行计算
if width is None:
# 如果图像是 PIL 图像,获取其宽度
if isinstance(image, PIL.Image.Image):
width = image.width
# 如果图像是张量,获取其形状中的宽度
elif isinstance(image, torch.Tensor):
width = image.shape[-1]
# 向下取整至最近的 `self.adapter.downscale_factor` 的倍数
width = (width // self.adapter.downscale_factor) * self.adapter.downscale_factor
# 返回调整后的高度和宽度
return height, width
# 从 diffusers.pipelines.latent_consistency_models.pipeline_latent_consistency_text2img.LatentConsistencyModelPipeline 获取引导尺度嵌入
def get_guidance_scale_embedding(
self, w: torch.Tensor, embedding_dim: int = 512, dtype: torch.dtype = torch.float32
) -> torch.Tensor: # 定义一个返回类型为 torch.Tensor 的函数
""" # 函数的文档字符串,描述功能和参数
See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298 # 相关链接,提供更多信息
Args: # 参数说明部分开始
w (`torch.Tensor`): # 参数 w,类型为 torch.Tensor
Generate embedding vectors with a specified guidance scale to subsequently enrich timestep embeddings. # 用于生成嵌入向量,指定引导比例以丰富时间步嵌入
embedding_dim (`int`, *optional*, defaults to 512): # 可选参数,嵌入的维度,默认值为 512
Dimension of the embeddings to generate. # 嵌入向量的维度
dtype (`torch.dtype`, *optional*, defaults to `torch.float32`): # 可选参数,数据类型,默认为 torch.float32
Data type of the generated embeddings. # 生成的嵌入的类型
Returns: # 返回值说明部分开始
`torch.Tensor`: Embedding vectors with shape `(len(w), embedding_dim)`. # 返回形状为 (len(w), embedding_dim) 的嵌入向量
"""
assert len(w.shape) == 1 # 确保 w 是一维张量
w = w * 1000.0 # 将 w 的值放大 1000 倍
half_dim = embedding_dim // 2 # 计算嵌入维度的一半
emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1) # 计算嵌入的基准值
emb = torch.exp(torch.arange(half_dim, dtype=dtype) * -emb) # 生成对数空间的嵌入值
emb = w.to(dtype)[:, None] * emb[None, :] # 将 w 转换为指定 dtype 并与 emb 进行广播相乘
emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1) # 将 sin 和 cos 值沿着维度 1 拼接
if embedding_dim % 2 == 1: # 如果嵌入维度为奇数
emb = torch.nn.functional.pad(emb, (0, 1)) # 在最后一维进行零填充
assert emb.shape == (w.shape[0], embedding_dim) # 确保输出的形状正确
return emb # 返回计算得到的嵌入
@property # 将方法转换为属性
def guidance_scale(self): # 定义 guidance_scale 属性
return self._guidance_scale # 返回内部存储的引导比例
# here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) # 解释 guidance_scale 的定义
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` # 说明在特定论文中的应用
# corresponds to doing no classifier free guidance. # 解释当 guidance_scale 为 1 时的含义
@property # 将方法转换为属性
def do_classifier_free_guidance(self): # 定义 do_classifier_free_guidance 属性
return self._guidance_scale > 1 and self.unet.config.time_cond_proj_dim is None # 检查引导比例是否大于 1 且配置是否为 None
@torch.no_grad() # 在不计算梯度的上下文中执行
@replace_example_docstring(EXAMPLE_DOC_STRING) # 用示例文档字符串替换当前文档
def __call__( # 定义可调用对象的方法
self, # 对象自身的引用
prompt: Union[str, List[str]] = None, # 提示,可以是字符串或字符串列表,默认为 None
image: Union[torch.Tensor, PIL.Image.Image, List[PIL.Image.Image]] = None, # 输入图像,可以是 Tensor 或 PIL 图像,默认为 None
height: Optional[int] = None, # 可选参数,图像高度
width: Optional[int] = None, # 可选参数,图像宽度
num_inference_steps: int = 50, # 推理步骤的数量,默认为 50
timesteps: List[int] = None, # 可选参数,时间步列表
sigmas: List[float] = None, # 可选参数,sigma 列表
guidance_scale: float = 7.5, # 引导比例,默认为 7.5
negative_prompt: Optional[Union[str, List[str]]] = None, # 可选参数,负提示
num_images_per_prompt: Optional[int] = 1, # 每个提示生成的图像数量,默认为 1
eta: float = 0.0, # 可选参数,eta 值,默认为 0.0
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, # 可选参数,随机数生成器
latents: Optional[torch.Tensor] = None, # 可选参数,潜在张量
prompt_embeds: Optional[torch.Tensor] = None, # 可选参数,提示的嵌入
negative_prompt_embeds: Optional[torch.Tensor] = None, # 可选参数,负提示的嵌入
output_type: Optional[str] = "pil", # 可选参数,输出类型,默认为 "pil"
return_dict: bool = True, # 是否返回字典,默认为 True
callback: Optional[Callable[[int, int, torch.Tensor], None]] = None, # 可选参数,回调函数
callback_steps: int = 1, # 回调步骤,默认为 1
cross_attention_kwargs: Optional[Dict[str, Any]] = None, # 可选参数,交叉注意力的关键字参数
adapter_conditioning_scale: Union[float, List[float]] = 1.0, # 可选参数,适配器条件比例,默认为 1.0
clip_skip: Optional[int] = None, # 可选参数,剪辑跳过的步骤
.\diffusers\pipelines\t2i_adapter\pipeline_stable_diffusion_xl_adapter.py
# 版权声明,表明该代码归腾讯ARC和HuggingFace团队所有
#
# 根据Apache许可证第2.0版(“许可证”)许可;
# 除非遵循许可证,否则不得使用此文件。
# 可在以下网址获得许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面同意,否则根据许可证分发的软件
# 是按“原样”基础提供的,不附带任何明示或暗示的保证或条件。
# 有关许可证下特定权限和限制的更多信息,请参阅许可证。
import inspect # 导入inspect模块,用于获取对象的签名和文档等信息
from typing import Any, Callable, Dict, List, Optional, Tuple, Union # 从typing模块导入类型注解
import numpy as np # 导入numpy库,用于数值计算
import PIL.Image # 导入PIL库的Image模块,用于图像处理
import torch # 导入PyTorch库,用于深度学习
from transformers import ( # 从transformers库导入多个模型和处理器
CLIPImageProcessor, # 导入CLIP图像处理器
CLIPTextModel, # 导入CLIP文本模型
CLIPTextModelWithProjection, # 导入带有投影的CLIP文本模型
CLIPTokenizer, # 导入CLIP分词器
CLIPVisionModelWithProjection, # 导入带有投影的CLIP视觉模型
)
from ...image_processor import PipelineImageInput, VaeImageProcessor # 从相对路径导入图像处理器相关类
from ...loaders import ( # 从相对路径导入加载器相关混合类
FromSingleFileMixin, # 导入单文件加载混合类
IPAdapterMixin, # 导入IP适配器混合类
StableDiffusionXLLoraLoaderMixin, # 导入稳定扩散XL Lora加载混合类
TextualInversionLoaderMixin, # 导入文本反转加载混合类
)
from ...models import AutoencoderKL, ImageProjection, MultiAdapter, T2IAdapter, UNet2DConditionModel # 从相对路径导入多个模型
from ...models.attention_processor import ( # 从相对路径导入注意力处理器
AttnProcessor2_0, # 导入2.0版本的注意力处理器
XFormersAttnProcessor, # 导入XFormers注意力处理器
)
from ...models.lora import adjust_lora_scale_text_encoder # 从相对路径导入调整Lora规模的文本编码器函数
from ...schedulers import KarrasDiffusionSchedulers # 从相对路径导入Karras扩散调度器
from ...utils import ( # 从相对路径导入多个实用工具
PIL_INTERPOLATION, # 导入PIL插值方法
USE_PEFT_BACKEND, # 导入是否使用PEFT后端的标志
logging, # 导入日志模块
replace_example_docstring, # 导入替换示例文档字符串的函数
scale_lora_layers, # 导入缩放Lora层的函数
unscale_lora_layers, # 导入反缩放Lora层的函数
)
from ...utils.torch_utils import randn_tensor # 从相对路径导入随机张量生成函数
from ..pipeline_utils import DiffusionPipeline, StableDiffusionMixin # 从相对路径导入扩散管道和稳定扩散混合类
from ..stable_diffusion_xl.pipeline_output import StableDiffusionXLPipelineOutput # 从相对路径导入稳定扩散XL管道输出类
logger = logging.get_logger(__name__) # 获取当前模块的日志记录器实例
EXAMPLE_DOC_STRING = """ # 定义示例文档字符串
# 示例代码,展示如何使用 T2IAdapter 进行图像生成
Examples:
```py
# 导入 PyTorch 库
>>> import torch
# 从 diffusers 库导入必要的类
>>> from diffusers import T2IAdapter, StableDiffusionXLAdapterPipeline, DDPMScheduler
>>> from diffusers.utils import load_image
# 加载并转换指定 URL 的图像为灰度图像
>>> sketch_image = load_image("https://huggingface.co/Adapter/t2iadapter/resolve/main/sketch.png").convert("L")
# 指定模型的 ID
>>> model_id = "stabilityai/stable-diffusion-xl-base-1.0"
# 从预训练模型加载适配器
>>> adapter = T2IAdapter.from_pretrained(
... "Adapter/t2iadapter", # 适配器的路径
... subfolder="sketch_sdxl_1.0", # 子文件夹名
... torch_dtype=torch.float16, # 设置数据类型为 float16
... adapter_type="full_adapter_xl", # 适配器类型
... )
# 从预训练模型加载调度器
>>> scheduler = DDPMScheduler.from_pretrained(model_id, subfolder="scheduler")
# 创建 StableDiffusionXLAdapterPipeline 对象并将其移动到 GPU
>>> pipe = StableDiffusionXLAdapterPipeline.from_pretrained(
... model_id, # 模型 ID
... adapter=adapter, # 加载的适配器
... torch_dtype=torch.float16, # 设置数据类型为 float16
... variant="fp16", # 变体设置为 fp16
... scheduler=scheduler # 使用的调度器
... ).to("cuda") # 将管道移动到 GPU
# 设置随机种子以确保结果可复现
>>> generator = torch.manual_seed(42)
# 使用管道生成图像,提供提示和负面提示
>>> sketch_image_out = pipe(
... prompt="a photo of a dog in real world, high quality", # 正面提示
... negative_prompt="extra digit, fewer digits, cropped, worst quality, low quality", # 负面提示
... image=sketch_image, # 输入的草图图像
... generator=generator, # 随机数生成器
... guidance_scale=7.5, # 引导比例
... ).images[0] # 获取生成的图像
```
"""
预处理适配器图像的函数定义,接受图像、高度和宽度作为参数
def _preprocess_adapter_image(image, height, width):
# 检查图像是否为 PyTorch 张量
if isinstance(image, torch.Tensor):
# 如果是,直接返回该张量
return image
# 检查图像是否为 PIL 图像
elif isinstance(image, PIL.Image.Image):
# 将单个图像转换为列表
image = [image]
# 如果图像的第一个元素是 PIL 图像
if isinstance(image[0], PIL.Image.Image):
# 将每个图像调整为指定的高度和宽度,并转换为 NumPy 数组
image = [np.array(i.resize((width, height), resample=PIL_INTERPOLATION["lanczos"])) for i in image]
# 扩展数组的维度,以确保其格式为 [b, h, w, c]
image = [
i[None, ..., None] if i.ndim == 2 else i[None, ...] for i in image
] # expand [h, w] or [h, w, c] to [b, h, w, c]
# 将所有图像沿第一个轴拼接成一个大数组
image = np.concatenate(image, axis=0)
# 将图像数据类型转换为浮点型并进行归一化
image = np.array(image).astype(np.float32) / 255.0
# 调整数组的维度顺序
image = image.transpose(0, 3, 1, 2)
# 将 NumPy 数组转换为 PyTorch 张量
image = torch.from_numpy(image)
# 如果图像的第一个元素是 PyTorch 张量
elif isinstance(image[0], torch.Tensor):
# 检查维度并堆叠或拼接张量
if image[0].ndim == 3:
image = torch.stack(image, dim=0)
elif image[0].ndim == 4:
image = torch.cat(image, dim=0)
else:
# 如果维度不符合预期,则抛出错误
raise ValueError(
f"Invalid image tensor! Expecting image tensor with 3 or 4 dimension, but recive: {image[0].ndim}"
)
# 返回处理后的图像
return image
从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.rescale_noise_cfg 复制的函数定义
def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0):
"""
根据 guidance_rescale
对 noise_cfg
进行重新缩放。基于 Common Diffusion Noise Schedules and
Sample Steps are Flawed 的发现。参见第 3.4 节
"""
# 计算噪声预测文本的标准差
std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True)
# 计算噪声配置的标准差
std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True)
# 通过标准差进行结果的重新缩放(修复过曝问题)
noise_pred_rescaled = noise_cfg * (std_text / std_cfg)
# 按照 guidance_rescale
比例混合原始结果,以避免“普通”图像
noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg
# 返回重新缩放后的噪声配置
return noise_cfg
从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps 复制的函数定义
def retrieve_timesteps(
scheduler,
num_inference_steps: Optional[int] = None,
device: Optional[Union[str, torch.device]] = None,
timesteps: Optional[List[int]] = None,
sigmas: Optional[List[float]] = None,
**kwargs,
):
"""
调用调度器的 set_timesteps
方法并在调用后从调度器中检索时间步。处理自定义时间步。所有的关键字参数将传递给 scheduler.set_timesteps
。
# 函数参数说明
Args:
scheduler (SchedulerMixin
): # 调度器,用于获取时间步
The scheduler to get timesteps from.
num_inference_steps (int
): # 生成样本时使用的扩散步数
The number of diffusion steps used when generating samples with a pre-trained model. If used, timesteps
must be None
.
device (str
or torch.device
, optional): # 指定移动时间步的设备
The device to which the timesteps should be moved to. If None
, the timesteps are not moved.
timesteps (List[int]
, optional): # 自定义时间步,覆盖调度器的时间步策略
Custom timesteps used to override the timestep spacing strategy of the scheduler. If timesteps
is passed,
num_inference_steps
and sigmas
must be None
.
sigmas (List[float]
, optional): # 自定义sigma,覆盖调度器的时间步策略
Custom sigmas used to override the timestep spacing strategy of the scheduler. If sigmas
is passed,
num_inference_steps
and timesteps
must be None
.
# 返回一个元组,包含时间步调度和推理步骤数
Returns:
`Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the
second element is the number of inference steps.
"""
# 如果同时传入时间步和sigma,抛出错误
if timesteps is not None and sigmas is not None:
raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
# 如果传入了时间步
if timesteps is not None:
# 检查调度器是否支持自定义时间步
accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
# 如果不支持,抛出错误
if not accepts_timesteps:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" timestep schedules. Please check whether you are using the correct scheduler."
)
# 设置调度器的时间步
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
# 获取当前调度器的时间步
timesteps = scheduler.timesteps
# 计算推理步骤的数量
num_inference_steps = len(timesteps)
# 如果传入了sigma
elif sigmas is not None:
# 检查调度器是否支持自定义sigma
accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
# 如果不支持,抛出错误
if not accept_sigmas:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" sigmas schedules. Please check whether you are using the correct scheduler."
)
# 设置调度器的sigma
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
# 获取当前调度器的时间步
timesteps = scheduler.timesteps
# 计算推理步骤的数量
num_inference_steps = len(timesteps)
# 如果都没有传入,使用默认推理步骤数
else:
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
# 获取当前调度器的时间步
timesteps = scheduler.timesteps
# 返回时间步和推理步骤数
return timesteps, num_inference_steps
定义一个名为 StableDiffusionXLAdapterPipeline 的类,该类继承多个混入类和主类
class StableDiffusionXLAdapterPipeline(
# 从 DiffusionPipeline 继承,提供扩散管道的基础功能
DiffusionPipeline,
# 从 StableDiffusionMixin 继承,提供稳定扩散相关的功能
StableDiffusionMixin,
# 从 TextualInversionLoaderMixin 继承,提供文本反转加载功能
TextualInversionLoaderMixin,
# 从 StableDiffusionXLLoraLoaderMixin 继承,提供 LoRA 权重加载和保存功能
StableDiffusionXLLoraLoaderMixin,
# 从 IPAdapterMixin 继承,提供 IP 适配器加载功能
IPAdapterMixin,
# 从 FromSingleFileMixin 继承,提供从单个文件加载功能
FromSingleFileMixin,
):
# 文档字符串,说明该管道的用途和背景
r"""
使用增强 T2I-Adapter 的 Stable Diffusion 进行文本到图像生成的管道
https://arxiv.org/abs/2302.08453
此模型继承自 [`DiffusionPipeline`]。查看超类文档以获取库为所有管道实现的通用方法(例如下载、保存、在特定设备上运行等)
该管道还继承以下加载方法:
- [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] 用于加载文本反转嵌入
- [`~loaders.FromSingleFileMixin.from_single_file`] 用于加载 `.ckpt` 文件
- [`~loaders.StableDiffusionXLLoraLoaderMixin.load_lora_weights`] 用于加载 LoRA 权重
- [`~loaders.StableDiffusionXLLoraLoaderMixin.save_lora_weights`] 用于保存 LoRA 权重
- [`~loaders.IPAdapterMixin.load_ip_adapter`] 用于加载 IP 适配器
"""
# 参数说明部分,描述类的初始化所需参数
Args:
adapter ([`T2IAdapter`] or [`MultiAdapter`] or `List[T2IAdapter]`):
提供在去噪过程中对 unet 的额外条件。如果将多个适配器作为列表设置,
则每个适配器的输出将相加以创建一个合并的额外条件。
adapter_weights (`List[float]`, *optional*, defaults to None):
表示每个适配器输出前加权的浮点数列表,权重将在相加前乘以各适配器的输出。
vae ([`AutoencoderKL`]):
变分自编码器 (VAE) 模型,用于将图像编码和解码为潜在表示。
text_encoder ([`CLIPTextModel`]):
冻结的文本编码器。稳定扩散使用
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel) 的文本部分,
具体是 [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) 变体。
tokenizer (`CLIPTokenizer`):
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer) 类的分词器。
unet ([`UNet2DConditionModel`]): 条件 U-Net 架构,用于去噪编码后的图像潜在值。
scheduler ([`SchedulerMixin`]):
用于与 `unet` 结合使用的调度器,以去噪编码的图像潜在值。可以是
[`DDIMScheduler`], [`LMSDiscreteScheduler`] 或 [`PNDMScheduler`] 中的一个。
safety_checker ([`StableDiffusionSafetyChecker`]):
分类模块,估计生成的图像是否可能被认为是冒犯性或有害的。
请参考 [模型卡](https://huggingface.co/runwayml/stable-diffusion-v1-5) 获取详细信息。
feature_extractor ([`CLIPImageProcessor`]):
从生成图像中提取特征的模型,用作 `safety_checker` 的输入。
"""
# 定义模型的 CPU 卸载顺序
model_cpu_offload_seq = "text_encoder->text_encoder_2->image_encoder->unet->vae"
# 可选组件的列表,包括多个可能使用的模型组件
_optional_components = [
"tokenizer",
"tokenizer_2",
"text_encoder",
"text_encoder_2",
"feature_extractor",
"image_encoder",
]
# 初始化方法,设置模型的各种组件
def __init__(
self,
vae: AutoencoderKL, # 初始化变分自编码器
text_encoder: CLIPTextModel, # 初始化文本编码器
text_encoder_2: CLIPTextModelWithProjection, # 初始化第二个文本编码器
tokenizer: CLIPTokenizer, # 初始化第一个分词器
tokenizer_2: CLIPTokenizer, # 初始化第二个分词器
unet: UNet2DConditionModel, # 初始化条件 U-Net 模型
adapter: Union[T2IAdapter, MultiAdapter, List[T2IAdapter]], # 初始化适配器
scheduler: KarrasDiffusionSchedulers, # 初始化调度器
force_zeros_for_empty_prompt: bool = True, # 是否为空提示强制使用零
feature_extractor: CLIPImageProcessor = None, # 初始化特征提取器,默认为 None
image_encoder: CLIPVisionModelWithProjection = None, # 初始化图像编码器,默认为 None
# 调用父类的初始化方法
):
super().__init__()
# 注册各种模块,包括 VAE、文本编码器等
self.register_modules(
vae=vae,
text_encoder=text_encoder,
text_encoder_2=text_encoder_2,
tokenizer=tokenizer,
tokenizer_2=tokenizer_2,
unet=unet,
adapter=adapter,
scheduler=scheduler,
feature_extractor=feature_extractor,
image_encoder=image_encoder,
)
# 将配置项注册,包括强制对空提示的零处理
self.register_to_config(force_zeros_for_empty_prompt=force_zeros_for_empty_prompt)
# 计算 VAE 的缩放因子
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
# 初始化图像处理器,使用 VAE 缩放因子
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
# 获取默认样本大小
self.default_sample_size = self.unet.config.sample_size
# 从 StableDiffusionXLPipeline 复制的 encode_prompt 方法
def encode_prompt(
self,
prompt: str,
prompt_2: Optional[str] = None,
device: Optional[torch.device] = None,
num_images_per_prompt: int = 1,
do_classifier_free_guidance: bool = True,
negative_prompt: Optional[str] = None,
negative_prompt_2: Optional[str] = None,
prompt_embeds: Optional[torch.Tensor] = None,
negative_prompt_embeds: Optional[torch.Tensor] = None,
pooled_prompt_embeds: Optional[torch.Tensor] = None,
negative_pooled_prompt_embeds: Optional[torch.Tensor] = None,
lora_scale: Optional[float] = None,
clip_skip: Optional[int] = None,
# 从 StableDiffusionPipeline 复制的 encode_image 方法
def encode_image(self, image, device, num_images_per_prompt, output_hidden_states=None):
# 获取图像编码器参数的数据类型
dtype = next(self.image_encoder.parameters()).dtype
# 如果输入的图像不是张量,使用特征提取器转换
if not isinstance(image, torch.Tensor):
image = self.feature_extractor(image, return_tensors="pt").pixel_values
# 将图像移动到指定设备并设置数据类型
image = image.to(device=device, dtype=dtype)
# 如果要求输出隐藏状态
if output_hidden_states:
# 编码图像并获取倒数第二个隐藏状态
image_enc_hidden_states = self.image_encoder(image, output_hidden_states=True).hidden_states[-2]
# 重复隐藏状态以适应每个提示的图像数量
image_enc_hidden_states = image_enc_hidden_states.repeat_interleave(num_images_per_prompt, dim=0)
# 编码空图像并获取其隐藏状态
uncond_image_enc_hidden_states = self.image_encoder(
torch.zeros_like(image), output_hidden_states=True
).hidden_states[-2]
# 重复空图像的隐藏状态
uncond_image_enc_hidden_states = uncond_image_enc_hidden_states.repeat_interleave(
num_images_per_prompt, dim=0
)
# 返回图像和无条件图像的隐藏状态
return image_enc_hidden_states, uncond_image_enc_hidden_states
else:
# 编码图像并获取图像嵌入
image_embeds = self.image_encoder(image).image_embeds
# 重复图像嵌入以适应每个提示的图像数量
image_embeds = image_embeds.repeat_interleave(num_images_per_prompt, dim=0)
# 创建与图像嵌入形状相同的零张量作为无条件嵌入
uncond_image_embeds = torch.zeros_like(image_embeds)
# 返回图像嵌入和无条件嵌入
return image_embeds, uncond_image_embeds
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_ip_adapter_image_embeds 复制而来
def prepare_ip_adapter_image_embeds(
# 定义方法,接受适配器图像、图像嵌入、设备、每个提示的图像数量和是否进行无分类器自由引导的标志
self, ip_adapter_image, ip_adapter_image_embeds, device, num_images_per_prompt, do_classifier_free_guidance
):
# 初始化图像嵌入列表
image_embeds = []
# 如果启用无分类器自由引导
if do_classifier_free_guidance:
# 初始化负图像嵌入列表
negative_image_embeds = []
# 如果图像嵌入为空
if ip_adapter_image_embeds is None:
# 如果输入的图像不是列表,则将其转换为列表
if not isinstance(ip_adapter_image, list):
ip_adapter_image = [ip_adapter_image]
# 检查输入图像数量与适配器数量是否匹配
if len(ip_adapter_image) != len(self.unet.encoder_hid_proj.image_projection_layers):
raise ValueError(
# 抛出错误,提示输入图像数量与适配器数量不匹配
f"`ip_adapter_image` must have same length as the number of IP Adapters. Got {len(ip_adapter_image)} images and {len(self.unet.encoder_hid_proj.image_projection_layers)} IP Adapters."
)
# 遍历输入图像和图像投影层
for single_ip_adapter_image, image_proj_layer in zip(
ip_adapter_image, self.unet.encoder_hid_proj.image_projection_layers
):
# 检查图像投影层是否为 ImageProjection 类型
output_hidden_state = not isinstance(image_proj_layer, ImageProjection)
# 编码单个图像,获取嵌入和负嵌入
single_image_embeds, single_negative_image_embeds = self.encode_image(
single_ip_adapter_image, device, 1, output_hidden_state
)
# 将图像嵌入添加到列表中
image_embeds.append(single_image_embeds[None, :])
# 如果启用无分类器自由引导,将负嵌入添加到列表中
if do_classifier_free_guidance:
negative_image_embeds.append(single_negative_image_embeds[None, :])
else:
# 如果图像嵌入已提供
for single_image_embeds in ip_adapter_image_embeds:
# 如果启用无分类器自由引导,分割负嵌入和图像嵌入
if do_classifier_free_guidance:
single_negative_image_embeds, single_image_embeds = single_image_embeds.chunk(2)
# 将负嵌入添加到列表中
negative_image_embeds.append(single_negative_image_embeds)
# 将图像嵌入添加到列表中
image_embeds.append(single_image_embeds)
# 初始化适配器图像嵌入列表
ip_adapter_image_embeds = []
# 遍历图像嵌入列表
for i, single_image_embeds in enumerate(image_embeds):
# 将单个图像嵌入复制指定次数
single_image_embeds = torch.cat([single_image_embeds] * num_images_per_prompt, dim=0)
# 如果启用无分类器自由引导,复制负嵌入
if do_classifier_free_guidance:
single_negative_image_embeds = torch.cat([negative_image_embeds[i]] * num_images_per_prompt, dim=0)
# 将负嵌入和图像嵌入拼接
single_image_embeds = torch.cat([single_negative_image_embeds, single_image_embeds], dim=0)
# 将图像嵌入移动到指定设备
single_image_embeds = single_image_embeds.to(device=device)
# 添加到适配器图像嵌入列表
ip_adapter_image_embeds.append(single_image_embeds)
# 返回适配器图像嵌入列表
return ip_adapter_image_embeds
# 准备额外的参数用于调度器步骤,因为并非所有调度器都有相同的参数签名
def prepare_extra_step_kwargs(self, generator, eta):
# eta (η) 仅用于 DDIMScheduler,在其他调度器中将被忽略
# eta 对应于 DDIM 论文中的 η: https://arxiv.org/abs/2010.02502
# 应该在 [0, 1] 之间
# 检查调度器的 step 方法是否接受 eta 参数
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 初始化额外参数的字典
extra_step_kwargs = {}
# 如果调度器接受 eta,则将其添加到额外参数中
if accepts_eta:
extra_step_kwargs["eta"] = eta
# 检查调度器的 step 方法是否接受 generator 参数
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 如果调度器接受 generator,则将其添加到额外参数中
if accepts_generator:
extra_step_kwargs["generator"] = generator
# 返回准备好的额外参数字典
return extra_step_kwargs
# 从 diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl.StableDiffusionXLPipeline.check_inputs 复制的
def check_inputs(
self,
prompt,
prompt_2,
height,
width,
callback_steps,
negative_prompt=None,
negative_prompt_2=None,
prompt_embeds=None,
negative_prompt_embeds=None,
pooled_prompt_embeds=None,
negative_pooled_prompt_embeds=None,
ip_adapter_image=None,
ip_adapter_image_embeds=None,
callback_on_step_end_tensor_inputs=None,
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents 复制的
def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None):
# 根据批量大小、通道数、高度和宽度构建形状元组
shape = (
batch_size,
num_channels_latents,
int(height) // self.vae_scale_factor,
int(width) // self.vae_scale_factor,
)
# 如果 generator 是列表且长度与批量大小不匹配,则引发错误
if isinstance(generator, list) and len(generator) != batch_size:
raise ValueError(
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
# 如果没有提供 latents,则生成随机噪声
if latents is None:
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
else:
# 如果提供了 latents,则将其转移到指定设备
latents = latents.to(device)
# 将初始噪声按调度器要求的标准差进行缩放
latents = latents * self.scheduler.init_noise_sigma
# 返回处理后的潜变量
return latents
# 从 diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl.StableDiffusionXLPipeline._get_add_time_ids 复制的
def _get_add_time_ids(
self, original_size, crops_coords_top_left, target_size, dtype, text_encoder_projection_dim=None
# 定义参数
):
# 将原始尺寸、裁剪坐标和目标尺寸合并为一个列表
add_time_ids = list(original_size + crops_coords_top_left + target_size)
# 计算通过传递的时间嵌入维度和文本编码器投影维度得出的总嵌入维度
passed_add_embed_dim = (
self.unet.config.addition_time_embed_dim * len(add_time_ids) + text_encoder_projection_dim
)
# 获取模型期望的添加时间嵌入维度
expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features
# 检查实际嵌入维度是否与期望值相等
if expected_add_embed_dim != passed_add_embed_dim:
# 如果不相等,抛出值错误并提供调试信息
raise ValueError(
f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. The model has an incorrect config. Please check `unet.config.time_embedding_type` and `text_encoder_2.config.projection_dim`."
)
# 将添加的时间 ID 转换为张量,并指定数据类型
add_time_ids = torch.tensor([add_time_ids], dtype=dtype)
# 返回添加的时间 ID
return add_time_ids
# 从 StableDiffusionUpscalePipeline 类复制的方法
def upcast_vae(self):
# 获取 VAE 的数据类型
dtype = self.vae.dtype
# 将 VAE 转换为浮点32位
self.vae.to(dtype=torch.float32)
# 检查是否使用了 Torch 2.0 或 XFormers
use_torch_2_0_or_xformers = isinstance(
self.vae.decoder.mid_block.attentions[0].processor,
(
AttnProcessor2_0,
XFormersAttnProcessor,
),
)
# 如果使用了 XFormers 或 Torch 2.0,则注意力块不需要为浮点32位,从而节省内存
if use_torch_2_0_or_xformers:
# 将 VAE 的后量化卷积层转换为相应的数据类型
self.vae.post_quant_conv.to(dtype)
# 将 VAE 的输入卷积层转换为相应的数据类型
self.vae.decoder.conv_in.to(dtype)
# 将 VAE 的中间块转换为相应的数据类型
self.vae.decoder.mid_block.to(dtype)
# 从 StableDiffusionAdapterPipeline 类复制的方法
def _default_height_width(self, height, width, image):
# 注意:图像列表中的每个图像可能具有不同的维度
# 所以只检查第一个图像并不完全正确,但比较简单
while isinstance(image, list):
# 获取图像列表的第一个图像
image = image[0]
# 如果高度为 None,则根据图像获取高度
if height is None:
if isinstance(image, PIL.Image.Image):
# 获取 PIL 图像的高度
height = image.height
elif isinstance(image, torch.Tensor):
# 获取张量的高度
height = image.shape[-2]
# 向下取整到 `self.adapter.downscale_factor` 的最近倍数
height = (height // self.adapter.downscale_factor) * self.adapter.downscale_factor
# 如果宽度为 None,则根据图像获取宽度
if width is None:
if isinstance(image, PIL.Image.Image):
# 获取 PIL 图像的宽度
width = image.width
elif isinstance(image, torch.Tensor):
# 获取张量的宽度
width = image.shape[-1]
# 向下取整到 `self.adapter.downscale_factor` 的最近倍数
width = (width // self.adapter.downscale_factor) * self.adapter.downscale_factor
# 返回最终的高度和宽度
return height, width
# 从 LatentConsistencyModelPipeline 类复制的方法
# 定义获取指导尺度嵌入的函数,接收权重张量、嵌入维度和数据类型
def get_guidance_scale_embedding(
self, w: torch.Tensor, embedding_dim: int = 512, dtype: torch.dtype = torch.float32
) -> torch.Tensor:
# 参见指定链接中的文档说明
"""
See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298
Args:
w (`torch.Tensor`):
生成具有指定指导尺度的嵌入向量,以丰富时间步嵌入。
embedding_dim (`int`, *optional*, defaults to 512):
要生成的嵌入的维度。
dtype (`torch.dtype`, *optional*, defaults to `torch.float32`):
生成的嵌入的数据类型。
Returns:
`torch.Tensor`: 形状为 `(len(w), embedding_dim)` 的嵌入向量。
"""
# 确保输入权重张量是一维的
assert len(w.shape) == 1
# 将权重乘以1000.0以增强数值范围
w = w * 1000.0
# 计算嵌入的一半维度
half_dim = embedding_dim // 2
# 计算缩放因子
emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1)
# 生成指数衰减的嵌入
emb = torch.exp(torch.arange(half_dim, dtype=dtype) * -emb)
# 计算最终嵌入,通过权重张量调整嵌入
emb = w.to(dtype)[:, None] * emb[None, :]
# 连接正弦和余弦嵌入
emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1)
# 如果嵌入维度为奇数,则进行零填充
if embedding_dim % 2 == 1: # zero pad
emb = torch.nn.functional.pad(emb, (0, 1))
# 确保生成的嵌入形状正确
assert emb.shape == (w.shape[0], embedding_dim)
# 返回生成的嵌入
return emb
# 定义属性以获取指导尺度
@property
def guidance_scale(self):
return self._guidance_scale
# 这里定义的 `guidance_scale` 类似于论文中方程 (2) 的指导权重 `w`
# `guidance_scale = 1` 表示不进行无分类器指导。
@property
def do_classifier_free_guidance(self):
# 返回是否需要进行无分类器指导的布尔值
return self._guidance_scale > 1 and self.unet.config.time_cond_proj_dim is None
# 装饰器表示不计算梯度
@torch.no_grad()
# 替换示例文档字符串的装饰器
@replace_example_docstring(EXAMPLE_DOC_STRING)
# 定义可调用的方法,允许多个输入参数
def __call__(
# 输入的提示,可以是字符串或字符串列表
self,
prompt: Union[str, List[str]] = None,
# 第二个输入提示,选填,可以是字符串或字符串列表
prompt_2: Optional[Union[str, List[str]]] = None,
# 输入的图像,可以是特定格式
image: PipelineImageInput = None,
# 输出图像的高度,选填
height: Optional[int] = None,
# 输出图像的宽度,选填
width: Optional[int] = None,
# 推理步骤的数量,默认为50
num_inference_steps: int = 50,
# 指定时间步,选填
timesteps: List[int] = None,
# 指定 sigma 值,选填
sigmas: List[float] = None,
# 去噪结束的值,选填
denoising_end: Optional[float] = None,
# 指导比例,默认为5.0
guidance_scale: float = 5.0,
# 负面提示,选填,可以是字符串或字符串列表
negative_prompt: Optional[Union[str, List[str]]] = None,
# 第二个负面提示,选填
negative_prompt_2: Optional[Union[str, List[str]]] = None,
# 每个提示生成的图像数量,默认为1
num_images_per_prompt: Optional[int] = 1,
# eta 值,默认为0.0
eta: float = 0.0,
# 生成器,选填,可以是单个或多个 torch.Generator 对象
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 潜在变量,选填,可以是 torch.Tensor 对象
latents: Optional[torch.Tensor] = None,
# 提示嵌入,选填,可以是 torch.Tensor 对象
prompt_embeds: Optional[torch.Tensor] = None,
# 负面提示嵌入,选填,可以是 torch.Tensor 对象
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 池化提示嵌入,选填,可以是 torch.Tensor 对象
pooled_prompt_embeds: Optional[torch.Tensor] = None,
# 负面池化提示嵌入,选填,可以是 torch.Tensor 对象
negative_pooled_prompt_embeds: Optional[torch.Tensor] = None,
# 输入适配器图像,选填
ip_adapter_image: Optional[PipelineImageInput] = None,
# 输入适配器图像嵌入,选填,可以是 torch.Tensor 列表
ip_adapter_image_embeds: Optional[List[torch.Tensor]] = None,
# 输出类型,默认为"pil"
output_type: Optional[str] = "pil",
# 是否返回字典格式,默认为True
return_dict: bool = True,
# 回调函数,选填,用于特定操作
callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
# 回调执行的步骤,默认为1
callback_steps: int = 1,
# 交叉注意力参数,选填
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 指导重缩放值,默认为0.0
guidance_rescale: float = 0.0,
# 原始图像尺寸,选填
original_size: Optional[Tuple[int, int]] = None,
# 裁剪左上角坐标,默认为(0, 0)
crops_coords_top_left: Tuple[int, int] = (0, 0),
# 目标图像尺寸,选填
target_size: Optional[Tuple[int, int]] = None,
# 负面原始图像尺寸,选填
negative_original_size: Optional[Tuple[int, int]] = None,
# 负面裁剪左上角坐标,默认为(0, 0)
negative_crops_coords_top_left: Tuple[int, int] = (0, 0),
# 负面目标图像尺寸,选填
negative_target_size: Optional[Tuple[int, int]] = None,
# 适配器条件缩放,默认为1.0
adapter_conditioning_scale: Union[float, List[float]] = 1.0,
# 适配器条件因子,默认为1.0
adapter_conditioning_factor: float = 1.0,
# 跳过的剪辑步数,选填
clip_skip: Optional[int] = None,
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Blazor Hybrid适配到HarmonyOS系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 分享4款.NET开源、免费、实用的商城系统
· 解决跨域问题的这6种方案,真香!
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库