diffusers-源码解析-三十六-

diffusers 源码解析(三十六)

.\diffusers\pipelines\latte\__init__.py

# 导入类型检查模块
from typing import TYPE_CHECKING

# 从相对路径导入多个实用工具函数和常量
from ...utils import (
    DIFFUSERS_SLOW_IMPORT,  # 用于判断是否为慢导入
    OptionalDependencyNotAvailable,  # 表示可选依赖不可用的异常
    _LazyModule,  # 懒加载模块的类
    get_objects_from_module,  # 从模块中获取对象的函数
    is_torch_available,  # 检查 PyTorch 是否可用的函数
    is_transformers_available,  # 检查 Transformers 是否可用的函数
)

# 存放空对象的字典
_dummy_objects = {}
# 存放导入结构的字典
_import_structure = {}

# 尝试检查是否可以使用 transformers 和 torch
try:
    if not (is_transformers_available() and is_torch_available()):  # 检查两个库的可用性
        raise OptionalDependencyNotAvailable()  # 如果不可用,抛出异常
except OptionalDependencyNotAvailable:  # 捕获可选依赖不可用的异常
    from ...utils import dummy_torch_and_transformers_objects  # noqa F403  # 导入虚拟对象以避免错误

    # 更新 _dummy_objects 字典,填充虚拟对象
    _dummy_objects.update(get_objects_from_module(dummy_torch_and_transformers_objects))
else:
    # 如果可用,将 "pipeline_latte" 加入导入结构
    _import_structure["pipeline_latte"] = ["LattePipeline"]

# 检查是否进行类型检查或慢导入
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
    try:
        if not (is_transformers_available() and is_torch_available()):  # 再次检查库的可用性
            raise OptionalDependencyNotAvailable()  # 如果不可用,抛出异常

    except OptionalDependencyNotAvailable:  # 捕获可选依赖不可用的异常
        from ...utils.dummy_torch_and_transformers_objects import *  # 导入虚拟对象以避免错误
    else:
        # 如果可用,导入 LattePipeline
        from .pipeline_latte import LattePipeline

else:
    import sys  # 导入系统模块

    # 用懒加载模块替换当前模块
    sys.modules[__name__] = _LazyModule(
        __name__,  # 当前模块名称
        globals()["__file__"],  # 当前模块文件路径
        _import_structure,  # 导入结构
        module_spec=__spec__,  # 模块规范
    )

    # 将 _dummy_objects 中的虚拟对象添加到当前模块中
    for name, value in _dummy_objects.items():
        setattr(sys.modules[__name__], name, value)  # 设置模块属性

.\diffusers\pipelines\ledits_pp\pipeline_leditspp_stable_diffusion.py

# 导入 inspect 模块,用于获取对象的信息
import inspect
# 导入 math 模块,提供数学函数
import math
# 从 itertools 导入 repeat,用于生成重复元素的迭代器
from itertools import repeat
# 从 typing 导入常用类型,用于类型注解
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

# 导入 torch 库,用于深度学习
import torch
# 从 torch.nn.functional 导入 F,提供各种神经网络功能
import torch.nn.functional as F
# 从 packaging 导入 version,用于版本比较
from packaging import version
# 从 transformers 导入 CLIP 相关组件,用于图像和文本处理
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer

# 从上级目录导入 FrozenDict,用于不可变字典
from ...configuration_utils import FrozenDict
# 从上级目录导入图像处理相关类
from ...image_processor import PipelineImageInput, VaeImageProcessor
# 从上级目录导入加载器混合类
from ...loaders import FromSingleFileMixin, IPAdapterMixin, StableDiffusionLoraLoaderMixin, TextualInversionLoaderMixin
# 从上级目录导入模型
from ...models import AutoencoderKL, UNet2DConditionModel
# 从注意力处理器模块导入 Attention 和 AttnProcessor
from ...models.attention_processor import Attention, AttnProcessor
# 从 lora 模块导入调整 LORA 比例的函数
from ...models.lora import adjust_lora_scale_text_encoder
# 从稳定扩散管道导入安全检查器
from ...pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker
# 从调度器导入 DDIM 和 DPMSolver 多步调度器
from ...schedulers import DDIMScheduler, DPMSolverMultistepScheduler
# 从工具模块导入各种工具函数
from ...utils import (
    USE_PEFT_BACKEND,  # 用于是否使用 PEFT 后端的标志
    deprecate,  # 用于标记弃用的函数
    logging,  # 用于日志记录
    replace_example_docstring,  # 用于替换示例文档字符串的函数
    scale_lora_layers,  # 用于缩放 LORA 层的函数
    unscale_lora_layers,  # 用于反缩放 LORA 层的函数
)
# 从 torch_utils 导入随机张量生成函数
from ...utils.torch_utils import randn_tensor
# 从管道工具模块导入扩散管道类
from ..pipeline_utils import DiffusionPipeline
# 从管道输出模块导入管道输出类
from .pipeline_output import LEditsPPDiffusionPipelineOutput, LEditsPPInversionPipelineOutput

# 创建日志记录器,使用当前模块名称
logger = logging.get_logger(__name__)  # pylint: disable=invalid-name

# 示例文档字符串,提供使用示例
EXAMPLE_DOC_STRING = """
    Examples:
        ```py
        >>> import PIL  # 导入 PIL 库,用于图像处理
        >>> import requests  # 导入 requests 库,用于 HTTP 请求
        >>> import torch  # 导入 torch 库,用于深度学习
        >>> from io import BytesIO  # 从 io 导入 BytesIO,用于字节流处理

        >>> from diffusers import LEditsPPPipelineStableDiffusion  # 从 diffusers 导入 LEditsPPPipelineStableDiffusion 类
        >>> from diffusers.utils import load_image  # 从 diffusers.utils 导入 load_image 函数

        >>> pipe = LEditsPPPipelineStableDiffusion.from_pretrained(  # 从预训练模型创建管道
        ...     "runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16  # 指定模型路径和数据类型
        ... )
        >>> pipe = pipe.to("cuda")  # 将管道移动到 GPU

        >>> img_url = "https://www.aiml.informatik.tu-darmstadt.de/people/mbrack/cherry_blossom.png"  # 图像的 URL
        >>> image = load_image(img_url).convert("RGB")  # 加载图像并转换为 RGB 格式

        >>> _ = pipe.invert(image=image, num_inversion_steps=50, skip=0.1)  # 对图像进行反演处理

        >>> edited_image = pipe(  # 使用管道进行图像编辑
        ...     editing_prompt=["cherry blossom"], edit_guidance_scale=10.0, edit_threshold=0.75  # 设置编辑提示和参数
        ... ).images[0]  # 获取编辑后的图像
        ```py
"""

# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionAttendAndExcitePipeline 修改的类
class LeditsAttentionStore:
    @staticmethod
    # 静态方法,获取一个空的注意力存储
    def get_empty_store():
        # 返回一个字典,包含不同层次的注意力存储列表
        return {"down_cross": [], "mid_cross": [], "up_cross": [], "down_self": [], "mid_self": [], "up_self": []}
    # 定义一个可调用的方法,接收注意力权重和其他参数
    def __call__(self, attn, is_cross: bool, place_in_unet: str, editing_prompts, PnP=False):
        # 注意力权重的形状为:批大小 * 头大小, 序列长度查询, 序列长度键
        if attn.shape[1] <= self.max_size:  # 检查序列长度是否小于或等于最大大小
            bs = 1 + int(PnP) + editing_prompts  # 计算批次大小
            skip = 2 if PnP else 1  # 确定跳过的步骤:如果是 PnP,跳过 2,否则跳过 1
            # 将注意力权重拆分为指定批大小的张量,并调整维度顺序
            attn = torch.stack(attn.split(self.batch_size)).permute(1, 0, 2, 3)
            source_batch_size = int(attn.shape[1] // bs)  # 计算源批次大小
            # 调用 forward 方法,传入处理后的注意力权重
            self.forward(attn[:, skip * source_batch_size :], is_cross, place_in_unet)

    # 定义 forward 方法,用于处理注意力权重
    def forward(self, attn, is_cross: bool, place_in_unet: str):
        # 生成键值,用于存储不同位置的注意力
        key = f"{place_in_unet}_{'cross' if is_cross else 'self'}"
        # 将当前注意力权重添加到对应的存储列表中
        self.step_store[key].append(attn)

    # 定义在步骤之间的操作,可以选择存储当前步骤
    def between_steps(self, store_step=True):
        if store_step:  # 如果需要存储当前步骤
            if self.average:  # 如果启用平均
                if len(self.attention_store) == 0:  # 如果注意力存储为空
                    self.attention_store = self.step_store  # 直接赋值
                else:
                    # 对现有注意力存储进行更新
                    for key in self.attention_store:
                        for i in range(len(self.attention_store[key])):
                            self.attention_store[key][i] += self.step_store[key][i]
            else:  # 如果不启用平均
                if len(self.attention_store) == 0:  # 如果注意力存储为空
                    self.attention_store = [self.step_store]  # 作为第一个存储项
                else:
                    self.attention_store.append(self.step_store)  # 添加新的存储项

            self.cur_step += 1  # 更新当前步骤计数
        # 重置当前步骤的存储
        self.step_store = self.get_empty_store()

    # 定义获取特定步骤注意力的方法
    def get_attention(self, step: int):
        if self.average:  # 如果启用平均
            # 计算平均注意力,按当前步骤进行归一化
            attention = {
                key: [item / self.cur_step for item in self.attention_store[key]] for key in self.attention_store
            }
        else:  # 否则,确保提供了步骤
            assert step is not None  # 断言步骤不为 None
            attention = self.attention_store[step]  # 获取指定步骤的注意力
        return attention  # 返回注意力

    # 定义聚合注意力的方法,处理多个参数
    def aggregate_attention(
        self, attention_maps, prompts, res: Union[int, Tuple[int]], from_where: List[str], is_cross: bool, select: int
    ):
        out = [[] for x in range(self.batch_size)]  # 初始化输出列表
        if isinstance(res, int):  # 如果分辨率为整数
            num_pixels = res**2  # 计算像素数量
            resolution = (res, res)  # 设置分辨率
        else:  # 如果分辨率为元组
            num_pixels = res[0] * res[1]  # 计算像素数量
            resolution = res[:2]  # 设置分辨率

        # 遍历指定来源,提取注意力图
        for location in from_where:
            for bs_item in attention_maps[f"{location}_{'cross' if is_cross else 'self'}"]:
                for batch, item in enumerate(bs_item):
                    if item.shape[1] == num_pixels:  # 检查当前项的形状是否匹配
                        # 重塑注意力图并选择指定项
                        cross_maps = item.reshape(len(prompts), -1, *resolution, item.shape[-1])[select]
                        out[batch].append(cross_maps)  # 将提取的映射添加到输出

        # 合并输出,计算每个批次的平均值
        out = torch.stack([torch.cat(x, dim=0) for x in out])  # 在第一个维度上堆叠
        out = out.sum(1) / out.shape[1]  # 在头维度上取平均
        return out  # 返回聚合后的输出
    # 初始化方法,用于创建类的实例,接受多个参数
        def __init__(self, average: bool, batch_size=1, max_resolution=16, max_size: int = None):
            # 获取一个空的存储结构,用于保存步骤数据
            self.step_store = self.get_empty_store()
            # 初始化注意力存储列表
            self.attention_store = []
            # 当前步骤计数器初始化为 0
            self.cur_step = 0
            # 设置是否计算平均值的标志
            self.average = average
            # 设置批处理大小,默认值为 1
            self.batch_size = batch_size
            # 如果没有指定最大大小,则计算为最大分辨率的平方
            if max_size is None:
                self.max_size = max_resolution**2
            # 如果指定了最大大小且没有指定最大分辨率,则使用指定的最大大小
            elif max_size is not None and max_resolution is None:
                self.max_size = max_size
            # 如果同时指定了最大分辨率和最大大小,则抛出错误
            else:
                raise ValueError("Only allowed to set one of max_resolution or max_size")
# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionAttendAndExcitePipeline.GaussianSmoothing 修改而来
class LeditsGaussianSmoothing:
    # 初始化函数,接收设备参数
    def __init__(self, device):
        # 定义高斯核的大小
        kernel_size = [3, 3]
        # 定义高斯核的标准差
        sigma = [0.5, 0.5]

        # 高斯核是每个维度高斯函数的乘积
        kernel = 1
        # 创建网格以便于计算高斯核
        meshgrids = torch.meshgrid([torch.arange(size, dtype=torch.float32) for size in kernel_size])
        # 遍历每个维度的大小、标准差和网格
        for size, std, mgrid in zip(kernel_size, sigma, meshgrids):
            # 计算高斯核的均值
            mean = (size - 1) / 2
            # 计算高斯核值
            kernel *= 1 / (std * math.sqrt(2 * math.pi)) * torch.exp(-(((mgrid - mean) / (2 * std)) ** 2))

        # 确保高斯核的值之和为1
        kernel = kernel / torch.sum(kernel)

        # 将高斯核重塑为深度卷积权重
        kernel = kernel.view(1, 1, *kernel.size())
        # 重复高斯核以适应卷积层的形状
        kernel = kernel.repeat(1, *[1] * (kernel.dim() - 1))

        # 将权重移动到指定设备上
        self.weight = kernel.to(device)

    # 调用函数,用于应用高斯滤波
    def __call__(self, input):
        """
        参数:
        对输入应用高斯滤波。
            input (torch.Tensor): 需要应用高斯滤波的输入。
        返回:
            filtered (torch.Tensor): 经过滤波的输出。
        """
        # 使用卷积操作应用高斯滤波
        return F.conv2d(input, weight=self.weight.to(input.dtype))


class LEDITSCrossAttnProcessor:
    # 初始化函数,接收多个参数以设置注意力处理器
    def __init__(self, attention_store, place_in_unet, pnp, editing_prompts):
        # 存储注意力的变量
        self.attnstore = attention_store
        # 设置在 UNet 中的位置
        self.place_in_unet = place_in_unet
        # 存储编辑提示
        self.editing_prompts = editing_prompts
        # 存储 PnP 相关的变量
        self.pnp = pnp

    # 调用函数,处理注意力和隐藏状态
    def __call__(
        self,
        attn: Attention,
        hidden_states,
        encoder_hidden_states,
        attention_mask=None,
        temb=None,
    # 处理输入,确定批量大小和序列长度
        ):
            batch_size, sequence_length, _ = (
                # 如果没有编码器隐藏状态,则使用隐藏状态形状,否则使用编码器隐藏状态形状
                hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
            )
            # 准备注意力掩码,以便后续的注意力计算
            attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
    
            # 将隐藏状态转换为查询向量
            query = attn.to_q(hidden_states)
    
            # 如果没有编码器隐藏状态,使用隐藏状态;否则,如果需要规范化,则规范化编码器隐藏状态
            if encoder_hidden_states is None:
                encoder_hidden_states = hidden_states
            elif attn.norm_cross:
                encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
    
            # 将编码器隐藏状态转换为键向量
            key = attn.to_k(encoder_hidden_states)
            # 将编码器隐藏状态转换为值向量
            value = attn.to_v(encoder_hidden_states)
    
            # 将查询向量转换为批处理维度
            query = attn.head_to_batch_dim(query)
            # 将键向量转换为批处理维度
            key = attn.head_to_batch_dim(key)
            # 将值向量转换为批处理维度
            value = attn.head_to_batch_dim(value)
    
            # 计算注意力得分
            attention_probs = attn.get_attention_scores(query, key, attention_mask)
            # 存储注意力得分,标记为交叉注意力
            self.attnstore(
                attention_probs,
                is_cross=True,
                place_in_unet=self.place_in_unet,
                editing_prompts=self.editing_prompts,
                PnP=self.pnp,
            )
    
            # 通过值向量和注意力得分计算新的隐藏状态
            hidden_states = torch.bmm(attention_probs, value)
            # 将隐藏状态转换回头部维度
            hidden_states = attn.batch_to_head_dim(hidden_states)
    
            # 进行线性投影
            hidden_states = attn.to_out[0](hidden_states)
            # 进行 dropout 操作
            hidden_states = attn.to_out[1](hidden_states)
    
            # 重新缩放输出
            hidden_states = hidden_states / attn.rescale_output_factor
            # 返回最终的隐藏状态
            return hidden_states
# 从 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


# 定义 LEditsPPPipelineStableDiffusion 类,继承多个混合类
class LEditsPPPipelineStableDiffusion(
    DiffusionPipeline, TextualInversionLoaderMixin, StableDiffusionLoraLoaderMixin, IPAdapterMixin, FromSingleFileMixin
):
    """
    使用 LEDits++ 和 Stable Diffusion 进行文本图像编辑的管道。

    此模型继承自 [`DiffusionPipeline`],并建立在 [`StableDiffusionPipeline`] 之上。查看超类
    文档以了解所有管道实现的通用方法(下载、保存、在特定设备上运行等)。
    # 文档字符串,定义类的参数及其描述
    Args:
        vae ([`AutoencoderKL`]):
            # 变分自编码器模型,用于将图像编码和解码为潜在表示
            Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations.
        text_encoder ([`~transformers.CLIPTextModel`]):
            # 冻结的文本编码器,Stable Diffusion 使用 CLIP 的文本部分
            Frozen text-encoder. Stable Diffusion uses the text portion of
            # 指定的 CLIP 模型变体
            [CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically
            # 指定的 CLIP-ViT 大模型变体
            [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant.
        tokenizer ([`~transformers.CLIPTokenizer`]):
            # CLIPTokenizer 类的分词器,用于文本处理
            Tokenizer of class
            [CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer).
        unet ([`UNet2DConditionModel`]): 
            # 条件 U-Net 架构,用于对编码的图像潜在表示进行去噪
            Conditional U-Net architecture to denoise the encoded image latents.
        scheduler ([`DPMSolverMultistepScheduler`] or [`DDIMScheduler`]):
            # 调度器,用于与 `unet` 结合去噪编码的图像潜在表示
            A scheduler to be used in combination with `unet` to denoise the encoded image latens. Can be one of
            # 允许的调度器类型,若传入其他类型则默认为 DPMSolverMultistepScheduler
            [`DPMSolverMultistepScheduler`] or [`DDIMScheduler`]. If any other scheduler is passed it will
            automatically be set to [`DPMSolverMultistepScheduler`].
        safety_checker ([`StableDiffusionSafetyChecker`]):
            # 分类模块,评估生成的图像是否可能被视为冒犯或有害
            Classification module that estimates whether generated images could be considered offensive or harmful.
            # 有关详细信息,请参考模型卡
            Please, refer to the [model card](https://huggingface.co/CompVis/stable-diffusion-v1-4) for details.
        feature_extractor ([`~transformers.CLIPImageProcessor`]):
            # 从生成的图像中提取特征,以作为 `safety_checker` 的输入
            Model that extracts features from generated images to be used as inputs for the `safety_checker`.
    """

    # 定义模型在 CPU 卸载时的顺序
    model_cpu_offload_seq = "text_encoder->unet->vae"
    # 定义在 CPU 卸载时排除的组件
    _exclude_from_cpu_offload = ["safety_checker"]
    # 定义回调张量输入
    _callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"]
    # 定义可选组件
    _optional_components = ["safety_checker", "feature_extractor", "image_encoder"]

    # 初始化方法,定义类的构造函数
    def __init__(
        self,
        # 变分自编码器模型实例
        vae: AutoencoderKL,
        # 文本编码器实例
        text_encoder: CLIPTextModel,
        # 分词器实例
        tokenizer: CLIPTokenizer,
        # U-Net 模型实例
        unet: UNet2DConditionModel,
        # 调度器实例
        scheduler: Union[DDIMScheduler, DPMSolverMultistepScheduler],
        # 安全检查器实例
        safety_checker: StableDiffusionSafetyChecker,
        # 特征提取器实例
        feature_extractor: CLIPImageProcessor,
        # 指示是否需要安全检查器的布尔值,默认为 True
        requires_safety_checker: bool = True,
    # 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.run_safety_checker 复制的内容
    # 运行安全检查器,对输入图像进行安全性检测
        def run_safety_checker(self, image, device, dtype):
            # 检查安全检查器是否已初始化
            if self.safety_checker is None:
                # 若未初始化,NSFW 概念标志设置为 None
                has_nsfw_concept = None
            else:
                # 检查输入图像是否为张量
                if torch.is_tensor(image):
                    # 如果是张量,则处理图像为 PIL 格式
                    feature_extractor_input = self.image_processor.postprocess(image, output_type="pil")
                else:
                    # 如果不是张量,则将 NumPy 数组转换为 PIL 图像
                    feature_extractor_input = self.image_processor.numpy_to_pil(image)
                # 提取图像特征,返回张量并移动到指定设备
                safety_checker_input = self.feature_extractor(feature_extractor_input, return_tensors="pt").to(device)
                # 执行安全检查,返回处理后的图像和 NSFW 概念标志
                image, has_nsfw_concept = self.safety_checker(
                    images=image, clip_input=safety_checker_input.pixel_values.to(dtype)
                )
            # 返回处理后的图像和 NSFW 概念标志
            return image, has_nsfw_concept
    
        # 从 StableDiffusionPipeline 复制的解码潜变量方法
        def decode_latents(self, latents):
            # 提示解码方法已弃用,未来版本将移除
            deprecation_message = "The decode_latents method is deprecated and will be removed in 1.0.0. Please use VaeImageProcessor.postprocess(...) instead"
            deprecate("decode_latents", "1.0.0", deprecation_message, standard_warn=False)
    
            # 根据 VAE 配置缩放潜变量
            latents = 1 / self.vae.config.scaling_factor * latents
            # 解码潜变量,获取图像
            image = self.vae.decode(latents, return_dict=False)[0]
            # 将图像数据归一化并限制在 [0, 1] 范围内
            image = (image / 2 + 0.5).clamp(0, 1)
            # 将图像转换为 float32 格式,以兼容 bfloat16,且不会造成显著开销
            image = image.cpu().permute(0, 2, 3, 1).float().numpy()
            # 返回处理后的图像
            return image
    
        # 从 StableDiffusionPipeline 复制的准备额外步骤参数的方法
        def prepare_extra_step_kwargs(self, eta, generator=None):
            # 准备调度器步骤的额外参数,不同调度器的参数签名可能不同
            # 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
    
        # 从 StableDiffusionPipeline 复制的输入检查方法
        def check_inputs(
            self,
            # 定义检查的输入参数,负提示和编辑提示嵌入等
            negative_prompt=None,
            editing_prompt_embeddings=None,
            negative_prompt_embeds=None,
            callback_on_step_end_tensor_inputs=None,
    ):
        # 检查回调输入是否为空,并确保所有输入都在回调张量输入列表中
        if callback_on_step_end_tensor_inputs is not None and not all(
            k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
        ):
            # 如果有输入不在回调张量输入列表中,抛出值错误
            raise ValueError(
                f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
            )
        # 检查负提示和负提示嵌入是否同时存在
        if negative_prompt is not None and negative_prompt_embeds is not None:
            # 如果同时存在,抛出值错误
            raise ValueError(
                f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
                f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
            )

        # 检查编辑提示嵌入和负提示嵌入是否同时存在
        if editing_prompt_embeddings is not None and negative_prompt_embeds is not None:
            # 检查两者形状是否相同
            if editing_prompt_embeddings.shape != negative_prompt_embeds.shape:
                # 如果形状不匹配,抛出值错误
                raise ValueError(
                    "`editing_prompt_embeddings` and `negative_prompt_embeds` must have the same shape when passed directly, but"
                    f" got: `editing_prompt_embeddings` {editing_prompt_embeddings.shape} != `negative_prompt_embeds`"
                    f" {negative_prompt_embeds.shape}."
                )

    # 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents 修改而来
    def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, latents):
        # 计算预期的张量形状
        # shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor)

        # 如果输入的 latents 形状与预期形状不匹配,抛出值错误
        # if latents.shape != shape:
        #    raise ValueError(f"Unexpected latents shape, got {latents.shape}, expected {shape}")

        # 将输入的 latents 张量移动到指定设备
        latents = latents.to(device)

        # 根据调度器所需的标准差缩放初始噪声
        latents = latents * self.scheduler.init_noise_sigma
        # 返回处理后的 latents
        return latents

    def prepare_unet(self, attention_store, PnP: bool = False):
        # 创建一个空字典用于存储注意力处理器
        attn_procs = {}
        # 遍历 UNet 中的注意力处理器键
        for name in self.unet.attn_processors.keys():
            # 根据名称前缀确定其在 UNet 中的位置
            if name.startswith("mid_block"):
                place_in_unet = "mid"
            elif name.startswith("up_blocks"):
                place_in_unet = "up"
            elif name.startswith("down_blocks"):
                place_in_unet = "down"
            else:
                continue

            # 根据名称选择合适的注意力处理器
            if "attn2" in name and place_in_unet != "mid":
                attn_procs[name] = LEDITSCrossAttnProcessor(
                    attention_store=attention_store,
                    place_in_unet=place_in_unet,
                    pnp=PnP,
                    editing_prompts=self.enabled_editing_prompts,
                )
            else:
                attn_procs[name] = AttnProcessor()

        # 将注意力处理器设置到 UNet 中
        self.unet.set_attn_processor(attn_procs)
    # 定义一个编码提示的方法,接收多个参数
    def encode_prompt(
        self,  # 当前对象的引用
        device,  # 设备类型,例如 CPU 或 GPU
        num_images_per_prompt,  # 每个提示生成的图像数量
        enable_edit_guidance,  # 是否启用编辑引导
        negative_prompt=None,  # 可选的负面提示
        editing_prompt=None,  # 可选的编辑提示
        negative_prompt_embeds: Optional[torch.Tensor] = None,  # 可选的负面提示嵌入
        editing_prompt_embeds: Optional[torch.Tensor] = None,  # 可选的编辑提示嵌入
        lora_scale: Optional[float] = None,  # 可选的 Lora 缩放因子
        clip_skip: Optional[int] = None,  # 可选的跳过的剪辑层数
    # 获取指导重缩放的属性
    @property
    def guidance_rescale(self):  
        return self._guidance_rescale  # 返回指导重缩放的值

    # 获取剪辑跳过的属性
    @property
    def clip_skip(self):  
        return self._clip_skip  # 返回剪辑跳过的值

    # 获取交叉注意力参数的属性
    @property
    def cross_attention_kwargs(self):  
        return self._cross_attention_kwargs  # 返回交叉注意力参数

    # 装饰器,指示不计算梯度
    @torch.no_grad()
    # 替换示例文档字符串的装饰器
    @replace_example_docstring(EXAMPLE_DOC_STRING)
    # 定义可调用的方法,接收多个可选参数
    def __call__(
        self,  # 方法本身的引用
        negative_prompt: Optional[Union[str, List[str]]] = None,  # 可选的负面提示
        generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,  # 可选的生成器
        output_type: Optional[str] = "pil",  # 输出类型,默认是 PIL
        return_dict: bool = True,  # 是否返回字典格式的输出
        editing_prompt: Optional[Union[str, List[str]]] = None,  # 可选的编辑提示
        editing_prompt_embeds: Optional[torch.Tensor] = None,  # 可选的编辑提示嵌入
        negative_prompt_embeds: Optional[torch.Tensor] = None,  # 可选的负面提示嵌入
        reverse_editing_direction: Optional[Union[bool, List[bool]]] = False,  # 是否反向编辑方向
        edit_guidance_scale: Optional[Union[float, List[float]]] = 5,  # 编辑引导缩放因子
        edit_warmup_steps: Optional[Union[int, List[int]]] = 0,  # 编辑热身步数
        edit_cooldown_steps: Optional[Union[int, List[int]]] = None,  # 编辑冷却步数
        edit_threshold: Optional[Union[float, List[float]]] = 0.9,  # 编辑阈值
        user_mask: Optional[torch.Tensor] = None,  # 可选的用户掩码
        sem_guidance: Optional[List[torch.Tensor]] = None,  # 可选的语义引导
        use_cross_attn_mask: bool = False,  # 是否使用交叉注意力掩码
        use_intersect_mask: bool = True,  # 是否使用交集掩码
        attn_store_steps: Optional[List[int]] = [],  # 存储注意力的步骤列表
        store_averaged_over_steps: bool = True,  # 是否在步骤上存储平均值
        cross_attention_kwargs: Optional[Dict[str, Any]] = None,  # 可选的交叉注意力参数
        guidance_rescale: float = 0.0,  # 指导重缩放的默认值
        clip_skip: Optional[int] = None,  # 可选的剪辑跳过层数
        callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,  # 可选的步骤结束回调
        callback_on_step_end_tensor_inputs: List[str] = ["latents"],  # 默认的步骤结束回调输入
        **kwargs,  # 接收其他关键字参数
    # 装饰器,指示不计算梯度
    @torch.no_grad()
    # 定义反转的方法,接收多个参数
    def invert(
        self,  # 方法本身的引用
        image: PipelineImageInput,  # 输入图像
        source_prompt: str = "",  # 源提示字符串
        source_guidance_scale: float = 3.5,  # 源指导缩放因子
        num_inversion_steps: int = 30,  # 反转步骤数量
        skip: float = 0.15,  # 跳过的比例
        generator: Optional[torch.Generator] = None,  # 可选的生成器
        cross_attention_kwargs: Optional[Dict[str, Any]] = None,  # 可选的交叉注意力参数
        clip_skip: Optional[int] = None,  # 可选的剪辑跳过层数
        height: Optional[int] = None,  # 可选的图像高度
        width: Optional[int] = None,  # 可选的图像宽度
        resize_mode: Optional[str] = "default",  # 图像调整大小的模式
        crops_coords: Optional[Tuple[int, int, int, int]] = None,  # 可选的裁剪坐标
    # 装饰器,指示不计算梯度
    @torch.no_grad()
    # 定义一个编码图像的函数,接收多个参数以处理图像
    def encode_image(self, image, dtype=None, height=None, width=None, resize_mode="default", crops_coords=None):
        # 使用图像处理器预处理输入图像,调整其高度、宽度和裁剪坐标
        image = self.image_processor.preprocess(
            image=image, height=height, width=width, resize_mode=resize_mode, crops_coords=crops_coords
        )
        # 使用图像处理器后处理图像,输出类型为 PIL 图像
        resized = self.image_processor.postprocess(image=image, output_type="pil")
    
        # 检查输入图像的最大维度是否超过默认分辨率的 1.5 倍
        if max(image.shape[-2:]) > self.vae.config["sample_size"] * 1.5:
            # 记录警告信息,提示输入图像分辨率过高可能导致输出图像有严重伪影
            logger.warning(
                "Your input images far exceed the default resolution of the underlying diffusion model. "
                "The output images may contain severe artifacts! "
                "Consider down-sampling the input using the `height` and `width` parameters"
            )
        # 将图像转换为指定的数据类型
        image = image.to(dtype)
    
        # 使用 VAE 编码器对图像进行编码,获取潜在分布的模式
        x0 = self.vae.encode(image.to(self.device)).latent_dist.mode()
        # 将编码结果转换为指定的数据类型
        x0 = x0.to(dtype)
        # 将编码结果乘以缩放因子
        x0 = self.vae.config.scaling_factor * x0
        # 返回编码后的结果和处理后的图像
        return x0, resized
# 计算 DDIM 噪声
def compute_noise_ddim(scheduler, prev_latents, latents, timestep, noise_pred, eta):
    # 1. 获取前一个时间步的值(t-1)
    prev_timestep = timestep - scheduler.config.num_train_timesteps // scheduler.num_inference_steps

    # 2. 计算 alphas 和 betas
    alpha_prod_t = scheduler.alphas_cumprod[timestep]  # 当前时间步的累积 alpha
    alpha_prod_t_prev = (
        scheduler.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else scheduler.final_alpha_cumprod
    )  # 前一个时间步的累积 alpha,若为负则使用最终 alpha

    beta_prod_t = 1 - alpha_prod_t  # 当前时间步的 beta

    # 3. 从预测噪声计算预测的原始样本,也称为“预测的 x_0”
    pred_original_sample = (latents - beta_prod_t ** (0.5) * noise_pred) / alpha_prod_t ** (0.5)

    # 4. 裁剪“预测的 x_0”
    if scheduler.config.clip_sample:
        pred_original_sample = torch.clamp(pred_original_sample, -1, 1)  # 将值限制在 -1 到 1 之间

    # 5. 计算方差:“sigma_t(η)” -> 参见公式 (16)
    variance = scheduler._get_variance(timestep, prev_timestep)  # 获取方差
    std_dev_t = eta * variance ** (0.5)  # 计算标准差

    # 6. 计算指向 x_t 的方向,参见公式 (12)
    pred_sample_direction = (1 - alpha_prod_t_prev - std_dev_t**2) ** (0.5) * noise_pred

    # 修改以返回更新后的 xtm1(避免误差累积)
    mu_xt = alpha_prod_t_prev ** (0.5) * pred_original_sample + pred_sample_direction  # 计算 mu_xt
    if variance > 0.0:
        noise = (prev_latents - mu_xt) / (variance ** (0.5) * eta)  # 计算噪声
    else:
        noise = torch.tensor([0.0]).to(latents.device)  # 方差为零时噪声设为零

    return noise, mu_xt + (eta * variance**0.5) * noise  # 返回噪声和更新后的样本


# 计算 SDE DPM PP 二阶噪声
def compute_noise_sde_dpm_pp_2nd(scheduler, prev_latents, latents, timestep, noise_pred, eta):
    def first_order_update(model_output, sample):  # 定义一阶更新
        sigma_t, sigma_s = scheduler.sigmas[scheduler.step_index + 1], scheduler.sigmas[scheduler.step_index]  # 获取当前和前一个 sigma
        alpha_t, sigma_t = scheduler._sigma_to_alpha_sigma_t(sigma_t)  # 将 sigma 转换为 alpha
        alpha_s, sigma_s = scheduler._sigma_to_alpha_sigma_t(sigma_s)  # 将前一个 sigma 转换为 alpha
        lambda_t = torch.log(alpha_t) - torch.log(sigma_t)  # 计算 lambda_t
        lambda_s = torch.log(alpha_s) - torch.log(sigma_s)  # 计算 lambda_s

        h = lambda_t - lambda_s  # 计算 h

        mu_xt = (sigma_t / sigma_s * torch.exp(-h)) * sample + (alpha_t * (1 - torch.exp(-2.0 * h))) * model_output  # 计算 mu_xt

        mu_xt = scheduler.dpm_solver_first_order_update(  # 更新 mu_xt
            model_output=model_output, sample=sample, noise=torch.zeros_like(sample)  # 将噪声设为零
        )

        sigma = sigma_t * torch.sqrt(1.0 - torch.exp(-2 * h))  # 计算 sigma
        if sigma > 0.0:
            noise = (prev_latents - mu_xt) / sigma  # 计算噪声
        else:
            noise = torch.tensor([0.0]).to(sample.device)  # 方差为零时噪声设为零

        prev_sample = mu_xt + sigma * noise  # 计算前一个样本
        return noise, prev_sample  # 返回噪声和前一个样本
    # 定义二阶更新函数,接受模型输出列表和样本
        def second_order_update(model_output_list, sample):  # timestep_list, prev_timestep, sample):
            # 获取当前和前后时刻的 sigma 值
            sigma_t, sigma_s0, sigma_s1 = (
                scheduler.sigmas[scheduler.step_index + 1],  # 当前时刻的 sigma
                scheduler.sigmas[scheduler.step_index],      # 上一时刻的 sigma
                scheduler.sigmas[scheduler.step_index - 1],  # 更早时刻的 sigma
            )
    
            # 将 sigma 转换为 alpha 和 sigma_t
            alpha_t, sigma_t = scheduler._sigma_to_alpha_sigma_t(sigma_t)
            alpha_s0, sigma_s0 = scheduler._sigma_to_alpha_sigma_t(sigma_s0)
            alpha_s1, sigma_s1 = scheduler._sigma_to_alpha_sigma_t(sigma_s1)
    
            # 计算 lambda 值
            lambda_t = torch.log(alpha_t) - torch.log(sigma_t)  # 当前时刻的 lambda
            lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0)  # 上一时刻的 lambda
            lambda_s1 = torch.log(alpha_s1) - torch.log(sigma_s1)  # 更早时刻的 lambda
    
            # 获取最后两个模型输出
            m0, m1 = model_output_list[-1], model_output_list[-2]
    
            # 计算 h 和 r0 值
            h, h_0 = lambda_t - lambda_s0, lambda_s0 - lambda_s1  # h 和 h_0
            r0 = h_0 / h  # r0 的计算
            D0, D1 = m0, (1.0 / r0) * (m0 - m1)  # D0 和 D1 的计算
    
            # 计算 mu_xt
            mu_xt = (
                (sigma_t / sigma_s0 * torch.exp(-h)) * sample  # 根据样本和 sigma 计算
                + (alpha_t * (1 - torch.exp(-2.0 * h))) * D0  # 加上 D0 的贡献
                + 0.5 * (alpha_t * (1 - torch.exp(-2.0 * h))) * D1  # 加上 D1 的一半贡献
            )
    
            # 计算 sigma
            sigma = sigma_t * torch.sqrt(1.0 - torch.exp(-2 * h))  # 最终的 sigma 计算
            # 根据 sigma 计算噪声
            if sigma > 0.0:
                noise = (prev_latents - mu_xt) / sigma  # 正常情况下计算噪声
            else:
                noise = torch.tensor([0.0]).to(sample.device)  # sigma 为零时,噪声为零
    
            # 计算前一个样本
            prev_sample = mu_xt + sigma * noise  # 最终样本的计算
    
            return noise, prev_sample  # 返回噪声和前一个样本
    
        # 初始化 step_index,如果未定义的话
        if scheduler.step_index is None:
            scheduler._init_step_index(timestep)  # 初始化步进索引
    
        # 将模型输出转换为合适格式
        model_output = scheduler.convert_model_output(model_output=noise_pred, sample=latents)
        # 更新模型输出列表
        for i in range(scheduler.config.solver_order - 1):
            scheduler.model_outputs[i] = scheduler.model_outputs[i + 1]  # 向前移动输出
        scheduler.model_outputs[-1] = model_output  # 保存当前模型输出
    
        # 根据 lower_order_nums 选择更新方法
        if scheduler.lower_order_nums < 1:
            noise, prev_sample = first_order_update(model_output, latents)  # 一阶更新
        else:
            noise, prev_sample = second_order_update(scheduler.model_outputs, latents)  # 二阶更新
    
        # 更新 lower_order_nums
        if scheduler.lower_order_nums < scheduler.config.solver_order:
            scheduler.lower_order_nums += 1  # 增加 lower_order_nums
    
        # 完成后增加步进索引
        scheduler._step_index += 1  # 增加步进索引
    
        return noise, prev_sample  # 返回噪声和前一个样本
# 定义一个计算噪声的函数,接收调度器和可变参数
def compute_noise(scheduler, *args):
    # 检查调度器是否为 DDIMScheduler 类型
    if isinstance(scheduler, DDIMScheduler):
        # 调用对应的函数计算 DDIM 噪声
        return compute_noise_ddim(scheduler, *args)
    # 检查调度器是否为 DPMSolverMultistepScheduler,并验证其配置
    elif (
        isinstance(scheduler, DPMSolverMultistepScheduler)
        and scheduler.config.algorithm_type == "sde-dpmsolver++"
        and scheduler.config.solver_order == 2
    ):
        # 调用对应的函数计算 SDE-DPM 噪声(2阶)
        return compute_noise_sde_dpm_pp_2nd(scheduler, *args)
    else:
        # 如果不支持的调度器类型,抛出未实现的错误
        raise NotImplementedError

.\diffusers\pipelines\ledits_pp\pipeline_leditspp_stable_diffusion_xl.py

# 版权声明,表示此文件的版权信息及归属
# Copyright 2023 The HuggingFace Team. All rights reserved.
#
# 许可条款,声明该文件根据 Apache License 2.0 进行许可
# Licensed under the Apache License, Version 2.0 (the "License");
# 该文件的使用需遵循许可证的规定
# you may not use this file except in compliance with the License.
# 可以在以下网址获取许可证
# you may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# 除非在适用法律或书面协议中另有规定,软件按“原样”提供
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 不提供任何形式的明示或暗示的担保
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 请参阅许可证以了解有关权限和限制的具体信息
# See the License for the specific language governing permissions and
# limitations under the License.

import inspect  # 导入 inspect 模块,用于获取对象的内部信息
import math  # 导入 math 模块,提供数学函数
from typing import Any, Callable, Dict, List, Optional, Tuple, Union  # 从 typing 导入类型注解,用于类型提示

import torch  # 导入 PyTorch 库,用于深度学习
import torch.nn.functional as F  # 导入 PyTorch 的功能性神经网络模块,通常用于激活函数等
from transformers import (  # 从 transformers 库导入多个类和函数
    CLIPImageProcessor,  # 导入 CLIP 图像处理器类
    CLIPTextModel,  # 导入 CLIP 文本模型类
    CLIPTextModelWithProjection,  # 导入带有投影的 CLIP 文本模型类
    CLIPTokenizer,  # 导入 CLIP 分词器类
    CLIPVisionModelWithProjection,  # 导入带有投影的 CLIP 视觉模型类
)

from ...image_processor import PipelineImageInput, VaeImageProcessor  # 从相对路径导入图像处理相关类
from ...loaders import (  # 从相对路径导入加载器相关类
    FromSingleFileMixin,  # 单文件混合加载器
    IPAdapterMixin,  # IP 适配器混合类
    StableDiffusionXLLoraLoaderMixin,  # Stable Diffusion XL LoRA 加载器混合类
    TextualInversionLoaderMixin,  # 文本反演加载器混合类
)
from ...models import AutoencoderKL, UNet2DConditionModel  # 从相对路径导入模型类
from ...models.attention_processor import (  # 从相对路径导入注意力处理器相关类
    Attention,  # 注意力机制类
    AttnProcessor,  # 注意力处理器基类
    AttnProcessor2_0,  # 注意力处理器版本 2.0
    XFormersAttnProcessor,  # XFormers 注意力处理器
)
from ...models.lora import adjust_lora_scale_text_encoder  # 从相对路径导入调整 LoRA 比例的函数
from ...schedulers import DDIMScheduler, DPMSolverMultistepScheduler  # 从相对路径导入调度器类
from ...utils import (  # 从相对路径导入实用工具函数
    USE_PEFT_BACKEND,  # 用于 PEFT 后端的常量
    is_invisible_watermark_available,  # 检查是否可用隐形水印的函数
    is_torch_xla_available,  # 检查是否可用 Torch XLA 的函数
    logging,  # 日志记录模块
    replace_example_docstring,  # 替换示例文档字符串的函数
    scale_lora_layers,  # 缩放 LoRA 层的函数
    unscale_lora_layers,  # 反缩放 LoRA 层的函数
)
from ...utils.torch_utils import randn_tensor  # 从相对路径导入生成随机张量的函数
from ..pipeline_utils import DiffusionPipeline  # 从相对路径导入扩散管道类
from .pipeline_output import LEditsPPDiffusionPipelineOutput, LEditsPPInversionPipelineOutput  # 导入管道输出相关类


# 如果隐形水印可用,则导入相应的水印处理类
if is_invisible_watermark_available():
    from ..stable_diffusion_xl.watermark import StableDiffusionXLWatermarker  # 导入 Stable Diffusion XL 水印类

# 检查是否可用 Torch XLA,若可用则导入其核心模块
if is_torch_xla_available():
    import torch_xla.core.xla_model as xm  # 导入 Torch XLA 核心模型模块

    XLA_AVAILABLE = True  # 设置标志,表示 XLA 可用
else:
    XLA_AVAILABLE = False  # 设置标志,表示 XLA 不可用

logger = logging.get_logger(__name__)  # 获取当前模块的日志记录器,便于后续日志记录使用

EXAMPLE_DOC_STRING = """  # 定义示例文档字符串的多行字符串,用于文档或示例说明
# 示例代码,展示如何使用 LE edits PP 管道进行图像编辑
    Examples:
        ```py
        # 导入所需的库
        >>> import torch  # 导入 PyTorch 库
        >>> import PIL  # 导入 Python Imaging Library (PIL)
        >>> import requests  # 导入请求库以获取网络资源
        >>> from io import BytesIO  # 导入 BytesIO 用于字节流处理

        # 从 diffusers 库导入 LEditsPPPipelineStableDiffusionXL 类
        >>> from diffusers import LEditsPPPipelineStableDiffusionXL

        # 创建一个 LEditsPPPipelineStableDiffusionXL 的实例,使用预训练模型
        >>> pipe = LEditsPPPipelineStableDiffusionXL.from_pretrained(
        ...     "stabilityai/stable-diffusion-xl-base-1.0", torch_dtype=torch.float16
        ... )
        # 将管道移动到 CUDA 设备以加速计算
        >>> pipe = pipe.to("cuda")

        # 定义一个函数下载并返回图像
        >>> def download_image(url):
        ...     # 发送 GET 请求获取图像
        ...     response = requests.get(url)
        ...     # 将响应内容转换为 RGB 格式的图像
        ...     return PIL.Image.open(BytesIO(response.content)).convert("RGB")

        # 图像的 URL
        >>> img_url = "https://www.aiml.informatik.tu-darmstadt.de/people/mbrack/tennis.jpg"
        # 下载图像并存储
        >>> image = download_image(img_url)

        # 使用管道进行图像反转操作,指定反转步骤和跳过比例
        >>> _ = pipe.invert(image=image, num_inversion_steps=50, skip=0.2)

        # 使用管道进行图像编辑,定义编辑提示和参数
        >>> edited_image = pipe(
        ...     editing_prompt=["tennis ball", "tomato"],
        ...     reverse_editing_direction=[True, False],
        ...     edit_guidance_scale=[5.0, 10.0],
        ...     edit_threshold=[0.9, 0.85],
        ... ).images[0]  # 获取编辑后的第一张图像

文档字符串,通常用于描述类或方法的功能

"""

从 diffusers.pipelines.ledits_pp.pipeline_leditspp_stable_diffusion.LeditsAttentionStore 复制的类

class LeditsAttentionStore:
# 静态方法,返回一个空的注意力存储结构
@staticmethod
def get_empty_store():
return {"down_cross": [], "mid_cross": [], "up_cross": [], "down_self": [], "mid_self": [], "up_self": []}

# 使对象可调用的方法,处理注意力矩阵
def __call__(self, attn, is_cross: bool, place_in_unet: str, editing_prompts, PnP=False):
    # attn.shape = batch_size * head_size, seq_len query, seq_len_key
    # 如果注意力矩阵的第二维小于等于最大大小
    if attn.shape[1] <= self.max_size:
        # 计算批次大小,考虑 PnP 和编辑提示
        bs = 1 + int(PnP) + editing_prompts
        skip = 2 if PnP else 1  # 跳过 PnP 和无条件
        # 将注意力矩阵分割并重新排列维度
        attn = torch.stack(attn.split(self.batch_size)).permute(1, 0, 2, 3)
        # 计算源批次大小
        source_batch_size = int(attn.shape[1] // bs)
        # 调用前向传播方法
        self.forward(attn[:, skip * source_batch_size :], is_cross, place_in_unet)

# 前向传播方法,存储注意力
def forward(self, attn, is_cross: bool, place_in_unet: str):
    # 创建键,用于存储当前层的注意力
    key = f"{place_in_unet}_{'cross' if is_cross else 'self'}"
    # 将当前注意力添加到步骤存储中
    self.step_store[key].append(attn)

# 在步骤之间调用的方法,决定是否存储步骤
def between_steps(self, store_step=True):
    if store_step:
        # 如果需要平均处理
        if self.average:
            # 如果注意力存储为空,初始化
            if len(self.attention_store) == 0:
                self.attention_store = self.step_store
            else:
                # 将步骤存储中的注意力与已有的注意力相加
                for key in self.attention_store:
                    for i in range(len(self.attention_store[key])):
                        self.attention_store[key][i] += self.step_store[key][i]
        else:
            # 如果不平均处理,初始化或追加步骤存储
            if len(self.attention_store) == 0:
                self.attention_store = [self.step_store]
            else:
                self.attention_store.append(self.step_store)

        # 当前步骤计数加一
        self.cur_step += 1
    # 重置步骤存储为一个空的注意力存储
    self.step_store = self.get_empty_store()

# 获取特定步骤的注意力数据
def get_attention(self, step: int):
    # 如果需要平均,计算平均注意力
    if self.average:
        attention = {
            key: [item / self.cur_step for item in self.attention_store[key]] for key in self.attention_store
        }
    else:
        # 断言步骤不为空
        assert step is not None
        # 获取指定步骤的注意力
        attention = self.attention_store[step]
    return attention

# 聚合注意力的方法,处理多个输入
def aggregate_attention(
    self, attention_maps, prompts, res: Union[int, Tuple[int]], from_where: List[str], is_cross: bool, select: int
):
    # 初始化一个列表,包含 self.batch_size 个空列表,用于存储输出
    out = [[] for x in range(self.batch_size)]
    # 检查 res 是否为整数
    if isinstance(res, int):
        # 计算每个像素的数量,假设为 res 的平方
        num_pixels = res**2
        # 设置分辨率为 (res, res)
        resolution = (res, res)
    else:
        # 计算像素数量为 res 的两个维度的乘积
        num_pixels = res[0] * res[1]
        # 设置分辨率为 res 的前两个值
        resolution = res[:2]

    # 遍历 from_where 列表
    for location in from_where:
        # 遍历与当前位置相关的注意力图
        for bs_item in attention_maps[f"{location}_{'cross' if is_cross else 'self'}"]:
            # 枚举当前批次和项目
            for batch, item in enumerate(bs_item):
                # 检查当前项目的第二维是否等于 num_pixels
                if item.shape[1] == num_pixels:
                    # 将项目重塑为指定形状,并选择相关的项目
                    cross_maps = item.reshape(len(prompts), -1, *resolution, item.shape[-1])[select]
                    # 将重塑的项目添加到输出的相应批次中
                    out[batch].append(cross_maps)

    # 将每个批次的输出合并成一个张量
    out = torch.stack([torch.cat(x, dim=0) for x in out])
    # 对每个头进行平均
    out = out.sum(1) / out.shape[1]
    # 返回最终的输出
    return out

def __init__(self, average: bool, batch_size=1, max_resolution=16, max_size: int = None):
    # 初始化一个空的存储,用于保存步骤信息
    self.step_store = self.get_empty_store()
    # 初始化一个空的注意力存储列表
    self.attention_store = []
    # 当前步骤初始化为 0
    self.cur_step = 0
    # 设置平均标志
    self.average = average
    # 设置批次大小
    self.batch_size = batch_size
    # 如果 max_size 为空,计算最大尺寸为 max_resolution 的平方
    if max_size is None:
        self.max_size = max_resolution**2
    # 如果 max_size 不为空且 max_resolution 为空,设置最大尺寸为 max_size
    elif max_size is not None and max_resolution is None:
        self.max_size = max_size
    # 如果两个都被设置,则抛出错误
    else:
        raise ValueError("Only allowed to set one of max_resolution or max_size")

从 diffusers.pipelines.ledits_pp.pipeline_leditspp_stable_diffusion 导入的 LeditsGaussianSmoothing 类

class LeditsGaussianSmoothing:
# 初始化函数,接受设备参数
def init(self, device):
# 定义高斯核的大小
kernel_size = [3, 3]
# 定义高斯核的标准差
sigma = [0.5, 0.5]

    # 高斯核是每个维度的高斯函数的乘积
    kernel = 1
    # 创建网格,用于生成高斯核
    meshgrids = torch.meshgrid([torch.arange(size, dtype=torch.float32) for size in kernel_size])
    # 遍历每个维度的大小、标准差和网格
    for size, std, mgrid in zip(kernel_size, sigma, meshgrids):
        # 计算均值
        mean = (size - 1) / 2
        # 更新高斯核值
        kernel *= 1 / (std * math.sqrt(2 * math.pi)) * torch.exp(-(((mgrid - mean) / (2 * std)) ** 2))

    # 确保高斯核的所有值之和等于 1
    kernel = kernel / torch.sum(kernel)

    # 将高斯核重塑为深度可分离卷积的权重
    kernel = kernel.view(1, 1, *kernel.size())
    # 重复高斯核以适应多通道输入
    kernel = kernel.repeat(1, *[1] * (kernel.dim() - 1))

    # 将权重转移到指定设备上
    self.weight = kernel.to(device)

# 调用函数,用于应用高斯滤波
def __call__(self, input):
    """
    参数:
    对输入应用高斯滤波。
        input (torch.Tensor): 要应用高斯滤波的输入。
    返回:
        filtered (torch.Tensor): 滤波后的输出。
    """
    # 使用卷积函数对输入应用高斯滤波
    return F.conv2d(input, weight=self.weight.to(input.dtype))

从 diffusers.pipelines.ledits_pp.pipeline_leditspp_stable_diffusion 导入的 LEDITSCrossAttnProcessor 类

class LEDITSCrossAttnProcessor:
# 初始化函数,接受注意力存储、在 UNet 中的位置、PnP 和编辑提示
def init(self, attention_store, place_in_unet, pnp, editing_prompts):
# 设置注意力存储
self.attnstore = attention_store
# 设置在 UNet 中的位置
self.place_in_unet = place_in_unet
# 设置编辑提示
self.editing_prompts = editing_prompts
# 设置 PnP 参数
self.pnp = pnp

# 调用函数,用于处理注意力和隐藏状态
def __call__(
    self,
    attn: Attention,
    hidden_states,
    encoder_hidden_states,
    attention_mask=None,
    temb=None,
):
    # 获取批次大小、序列长度和特征维度,如果没有编码器隐藏状态,则使用隐藏状态的形状
    batch_size, sequence_length, _ = (
        hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
    )
    # 准备注意力掩码,调整为适合序列长度和批次大小
    attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)

    # 将隐藏状态转换为查询向量
    query = attn.to_q(hidden_states)

    # 如果没有编码器隐藏状态,使用当前隐藏状态;如果存在且需要归一化,则归一化编码器隐藏状态
    if encoder_hidden_states is None:
        encoder_hidden_states = hidden_states
    elif attn.norm_cross:
        encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)

    # 将编码器隐藏状态转换为键向量
    key = attn.to_k(encoder_hidden_states)
    # 将编码器隐藏状态转换为值向量
    value = attn.to_v(encoder_hidden_states)

    # 将查询向量调整为批次维度
    query = attn.head_to_batch_dim(query)
    # 将键向量调整为批次维度
    key = attn.head_to_batch_dim(key)
    # 将值向量调整为批次维度
    value = attn.head_to_batch_dim(value)

    # 计算注意力得分
    attention_probs = attn.get_attention_scores(query, key, attention_mask)
    # 存储注意力得分到内部存储中,标记为跨注意力
    self.attnstore(
        attention_probs,
        is_cross=True,
        place_in_unet=self.place_in_unet,
        editing_prompts=self.editing_prompts,
        PnP=self.pnp,
    )

    # 使用注意力得分和值向量进行批次矩阵乘法以获取新的隐藏状态
    hidden_states = torch.bmm(attention_probs, value)
    # 将隐藏状态从批次维度转换回头部维度
    hidden_states = attn.batch_to_head_dim(hidden_states)

    # 通过线性投影转换隐藏状态
    hidden_states = attn.to_out[0](hidden_states)
    # 应用 dropout 操作
    hidden_states = attn.to_out[1](hidden_states)

    # 将隐藏状态按输出缩放因子进行归一化
    hidden_states = hidden_states / attn.rescale_output_factor
    # 返回最终的隐藏状态
    return hidden_states

定义一个名为 LEditsPPPipelineStableDiffusionXL 的类,继承多个混入类

class LEditsPPPipelineStableDiffusionXL(
# 继承自 DiffusionPipeline,提供扩散模型功能
DiffusionPipeline,
# 继承自 FromSingleFileMixin,允许从单一文件加载数据
FromSingleFileMixin,
# 继承自 StableDiffusionXLLoraLoaderMixin,支持加载 LoRA 权重
StableDiffusionXLLoraLoaderMixin,
# 继承自 TextualInversionLoaderMixin,支持文本反演功能
TextualInversionLoaderMixin,
# 继承自 IPAdapterMixin,提供图像处理适配功能
IPAdapterMixin,
):
"""
使用 LEDits++ 和 Stable Diffusion XL 进行文本图像编辑的管道。

此模型继承自 [`DiffusionPipeline`] 并基于 [`StableDiffusionXLPipeline`]。查看超类文档以获取
所有管道实现的通用方法(下载、保存、在特定设备上运行等)。

此外,管道还继承了以下加载方法:
    - *LoRA*: [`LEditsPPPipelineStableDiffusionXL.load_lora_weights`]
    - *Ckpt*: [`loaders.FromSingleFileMixin.from_single_file`]

以及以下保存方法:
    - *LoRA*: [`loaders.StableDiffusionXLPipeline.save_lora_weights`]
```
# 参数说明
Args:
    vae ([`AutoencoderKL`]):
        # 变分自编码器模型,用于将图像编码为潜在表示并解码。
    text_encoder ([`~transformers.CLIPTextModel`]):
        # 冻结的文本编码器,Stable Diffusion XL 使用 CLIP 的文本部分。
        # 具体使用 [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) 变体。
    text_encoder_2 ([`~transformers.CLIPTextModelWithProjection`]):
        # 第二个冻结文本编码器,Stable Diffusion XL 使用 CLIP 的文本和池化部分。
        # 具体使用 [laion/CLIP-ViT-bigG-14-laion2B-39B-b160k](https://huggingface.co/laion/CLIP-ViT-bigG-14-laion2B-39B-b160k) 变体。
    tokenizer ([`~transformers.CLIPTokenizer`]):
        # CLIPTokenizer 类的分词器。
    tokenizer_2 ([`~transformers.CLIPTokenizer`]):
        # 第二个 CLIPTokenizer 类的分词器。
    unet ([`UNet2DConditionModel`]): 
        # 条件 U-Net 架构,用于对编码的图像潜在表示进行去噪。
    scheduler ([`DPMSolverMultistepScheduler`] or [`DDIMScheduler`]):
        # 与 `unet` 结合使用的调度器,用于去噪编码的图像潜在表示。
        # 可以是 [`DPMSolverMultistepScheduler`] 或 [`DDIMScheduler`],如传入其他调度器,将默认设置为 [`DPMSolverMultistepScheduler`]。
    force_zeros_for_empty_prompt (`bool`, *optional*, defaults to `"True"`):
        # 是否强制将负提示嵌入始终设置为 0。另见 `stabilityai/stable-diffusion-xl-base-1-0` 的配置。
    add_watermarker (`bool`, *optional*):
        # 是否使用 [invisible_watermark 库](https://github.com/ShieldMnt/invisible-watermark/) 对输出图像进行水印处理。
        # 如果未定义,且包已安装,则默认设置为 True,否则不使用水印。
"""

# 定义模型的 CPU 离线加载顺序
model_cpu_offload_seq = "text_encoder->text_encoder_2->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",
    "add_time_ids",
    "negative_pooled_prompt_embeds",
    "negative_add_time_ids",
]
# 初始化方法,用于设置类的基本属性
    def __init__(
        self,
        vae: AutoencoderKL,  # 变分自编码器模型
        text_encoder: CLIPTextModel,  # 文本编码器模型
        text_encoder_2: CLIPTextModelWithProjection,  # 第二文本编码器模型,带投影
        tokenizer: CLIPTokenizer,  # 文本分词器
        tokenizer_2: CLIPTokenizer,  # 第二文本分词器
        unet: UNet2DConditionModel,  # UNet2D条件模型
        scheduler: Union[DPMSolverMultistepScheduler, DDIMScheduler],  # 调度器,可以是多步DPMSolver或DDIM调度器
        image_encoder: CLIPVisionModelWithProjection = None,  # 图像编码器,带投影,可选
        feature_extractor: CLIPImageProcessor = None,  # 特征提取器,可选
        force_zeros_for_empty_prompt: bool = True,  # 是否强制将空提示处理为零
        add_watermarker: Optional[bool] = None,  # 是否添加水印,可选
    ):
        # 调用父类的初始化方法
        super().__init__()

        # 注册多个模块,使其可供后续使用
        self.register_modules(
            vae=vae,
            text_encoder=text_encoder,
            text_encoder_2=text_encoder_2,
            tokenizer=tokenizer,
            tokenizer_2=tokenizer_2,
            unet=unet,
            scheduler=scheduler,
            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.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)

        # 检查调度器类型并初始化为DPMSolverMultistepScheduler(如果必要)
        if not isinstance(scheduler, DDIMScheduler) and not isinstance(scheduler, DPMSolverMultistepScheduler):
            self.scheduler = DPMSolverMultistepScheduler.from_config(
                scheduler.config, algorithm_type="sde-dpmsolver++", solver_order=2
            )
            # 记录警告,说明调度器已更改
            logger.warning(
                "This pipeline only supports DDIMScheduler and DPMSolverMultistepScheduler. "
                "The scheduler has been changed to DPMSolverMultistepScheduler."
            )

        # 设置默认样本大小
        self.default_sample_size = self.unet.config.sample_size

        # 如果add_watermarker为None,则根据可用性确定是否添加水印
        add_watermarker = add_watermarker if add_watermarker is not None else is_invisible_watermark_available()

        # 根据是否添加水印初始化水印对象
        if add_watermarker:
            self.watermark = StableDiffusionXLWatermarker()
        else:
            self.watermark = None
        # 初始化反转步骤为None
        self.inversion_steps = None

    # 编码提示方法,用于处理输入提示
    def encode_prompt(
        self,
        device: Optional[torch.device] = None,  # 指定计算设备,可选
        num_images_per_prompt: int = 1,  # 每个提示生成的图像数量
        negative_prompt: Optional[str] = None,  # 负提示,可选
        negative_prompt_2: Optional[str] = None,  # 第二个负提示,可选
        negative_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,  # 跳过的CLIP层数量,可选
        enable_edit_guidance: bool = True,  # 是否启用编辑指导
        editing_prompt: Optional[str] = None,  # 编辑提示,可选
        editing_prompt_embeds: Optional[torch.Tensor] = None,  # 编辑提示的嵌入表示,可选
        editing_pooled_prompt_embeds: Optional[torch.Tensor] = None,  # 编辑提示的池化嵌入表示,可选
    # 从diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs复制的内容
# 为调度器步骤准备额外的关键字参数,因不同调度器的签名不同
def prepare_extra_step_kwargs(self, eta, generator=None):
    # 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,
    negative_prompt=None,
    negative_prompt_2=None,
    negative_prompt_embeds=None,
    negative_pooled_prompt_embeds=None,
):
    # 如果同时提供 negative_prompt 和 negative_prompt_embeds,抛出错误
    if negative_prompt is not None and negative_prompt_embeds is not None:
        raise ValueError(
            f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
            f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
        )
    # 如果同时提供 negative_prompt_2 和 negative_prompt_embeds,抛出错误
    elif negative_prompt_2 is not None and negative_prompt_embeds is not None:
        raise ValueError(
            f"Cannot forward both `negative_prompt_2`: {negative_prompt_2} and `negative_prompt_embeds`:"
            f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
        )

    # 如果提供 negative_prompt_embeds 但未提供 negative_pooled_prompt_embeds,抛出错误
    if negative_prompt_embeds is not None and negative_pooled_prompt_embeds is None:
        raise ValueError(
            "If `negative_prompt_embeds` are provided, `negative_pooled_prompt_embeds` also have to be passed. Make sure to generate `negative_pooled_prompt_embeds` from the same text encoder that was used to generate `negative_prompt_embeds`."
        )

    # 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_latents 修改而来
    def prepare_latents(self, device, latents):
        # 将潜在变量移动到指定设备
        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
    )
    # 获取模型期望的附加时间嵌入的输入特征维度
    expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features

    # 检查实际生成的嵌入维度是否与期望的维度匹配
    if expected_add_embed_dim != passed_add_embed_dim:
        # 如果不匹配,抛出错误并提供详细信息
        raise ValueError(
            f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. The model has an incorrect config. Please check `unet.config.time_embedding_type` and `text_encoder_2.config.projection_dim`."
        )

    # 将时间 ID 转换为张量,并指定数据类型
    add_time_ids = torch.tensor([add_time_ids], dtype=dtype)
    # 返回生成的时间 ID 张量
    return add_time_ids

