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