Transformers documentation

Exportateurs

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Exportateurs

Exportez n'importe quel PreTrainedModel vers ONNX, ExecuTorch ou un programme PyTorch autonome, quel que soit le runtime cible.

exporter = DynamoExporter()  # or OnnxExporter, ExecutorchExporter
config = DynamoConfig(dynamic=True)
exported = exporter.export(model, inputs, config=config)

Les exportateurs sont intégrés à Transformers plutôt qu’à une bibliothèque en aval, de sorte que les modifications d’architecture, les nouveaux motifs d’attention et les types de cache personnalisés sont pris en charge lors de l’exportation dès qu’ils sont intégrés au code de modélisation.

Les exportateurs sont expérimentaux. De nombreux correctifs de ce module contournent des bogues spécifiques en amont (Torch, ONNX Script, ONNX Runtime, ExecuTorch) et seront supprimés dès que le correctif sera intégré en amont. Jusqu’à ce que l’API se stabilise, considérez les correctifs comme liés aux versions utilisées dans la suite de tests. Fixez ces versions dans les outils de production, et attendez-vous à ce que de nouveaux correctifs apparaissent et que d’anciens disparaissent au fur et à mesure que les modifications en amont sont intégrées.

ExporterSortieRuntime
DynamoExporterExportedProgramTout runtime PyTorch, compilation AOT
OnnxExporterONNXProgramTout runtime ONNX (ORT, TensorRT, OpenVINO)
ExecutorchExporterExecutorchProgramManagerAppareils mobiles et périphériques (ExecuTorch)
OpenVINOExporteropenvino.ModelRuntime OpenVINO (CPU/GPU/NPU Intel)

AutoHfExporter sélectionne l’exporteur approprié à partir d’une configuration, et AutoExportConfig sélectionne la classe de configuration appropriée à partir d’un dictionnaire. Les deux suivent le même motif de classe automatique que dans Transformers, ce qui est utile lorsque le backend est sélectionné à l’exécution plutôt qu’encodé en dur au point d’appel.

from transformers.exporters import AutoExportConfig, AutoHfExporter

export_config_dict = {"export_format": "onnx", "dynamic": True}
config = AutoExportConfig.from_dict(export_config_dict)
exporter = AutoHfExporter.from_config(config)

onnx_program = exporter.export(model, inputs, config=config)

Installation

Installez les dépendances pour le backend vers lequel vous prévoyez d'exporter.

Les versions ci-dessous sont celles auxquelles la suite de tests de l’exportateur est verrouillée. Des versions plus récentes ou plus anciennes fonctionnent souvent, mais les correctifs de l’exportateur ciblent une surface d’API spécifique ; pour les outils de production, verrouillez ces versions et attendez-vous à ce que HfExporter journalise un avertissement lorsqu’il détecte un écart.

Dynamo
ONNX
ExecuTorch XNNPACK
ExecuTorch MLX
OpenVINO
pip install transformers "torch==2.12.0"

Exporter un modèle

Tous les exportateurs partagent la même interface. Créez un exportateur avec une configuration, puis appelez export().

Basculez entre les environnements d’exécution en changeant la classe d’exportation.

Dynamo
ONNX
ExecuTorch
OpenVINO
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import DynamoExporter, DynamoConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer("Hello, world!", return_tensors="pt")

exporter = DynamoExporter()
config = DynamoConfig(dynamic=True)
exported = exporter.export(model, inputs, config=config)

# run the exported graph directly
outputs = exported.module()(**inputs)

Formes dynamiques

Passer dynamic=True marque chaque dimension de tenseur comme dynamique, de sorte que le graphe exporté accepte des entrées de n’importe quelle taille à l’exécution, sans retracage.

Pour un contrôle fin des dimensions dynamiques, passez plutôt des dynamic_shapes explicites, qui sont transmis directement à torch.export.export.

Dynamo
ONNX
ExecuTorch
OpenVINO
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import DynamoExporter, DynamoConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer(["Hello, world!", "Hi"], padding=True, return_tensors="pt")

batch = torch.export.Dim("batch", min=1, max=32)
seq = torch.export.Dim("seq", min=1, max=2048)

exporter = DynamoExporter()
config = DynamoConfig(
    dynamic_shapes={"input_ids": {0: batch, 1: seq}, "attention_mask": {0: batch, 1: seq}},
    # Emit data-dependent shape guards as runtime asserts instead of failing the export when a
    # guard wouldn't hold across the explicit symbolic range. Most LLMs need this under fine-grained
    # ``Dim(min=, max=)`` bounds. Not needed with ``dynamic=True`` / ``Dim.AUTO``, where torch.export
    # infers shape relations instead of verifying them against user-stated bounds.
    prefer_deferred_runtime_asserts_over_guards=True,
)
exported = exporter.export(model, inputs, config=config)