# 从 diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_upscale.StableDiffusionUpscalePipeline.upcast_vae 复制的代码
def upcast_vae(self):
    # 获取 VAE 的数据类型
    dtype = self.vae.dtype
    # 将 VAE 转换为 float32 数据类型
    self.vae.to(dtype=torch.float32)
    # 检查 VAE 解码器中的注意力处理器是否使用 Torch 2.0 或 XFormers
    use_torch_2_0_or_xformers = isinstance(
        self.vae.decoder.mid_block.attentions[0].processor,
        (
            AttnProcessor2_0,
            XFormersAttnProcessor,
        ),
    )
    # 如果使用 XFormers 或 Torch 2.0,则注意力块不需要是 float32,可以节省大量内存
    if use_torch_2_0_or_xformers:
        # 将后量化卷积层转换为相应的数据类型
        self.vae.post_quant_conv.to(dtype)
        # 将解码器输入卷积层转换为相应的数据类型
        self.vae.decoder.conv_in.to(dtype)
        # 将解码器中间块转换为相应的数据类型
        self.vae.decoder.mid_block.to(dtype)

# 从 diffusers.pipelines.latent_consistency_models.pipeline_latent_consistency_text2img.LatentConsistencyModelPipeline.get_guidance_scale_embedding 复制的代码
def get_guidance_scale_embedding(
    # 输入张量 w 和嵌入维度,默认嵌入维度为 512,数据类型默认为 float32
    self, w: torch.Tensor, embedding_dim: int = 512, dtype: torch.dtype = torch.float32
# 函数返回嵌入向量,类型为 torch.Tensor
    ) -> torch.Tensor:
        """
        # 文档字符串,提供函数的详细说明和参数描述
        See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298

        Args:
            w (`torch.Tensor`):
                # 指定引导尺度生成嵌入向量,以丰富时间步嵌入
                Generate embedding vectors with a specified guidance scale to subsequently enrich timestep embeddings.
            embedding_dim (`int`, *optional*, defaults to 512):
                # 要生成的嵌入维度
                Dimension of the embeddings to generate.
            dtype (`torch.dtype`, *optional*, defaults to `torch.float32`):
                # 生成的嵌入数据类型
                Data type of the generated embeddings.

        Returns:
            `torch.Tensor`: # 返回嵌入向量,形状为 (len(w), embedding_dim)
            Embedding vectors with shape `(len(w), embedding_dim)`.
        """
        # 确保输入张量 w 是一维的
        assert len(w.shape) == 1
        # 将 w 的值放大 1000 倍
        w = w * 1000.0

        # 计算嵌入的半维度
        half_dim = embedding_dim // 2
        # 计算嵌入的基础值
        emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1)
        # 计算负指数以获得衰减的嵌入
        emb = torch.exp(torch.arange(half_dim, dtype=dtype) * -emb)
        # 将 w 转换为目标数据类型并计算最终的嵌入
        emb = w.to(dtype)[:, None] * emb[None, :]
        # 将正弦和余弦嵌入合并
        emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1)
        # 如果嵌入维度是奇数,则进行零填充
        if embedding_dim % 2 == 1:  # zero pad
            emb = torch.nn.functional.pad(emb, (0, 1))
        # 确保最终嵌入的形状正确
        assert emb.shape == (w.shape[0], embedding_dim)
        # 返回最终的嵌入
        return emb

    # 属性获取引导尺度的值
    @property
    def guidance_scale(self):
        return self._guidance_scale

    # 属性获取引导重新缩放的值
    @property
    def guidance_rescale(self):
        return self._guidance_rescale

    # 属性获取剪辑跳过的值
    @property
    def clip_skip(self):
        return self._clip_skip

    # 此属性定义了与方程 (2) 中引导权重 w 类似的引导尺度
    # 引导尺度 = 1 表示没有进行分类器自由引导。
    # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
    # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
    # corresponds to doing no classifier free guidance.
    @property
    def do_classifier_free_guidance(self):
        # 判断是否进行分类器自由引导
        return self._guidance_scale > 1 and self.unet.config.time_cond_proj_dim is None

    # 属性获取交叉注意力的关键字参数
    @property
    def cross_attention_kwargs(self):
        return self._cross_attention_kwargs

    # 属性获取去噪结束的值
    @property
    def denoising_end(self):
        return self._denoising_end

    # 属性获取时间步数的值
    @property
    def num_timesteps(self):
        return self._num_timesteps

    # 从指定管道复制的内容
    # Copied from diffusers.pipelines.ledits_pp.pipeline_leditspp_stable_diffusion.LEditsPPPipelineStableDiffusion.prepare_unet
