Diffusers documentation
ModularPipeline
ModularPipeline
ModularPipeline convertit les ModularPipelineBlocks en un pipeline exécutable qui charge les modèles et effectue les étapes de calcul définies dans les blocs. C'est l'interface principale pour exécuter un pipeline et l'API est très similaire à celle de DiffusionPipeline, mais avec quelques différences clés.
Le chargement est différé. Avec DiffusionPipeline, from_pretrained() crée le pipeline et charge tous les modèles en même temps. Avec ModularPipeline, la création et le chargement sont deux étapes distinctes : from_pretrained() lit la configuration et sait où charger chaque composant, mais ne charge pas réellement les poids du modèle. Vous chargez les modèles plus tard avec load_components(), où vous passez les arguments de chargement tels que
dtypeetquantization_config.Deux façons de créer un pipeline. Vous pouvez utiliser from_pretrained() avec un dépôt de modèle diffusers existant — il mappe automatiquement sur les blocs de pipeline par défaut, puis convertit en ModularPipeline sans configuration supplémentaire. Vous pouvez consulter le modular_pipelines_directory pour voir quels modèles sont actuellement pris en charge. Vous pouvez également assembler votre propre pipeline à partir de ModularPipelineBlocks et le convertir avec la méthode init_pipeline() (voir Créer un pipeline pour plus de détails).
L’exécution du pipeline est identique. Une fois chargé, vous appelez le pipeline avec les mêmes arguments que d’habitude. Un seul ModularPipeline peut prendre en charge plusieurs flux de travail (texte-vers-image, image-vers-image, inpainting, etc.) lorsque les blocs du pipeline utilisent
AutoPipelineBlockspour sélectionner automatiquement le flux de travail en fonction de vos entrées.
Voici des exemples complets pour la génération d'images à partir de texte, la transformation d'image en image et l'inpainting avec SDXL.
import torch
from diffusers import ModularPipeline
pipeline = ModularPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0")
pipeline.load_components(dtype=torch.float16)
pipeline.to("cuda") # or "mps", "xpu", "cpu"
image = pipeline(prompt="Astronaut in a jungle, cold color palette, muted colors, detailed, 8k").images[0]
image.save("modular_t2i_out.png")import torch
from diffusers import ModularPipeline
from diffusers.utils import load_image
pipeline = ModularPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0")
pipeline.load_components(dtype=torch.float16)
pipeline.to("cuda") # or "mps", "xpu", "cpu"
url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/sdxl-text2img.png"
init_image = load_image(url)
prompt = "a dog catching a frisbee in the jungle"
image = pipeline(prompt=prompt, image=init_image, strength=0.8).images[0]
image.save("modular_i2i_out.png")import torch
from diffusers import ModularPipeline
from diffusers.utils import load_image
pipeline = ModularPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0")
pipeline.load_components(dtype=torch.float16)
pipeline.to("cuda") # or "mps", "xpu", "cpu"
img_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/sdxl-text2img.png"
mask_url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/sdxl-inpaint-mask.png"
init_image = load_image(img_url)
mask_image = load_image(mask_url)
prompt = "A deep sea diver floating"
image = pipeline(prompt=prompt, image=init_image, mask_image=mask_image, strength=0.85).images[0]
image.save("modular_inpaint_out.png")Ce guide vous montre comment créer un ModularPipeline, gérer ses composants et exécuter le pipeline.
Créer un pipeline
Il existe deux façons de créer un ModularPipeline. Assembler et créer un pipeline à partir de ModularPipelineBlocks avec init_pipeline(), ou charger un pipeline existant avec from_pretrained().
Vous pouvez également initialiser un ComponentsManager pour gérer le placement sur les périphériques et la gestion de la mémoire. Si vous n’avez pas besoin de déchargement automatique, vous pouvez passer cette étape et déplacer le pipeline vers votre périphérique manuellement avec pipeline.to("cuda").
Consultez la documentation de ComponentsManager pour plus de détails sur la manière dont il peut aider à gérer les composants à travers différents flux de travail.
init_pipeline
init_pipeline() convertit tout ModularPipelineBlocks en ModularPipeline.
Définissons un bloc minimal pour voir comment il fonctionne :
from transformers import CLIPTextModel
from diffusers.modular_pipelines import (
ComponentSpec,
ModularPipelineBlocks,
PipelineState,
)
class MyBlock(ModularPipelineBlocks):
@property
def expected_components(self):
return [
ComponentSpec(
name="text_encoder",
type_hint=CLIPTextModel,
pretrained_model_name_or_path="openai/clip-vit-large-patch14",
),
]
def __call__(self, components, state: PipelineState) -> PipelineState:
return components, stateAppel init_pipeline() pour le convertir en pipeline. L'attribut blocks du pipeline correspond aux blocs à partir desquels il a été créé — il détermine les entrées, les sorties et la logique de calcul attendues.
block = MyBlock() pipe = block.init_pipeline() pipe.blocks
MyBlock {
"_class_name": "MyBlock",
"_diffusers_version": "0.37.0.dev0"
}Les blocs sont mutables — vous pouvez librement ajouter, supprimer ou remplacer des blocs avant de créer un pipeline. Cependant, une fois un pipeline créé, modifier
pipeline.blocksn’aura aucun effet sur le pipeline, car il renvoie une copie. Si vous souhaitez une structure de blocs différente, créez un nouveau pipeline après avoir modifié les blocs.
Lorsque vous appelez init_pipeline() sans dépôt, il utilise le pretrained_model_name_or_path défini dans le ComponentSpec du bloc pour déterminer d’où charger chaque composant. L’affichage du pipeline montre la configuration de chargement des composants.
pipe
ModularPipeline {
"_blocks_class_name": "MyBlock",
"_class_name": "ModularPipeline",
"_diffusers_version": "0.37.0.dev0",
"text_encoder": [
null,
null,
{
"pretrained_model_name_or_path": "openai/clip-vit-large-patch14",
"revision": null,
"subfolder": "",
"type_hint": [
"transformers",
"CLIPTextModel"
],
"variant": null
}
]
}Si vous passez un dépôt à init_pipeline(), il remplace le chemin de chargement en comparant les composants de votre bloc avec la configuration du pipeline dans ce dépôt (model_index.json ou modular_model_index.json). Consultez Modular repository pour savoir comment les spécifications de chargement sont enregistrées et sauvegardées.
Dans l'exemple ci-dessous, le pretrained_model_name_or_path sera mis à jour vers "stabilityai/stable-diffusion-xl-base-1.0".
pipe = block.init_pipeline("stabilityai/stable-diffusion-xl-base-1.0")
pipe
ModularPipeline {
"_blocks_class_name": "MyBlock",
"_class_name": "ModularPipeline",
"_diffusers_version": "0.37.0.dev0",
"text_encoder": [
null,
null,
{
"pretrained_model_name_or_path": "stabilityai/stable-diffusion-xl-base-1.0",
"revision": null,
"subfolder": "text_encoder",
"type_hint": [
"transformers",
"CLIPTextModel"
],
"variant": null
}
]
}Si un composant de votre bloc n’existe pas dans le dépôt, il reste null et est ignoré lors de l’appel à load_components().
from_pretrained
from_pretrained() est une méthode pratique pour créer un ModularPipeline sans avoir à définir les blocs vous-même.
Il fonctionne avec trois types de dépôts.
Un dépôt diffusers standard. Passez n'importe quel dépôt de modèle pris en charge et il sera automatiquement mappé sur les blocs de pipeline par défaut. Les modèles actuellement pris en charge incluent SDXL, Wan, Qwen, Z-Image, Flux et Flux2.
from diffusers import ModularPipeline, ComponentsManager
components = ComponentsManager()
pipeline = ModularPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0", components_manager=components
)Un dépôt modulaire. Ces dépôts contiennent un modular_model_index.json qui spécifie l’emplacement de chargement de chaque composant — les composants peuvent provenir de dépôts différents et le dépôt modulaire lui-même peut ne contenir aucun poids de modèle. Par exemple, diffusers/flux2-bnb-4bit-modular charge un transformeur quantisé depuis un dépôt et les composants restants depuis un autre. Consultez Dépôt modulaire pour plus de détails sur le format.
from diffusers import ModularPipeline, ComponentsManager
components = ComponentsManager()
pipeline = ModularPipeline.from_pretrained(
"diffusers/flux2-bnb-4bit-modular", components_manager=components
)Un dépôt modulaire avec du code personnalisé. Certains dépôts incluent des blocs de pipeline personnalisés en plus de la configuration de chargement. Ajoutez trust_remote_code=True pour les charger. Consultez Blocs personnalisés pour savoir comment créer les vôtres.
from diffusers import ModularPipeline, ComponentsManager
components = ComponentsManager()
pipeline = ModularPipeline.from_pretrained(
"diffusers/Florence2-image-Annotator", trust_remote_code=True, components_manager=components
)Lorsque les blocs du pipeline définissent des workflows (consultez pipeline.blocks.available_workflows), passez workflow= pour ne conserver que les blocs de ce workflow — la même élagage que get_workflow(). Le pipeline ne déclare alors que les composants utilisés par ce workflow, et sa docstring décrit précisément les entrées de ce workflow.
pipeline = ModularPipeline.from_pretrained("Qwen/Qwen-Image", workflow="inpainting")Chargement des composants
Un ModularPipeline n’est pas instancié automatiquement avec des composants. Il charge uniquement la configuration et les spécifications des composants. Vous pouvez charger les composants avec load_components().
Cela chargera tous les composants qui disposent d'une spécification de chargement valide.
import torch
pipeline.load_components(dtype=torch.float16)Vous pouvez également charger des composants spécifiques par leur nom. L'exemple ci-dessous ne charge que le text_encoder.
pipeline.load_components(names=["text_encoder"], dtype=torch.float16)Sur un pipeline dont les blocs définissent des workflows, workflow= ne charge que les composants utilisés par ce workflow. Le pipeline conserve tous ses blocs, ce qui en fait la méthode pratique pour exécuter un même pipeline à travers plusieurs workflows : chaque appel n’ajoute que ce qui manque encore au nouveau workflow.
pipeline.load_components(workflow="inpainting", dtype=torch.float16)Après le chargement, l’affichage du pipeline indique les composants chargés — les deux premiers champs passent de null à la bibliothèque et à la classe du composant.
pipeline
# text_encoder is loaded - shows library and class
"text_encoder": [
"transformers",
"CLIPTextModel",
{ ... }
]
# unet is not loaded yet - still null
"unet": [
null,
null,
{ ... }
]Les arguments de mots-clés tels que dtype, variant, revision et quantization_config sont transmis à from_pretrained() pour chaque composant. Vous pouvez passer une valeur unique à appliquer à tous les composants, ou un dictionnaire pour définir des valeurs par composant.
# apply bfloat16 to all components
pipeline.load_components(dtype=torch.bfloat16)
# different dtypes per component
pipeline.load_components(dtype={"transformer": torch.bfloat16, "default": torch.float32})load_components() ne charge que les composants qui n’ont pas encore été chargés et qui disposent d’une spécification de chargement valide. Cela signifie que si vous avez déjà défini un composant sur le pipeline, un nouvel appel à load_components() ne le rechargera pas.
Mise à jour des composants
update_components() remplace un composant du pipeline par un nouveau. Lorsqu’un composant est mis à jour, les spécifications de chargement sont également mises à jour dans la configuration du pipeline et load_components() l’ignora lors des appels suivants.
Depuis AutoModel
Vous pouvez passer un objet de modèle chargé avec AutoModel.from_pretrained(). Les modèles chargés de cette manière sont automatiquement étiquetés avec leurs informations de chargement.
from diffusers import AutoModel
unet = AutoModel.from_pretrained(
"RunDiffusion/Juggernaut-XL-v9", subfolder="unet", variant="fp16", dtype=torch.float16
)
pipeline.update_components(unet=unet)Depuis ComponentSpec
Utilisez get_component_spec() pour obtenir une copie de la spécification actuelle du composant, la modifier, puis charger un nouveau composant.
unet_spec = pipeline.get_component_spec("unet")
# modify to load from a different repository
unet_spec.pretrained_model_name_or_path = "RunDiffusion/Juggernaut-XL-v9"
# load and update
unet = unet_spec.load(dtype=torch.float16)
pipeline.update_components(unet=unet)Vous pouvez également créer un ComponentSpec à partir de zéro.
Tous les composants ne sont pas chargés à partir de poids pré-entraînés — certains sont créés à partir d'une configuration (listée sous pipeline.config_component_names). Pour ceux-ci, utilisez create() au lieu de load().
guider_spec = pipeline.get_component_spec("guider")
guider_spec.config = {"guidance_scale": 5.0}
guider = guider_spec.create()
pipeline.update_components(guider=guider)Ou passez simplement l'objet directement.
from diffusers.guiders import ClassifierFreeGuidance
guider = ClassifierFreeGuidance(guidance_scale=5.0)
pipeline.update_components(guider=guider)Consultez le guide Guiders pour plus de détails sur les guiders disponibles et sur leur configuration.
Découper un pipeline en étapes
Les blocs étant composables, vous pouvez décomposer un pipeline et le reconstruire en pipelines distincts pour chaque étape. L'exemple ci-dessous montre comment séparer le bloc d'encodeur de texte du reste du pipeline, afin que vous puissiez encoder le prompt indépendamment et transmettre les embeddings au pipeline principal.
from diffusers import ModularPipeline, ComponentsManager
import torch
device = "cuda" # or "mps", "xpu", "cpu"
dtype = torch.bfloat16
repo_id = "black-forest-labs/FLUX.2-klein-4B"
# get the blocks and separate out the text encoder
blocks = ModularPipeline.from_pretrained(repo_id).blocks
text_block = blocks.sub_blocks.pop("text_encoder")
# use ComponentsManager to handle offloading across multiple pipelines
manager = ComponentsManager()
manager.enable_auto_cpu_offload(device=device)
# create separate pipelines for each stage
text_encoder_pipeline = text_block.init_pipeline(repo_id, components_manager=manager)
pipeline = blocks.init_pipeline(repo_id, components_manager=manager)
# encode text
text_encoder_pipeline.load_components(dtype=dtype)
text_embeddings = text_encoder_pipeline(prompt="a cat").get_by_kwargs("denoiser_input_fields")
# denoise and decode
pipeline.load_components(dtype=dtype)
output = pipeline(
**text_embeddings,
num_inference_steps=4,
).images[0]ComponentsManager gère la mémoire sur plusieurs pipelines. Contrairement aux stratégies de déchargement de DiffusionPipeline qui suivent un ordre fixe, ComponentsManager prend des décisions de déchargement dynamiquement à chaque passage avant d’un modèle, en fonction de la situation mémoire actuelle. Cela signifie qu’il fonctionne quel que soit le nombre de pipelines créés ou l’ordre dans lequel ils sont exécutés. Consultez le guide ComponentsManager pour plus de détails.
Si les étapes du pipeline partagent des composants (par exemple, le même VAE utilisé pour l’encodage et le décodage), vous pouvez utiliser update_components() pour passer un composant déjà chargé à un autre pipeline au lieu de le recharger.
Dépôt modulaire
Un dépôt est requis si les blocs de pipeline utilisent des composants pré-entraînés. Le dépôt fournit les spécifications de chargement et les métadonnées.
ModularPipeline fonctionne avec les dépôts diffusers classiques sans configuration supplémentaire. Vous pouvez toutefois créer un dépôt modulaire pour plus de flexibilité. Un dépôt modulaire contient un fichier modular_model_index.json qui comprend les 3 éléments suivants.
libraryetclassindiquent la bibliothèque d’origine du composant et sa classe. Si la valeur estnull, le composant n’a pas encore été chargé.loading_specs_dictcontient les informations nécessaires pour charger le composant, telles que le dépôt et le sous-dossier d’origine.
L'avantage principal d'un dépôt modulaire est que les composants peuvent être chargés depuis différents dépôts. Par exemple, diffusers/flux2-bnb-4bit-modular charge un transformeur quantifié depuis diffusers/FLUX.2-dev-bnb-4bit tout en chargeant les composants restants depuis black-forest-labs/FLUX.2-dev.
Pour convertir un dépôt diffusers classique en dépôt modulaire, créez le pipeline à l’aide du dépôt classique, puis poussez-le sur le Hub. Le dépôt enregistré contiendra un modular_model_index.json avec toutes les spécifications de chargement.
from diffusers import ModularPipeline
# load from a regular repo
pipeline = ModularPipeline.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0")
# push as a modular repository
pipeline.save_pretrained("local/path", repo_id="my-username/sdxl-modular", push_to_hub=True)Par défaut, save_pretrained() écrit chaque composant actuellement chargé que Diffusers peut sérialiser. Les composants non chargés, ou qui ne peuvent pas être sérialisés, ne sont pas écrits et conservent leurs spécifications de chargement existantes. Cela vous offre deux façons d'enregistrer, selon vos besoins.
Enregistrer une copie autonome
Chargez tous les composants, puis enregistrez-les. Chaque spécification pointe vers le résultat, de sorte qu'il se recharge entièrement depuis un seul endroit, y compris hors ligne.
pipe = ModularPipeline.from_pretrained("MiniMaxAI/MiniMax-H3")
pipe.load_components()
pipe.save_pretrained("path/to/local-copy")La destination enregistrée dans modular_model_index.json est l’endroit où save_pretrained() écrit : le save_directory pour une sauvegarde locale, ou le repo_id lorsque vous passez push_to_hub=True.
À partir de huggingface_hub>=1.32.0, les fichiers identiques basés sur Xet peuvent partager une même charge utile mise en cache entre différents dépôts du Hub. Cela inclut les fichiers individuels d'un point de contrôle fragmenté : un fragment ayant le même hachage Xet peut être réutilisé, même si les autres fragments diffèrent. Les deux téléchargements doivent utiliser le même répertoire de cache du Hub avec prise en charge des liens symboliques. Modifier le type de données, la sérialisation ou les limites des fragments peut changer les hachages des fichiers et empêcher la réutilisation. Cela permet d'économiser le trafic de téléchargement et l'espace disque, mais pas la mémoire utilisée par les composants chargés.
La mise en cache partagée est effectuée sur une base de meilleur effort. La définition de HF_HUB_DISABLE_SHARED_BLOBS=1 prend toujours en charge le chargement, mais met en cache des copies séparées. Les références aux composants existantes dans modular_model_index.json restent inchangées ; conservez les références externes pour les composants qui ne sont pas stockés dans le dépôt modulaire.
Mettre à jour un index modulaire existant
La migration est facultative : les références au dépôt d’origine réutilisent déjà ses fichiers en cache. Mettez à jour une référence si elle ne servait qu’à éviter les téléchargements en double et que vous souhaitez que ce composant soit chargé depuis le dépôt modulaire à la place.
Vérifiez d'abord que le dépôt modulaire contient la même configuration de composants ainsi que tous les fichiers de poids requis, y compris l'index des poids et chaque fragment pour un point de contrôle fragmenté. Les hachages Xet correspondants déterminent quels fichiers de poids peuvent réutiliser le cache partagé. Conservez les références aux composants absents ou chargés intentionnellement depuis un autre point de contrôle.
Modifiez uniquement la spécification de chargement pertinente dans une copie locale de modular_model_index.json. Par exemple, pour utiliser un VAE stocké sous vae/ dans le dépôt modulaire avec la même variante de poids :
import json
from pathlib import Path
index_path = Path("path/to/local-copy/modular_model_index.json")
index = json.loads(index_path.read_text())
spec = index["vae"][2]
spec["pretrained_model_name_or_path"] = "my-username/my-modular-repo"
spec.pop("repo", None)
spec["subfolder"] = "vae"
spec["revision"] = None
index_path.write_text(json.dumps(index, indent=2) + "\n")Cela supprime également le champ repo obsolète. Une révision du dépôt d’origine n’identifie pas une révision dans la destination : utilisez None pour la branche par défaut de la destination, ou fixez une révision de destination contenant le composant. Ajustez subfolder et variant si les noms de fichiers diffèrent.
Avant de publier l’index mis à jour, chargez-le avec ModularPipeline.from_pretrained("path/to/local-copy"), appelez load_components(names="vae") et vérifiez que le composant se charge avec les paramètres attendus. Téléchargez uniquement l’index modifié ; la réenregistrement des poids peut modifier leurs hachages et empêcher la réutilisation du cache.
Conserver les références aux composants existants
Chargez uniquement ce qui est nouveau (ou rien du tout). Seuls les composants chargés sont enregistrés ; le reste reste un pointeur vers son dépôt d’origine. Utilisez ce mode lorsque vous souhaitez remplacer un composant tout en continuant à charger les autres depuis leur dépôt d’origine. Par exemple, enregistrez un transformateur personnalisé tandis que les composants restants continuent de se charger depuis le dépôt de base.
pipe = ModularPipeline.from_pretrained("black-forest-labs/FLUX.2-dev")
pipe.update_components(transformer=my_custom_transformer) # the only component in memory
pipe.save_pretrained("local/path", repo_id="my-username/flux2-custom-transformer", push_to_hub=True)Passez overwrite_modular_index=False pour conserver les spécifications de chargement de modular_model_index.json en l’état. Un composant enregistré dont la spécification de chargement est vide est toujours complété avec la destination, car il n’y a rien à préserver.
Déplacer les fichiers de toute autre manière ne réécrit pas l’index. Une copie téléchargée avec hf download --local-dir fonctionne toujours : lorsqu’un pipeline est chargé depuis un répertoire local, chaque composant dont les fichiers sont présents dans ce répertoire est chargé depuis celui-ci plutôt que depuis le dépôt enregistré. Une copie téléversée avec hf upload continue de pointer vers l’ancien emplacement, il faut donc mettre à jour l’index manuellement dans ce cas.
Un dépôt modulaire peut également inclure des blocs de pipeline personnalisés sous forme de code Python. Cela vous permet de partager des blocs spécialisés qui ne sont pas natifs à Diffusers. Par exemple, diffusers/Florence2-image-Annotator contient des blocs personnalisés aux côtés de la configuration de chargement :
Florence2-image-Annotator/
├── block.py # Custom pipeline blocks implementation
├── config.json # Pipeline configuration and auto_map
├── mellon_config.json # UI configuration for Mellon
└── modular_model_index.json # Component loading specificationsLe fichier config.json contient une clé auto_map qui indique à ModularPipeline où trouver les blocs personnalisés :
{
"_class_name": "Florence2AnnotatorBlocks",
"auto_map": {
"ModularPipelineBlocks": "block.Florence2AnnotatorBlocks"
}
}Chargez des dépôts de code personnalisés avec trust_remote_code=True comme indiqué dans from_pretrained. Consultez Blocs personnalisés pour savoir comment créer et partager les vôtres.