Diffusers documentation
Philosophie
Philosophie
🧨 Diffusers fournit des modèles de diffusion préentraînés de pointe sur plusieurs modalités. Son objectif est de servir de boîte à outils modulaire pour l'inférence et l'entraînement.
Nous visons à créer une bibliothèque qui résistera à l'épreuve du temps et prenons donc très au sérieux la conception de l'API.
En résumé, Diffusers est conçu pour être une extension naturelle de PyTorch. Par conséquent, la plupart de nos choix de conception reposent sur les principes de conception de PyTorch. Passons en revue les plus importants :
Privilégier l'ergonomie à la performance
- Bien que Diffusers intègre de nombreuses fonctionnalités d’optimisation des performances (voir Mémoire et vitesse), les modèles sont toujours chargés avec la plus haute précision et la plus faible optimisation. Par conséquent, par défaut, les pipelines de diffusion sont toujours instanciés sur CPU avec une précision float32, sauf si l’utilisateur en définit autrement. Cela garantit une utilisabilité sur différentes plateformes et accélérateurs et signifie qu’aucune installation complexe n’est requise pour exécuter la bibliothèque.
- Diffusers vise à être un package léger et a donc très peu de dépendances obligatoires, mais de nombreuses dépendances optionnelles qui peuvent améliorer les performances (telles que
accelerate,safetensors,onnx, etc…). Nous nous efforçons de garder la bibliothèque aussi légère que possible afin qu’elle puisse être ajoutée sans grande préoccupation en tant que dépendance d’autres packages. - Diffusers privilégie un code simple et auto-explicatif plutôt qu'un code condensé et « magique ». Cela signifie que les syntaxes abrégées, telles que les fonctions lambda, ainsi que les opérateurs avancés de PyTorch sont souvent indésirables.
Simple, cuit à point
Comme le souligne PyTorch, l’explicite est préférable à l’implicite et le simple est préférable au complexe. Cette philosophie de conception se reflète dans plusieurs parties de la bibliothèque :
- Nous suivons l’API de PyTorch avec des méthodes comme
DiffusionPipeline.topour laisser à l’utilisateur la gestion des appareils. - Il est préférable de lever des messages d’erreur concis plutôt que de corriger silencieusement une entrée erronée. Diffusers vise à enseigner à l’utilisateur, plutôt qu’à rendre la bibliothèque aussi facile à utiliser que possible.
- La logique complexe du modèle par rapport à celle du planificateur est exposée plutôt que gérée de manière magique en interne. Les planificateurs/échantillonneurs sont séparés des modèles de diffusion, avec des dépendances minimales entre eux. Cela oblige l'utilisateur à écrire la boucle de débruitage déroulée. Toutefois, cette séparation facilite le débogage et offre à l'utilisateur un meilleur contrôle pour adapter le processus de débruitage ou remplacer les modèles de diffusion ou les planificateurs.
- Les composants entraînés séparément du pipeline de diffusion, par exemple l’encodeur de texte, l’unet et l’auto-encodeur variationnel, disposent chacun de leur propre classe de modèle. Cela oblige l’utilisateur à gérer l’interaction entre les différents composants du modèle, et le format de sérialisation sépare les composants du modèle dans des fichiers distincts. Toutefois, cela facilite le débogage et la personnalisation. L’entraînement DreamBooth ou Textual Inversion est très simple grâce à la capacité de Diffusers à séparer les composants individuels du pipeline de diffusion.
Abstraction modifiable et conviviale pour les contributeurs
Pour une grande partie de la bibliothèque, Diffusers adopte un principe de conception important de la bibliothèque Transformers, qui consiste à privilégier le code copié-collé plutôt que des abstractions hâtives. Ce principe de conception est très tranché et contraste fortement avec des principes de conception populaires tels que Ne vous répétez pas (DRY). En bref, tout comme Transformers le fait pour les fichiers de modélisation, Diffusers préfère maintenir un niveau d'abstraction extrêmement bas et un code très autonome pour les pipelines et les planificateurs. Les fonctions, les longs blocs de code et même les classes peuvent être copiés dans plusieurs fichiers, ce qui peut sembler au premier abord un choix de conception négligé et de mauvaise qualité, rendant la bibliothèque intenable. Cependant, cette conception s'est avérée extrêmement réussie pour Transformers et a beaucoup de sens pour les bibliothèques d'apprentissage automatique open source et communautaires, car :
- Le Machine Learning est un domaine en évolution extrêmement rapide, dans lequel les paradigmes, les architectures de modèles et les algorithmes changent constamment, ce qui rend donc très difficile la définition d’abstractions de code pérennes.
- Les praticiens du Machine Learning apprécient de pouvoir ajuster rapidement du code existant pour l’idéation et la recherche, et préfèrent donc un code autonome à un code contenant de nombreuses abstractions.
- Les bibliothèques open source s’appuient sur les contributions de la communauté et doivent donc construire une bibliothèque facile à laquelle contribuer. Plus le code est abstrait, plus il y a de dépendances, plus il est difficile à lire et à faire évoluer. Les contributeurs cessent simplement de contribuer aux bibliothèques très abstraites par peur de casser des fonctionnalités vitales. Si contribuer à une bibliothèque ne peut pas casser d’autres code fondamentaux, non seulement c’est plus accueillant pour les nouveaux contributeurs potentiels, mais c’est aussi plus facile à examiner et à faire évoluer sur plusieurs parties en parallèle.
Chez Hugging Face, nous appelons cette conception la politique du fichier unique, ce qui signifie que la quasi-totalité du code d'une classe donnée doit être écrite dans un fichier unique et autonome. Pour en savoir plus sur cette philosophie, vous pouvez consulter ce billet de blog.
Dans Diffusers, nous appliquons cette philosophie aux pipelines, aux planificateurs et aux modèles. Certains modèles plus anciens antérieurs à cette convention sont conservés tels quels ; toutes les nouvelles architectures de modèles sont regroupées dans des fichiers autonomes. Consultez la section Models ci-dessous pour plus de détails.
Parfait, vous devriez maintenant avoir une idée générale de la raison pour laquelle 🧨 Diffusers est conçu ainsi 🤗. Nous cherchons à appliquer ces principes de conception de manière cohérente dans toute la bibliothèque. Néanmoins, il existe quelques exceptions mineures à la philosophie ou certains choix de conception malheureux. Si vous avez des retours concernant la conception, nous serions ❤️ ravis de les entendre directement sur GitHub.
Détails de la philosophie de conception
Passons maintenant aux détails concrets de la philosophie de conception. Diffusers vous offre deux façons de composer des modèles et des schedulers en un workflow exécutable : les pipelines standard, qui sont monolithiques avec une tâche par classe de pipeline, et Modular Diffusers, qui est composable et basé sur des blocs. Les sections ci-dessous couvrent successivement les pipelines, les pipelines modulaires, les modèles et les schedulers.
Pipelines
Les pipelines, qu’ils soient standard ou modulaires, sont destinés uniquement à l’inférence. Ils sont conçus pour être faciles à utiliser (ils ne suivent donc pas à 100 % le principe Simple over easy) : lisibles, explicites, faciles à ajuster, et à considérer avant tout comme des exemples d’utilisation des modèles et des schedulers. Ils ne sont pas exhaustifs en termes de fonctionnalités. Pour construire des interfaces utilisateur complètes sur la base de Diffusers, utilisez Modular Diffusers.
Les principes de conception suivants sont appliqués :
- Les pipelines suivent la politique du fichier unique. Tous les pipelines se trouvent dans des répertoires individuels sous src/diffusers/pipelines. Un dossier de pipeline correspond à un article, projet ou release de diffusion. Plusieurs fichiers de pipeline peuvent être regroupés dans un même dossier de pipeline, comme c’est le cas pour
src/diffusers/pipelines/stable-diffusion. Si des pipelines partagent des fonctionnalités similaires, on peut utiliser le mécanisme # Copied from. - Tous les pipelines héritent de DiffusionPipeline.
- Chaque pipeline est composé de différents composants de modèle et de planificateur, documentés dans le fichier
model_index.json, accessibles sous le même nom en tant qu'attributs du pipeline et partageables entre les pipelines à l'aide de la fonctionDiffusionPipeline.components. - Chaque pipeline doit être chargeable via la fonction
DiffusionPipeline.from_pretrained. - Chaque pipeline doit disposer d'une et d'une seule méthode pour l'exécuter, à savoir la méthode
__call__. La nomenclature des arguments de__call__doit être cohérente entre tous les pipelines. - Les pipelines doivent être nommés d'après la tâche qu'ils sont censés résoudre.
Diffuseurs modulaires
Modular Diffusers est l’alternative composable aux pipelines standard. Vous construisez un flux de travail à partir de blocs de pipeline réutilisables que vous pouvez combiner, assembler, remplacer et partager. Les pipelines standard sont des exemples de référence non contraignants sur l’utilisation des modèles et des planificateurs. Modular Diffusers est la voie recommandée pour construire des interfaces utilisateur complètes sur la base de Diffusers, et pour permettre à la communauté de créer et de partager de nouveaux pipelines de manière décentralisée.
Les principes de conception suivants sont appliqués :
- Les pipelines modulaires suivent la politique du fichier unique. Chacun est situé dans son propre dossier sous src/diffusers/modular_pipelines/, et le dossier répartit le flux de travail sur un fichier par étape :
encoders.py,before_denoise.py,denoise.pyetdecoders.py. Deux fichiers supplémentaires complètent le pipeline.modular_blocks_<model>.pyassemble les étapes, etmodular_pipeline.pydéfinit la sous-classe ModularPipeline propre à chaque modèle. Les pipelines modulaires n’importent pas les uns des autres. - Each modular pipeline is defined as a set of ModularPipelineBlocks. Leaf blocks live in the stage files (
encoders.py,before_denoise.py,denoise.py,decoders.py), andmodular_blocks_<model>.pyassembles them into the full workflow with container classes like SequentialPipelineBlocks and AutoPipelineBlocks. This splits apart two concepts thatDiffusionPipeline 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. - Pour prendre en charge une nouvelle tâche, écrivez les blocs spécifiques à la tâche, composez-les avec les blocs existants et enregistrez le flux de travail dans
_workflow_mapsur l’assemblage de blocs de niveau supérieur. Un seulModularPipelinepeut prendre en charge de nombreux flux de travail, tels que text-to-image, image-to-image et inpainting, tandis qu’unDiffusionPipelinen’exécute qu’un seul flux de travail.
Consultez la documentation de Modular Diffusers pour le guide complet de conception et d'utilisation.
Modèles
Les modèles sont conçus comme des boîtes à outils configurables, qui constituent des extensions naturelles de la classe Module de PyTorch. Ils doivent respecter la politique du fichier unique. Certains modèles plus anciens antérieurs à cette convention sont conservés en l’état. Considérez-les comme des exceptions de legacy, et non comme des modèles à suivre pour les nouveaux modèles. Par exemple, la classe UNet2DConditionModel d’origine était utilisée pour plusieurs variations de UNet.
Les principes de conception suivants sont appliqués :
- Chaque type d’architecture de modèle se trouve dans son propre dossier sous src/diffusers/models, par exemple transformers/, autoencoders/ ou unets/. Chaque famille de modèles possède son propre fichier dans ce dossier, comme transformer_flux.py et transformer_wan.py.
- Les modèles suivent la politique du fichier unique. Chaque fichier de modèle doit être autonome, à l'exception d'un petit nombre de modules standard que chaque modèle utilise de manière identique, tels que les embeddings de timestep et les couches de normalisation. Importez-les depuis embeddings.py et normalization.py.
- Les modèles visent à exposer la complexité, tout comme la classe
Modulede PyTorch, et à fournir des messages d'erreur clairs. - Tous les modèles héritent de
ModelMixinetConfigMixin. - Les modèles peuvent être optimisés pour les performances lorsque cela ne nécessite pas de modifications majeures du code, préserve la compatibilité ascendante et procure un gain significatif en mémoire ou en calcul.
- Les modèles doivent par défaut avoir la plus haute précision et le réglage de performance le plus bas.
- Pour intégrer une nouvelle architecture de modèle similaire à une existante, copiez le fichier existant comme point de départ et adaptez-le. Utilisez les annotations
# Copied fromsur les couches qui restent identiques afin quemake fix-copiesles maintienne synchronisées.
Planificateurs
Les Schedulers sont chargés de guider le processus de débruitage lors de l'inférence ainsi que de définir un calendrier de bruit pour l'entraînement. Ils sont conçus sous forme de classes individuelles avec des fichiers de configuration chargeables et suivent strictement la politique du fichier unique.
Les principes de conception suivants sont appliqués :
- Tous les planificateurs se trouvent dans
src/diffusers/schedulers. - Les Schedulers ne sont pas autorisés à importer depuis les fichiers utils volumineux et doivent rester très autonomes.
- Un fichier Python de planificateur correspond à un algorithme de planificateur (tel qu'il peut être défini dans un article).
- Si les planificateurs partagent des fonctionnalités similaires, nous pouvons utiliser le mécanisme
# Copied from. - Les planificateurs héritent tous de
SchedulerMixinet deConfigMixin. - Les planificateurs peuvent être facilement remplacés à l'aide de la méthode
ConfigMixin.from_config, comme expliqué en détail ici. - Chaque planificateur doit disposer d'une fonction
set_num_inference_stepset d'une fonctionstep.set_num_inference_steps(...)doit être appelé avant chaque processus de débruitage, c'est-à-dire avant l'appel destep(...). - Chaque planificateur expose les pas de temps à parcourir via l'attribut
timesteps, qui est un tableau des pas de temps pour lesquels le modèle sera appelé. - La fonction
step(...)prend en entrée la sortie prédite du modèle et l'échantillon « actuel » (x_t), puis renvoie l'échantillon « précédent », légèrement moins bruité (x_t-1). - Compte tenu de la complexité des planificateurs de diffusion, la fonction
stepn’expose pas toute cette complexité et peut apparaître comme une sorte de « boîte noire ». - Dans presque tous les cas, les nouveaux planificateurs doivent être implémentés dans un nouveau fichier de planification.