# 准备 UNet 模型,设置注意力处理器
def prepare_unet(self, attention_store, PnP: bool = False):
    # 初始化一个空字典用于存储注意力处理器
    attn_procs = {}
    # 遍历 UNet 的注意力处理器的键
    for name in self.unet.attn_processors.keys():
        # 如果名字以 "mid_block" 开头,则设置位置为 "mid"
        if name.startswith("mid_block"):
            place_in_unet = "mid"
        # 如果名字以 "up_blocks" 开头,则设置位置为 "up"
        elif name.startswith("up_blocks"):
            place_in_unet = "up"
        # 如果名字以 "down_blocks" 开头,则设置位置为 "down"
        elif name.startswith("down_blocks"):
            place_in_unet = "down"
        # 如果名字不符合以上条件,则跳过当前循环
        else:
            continue

        # 如果名字包含 "attn2" 且位置不是 "mid"
        if "attn2" in name and place_in_unet != "mid":
            # 创建 LEDITSCrossAttnProcessor 实例并加入字典
            attn_procs[name] = LEDITSCrossAttnProcessor(
                attention_store=attention_store,  # 传入注意力存储
                place_in_unet=place_in_unet,      # 传入在 UNet 中的位置
                pnp=PnP,                          # 传入 PnP 标志
                editing_prompts=self.enabled_editing_prompts,  # 传入启用的编辑提示
            )
        # 否则,创建默认的 AttnProcessor 实例
        else:
            attn_procs[name] = AttnProcessor()

    # 设置 UNet 的注意力处理器
    self.unet.set_attn_processor(attn_procs)