Modèles génératifs

Pour la génération autorégressive, la forme de la méthode forward du modèle diffère entre l'étape de préremplissage (prompt complet, sans cache KV) et l'étape de décodage (jeton unique, cache KV rempli). Les exportateurs exposent ~HfExporter.export_for_generation, qui sépare les deux étapes et exporte chacune d'elles.

Pour les modèles génératifs multi-modaux, le préremplissage se divise en outre en un encodeur d’image ou d’audio, le modèle de langage et lm_head. La découverte de l’encodeur et du modèle de langage utilise les accesseurs get_encoder() (modality="image" ou "audio") et get_decoder(), de sorte que toute nouvelle architecture utilisant ces composants fonctionne immédiatement.

Un composant de projecteur n’apparaît que lorsque le modèle en expose un sous un nom d’attribut (multi_modal_projector, connector, embed_vision, embed_audio). Qwen2-VL ci-dessous intègre son projecteur dans la tour de vision, de sorte que son dictionnaire de composants ne contient pas de clé multi_modal_projector distincte. Les nouvelles architectures doivent aligner l’attribut de leur projecteur sur l’un de ces noms plutôt que d’allonger la liste.

Dynamo
ONNX
ExecuTorch
OpenVINO
from transformers import AutoModelForImageTextToText, AutoProcessor
from transformers.exporters import DynamoExporter, DynamoConfig

model = AutoModelForImageTextToText.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
messages = [{"role": "user", "content": [{"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"}, {"type": "text", "text": "Describe this image."}]}]
text = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
inputs = processor(text=text, images=messages[0]["content"][0]["url"], return_tensors="pt").to(model.device)

exporter = DynamoExporter()
config = DynamoConfig(dynamic=True)
components = exporter.export_for_generation(model, inputs, config=config)
# components = {"image_encoder": ExportedProgram, "language_model": ExportedProgram, "lm_head": ExportedProgram, "decode": ExportedProgram}

Les composants exportés sont des graphes indépendants, et non un pipeline d'inférence prêt à l'emploi. L'appelant est responsable de l'exécution de chaque encodeur, de la projection des embeddings et de l'orchestration de la boucle de génération.

Comment fonctionne export_for_generation

decompose_for_generation() exécute model.generate(**inputs, max_new_tokens=2) une fois et installe des hooks sur model.forward pour capturer les kwargs réels de préfill et de décodage (ainsi que les kwargs par sous-module via des hooks sur chaque encodeur/projecteur/modèle de langage si le modèle est multimodal). C’est pourquoi cela fonctionne pour n’importe quelle architecture, y compris les modèles uniquement décodeur, SSM, encodeur-décodeur et multimodaux, sans code de liaison spécifique à chaque modèle. export_for_generation est un simple appel à cette fonction.

La capture exécute le modèle de manière immédiate sur inputs. Il est donc recommandé de transmettre des valeurs petites mais représentatives, telles qu’un court prompt, une seule petite image ou quelques trames audio. Le programme exporté n’est pas lié à ces tailles (les formes dynamiques continuent de circuler), mais des entrées de capture plus petites rendent decompose_for_generation moins coûteux et maintiennent l’inférence des formes symboliques gérable.

Appel direct de decompose_for_generation pour intervenir entre la décomposition et l’export, par exemple pour exécuter un passage avant en mode eager à des fins de vérification, remplacer les entrées d’un sous-module ou ignorer une étape.

from transformers.exporters.utils import decompose_for_generation

components = decompose_for_generation(model, inputs)
# {"image_encoder": (submodel, fwd_kwargs), "language_model": (...), ..., "decode": (...)}

exported = {}
for name, (submodel, subinputs) in components.items():
    eager_outputs = submodel(**subinputs)  # sanity-check the eager forward before exporting
    exported[name] = exporter.export(submodel, subinputs, config=config)

Décodage multi-jetons

Par défaut, le composant decode est une étape mono-jeton — un jeton de requête face au cache KV — de sorte que torch.export spécialise son axe de séquence de requête à 1. Passez multi_token_decode=True pour capturer decode comme un décodage multi-jetons à la place : decompose_for_generation() fusionne deux étapes de décodage consécutives (il capture avec max_new_tokens=3) en un seul forward, de sorte que cet axe reste symbolique. Un seul graphe sert alors toutes les longueurs de requête — un jeton (décodage ordinaire), plusieurs jetons à la fois (continuation à partir du passé, par exemple l’acceptation d’un bloc de jetons spéculatifs), et un préremplissage simple lorsque le cache est vide.

Dynamo
ONNX
ExecuTorch
from transformers.exporters import DynamoExporter, DynamoConfig

exporter = DynamoExporter()
config = DynamoConfig(dynamic=True)
components = exporter.export_for_generation(model, inputs, config=config, multi_token_decode=True)
# components["decode"] now accepts a variable number of query tokens

