diffusers 源码解析(十七)
.\diffusers\models\vq_model.py
# 版权声明,表明版权归 HuggingFace 团队所有
# Copyright 2024 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.
# 从 utils 模块导入 deprecate 函数
from ..utils import deprecate
# 从 autoencoders.vq_model 模块导入 VQEncoderOutput 和 VQModel 类
from .autoencoders.vq_model import VQEncoderOutput, VQModel
# 定义 VQEncoderOutput 类,继承自 VQEncoderOutput
class VQEncoderOutput(VQEncoderOutput):
# 初始化方法,接收可变参数
def __init__(self, *args, **kwargs):
# 定义过时警告信息,提示用户导入路径已过时
deprecation_message = "Importing `VQEncoderOutput` from `diffusers.models.vq_model` is deprecated and this will be removed in a future version. Please use `from diffusers.models.autoencoders.vq_model import VQEncoderOutput`, instead."
# 调用 deprecate 函数,记录过时警告
deprecate("VQEncoderOutput", "0.31", deprecation_message)
# 调用父类的初始化方法
super().__init__(*args, **kwargs)
# 定义 VQModel 类,继承自 VQModel
class VQModel(VQModel):
# 初始化方法,接收可变参数
def __init__(self, *args, **kwargs):
# 定义过时警告信息,提示用户导入路径已过时
deprecation_message = "Importing `VQModel` from `diffusers.models.vq_model` is deprecated and this will be removed in a future version. Please use `from diffusers.models.autoencoders.vq_model import VQModel`, instead."
# 调用 deprecate 函数,记录过时警告
deprecate("VQModel", "0.31", deprecation_message)
# 调用父类的初始化方法
super().__init__(*args, **kwargs)
.\diffusers\models\__init__.py
# 版权声明,表明该代码属于 HuggingFace 团队,保留所有权利。
# 根据 Apache 2.0 许可证进行许可,使用该文件需遵循许可证的条款。
# 提供许可证的获取链接。
#
# 许可证条款的摘要,软件在“现状”基础上分发,没有任何明示或暗示的保证。
# 参考许可证以获取关于权限和限制的具体信息。
# 导入类型检查模块
from typing import TYPE_CHECKING
# 从上层目录的 utils 模块导入所需的工具和常量
from ..utils import (
DIFFUSERS_SLOW_IMPORT, # 慢导入的常量
_LazyModule, # 延迟加载模块的工具
is_flax_available, # 检查 Flax 库是否可用的函数
is_torch_available, # 检查 PyTorch 库是否可用的函数
)
# 初始化一个空的字典,用于存储导入结构
_import_structure = {}
# 如果 PyTorch 可用,添加相关模块到导入结构字典
if is_torch_available():
_import_structure["adapter"] = ["MultiAdapter", "T2IAdapter"] # 适配器模块
_import_structure["autoencoders.autoencoder_asym_kl"] = ["AsymmetricAutoencoderKL"] # 非对称自动编码器
_import_structure["autoencoders.autoencoder_kl"] = ["AutoencoderKL"] # 自动编码器
_import_structure["autoencoders.autoencoder_kl_cogvideox"] = ["AutoencoderKLCogVideoX"] # CogVideoX 自动编码器
_import_structure["autoencoders.autoencoder_kl_temporal_decoder"] = ["AutoencoderKLTemporalDecoder"] # 时间解码器
_import_structure["autoencoders.autoencoder_oobleck"] = ["AutoencoderOobleck"] # Oobleck 自动编码器
_import_structure["autoencoders.autoencoder_tiny"] = ["AutoencoderTiny"] # Tiny 自动编码器
_import_structure["autoencoders.consistency_decoder_vae"] = ["ConsistencyDecoderVAE"] # 一致性解码器 VAE
_import_structure["autoencoders.vq_model"] = ["VQModel"] # VQ 模型
_import_structure["controlnet"] = ["ControlNetModel"] # 控制网络模型
_import_structure["controlnet_hunyuan"] = ["HunyuanDiT2DControlNetModel", "HunyuanDiT2DMultiControlNetModel"] # Hunyuan 控制网络模型
_import_structure["controlnet_sd3"] = ["SD3ControlNetModel", "SD3MultiControlNetModel"] # SD3 控制网络模型
_import_structure["controlnet_sparsectrl"] = ["SparseControlNetModel"] # 稀疏控制网络模型
_import_structure["controlnet_xs"] = ["ControlNetXSAdapter", "UNetControlNetXSModel"] # XS 适配器和 U-Net 模型
_import_structure["embeddings"] = ["ImageProjection"] # 图像投影模块
_import_structure["modeling_utils"] = ["ModelMixin"] # 模型混合工具
_import_structure["transformers.auraflow_transformer_2d"] = ["AuraFlowTransformer2DModel"] # AuraFlow 2D 模型
_import_structure["transformers.cogvideox_transformer_3d"] = ["CogVideoXTransformer3DModel"] # CogVideoX 3D 模型
_import_structure["transformers.dit_transformer_2d"] = ["DiTTransformer2DModel"] # DiT 2D 模型
_import_structure["transformers.dual_transformer_2d"] = ["DualTransformer2DModel"] # 双重 2D 模型
_import_structure["transformers.hunyuan_transformer_2d"] = ["HunyuanDiT2DModel"] # Hunyuan 2D 模型
_import_structure["transformers.latte_transformer_3d"] = ["LatteTransformer3DModel"] # Latte 3D 模型
_import_structure["transformers.lumina_nextdit2d"] = ["LuminaNextDiT2DModel"] # Lumina Next DiT 2D 模型
_import_structure["transformers.pixart_transformer_2d"] = ["PixArtTransformer2DModel"] # PixArt 2D 模型
_import_structure["transformers.prior_transformer"] = ["PriorTransformer"] # 优先变换模型
_import_structure["transformers.stable_audio_transformer"] = ["StableAudioDiTModel"] # 稳定音频模型
# 将 T5FilmDecoder 类添加到 transformers.t5_film_transformer 的导入结构中
_import_structure["transformers.t5_film_transformer"] = ["T5FilmDecoder"]
# 将 Transformer2DModel 类添加到 transformers.transformer_2d 的导入结构中
_import_structure["transformers.transformer_2d"] = ["Transformer2DModel"]
# 将 FluxTransformer2DModel 类添加到 transformers.transformer_flux 的导入结构中
_import_structure["transformers.transformer_flux"] = ["FluxTransformer2DModel"]
# 将 SD3Transformer2DModel 类添加到 transformers.transformer_sd3 的导入结构中
_import_structure["transformers.transformer_sd3"] = ["SD3Transformer2DModel"]
# 将 TransformerTemporalModel 类添加到 transformers.transformer_temporal 的导入结构中
_import_structure["transformers.transformer_temporal"] = ["TransformerTemporalModel"]
# 将 UNet1DModel 类添加到 unets.unet_1d 的导入结构中
_import_structure["unets.unet_1d"] = ["UNet1DModel"]
# 将 UNet2DModel 类添加到 unets.unet_2d 的导入结构中
_import_structure["unets.unet_2d"] = ["UNet2DModel"]
# 将 UNet2DConditionModel 类添加到 unets.unet_2d_condition 的导入结构中
_import_structure["unets.unet_2d_condition"] = ["UNet2DConditionModel"]
# 将 UNet3DConditionModel 类添加到 unets.unet_3d_condition 的导入结构中
_import_structure["unets.unet_3d_condition"] = ["UNet3DConditionModel"]
# 将 I2VGenXLUNet 类添加到 unets.unet_i2vgen_xl 的导入结构中
_import_structure["unets.unet_i2vgen_xl"] = ["I2VGenXLUNet"]
# 将 Kandinsky3UNet 类添加到 unets.unet_kandinsky3 的导入结构中
_import_structure["unets.unet_kandinsky3"] = ["Kandinsky3UNet"]
# 将 MotionAdapter 和 UNetMotionModel 类添加到 unets.unet_motion_model 的导入结构中
_import_structure["unets.unet_motion_model"] = ["MotionAdapter", "UNetMotionModel"]
# 将 UNetSpatioTemporalConditionModel 类添加到 unets.unet_spatio_temporal_condition 的导入结构中
_import_structure["unets.unet_spatio_temporal_condition"] = ["UNetSpatioTemporalConditionModel"]
# 将 StableCascadeUNet 类添加到 unets.unet_stable_cascade 的导入结构中
_import_structure["unets.unet_stable_cascade"] = ["StableCascadeUNet"]
# 将 UVit2DModel 类添加到 unets.uvit_2d 的导入结构中
_import_structure["unets.uvit_2d"] = ["UVit2DModel"]
# 检查 Flax 库是否可用
if is_flax_available():
# 在导入结构中添加 ControlNet 的 Flax 模型
_import_structure["controlnet_flax"] = ["FlaxControlNetModel"]
# 在导入结构中添加 2D 条件 UNet 的 Flax 模型
_import_structure["unets.unet_2d_condition_flax"] = ["FlaxUNet2DConditionModel"]
# 在导入结构中添加 VAE 的 Flax 模型
_import_structure["vae_flax"] = ["FlaxAutoencoderKL"]
# 检查类型检查或慢导入条件
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
# 检查 PyTorch 库是否可用
if is_torch_available():
# 从适配器模块导入多个适配器类
from .adapter import MultiAdapter, T2IAdapter
# 从自动编码器模块导入多个自动编码器类
from .autoencoders import (
AsymmetricAutoencoderKL,
AutoencoderKL,
AutoencoderKLCogVideoX,
AutoencoderKLTemporalDecoder,
AutoencoderOobleck,
AutoencoderTiny,
ConsistencyDecoderVAE,
VQModel,
)
# 从 ControlNet 模块导入 ControlNet 模型
from .controlnet import ControlNetModel
# 从 Hunyuan ControlNet 模块导入模型
from .controlnet_hunyuan import HunyuanDiT2DControlNetModel, HunyuanDiT2DMultiControlNetModel
# 从 SD3 ControlNet 模块导入模型
from .controlnet_sd3 import SD3ControlNetModel, SD3MultiControlNetModel
# 从 SparseControlNet 模块导入模型
from .controlnet_sparsectrl import SparseControlNetModel
# 从 XS ControlNet 模块导入适配器和模型
from .controlnet_xs import ControlNetXSAdapter, UNetControlNetXSModel
# 从嵌入模块导入图像投影类
from .embeddings import ImageProjection
# 从建模工具模块导入模型混合类
from .modeling_utils import ModelMixin
# 从转换器模块导入多个转换器类
from .transformers import (
AuraFlowTransformer2DModel,
CogVideoXTransformer3DModel,
DiTTransformer2DModel,
DualTransformer2DModel,
FluxTransformer2DModel,
HunyuanDiT2DModel,
LatteTransformer3DModel,
LuminaNextDiT2DModel,
PixArtTransformer2DModel,
PriorTransformer,
SD3Transformer2DModel,
StableAudioDiTModel,
T5FilmDecoder,
Transformer2DModel,
TransformerTemporalModel,
)
# 从 UNet 模块导入多个 UNet 类
from .unets import (
I2VGenXLUNet,
Kandinsky3UNet,
MotionAdapter,
StableCascadeUNet,
UNet1DModel,
UNet2DConditionModel,
UNet2DModel,
UNet3DConditionModel,
UNetMotionModel,
UNetSpatioTemporalConditionModel,
UVit2DModel,
)
# 检查 Flax 库是否可用
if is_flax_available():
# 从 Flax ControlNet 模块导入模型
from .controlnet_flax import FlaxControlNetModel
# 从 UNet 模块导入 Flax 2D 条件模型
from .unets import FlaxUNet2DConditionModel
# 从 Flax VAE 模块导入模型
from .vae_flax import FlaxAutoencoderKL
# 如果以上条件都不满足
else:
# 导入系统模块
import sys
# 用懒加载模块替代当前模块
sys.modules[__name__] = _LazyModule(__name__, globals()["__file__"], _import_structure, module_spec=__spec__)
.\diffusers\optimization.py
# coding=utf-8 # 指定源文件的编码为 UTF-8
# Copyright 2024 The HuggingFace Inc. team. # 版权声明,标明版权归 HuggingFace Inc. 所有
#
# Licensed under the Apache License, Version 2.0 (the "License"); # 指明该文件遵循 Apache 2.0 许可证
# 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. # 以及使用限制
"""PyTorch optimization for diffusion models.""" # 模块说明:用于扩散模型的 PyTorch 优化
import math # 导入数学模块
from enum import Enum # 从 enum 模块导入 Enum 类
from typing import Optional, Union # 导入类型提示:Optional 和 Union
from torch.optim import Optimizer # 从 torch.optim 导入 Optimizer 类
from torch.optim.lr_scheduler import LambdaLR # 从 torch.optim.lr_scheduler 导入 LambdaLR 类
from .utils import logging # 从当前包的 utils 模块导入 logging
logger = logging.get_logger(__name__) # 创建一个记录器,用于记录当前模块的日志
class SchedulerType(Enum): # 定义调度器类型的枚举类
LINEAR = "linear" # 线性调度类型
COSINE = "cosine" # 余弦调度类型
COSINE_WITH_RESTARTS = "cosine_with_restarts" # 余弦调度类型(带重启)
POLYNOMIAL = "polynomial" # 多项式调度类型
CONSTANT = "constant" # 常量调度类型
CONSTANT_WITH_WARMUP = "constant_with_warmup" # 带热身的常量调度类型
PIECEWISE_CONSTANT = "piecewise_constant" # 分段常量调度类型
def get_constant_schedule(optimizer: Optimizer, last_epoch: int = -1) -> LambdaLR: # 定义获取常量学习率调度的函数
"""
Create a schedule with a constant learning rate, using the learning rate set in optimizer. # 创建一个常量学习率调度,使用优化器中设置的学习率
Args: # 参数说明
optimizer ([`~torch.optim.Optimizer`]): # 优化器参数类型
The optimizer for which to schedule the learning rate. # 用于调度学习率的优化器
last_epoch (`int`, *optional*, defaults to -1): # 最后一个 epoch 的索引,默认值为 -1
The index of the last epoch when resuming training. # 继续训练时的最后一个 epoch 索引
Return: # 返回值说明
`torch.optim.lr_scheduler.LambdaLR` with the appropriate schedule. # 返回相应的 LambdaLR 调度
"""
return LambdaLR(optimizer, lambda _: 1, last_epoch=last_epoch) # 返回一个常量学习率调度器,学习率始终为 1
def get_constant_schedule_with_warmup(optimizer: Optimizer, num_warmup_steps: int, last_epoch: int = -1) -> LambdaLR: # 定义获取带热身的常量学习率调度的函数
"""
Create a schedule with a constant learning rate preceded by a warmup period during which the learning rate # 创建一个常量学习率调度,前面有热身期,在此期间学习率
increases linearly between 0 and the initial lr set in the optimizer. # 从 0 线性增加到优化器中设置的初始学习率
Args: # 参数说明
optimizer ([`~torch.optim.Optimizer`]): # 优化器参数类型
The optimizer for which to schedule the learning rate. # 用于调度学习率的优化器
num_warmup_steps (`int`): # 热身步骤的数量
The number of steps for the warmup phase. # 热身阶段的步骤数
last_epoch (`int`, *optional*, defaults to -1): # 最后一个 epoch 的索引,默认值为 -1
The index of the last epoch when resuming training. # 继续训练时的最后一个 epoch 索引
Return: # 返回值说明
`torch.optim.lr_scheduler.LambdaLR` with the appropriate schedule. # 返回相应的 LambdaLR 调度
"""
def lr_lambda(current_step: int): # 定义学习率调度的 lambda 函数
if current_step < num_warmup_steps: # 如果当前步骤小于热身步骤数
return float(current_step) / float(max(1.0, num_warmup_steps)) # 返回当前步骤与热身步骤数的比值
return 1.0 # 否则返回 1.0
return LambdaLR(optimizer, lr_lambda, last_epoch=last_epoch) # 返回带热身的常量学习率调度器
def get_piecewise_constant_schedule(optimizer: Optimizer, step_rules: str, last_epoch: int = -1) -> LambdaLR: # 定义获取分段常量学习率调度的函数
"""
Create a schedule with a constant learning rate, using the learning rate set in optimizer. # 创建一个常量学习率调度,使用优化器中设置的学习率
# 参数说明
Args:
optimizer ([`~torch.optim.Optimizer`]):
# 用于调度学习率的优化器
The optimizer for which to schedule the learning rate.
step_rules (`string`):
# 学习率调整的规则,例如:rule_steps="1:10,0.1:20,0.01:30,0.005",意味着学习率
# 在前10步乘以1,接下来20步乘以0.1,再接下来的30步乘以0.01,最后的步骤乘以0.005。
The rules for the learning rate. ex: rule_steps="1:10,0.1:20,0.01:30,0.005" it means that the learning rate
if multiple 1 for the first 10 steps, multiple 0.1 for the next 20 steps, multiple 0.01 for the next 30
steps and multiple 0.005 for the other steps.
last_epoch (`int`, *optional*, defaults to -1):
# 用于恢复训练时的最后一个epoch索引
The index of the last epoch when resuming training.
Return:
# 返回一个适当调度的 `torch.optim.lr_scheduler.LambdaLR`
`torch.optim.lr_scheduler.LambdaLR` with the appropriate schedule.
"""
# 创建一个空字典以存储学习率规则
rules_dict = {}
# 根据逗号分隔符将规则字符串分割成列表
rule_list = step_rules.split(",")
# 遍历除最后一个规则以外的所有规则字符串
for rule_str in rule_list[:-1]:
# 根据冒号分隔符将规则字符串分割成值和步数
value_str, steps_str = rule_str.split(":")
# 将步数转换为整数
steps = int(steps_str)
# 将值转换为浮点数
value = float(value_str)
# 将步数和对应值存入字典
rules_dict[steps] = value
# 获取最后一个学习率乘数
last_lr_multiple = float(rule_list[-1])
# 创建一个生成学习率的函数
def create_rules_function(rules_dict, last_lr_multiple):
# 定义学习率规则函数
def rule_func(steps: int) -> float:
# 获取已排序的步数
sorted_steps = sorted(rules_dict.keys())
# 遍历已排序的步数
for i, sorted_step in enumerate(sorted_steps):
# 如果当前步数小于当前规则的步数,则返回对应的学习率值
if steps < sorted_step:
return rules_dict[sorted_steps[i]]
# 如果步数超出所有规则,返回最后一个学习率乘数
return last_lr_multiple
# 返回生成的规则函数
return rule_func
# 调用函数生成学习率规则函数
rules_func = create_rules_function(rules_dict, last_lr_multiple)
# 返回配置了调度规则的LambdaLR对象
return LambdaLR(optimizer, rules_func, last_epoch=last_epoch)
# 创建一个带有预热阶段的学习率调度器,该调度器的学习率会线性减少到0
def get_linear_schedule_with_warmup(
# 优化器对象,用于设置学习率
optimizer: Optimizer,
# 预热阶段的步数
num_warmup_steps: int,
# 总的训练步数
num_training_steps: int,
# 最近一次训练的轮次,默认为-1
last_epoch: int = -1
) -> LambdaLR:
"""
创建一个学习率调度器,学习率在预热期内线性增加至初始学习率,然后线性减少至0。
Args:
optimizer ([`~torch.optim.Optimizer`]):
要为其调度学习率的优化器。
num_warmup_steps (`int`):
预热阶段的步数。
num_training_steps (`int`):
训练的总步数。
last_epoch (`int`, *可选*, 默认值为 -1):
恢复训练时最后一轮的索引。
Return:
`torch.optim.lr_scheduler.LambdaLR`,具有适当的调度。
"""
# 定义学习率的计算函数
def lr_lambda(current_step: int):
# 如果当前步数小于预热步数
if current_step < num_warmup_steps:
# 返回当前步数与预热步数的比值
return float(current_step) / float(max(1, num_warmup_steps))
# 返回剩余步数与总步数的比值,确保不小于0
return max(
0.0, float(num_training_steps - current_step) / float(max(1, num_training_steps - num_warmup_steps))
)
# 返回一个基于优化器和学习率计算函数的LambdaLR调度器
return LambdaLR(optimizer, lr_lambda, last_epoch)
# 创建一个带有预热阶段的余弦学习率调度器
def get_cosine_schedule_with_warmup(
# 优化器对象,用于设置学习率
optimizer: Optimizer,
# 预热阶段的步数
num_warmup_steps: int,
# 总的训练步数
num_training_steps: int,
# 余弦函数的周期数,默认为0.5
num_cycles: float = 0.5,
# 最近一次训练的轮次,默认为-1
last_epoch: int = -1
) -> LambdaLR:
"""
创建一个学习率调度器,学习率在预热阶段线性增加至初始学习率,然后根据余弦函数递减至0。
Args:
optimizer ([`~torch.optim.Optimizer`]):
要为其调度学习率的优化器。
num_warmup_steps (`int`):
预热阶段的步数。
num_training_steps (`int`):
训练的总步数。
num_cycles (`float`, *可选*, 默认值为0.5):
调度中余弦函数的周期数(默认情况下仅从最大值减少到0,遵循半余弦)。
last_epoch (`int`, *可选*, 默认值为-1):
恢复训练时最后一轮的索引。
Return:
`torch.optim.lr_scheduler.LambdaLR`,具有适当的调度。
"""
# 定义学习率的计算函数
def lr_lambda(current_step):
# 如果当前步数小于预热步数
if current_step < num_warmup_steps:
# 返回当前步数与预热步数的比值
return float(current_step) / float(max(1, num_warmup_steps))
# 计算经过预热阶段后的进度
progress = float(current_step - num_warmup_steps) / float(max(1, num_training_steps - num_warmup_steps))
# 根据余弦函数计算学习率,确保不小于0
return max(0.0, 0.5 * (1.0 + math.cos(math.pi * float(num_cycles) * 2.0 * progress)))
# 返回一个基于优化器和学习率计算函数的LambdaLR调度器
return LambdaLR(optimizer, lr_lambda, last_epoch)
# 创建一个带有预热阶段和硬重启的余弦学习率调度器
def get_cosine_with_hard_restarts_schedule_with_warmup(
# 定义一个函数参数,包括优化器、预热步数、训练步数、周期数和最后一个周期的索引
optimizer: Optimizer, # 优化器对象,负责更新模型参数
num_warmup_steps: int, # 预热步数,指定在训练初期的步数
num_training_steps: int, # 总训练步数,指定整个训练过程中的步数
num_cycles: int = 1, # 训练周期数,默认为1周期
last_epoch: int = -1 # 最后一个训练周期的索引,默认为-1表示没有上一个周期
# 返回一个 LambdaLR 的学习率调度器
) -> LambdaLR:
"""
创建一个学习率调度器,学习率在初始值与 0 之间按余弦函数递减,并有多个硬重启,
在热身阶段,学习率从 0 线性增加到优化器设定的初始学习率。
参数:
optimizer ([`~torch.optim.Optimizer`]):
需要调度学习率的优化器。
num_warmup_steps (`int`):
热身阶段的步数。
num_training_steps (`int`):
总的训练步数。
num_cycles (`int`, *可选*, 默认值为 1):
使用的硬重启次数。
last_epoch (`int`, *可选*, 默认值为 -1):
恢复训练时的最后一个周期索引。
返回:
`torch.optim.lr_scheduler.LambdaLR`,具有适当的调度。
"""
# 定义一个学习率更新函数
def lr_lambda(current_step):
# 如果当前步数小于热身步数,则线性增加学习率
if current_step < num_warmup_steps:
return float(current_step) / float(max(1, num_warmup_steps))
# 计算热身后进度
progress = float(current_step - num_warmup_steps) / float(max(1, num_training_steps - num_warmup_steps))
# 如果进度达到或超过 1.0,则学习率为 0
if progress >= 1.0:
return 0.0
# 按余弦函数计算学习率
return max(0.0, 0.5 * (1.0 + math.cos(math.pi * ((float(num_cycles) * progress) % 1.0))))
# 返回基于定义的 lr_lambda 函数的 LambdaLR 调度器
return LambdaLR(optimizer, lr_lambda, last_epoch)
# 返回一个带有多项式衰减和热身的学习率调度器
def get_polynomial_decay_schedule_with_warmup(
optimizer: Optimizer,
num_warmup_steps: int,
num_training_steps: int,
lr_end: float = 1e-7,
power: float = 1.0,
last_epoch: int = -1,
) -> LambdaLR:
"""
创建一个学习率调度器,学习率从优化器设定的初始值按多项式衰减到由 *lr_end* 定义的结束学习率,
在热身阶段,学习率从 0 线性增加到优化器设定的初始学习率。
参数:
optimizer ([`~torch.optim.Optimizer`]):
需要调度学习率的优化器。
num_warmup_steps (`int`):
热身阶段的步数。
num_training_steps (`int`):
总的训练步数。
lr_end (`float`, *可选*, 默认值为 1e-7):
结束学习率。
power (`float`, *可选*, 默认值为 1.0):
幂因子。
last_epoch (`int`, *可选*, 默认值为 -1):
恢复训练时的最后一个周期索引。
注意:*power* 默认值为 1.0,基于 fairseq 实现,进而基于原始 BERT 实现。
返回:
`torch.optim.lr_scheduler.LambdaLR`,具有适当的调度。
"""
# 获取优化器的初始学习率
lr_init = optimizer.defaults["lr"]
# 检查初始学习率是否大于结束学习率
if not (lr_init > lr_end):
raise ValueError(f"lr_end ({lr_end}) must be be smaller than initial lr ({lr_init})")
# 定义一个学习率调度函数,根据当前训练步骤调整学习率
def lr_lambda(current_step: int):
# 如果当前步骤在预热步骤内,返回预热比例
if current_step < num_warmup_steps:
return float(current_step) / float(max(1, num_warmup_steps))
# 如果当前步骤超过训练总步骤,返回结束学习率与初始学习率的比值
elif current_step > num_training_steps:
return lr_end / lr_init # 因为 LambdaLR 会将结果乘以 lr_init
else:
# 计算初始学习率与结束学习率的差值
lr_range = lr_init - lr_end
# 计算衰减步骤
decay_steps = num_training_steps - num_warmup_steps
# 计算剩余的训练比例
pct_remaining = 1 - (current_step - num_warmup_steps) / decay_steps
# 计算衰减后的学习率
decay = lr_range * pct_remaining**power + lr_end
# 返回衰减后的学习率与初始学习率的比值
return decay / lr_init # 因为 LambdaLR 会将结果乘以 lr_init
# 返回使用定义的学习率调度函数的 LambdaLR 对象
return LambdaLR(optimizer, lr_lambda, last_epoch)
# 定义调度器类型与对应的调度函数的映射关系
TYPE_TO_SCHEDULER_FUNCTION = {
# 线性调度器
SchedulerType.LINEAR: get_linear_schedule_with_warmup,
# 余弦调度器
SchedulerType.COSINE: get_cosine_schedule_with_warmup,
# 带硬重启的余弦调度器
SchedulerType.COSINE_WITH_RESTARTS: get_cosine_with_hard_restarts_schedule_with_warmup,
# 多项式衰减调度器
SchedulerType.POLYNOMIAL: get_polynomial_decay_schedule_with_warmup,
# 常量调度器
SchedulerType.CONSTANT: get_constant_schedule,
# 带热身的常量调度器
SchedulerType.CONSTANT_WITH_WARMUP: get_constant_schedule_with_warmup,
# 分段常量调度器
SchedulerType.PIECEWISE_CONSTANT: get_piecewise_constant_schedule,
}
# 获取调度器的统一接口
def get_scheduler(
# 调度器名称,可以是字符串或 SchedulerType
name: Union[str, SchedulerType],
# 使用的优化器
optimizer: Optimizer,
# 步骤规则,仅 PIECEWISE_CONSTANT 调度器需要
step_rules: Optional[str] = None,
# 热身步骤数,可选参数
num_warmup_steps: Optional[int] = None,
# 训练步骤数,可选参数
num_training_steps: Optional[int] = None,
# 硬重启的周期数,默认值为 1
num_cycles: int = 1,
# 多项式调度器的幂因子,默认值为 1.0
power: float = 1.0,
# 恢复训练时的最后一个 epoch 索引,默认值为 -1
last_epoch: int = -1,
) -> LambdaLR:
"""
从调度器名称获取相应的调度器。
Args:
name (`str` or `SchedulerType`):
要使用的调度器名称。
optimizer (`torch.optim.Optimizer`):
训练中使用的优化器。
step_rules (`str`, *optional*):
表示步骤规则的字符串,仅 PIECEWISE_CONSTANT 调度器使用。
num_warmup_steps (`int`, *optional*):
热身步骤数。并非所有调度器都需要此参数(因此为可选)。
num_training_steps (`int`, *optional*):
训练步骤数。并非所有调度器都需要此参数(因此为可选)。
num_cycles (`int`, *optional*):
用于 COSINE_WITH_RESTARTS 调度器的硬重启次数。
power (`float`, *optional*, defaults to 1.0):
幂因子。见 POLYNOMIAL 调度器。
last_epoch (`int`, *optional*, defaults to -1):
恢复训练时的最后一个 epoch 的索引。
"""
# 将名称转换为 SchedulerType 类型
name = SchedulerType(name)
# 根据名称获取调度函数
schedule_func = TYPE_TO_SCHEDULER_FUNCTION[name]
# 如果是常量调度器,直接返回
if name == SchedulerType.CONSTANT:
return schedule_func(optimizer, last_epoch=last_epoch)
# 如果是分段常量调度器,返回带步骤规则的调度器
if name == SchedulerType.PIECEWISE_CONSTANT:
return schedule_func(optimizer, step_rules=step_rules, last_epoch=last_epoch)
# 其他调度器需要提供 num_warmup_steps
if num_warmup_steps is None:
raise ValueError(f"{name} requires `num_warmup_steps`, please provide that argument.")
# 如果是带热身的常量调度器,返回带热身步骤的调度器
if name == SchedulerType.CONSTANT_WITH_WARMUP:
return schedule_func(optimizer, num_warmup_steps=num_warmup_steps, last_epoch=last_epoch)
# 其他调度器需要提供 num_training_steps
if num_training_steps is None:
raise ValueError(f"{name} requires `num_training_steps`, please provide that argument.")
# 检查调度器类型是否为 COSINE_WITH_RESTARTS
if name == SchedulerType.COSINE_WITH_RESTARTS:
# 调用调度函数并传入相关参数
return schedule_func(
optimizer,
# 预热步数
num_warmup_steps=num_warmup_steps,
# 训练总步数
num_training_steps=num_training_steps,
# 周期数
num_cycles=num_cycles,
# 最后一个训练轮次
last_epoch=last_epoch,
)
# 检查调度器类型是否为 POLYNOMIAL
if name == SchedulerType.POLYNOMIAL:
# 调用调度函数并传入相关参数
return schedule_func(
optimizer,
# 预热步数
num_warmup_steps=num_warmup_steps,
# 训练总步数
num_training_steps=num_training_steps,
# 多项式的幂次
power=power,
# 最后一个训练轮次
last_epoch=last_epoch,
)
# 调用调度函数作为默认情况,传入相关参数
return schedule_func(
optimizer,
# 预热步数
num_warmup_steps=num_warmup_steps,
# 训练总步数
num_training_steps=num_training_steps,
# 最后一个训练轮次
last_epoch=last_epoch
)
.\diffusers\pipelines\amused\pipeline_amused.py
# 版权声明,表明该文件的版权归 HuggingFace 团队所有
# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# 根据 Apache License, Version 2.0("许可证")授权;
# 除非遵循许可证,否则不得使用此文件。
# 您可以在以下地址获得许可证副本:
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有约定,软件
# 在许可证下分发的,均按"原样"提供,没有任何明示或暗示的担保或条件。
# 请参阅许可证,以了解有关权限和
# 限制的具体条款。
# 从 typing 模块导入所需的类型提示
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
# 导入 PyTorch 库
import torch
# 从 transformers 库中导入 CLIP 文本模型和标记器
from transformers import CLIPTextModelWithProjection, CLIPTokenizer
# 从当前目录的上级导入 VAE 图像处理器
from ...image_processor import VaeImageProcessor
# 从当前目录的上级导入 UVit2D 模型和 VQ 模型
from ...models import UVit2DModel, VQModel
# 从当前目录的上级导入 Amused 调度器
from ...schedulers import AmusedScheduler
# 从当前目录的上级导入替换示例文档字符串的工具
from ...utils import replace_example_docstring
# 从上级目录的管道工具导入 DiffusionPipeline 和 ImagePipelineOutput
from ..pipeline_utils import DiffusionPipeline, ImagePipelineOutput
# 示例文档字符串,提供使用示例
EXAMPLE_DOC_STRING = """
Examples:
```py
>>> import torch
>>> from diffusers import AmusedPipeline
>>> pipe = AmusedPipeline.from_pretrained("amused/amused-512", variant="fp16", torch_dtype=torch.float16)
>>> pipe = pipe.to("cuda")
>>> prompt = "a photo of an astronaut riding a horse on mars"
>>> image = pipe(prompt).images[0]
```py
"""
# 定义 AmusedPipeline 类,继承自 DiffusionPipeline
class AmusedPipeline(DiffusionPipeline):
# 声明图像处理器属性
image_processor: VaeImageProcessor
# 声明 VQ 模型属性
vqvae: VQModel
# 声明标记器属性
tokenizer: CLIPTokenizer
# 声明文本编码器属性
text_encoder: CLIPTextModelWithProjection
# 声明转换器属性
transformer: UVit2DModel
# 声明调度器属性
scheduler: AmusedScheduler
# 定义 CPU 预加载顺序
model_cpu_offload_seq = "text_encoder->transformer->vqvae"
# 初始化方法,接收各个组件作为参数
def __init__(
self,
vqvae: VQModel,
tokenizer: CLIPTokenizer,
text_encoder: CLIPTextModelWithProjection,
transformer: UVit2DModel,
scheduler: AmusedScheduler,
):
# 调用父类的初始化方法
super().__init__()
# 注册各个组件
self.register_modules(
vqvae=vqvae,
tokenizer=tokenizer,
text_encoder=text_encoder,
transformer=transformer,
scheduler=scheduler,
)
# 计算 VAE 缩放因子
self.vae_scale_factor = 2 ** (len(self.vqvae.config.block_out_channels) - 1)
# 初始化图像处理器
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor, do_normalize=False)
# 装饰器,表示该方法不需要计算梯度
@torch.no_grad()
# 使用替换示例文档字符串的装饰器
@replace_example_docstring(EXAMPLE_DOC_STRING)
# 定义一个可调用的类方法,允许使用不同参数生成结果
def __call__(
# 用户输入的提示,可以是字符串或字符串列表,默认为 None
self,
prompt: Optional[Union[List[str], str]] = None,
# 输出图像的高度,默认为 None
height: Optional[int] = None,
# 输出图像的宽度,默认为 None
width: Optional[int] = None,
# 推理步骤的数量,默认为 12
num_inference_steps: int = 12,
# 指导比例,影响生成图像与提示的一致性,默认为 10.0
guidance_scale: float = 10.0,
# 负提示,可以是字符串或字符串列表,默认为 None
negative_prompt: Optional[Union[str, List[str]]] = None,
# 每个提示生成的图像数量,默认为 1
num_images_per_prompt: Optional[int] = 1,
# 用于生成的随机数生成器,默认为 None
generator: Optional[torch.Generator] = None,
# 潜在空间的张量,默认为 None
latents: Optional[torch.IntTensor] = None,
# 提示的嵌入张量,默认为 None
prompt_embeds: Optional[torch.Tensor] = None,
# 编码器的隐藏状态,默认为 None
encoder_hidden_states: Optional[torch.Tensor] = None,
# 负提示的嵌入张量,默认为 None
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 负编码器的隐藏状态,默认为 None
negative_encoder_hidden_states: Optional[torch.Tensor] = None,
# 输出类型,默认为 "pil"
output_type="pil",
# 是否返回字典格式的输出,默认为 True
return_dict: bool = True,
# 回调函数,用于在生成过程中执行特定操作,默认为 None
callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
# 回调执行的步骤间隔,默认为 1
callback_steps: int = 1,
# 跨注意力的额外参数,默认为 None
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 微调条件的美学评分,默认为 6
micro_conditioning_aesthetic_score: int = 6,
# 微调条件的裁剪坐标,默认为 (0, 0)
micro_conditioning_crop_coord: Tuple[int, int] = (0, 0),
# 温度参数,用于控制生成多样性,默认为 (2, 0)
temperature: Union[int, Tuple[int, int], List[int]] = (2, 0),
.\diffusers\pipelines\amused\pipeline_amused_img2img.py
# 版权信息,声明该文件的版权所有者和许可证信息
# Copyright 2024 The HuggingFace Team. All rights reserved.
#
# 使用 Apache License, Version 2.0(“许可证”)进行授权;
# 除非遵循许可证,否则不得使用此文件。
# 可以在以下网址获取许可证的副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有约定,软件在许可证下以“按现状”基础分发,
# 不附带任何类型的明示或暗示的担保或条件。
# 有关许可证下的具体权限和限制,请参见许可证。
# 从 typing 模块导入类型注释
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
# 导入 PyTorch 库
import torch
# 从 transformers 库导入 CLIP 相关模型和标记器
from transformers import CLIPTextModelWithProjection, CLIPTokenizer
# 从本地模块导入图像处理相关的类
from ...image_processor import PipelineImageInput, VaeImageProcessor
# 从本地模块导入模型类
from ...models import UVit2DModel, VQModel
# 从本地模块导入调度器类
from ...schedulers import AmusedScheduler
# 从本地模块导入文档字符串替换工具
from ...utils import replace_example_docstring
# 从本地模块导入扩散管道和图像输出相关类
from ..pipeline_utils import DiffusionPipeline, ImagePipelineOutput
# 示例文档字符串,用于展示如何使用该管道的示例代码
EXAMPLE_DOC_STRING = """
Examples:
```py
>>> import torch
>>> from diffusers import AmusedImg2ImgPipeline
>>> from diffusers.utils import load_image
>>> pipe = AmusedImg2ImgPipeline.from_pretrained(
... "amused/amused-512", variant="fp16", torch_dtype=torch.float16
... )
>>> pipe = pipe.to("cuda")
>>> prompt = "winter mountains"
>>> input_image = (
... load_image(
... "https://huggingface.co/datasets/diffusers/docs-images/resolve/main/open_muse/mountains.jpg"
... )
... .resize((512, 512))
... .convert("RGB")
... )
>>> image = pipe(prompt, input_image).images[0]
```py
"""
# 定义 AmusedImg2ImgPipeline 类,继承自 DiffusionPipeline
class AmusedImg2ImgPipeline(DiffusionPipeline):
# 声明类属性,表示图像处理器
image_processor: VaeImageProcessor
# 声明类属性,表示 VQ 模型
vqvae: VQModel
# 声明类属性,表示 CLIP 标记器
tokenizer: CLIPTokenizer
# 声明类属性,表示 CLIP 文本编码器
text_encoder: CLIPTextModelWithProjection
# 声明类属性,表示变换模型
transformer: UVit2DModel
# 声明类属性,表示调度器
scheduler: AmusedScheduler
# 定义模型 CPU 卸载顺序
model_cpu_offload_seq = "text_encoder->transformer->vqvae"
# TODO - 处理 self.vqvae.quantize 时使用 self.vqvae.quantize.embedding.weight,
# 该调用在 self.vqvae.quantize 的前向方法之前,因此钩子不会被调用以将参数
# 从 meta 设备移除。需要找到解决方法,而不是仅仅不卸载它
_exclude_from_cpu_offload = ["vqvae"]
# 初始化方法,接收多个模型和调度器作为参数
def __init__(
self,
# VQ 模型
vqvae: VQModel,
# CLIP 标记器
tokenizer: CLIPTokenizer,
# CLIP 文本编码器
text_encoder: CLIPTextModelWithProjection,
# UVit 变换模型
transformer: UVit2DModel,
# Amused 调度器
scheduler: AmusedScheduler,
# 初始化父类
):
super().__init__()
# 注册多个模块以供使用
self.register_modules(
vqvae=vqvae, # 注册变分量化自编码器
tokenizer=tokenizer, # 注册分词器
text_encoder=text_encoder, # 注册文本编码器
transformer=transformer, # 注册变换器
scheduler=scheduler, # 注册调度器
)
# 计算 VAE 的缩放因子,基于块输出通道数
self.vae_scale_factor = 2 ** (len(self.vqvae.config.block_out_channels) - 1)
# 创建图像处理器实例,未进行归一化处理
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor, do_normalize=False)
# 禁止梯度计算以节省内存和加快推理
@torch.no_grad()
# 替换示例文档字符串
@replace_example_docstring(EXAMPLE_DOC_STRING)
def __call__(
# 接收可选的提示,支持单个字符串或字符串列表
prompt: Optional[Union[List[str], str]] = None,
# 可选的输入图像
image: PipelineImageInput = None,
# 强度参数,控制生成图像的混合程度
strength: float = 0.5,
# 推理步骤的数量
num_inference_steps: int = 12,
# 引导比例,影响生成图像的风格
guidance_scale: float = 10.0,
# 可选的负面提示
negative_prompt: Optional[Union[str, List[str]]] = None,
# 每个提示生成的图像数量
num_images_per_prompt: Optional[int] = 1,
# 可选的随机数生成器
generator: Optional[torch.Generator] = None,
# 可选的提示嵌入
prompt_embeds: Optional[torch.Tensor] = None,
# 可选的编码器隐藏状态
encoder_hidden_states: Optional[torch.Tensor] = None,
# 可选的负面提示嵌入
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 可选的负面编码器隐藏状态
negative_encoder_hidden_states: Optional[torch.Tensor] = None,
# 输出类型,默认为 PIL 格式
output_type="pil",
# 是否返回字典格式的输出
return_dict: bool = True,
# 可选的回调函数,处理中间结果
callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
# 回调的步骤间隔
callback_steps: int = 1,
# 可选的交叉注意力参数
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 微调条件的美学评分,默认为 6
micro_conditioning_aesthetic_score: int = 6,
# 微调条件的裁剪坐标,默认为 (0, 0)
micro_conditioning_crop_coord: Tuple[int, int] = (0, 0),
# 温度参数,控制生成的多样性
temperature: Union[int, Tuple[int, int], List[int]] = (2, 0),
.\diffusers\pipelines\amused\pipeline_amused_inpaint.py
# 版权所有 2024 HuggingFace 团队,保留所有权利。
#
# 根据 Apache 许可证第 2.0 版(“许可证”)授权;
# 除非遵守许可证,否则您不得使用此文件。
# 您可以在以下网址获取许可证的副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有规定,软件
# 根据许可证分发是在“按现状”基础上,
# 不提供任何形式的担保或条件,无论是明示或暗示。
# 请参阅许可证以获取有关特定语言的权限和
# 限制的详细信息。
# 从 typing 模块导入类型提示
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
# 导入 PyTorch 库
import torch
# 从 transformers 模块导入 CLIP 模型和分词器
from transformers import CLIPTextModelWithProjection, CLIPTokenizer
# 从当前模块的相对路径导入图像处理器和模型
from ...image_processor import PipelineImageInput, VaeImageProcessor
from ...models import UVit2DModel, VQModel
from ...schedulers import AmusedScheduler
from ...utils import replace_example_docstring
from ..pipeline_utils import DiffusionPipeline, ImagePipelineOutput
# 示例文档字符串,提供用法示例
EXAMPLE_DOC_STRING = """
Examples:
```py
>>> import torch
>>> from diffusers import AmusedInpaintPipeline
>>> from diffusers.utils import load_image
>>> pipe = AmusedInpaintPipeline.from_pretrained(
... "amused/amused-512", variant="fp16", torch_dtype=torch.float16
... )
>>> pipe = pipe.to("cuda")
>>> prompt = "fall mountains"
>>> input_image = (
... load_image(
... "https://huggingface.co/datasets/diffusers/docs-images/resolve/main/open_muse/mountains_1.jpg"
... )
... .resize((512, 512))
... .convert("RGB")
... )
>>> mask = (
... load_image(
... "https://huggingface.co/datasets/diffusers/docs-images/resolve/main/open_muse/mountains_1_mask.png"
... )
... .resize((512, 512))
... .convert("L")
... )
>>> pipe(prompt, input_image, mask).images[0].save("out.png")
```py
"""
# 定义 AmusedInpaintPipeline 类,继承自 DiffusionPipeline
class AmusedInpaintPipeline(DiffusionPipeline):
# 定义类属性,表示图像处理器的类型
image_processor: VaeImageProcessor
# 定义类属性,表示 VQ 模型的类型
vqvae: VQModel
# 定义类属性,表示分词器的类型
tokenizer: CLIPTokenizer
# 定义类属性,表示文本编码器的类型
text_encoder: CLIPTextModelWithProjection
# 定义类属性,表示变换器的类型
transformer: UVit2DModel
# 定义类属性,表示调度器的类型
scheduler: AmusedScheduler
# 定义模型的 CPU 卸载顺序
model_cpu_offload_seq = "text_encoder->transformer->vqvae"
# TODO - 解决在调用 self.vqvae.quantize 时,未能调用钩子来移动参数的问题
_exclude_from_cpu_offload = ["vqvae"]
# 初始化方法,接受多个参数以构建管道
def __init__(
self,
vqvae: VQModel, # VQ模型
tokenizer: CLIPTokenizer, # CLIP分词器
text_encoder: CLIPTextModelWithProjection, # 文本编码器
transformer: UVit2DModel, # 变换器模型
scheduler: AmusedScheduler, # 调度器
):
# 调用父类的初始化方法
super().__init__()
# 注册模块,包括 VQ-VAE、tokenizer、文本编码器、转换器和调度器
self.register_modules(
vqvae=vqvae,
tokenizer=tokenizer,
text_encoder=text_encoder,
transformer=transformer,
scheduler=scheduler,
)
# 计算 VAE 的缩放因子,根据 VQ-VAE 的块输出通道数
self.vae_scale_factor = 2 ** (len(self.vqvae.config.block_out_channels) - 1)
# 创建图像处理器,配置 VAE 缩放因子,并设置不进行归一化
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor, do_normalize=False)
# 创建掩码处理器,配置 VAE 缩放因子,设置多项处理选项
self.mask_processor = VaeImageProcessor(
vae_scale_factor=self.vae_scale_factor,
do_normalize=False,
do_binarize=True, # 开启二值化处理
do_convert_grayscale=True, # 开启灰度转换
do_resize=True, # 开启缩放处理
)
# 将掩码调度注册到配置中,使用线性调度
self.scheduler.register_to_config(masking_schedule="linear")
@torch.no_grad() # 禁用梯度计算,提高推理效率
@replace_example_docstring(EXAMPLE_DOC_STRING) # 用示例文档字符串替换默认文档
def __call__(
self,
# 提示信息,可以是字符串或字符串列表
prompt: Optional[Union[List[str], str]] = None,
# 输入图像,类型为 PipelineImageInput
image: PipelineImageInput = None,
# 掩码图像,类型为 PipelineImageInput
mask_image: PipelineImageInput = None,
# 强度参数,影响生成图像的效果
strength: float = 1.0,
# 推理步骤数,影响生成质量
num_inference_steps: int = 12,
# 指导比例,影响生成图像的多样性
guidance_scale: float = 10.0,
# 负面提示信息,可以是字符串或字符串列表
negative_prompt: Optional[Union[str, List[str]]] = None,
# 每个提示生成的图像数量
num_images_per_prompt: Optional[int] = 1,
# 随机数生成器,控制随机性
generator: Optional[torch.Generator] = None,
# 提示的嵌入表示,类型为 torch.Tensor
prompt_embeds: Optional[torch.Tensor] = None,
# 编码器隐藏状态,类型为 torch.Tensor
encoder_hidden_states: Optional[torch.Tensor] = None,
# 负面提示的嵌入表示,类型为 torch.Tensor
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 负面编码器隐藏状态,类型为 torch.Tensor
negative_encoder_hidden_states: Optional[torch.Tensor] = None,
# 输出类型,默认为 "pil"
output_type="pil",
# 是否返回字典格式的结果
return_dict: bool = True,
# 回调函数,处理推理过程中的信息
callback: Optional[Callable[[int, int, torch.Tensor], None]] = None,
# 回调频率,控制回调调用的步骤
callback_steps: int = 1,
# 跨注意力参数,调整模型的注意力机制
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 微调条件美学评分,默认值为 6
micro_conditioning_aesthetic_score: int = 6,
# 微调裁剪坐标,默认值为 (0, 0)
micro_conditioning_crop_coord: Tuple[int, int] = (0, 0),
# 温度参数,影响生成的随机性
temperature: Union[int, Tuple[int, int], List[int]] = (2, 0),
.\diffusers\pipelines\amused\__init__.py
# 从 typing 模块导入 TYPE_CHECKING,用于类型检查
from typing import TYPE_CHECKING
# 从上级目录的 utils 模块导入多个工具和常量
from ...utils import (
DIFFUSERS_SLOW_IMPORT, # 导入用于慢导入的标志
OptionalDependencyNotAvailable, # 导入可选依赖项不可用异常
_LazyModule, # 导入延迟模块加载器
is_torch_available, # 导入检查 PyTorch 是否可用的函数
is_transformers_available, # 导入检查 Transformers 是否可用的函数
)
# 初始化一个空字典,用于存储虚拟对象
_dummy_objects = {}
# 初始化一个空字典,用于存储模块导入结构
_import_structure = {}
# 尝试块,用于检查依赖项的可用性
try:
# 如果 Transformers 和 Torch 其中一个不可用,抛出异常
if not (is_transformers_available() and is_torch_available()):
raise OptionalDependencyNotAvailable()
# 捕获可选依赖项不可用的异常
except OptionalDependencyNotAvailable:
# 从 dummy_torch_and_transformers_objects 中导入虚拟管道对象
from ...utils.dummy_torch_and_transformers_objects import (
AmusedImg2ImgPipeline, # 导入虚拟图像到图像管道
AmusedInpaintPipeline, # 导入虚拟图像修复管道
AmusedPipeline, # 导入虚拟通用管道
)
# 更新虚拟对象字典
_dummy_objects.update(
{
"AmusedPipeline": AmusedPipeline, # 更新字典,映射管道名称到对象
"AmusedImg2ImgPipeline": AmusedImg2ImgPipeline, # 更新字典,映射图像到图像管道名称到对象
"AmusedInpaintPipeline": AmusedInpaintPipeline, # 更新字典,映射图像修复管道名称到对象
}
)
# 如果依赖项可用,更新导入结构字典
else:
_import_structure["pipeline_amused"] = ["AmusedPipeline"] # 添加通用管道到导入结构
_import_structure["pipeline_amused_img2img"] = ["AmusedImg2ImgPipeline"] # 添加图像到图像管道到导入结构
_import_structure["pipeline_amused_inpaint"] = ["AmusedInpaintPipeline"] # 添加图像修复管道到导入结构
# 检查类型是否在检查阶段,或慢导入标志是否为真
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
# 尝试块,用于再次检查依赖项的可用性
try:
# 如果 Transformers 和 Torch 其中一个不可用,抛出异常
if not (is_transformers_available() and is_torch_available()):
raise OptionalDependencyNotAvailable()
# 捕获可选依赖项不可用的异常
except OptionalDependencyNotAvailable:
# 从 dummy_torch_and_transformers_objects 中导入虚拟管道对象
from ...utils.dummy_torch_and_transformers_objects import (
AmusedPipeline, # 导入虚拟通用管道
)
# 如果依赖项可用,导入实际的管道对象
else:
from .pipeline_amused import AmusedPipeline # 导入实际通用管道
from .pipeline_amused_img2img import AmusedImg2ImgPipeline # 导入实际图像到图像管道
from .pipeline_amused_inpaint import AmusedInpaintPipeline # 导入实际图像修复管道
# 如果不在类型检查或慢导入阶段
else:
import sys
# 使用延迟模块加载器替换当前模块
sys.modules[__name__] = _LazyModule(
__name__, # 当前模块名称
globals()["__file__"], # 当前文件路径
_import_structure, # 导入结构
module_spec=__spec__, # 模块规格
)
# 遍历虚拟对象字典,将虚拟对象添加到当前模块
for name, value in _dummy_objects.items():
setattr(sys.modules[__name__], name, value) # 动态设置模块属性
.\diffusers\pipelines\animatediff\pipeline_animatediff.py
# 版权所有 2024 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 # 从图像处理模块导入 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 # 从 UNet 动作模型导入 MotionAdapter 类
from ...schedulers import ( # 导入不同调度器类
DDIMScheduler,
DPMSolverMultistepScheduler,
EulerAncestralDiscreteScheduler,
EulerDiscreteScheduler,
LMSDiscreteScheduler,
PNDMScheduler,
)
from ...utils import ( # 从 utils 模块导入常用工具
USE_PEFT_BACKEND,
deprecate,
logging,
replace_example_docstring,
scale_lora_layers,
unscale_lora_layers,
)
from ...utils.torch_utils import randn_tensor # 从 PyTorch 工具模块导入随机张量生成函数
from ...video_processor import VideoProcessor # 导入视频处理类
from ..free_init_utils import FreeInitMixin # 导入自由初始化混合类
from ..free_noise_utils import AnimateDiffFreeNoiseMixin # 导入动画差异自由噪声混合类
from ..pipeline_utils import DiffusionPipeline, StableDiffusionMixin # 导入扩散管道及其混合类
from .pipeline_output import AnimateDiffPipelineOutput # 导入动画差异管道输出类
logger = logging.get_logger(__name__) # 获取当前模块的日志记录器,供后续使用
EXAMPLE_DOC_STRING = """ # 定义示例文档字符串,以展示使用方法
Examples: # 示例部分的起始
```py # 示例代码块开始
>>> import torch # 导入 PyTorch 库
>>> from diffusers import MotionAdapter, AnimateDiffPipeline, DDIMScheduler # 导入相关类
>>> from diffusers.utils import export_to_gif # 导入 GIF 导出工具
>>> adapter = MotionAdapter.from_pretrained("guoyww/animatediff-motion-adapter-v1-5-2") # 加载预训练的动作适配器
>>> pipe = AnimateDiffPipeline.from_pretrained("frankjoshua/toonyou_beta6", motion_adapter=adapter) # 加载动画差异管道并设置动作适配器
>>> pipe.scheduler = DDIMScheduler(beta_schedule="linear", steps_offset=1, clip_sample=False) # 设置调度器为 DDIM,并配置参数
>>> output = pipe(prompt="A corgi walking in the park") # 生成输出,输入提示文本
>>> frames = output.frames[0] # 提取第一帧
>>> export_to_gif(frames, "animation.gif") # 将帧导出为 GIF 文件
```py # 示例代码块结束
"""
class AnimateDiffPipeline( # 定义 AnimateDiffPipeline 类
DiffusionPipeline, # 继承自扩散管道类
StableDiffusionMixin, # 混合稳定扩散功能
TextualInversionLoaderMixin, # 混合文本反演加载功能
IPAdapterMixin, # 混合图像处理适配功能
StableDiffusionLoraLoaderMixin, # 混合稳定扩散 LoRA 加载功能
FreeInitMixin, # 混合自由初始化功能
AnimateDiffFreeNoiseMixin, # 混合动画差异自由噪声功能
):
r""" # 定义类文档字符串
Pipeline for text-to-video generation. # 描述该类为文本到视频生成的管道
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods # 指明该模型继承自扩散管道,并建议查看父类文档
``` # 文档字符串结束
# 该管道实现了所有管道操作(下载、保存、在特定设备上运行等)。
# 此管道还继承了以下加载方法:
# [`~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`] ):用于创建 UNetMotionModel 以去噪编码的视频潜在表示的 [`UNet2DConditionModel`]。
# motion_adapter ([`MotionAdapter`] ):与 `unet` 结合使用的 [`MotionAdapter`],用于去噪编码的视频潜在表示。
# 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__(
# 初始化所需的变分自编码器
self,
vae: AutoencoderKL,
# 初始化所需的文本编码器
text_encoder: CLIPTextModel,
# 初始化所需的标记器
tokenizer: CLIPTokenizer,
# 初始化所需的 UNet 模型(可以是 UNet2DConditionModel 或 UNetMotionModel)
unet: Union[UNet2DConditionModel, UNetMotionModel],
# 初始化所需的运动适配器
motion_adapter: MotionAdapter,
# 初始化所需的调度器(多种选择)
scheduler: Union[
DDIMScheduler,
PNDMScheduler,
LMSDiscreteScheduler,
EulerDiscreteScheduler,
EulerAncestralDiscreteScheduler,
DPMSolverMultistepScheduler,
],
# 可选的特征提取器
feature_extractor: CLIPImageProcessor = None,
# 可选的图像编码器
image_encoder: CLIPVisionModelWithProjection = None,
):
# 调用父类初始化方法
super().__init__()
# 如果 unet 是 UNet2DConditionModel,则将其转换为 UNetMotionModel
if isinstance(unet, UNet2DConditionModel):
unet = UNetMotionModel.from_unet2d(unet, motion_adapter)
# 注册所有模块,设置相应的属性
self.register_modules(
vae=vae,
text_encoder=text_encoder,
tokenizer=tokenizer,
unet=unet,
motion_adapter=motion_adapter,
scheduler=scheduler,
feature_extractor=feature_extractor,
image_encoder=image_encoder,
)
# 计算 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)
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.encode_prompt 复制的代码,修改了参数 num_images_per_prompt 为 num_videos_per_prompt
def encode_prompt(
self, # 方法所属的类实例
prompt, # 输入的提示文本
device, # 计算设备(如 GPU 或 CPU)
num_images_per_prompt, # 每个提示生成的图像数量
do_classifier_free_guidance, # 是否使用无分类器引导
negative_prompt=None, # 负向提示文本,默认为 None
prompt_embeds: Optional[torch.Tensor] = None, # 可选的提示嵌入
negative_prompt_embeds: Optional[torch.Tensor] = None, # 可选的负向提示嵌入
lora_scale: Optional[float] = None, # 可选的 LORA 缩放因子
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): # 检查图像是否为 Tensor
image = self.feature_extractor(image, return_tensors="pt").pixel_values # 使用特征提取器处理图像并转换为 Tensor
image = image.to(device=device, dtype=dtype) # 将图像移动到指定设备并转换为正确的数据类型
if output_hidden_states: # 如果要求输出隐藏状态
image_enc_hidden_states = self.image_encoder(image, output_hidden_states=True).hidden_states[-2] # 获取倒数第二个隐藏状态
image_enc_hidden_states = image_enc_hidden_states.repeat_interleave(num_images_per_prompt, dim=0) # 根据每个提示的图像数量重复隐藏状态
uncond_image_enc_hidden_states = self.image_encoder( # 对全零图像编码以获取无条件隐藏状态
torch.zeros_like(image), output_hidden_states=True
).hidden_states[-2] # 获取倒数第二个隐藏状态
uncond_image_enc_hidden_states = uncond_image_enc_hidden_states.repeat_interleave( # 根据每个提示的图像数量重复无条件隐藏状态
num_images_per_prompt, dim=0
)
return image_enc_hidden_states, uncond_image_enc_hidden_states # 返回图像和无条件的隐藏状态
else: # 如果不要求输出隐藏状态
image_embeds = self.image_encoder(image).image_embeds # 编码图像并获取图像嵌入
image_embeds = image_embeds.repeat_interleave(num_images_per_prompt, dim=0) # 根据每个提示的图像数量重复图像嵌入
uncond_image_embeds = torch.zeros_like(image_embeds) # 创建与图像嵌入形状相同的全零张量作为无条件嵌入
return image_embeds, uncond_image_embeds # 返回图像嵌入和无条件嵌入
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_ip_adapter_image_embeds 复制的代码
def prepare_ip_adapter_image_embeds( # 方法用于准备图像适配器的图像嵌入
self, # 方法所属的类实例
ip_adapter_image, # 输入的适配器图像
ip_adapter_image_embeds, # 输入的适配器图像嵌入
device, # 计算设备
num_images_per_prompt, # 每个提示生成的图像数量
do_classifier_free_guidance # 是否使用无分类器引导
):
# 初始化一个空列表,用于存储图像嵌入
image_embeds = []
# 如果启用了分类器自由引导,则初始化一个空列表用于负图像嵌入
if do_classifier_free_guidance:
negative_image_embeds = []
# 检查 ip_adapter_image_embeds 是否为 None
if ip_adapter_image_embeds is None:
# 如果 ip_adapter_image 不是列表,则将其转换为单元素列表
if not isinstance(ip_adapter_image, list):
ip_adapter_image = [ip_adapter_image]
# 确保 ip_adapter_image 的长度与 IP 适配器的数量相同
if len(ip_adapter_image) != len(self.unet.encoder_hid_proj.image_projection_layers):
raise ValueError(
# 抛出错误,提示 ip_adapter_image 的长度与 IP 适配器数量不匹配
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."
)
# 遍历 ip_adapter_image 和对应的图像投影层
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)
# 编码单个 IP 适配器图像,获取嵌入和负嵌入
single_image_embeds, single_negative_image_embeds = self.encode_image(
single_ip_adapter_image, device, 1, output_hidden_state
)
# 将单个图像嵌入添加到列表中,使用 None 维度增加维度
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 已定义,直接使用其中的图像嵌入
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 适配器图像嵌入
ip_adapter_image_embeds = []
# 遍历每个图像嵌入及其索引
for i, single_image_embeds in enumerate(image_embeds):
# 将每个图像嵌入复制 num_images_per_prompt 次
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)
# 返回最终的 IP 适配器图像嵌入列表
return ip_adapter_image_embeds
# 解码潜在向量的函数,接受潜在向量和解码块大小作为参数
def decode_latents(self, latents, decode_chunk_size: int = 16):
# 根据 VAE 配置的缩放因子调整潜在向量的值
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]
# 调用 VAE 的解码器进行解码,并提取样本数据
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 = {}
# 如果接受 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,
# 准备潜在向量的函数,接受多个参数
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:
# 准备 FreeNoise 模式下的潜变量,传入相关参数
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,
)
# 如果潜变量为 None,则生成随机潜变量
if latents is None:
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
else:
# 如果潜变量已存在,将其转移到指定设备
latents = latents.to(device)
# 按照调度器所需的标准差缩放初始噪声
latents = latents * self.scheduler.init_noise_sigma
# 返回生成的潜变量
return latents
@property
# 返回当前的引导比例
def guidance_scale(self):
return self._guidance_scale
@property
# 返回当前的剪切跳过值
def clip_skip(self):
return self._clip_skip
# 在此定义 `guidance_scale`,其类似于 Imagen 论文中公式 (2) 的引导权重 `w`: https://arxiv.org/pdf/2205.11487.pdf
# `guidance_scale = 1` 表示不进行分类器自由引导。
@property
# 判断是否进行分类器自由引导,基于引导比例是否大于 1
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: 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,
# 提供适配器图像,类型为 PipelineImageInput,默认为 None
ip_adapter_image: Optional[PipelineImageInput] = None,
# 提供适配器图像的嵌入表示,类型为 torch.Tensor 列表,默认为 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,
# 指定跳过的剪辑层数,默认为 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,
# 接受任意其他关键字参数
**kwargs,
.\diffusers\pipelines\animatediff\pipeline_animatediff_controlnet.py
# 版权信息,声明版权归 HuggingFace 团队所有
#
# 根据 Apache 许可证第 2.0 版(“许可证”)授权;
# 除非遵守许可证,否则不得使用本文件。
# 可通过以下网址获取许可证副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律或书面协议另有规定,软件
# 在“按原样”基础上分发,不提供任何形式的担保或条件。
# 请参阅许可证了解有关权限和
# 限制的具体语言。
# 导入 inspect 模块,用于检查对象
import inspect
# 从 typing 模块导入类型注释所需的类
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
# 导入 PyTorch 库
import torch
# 导入 PyTorch 的功能性操作模块
import torch.nn.functional as F
# 从 transformers 库导入与 CLIP 相关的模型和处理器
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer, CLIPVisionModelWithProjection
# 从图像处理模块导入 PipelineImageInput 类
from ...image_processor import PipelineImageInput
# 从 loaders 模块导入多个混合类
from ...loaders import IPAdapterMixin, StableDiffusionLoraLoaderMixin, TextualInversionLoaderMixin
# 从 models 模块导入多个模型类
from ...models import AutoencoderKL, ControlNetModel, ImageProjection, UNet2DConditionModel, UNetMotionModel
# 从 LoRA 模块导入调整文本编码器的函数
from ...models.lora import adjust_lora_scale_text_encoder
# 从 UNet 动作模型模块导入 MotionAdapter 类
from ...models.unets.unet_motion_model import MotionAdapter
# 从调度器模块导入 KarrasDiffusionSchedulers 类
from ...schedulers import KarrasDiffusionSchedulers
# 从工具模块导入多个实用函数
from ...utils import USE_PEFT_BACKEND, logging, scale_lora_layers, unscale_lora_layers
# 从 PyTorch 工具模块导入一些辅助函数
from ...utils.torch_utils import is_compiled_module, randn_tensor
# 从视频处理模块导入 VideoProcessor 类
from ...video_processor import VideoProcessor
# 从控制网络模块导入 MultiControlNetModel 类
from ..controlnet.multicontrolnet import MultiControlNetModel
# 从免费初始化工具模块导入 FreeInitMixin 类
from ..free_init_utils import FreeInitMixin
# 从免费噪声工具模块导入 AnimateDiffFreeNoiseMixin 类
from ..free_noise_utils import AnimateDiffFreeNoiseMixin
# 从管道工具模块导入 DiffusionPipeline 和 StableDiffusionMixin 类
from ..pipeline_utils import DiffusionPipeline, StableDiffusionMixin
# 从管道输出模块导入 AnimateDiffPipelineOutput 类
from .pipeline_output import AnimateDiffPipelineOutput
# 获取当前模块的日志记录器实例
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
# 示例文档字符串,当前为空
EXAMPLE_DOC_STRING = """
"""
# 定义 AnimateDiffControlNetPipeline 类,继承多个混合类
class AnimateDiffControlNetPipeline(
DiffusionPipeline,
StableDiffusionMixin,
TextualInversionLoaderMixin,
IPAdapterMixin,
StableDiffusionLoraLoaderMixin,
FreeInitMixin,
AnimateDiffFreeNoiseMixin,
):
r"""
用于基于 ControlNet 指导的文本到视频生成的管道。
该模型继承自 [`DiffusionPipeline`]。请查看超类文档了解所有管道的通用方法
(下载、保存、在特定设备上运行等)。
该管道还继承以下加载方法:
- [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] 用于加载文本反演嵌入
- [`~loaders.StableDiffusionLoraLoaderMixin.load_lora_weights`] 用于加载 LoRA 权重
- [`~loaders.StableDiffusionLoraLoaderMixin.save_lora_weights`] 用于保存 LoRA 权重
- [`~loaders.IPAdapterMixin.load_ip_adapter`] 用于加载 IP 适配器
# 定义参数文档字符串,描述每个参数的用途和类型
Args:
vae ([`AutoencoderKL`]):
Variational Auto-Encoder (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->unet->vae"
# 可选组件列表
_optional_components = ["feature_extractor", "image_encoder"]
# 注册的张量输入列表
_callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"]
# 初始化方法,设置模型的各个组件
def __init__(
self,
vae: AutoencoderKL, # VAE 模型
text_encoder: CLIPTextModel, # 文本编码器
tokenizer: CLIPTokenizer, # 文本分词器
unet: Union[UNet2DConditionModel, UNetMotionModel], # UNet 模型
motion_adapter: MotionAdapter, # 动作适配器
controlnet: Union[ControlNetModel, List[ControlNetModel], Tuple[ControlNetModel], MultiControlNetModel], # 控制网络模型
scheduler: KarrasDiffusionSchedulers, # 调度器
feature_extractor: Optional[CLIPImageProcessor] = None, # 可选特征提取器
image_encoder: Optional[CLIPVisionModelWithProjection] = None, # 可选图像编码器
):
# 调用父类的初始化方法
super().__init__()
# 检查 UNet 类型,如果是 UNet2DConditionModel,则转换为 UNetMotionModel
if isinstance(unet, UNet2DConditionModel):
unet = UNetMotionModel.from_unet2d(unet, motion_adapter)
# 如果 controlnet 是列表或元组,转换为 MultiControlNetModel
if isinstance(controlnet, (list, tuple)):
controlnet = MultiControlNetModel(controlnet)
# 注册各个模型模块
self.register_modules(
vae=vae, # 注册 VAE 模型
text_encoder=text_encoder, # 注册文本编码器
tokenizer=tokenizer, # 注册文本分词器
unet=unet, # 注册 UNet 模型
motion_adapter=motion_adapter, # 注册动作适配器
controlnet=controlnet, # 注册控制网络模型
scheduler=scheduler, # 注册调度器
feature_extractor=feature_extractor, # 注册特征提取器
image_encoder=image_encoder, # 注册图像编码器
)
# 计算 VAE 的缩放因子
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
# 创建视频处理器,使用 VAE 缩放因子
self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor)
# 创建控制视频处理器,带 RGB 转换和归一化选项
self.control_video_processor = VideoProcessor(
vae_scale_factor=self.vae_scale_factor, do_convert_rgb=True, do_normalize=False
)
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.encode_prompt 复制,修改 num_images_per_prompt 为 num_videos_per_prompt
# 定义一个编码提示的函数,接受多个参数以进行处理
def encode_prompt(
self,
prompt, # 要编码的提示文本
device, # 设备类型(CPU或GPU)
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_scale: Optional[float] = None, # 可选的 LoRA 缩放因子
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, device, num_images_per_prompt, do_classifier_free_guidance # 定义一个函数以准备 IP 适配器的图像嵌入
): # 函数定义结束,开始函数体
image_embeds = [] # 初始化用于存储图像嵌入的列表
if do_classifier_free_guidance: # 检查是否使用无分类器引导
negative_image_embeds = [] # 初始化用于存储负图像嵌入的列表
if ip_adapter_image_embeds is None: # 检查输入适配器图像嵌入是否为 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 # 通过 zip 函数组合图像和投影层
):
output_hidden_state = not isinstance(image_proj_layer, ImageProjection) # 判断是否需要输出隐藏状态
single_image_embeds, single_negative_image_embeds = self.encode_image( # 调用 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: # 如果输入的适配器图像嵌入不为 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 = [] # 初始化用于存储适配器图像嵌入的列表
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 # 返回适配器图像嵌入列表
# Copied from diffusers.pipelines.animatediff.pipeline_animatediff.AnimateDiffPipeline.decode_latents # 复制自其他模块的解码函数
# 解码潜在向量,将其转换为视频数据
def decode_latents(self, latents, decode_chunk_size: int = 16):
# 根据 VAE 的缩放因子调整潜在向量
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
# 从稳定扩散管道复制的准备额外步骤参数的函数
def prepare_extra_step_kwargs(self, generator, eta):
# 准备调度器步骤的额外参数,因为并非所有调度器都有相同的参数签名
# eta (η) 仅在 DDIMScheduler 中使用,其他调度器将被忽略
# eta 在 DDIM 论文中对应于 η,范围应在 [0, 1] 之间
# 检查调度器是否接受 eta 参数
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 初始化额外步骤参数字典
extra_step_kwargs = {}
# 如果调度器接受 eta,则将其添加到字典中
if accepts_eta:
extra_step_kwargs["eta"] = eta
# 检查调度器是否接受 generator 参数
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 如果调度器接受 generator,则将其添加到字典中
if accepts_generator:
extra_step_kwargs["generator"] = generator
# 返回包含额外参数的字典
return extra_step_kwargs
# 检查输入参数的函数
def check_inputs(
self,
prompt,
height,
width,
num_frames,
negative_prompt=None,
prompt_embeds=None,
negative_prompt_embeds=None,
callback_on_step_end_tensor_inputs=None,
video=None,
controlnet_conditioning_scale=1.0,
control_guidance_start=0.0,
control_guidance_end=1.0,
):
# 从动画扩散管道复制的准备潜在向量的函数
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:
# 准备潜在变量,使用 FreeNoise 方法
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,
)
# 如果潜在变量为 None,则生成随机潜在变量
if latents is None:
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
else:
# 将现有的潜在变量移动到指定的设备
latents = latents.to(device)
# 根据调度器要求的标准差缩放初始噪声
latents = latents * self.scheduler.init_noise_sigma
# 返回生成的潜在变量
return latents
def prepare_video(
self,
video,
width,
height,
batch_size,
num_videos_per_prompt,
device,
dtype,
do_classifier_free_guidance=False,
guess_mode=False,
):
# 对输入视频进行预处理,调整大小并转换为指定的数据类型
video = self.control_video_processor.preprocess_video(video, height=height, width=width).to(
dtype=torch.float32
)
# 重新排列视频的维度,并将前两个维度扁平化
video = video.permute(0, 2, 1, 3, 4).flatten(0, 1)
# 获取视频的批大小
video_batch_size = video.shape[0]
# 根据视频批大小确定重复次数
if video_batch_size == 1:
repeat_by = batch_size
else:
# 如果视频批大小与提示批大小相同,则重复次数为每个提示的视频数量
repeat_by = num_videos_per_prompt
# 根据计算的重复次数重复视频数据
video = video.repeat_interleave(repeat_by, dim=0)
# 将视频移动到指定的设备和数据类型
video = video.to(device=device, dtype=dtype)
# 如果启用无分类器引导且不处于猜测模式,则将视频重复拼接
if do_classifier_free_guidance and not guess_mode:
video = torch.cat([video] * 2)
# 返回处理后的视频
return video
@property
def guidance_scale(self):
# 返回引导缩放因子
return self._guidance_scale
@property
def clip_skip(self):
# 返回跳过的剪辑数
return self._clip_skip
# 此处 `guidance_scale` 定义类似于 Imagen 论文中公式 (2) 的引导权重 `w`
# https://arxiv.org/pdf/2205.11487.pdf 。 `guidance_scale = 1`
# 表示不进行无分类器引导。
@property
def do_classifier_free_guidance(self):
# 如果引导缩放因子大于 1,则启用无分类器引导
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()
# 定义可调用方法,用于执行某些操作
def __call__(
# 提示文本,可以是单个字符串或字符串列表
self,
prompt: 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,
# 额外参数 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[PipelineImageInput] = None,
# 条件帧的列表,默认为 None
conditioning_frames: Optional[List[PipelineImageInput]] = None,
# 输出类型,默认为 "pil"
output_type: Optional[str] = "pil",
# 是否返回字典,默认为 True
return_dict: bool = True,
# 跨注意力参数,默认为 None
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
# 控制网络的条件比例,默认为 1.0
controlnet_conditioning_scale: Union[float, List[float]] = 1.0,
# 猜测模式,默认为 False
guess_mode: bool = False,
# 控制指导开始比例,默认为 0.0
control_guidance_start: Union[float, List[float]] = 0.0,
# 控制指导结束比例,默认为 1.0
control_guidance_end: Union[float, List[float]] = 1.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"],
# 解码块大小,默认为 16
decode_chunk_size: int = 16,
.\diffusers\pipelines\animatediff\pipeline_animatediff_sdxl.py
# 版权声明,标明此文件由 HuggingFace 团队编写并保留所有权利
#
# 根据 Apache 2.0 许可协议进行许可(“许可协议”);
# 除非遵循该许可协议,否则不得使用此文件。
# 可在以下地址获取许可协议副本:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 除非根据适用法律或书面协议另有约定,
# 否则根据许可协议分发的软件按“原样”提供,
# 不附带任何形式的保证或条件,无论是明示还是暗示。
# 有关许可协议所涉及权限和限制的具体信息,
# 请参见许可协议。
#
# 导入 inspect 模块以进行对象的检查和获取信息
import inspect
# 从 typing 模块导入类型注解相关的类
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
# 导入 torch 库以支持深度学习操作
import torch
# 从 transformers 库导入多个 CLIP 相关模型和处理器
from transformers import (
CLIPImageProcessor, # 用于处理图像的 CLIP 处理器
CLIPTextModel, # 文本模型
CLIPTextModelWithProjection, # 带有投影功能的文本模型
CLIPTokenizer, # CLIP 的分词器
CLIPVisionModelWithProjection, # 带有投影功能的视觉模型
)
# 从本地模块导入图像处理相关的类
from ...image_processor import PipelineImageInput
# 从加载器模块导入多个混合类
from ...loaders import (
FromSingleFileMixin, # 从单个文件加载的混合类
IPAdapterMixin, # 适配器混合类
StableDiffusionXLLoraLoaderMixin, # 稳定扩散模型加载器混合类
TextualInversionLoaderMixin, # 文本反转加载器混合类
)
# 从模型模块导入多个深度学习模型
from ...models import AutoencoderKL, ImageProjection, MotionAdapter, UNet2DConditionModel, UNetMotionModel
# 从注意力处理器模块导入不同的注意力处理器类
from ...models.attention_processor import (
AttnProcessor2_0, # 注意力处理器版本 2.0
FusedAttnProcessor2_0, # 融合的注意力处理器版本 2.0
XFormersAttnProcessor, # XFormers 注意力处理器
)
# 从 LoRA 模块导入调整文本编码器 LoRA 权重的函数
from ...models.lora import adjust_lora_scale_text_encoder
# 从调度器模块导入多种调度器类
from ...schedulers import (
DDIMScheduler, # DDIM 调度器
DPMSolverMultistepScheduler, # DPM 多步调度器
EulerAncestralDiscreteScheduler, # 欧拉祖先离散调度器
EulerDiscreteScheduler, # 欧拉离散调度器
LMSDiscreteScheduler, # LMS 离散调度器
PNDMScheduler, # PNDM 调度器
)
# 从工具模块导入多个实用功能和常量
from ...utils import (
USE_PEFT_BACKEND, # 指示是否使用 PEFT 后端的标志
logging, # 导入日志记录工具
replace_example_docstring, # 替换示例文档字符串的功能
scale_lora_layers, # 调整 LoRA 层的规模的功能
unscale_lora_layers, # 恢复 LoRA 层规模的功能
)
# 从 PyTorch 工具模块导入随机张量生成函数
from ...utils.torch_utils import randn_tensor
# 导入视频处理器类
from ...video_processor import VideoProcessor
# 导入自由初始化相关的混合类
from ..free_init_utils import FreeInitMixin
# 导入扩散管道和稳定扩散相关的类
from ..pipeline_utils import DiffusionPipeline, StableDiffusionMixin
# 导入管道输出模块中的 AnimateDiffPipelineOutput 类
from .pipeline_output import AnimateDiffPipelineOutput
# 获取当前模块的日志记录器实例
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
# 示例文档字符串,未给出具体内容
EXAMPLE_DOC_STRING = """
# 示例代码展示如何使用Diffusers库生成动画
Examples:
```py
# 导入所需的库和模块
>>> import torch
>>> from diffusers.models import MotionAdapter
>>> from diffusers import AnimateDiffSDXLPipeline, DDIMScheduler
>>> from diffusers.utils import export_to_gif
# 从预训练模型加载运动适配器,使用半精度浮点数
>>> adapter = MotionAdapter.from_pretrained(
... "a-r-r-o-w/animatediff-motion-adapter-sdxl-beta", torch_dtype=torch.float16
... )
# 定义基础模型ID
>>> model_id = "stabilityai/stable-diffusion-xl-base-1.0"
# 从预训练模型加载调度器,配置相关参数
>>> scheduler = DDIMScheduler.from_pretrained(
... model_id,
... subfolder="scheduler",
... clip_sample=False,
... timestep_spacing="linspace",
... beta_schedule="linear",
... steps_offset=1,
... )
# 从预训练模型加载动画管道,并将其移至GPU
>>> pipe = AnimateDiffSDXLPipeline.from_pretrained(
... model_id,
... motion_adapter=adapter,
... scheduler=scheduler,
... torch_dtype=torch.float16,
... variant="fp16",
... ).to("cuda")
# 启用内存节省功能
>>> # enable memory savings
>>> pipe.enable_vae_slicing()
>>> pipe.enable_vae_tiling()
# 调用管道生成动画,提供提示和参数
>>> output = pipe(
... prompt="a panda surfing in the ocean, realistic, high quality",
... negative_prompt="low quality, worst quality",
... num_inference_steps=20,
... guidance_scale=8,
... width=1024,
... height=1024,
... num_frames=16,
... )
# 提取生成的第一帧
>>> frames = output.frames[0]
# 将帧导出为GIF动画文件
>>> export_to_gif(frames, "animation.gif")
```py
"""
# 该模块提供噪声配置的调整和时间步检索功能
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.rescale_noise_cfg
def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0):
"""
根据 `guidance_rescale` 调整 `noise_cfg`。基于论文 [Common Diffusion Noise Schedules and
Sample Steps are Flawed](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
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
def retrieve_timesteps(
scheduler,
num_inference_steps: Optional[int] = None,
device: Optional[Union[str, torch.device]] = None,
timesteps: Optional[List[int]] = None,
sigmas: Optional[List[float]] = None,
**kwargs,
):
"""
调用调度器的 `set_timesteps` 方法,并在调用后从调度器检索时间步。处理自定义时间步。任何关键字参数将被传递给 `scheduler.set_timesteps`。
Args:
scheduler (`SchedulerMixin`):
用于获取时间步的调度器。
num_inference_steps (`int`):
生成样本时使用的扩散步骤数。如果使用,则 `timesteps` 必须为 `None`。
device (`str` 或 `torch.device`,*可选*):
要移动到的设备。如果为 `None`,则不移动时间步。
timesteps (`List[int]`,*可选*):
用于覆盖调度器时间步间距策略的自定义时间步。如果传递了 `timesteps`,则 `num_inference_steps` 和 `sigmas` 必须为 `None`。
sigmas (`List[float]`,*可选*):
用于覆盖调度器时间步间距策略的自定义 sigma。如果传递了 `sigmas`,则 `num_inference_steps` 和 `timesteps` 必须为 `None`。
Returns:
`Tuple[torch.Tensor, int]`: 一个元组,第一个元素是来自调度器的时间步调度,第二个元素是推理步骤的数量。
"""
# 检查是否同时传递了自定义时间步和 sigma,若是则抛出错误
if timesteps is not None and sigmas is not None:
raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
# 如果提供了时间步长参数
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."
)
# 设置调度器的时间步长,传入设备和其他参数
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
# 从调度器获取设置后的时间步长
timesteps = scheduler.timesteps
# 计算推理步骤的数量
num_inference_steps = len(timesteps)
# 如果提供了 sigma 参数
elif sigmas is not None:
# 检查调度器的 set_timesteps 方法是否接受 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."
)
# 设置调度器的时间步长,传入 sigma、设备和其他参数
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
# 从调度器获取设置后的时间步长
timesteps = scheduler.timesteps
# 计算推理步骤的数量
num_inference_steps = len(timesteps)
# 如果既没有时间步长也没有 sigma 参数
else:
# 设置调度器的时间步长为推理步骤数量,传入设备和其他参数
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
# 从调度器获取设置后的时间步长
timesteps = scheduler.timesteps
# 返回时间步长和推理步骤的数量
return timesteps, num_inference_steps
# 定义一个名为 AnimateDiffSDXLPipeline 的类,继承多个基类
class AnimateDiffSDXLPipeline(
# 继承 DiffusionPipeline 类,提供扩散管道功能
DiffusionPipeline,
# 继承 StableDiffusionMixin 类,提供稳定扩散相关功能
StableDiffusionMixin,
# 继承 FromSingleFileMixin 类,允许从单一文件加载
FromSingleFileMixin,
# 继承 StableDiffusionXLLoraLoaderMixin 类,允许加载 LoRA 权重
StableDiffusionXLLoraLoaderMixin,
# 继承 TextualInversionLoaderMixin 类,允许加载文本反转嵌入
TextualInversionLoaderMixin,
# 继承 IPAdapterMixin 类,提供 IP 适配器加载功能
IPAdapterMixin,
# 继承 FreeInitMixin 类,提供自由初始化相关功能
FreeInitMixin,
):
# 文档字符串,描述该类的用途和继承的功能
r"""
Pipeline for text-to-video generation using Stable Diffusion XL.
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the
library implements for all the pipelines (such as downloading or 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.FromSingleFileMixin.from_single_file`] for loading `.ckpt` files
- [`~loaders.StableDiffusionXLLoraLoaderMixin.load_lora_weights`] for loading LoRA weights
- [`~loaders.StableDiffusionXLLoraLoaderMixin.save_lora_weights`] for saving LoRA weights
- [`~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.
# 冻结的文本编码器,Stable Diffusion XL 使用 CLIP 的文本部分
text_encoder ([`CLIPTextModel`]):
Frozen text-encoder. Stable Diffusion XL uses the text portion of
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically
the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant.
# 第二个冻结文本编码器,Stable Diffusion XL 使用 CLIP 的文本和池部分
text_encoder_2 ([` CLIPTextModelWithProjection`]):
Second frozen text-encoder. Stable Diffusion XL uses the text and pool portion of
[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.
# CLIPTokenizer 类的分词器
tokenizer (`CLIPTokenizer`):
Tokenizer of class
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer).
# 第二个 CLIPTokenizer 类的分词器
tokenizer_2 (`CLIPTokenizer`):
Second Tokenizer of class
[CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer).
# 条件 U-Net 架构,用于去噪编码后的图像潜在表示
unet ([`UNet2DConditionModel`]):
Conditional U-Net architecture to denoise the encoded image latents.
# 调度器,与 U-Net 结合使用以去噪编码的图像潜在表示
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`].
# 是否强制将负提示嵌入设置为 0 的布尔值,可选,默认为 "True"
force_zeros_for_empty_prompt (`bool`, *optional*, defaults to `"True"`):
Whether the negative prompt embeddings shall be forced to always be set to 0. Also see the config of
`stabilityai/stable-diffusion-xl-base-1-0`.
"""
# 定义模型在 CPU 上卸载的顺序
model_cpu_offload_seq = "text_encoder->text_encoder_2->image_encoder->unet->vae"
# 可选组件的列表
_optional_components = [
# 分词器
"tokenizer",
# 第二个分词器
"tokenizer_2",
# 文本编码器
"text_encoder",
# 第二个文本编码器
"text_encoder_2",
# 图像编码器
"image_encoder",
# 特征提取器
"feature_extractor",
]
# 回调张量输入的列表
_callback_tensor_inputs = [
# 潜在表示
"latents",
# 提示嵌入
"prompt_embeds",
# 负提示嵌入
"negative_prompt_embeds",
# 附加文本嵌入
"add_text_embeds",
# 附加时间 ID
"add_time_ids",
# 负池化提示嵌入
"negative_pooled_prompt_embeds",
# 负附加时间 ID
"negative_add_time_ids",
]
# 初始化类的构造函数
def __init__(
self,
vae: AutoencoderKL, # 定义变分自编码器
text_encoder: CLIPTextModel, # 定义第一个文本编码器
text_encoder_2: CLIPTextModelWithProjection, # 定义第二个文本编码器,带投影
tokenizer: CLIPTokenizer, # 定义第一个分词器
tokenizer_2: CLIPTokenizer, # 定义第二个分词器
unet: Union[UNet2DConditionModel, UNetMotionModel], # 定义 UNet 模型,可以是条件模型或运动模型
motion_adapter: MotionAdapter, # 定义运动适配器
scheduler: Union[ # 定义调度器,支持多种类型
DDIMScheduler,
PNDMScheduler,
LMSDiscreteScheduler,
EulerDiscreteScheduler,
EulerAncestralDiscreteScheduler,
DPMSolverMultistepScheduler,
],
image_encoder: CLIPVisionModelWithProjection = None, # 可选的图像编码器,带投影
feature_extractor: CLIPImageProcessor = None, # 可选的特征提取器
force_zeros_for_empty_prompt: bool = True, # 空提示时是否强制使用零
):
# 调用父类构造函数
super().__init__()
# 如果 UNet 是二维条件模型,则将其转换为运动模型
if isinstance(unet, UNet2DConditionModel):
unet = UNetMotionModel.from_unet2d(unet, motion_adapter)
# 注册各个模块,以便在模型中使用
self.register_modules(
vae=vae,
text_encoder=text_encoder,
text_encoder_2=text_encoder_2,
tokenizer=tokenizer,
tokenizer_2=tokenizer_2,
unet=unet,
motion_adapter=motion_adapter,
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)
# 初始化视频处理器
self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor)
# 设置默认采样大小
self.default_sample_size = self.unet.config.sample_size
# 从 StableDiffusionXLPipeline 复制的 encode_prompt 方法,修改参数为 num_videos_per_prompt
def encode_prompt(
self,
prompt: str, # 输入的提示文本
prompt_2: Optional[str] = None, # 可选的第二个提示文本
device: Optional[torch.device] = None, # 可选的设备设置
num_videos_per_prompt: int = 1, # 每个提示生成的视频数量
do_classifier_free_guidance: bool = True, # 是否使用无分类器引导
negative_prompt: Optional[str] = None, # 可选的负面提示文本
negative_prompt_2: Optional[str] = None, # 可选的第二个负面提示文本
prompt_embeds: Optional[torch.Tensor] = None, # 可选的提示嵌入
negative_prompt_embeds: Optional[torch.Tensor] = None, # 可选的负面提示嵌入
pooled_prompt_embeds: Optional[torch.Tensor] = None, # 可选的池化提示嵌入
negative_pooled_prompt_embeds: Optional[torch.Tensor] = None, # 可选的负面池化嵌入
lora_scale: Optional[float] = None, # 可选的 Lora 缩放因子
clip_skip: Optional[int] = None, # 可选的跳过剪辑
# 从 StableDiffusionPipeline 复制的 encode_image 方法
# 定义一个方法用于编码图像
def encode_image(self, image, device, num_images_per_prompt, output_hidden_states=None):
# 获取图像编码器参数的数据类型
dtype = next(self.image_encoder.parameters()).dtype
# 检查输入的图像是否为张量,如果不是,则使用特征提取器将其转换为张量
if not isinstance(image, torch.Tensor):
image = self.feature_extractor(image, return_tensors="pt").pixel_values
# 将图像移动到指定设备并转换为相应的数据类型
image = image.to(device=device, dtype=dtype)
# 如果要求输出隐藏状态,则进行相应的处理
if output_hidden_states:
# 编码图像并获取倒数第二层的隐藏状态
image_enc_hidden_states = self.image_encoder(image, output_hidden_states=True).hidden_states[-2]
# 将隐藏状态根据每个提示的图像数量进行重复
image_enc_hidden_states = image_enc_hidden_states.repeat_interleave(num_images_per_prompt, dim=0)
# 对全零张量进行编码以获取无条件隐藏状态
uncond_image_enc_hidden_states = self.image_encoder(
torch.zeros_like(image), output_hidden_states=True
).hidden_states[-2]
# 将无条件隐藏状态根据每个提示的图像数量进行重复
uncond_image_enc_hidden_states = uncond_image_enc_hidden_states.repeat_interleave(
num_images_per_prompt, dim=0
)
# 返回编码后的图像隐藏状态和无条件隐藏状态
return image_enc_hidden_states, uncond_image_enc_hidden_states
else:
# 如果不需要输出隐藏状态,直接获取图像嵌入
image_embeds = self.image_encoder(image).image_embeds
# 将图像嵌入根据每个提示的图像数量进行重复
image_embeds = image_embeds.repeat_interleave(num_images_per_prompt, dim=0)
# 创建与图像嵌入形状相同的全零张量作为无条件嵌入
uncond_image_embeds = torch.zeros_like(image_embeds)
# 返回图像嵌入和无条件嵌入
return image_embeds, uncond_image_embeds
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_ip_adapter_image_embeds 复制的方法
def prepare_ip_adapter_image_embeds(
# 定义参数,包括适配器图像、图像嵌入、设备、每个提示的图像数量和分类器自由引导的标志
self, ip_adapter_image, ip_adapter_image_embeds, device, num_images_per_prompt, do_classifier_free_guidance
# 定义一个函数,用于处理图像嵌入
):
# 初始化一个空列表用于存储图像嵌入
image_embeds = []
# 如果启用了分类器自由引导,初始化一个空列表用于存储负图像嵌入
if do_classifier_free_guidance:
negative_image_embeds = []
# 如果输入适配器图像嵌入为空
if ip_adapter_image_embeds is None:
# 如果输入适配器图像不是列表,将其转换为列表
if not isinstance(ip_adapter_image, list):
ip_adapter_image = [ip_adapter_image]
# 检查输入适配器图像的数量是否与 IP 适配器数量一致
if len(ip_adapter_image) != len(self.unet.encoder_hid_proj.image_projection_layers):
raise ValueError(
# 抛出值错误,说明输入适配器图像的数量与 IP 适配器的数量不匹配
f"`ip_adapter_image` must have same length as the number of IP Adapters. Got {len(ip_adapter_image)} images and {len(self.unet.encoder_hid_proj.image_projection_layers)} IP Adapters."
)
# 遍历输入适配器图像和图像投影层
for single_ip_adapter_image, image_proj_layer in zip(
ip_adapter_image, self.unet.encoder_hid_proj.image_projection_layers
):
# 判断当前图像投影层是否是 ImageProjection 的实例
output_hidden_state = not isinstance(image_proj_layer, ImageProjection)
# 编码当前单张适配器图像,获取图像嵌入和负图像嵌入
single_image_embeds, single_negative_image_embeds = self.encode_image(
single_ip_adapter_image, device, 1, output_hidden_state
)
# 将单张图像嵌入添加到图像嵌入列表
image_embeds.append(single_image_embeds[None, :])
# 如果启用了分类器自由引导,将负图像嵌入添加到负图像嵌入列表
if do_classifier_free_guidance:
negative_image_embeds.append(single_negative_image_embeds[None, :])
else:
# 遍历已提供的适配器图像嵌入
for single_image_embeds in ip_adapter_image_embeds:
# 如果启用了分类器自由引导,分离出负图像嵌入和图像嵌入
if do_classifier_free_guidance:
single_negative_image_embeds, single_image_embeds = single_image_embeds.chunk(2)
# 将负图像嵌入添加到负图像嵌入列表
negative_image_embeds.append(single_negative_image_embeds)
# 将图像嵌入添加到图像嵌入列表
image_embeds.append(single_image_embeds)
# 初始化一个空列表用于存储适配器图像嵌入
ip_adapter_image_embeds = []
# 遍历图像嵌入及其索引
for i, single_image_embeds in enumerate(image_embeds):
# 根据每个提示的图像数量扩展单张图像嵌入
single_image_embeds = torch.cat([single_image_embeds] * num_images_per_prompt, dim=0)
# 如果启用了分类器自由引导,扩展负图像嵌入并与图像嵌入连接
if do_classifier_free_guidance:
single_negative_image_embeds = torch.cat([negative_image_embeds[i]] * num_images_per_prompt, dim=0)
single_image_embeds = torch.cat([single_negative_image_embeds, single_image_embeds], dim=0)
# 将图像嵌入移动到指定设备
single_image_embeds = single_image_embeds.to(device=device)
# 将处理后的图像嵌入添加到适配器图像嵌入列表
ip_adapter_image_embeds.append(single_image_embeds)
# 返回最终的适配器图像嵌入列表
return ip_adapter_image_embeds
# 从 diffusers.pipelines.text_to_video_synthesis/pipeline_text_to_video_synth.TextToVideoSDPipeline.decode_latents 复制的方法
def decode_latents(self, latents):
# 根据 VAE 的缩放因子调整潜在向量
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)
# 解码潜在向量以生成图像
image = self.vae.decode(latents).sample
# 将解码后的图像调整为视频格式
video = image[None, :].reshape((batch_size, num_frames, -1) + image.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 论文中的 η: https://arxiv.org/abs/2010.02502
# 应该在 [0, 1] 之间
# 检查调度器的 step 方法的参数中是否接受 eta
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 初始化一个空字典,用于存储额外的步骤参数
extra_step_kwargs = {}
# 如果调度器接受 eta,则将其添加到字典中
if accepts_eta:
extra_step_kwargs["eta"] = eta
# 检查调度器的 step 方法的参数中是否接受 generator
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
# 如果调度器接受 generator,则将其添加到字典中
if accepts_generator:
extra_step_kwargs["generator"] = generator
# 返回准备好的额外步骤参数字典
return extra_step_kwargs
def check_inputs(
self,
prompt,
prompt_2,
height,
width,
negative_prompt=None,
negative_prompt_2=None,
prompt_embeds=None,
negative_prompt_embeds=None,
pooled_prompt_embeds=None,
negative_pooled_prompt_embeds=None,
callback_on_step_end_tensor_inputs=None,
# 从 diffusers.pipelines.text_to_video_synthesis.pipeline_text_to_video_synth.TextToVideoSDPipeline.prepare_latents 拷贝而来
def prepare_latents(
self, batch_size, num_channels_latents, num_frames, height, width, dtype, device, generator, latents=None
):
# 计算生成的潜在张量的形状
shape = (
batch_size,
num_channels_latents,
num_frames,
height // self.vae_scale_factor,
width // self.vae_scale_factor,
)
# 如果生成器是列表且长度与批量大小不匹配,则引发值错误
if isinstance(generator, list) and len(generator) != batch_size:
raise ValueError(
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
# 如果没有提供潜在张量,则根据指定形状生成随机张量
if latents is None:
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
else:
# 如果提供了潜在张量,则将其移动到指定设备
latents = latents.to(device)
# 根据调度器所需的标准差缩放初始噪声
latents = latents * self.scheduler.init_noise_sigma
# 返回缩放后的潜在张量
return latents
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
)
# 获取 UNet 模型中线性层的输入特征维度
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
def upcast_vae(self):
# 获取 VAE 的数据类型
dtype = self.vae.dtype
# 将 VAE 转换为 float32 类型
self.vae.to(dtype=torch.float32)
# 检查 VAE 解码器中是否使用了指定的处理器
use_torch_2_0_or_xformers = isinstance(
self.vae.decoder.mid_block.attentions[0].processor,
(
AttnProcessor2_0,
XFormersAttnProcessor,
FusedAttnProcessor2_0,
),
)
# 如果使用了 xformers 或 torch_2_0,则将相关层转换为相应数据类型以节省内存
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 导入的函数
def get_guidance_scale_embedding(
self, w: torch.Tensor, embedding_dim: int = 512, dtype: torch.dtype = torch.float32
) -> torch.Tensor:
"""
参见 GitHub 链接,获取引导尺度嵌入向量
参数:
w (`torch.Tensor`):
用于生成嵌入向量的引导尺度。
embedding_dim (`int`, *可选*, 默认值为 512):
要生成的嵌入维度。
dtype (`torch.dtype`, *可选*, 默认值为 `torch.float32`):
生成嵌入的数值类型。
返回:
`torch.Tensor`: 形状为 `(len(w), embedding_dim)` 的嵌入向量。
"""
# 确保输入张量是 1D 的
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):
# 返回私有变量 _guidance_scale 的值
return self._guidance_scale
# 属性装饰器,定义获取指导重标定的属性
@property
def guidance_rescale(self):
# 返回私有变量 _guidance_rescale 的值
return self._guidance_rescale
# 属性装饰器,定义获取剪辑跳过的属性
@property
def clip_skip(self):
# 返回私有变量 _clip_skip 的值
return self._clip_skip
# 定义无分类器自由指导的属性,参照 Imagen 论文中的指导比例
@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):
# 返回私有变量 _cross_attention_kwargs 的值
return self._cross_attention_kwargs
# 属性装饰器,定义获取去噪结束的属性
@property
def denoising_end(self):
# 返回私有变量 _denoising_end 的值
return self._denoising_end
# 属性装饰器,定义获取时间步数的属性
@property
def num_timesteps(self):
# 返回私有变量 _num_timesteps 的值
return self._num_timesteps
# 属性装饰器,定义获取中断状态的属性
@property
def interrupt(self):
# 返回私有变量 _interrupt 的值
return self._interrupt
# 禁用梯度计算的装饰器,减少内存使用
@torch.no_grad()
# 用于替换示例文档字符串的装饰器
@replace_example_docstring(EXAMPLE_DOC_STRING)
# 定义可调用的方法,支持多种参数
def __call__(
# 提示文本,支持字符串或字符串列表
prompt: Union[str, List[str]] = None,
# 第二个提示文本,支持字符串或字符串列表
prompt_2: Optional[Union[str, List[str]]] = None,
# 视频帧数量,默认为 16
num_frames: int = 16,
# 图像高度,默认为 None
height: Optional[int] = None,
# 图像宽度,默认为 None
width: Optional[int] = None,
# 推理步骤数量,默认为 50
num_inference_steps: int = 50,
# 自定义时间步列表,默认为 None
timesteps: List[int] = None,
# 噪声参数列表,默认为 None
sigmas: List[float] = None,
# 去噪结束时间,默认为 None
denoising_end: Optional[float] = None,
# 指导比例,默认为 5.0
guidance_scale: float = 5.0,
# 负面提示文本,支持字符串或字符串列表
negative_prompt: Optional[Union[str, List[str]]] = None,
# 第二个负面提示文本,支持字符串或字符串列表
negative_prompt_2: Optional[Union[str, List[str]]] = None,
# 每个提示生成视频的数量,默认为 1
num_videos_per_prompt: Optional[int] = 1,
# 噪声的 eta 值,默认为 0.0
eta: float = 0.0,
# 随机数生成器,支持单个或多个生成器
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
# 潜在空间张量,默认为 None
latents: Optional[torch.Tensor] = None,
# 提示嵌入,默认为 None
prompt_embeds: Optional[torch.Tensor] = None,
# 负面提示嵌入,默认为 None
negative_prompt_embeds: Optional[torch.Tensor] = None,
# 池化的提示嵌入,默认为 None
pooled_prompt_embeds: Optional[torch.Tensor] = None,
# 负面池化的提示嵌入,默认为 None
negative_pooled_prompt_embeds: Optional[torch.Tensor] = None,
# 输入适配器图像,默认为 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
original_size: Optional[Tuple[int, int]] = None,
# 图像裁剪左上角坐标,默认为 (0, 0)
crops_coords_top_left: Tuple[int, int] = (0, 0),
# 目标图像尺寸,默认为 None
target_size: Optional[Tuple[int, int]] = None,
# 负面原始图像尺寸,默认为 None
negative_original_size: Optional[Tuple[int, int]] = None,
# 负面裁剪左上角坐标,默认为 (0, 0)
negative_crops_coords_top_left: Tuple[int, int] = (0, 0),
# 负面目标图像尺寸,默认为 None
negative_target_size: Optional[Tuple[int, int]] = 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"],