# 在不计算梯度的情况下调用该函数
@torch.no_grad()
# 用示例文档字符串替换文档字符串
@replace_example_docstring(EXAMPLE_DOC_STRING)
def __call__(
    # 可选的去噪结束时间
    denoising_end: Optional[float] = None,
    # 可选的负提示,可以是字符串或字符串列表
    negative_prompt: Optional[Union[str, List[str]]] = None,
    # 第二个可选的负提示
    negative_prompt_2: Optional[Union[str, List[str]]] = None,
    # 可选的负提示嵌入
    negative_prompt_embeds: Optional[torch.Tensor] = None,
    # 可选的负池化提示嵌入
    negative_pooled_prompt_embeds: Optional[torch.Tensor] = None,
    # 可选的输入适配器图像
    ip_adapter_image: Optional[PipelineImageInput] = None,
    # 输出类型的可选参数,默认为 "pil"
    output_type: Optional[str] = "pil",
    # 是否返回字典的可选参数,默认为 True
    return_dict: bool = True,
    # 可选的交叉注意力参数
    cross_attention_kwargs: Optional[Dict[str, Any]] = None,
    # 引导缩放因子的可选参数,默认为 0.0
    guidance_rescale: float = 0.0,
    # 左上角裁剪坐标的可选参数,默认为 (0, 0)
    crops_coords_top_left: Tuple[int, int] = (0, 0),
    # 可选的目标尺寸
    target_size: Optional[Tuple[int, int]] = None,
    # 可选的编辑提示
    editing_prompt: Optional[Union[str, List[str]]] = None,
    # 可选的编辑提示嵌入
    editing_prompt_embeddings: Optional[torch.Tensor] = None,
    # 可选的编辑池化提示嵌入
    editing_pooled_prompt_embeds: Optional[torch.Tensor] = None,
    # 可选的反向编辑方向,默认为 False
    reverse_editing_direction: Optional[Union[bool, List[bool]]] = False,
    # 编辑引导缩放因子的可选参数,默认为 5
    edit_guidance_scale: Optional[Union[float, List[float]]] = 5,
    # 编辑热身步骤的可选参数,默认为 0
    edit_warmup_steps: Optional[Union[int, List[int]]] = 0,
    # 编辑冷却步骤的可选参数
    edit_cooldown_steps: Optional[Union[int, List[int]]] = None,
    # 编辑阈值的可选参数,默认为 0.9
    edit_threshold: Optional[Union[float, List[float]]] = 0.9,
    # 可选的语义引导张量列表
    sem_guidance: Optional[List[torch.Tensor]] = None,
    # 使用交叉注意力掩膜的可选参数,默认为 False
    use_cross_attn_mask: bool = False,
    # 使用交集掩膜的可选参数,默认为 False
    use_intersect_mask: bool = False,
    # 用户掩膜的可选参数
    user_mask: Optional[torch.Tensor] = None,
    # 注意力存储步骤的可选列表,默认为空列表
    attn_store_steps: Optional[List[int]] = [],
    # 是否在步骤间平均存储的可选参数,默认为 True
    store_averaged_over_steps: bool = True,
    # 可选的跳过剪辑的参数
    clip_skip: Optional[int] = None,
    # 可选的步骤结束回调函数
    callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
    # 步骤结束时的张量输入回调参数,默认为 ["latents"]
    callback_on_step_end_tensor_inputs: List[str] = ["latents"],
    # 其他可选参数
    **kwargs,
# 在不计算梯度的情况下调用该函数
@torch.no_grad()
# 从 diffusers.pipelines.ledits_pp.pipeline_leditspp_stable_diffusion.LEditsPPPipelineStableDiffusion.encode_image 修改而来
# 定义一个方法用于编码图像,接受多个可选参数
def encode_image(self, image, dtype=None, height=None, width=None, resize_mode="default", crops_coords=None):
    # 预处理图像,调整大小和裁剪,返回处理后的图像
    image = self.image_processor.preprocess(
        image=image, height=height, width=width, resize_mode=resize_mode, crops_coords=crops_coords
    )
    # 后处理图像,转换为 PIL 格式
    resized = self.image_processor.postprocess(image=image, output_type="pil")

    # 检查图像的最大尺寸是否超过配置的采样大小的 1.5 倍
    if max(image.shape[-2:]) > self.vae.config["sample_size"] * 1.5:
        # 记录警告信息,提示用户输入图像分辨率过高
        logger.warning(
            "Your input images far exceed the default resolution of the underlying diffusion model. "
            "The output images may contain severe artifacts! "
            "Consider down-sampling the input using the `height` and `width` parameters"
        )
    # 将图像转换为指定的设备和数据类型
    image = image.to(self.device, dtype=dtype)
    # 检查是否需要将数据类型上调
    needs_upcasting = self.vae.dtype == torch.float16 and self.vae.config.force_upcast

    # 如果需要上调数据类型
    if needs_upcasting:
        # 将图像转换为浮点数
        image = image.float()
        # 上调 VAE 模型的数据类型
        self.upcast_vae()

    # 使用 VAE 编码图像,获取潜在分布的模式
    x0 = self.vae.encode(image).latent_dist.mode()
    # 将模式转换为指定的数据类型
    x0 = x0.to(dtype)
    # 如果需要,转换回 fp16 数据类型
    if needs_upcasting:
        self.vae.to(dtype=torch.float16)

    # 根据配置的缩放因子调整潜在向量
    x0 = self.vae.config.scaling_factor * x0
    # 返回潜在向量和调整后的图像
    return x0, resized

# 装饰器,表示该方法在执行时不计算梯度
@torch.no_grad()
# 定义一个方法用于反转图像,接受多个可选参数
def invert(
    self,
    image: PipelineImageInput,
    source_prompt: str = "",
    source_guidance_scale=3.5,
    negative_prompt: str = None,
    negative_prompt_2: str = None,
    num_inversion_steps: int = 50,
    skip: float = 0.15,
    generator: Optional[torch.Generator] = None,
    crops_coords_top_left: Tuple[int, int] = (0, 0),
    num_zero_noise_steps: int = 3,
    cross_attention_kwargs: Optional[Dict[str, Any]] = None,

从 diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl.rescale_noise_cfg 复制

def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0):
"""
根据 guidance_rescale 重新缩放 noise_cfg。基于 Common Diffusion Noise Schedules and
Sample Steps are Flawed
的研究结果。见第 3.4 节
"""
# 计算 noise_pred_text 的标准差,沿着指定维度,保持维度
std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True)
# 计算 noise_cfg 的标准差,沿着指定维度,保持维度
std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True)
# 通过标准差的比值重新缩放指导结果(修正过度曝光)
noise_pred_rescaled = noise_cfg * (std_text / std_cfg)
# 用指导结果与原始结果混合,通过 guidance_rescale 因子避免“普通”图像
noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg
# 返回重新缩放后的噪声配置
return noise_cfg

