PHILOSOPHY.md
🧨 Diffusers provides state-of-the-art pretrained diffusion models across multiple modalities. Its purpose is to serve as a modular toolbox for both inference and training.
We aim at building a library that stands the test of time and therefore take API design very seriously.
In a nutshell, Diffusers is built to be a natural extension of PyTorch. Therefore, most of our design choices are based on PyTorch's Design Principles. Let's go over the most important ones:
accelerate, safetensors, onnx, etc...). We strive to keep the library as lightweight as possible so that it can be added without much concern as a dependency on other packages.As PyTorch states, explicit is better than implicit and simple is better than complex. This design philosophy is reflected in multiple parts of the library:
DiffusionPipeline.to to let the user handle device management.For large parts of the library, Diffusers adopts an important design principle of the Transformers library, which is to prefer copy-pasted code over hasty abstractions. This design principle is very opinionated and stands in stark contrast to popular design principles such as Don't repeat yourself (DRY). In short, just like Transformers does for modeling files, Diffusers prefers to keep an extremely low level of abstraction and very self-contained code for pipelines and schedulers. Functions, long code blocks, and even classes can be copied across multiple files which at first can look like a bad, sloppy design choice that makes the library unmaintainable. However, this design has proven to be extremely successful for Transformers and makes a lot of sense for community-driven, open-source machine learning libraries because:
At Hugging Face, we call this design the single-file policy which means that almost all of the code of a certain class should be written in a single, self-contained file. To read more about the philosophy, you can have a look at this blog post.
In Diffusers, we follow this philosophy for pipelines, schedulers, and models alike. Some older models predate this convention and are kept as-is; all new model architectures live in their own self-contained files. See the Models section below for details.
Great, now you should have generally understood why 🧨 Diffusers is designed the way it is 🤗. We try to apply these design principles consistently across the library. Nevertheless, there are some minor exceptions to the philosophy or some unlucky design choices. If you have feedback regarding the design, we would ❤️ to hear it directly on GitHub.
Now for the nitty-gritty details of the design philosophy. Diffusers gives you two ways to compose models and schedulers into a runnable workflow: standard pipelines, which are monolithic with one task per pipeline class, and Modular Diffusers, which is composable and block-based. The sections below cover pipelines, modular pipelines, models, and schedulers in turn.
Pipelines, standard or modular, are intended only for inference. They're designed to be easy to use (so they don't follow Simple over easy 100%): readable, self-explanatory, easy to tweak, and best seen as examples of how to use models and schedulers. They aren't feature complete. To build feature-complete user interfaces on top of Diffusers, use Modular Diffusers.
The following design principles are followed:
src/diffusers/pipelines/stable-diffusion. If pipelines share similar functionality, one can make use of the # Copied from mechanism.DiffusionPipeline].model_index.json file, are accessible under the same name as attributes of the pipeline and can be shared between pipelines with DiffusionPipeline.components function.DiffusionPipeline.from_pretrained function.__call__ method. The naming of the __call__ arguments should be consistent across all pipelines.Modular Diffusers is the composable alternative to standard pipelines. You build a workflow from reusable pipeline blocks that you can mix, match, swap, and share. Standard pipelines are loose reference examples of how to use models and schedulers. Modular Diffusers is the recommended path for building feature-complete user interfaces on top of Diffusers, and for the community to build and share new pipelines in a decentralized way.
The following design principles are followed:
encoders.py, before_denoise.py, denoise.py, and decoders.py. Two more files complete the pipeline. modular_blocks_<model>.py assembles the stages, and modular_pipeline.py defines the per-model [ModularPipeline] subclass. Modular pipelines don't cross-import each other.ModularPipelineBlocks]. Leaf blocks live in the stage files (encoders.py, before_denoise.py, denoise.py, decoders.py), and modular_blocks_<model>.py assembles them into the full workflow with container classes like [SequentialPipelineBlocks] and [AutoPipelineBlocks]. This splits apart two concepts that [DiffusionPipeline combines]. A block is a pure definition. It declares inputs, outputs, and component dependencies, but holds no weights and can't run. A [ModularPipeline], created with .init_pipeline(repo_id), is the runnable counterpart. Keeping blocks stateless and weight-free is what makes them freely composable, swappable, and shareable across workflows._workflow_map on the top-level block assembly. A single ModularPipeline can support many workflows, such as text-to-image, image-to-image, and inpainting, whereas a DiffusionPipeline runs only one.See the Modular Diffusers documentation for the full design and usage guide.
Models are designed as configurable toolboxes that are natural extensions of PyTorch's Module class. They should follow the single-file policy. Some older models predate this convention and are kept as-is. Treat them as legacy exceptions, not patterns to follow for new models. For example, the original [UNet2DConditionModel] class was used for several UNet variations.
The following design principles are followed:
Module class, and give clear error messages.ModelMixin and ConfigMixin.# Copied from annotations on layers that remain identical so make fix-copies keeps them in sync.Schedulers are responsible to guide the denoising process for inference as well as to define a noise schedule for training. They are designed as individual classes with loadable configuration files and strongly follow the single-file policy.
The following design principles are followed:
src/diffusers/schedulers.# Copied from mechanism.SchedulerMixin and ConfigMixin.ConfigMixin.from_config method as explained in detail here.set_num_inference_steps, and a step function. set_num_inference_steps(...) has to be called before every denoising process, i.e. before step(...) is called.timesteps attribute, which is an array of timesteps the model will be called upon.step(...) function takes a predicted model output and the "current" sample (x_t) and returns the "previous", slightly more denoised sample (x_t-1).step function does not expose all the complexity and can be a bit of a "black box".