L’axe de requête ne reste symbolique que lors d’un export à forme dynamique (dynamic=True) ; un export statique le fige à la longueur capturée, produisant un graphe multi-jetons fixe. Il se combine avec le cache KV statique ci-dessous — le décodage fusionné écrit les jetons de chaque étape dans le cache de taille fixe sur place, et le cache gère en interne leur emplacement.

Cache KV statique

Les exemples ExecuTorch de cette section, y compris les mises à jour sans copie et la boucle de décodage, sont exclusifs à XNNPACK. L’exportateur MLX rejette StaticCache ; utilisez DynamicCache avec MLX.

generate() fait croître un DynamicCache par défaut, en réallouant à mesure que la séquence s’allonge — une cible mouvante pour un graphe exporté. Un cache statique est un tampon de taille fixe, alloué une seule fois et écrit sur place à la position courante à chaque étape. Combiné à un décodage multi-jetons, il réduit la génération à un seul graphe exporté : le graphe decode prend un cache de taille fixe et un nombre variable de jetons de requête, de sorte qu’un seul graphe sert à la fois le prompt (cache vide → préremplissage) et chaque jeton généré (cache rempli → décodage). Exportez-le en transmettant un GenerationConfig avec cache_implementation="static" (et un max_cache_len) aux côtés de multi_token_decode=True :

Dynamo
ONNX
ExecuTorch XNNPACK
from transformers import GenerationConfig
from transformers.exporters import DynamoExporter, DynamoConfig

exporter = DynamoExporter()
gen_config = GenerationConfig(cache_implementation="static", max_cache_len=2048)
components = exporter.export_for_generation(
    model, inputs, config=DynamoConfig(dynamic=True), generation_config=gen_config, multi_token_decode=True
)

Le graphe decode possède désormais deux axes symboliques — la longueur de la requête (le nombre de jetons fournis) et la longueur du cache (max_cache_len, redimensionnable au chargement). dynamic=True marque ces axes (ainsi que tous les autres) comme Dim.AUTO, de sorte que le graphe exporté accepte n'importe quelle longueur de prompt et n'importe quelle taille de cache au moment du chargement.

Mises à jour sur place sans copie

Le cache statique est transmis et modifié sur place, de sorte qu’un seul tampon conserve l’état entre les étapes de décodage sans copie hôte — à condition que le runtime lie les tampons de l’appelant plutôt que de copier via son propre arène. Ce que cela implique est la seule partie propre à chaque backend restante :

  • Dynamo — le programme exporté modélise l’écriture du cache comme une USER_INPUT_MUTATION, de sorte que l’appel de components["decode"].module()(...) met à jour directement les tenseurs de cache que vous transmettez. Réutilisez les mêmes tenseurs à chaque étape ; aucune configuration n’est nécessaire.

  • ONNX Runtime — le graphe de décodage expose le cache sous forme de paires appariées input.<name> / output.<name>. La méthode CudaSession.set_buffer_sharing d’ORT (onnxruntime.transformers.io_binding_helper) associe chaque paire à un tampon de périphérique unique, de sorte que le cache est lu et mis à jour sur place au fil de la boucle, sans aucun aller-retour avec l’hôte.

  • ExecuTorch (XNNPACK) — désactivez les allocations de planification mémoire sur ExecutorchConfig afin que l’écriture en place puisse se faire dans le tenseur de l’appelant (voir la référence pour connaître le rôle de chaque drapeau) :

    config = ExecutorchConfig(
        backend="xnnpack",
        dynamic=True,
        alloc_graph_input=False,
        alloc_graph_output=False,
        alloc_mutable_buffers=False,
    )

    L'écriture in-place sans copie nécessite également que l'appelant lie les tampons de sortie à l'exécution via Method::set_output_data_ptr — non exposé par le runtime Python (executorch.runtime.Method n'expose que execute/set_inputs/get_outputs). Les drapeaux ci-dessus le configurent, mais l'écriture in-place est un chemin exclusif au C++ (voir l'exemple de boucle de décodage ExecuTorch ci-dessous). Depuis Python, relisez le cache mis à jour depuis les sorties de la méthode à chaque étape.

Decode-loop inference examples

La boucle a la même forme sur chaque backend — c’est le même graphe tout au long. On part d’un cache de taille fixe vide, on alimente le prompt complet une seule fois (cache vide → préremplissage), puis un jeton à la fois (cache rempli → décodage). Chaque appel transmet input_ids, un attention_mask causal et position_ids (avancés du nombre de nouveaux jetons à chaque étape), ainsi que le cache, et renvoie les logits pour chaque position de requête. L’emplacement de chaque jeton dans le cache est suivi en interne par le cache statique, il n’y a donc rien d’autre à passer dans l’appel. La configuration du cache varie selon le runtime (un objet StaticCache pour Dynamo, des tampons bruts de l’appareil pour ONNX Runtime, des tableaux de l’appelant en C++ pour ExecuTorch), chaque onglet construit donc le sien ci-dessous. Les onglets Dynamo et ONNX Runtime mettent à jour le cache sur place ; le chemin sur place d’ExecuTorch est en C++ (son runtime Python ne le peut pas, comme indiqué plus haut).