从 diffusers.pipelines.ledits_pp.pipeline_leditspp_stable_diffusion.compute_noise_ddim 复制

def compute_noise_ddim(scheduler, prev_latents, latents, timestep, noise_pred, eta):
# 1. 获取前一步值(t-1)
prev_timestep = timestep - scheduler.config.num_train_timesteps // scheduler.num_inference_steps

# 2. 计算 alphas 和 betas
# 当前时间步的累积 alpha 值
alpha_prod_t = scheduler.alphas_cumprod[timestep]
# 前一步的累积 alpha 值,若为负则使用最终的 alpha 值
alpha_prod_t_prev = (
    scheduler.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else scheduler.final_alpha_cumprod
)
# 当前时间步的 beta 值
beta_prod_t = 1 - alpha_prod_t

# 3. 从预测噪声计算预测的原始样本,称为 "predicted x_0"
pred_original_sample = (latents - beta_prod_t ** (0.5) * noise_pred) / alpha_prod_t ** (0.5)

# 4. 限制 "predicted x_0" 的范围
if scheduler.config.clip_sample:
    # 将预测样本限制在 -1 和 1 之间
    pred_original_sample = torch.clamp(pred_original_sample, -1, 1)

# 5. 计算方差:σ_t(η),见公式 (16)
# σ_t = sqrt((1 − α_t−1)/(1 − α_t)) * sqrt(1 − α_t/α_t−1)
variance = scheduler._get_variance(timestep, prev_timestep)
# 计算标准差
std_dev_t = eta * variance ** (0.5)

