Diffusers 文档
理念
并获得增强的文档体验
开始使用
理念
🧨 Diffusers 提供了跨多种模态的**最先进**的预训练扩散模型。其目的是作为一个用于推理和训练的**模块化工具箱**。
我们的目标是建立一个经得起时间考验的库,因此我们非常重视 API 设计。
简而言之,Diffusers 被构建为 PyTorch 的自然扩展。因此,我们的大部分设计选择都基于 PyTorch 的设计原则。让我们回顾一下最重要的几条:
可用性优于性能
- 虽然 Diffusers 有许多内置的性能增强功能(参见内存和速度),但模型总是以最高精度和最低优化级别加载。因此,默认情况下,如果用户没有另行定义,扩散 pipelines 始终在 CPU 上以 float32 精度实例化。这确保了在不同平台和加速器上的可用性,也意味着运行该库不需要复杂的安装。
- Diffusers 旨在成为一个**轻量级**的包,因此它只有很少的必需依赖项,但有许多可以提高性能的软依赖项(例如
accelerate
、safetensors
、onnx
等)。我们努力保持库的轻量级,以便可以毫无顾虑地将其作为其他包的依赖项添加。 - Diffusers 偏爱简单、自解释的代码,而不是浓缩、神奇的代码。这意味着像 lambda 函数这样的简写代码语法和高级 PyTorch 运算符通常是不受欢迎的。
简单优于便捷
正如 PyTorch 所言,**显式优于隐式**,**简单优于复杂**。这一设计理念在库的多个部分都有体现:
- 我们遵循 PyTorch 的 API,使用像
DiffusionPipeline.to
这样的方法,让用户自己处理设备管理。 - 引发简洁的错误消息优于静默地纠正错误输入。Diffusers 旨在教导用户,而不是让库尽可能易于使用。
- 复杂的模型与调度器逻辑被暴露出来,而不是在内部被神奇地处理。调度器/采样器与扩散模型是分开的,它们之间的依赖关系极小。这迫使用户编写展开的去噪循环。然而,这种分离使得调试更容易,并让用户在调整去噪过程或更换扩散模型或调度器时有更多的控制权。
- 扩散 pipeline 中独立训练的组件,例如文本编码器、U-Net 和变分自编码器,各自拥有自己的模型类。这迫使用户处理不同模型组件之间的交互,并且序列化格式将模型组件分离到不同的文件中。然而,这使得调试和定制变得更加容易。由于 Diffusers 能够分离扩散 pipeline 的单个组件,DreamBooth 或 Textual Inversion 训练变得非常简单。
可调整、贡献者友好优于抽象
对于库的大部分内容,Diffusers 采用了Transformers 库的一个重要设计原则,即偏爱复制粘贴的代码而不是草率的抽象。这个设计原则非常主观,与流行的设计原则如不要重复自己 (DRY) 形成鲜明对比。简而言之,就像 Transformers 对待模型文件一样,Diffusers 偏爱为 pipelines 和调度器保持极低的抽象级别和非常自包含的代码。函数、长代码块,甚至类都可以在多个文件中复制,这起初可能看起来像一个糟糕、草率的设计选择,使得库难以维护。**然而**,这种设计已被证明对 Transformers 来说非常成功,并且对于社区驱动的开源机器学习库非常有意义,因为:
- 机器学习是一个极其快速发展的领域,其中范式、模型架构和算法变化迅速,因此很难定义持久的代码抽象。
- 机器学习从业者喜欢能够快速调整现有代码以进行构思和研究,因此他们更喜欢自包含的代码,而不是包含许多抽象的代码。
- 开源库依赖于社区贡献,因此必须构建一个易于贡献的库。代码越抽象,依赖关系越多,就越难阅读,也越难贡献。贡献者们会因为害怕破坏重要功能而停止为非常抽象的库做贡献。如果对库的贡献不会破坏其他基础代码,这不仅对潜在的新贡献者更具吸引力,而且也更容易并行地审查和贡献到多个部分。
在 Hugging Face,我们称这种设计为**单文件策略**,这意味着某个特定类的几乎所有代码都应该写在一个单一的、自包含的文件中。要了解更多关于这个理念的信息,你可以查看这篇博客文章。
在 Diffusers 中,我们对 pipelines 和调度器都遵循这一理念,但对于扩散模型只部分遵循。我们不完全遵循这一设计的原因是,几乎所有的扩散 pipelines,如DDPM、Stable Diffusion、unCLIP (DALL·E 2) 和 Imagen 都依赖于同一个扩散模型,即U-Net。
太好了,现在你应该大致理解了为什么 🧨 Diffusers 被设计成这个样子 🤗。我们试图在整个库中一致地应用这些设计原则。然而,这个理念也有一些小小的例外或一些不幸的设计选择。如果你对设计有任何反馈,我们非常希望 ❤️ 在 GitHub 上直接听到。
设计理念详解
现在,让我们深入了解设计理念的一些细节。Diffusers 基本上由三个主要类别组成:pipelines、模型和调度器。让我们详细了解每个类别的设计决策。
Pipelines
Pipelines 的设计旨在易于使用(因此不完全遵循简单优于便捷原则),功能不求完备,应大致视为如何使用模型和调度器进行推理的示例。
遵循以下设计原则:
- Pipelines 遵循单文件策略。所有 pipelines 都可以在 src/diffusers/pipelines 下的独立目录中找到。一个 pipeline 文件夹对应一篇扩散论文/项目/发布。多个 pipeline 文件可以聚集在一个 pipeline 文件夹中,就像
src/diffusers/pipelines/stable-diffusion
所做的那样。如果 pipelines 共享相似的功能,可以利用# Copied from 机制。 - 所有 pipelines 都继承自 DiffusionPipeline。
- 每个 pipeline 由不同的模型和调度器组件组成,这些组件在
model_index.json
文件中记录,并可以通过与 pipeline 属性相同的名称访问,并且可以使用DiffusionPipeline.components
函数在 pipelines 之间共享。 - 每个 pipeline 都应该可以通过
DiffusionPipeline.from_pretrained
函数加载。 - Pipelines 应该**仅**用于推理。
- Pipelines 应该非常易读、自解释,并且易于调整。
- Pipelines 应该被设计成可以相互构建,并且易于集成到更高级别的 API 中。
- Pipelines **不**旨在成为功能完备的用户界面。对于功能完备的用户界面,应参考InvokeAI、Diffuzers 和 lama-cleaner。
- 每个 pipeline 都应该有且只有一种通过
__call__
方法运行它的方式。__call__
参数的命名应该在所有 pipelines 中共享。 - Pipelines 应该以它们旨在解决的任务命名。
- 在几乎所有情况下,新颖的扩散 pipelines 都应在新的 pipeline 文件夹/文件中实现。
模型
模型被设计为可配置的工具箱,是PyTorch 的 Module 类的自然扩展。它们只部分遵循**单文件策略**。
遵循以下设计原则:
- 模型对应于**一种模型架构类型**。例如,UNet2DConditionModel 类用于所有期望 2D 图像输入并以某些上下文为条件的 U-Net 变体。
- 所有模型都可以在
src/diffusers/models
中找到,并且每种模型架构都应在其自己的文件中定义,例如unets/unet_2d_condition.py
、transformers/transformer_2d.py
等。 - 模型**不**遵循单文件策略,应该利用更小的模型构建块,例如
attention.py
、resnet.py
、embeddings.py
等。**注意**:这与 Transformers 的建模文件形成鲜明对比,表明模型并不真正遵循单文件策略。 - 模型旨在暴露复杂性,就像 PyTorch 的
Module
类一样,并提供清晰的错误消息。 - 所有模型都继承自
ModelMixin
和ConfigMixin
。 - 当不要求重大代码更改、保持向后兼容性并能带来显著的内存或计算增益时,模型可以进行性能优化。
- 模型默认应该具有最高精度和最低性能设置。
- 要集成新的模型检查点,如果其通用架构可以归类为 Diffusers 中已有的架构,则应调整现有模型架构以使其与新检查点兼容。只有当模型架构根本不同时,才应创建新文件。
- 模型的设计应易于扩展以适应未来的变化。这可以通过限制公共函数参数、配置参数以及“预见”未来的变化来实现,例如,添加可以轻松扩展到未来新类型的
string
“...type”参数通常比添加布尔型is_..._type
参数更好。为了使新的模型检查点能够工作,对现有架构的更改应尽可能少。 - 模型设计是在保持代码可读性和简洁性与支持许多模型检查点之间的一个困难权衡。对于大部分建模代码,应为新的模型检查点调整类,但在某些情况下,为了确保代码长期保持简洁和可读,最好添加新的类,例如U-Net 块和注意力处理器。
调度器
调度器负责在推理过程中引导去噪过程,以及为训练定义噪声调度。它们被设计为具有可加载配置文件的独立类,并严格遵循**单文件策略**。
遵循以下设计原则:
- 所有调度器都可以在
src/diffusers/schedulers
中找到。 - 调度器**不**允许从大型工具文件中导入,并且应保持非常自包含。
- 一个调度器 Python 文件对应一个调度器算法(可能在论文中定义)。
- 如果调度器共享相似的功能,我们可以利用
# Copied from
机制。 - 所有调度器都继承自
SchedulerMixin
和ConfigMixin
。 - 调度器可以通过
ConfigMixin.from_config
方法轻松更换,具体细节在这里有详细解释。 - 每个调度器都必须有一个
set_num_inference_steps
和一个step
函数。set_num_inference_steps(...)
必须在每个去噪过程之前调用,即在调用step(...)
之前。 - 每个调度器通过一个
timesteps
属性暴露要“循环遍历”的时间步长,这是一个模型将被调用的时间步长数组。 step(...)
函数接受一个预测的模型输出和“当前”样本 (x_t),并返回“前一个”、稍微去噪更多的样本 (x_t-1)。- 鉴于扩散调度器的复杂性,
step
函数不暴露所有复杂性,可能有点像一个“黑匣子”。 - 在几乎所有情况下,新颖的调度器都应在新的调度文件中实现。