diffusers-源码解析-十七-

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"],
posted @ 2024-10-22 12:36  绝不原创的飞龙  阅读(36)  评论(0编辑  收藏  举报