diffusers 源码解析(三十九)
.\diffusers\pipelines\pag\pipeline_pag_kolors.py
# 版权所有 2024 Stability AI, Kwai-Kolors Team 和 The HuggingFace Team。保留所有权利。
#
# 根据 Apache 许可证,版本 2.0(“许可证”)进行许可;
# 除非遵循许可证,否则您不得使用此文件。
# 您可以在以下位置获得许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有约定,按照许可证分发的软件均按“原样”提供,
# 不提供任何形式的担保或条件,明示或暗示。
# 请参阅许可证以获取有关权限和限制的具体语言。
import inspect # 导入 inspect 模块以便进行对象检查
from typing import Any, Callable, Dict, List, Optional, Tuple, Union # 导入类型提示以便于类型注释
import torch # 导入 PyTorch 库
from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection # 从 transformers 导入图像处理器和模型
from ...callbacks import MultiPipelineCallbacks, PipelineCallback # 从回调模块导入多管道回调和单个管道回调
from ...image_processor import PipelineImageInput, VaeImageProcessor # 从图像处理模块导入图像输入和变分自编码器图像处理器
from ...loaders import IPAdapterMixin, StableDiffusionXLLoraLoaderMixin # 从加载器模块导入适配器混合类
from ...models import AutoencoderKL, ImageProjection, UNet2DConditionModel # 从模型模块导入自编码器、图像投影和条件模型
from ...models.attention_processor import AttnProcessor2_0, FusedAttnProcessor2_0, XFormersAttnProcessor # 导入注意力处理器
from ...schedulers import KarrasDiffusionSchedulers # 从调度器模块导入 Karras 扩散调度器
from ...utils import is_torch_xla_available, logging, replace_example_docstring # 从工具模块导入工具函数
from ...utils.torch_utils import randn_tensor # 从 PyTorch 工具模块导入随机张量生成函数
from ..kolors.pipeline_output import KolorsPipelineOutput # 从 Kolors 模块导入管道输出类
from ..kolors.text_encoder import ChatGLMModel # 从 Kolors 模块导入文本编码器模型
from ..kolors.tokenizer import ChatGLMTokenizer # 从 Kolors 模块导入分词器
from ..pipeline_utils import DiffusionPipeline, StableDiffusionMixin # 从管道工具模块导入扩散管道和稳定扩散混合类
from .pag_utils import PAGMixin # 从 PAG 工具模块导入 PAG 混合类
# 检查 XLA 是否可用,如果可用,则导入相关模块
if is_torch_xla_available():
import torch_xla.core.xla_model as xm # 导入 XLA 模型相关功能
XLA_AVAILABLE = True # 标记 XLA 可用
else:
XLA_AVAILABLE = False # 标记 XLA 不可用
# 创建日志记录器实例,用于记录模块内的日志
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
# 示例文档字符串,提供用法示例
EXAMPLE_DOC_STRING = """
Examples:
```py
>>> import torch # 导入 PyTorch 库
>>> from diffusers import AutoPipelineForText2Image # 从 diffusers 导入自动文本转图像管道
>>> pipe = AutoPipelineForText2Image.from_pretrained( # 从预训练模型创建管道实例
... "Kwai-Kolors/Kolors-diffusers", # 指定模型名称
... variant="fp16", # 指定变体为 fp16
... torch_dtype=torch.float16, # 设置 PyTorch 数据类型为 float16
... enable_pag=True, # 启用 PAG 功能
... pag_applied_layers=["down.block_2.attentions_1", "up.block_0.attentions_1"], # 指定 PAG 应用层
... )
>>> pipe = pipe.to("cuda") # 将管道移动到 CUDA 设备
>>> prompt = ( # 定义提示文本
... "A photo of a ladybug, macro, zoom, high quality, film, holding a wooden sign with the text 'KOLORS'" # 提示内容
... )
>>> image = pipe(prompt, guidance_scale=5.5, pag_scale=1.5).images[0] # 生成图像并提取第一张
```py
"""
# 从 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, # 其他可选参数
):
"""
# 调用调度器的 `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]`, *可选*):
# 自定义 sigmas,用于覆盖调度器的时间步间隔策略。如果传递 `sigmas`,
# 则 `num_inference_steps` 和 `timesteps` 必须为 `None`。
# 返回:
# `Tuple[torch.Tensor, int]`: 一个元组,其中第一个元素是调度器的时间步调度,
# 第二个元素是推理步骤的数量。
"""
# 检查是否同时传递了 `timesteps` 和 `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:
# 检查调度器的 `set_timesteps` 方法是否接受时间步
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."
)
# 调用调度器的 `set_timesteps` 方法,传入自定义时间步及设备和关键字参数
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
# 获取调度器的时间步
timesteps = scheduler.timesteps
# 计算推理步骤的数量
num_inference_steps = len(timesteps)
# 如果提供了自定义 sigmas
elif sigmas is not None:
# 检查调度器的 `set_timesteps` 方法是否接受 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."
)
# 调用调度器的 `set_timesteps` 方法,传入自定义 sigmas 及设备和关键字参数
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
# 获取调度器的时间步
timesteps = scheduler.timesteps
# 计算推理步骤的数量
num_inference_steps = len(timesteps)
# 如果没有提供 `timesteps` 和 `sigmas`
else:
# 调用调度器的 `set_timesteps` 方法,传入推理步骤数量及设备和关键字参数
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
# 获取调度器的时间步
timesteps = scheduler.timesteps
# 返回时间步和推理步骤数量
return timesteps, num_inference_steps
# 定义一个名为 KolorsPAGPipeline 的类,继承自多个基类,用于文本到图像的生成
class KolorsPAGPipeline(
# 继承 DiffusionPipeline、StableDiffusionMixin、StableDiffusionXLLoraLoaderMixin、IPAdapterMixin 和 PAGMixin
DiffusionPipeline, StableDiffusionMixin, StableDiffusionXLLoraLoaderMixin, IPAdapterMixin, PAGMixin
):
r"""
使用 Kolors 进行文本到图像生成的管道。
该模型继承自 [`DiffusionPipeline`]。请查看超类文档以了解库为所有管道实现的通用方法(如下载或保存、在特定设备上运行等)。
该管道还继承了以下加载方法:
- [`~loaders.StableDiffusionXLLoraLoaderMixin.load_lora_weights`] 用于加载 LoRA 权重
- [`~loaders.StableDiffusionXLLoraLoaderMixin.save_lora_weights`] 用于保存 LoRA 权重
- [`~loaders.IPAdapterMixin.load_ip_adapter`] 用于加载 IP 适配器
参数:
vae ([`AutoencoderKL`]):
变分自编码器 (VAE) 模型,用于将图像编码和解码为潜在表示。
text_encoder ([`ChatGLMModel`]):
冻结的文本编码器。Kolors 使用 [ChatGLM3-6B](https://huggingface.co/THUDM/chatglm3-6b)。
tokenizer (`ChatGLMTokenizer`):
类的标记器
[ChatGLMTokenizer](https://huggingface.co/THUDM/chatglm3-6b/blob/main/tokenization_chatglm.py)。
unet ([`UNet2DConditionModel`]): 条件 U-Net 架构,用于去噪编码的图像潜在表示。
scheduler ([`SchedulerMixin`]):
结合 `unet` 使用的调度器,用于去噪编码的图像潜在表示。可以是
[`DDIMScheduler`]、[`LMSDiscreteScheduler`] 或 [`PNDMScheduler`]。
force_zeros_for_empty_prompt (`bool`, *optional*, defaults to `"False"`):
是否强制负提示嵌入始终设置为 0。另请参见 `Kwai-Kolors/Kolors-diffusers` 的配置。
pag_applied_layers (`str` or `List[str]``, *optional*, defaults to `"mid"`):
设置要应用扰动注意力引导的变压器注意力层。可以是字符串或字符串列表,包含 "down"、"mid"、"up",或整个变压器块或特定变压器块注意力层,例如:
["mid"] ["down", "mid"] ["down", "mid", "up.block_1"] ["down", "mid", "up.block_1.attentions_0",
"up.block_1.attentions_1"]
"""
# 定义模型在 CPU 上的卸载顺序
model_cpu_offload_seq = "text_encoder->image_encoder->unet->vae"
# 定义可选组件的列表,这些组件在实例化时可以选择性提供
_optional_components = [
"image_encoder",
"feature_extractor",
]
# 定义需要作为张量输入的回调列表,这些输入将用于后续的处理
_callback_tensor_inputs = [
"latents",
"prompt_embeds",
"negative_prompt_embeds",
"add_text_embeds",
"add_time_ids",
"negative_pooled_prompt_embeds",
"negative_add_time_ids",
]
# 初始化方法,用于设置类的初始状态
def __init__(
self,
vae: AutoencoderKL, # VAE模型,用于图像编码
text_encoder: ChatGLMModel, # 文本编码器模型,用于处理输入文本
tokenizer: ChatGLMTokenizer, # 令牌化工具,将文本转为模型可处理的格式
unet: UNet2DConditionModel, # UNet模型,用于生成图像
scheduler: KarrasDiffusionSchedulers, # 调度器,用于管理采样过程
image_encoder: CLIPVisionModelWithProjection = None, # 可选的图像编码器
feature_extractor: CLIPImageProcessor = None, # 可选的特征提取器
force_zeros_for_empty_prompt: bool = False, # 是否强制为空提示时输出零
pag_applied_layers: Union[str, List[str]] = "mid", # 应用的层,默认设置为“mid”
):
super().__init__() # 调用父类的初始化方法
# 注册多个模块,以便于管理和访问
self.register_modules(
vae=vae,
text_encoder=text_encoder,
tokenizer=tokenizer,
unet=unet,
scheduler=scheduler,
image_encoder=image_encoder,
feature_extractor=feature_extractor,
)
# 将配置参数注册到类中
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) if hasattr(self, "vae") and self.vae is not None else 8
)
# 创建图像处理器实例,使用 VAE 的缩放因子
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
# 设置默认的样本大小,从 UNet 配置中获取
self.default_sample_size = self.unet.config.sample_size
# 设置应用的层
self.set_pag_applied_layers(pag_applied_layers)
# 从其他管道复制的 encode_prompt 方法,用于编码提示
def encode_prompt(
self,
prompt, # 输入提示文本
device: Optional[torch.device] = None, # 可选的设备参数,用于模型计算
num_images_per_prompt: int = 1, # 每个提示生成的图像数量
do_classifier_free_guidance: bool = True, # 是否进行无分类器引导
negative_prompt=None, # 可选的负提示文本
prompt_embeds: Optional[torch.FloatTensor] = None, # 可选的提示嵌入
pooled_prompt_embeds: Optional[torch.Tensor] = None, # 可选的池化提示嵌入
negative_prompt_embeds: Optional[torch.FloatTensor] = None, # 可选的负提示嵌入
negative_pooled_prompt_embeds: Optional[torch.Tensor] = None, # 可选的负池化提示嵌入
max_sequence_length: int = 256, # 最大序列长度
# 从其他管道复制的 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
# 从 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
):
# 确定输出是否为隐藏状态
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
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs 复制的代码
# 准备额外的参数用于调度器的步骤,因为并非所有调度器的签名相同
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 = {}
# 如果调度器接受 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
# 从 diffusers.pipelines.kolors.pipeline_kolors.KolorsPipeline.check_inputs 复制的函数
def check_inputs(
self,
prompt,
num_inference_steps,
height,
width,
negative_prompt=None,
prompt_embeds=None,
pooled_prompt_embeds=None,
negative_prompt_embeds=None,
negative_pooled_prompt_embeds=None,
ip_adapter_image=None,
ip_adapter_image_embeds=None,
callback_on_step_end_tensor_inputs=None,
max_sequence_length=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"您传入的生成器列表长度为 {len(generator)},请求的有效批量大小为 {batch_size}。"
f" 确保批量大小与生成器长度一致。"
)
# 如果潜变量为 None,则生成随机潜变量
if latents is None:
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
else:
# 如果潜变量不为 None,将其移至指定设备
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
# 从 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)
# 从 diffusers.pipelines.latent_consistency_models.pipeline_latent_consistency_text2img.LatentConsistencyModelPipeline.get_guidance_scale_embedding 复制的函数
def get_guidance_scale_embedding(
# 输入的张量 w 和嵌入维度以及数据类型的默认值
self, w: torch.Tensor, embedding_dim: int = 512, dtype: torch.dtype = torch.float32
# 该函数返回生成的嵌入向量,形状为 (len(w), embedding_dim)
) -> torch.Tensor:
"""
# 引用文档,详细说明函数的实现和参数
See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298
Args:
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)`.
"""
# 确保输入的 w 只有一维
assert len(w.shape) == 1
# 将 w 的值扩大1000倍
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)
# 将 w 转换为指定 dtype 并进行广播
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
# 定义指导尺度,与Imagen论文中的指导权重 w 类似
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `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
# 返回交叉注意力的关键字参数
@property
def cross_attention_kwargs(self):
return self._cross_attention_kwargs
# 返回去噪结束的属性
@property
def denoising_end(self):
return self._denoising_end
# 返回时间步数的属性
@property
def num_timesteps(self):
return self._num_timesteps
# 返回中断的属性
@property
def interrupt(self):
return self._interrupt
# 禁用梯度计算以提高效率
@torch.no_grad()
# 替换示例文档字符串
@replace_example_docstring(EXAMPLE_DOC_STRING)
# 定义调用方法,允许通过实例调用该类
def __call__(
# 输入提示,可以是字符串或字符串列表
self,
prompt: Union[str, List[str]] = 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,
# 负面提示,可以是字符串或字符串列表,默认为 None
negative_prompt: Optional[Union[str, List[str]]] = None,
# 每个提示生成的图像数量,默认为 1
num_images_per_prompt: Optional[int] = 1,
# eta 值,默认为 0.0
eta: float = 0.0,
# 随机数生成器,可选
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 潜在表示,可选
latents: Optional[torch.Tensor] = None,
# 提示嵌入,可选
prompt_embeds: Optional[torch.Tensor] = None,
# 处理过的提示嵌入,可选
pooled_prompt_embeds: Optional[torch.Tensor] = None,
# 负面提示嵌入,可选
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 处理过的负面提示嵌入,可选
negative_pooled_prompt_embeds: Optional[torch.Tensor] = None,
# IP 适配器图像,可选
ip_adapter_image: Optional[PipelineImageInput] = None,
# IP 适配器图像嵌入的列表,可选
ip_adapter_image_embeds: Optional[List[torch.Tensor]] = None,
# 输出类型,默认为 "pil"
output_type: Optional[str] = "pil",
# 是否返回字典,默认为 True
return_dict: bool = True,
# 跨注意力参数的字典,可选
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 原始图像大小,可选
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,
# 在步骤结束时的回调函数,可选
callback_on_step_end: Optional[
Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks]
] = None,
# 在步骤结束时的张量输入回调名称,默认为 ["latents"]
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
# PAG 缩放因子,默认为 3.0
pag_scale: float = 3.0,
# 自适应 PAG 缩放,默认为 0.0
pag_adaptive_scale: float = 0.0,
# 最大序列长度,默认为 256
max_sequence_length: int = 256,
.\diffusers\pipelines\pag\pipeline_pag_pixart_sigma.py
# 版权声明,说明本文件的版权归 PixArt-Sigma 作者和 HuggingFace 团队所有,保留所有权利。
#
# 根据 Apache 许可证第 2.0 版("许可证")进行许可;
# 除非遵循许可证,否则您不得使用此文件。
# 您可以在以下地址获取许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面同意,否则根据许可证分发的软件在 "按原样" 基础上提供,
# 不附带任何明示或暗示的担保或条件。
# 有关许可证下特定权限和限制的信息,请参见许可证。
import html # 导入 HTML 处理库
import inspect # 导入用于检查对象的库
import re # 导入正则表达式库
import urllib.parse as ul # 导入用于解析和构建 URL 的库
from typing import Callable, List, Optional, Tuple, Union # 导入类型提示
import torch # 导入 PyTorch 库
from transformers import T5EncoderModel, T5Tokenizer # 从 transformers 库导入 T5 编码器模型和分词器
from ...image_processor import PixArtImageProcessor # 从相对路径导入 PixArt 图像处理器
from ...models import AutoencoderKL, PixArtTransformer2DModel # 从相对路径导入模型
from ...schedulers import KarrasDiffusionSchedulers # 从相对路径导入 Karras 扩散调度器
from ...utils import ( # 从相对路径导入多个实用工具
BACKENDS_MAPPING, # 后端映射
deprecate, # 废弃标记
is_bs4_available, # 检查 BeautifulSoup 是否可用的函数
is_ftfy_available, # 检查 ftfy 是否可用的函数
logging, # 日志记录工具
replace_example_docstring, # 替换示例文档字符串的工具
)
from ...utils.torch_utils import randn_tensor # 从相对路径导入生成随机张量的工具
from ..pipeline_utils import DiffusionPipeline, ImagePipelineOutput # 从相对路径导入扩散管道和图像输出类
from ..pixart_alpha.pipeline_pixart_alpha import ( # 从相对路径导入 PixArt Alpha 管道中的多个常量
ASPECT_RATIO_256_BIN, # 256 维的长宽比常量
ASPECT_RATIO_512_BIN, # 512 维的长宽比常量
ASPECT_RATIO_1024_BIN, # 1024 维的长宽比常量
)
from ..pixart_alpha.pipeline_pixart_sigma import ASPECT_RATIO_2048_BIN # 从相对路径导入 2048 维的长宽比常量
from .pag_utils import PAGMixin # 从当前模块导入 PAGMixin 类
logger = logging.get_logger(__name__) # 获取当前模块的日志记录器实例,禁用 pylint 的无效名称警告
if is_bs4_available(): # 检查是否可用 BeautifulSoup 库
from bs4 import BeautifulSoup # 从 bs4 导入 BeautifulSoup 类
if is_ftfy_available(): # 检查是否可用 ftfy 库
import ftfy # 导入 ftfy 库
EXAMPLE_DOC_STRING = """ # 示例文档字符串,用于展示如何使用该管道
Examples: # 示例部分的标题
```py # Python 代码块开始
>>> import torch # 导入 PyTorch 库
>>> from diffusers import AutoPipelineForText2Image # 从 diffusers 库导入自动文本到图像的管道
>>> pipe = AutoPipelineForText2Image.from_pretrained( # 从预训练模型加载管道
... "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", # 指定模型路径
... torch_dtype=torch.float16, # 设置使用的 PyTorch 数据类型
... pag_applied_layers=["blocks.14"], # 指定应用 PAG 的层
... enable_pag=True, # 启用 PAG
... ) # 结束管道初始化
>>> pipe = pipe.to("cuda") # 将管道移动到 CUDA 设备
>>> prompt = "A small cactus with a happy face in the Sahara desert" # 定义提示语
>>> image = pipe(prompt, pag_scale=4.0, guidance_scale=1.0).images[0] # 生成图像并获取第一张图像
```py # Python 代码块结束
"""
# 从 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, # 额外的关键字参数
):
""" # 函数文档字符串
调用调度器的 `set_timesteps` 方法,并在调用后从调度器中检索时间步。处理
自定义时间步。任何 kwargs 将被传递给 `scheduler.set_timesteps`。
``` # 函数文档字符串结束
```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 # 代码块结束
``` # 代码块结束
# 参数说明部分,解释函数输入的参数类型和含义
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` # 如果使用此参数,则 `timesteps` 必须为 `None`
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. # 如果为 `None`,则不移动时间步
timesteps (`List[int]`, *optional*): # 自定义时间步,用于覆盖调度器的时间步间隔策略
Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed, # 如果传入 `timesteps`,则 `num_inference_steps` 和 `sigmas` 必须为 `None`
`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, # 如果传入 `sigmas`,则 `num_inference_steps` 和 `timesteps` 必须为 `None`
`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. # 第二个元素是推理步骤的数量
"""
if timesteps is not None and sigmas is not None: # 检查是否同时提供了时间步和 sigma
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) # 计算推理步骤的数量
elif sigmas is not None: # 如果提供了 sigma
accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) # 检查调度器是否支持自定义 sigma
if not accept_sigmas: # 如果不支持
raise ValueError( # 抛出值错误,提示不支持自定义 sigma
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."
)
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs) # 设置调度器的 sigma
timesteps = scheduler.timesteps # 获取调度器的时间步
num_inference_steps = len(timesteps) # 计算推理步骤的数量
else: # 如果既没有提供时间步也没有提供 sigma
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) # 使用推理步骤数量设置时间步
timesteps = scheduler.timesteps # 获取调度器的时间步
return timesteps, num_inference_steps # 返回时间步和推理步骤的数量
# 定义一个名为 PixArtSigmaPAGPipeline 的类,继承自 DiffusionPipeline 和 PAGMixin
class PixArtSigmaPAGPipeline(DiffusionPipeline, PAGMixin):
r"""
[PAG pipeline](https://huggingface.co/docs/diffusers/main/en/using-diffusers/pag) for text-to-image generation
using PixArt-Sigma.
""" # 文档字符串,描述该管道的用途
# 定义一个用于匹配不良标点符号的正则表达式
bad_punct_regex = re.compile(
r"["
+ "#®•©™&@·º½¾¿¡§~" # 匹配特定的不良字符
+ r"\)" # 匹配右括号
+ r"\(" # 匹配左括号
+ r"\]" # 匹配右方括号
+ r"\[" # 匹配左方括号
+ r"\}" # 匹配右花括号
+ r"\{" # 匹配左花括号
+ r"\|" # 匹配竖线
+ "\\" # 匹配反斜杠
+ r"\/" # 匹配斜杠
+ r"\*" # 匹配星号
+ r"]{1,}" # 匹配一个或多个上述字符
) # noqa
# 定义可选组件的列表,包括 tokenizer 和 text_encoder
_optional_components = ["tokenizer", "text_encoder"]
# 定义模型的 CPU 离线顺序
model_cpu_offload_seq = "text_encoder->transformer->vae"
# 初始化方法,接收多个参数以构建管道
def __init__(
self,
tokenizer: T5Tokenizer, # 传入的 tokenizer 对象
text_encoder: T5EncoderModel, # 传入的文本编码器对象
vae: AutoencoderKL, # 传入的变分自编码器对象
transformer: PixArtTransformer2DModel, # 传入的变换器模型对象
scheduler: KarrasDiffusionSchedulers, # 传入的调度器对象
pag_applied_layers: Union[str, List[str]] = "blocks.1", # 应用 PAG 的层,默认为第一个变换器块
):
# 调用父类的初始化方法
super().__init__()
# 注册各个模块以供后续使用
self.register_modules(
tokenizer=tokenizer, text_encoder=text_encoder, vae=vae, transformer=transformer, scheduler=scheduler
)
# 计算 VAE 的缩放因子
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
# 创建 PixArt 图像处理器对象,使用 VAE 缩放因子
self.image_processor = PixArtImageProcessor(vae_scale_factor=self.vae_scale_factor)
# 设置 PAG 应用的层
self.set_pag_applied_layers(pag_applied_layers)
# 从 PixArtAlphaPipeline 复制的 encode_prompt 方法,修改了最大序列长度
def encode_prompt(
self,
prompt: Union[str, List[str]], # 输入的提示文本,可以是字符串或字符串列表
do_classifier_free_guidance: bool = True, # 是否进行无分类器引导
negative_prompt: str = "", # 可选的负面提示文本
num_images_per_prompt: int = 1, # 每个提示生成的图像数量
device: Optional[torch.device] = None, # 可选的设备参数
prompt_embeds: Optional[torch.Tensor] = None, # 可选的提示嵌入
negative_prompt_embeds: Optional[torch.Tensor] = None, # 可选的负面提示嵌入
prompt_attention_mask: Optional[torch.Tensor] = None, # 可选的提示注意力掩码
negative_prompt_attention_mask: Optional[torch.Tensor] = None, # 可选的负面提示注意力掩码
clean_caption: bool = False, # 是否清理标题
max_sequence_length: int = 300, # 最大序列长度,默认为 300
**kwargs, # 其他可选参数
# 从 StableDiffusionPipeline 复制的 prepare_extra_step_kwargs 方法
# 准备调度器步骤的额外参数,因为并非所有调度器都有相同的参数签名
def prepare_extra_step_kwargs(self, generator, eta):
# eta (η) 仅用于 DDIMScheduler,对于其他调度器将被忽略
# eta 对应于 DDIM 论文中的 η: https://arxiv.org/abs/2010.02502
# eta 应在 [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.pixart_alpha.pipeline_pixart_alpha.PixArtAlphaPipeline.check_inputs 复制而来
def check_inputs(
self,
# 输入的提示信息
prompt,
# 输出图像的高度
height,
# 输出图像的宽度
width,
# 负提示信息
negative_prompt,
# 回调步骤
callback_steps,
# 可选的提示嵌入
prompt_embeds=None,
# 可选的负提示嵌入
negative_prompt_embeds=None,
# 可选的提示注意力掩码
prompt_attention_mask=None,
# 可选的负提示注意力掩码
negative_prompt_attention_mask=None,
# 从 diffusers.pipelines.deepfloyd_if.pipeline_if.IFPipeline._text_preprocessing 复制而来
def _text_preprocessing(self, text, clean_caption=False):
# 如果需要清理标题但缺少 bs4 库,则发出警告
if clean_caption and not is_bs4_available():
logger.warning(BACKENDS_MAPPING["bs4"][-1].format("Setting `clean_caption=True`"))
logger.warning("Setting `clean_caption` to False...")
# 将 clean_caption 设置为 False
clean_caption = False
# 如果需要清理标题但缺少 ftfy 库,则发出警告
if clean_caption and not is_ftfy_available():
logger.warning(BACKENDS_MAPPING["ftfy"][-1].format("Setting `clean_caption=True`"))
logger.warning("Setting `clean_caption` to False...")
# 将 clean_caption 设置为 False
clean_caption = False
# 如果输入的文本不是元组或列表,则将其转换为列表
if not isinstance(text, (tuple, list)):
text = [text]
# 定义处理文本的函数
def process(text: str):
# 如果需要清理标题,则执行清理操作
if clean_caption:
text = self._clean_caption(text)
text = self._clean_caption(text)
else:
# 否则将文本转换为小写并去掉首尾空白
text = text.lower().strip()
# 返回处理后的文本
return text
# 返回处理后的所有文本
return [process(t) for t in text]
# 从 diffusers.pipelines.deepfloyd_if.pipeline_if.IFPipeline._clean_caption 复制而来
# 从 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,
)
# 检查生成器的类型和数量是否与批次大小匹配
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
# 禁用梯度计算以提高性能
@torch.no_grad()
# 替换文档字符串
@replace_example_docstring(EXAMPLE_DOC_STRING)
def __call__(
# 接受提示,可以是单个字符串或字符串列表
prompt: Union[str, List[str]] = None,
# 负提示,默认为空字符串
negative_prompt: str = "",
# 推理步骤的数量,默认为20
num_inference_steps: int = 20,
# 时间步列表,默认为None
timesteps: List[int] = None,
# sigma值列表,默认为None
sigmas: List[float] = None,
# 指导比例,默认为4.5
guidance_scale: float = 4.5,
# 每个提示生成的图像数量,默认为1
num_images_per_prompt: Optional[int] = 1,
# 图像高度,默认为None
height: Optional[int] = None,
# 图像宽度,默认为None
width: Optional[int] = None,
# eta值,默认为0.0
eta: float = 0.0,
# 随机生成器,默认为None
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 潜在变量,默认为None
latents: Optional[torch.Tensor] = None,
# 提示嵌入,默认为None
prompt_embeds: Optional[torch.Tensor] = None,
# 提示注意力掩码,默认为None
prompt_attention_mask: Optional[torch.Tensor] = None,
# 负提示嵌入,默认为None
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 负提示注意力掩码,默认为None
negative_prompt_attention_mask: 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,
# 是否清理提示,默认为True
clean_caption: bool = True,
# 是否使用分辨率分箱,默认为True
use_resolution_binning: bool = True,
# 最大序列长度,默认为300
max_sequence_length: int = 300,
# pag_scale,默认为3.0
pag_scale: float = 3.0,
# pag_adaptive_scale,默认为0.0
pag_adaptive_scale: float = 0.0,
.\diffusers\pipelines\pag\pipeline_pag_sd.py
# 版权声明,指明版权归 HuggingFace 团队所有
# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# 根据 Apache 许可证第 2.0 版(“许可证”)进行授权;
# 除非遵循许可证,否则不得使用此文件。
# 可以在以下网址获取许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有约定,否则根据许可证分发的软件是按“现状”基础分发的,
# 不附有任何形式的明示或暗示的担保或条件。
# 有关许可证下特定权限和限制的更多信息,请参见许可证。
import inspect # 导入 inspect 模块,用于获取活跃对象的详细信息
from typing import Any, Callable, Dict, List, Optional, Union # 导入类型提示相关的工具
import torch # 导入 PyTorch 库
from packaging import version # 导入版本管理工具
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer, CLIPVisionModelWithProjection # 导入 CLIP 相关的处理器和模型
from ...configuration_utils import FrozenDict # 导入 FrozenDict,可能用于不可变字典配置
from ...image_processor import PipelineImageInput, VaeImageProcessor # 导入图像处理相关的类
from ...loaders import FromSingleFileMixin, IPAdapterMixin, StableDiffusionLoraLoaderMixin, TextualInversionLoaderMixin # 导入不同的加载器混合类
from ...models import AutoencoderKL, ImageProjection, UNet2DConditionModel # 导入模型类
from ...models.lora import adjust_lora_scale_text_encoder # 导入用于调整 Lora 模型的函数
from ...schedulers import KarrasDiffusionSchedulers # 导入 Karras 扩散调度器
from ...utils import ( # 从工具模块导入多个实用函数
USE_PEFT_BACKEND, # 指示是否使用 PEFT 后端
deprecate, # 用于标记弃用的功能
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.pipeline_output import StableDiffusionPipelineOutput # 导入稳定扩散管道的输出类
from ..stable_diffusion.safety_checker import StableDiffusionSafetyChecker # 导入稳定扩散安全检查器
from .pag_utils import PAGMixin # 导入 PAG 相关的混合类
logger = logging.get_logger(__name__) # 创建一个日志记录器,记录当前模块的信息;禁用 pylint 的无效名称警告
EXAMPLE_DOC_STRING = """ # 定义一个示例文档字符串
Examples: # 示例部分
```py # 示例代码块开始
>>> import torch # 导入 PyTorch 库
>>> from diffusers import AutoPipelineForText2Image # 从 diffusers 导入自动文本到图像管道
>>> pipe = AutoPipelineForText2Image.from_pretrained( # 从预训练模型加载管道
... "runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16, enable_pag=True # 指定模型路径、数据类型及启用 PAG
... )
>>> pipe = pipe.to("cuda") # 将管道移动到 CUDA 设备
>>> prompt = "a photo of an astronaut riding a horse on mars" # 定义生成图像的提示
>>> image = pipe(prompt, pag_scale=0.3).images[0] # 生成图像并获取第一张图像
```py # 示例代码块结束
"""
# 从稳定扩散管道复制的函数,调整噪声配置
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
# 从 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
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]`, *可选*):
用于覆盖调度器的时间步间隔策略的自定义 sigmas。如果传入 `sigmas`,
则 `num_inference_steps` 和 `timesteps` 必须为 `None`。
返回:
`Tuple[torch.Tensor, int]`: 一个元组,第一个元素是来自调度器的时间步计划,
第二个元素是推理步骤的数量。
"""
# 如果同时传入了自定义时间步和 sigmas,抛出异常
if timesteps is not None and sigmas is not None:
raise ValueError("只能传入 `timesteps` 或 `sigmas` 中的一个。请选择一个以设置自定义值")
# 如果传入了自定义时间步
if timesteps is not None:
# 检查调度器的 `set_timesteps` 方法是否接受自定义时间步
accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
# 如果不支持自定义时间步,抛出异常
if not accepts_timesteps:
raise ValueError(
f"当前调度器类 {scheduler.__class__} 的 `set_timesteps` 不支持自定义"
f" 时间步计划。请检查是否使用了正确的调度器。"
)
# 设置调度器的时间步
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
# 从调度器获取设置后的时间步
timesteps = scheduler.timesteps
# 计算推理步骤的数量
num_inference_steps = len(timesteps)
# 如果传入了自定义 sigmas
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"当前调度器类 {scheduler.__class__} 的 `set_timesteps` 不支持自定义"
f" sigmas 时间步计划。请检查是否使用了正确的调度器。"
)
# 设置调度器的 sigmas
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
# 定义一个类 StableDiffusionPAGPipeline,继承自多个基类以实现特定功能
class StableDiffusionPAGPipeline(
# 继承自 DiffusionPipeline 基类,提供基本的扩散管道功能
DiffusionPipeline,
# 继承自 StableDiffusionMixin,添加 Stable Diffusion 特有的功能
StableDiffusionMixin,
# 继承自 TextualInversionLoaderMixin,支持文本反演加载功能
TextualInversionLoaderMixin,
# 继承自 StableDiffusionLoraLoaderMixin,支持加载 LoRA 权重
StableDiffusionLoraLoaderMixin,
# 继承自 IPAdapterMixin,支持加载 IP 适配器
IPAdapterMixin,
# 继承自 FromSingleFileMixin,支持从单一文件加载模型
FromSingleFileMixin,
# 继承自 PAGMixin,添加特定于 PAG 的功能
PAGMixin,
):
# 类文档字符串,描述该管道的功能
r"""
Pipeline for text-to-image generation using Stable 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.).
The pipeline also inherits the following loading methods:
- [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] for loading textual inversion embeddings
- [`~loaders.StableDiffusionLoraLoaderMixin.load_lora_weights`] for loading LoRA weights
- [`~loaders.StableDiffusionLoraLoaderMixin.save_lora_weights`] for saving LoRA weights
- [`~loaders.FromSingleFileMixin.from_single_file`] for loading `.ckpt` files
- [`~loaders.IPAdapterMixin.load_ip_adapter`] for loading IP Adapters
Args:
vae ([`AutoencoderKL`]):
Variational Auto-Encoder (VAE) model to encode and decode images to and from latent representations.
text_encoder ([`~transformers.CLIPTextModel`]):
Frozen text-encoder ([clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14)).
tokenizer ([`~transformers.CLIPTokenizer`]):
A `CLIPTokenizer` to tokenize text.
unet ([`UNet2DConditionModel`]):
A `UNet2DConditionModel` to denoise the encoded image latents.
scheduler ([`SchedulerMixin`]):
A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of
[`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`].
safety_checker ([`StableDiffusionSafetyChecker`]):
Classification module that estimates whether generated images could be considered offensive or harmful.
Please refer to the [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) for more details
about a model's potential harms.
feature_extractor ([`~transformers.CLIPImageProcessor`]):
A `CLIPImageProcessor` to extract features from generated images; used as inputs to the `safety_checker`.
"""
# 定义模型在 CPU 上的卸载顺序
model_cpu_offload_seq = "text_encoder->image_encoder->unet->vae"
# 定义可选组件的列表,包含可能的附加功能
_optional_components = ["safety_checker", "feature_extractor", "image_encoder"]
# 定义不进行 CPU 卸载的组件列表,确保安全检查器始终在 CPU 上
_exclude_from_cpu_offload = ["safety_checker"]
# 定义回调张量输入的列表,指定需要监控的输入数据
_callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"]
# 初始化类的构造函数,接收多个参数用于模型的配置
def __init__(
self,
# VAE模型,负责生成和重构图像
vae: AutoencoderKL,
# 文本编码器,使用CLIP进行文本嵌入
text_encoder: CLIPTextModel,
# 分词器,将文本转换为模型可处理的格式
tokenizer: CLIPTokenizer,
# UNet模型,用于图像生成
unet: UNet2DConditionModel,
# 调度器,控制生成过程中的采样
scheduler: KarrasDiffusionSchedulers,
# 安全检查器,用于确保生成内容的安全性
safety_checker: StableDiffusionSafetyChecker,
# 特征提取器,处理输入图像
feature_extractor: CLIPImageProcessor,
# 可选的图像编码器,用于图像的额外处理
image_encoder: CLIPVisionModelWithProjection = None,
# 是否需要安全检查的标志
requires_safety_checker: bool = True,
# 应用层的配置,控制图像处理的层次
pag_applied_layers: Union[str, List[str]] = "mid",
# 从稳定扩散管道中复制的函数,编码提示文本
def encode_prompt(
self,
# 输入的文本提示
prompt,
# 设备类型(CPU或GPU)
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层的参数
clip_skip: Optional[int] = None,
# 从稳定扩散管道中复制的函数,编码输入图像
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
# 从稳定扩散管道中复制的函数,准备图像嵌入
def prepare_ip_adapter_image_embeds(
# 输入的IP适配器图像
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: # 检查 IP 适配器图像嵌入是否为空
if not isinstance(ip_adapter_image, list): # 确保 ip_adapter_image 是列表
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 适配器图像和图像投影层
ip_adapter_image, self.unet.encoder_hid_proj.image_projection_layers # 将两者打包在一起
):
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: # 如果 ip_adapter_image_embeds 不是 None
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 = [] # 初始化最终的 IP 适配器图像嵌入列表
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 # 返回最终的 IP 适配器图像嵌入
# Copied from 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:
has_nsfw_concept = None
else:
# 如果输入的图像是张量,进行后处理以转换为 PIL 格式
if torch.is_tensor(image):
feature_extractor_input = self.image_processor.postprocess(image, output_type="pil")
# 如果输入的图像不是张量,将其从 numpy 数组转换为 PIL 格式
else:
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)
)
# 返回处理后的图像和不安全内容标志
return image, has_nsfw_concept
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs 复制
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 = {}
# 如果调度器接受 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
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.check_inputs 复制
def check_inputs(
self,
prompt,
height,
width,
callback_steps,
negative_prompt=None,
prompt_embeds=None,
negative_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,
)
# 检查生成器列表的长度是否与批量大小匹配
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
# 从 diffusers.pipelines.latent_consistency_models.pipeline_latent_consistency_text2img 导入的函数
def get_guidance_scale_embedding(
self, w: torch.Tensor, embedding_dim: int = 512, dtype: torch.dtype = torch.float32
) -> torch.Tensor:
"""
参见 https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298
参数:
w (`torch.Tensor`):
使用指定的引导比例生成嵌入向量,以随后丰富时间步嵌入。
embedding_dim (`int`, *可选*, 默认为 512):
生成的嵌入的维度。
dtype (`torch.dtype`, *可选*, 默认为 `torch.float32`):
生成嵌入的数据类型。
返回:
`torch.Tensor`: 形状为 `(len(w), embedding_dim)` 的嵌入向量。
"""
# 确保输入张量是一维的
assert len(w.shape) == 1
# 将 w 乘以 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
# 返回引导重标定的属性
@property
def guidance_rescale(self):
return self._guidance_rescale
# 返回跳过剪辑的属性
@property
def clip_skip(self):
return self._clip_skip
# 这里的 `guidance_scale` 按照 Imagen 论文中的引导权重 `w` 定义
# `guidance_scale = 1` 对应于不进行无分类器引导
@property
# 定义一个方法用于无分类器自由引导
def do_classifier_free_guidance(self):
# 检查引导比例是否大于1且时间条件投影维度为None
return self._guidance_scale > 1 and self.unet.config.time_cond_proj_dim is None
# 定义一个属性,用于获取交叉注意力的参数
@property
def cross_attention_kwargs(self):
# 返回交叉注意力的参数
return self._cross_attention_kwargs
# 定义一个属性,用于获取时间步数
@property
def num_timesteps(self):
# 返回时间步数的值
return self._num_timesteps
# 定义一个属性,用于获取中断状态
@property
def interrupt(self):
# 返回中断状态的值
return self._interrupt
# 使用装饰器禁用梯度计算以提高性能
@torch.no_grad()
# 替换示例文档字符串
@replace_example_docstring(EXAMPLE_DOC_STRING)
# 定义调用方法,接收多个参数
def __call__(
# 输入的提示文本,可以是字符串或字符串列表
prompt: Union[str, List[str]] = None,
# 高度参数,默认为None
height: Optional[int] = None,
# 宽度参数,默认为None
width: Optional[int] = None,
# 推理步骤数量,默认为50
num_inference_steps: int = 50,
# 时间步列表,默认为None
timesteps: List[int] = None,
# Sigma值列表,默认为None
sigmas: List[float] = None,
# 引导比例,默认为7.5
guidance_scale: float = 7.5,
# 负提示文本,可以是字符串或字符串列表,默认为None
negative_prompt: Optional[Union[str, List[str]]] = None,
# 每个提示生成的图像数量,默认为1
num_images_per_prompt: Optional[int] = 1,
# Eta值,默认为0.0
eta: float = 0.0,
# 随机数生成器,默认为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
ip_adapter_image: Optional[PipelineImageInput] = None,
# 图像适配器嵌入,默认为None
ip_adapter_image_embeds: Optional[List[torch.Tensor]] = None,
# 输出类型,默认为"pil"
output_type: Optional[str] = "pil",
# 是否返回字典,默认为True
return_dict: bool = True,
# 交叉注意力的参数,默认为None
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 引导重标定值,默认为0.0
guidance_rescale: float = 0.0,
# 跳过的剪辑次数,默认为None
clip_skip: Optional[int] = None,
# 每步结束时的回调函数,默认为None
callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
# 每步结束时的张量输入回调列表,默认为["latents"]
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
# PAG比例,默认为3.0
pag_scale: float = 3.0,
# 自适应PAG比例,默认为0.0
pag_adaptive_scale: float = 0.0,
.\diffusers\pipelines\pag\pipeline_pag_sd_3.py
# 版权声明,指明版权归属及相关许可信息
# Copyright 2024 Stability AI 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.
# 导入用于检查对象信息的模块
import inspect
# 导入类型提示相关的类型
from typing import Any, Callable, Dict, List, Optional, Union
# 导入 PyTorch 库
import torch
# 导入 transformers 库中的相关模型和分词器
from transformers import (
CLIPTextModelWithProjection, # 导入 CLIP 文本模型
CLIPTokenizer, # 导入 CLIP 分词器
T5EncoderModel, # 导入 T5 编码器模型
T5TokenizerFast, # 导入快速 T5 分词器
)
# 导入自定义图像处理器
from ...image_processor import VaeImageProcessor
# 导入自定义加载器
from ...loaders import FromSingleFileMixin, SD3LoraLoaderMixin
# 导入自定义注意力处理器
from ...models.attention_processor import PAGCFGJointAttnProcessor2_0, PAGJointAttnProcessor2_0
# 导入自定义自动编码器
from ...models.autoencoders import AutoencoderKL
# 导入自定义变换器模型
from ...models.transformers import SD3Transformer2DModel
# 导入自定义调度器
from ...schedulers import FlowMatchEulerDiscreteScheduler
# 导入各种实用工具函数
from ...utils import (
USE_PEFT_BACKEND, # 导入是否使用 PEFT 后端的标识
is_torch_xla_available, # 导入检查 XLA 是否可用的函数
logging, # 导入日志记录模块
replace_example_docstring, # 导入替换示例文档字符串的函数
scale_lora_layers, # 导入缩放 LoRA 层的函数
unscale_lora_layers, # 导入取消缩放 LoRA 层的函数
)
# 导入 PyTorch 相关的实用工具函数
from ...utils.torch_utils import randn_tensor
# 导入扩散管道的实用工具
from ..pipeline_utils import DiffusionPipeline
# 导入稳定扩散的管道输出
from ..stable_diffusion_3.pipeline_output import StableDiffusion3PipelineOutput
# 导入 PAG 相关的实用工具
from .pag_utils import PAGMixin
# 检查 XLA 是否可用,如果可用则导入 XLA 相关模块
if is_torch_xla_available():
import torch_xla.core.xla_model as xm # 导入 XLA 核心模型
XLA_AVAILABLE = True # 设置 XLA 可用标识为真
else:
XLA_AVAILABLE = False # 设置 XLA 可用标识为假
# 获取当前模块的日志记录器
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
# 示例文档字符串,包含使用示例
EXAMPLE_DOC_STRING = """
Examples:
```py
>>> import torch
>>> from diffusers import AutoPipelineForText2Image
>>> pipe = AutoPipelineForText2Image.from_pretrained(
... "stabilityai/stable-diffusion-3-medium-diffusers",
... torch_dtype=torch.float16,
... enable_pag=True,
... pag_applied_layers=["blocks.13"],
... )
>>> pipe.to("cuda") # 将管道移动到 GPU
>>> prompt = "A cat holding a sign that says hello world" # 定义生成图像的提示
>>> image = pipe(prompt, guidance_scale=5.0, pag_scale=0.7).images[0] # 生成图像
>>> image.save("sd3_pag.png") # 保存生成的图像
```py
"""
# 从 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, # 其他可选参数
):
"""
调用调度器的 `set_timesteps` 方法,并在调用后从调度器检索时间步。处理自定义时间步。
Any kwargs will be supplied to `scheduler.set_timesteps`.
# 参数说明
Args:
# 调度器,用于获取时间步的类
scheduler (`SchedulerMixin`):
# 从调度器获取时间步
The scheduler to get timesteps from.
# 生成样本时的扩散步骤数量
num_inference_steps (`int`):
# 使用预训练模型生成样本时的扩散步骤数。如果使用,`timesteps` 必须为 `None`
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*):
# 如果为 `None`,则时间步不会被移动
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
# 可选参数,自定义时间步以覆盖调度器的时间步间隔策略
timesteps (`List[int]`, *optional*):
# 如果传递 `timesteps`,则 `num_inference_steps` 和 `sigmas` 必须为 `None`
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 以覆盖调度器的时间步间隔策略
sigmas (`List[float]`, *optional*):
# 如果传递 `sigmas`,则 `num_inference_steps` 和 `timesteps` 必须为 `None`
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.
"""
# 如果同时传递了时间步和 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
# 定义一个名为 StableDiffusion3PAGPipeline 的类,继承自多个基类
class StableDiffusion3PAGPipeline(DiffusionPipeline, SD3LoraLoaderMixin, FromSingleFileMixin, PAGMixin):
r"""
# 文档字符串,描述该类的功能和参数
[PAG pipeline](https://huggingface.co/docs/diffusers/main/en/using-diffusers/pag) for text-to-image generation
using Stable Diffusion 3.
Args:
transformer ([`SD3Transformer2DModel`]):
Conditional Transformer (MMDiT) architecture to denoise the encoded image latents.
scheduler ([`FlowMatchEulerDiscreteScheduler`]):
A scheduler to be used in combination with `transformer` to denoise the encoded image latents.
vae ([`AutoencoderKL`]):
Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations.
text_encoder ([`CLIPTextModelWithProjection`]):
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModelWithProjection),
specifically the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant,
with an additional added projection layer that is initialized with a diagonal matrix with the `hidden_size`
as its dimension.
text_encoder_2 ([`CLIPTextModelWithProjection`]):
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModelWithProjection),
specifically the
[laion/CLIP-ViT-bigG-14-laion2B-39B-b160k](https://huggingface.co/laion/CLIP-ViT-bigG-14-laion2B-39B-b160k)
variant.
text_encoder_3 ([`T5EncoderModel`]):
Frozen text-encoder. Stable Diffusion 3 uses
[T5](https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5EncoderModel), specifically the
[t5-v1_1-xxl](https://huggingface.co/google/t5-v1_1-xxl) variant.
tokenizer (`CLIPTokenizer`):
Tokenizer of class
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer).
tokenizer_2 (`CLIPTokenizer`):
Second Tokenizer of class
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer).
tokenizer_3 (`T5TokenizerFast`):
Tokenizer of class
[T5Tokenizer](https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5Tokenizer).
"""
# 定义一个模型的 CPU 卸载顺序,指定各组件之间的依赖关系
model_cpu_offload_seq = "text_encoder->text_encoder_2->text_encoder_3->transformer->vae"
# 定义可选组件的空列表,可能在子类中进行扩展
_optional_components = []
# 定义回调张量输入的名称列表,这些输入将在处理过程中被回调
_callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds", "negative_pooled_prompt_embeds"]
# 初始化类的构造函数
def __init__(
self,
transformer: SD3Transformer2DModel, # 输入的 2D 转换器模型
scheduler: FlowMatchEulerDiscreteScheduler, # 调度器,用于控制模型的生成过程
vae: AutoencoderKL, # 自编码器,用于生成图像
text_encoder: CLIPTextModelWithProjection, # 文本编码器,将文本转为向量
tokenizer: CLIPTokenizer, # 文本分词器,将文本拆分为标记
text_encoder_2: CLIPTextModelWithProjection, # 第二个文本编码器
tokenizer_2: CLIPTokenizer, # 第二个文本分词器
text_encoder_3: T5EncoderModel, # 第三个文本编码器
tokenizer_3: T5TokenizerFast, # 第三个文本分词器
pag_applied_layers: Union[str, List[str]] = "blocks.1", # 默认应用于第一个变换层
):
# 调用父类的构造函数
super().__init__()
# 注册各个模块,使其可以在模型中使用
self.register_modules(
vae=vae, # 注册自编码器
text_encoder=text_encoder, # 注册文本编码器
text_encoder_2=text_encoder_2, # 注册第二个文本编码器
text_encoder_3=text_encoder_3, # 注册第三个文本编码器
tokenizer=tokenizer, # 注册文本分词器
tokenizer_2=tokenizer_2, # 注册第二个文本分词器
tokenizer_3=tokenizer_3, # 注册第三个文本分词器
transformer=transformer, # 注册转换器
scheduler=scheduler, # 注册调度器
)
# 根据 VAE 配置计算缩放因子
self.vae_scale_factor = (
2 ** (len(self.vae.config.block_out_channels) - 1) if hasattr(self, "vae") and self.vae is not None else 8
)
# 初始化图像处理器,使用计算出的缩放因子
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
# 获取分词器的最大长度
self.tokenizer_max_length = (
self.tokenizer.model_max_length if hasattr(self, "tokenizer") and self.tokenizer is not None else 77
)
# 获取转换器的默认样本大小
self.default_sample_size = (
self.transformer.config.sample_size
if hasattr(self, "transformer") and self.transformer is not None
else 128
)
# 设置应用于 PAG 的层以及注意力处理器
self.set_pag_applied_layers(
pag_applied_layers, pag_attn_processors=(PAGCFGJointAttnProcessor2_0(), PAGJointAttnProcessor2_0())
)
# 从其他模块复制的函数,用于获取 T5 提示嵌入
def _get_t5_prompt_embeds(
self,
prompt: Union[str, List[str]] = None, # 输入的提示,字符串或字符串列表
num_images_per_prompt: int = 1, # 每个提示生成的图像数量
max_sequence_length: int = 256, # 最大序列长度
device: Optional[torch.device] = None, # 指定设备(如 CPU 或 GPU)
dtype: Optional[torch.dtype] = None, # 指定数据类型
):
# 如果没有指定设备,则使用实例的执行设备
device = device or self._execution_device
# 如果没有指定数据类型,则使用文本编码器的数据类型
dtype = dtype or self.text_encoder.dtype
# 如果提示词是字符串,则将其放入列表中,否则保持原样
prompt = [prompt] if isinstance(prompt, str) else prompt
# 获取提示词的批处理大小
batch_size = len(prompt)
# 如果文本编码器未初始化,则返回全零的张量
if self.text_encoder_3 is None:
return torch.zeros(
(
# 张量的第一维为批处理大小乘以每个提示生成的图像数量
batch_size * num_images_per_prompt,
# 张量的第二维为最大令牌长度
self.tokenizer_max_length,
# 张量的第三维为变换器的联合注意力维度
self.transformer.config.joint_attention_dim,
),
# 指定设备
device=device,
# 指定数据类型
dtype=dtype,
)
# 使用 tokenizer_3 编码提示词,返回张量形式的输入
text_inputs = self.tokenizer_3(
prompt,
# 填充到最大长度
padding="max_length",
# 设置最大序列长度
max_length=max_sequence_length,
# 截断超出部分
truncation=True,
# 添加特殊令牌
add_special_tokens=True,
# 返回 PyTorch 张量
return_tensors="pt",
)
# 获取编码后的输入 ID
text_input_ids = text_inputs.input_ids
# 获取未截断的输入 ID
untruncated_ids = self.tokenizer_3(prompt, padding="longest", return_tensors="pt").input_ids
# 检查未截断的 ID 是否比截断的 ID 更长且不相等
if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
# 解码被截断的部分并记录警告
removed_text = self.tokenizer_3.batch_decode(untruncated_ids[:, self.tokenizer_max_length - 1 : -1])
logger.warning(
"The following part of your input was truncated because `max_sequence_length` is set to "
f" {max_sequence_length} tokens: {removed_text}"
)
# 将输入 ID 转移到指定设备并通过文本编码器获得提示嵌入
prompt_embeds = self.text_encoder_3(text_input_ids.to(device))[0]
# 获取文本编码器的数据类型
dtype = self.text_encoder_3.dtype
# 将提示嵌入转换为指定的数据类型和设备
prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
# 解包提示嵌入的形状信息
_, seq_len, _ = prompt_embeds.shape
# 为每个生成的图像复制文本嵌入和注意力掩码,使用适合 mps 的方法
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
# 重塑张量以符合(batch_size * num_images_per_prompt, seq_len, -1)的形状
prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
# 返回处理后的提示嵌入
return prompt_embeds
# 从 diffusers.pipelines.stable_diffusion_3.pipeline_stable_diffusion_3.StableDiffusion3Pipeline._get_clip_prompt_embeds 复制
def _get_clip_prompt_embeds(
self,
# 输入的提示词,可以是字符串或字符串列表
prompt: Union[str, List[str]],
# 每个提示生成的图像数量,默认为 1
num_images_per_prompt: int = 1,
# 可选的设备参数
device: Optional[torch.device] = None,
# 可选的跳过参数
clip_skip: Optional[int] = None,
# 使用的 clip 模型索引,默认为 0
clip_model_index: int = 0,
):
# 如果未指定设备,则使用执行环境中的默认设备
device = device or self._execution_device
# 初始化 CLIP 模型的分词器和文本编码器列表
clip_tokenizers = [self.tokenizer, self.tokenizer_2]
clip_text_encoders = [self.text_encoder, self.text_encoder_2]
# 根据索引选择当前使用的分词器和文本编码器
tokenizer = clip_tokenizers[clip_model_index]
text_encoder = clip_text_encoders[clip_model_index]
# 将提示文本转换为列表形式(如果输入为字符串则转为单元素列表),确定批处理大小
prompt = [prompt] if isinstance(prompt, str) else prompt
batch_size = len(prompt)
# 使用指定的分词器对提示文本进行编码,设置最大长度和填充方式,返回 PyTorch 张量
text_inputs = tokenizer(
prompt,
padding="max_length",
max_length=self.tokenizer_max_length,
truncation=True,
return_tensors="pt",
)
# 获取输入文本的 ID
text_input_ids = text_inputs.input_ids
# 获取未截断的文本 ID,并进行比较,如果存在截断则记录警告信息
untruncated_ids = tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
removed_text = tokenizer.batch_decode(untruncated_ids[:, self.tokenizer_max_length - 1 : -1])
logger.warning(
"The following part of your input was truncated because CLIP can only handle sequences up to"
f" {self.tokenizer_max_length} tokens: {removed_text}"
)
# 使用文本编码器对输入文本进行编码,输出包含隐藏状态
prompt_embeds = text_encoder(text_input_ids.to(device), output_hidden_states=True)
pooled_prompt_embeds = prompt_embeds[0]
# 根据 clip_skip 参数选择合适的隐藏状态
if clip_skip is None:
prompt_embeds = prompt_embeds.hidden_states[-2]
else:
prompt_embeds = prompt_embeds.hidden_states[-(clip_skip + 2)]
# 将编码结果转换为指定的数据类型和设备类型
prompt_embeds = prompt_embeds.to(dtype=self.text_encoder.dtype, device=device)
# 获取文本编码的序列长度
_, seq_len, _ = prompt_embeds.shape
# 使用 MPS 友好的方法复制文本嵌入以生成每个提示的生成结果
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
# 复制池化后的文本嵌入以生成每个提示的生成结果
pooled_prompt_embeds = pooled_prompt_embeds.repeat(1, num_images_per_prompt, 1)
pooled_prompt_embeds = pooled_prompt_embeds.view(batch_size * num_images_per_prompt, -1)
# 返回生成的提示文本嵌入结果
return prompt_embeds, pooled_prompt_embeds
# 从 diffusers.pipelines.stable_diffusion_3.pipeline_stable_diffusion_3.StableDiffusion3Pipeline.encode_prompt 复制过来的
# 定义编码提示的函数,接受多个参数以支持不同的提示输入
def encode_prompt(
# 第一个提示,可以是字符串或字符串列表
self,
prompt: Union[str, List[str]],
# 第二个提示,可以是字符串或字符串列表
prompt_2: Union[str, List[str]],
# 第三个提示,可以是字符串或字符串列表
prompt_3: Union[str, List[str]],
# 可选参数,指定设备(如 GPU 或 CPU)
device: Optional[torch.device] = None,
# 每个提示生成的图像数量,默认是 1
num_images_per_prompt: int = 1,
# 是否进行无分类器引导,默认是 True
do_classifier_free_guidance: bool = True,
# 可选的负面提示,可以是字符串或字符串列表
negative_prompt: Optional[Union[str, List[str]]] = None,
# 可选的第二个负面提示,可以是字符串或字符串列表
negative_prompt_2: Optional[Union[str, List[str]]] = None,
# 可选的第三个负面提示,可以是字符串或字符串列表
negative_prompt_3: Optional[Union[str, List[str]]] = None,
# 可选的提示嵌入,类型为浮点张量
prompt_embeds: Optional[torch.FloatTensor] = None,
# 可选的负面提示嵌入,类型为浮点张量
negative_prompt_embeds: Optional[torch.FloatTensor] = None,
# 可选的池化提示嵌入,类型为浮点张量
pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
# 可选的负面池化提示嵌入,类型为浮点张量
negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
# 可选参数,指定跳过的 CLIP 层数
clip_skip: Optional[int] = None,
# 最大序列长度,默认是 256
max_sequence_length: int = 256,
# 可选的 LORA 缩放因子
lora_scale: Optional[float] = None,
# 从 diffusers.pipelines.stable_diffusion_3.pipeline_stable_diffusion_3.StableDiffusion3Pipeline.check_inputs 复制而来
# 定义输入检查的函数,确保输入的有效性
def check_inputs(
self,
# 第一个提示
prompt,
# 第二个提示
prompt_2,
# 第三个提示
prompt_3,
# 图像高度
height,
# 图像宽度
width,
# 可选的负面提示
negative_prompt=None,
# 可选的第二个负面提示
negative_prompt_2=None,
# 可选的第三个负面提示
negative_prompt_3=None,
# 可选的提示嵌入
prompt_embeds=None,
# 可选的负面提示嵌入
negative_prompt_embeds=None,
# 可选的池化提示嵌入
pooled_prompt_embeds=None,
# 可选的负面池化提示嵌入
negative_pooled_prompt_embeds=None,
# 可选的回调,用于步骤结束时的张量输入
callback_on_step_end_tensor_inputs=None,
# 可选的最大序列长度
max_sequence_length=None,
# 从 diffusers.pipelines.stable_diffusion_3.pipeline_stable_diffusion_3.StableDiffusion3Pipeline.prepare_latents 复制而来
# 定义准备潜在张量的函数,生成潜在张量
def prepare_latents(
self,
# 批次大小
batch_size,
# 潜在张量的通道数
num_channels_latents,
# 图像高度
height,
# 图像宽度
width,
# 数据类型
dtype,
# 设备类型
device,
# 随机数生成器
generator,
# 可选的潜在张量
latents=None,
):
# 如果给定了潜在张量,则将其转移到指定设备和数据类型
if latents is not None:
return latents.to(device=device, dtype=dtype)
# 计算潜在张量的形状
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."
)
# 生成随机潜在张量
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
# 返回生成的潜在张量
return latents
# 定义一个只读属性,用于获取引导缩放因子
@property
def guidance_scale(self):
return self._guidance_scale
# 定义一个只读属性,用于获取 CLIP 跳过的层数
@property
def clip_skip(self):
return self._clip_skip
# 定义一个只读属性,检查是否进行无分类器引导
# 依据 Imagen 论文中的公式定义引导缩放
# guidance_scale = 1 表示没有进行无分类器引导
@property
def do_classifier_free_guidance(self):
return self._guidance_scale > 1
# 定义一个只读属性
# 定义获取联合注意力参数的方法
def joint_attention_kwargs(self):
# 返回类属性 _joint_attention_kwargs 的值
return self._joint_attention_kwargs
# 定义 num_timesteps 属性
@property
def num_timesteps(self):
# 返回类属性 _num_timesteps 的值
return self._num_timesteps
# 定义 interrupt 属性
@property
def interrupt(self):
# 返回类属性 _interrupt 的值
return self._interrupt
# 禁用梯度计算并替换示例文档字符串
@torch.no_grad()
@replace_example_docstring(EXAMPLE_DOC_STRING)
# 定义类的可调用方法
def __call__(
# 定义输入提示,默认为 None
prompt: Union[str, List[str]] = None,
# 定义第二个提示,默认为 None
prompt_2: Optional[Union[str, List[str]]] = None,
# 定义第三个提示,默认为 None
prompt_3: Optional[Union[str, List[str]]] = None,
# 定义图像高度,默认为 None
height: Optional[int] = None,
# 定义图像宽度,默认为 None
width: Optional[int] = None,
# 定义推理步骤数,默认为 28
num_inference_steps: int = 28,
# 定义时间步列表,默认为 None
timesteps: List[int] = None,
# 定义引导比例,默认为 7.0
guidance_scale: float = 7.0,
# 定义负提示,默认为 None
negative_prompt: Optional[Union[str, List[str]]] = None,
# 定义第二个负提示,默认为 None
negative_prompt_2: Optional[Union[str, List[str]]] = None,
# 定义第三个负提示,默认为 None
negative_prompt_3: Optional[Union[str, List[str]]] = None,
# 定义每个提示生成的图像数量,默认为 1
num_images_per_prompt: Optional[int] = 1,
# 定义生成器,默认为 None
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 定义潜在变量,默认为 None
latents: Optional[torch.FloatTensor] = None,
# 定义提示嵌入,默认为 None
prompt_embeds: Optional[torch.FloatTensor] = None,
# 定义负提示嵌入,默认为 None
negative_prompt_embeds: Optional[torch.FloatTensor] = None,
# 定义池化提示嵌入,默认为 None
pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
# 定义负池化提示嵌入,默认为 None
negative_pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
# 定义输出类型,默认为 "pil"
output_type: Optional[str] = "pil",
# 定义是否返回字典,默认为 True
return_dict: bool = True,
# 定义联合注意力参数,默认为 None
joint_attention_kwargs: Optional[Dict[str, Any]] = None,
# 定义跳过剪辑的数量,默认为 None
clip_skip: Optional[int] = None,
# 定义步骤结束时的回调,默认为 None
callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
# 定义步骤结束时张量输入的回调,默认为 ["latents"]
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
# 定义最大序列长度,默认为 256
max_sequence_length: int = 256,
# 定义页面缩放比例,默认为 3.0
pag_scale: float = 3.0,
# 定义自适应页面缩放比例,默认为 0.0
pag_adaptive_scale: float = 0.0,
.\diffusers\pipelines\pag\pipeline_pag_sd_animatediff.py
# 版权声明,标明版权归 HuggingFace 团队所有
#
# 根据 Apache 许可证第 2.0 版授权(“许可证”);
# 除非遵守许可证,否则您不得使用此文件。
# 您可以在以下网址获取许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有约定,软件按“原样”分发,
# 不提供任何形式的保证或条件,无论是明示还是暗示。
# 请参见许可证以获取管理权限和
# 限制的具体条款。
import inspect # 导入 inspect 模块,用于获取活跃对象的信息
from typing import Any, Callable, Dict, List, Optional, Union # 导入类型提示,用于增强代码的可读性和可维护性
import torch # 导入 PyTorch 库,用于深度学习和张量计算
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer, CLIPVisionModelWithProjection # 导入 Transformers 库中的 CLIP 相关模型和处理器
from ...image_processor import PipelineImageInput # 从自定义模块导入图像处理输入类
from ...loaders import IPAdapterMixin, StableDiffusionLoraLoaderMixin, TextualInversionLoaderMixin # 导入加载器混合类,用于处理不同模型加载
from ...models import AutoencoderKL, ImageProjection, UNet2DConditionModel, UNetMotionModel # 导入不同模型类
from ...models.lora import adjust_lora_scale_text_encoder # 导入函数,用于调整文本编码器的 LoRA 比例
from ...models.unets.unet_motion_model import MotionAdapter # 导入运动适配器类,用于处理动态模型
from ...schedulers import KarrasDiffusionSchedulers # 导入 Karras 扩散调度器类
from ...utils import ( # 导入多个工具函数和常量
USE_PEFT_BACKEND, # 用于指示是否使用 PEFT 后端的常量
logging, # 导入日志模块
replace_example_docstring, # 导入替换示例文档字符串的函数
scale_lora_layers, # 导入用于缩放 LoRA 层的函数
unscale_lora_layers, # 导入用于取消缩放 LoRA 层的函数
)
from ...utils.torch_utils import randn_tensor # 从工具模块导入生成随机张量的函数
from ...video_processor import VideoProcessor # 导入视频处理器类
from ..animatediff.pipeline_output import AnimateDiffPipelineOutput # 导入动画扩散管道输出类
from ..free_init_utils import FreeInitMixin # 导入自由初始化混合类
from ..free_noise_utils import AnimateDiffFreeNoiseMixin # 导入动画扩散自由噪声混合类
from ..pipeline_utils import DiffusionPipeline, StableDiffusionMixin # 导入扩散管道和稳定扩散混合类
from .pag_utils import PAGMixin # 导入 PAG 混合类
logger = logging.get_logger(__name__) # 创建一个记录器,用于日志记录,使用模块的名称
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 # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
```py # 示例文档字符串的结束
``` # 示例文档字符串的结束
Examples:
```py
# 导入 PyTorch 库
>>> import torch
# 从 diffusers 库导入相关的类
>>> from diffusers import AnimateDiffPAGPipeline, MotionAdapter, DDIMScheduler
# 从 diffusers.utils 导入 GIF 导出工具
>>> from diffusers.utils import export_to_gif
# 定义模型的 ID,用于加载预训练模型
>>> model_id = "SG161222/Realistic_Vision_V5.1_noVAE"
# 定义运动适配器的 ID,用于加载相应的适配器
>>> motion_adapter_id = "guoyww/animatediff-motion-adapter-v1-5-2"
# 从预训练模型加载运动适配器
>>> motion_adapter = MotionAdapter.from_pretrained(motion_adapter_id)
# 从预训练模型加载调度器,设置调度参数
>>> scheduler = DDIMScheduler.from_pretrained(
... model_id, subfolder="scheduler", beta_schedule="linear", steps_offset=1, clip_sample=False
... )
# 从预训练模型加载动画差异管道,并将其移动到 CUDA 设备
>>> pipe = AnimateDiffPAGPipeline.from_pretrained(
... model_id,
... motion_adapter=motion_adapter,
... scheduler=scheduler,
... pag_applied_layers=["mid"],
... torch_dtype=torch.float16,
... ).to("cuda")
# 生成视频,设置提示词和参数
>>> video = pipe(
... prompt="car, futuristic cityscape with neon lights, street, no human",
... negative_prompt="low quality, bad quality",
... num_inference_steps=25,
... guidance_scale=6.0,
... pag_scale=3.0,
... generator=torch.Generator().manual_seed(42),
... ).frames[0] # 获取生成的第一帧
# 导出生成的视频为 GIF 格式
>>> export_to_gif(video, "animatediff_pag.gif")
```
# 定义 AnimateDiffPAGPipeline 类,继承自多个基类以实现文本到视频的生成
class AnimateDiffPAGPipeline(
# 继承自 DiffusionPipeline 基类
DiffusionPipeline,
# 继承自 StableDiffusionMixin 以便于稳定扩散相关功能
StableDiffusionMixin,
# 继承自 TextualInversionLoaderMixin 用于加载文本反演嵌入
TextualInversionLoaderMixin,
# 继承自 IPAdapterMixin 用于加载 IP 适配器
IPAdapterMixin,
# 继承自 StableDiffusionLoraLoaderMixin 用于处理 LoRA 权重
StableDiffusionLoraLoaderMixin,
# 继承自 FreeInitMixin 用于初始化功能
FreeInitMixin,
# 继承自 AnimateDiffFreeNoiseMixin 以处理动画差异的噪声
AnimateDiffFreeNoiseMixin,
# 继承自 PAGMixin 用于应用 Perturbed Attention Guidance
PAGMixin,
):
r"""
文本到视频生成的管道,使用
[AnimateDiff](https://huggingface.co/docs/diffusers/en/api/pipelines/animatediff) 和 [Perturbed Attention
Guidance](https://huggingface.co/docs/diffusers/en/using-diffusers/pag)。
该模型继承自 [`DiffusionPipeline`]。请查阅超类文档,了解所有管道实现的通用方法
(下载、保存、在特定设备上运行等)。
该管道还继承了以下加载方法:
- [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] 用于加载文本反演嵌入
- [`~loaders.StableDiffusionLoraLoaderMixin.load_lora_weights`] 用于加载 LoRA 权重
- [`~loaders.StableDiffusionLoraLoaderMixin.save_lora_weights`] 用于保存 LoRA 权重
- [`~loaders.IPAdapterMixin.load_ip_adapter`] 用于加载 IP 适配器
参数:
vae ([`AutoencoderKL`]):
变分自编码器(VAE)模型,用于编码和解码图像的潜在表示。
text_encoder ([`CLIPTextModel`]):
冻结的文本编码器([clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14))。
tokenizer (`CLIPTokenizer`):
[`~transformers.CLIPTokenizer`] 用于文本标记化。
unet ([`UNet2DConditionModel`]):
[`UNet2DConditionModel`] 用于创建 UNetMotionModel 来去噪编码的视频潜在。
motion_adapter ([`MotionAdapter`]):
[`MotionAdapter`],与 `unet` 结合使用以去噪编码的视频潜在。
scheduler ([`SchedulerMixin`]):
与 `unet` 结合使用以去噪编码的图像潜在的调度器,可以是
[`DDIMScheduler`], [`LMSDiscreteScheduler`] 或 [`PNDMScheduler`]。
"""
# 定义模型在 CPU 上的卸载顺序
model_cpu_offload_seq = "text_encoder->image_encoder->unet->vae"
# 定义可选组件列表
_optional_components = ["feature_extractor", "image_encoder", "motion_adapter"]
# 定义需要回调的张量输入
_callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"]
# 初始化方法,定义构造函数参数
def __init__(
# VAE 模型,用于图像的编码和解码
vae: AutoencoderKL,
# 文本编码器,用于处理输入文本
text_encoder: CLIPTextModel,
# 文本标记化器
tokenizer: CLIPTokenizer,
# UNet 模型,处理条件模型或运动模型
unet: Union[UNet2DConditionModel, UNetMotionModel],
# 动作适配器,用于去噪处理
motion_adapter: MotionAdapter,
# 调度器,用于模型的调度控制
scheduler: KarrasDiffusionSchedulers,
# 可选的特征提取器,默认为 None
feature_extractor: CLIPImageProcessor = None,
# 可选的图像编码器,默认为 None
image_encoder: CLIPVisionModelWithProjection = None,
# 应用的 PAG 层,可以是字符串或字符串列表
pag_applied_layers: Union[str, List[str]] = "mid_block.*attn1", # ["mid"], ["down_blocks.1"]
):
# 调用父类的初始化方法
super().__init__()
# 检查给定的 unet 是否为 UNet2DConditionModel 类型
if isinstance(unet, UNet2DConditionModel):
# 从 UNet2DConditionModel 创建 UNetMotionModel 实例,并传入 motion_adapter
unet = UNetMotionModel.from_unet2d(unet, motion_adapter)
# 注册多个模块,便于后续使用
self.register_modules(
# 注册变换自编码器模块
vae=vae,
# 注册文本编码器模块
text_encoder=text_encoder,
# 注册分词器模块
tokenizer=tokenizer,
# 注册 UNet 模块
unet=unet,
# 注册运动适配器模块
motion_adapter=motion_adapter,
# 注册调度器模块
scheduler=scheduler,
# 注册特征提取器模块
feature_extractor=feature_extractor,
# 注册图像编码器模块
image_encoder=image_encoder,
)
# 计算 VAE 的缩放因子,基于 VAE 配置中的块输出通道数量
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
# 初始化视频处理器,不进行缩放,并设置 VAE 缩放因子
self.video_processor = VideoProcessor(do_resize=False, vae_scale_factor=self.vae_scale_factor)
# 设置应用于 PAG 的层
self.set_pag_applied_layers(pag_applied_layers)
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.encode_prompt 复制而来,num_images_per_prompt -> num_videos_per_prompt
def encode_prompt(
self,
# 输入的提示文本
prompt,
# 指定设备(CPU 或 GPU)
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.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,
# 指定设备(CPU 或 GPU)
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]
# 检查输入适配器图像的数量是否与 IP 适配器的数量一致
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
):
# 确定输出隐藏状态的标志
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
# 从 diffusers.pipelines.animatediff.pipeline_animatediff.AnimateDiffPipeline.decode_latents 复制的代码
# 解码潜在表示,返回解码后的视频张量
def decode_latents(self, latents, decode_chunk_size: int = 16):
# 根据配置的缩放因子对潜在表示进行缩放
latents = 1 / self.vae.config.scaling_factor * latents
# 获取潜在表示的批次大小、通道数、帧数、高度和宽度
batch_size, channels, num_frames, height, width = latents.shape
# 重新排列和调整潜在表示的形状,方便后续处理
latents = latents.permute(0, 2, 1, 3, 4).reshape(batch_size * num_frames, channels, height, width)
video = [] # 初始化视频列表以存储解码结果
# 按照解码块大小遍历潜在表示
for i in range(0, latents.shape[0], decode_chunk_size):
# 获取当前块的潜在表示
batch_latents = latents[i : i + decode_chunk_size]
# 解码当前块的潜在表示,并提取样本
batch_latents = self.vae.decode(batch_latents).sample
# 将解码后的块添加到视频列表
video.append(batch_latents)
# 将所有解码块连接成一个张量
video = torch.cat(video)
# 重新调整视频张量的形状,便于后续处理
video = video[None, :].reshape((batch_size, num_frames, -1) + video.shape[2:]).permute(0, 2, 1, 3, 4)
# 将视频张量转换为 float32 类型,以确保兼容性
video = video.float()
# 返回解码后的视频张量
return video
# 从 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 = {} # 初始化额外参数字典
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
# 从 diffusers.pipelines.pia.pipeline_pia.PIAPipeline.check_inputs 复制
def check_inputs(
self,
prompt,
height,
width,
negative_prompt=None,
prompt_embeds=None,
negative_prompt_embeds=None,
ip_adapter_image=None,
ip_adapter_image_embeds=None,
callback_on_step_end_tensor_inputs=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_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."
)
# 检查是否两个参数都未提供
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}."
)
# 检查是否同时提供了 ip_adapter_image 和 ip_adapter_image_embeds
if ip_adapter_image is not None and ip_adapter_image_embeds is not None:
# 抛出异常,提示只能提供其中一个
raise ValueError(
"Provide either `ip_adapter_image` or `ip_adapter_image_embeds`. Cannot leave both `ip_adapter_image` and `ip_adapter_image_embeds` defined."
)
# 检查 ip_adapter_image_embeds 是否存在
if ip_adapter_image_embeds is not None:
# 检查其类型是否为列表
if not isinstance(ip_adapter_image_embeds, list):
# 抛出异常,显示实际类型
raise ValueError(
f"`ip_adapter_image_embeds` has to be of type `list` but is {type(ip_adapter_image_embeds)}"
)
# 检查列表中第一个元素的维度是否为 3D 或 4D
elif ip_adapter_image_embeds[0].ndim not in [3, 4]:
# 抛出异常,显示实际维度
raise ValueError(
f"`ip_adapter_image_embeds` has to be a list of 3D or 4D tensors but is {ip_adapter_image_embeds[0].ndim}D"
)
# 从 diffusers.pipelines.animatediff.pipeline_animatediff.AnimateDiffPipeline.prepare_latents 复制的代码
# 准备潜在变量的方法,接收多个参数以配置潜在变量的生成
def prepare_latents(
self, batch_size, num_channels_latents, num_frames, height, width, dtype, device, generator, latents=None
):
# 如果启用了 FreeNoise,按照 [FreeNoise](https://arxiv.org/abs/2310.15169) 的公式 (7) 生成潜在变量
if self.free_noise_enabled:
# 调用 _prepare_latents_free_noise 方法生成潜在变量
latents = self._prepare_latents_free_noise(
batch_size, num_channels_latents, num_frames, height, width, dtype, device, generator, latents
)
# 检查生成器的类型和数量是否与请求的批量大小匹配
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."
)
# 定义潜在变量的形状,依据批量大小和其他参数计算
shape = (
batch_size,
num_channels_latents,
num_frames,
height // self.vae_scale_factor,
width // self.vae_scale_factor,
)
# 如果未提供潜在变量,则生成随机的潜在变量
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
# 定义一个属性,返回剪切跳过的值
@property
def clip_skip(self):
return self._clip_skip
# 定义一个属性,判断是否执行无分类器引导
# `guidance_scale` 类似于 Imagen 论文中公式 (2) 的引导权重 `w`
# `guidance_scale = 1` 表示不进行分类器自由引导
@property
def do_classifier_free_guidance(self):
return self._guidance_scale > 1
# 定义一个属性,返回交叉注意力的关键字参数
@property
def cross_attention_kwargs(self):
return self._cross_attention_kwargs
# 定义一个属性,返回时间步数的数量
@property
def num_timesteps(self):
return self._num_timesteps
# 禁用梯度计算,以减少内存消耗和提高计算效率
@torch.no_grad()
# 替换示例文档字符串为预定义的文档字符串
@replace_example_docstring(EXAMPLE_DOC_STRING)
# 定义可调用对象的特殊方法,用于执行某些操作
def __call__(
# 接收的提示信息,可以是字符串或字符串列表,默认为 None
self,
prompt: Optional[Union[str, List[str]]] = None,
# 要生成的帧数,默认为 16
num_frames: Optional[int] = 16,
# 输出图像的高度,默认为 None
height: Optional[int] = None,
# 输出图像的宽度,默认为 None
width: Optional[int] = None,
# 推理步骤的数量,默认为 50
num_inference_steps: int = 50,
# 指导比例,默认为 7.5,影响生成图像的质量
guidance_scale: float = 7.5,
# 负向提示信息,可以是字符串或字符串列表,默认为 None
negative_prompt: Optional[Union[str, List[str]]] = None,
# 每个提示生成的视频数量,默认为 1
num_videos_per_prompt: Optional[int] = 1,
# 控制噪声的强度,默认为 0.0
eta: float = 0.0,
# 随机数生成器,可以是单个或多个 torch.Generator,默认为 None
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 生成的潜在变量,可以是 torch.Tensor,默认为 None
latents: Optional[torch.Tensor] = None,
# 提示的嵌入表示,可以是 torch.Tensor,默认为 None
prompt_embeds: Optional[torch.Tensor] = None,
# 负向提示的嵌入表示,可以是 torch.Tensor,默认为 None
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 图像输入的适配器图像,默认为 None
ip_adapter_image: Optional[PipelineImageInput] = None,
# 适配器图像的嵌入表示,可以是 torch.Tensor 列表,默认为 None
ip_adapter_image_embeds: Optional[List[torch.Tensor]] = None,
# 输出类型,默认为 "pil",表示返回 PIL 图像
output_type: Optional[str] = "pil",
# 是否返回字典格式,默认为 True
return_dict: bool = True,
# 交叉注意力的额外参数,默认为 None
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 跳过的 CLIP 层数,默认为 None
clip_skip: Optional[int] = None,
# 每步结束时的回调函数,默认为 None
callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
# 每步结束时回调时传入的张量输入的名称,默认为 ["latents"]
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
# 解码时的块大小,默认为 16
decode_chunk_size: int = 16,
# PAG 的缩放比例,默认为 3.0
pag_scale: float = 3.0,
# PAG 的自适应缩放,默认为 0.0
pag_adaptive_scale: float = 0.0,