# 6. 计算指向 x_t 的方向,见公式 (12)
pred_sample_direction = (1 - alpha_prod_t_prev - std_dev_t**2) ** (0.5) * noise_pred

# 修改以返回更新后的 xtm1,以避免误差累积
mu_xt = alpha_prod_t_prev ** (0.5) * pred_original_sample + pred_sample_direction
# 如果方差大于 0,计算噪声
if variance > 0.0:
    noise = (prev_latents - mu_xt) / (variance ** (0.5) * eta)
else:
    # 否则噪声设置为零
    noise = torch.tensor([0.0]).to(latents.device)

# 返回计算出的噪声和更新后的 mu_xt
return noise, mu_xt + (eta * variance**0.5) * noise

从 diffusers.pipelines.ledits_pp.pipeline_leditspp_stable_diffusion.compute_noise_sde_dpm_pp_2nd 复制

def compute_noise_sde_dpm_pp_2nd(scheduler, prev_latents, latents, timestep, noise_pred, eta):
# 定义一阶更新函数,输入模型输出和样本
def first_order_update(model_output, sample): # timestep, prev_timestep, sample):
# 获取当前和前一个步骤的 sigma 值
sigma_t, sigma_s = scheduler.sigmas[scheduler.step_index + 1], scheduler.sigmas[scheduler.step_index]
# 将 sigma 转换为 alpha 和 sigma_t
alpha_t, sigma_t = scheduler._sigma_to_alpha_sigma_t(sigma_t)
alpha_s, sigma_s = scheduler._sigma_to_alpha_sigma_t(sigma_s)
# 计算 lambda_t 和 lambda_s
lambda_t = torch.log(alpha_t) - torch.log(sigma_t)
lambda_s = torch.log(alpha_s) - torch.log(sigma_s)

        # 计算 h 值
        h = lambda_t - lambda_s

        # 计算 mu_xt,结合样本和模型输出
        mu_xt = (sigma_t / sigma_s * torch.exp(-h)) * sample + (alpha_t * (1 - torch.exp(-2.0 * h))) * model_output

        # 使用调度器的 DPM 解决方案更新 mu_xt
        mu_xt = scheduler.dpm_solver_first_order_update(
            model_output=model_output, sample=sample, noise=torch.zeros_like(sample)
        )

        # 计算 sigma
        sigma = sigma_t * torch.sqrt(1.0 - torch.exp(-2 * h))
        # 如果 sigma 大于 0,计算噪声
        if sigma > 0.0:
            noise = (prev_latents - mu_xt) / sigma
        else:
            # 否则设定噪声为 0
            noise = torch.tensor([0.0]).to(sample.device)

        # 计算前一个样本
        prev_sample = mu_xt + sigma * noise
        # 返回噪声和前一个样本
        return noise, prev_sample

    # 定义二阶更新函数,输入模型输出列表和样本
    def second_order_update(model_output_list, sample):  # timestep_list, prev_timestep, sample):
        # 获取当前和前两个步骤的 sigma 值
        sigma_t, sigma_s0, sigma_s1 = (
            scheduler.sigmas[scheduler.step_index + 1],
            scheduler.sigmas[scheduler.step_index],
            scheduler.sigmas[scheduler.step_index - 1],
        )

        # 将 sigma 转换为 alpha 和 sigma_t
        alpha_t, sigma_t = scheduler._sigma_to_alpha_sigma_t(sigma_t)
        alpha_s0, sigma_s0 = scheduler._sigma_to_alpha_sigma_t(sigma_s0)
        alpha_s1, sigma_s1 = scheduler._sigma_to_alpha_sigma_t(sigma_s1)

        # 计算 lambda 值
        lambda_t = torch.log(alpha_t) - torch.log(sigma_t)
        lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0)
        lambda_s1 = torch.log(alpha_s1) - torch.log(sigma_s1)

        # 获取最后两个模型输出
        m0, m1 = model_output_list[-1], model_output_list[-2]

        # 计算 h 和 h_0
        h, h_0 = lambda_t - lambda_s0, lambda_s0 - lambda_s1
        r0 = h_0 / h
        # 设定 D0 和 D1
        D0, D1 = m0, (1.0 / r0) * (m0 - m1)

        # 计算 mu_xt
        mu_xt = (
            (sigma_t / sigma_s0 * torch.exp(-h)) * sample
            + (alpha_t * (1 - torch.exp(-2.0 * h))) * D0
            + 0.5 * (alpha_t * (1 - torch.exp(-2.0 * h))) * D1
        )

        # 计算 sigma
        sigma = sigma_t * torch.sqrt(1.0 - torch.exp(-2 * h))
        # 如果 sigma 大于 0,计算噪声
        if sigma > 0.0:
            noise = (prev_latents - mu_xt) / sigma
        else:
            # 否则设定噪声为 0
            noise = torch.tensor([0.0]).to(sample.device)

        # 计算前一个样本
        prev_sample = mu_xt + sigma * noise

        # 返回噪声和前一个样本
        return noise, prev_sample

    # 如果调度器的步骤索引为 None,初始化步骤索引
    if scheduler.step_index is None:
        scheduler._init_step_index(timestep)

    # 将模型输出转换为可用格式
    model_output = scheduler.convert_model_output(model_output=noise_pred, sample=latents)
    # 更新模型输出列表
    for i in range(scheduler.config.solver_order - 1):
        scheduler.model_outputs[i] = scheduler.model_outputs[i + 1]
    scheduler.model_outputs[-1] = model_output

    # 根据低阶数量决定使用一阶或二阶更新
    if scheduler.lower_order_nums < 1:
        noise, prev_sample = first_order_update(model_output, latents)
    else:
        noise, prev_sample = second_order_update(scheduler.model_outputs, latents)