Dynamo
ONNX Runtime
ExecuTorch XNNPACK

torch.export enregistre l’écriture du cache statique comme une USER_INPUT_MUTATION, de sorte que la méthode module() du graphe chargé met à jour le StaticCache que vous passez directement — un seul cache porte l’état sur l’ensemble de la boucle, sans rien à lier ni à renvoyer. register_pytree_node(StaticCache) permet à torch.export.load de déplier l’entrée StaticCache. Le cache doit être initialisé à l’avance (torch.export intègre les K/V alloués dans la spécification d’entrée, donc un cache vide initialisé paresseusement ne correspondra pas) — mais le programme enregistré contient ses propres example_inputs, il suffit donc de réutiliser ce modèle de StaticCache déjà initialisé, remis à zéro :

import copy
import torch
from transformers import StaticCache
from transformers.exporters.exporter_dynamo import register_pytree_node

register_pytree_node(StaticCache)
exported = torch.export.load("decode.pt2")
decode = exported.module()   # runs on the device its inputs / cache live on (CUDA here)

# the artifact carries an initialized StaticCache template — reuse it (reset to empty)
_, example_kwargs = exported.example_inputs
past_key_values = copy.deepcopy(example_kwargs["past_key_values"])
past_key_values.reset()

def causal_mask(positions, cache_len):   # [1, 1, len(positions), cache_len]
    return (torch.arange(cache_len, device="cuda")[None, :] <= positions[:, None])[None, None]

# prefill: the whole prompt in one call
positions = torch.arange(prompt_len, device="cuda")
logits = decode(input_ids=prompt_ids, attention_mask=causal_mask(positions, max_cache_len),
                position_ids=positions[None], past_key_values=past_key_values).logits
next_token = logits[:, -1:].argmax(-1)

# decode: query=1 buffers reused in place
input_ids = torch.empty((1, 1), dtype=torch.long, device="cuda")
position_ids = torch.empty((1, 1), dtype=torch.long, device="cuda")
attention_mask = torch.empty((1, 1, 1, max_cache_len), dtype=torch.bool, device="cuda")
slots = torch.arange(max_cache_len, device="cuda")
for position in range(prompt_len, max_cache_len):
    input_ids.copy_(next_token)
    position_ids.fill_(position)
    attention_mask[0, 0, 0].copy_(slots <= position)
    logits = decode(input_ids=input_ids, attention_mask=attention_mask,
                    position_ids=position_ids, past_key_values=past_key_values).logits
    next_token = logits[:, -1:].argmax(-1)

Quantification

Chaque configuration d’export prend un quantizer qui quantise le modèle pendant l’export, sur le graphe FX de torch.export (PT2E ou NNCF, pour chaque backend) ou sur le modèle ONNX ou OpenVINO converti (ONNX Runtime ou NNCF). Consultez Quantifier les exports pour les quantiseurs, leurs backends, la calibration et comment en ajouter un.

Limites et solutions de contournement

torch.export, torch.onnx.export et ExecuTorch présentent chacun des limites concernant certains motifs spécifiques de PyTorch. Les exporteurs contournent ces problèmes à l’aide d’un petit ensemble de correctifs réversibles et de corrections au niveau FX, appliqués à des points bien définis du flux d’exportation. Rien de tout cela n’est visible depuis l’API publique export, mais voici les points les plus courants à connaître :

  • FlashAttention et FlexAttention ne sont pas exportables sur aucun backend. sdpa est le paramètre recommandé et eager fonctionne également (plus lentement). Définissez l’un d’eux sur le modèle avant d’appeler export s’il utilise autre chose.
  • grouped_mm est tracé sans problème par DynamoExporter et est traduit automatiquement pour OnnxExporter. Pour ExecutorchExporter avec XNNPACK ou MLX, l’exportateur remplace les experts MoE par batched_mm avant l’exportation.
  • Les couches à fenêtre glissante (Mistral, Gemma 3) sont exportées avec un cache clé-valeur complet ; le masque glissant applique toujours la fenêtre.

Étapes suivantes

  • Ajoutez la prise en charge de l’exportation pour une nouvelle architecture ou un nouveau backend à l’aide des registres de correctifs et de modifications dans Étendre les exportateurs.
  • Quantifier un export, ou ajouter un quantificateur, dans Quantifier les exports.
Update on GitHub