# 如果当前调度器的低阶数量小于配置中的求解器阶数
if scheduler.lower_order_nums < scheduler.config.solver_order:
    # 增加低阶数量的计数
    scheduler.lower_order_nums += 1

# 完成后,将步骤索引增加一
scheduler._step_index += 1

# 返回噪声和之前的样本
return noise, prev_sample

从 diffusers.pipelines.ledits_pp.pipeline_leditspp_stable_diffusion 复制的代码

def compute_noise(scheduler, *args):
# 检查调度器是否为 DDIMScheduler 实例
if isinstance(scheduler, DDIMScheduler):
# 调用 DDIM 调度器的噪声计算函数并返回结果
return compute_noise_ddim(scheduler, *args)
# 检查调度器是否为 DPMSolverMultistepScheduler 实例,且满足特定配置
elif (
isinstance(scheduler, DPMSolverMultistepScheduler)
and scheduler.config.algorithm_type == "sde-dpmsolver++"
and scheduler.config.solver_order == 2
):
# 调用 SDE DPM 的二阶噪声计算函数并返回结果
return compute_noise_sde_dpm_pp_2nd(scheduler, *args)
else:
# 如果不满足以上条件,抛出未实现的错误
raise NotImplementedError


# `.\diffusers\pipelines\ledits_pp\pipeline_output.py`

```py
# 从 dataclass 模块导入 dataclass 装饰器
from dataclasses import dataclass
# 从 typing 模块导入 List, Optional, 和 Union 类型提示
from typing import List, Optional, Union

# 导入 numpy 库并重命名为 np
import numpy as np
# 导入 PIL.Image 模块
import PIL.Image

# 从上级目录的 utils 模块导入 BaseOutput 类
from ...utils import BaseOutput

# 定义 LEditsPPDiffusionPipelineOutput 类,继承自 BaseOutput
@dataclass
class LEditsPPDiffusionPipelineOutput(BaseOutput):
    """
    LEdits++ Diffusion 流水线的输出类。

    参数:
        images (`List[PIL.Image.Image]` 或 `np.ndarray`)
            长度为 `batch_size` 的去噪 PIL 图像列表或形状为 `(batch_size, height, width,
            num_channels)` 的 NumPy 数组。
        nsfw_content_detected (`List[bool]`)
            表示生成图像是否包含“不适合工作” (nsfw) 内容的列表,如果无法进行安全检查则为 `None`。
    """

    # 图像属性,可以是 PIL 图像列表或 NumPy 数组
    images: Union[List[PIL.Image.Image], np.ndarray]
    # NSFW 内容检测结果,可能是布尔值列表或 None
    nsfw_content_detected: Optional[List[bool]]

# 定义 LEditsPPInversionPipelineOutput 类,继承自 BaseOutput
@dataclass
class LEditsPPInversionPipelineOutput(BaseOutput):
    """
    LEdits++ Diffusion 流水线的输出类。

    参数:
        input_images (`List[PIL.Image.Image]` 或 `np.ndarray`)
            长度为 `batch_size` 的裁剪和调整大小后的输入图像列表,或形状为 `
            (batch_size, height, width, num_channels)` 的 NumPy 数组。
        vae_reconstruction_images (`List[PIL.Image.Image]` 或 `np.ndarray`)
            所有输入图像的 VAE 重建列表,作为长度为 `batch_size` 的 PIL 图像列表,或形状为
            ` (batch_size, height, width, num_channels)` 的 NumPy 数组。
    """

    # 输入图像属性,可以是 PIL 图像列表或 NumPy 数组
    images: Union[List[PIL.Image.Image], np.ndarray]
    # VAE 重建图像属性,可以是 PIL 图像列表或 NumPy 数组
    vae_reconstruction_images: Union[List[PIL.Image.Image], np.ndarray]

.\diffusers\pipelines\ledits_pp\__init__.py

# 从 typing 模块导入 TYPE_CHECKING,用于类型检查的标志
from typing import TYPE_CHECKING

# 从 utils 模块导入多个功能和常量
from ...utils import (
    DIFFUSERS_SLOW_IMPORT,  # 导入用于标识慢导入的常量
    OptionalDependencyNotAvailable,  # 导入表示可选依赖不可用的异常
    _LazyModule,  # 导入懒加载模块的类
    get_objects_from_module,  # 导入从模块获取对象的函数
    is_torch_available,  # 导入检查 PyTorch 是否可用的函数
    is_transformers_available,  # 导入检查 Transformers 是否可用的函数
)

# 初始化一个空字典,用于存储虚拟对象
_dummy_objects = {}
# 初始化一个空字典,用于存储模块导入结构
_import_structure = {}

# 尝试检测可选依赖项是否可用
try:
    # 检查 Transformers 和 PyTorch 是否都可用,如果不可用则抛出异常
    if not (is_transformers_available() and is_torch_available()):
        raise OptionalDependencyNotAvailable()
# 捕获可选依赖不可用的异常
except OptionalDependencyNotAvailable:
    # 从 utils 模块导入虚拟对象的模块,忽略相关的警告
    from ...utils import dummy_torch_and_transformers_objects  # noqa F403

    # 更新虚拟对象字典,将虚拟对象加入到字典中
    _dummy_objects.update(get_objects_from_module(dummy_torch_and_transformers_objects))
# 如果没有异常,执行以下代码
else:
    # 将稳定扩散的管道添加到导入结构字典中
    _import_structure["pipeline_leditspp_stable_diffusion"] = ["LEditsPPPipelineStableDiffusion"]
    # 将 XL 版本的稳定扩散管道添加到导入结构字典中
    _import_structure["pipeline_leditspp_stable_diffusion_xl"] = ["LEditsPPPipelineStableDiffusionXL"]

    # 将管道输出的相关类添加到导入结构字典中
    _import_structure["pipeline_output"] = ["LEditsPPDiffusionPipelineOutput", "LEditsPPDiffusionPipelineOutput"]

# 如果是类型检查或者使用了慢导入标志
if TYPE_CHECKING or DIFFUSERS_SLOW_IMPORT:
    # 尝试检测可选依赖项是否可用
    try:
        # 检查 Transformers 和 PyTorch 是否都可用,如果不可用则抛出异常
        if not (is_transformers_available() and is_torch_available()):
            raise OptionalDependencyNotAvailable()

    # 捕获可选依赖不可用的异常
    except OptionalDependencyNotAvailable:
        # 从虚拟对象的模块导入所有内容
        from ...utils.dummy_torch_and_transformers_objects import *
    else:
        # 从稳定扩散的管道模块导入相关类
        from .pipeline_leditspp_stable_diffusion import (
            LEditsPPDiffusionPipelineOutput,  # 导入稳定扩散管道输出类
            LEditsPPInversionPipelineOutput,  # 导入逆向稳定扩散管道输出类
            LEditsPPPipelineStableDiffusion,  # 导入稳定扩散管道类
        )
        # 从 XL 版本的稳定扩散管道模块导入相关类
        from .pipeline_leditspp_stable_diffusion_xl import LEditsPPPipelineStableDiffusionXL

# 如果不是类型检查且没有使用慢导入标志
else:
    # 导入 sys 模块以便操作模块系统
    import sys

    # 使用懒加载模块创建当前模块的 lazy 实例,并将其赋值给当前模块
    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)
posted @ 2024-10-22 12:33  绝不原创的飞龙  阅读(9)  评论(0编辑  收藏  举报