Transformers documentation

Exportadores

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

and get access to the augmented documentation experience

to get started

Exportadores

Exporta cualquier PreTrainedModel a ONNX, ExecuTorch o un programa de PyTorch independiente, sin importar el tiempo de ejecución objetivo.

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

Los exportadores se encuentran dentro de Transformers en lugar de en una biblioteca derivada, por lo que los cambios de arquitectura, los nuevos patrones de atención y los tipos de caché personalizados se admiten en el momento de la exportación en cuanto se incorporan al código de modelado.

Los exportadores son experimentales. Muchas de las correcciones de este módulo resuelven errores específicos de las bibliotecas de origen (Torch, ONNX Script, ONNX Runtime, ExecuTorch) y se eliminarán en cuanto se incorpore la solución en el código de origen. Hasta que la API se estabilice, considere las correcciones como vinculadas a las versiones utilizadas en el conjunto de pruebas. Fije esas versiones en las herramientas de producción y espere que aparezcan nuevas correcciones y desaparezcan las antiguas a medida que se incorporen los cambios en el código de origen.

ExportadorSalidaTiempo de ejecución
DynamoExporterExportedProgramCualquier entorno de ejecución de PyTorch, compilación AOT
OnnxExporterONNXProgramCualquier runtime de ONNX (ORT, TensorRT, OpenVINO)
ExecutorchExporterExecutorchProgramManagerDispositivos móviles y de borde (ExecuTorch)
OpenVINOExporteropenvino.ModelRuntime OpenVINO (CPU/GPU/NPU de Intel)

AutoHfExporter selecciona el exportador adecuado a partir de una configuración, y AutoExportConfig selecciona la clase de configuración adecuada a partir de un diccionario. Ambos siguen el mismo patrón de clase automática en Transformers, lo cual es útil cuando el backend se selecciona en tiempo de ejecución en lugar de estar codificado de forma fija en el punto de llamada.

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)

Instalación

Instale las dependencias del backend al que planea exportar.

Las versiones siguientes son las que tiene fijada la suite de pruebas del exportador. Las versiones más nuevas o más antiguas suelen funcionar, pero los parches del exportador apuntan a una superficie de API específica, por lo que para herramientas de producción fije estas versiones y espere que HfExporter registre una advertencia cuando detecte una desviación.

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

Exportar un modelo

Todos los exportadores comparten la misma interfaz. Crea un exportador con una configuración y llama a export().

Cambie entre runtimes cambiando la clase de exportador.

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)

Formas dinámicas

Pasar dynamic=True marca cada dimensión de tensor como dinámica, de modo que el grafo exportado acepte entradas de cualquier tamaño en tiempo de ejecución sin volver a trazar.

Para un control más detallado sobre qué dimensiones son dinámicas, pase explícitamente dynamic_shapes en su lugar, que se reenvía directamente a 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)

Modelos generativos

Para la generación autoregresiva, el forward del modelo tiene formas diferentes en el paso de prellenado (prompt completo, sin caché KV) frente al paso de decodificación (un solo token, caché KV poblada). Los exportadores exponen ~HfExporter.export_for_generation, que divide ambas etapas y exporta cada una.

Para los modelos generativos multimodales, el prefill se divide además en un codificador de imagen o audio, el modelo de lenguaje y lm_head. La detección del codificador y del modelo de lenguaje utiliza los accesores get_encoder() (modality="image" o "audio") y get_decoder(), de modo que cualquier nueva arquitectura que utilice estos funciona sin necesidad de configuración adicional.

Un componente de proyector solo aparece cuando el modelo expone uno bajo un nombre de atributo (multi_modal_projector, connector, embed_vision, embed_audio). Qwen2-VL, a continuación, integra su proyector en la torre de visión, por lo que su diccionario de componentes no tiene una clave separada multi_modal_projector. Las nuevas arquitecturas deben alinear su atributo de proyector con uno de estos nombres en lugar de ampliar la lista.

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}

Los componentes exportados son grafos independientes, no un pipeline de inferencia listo para ejecutarse. El responsable de ejecutar cada codificador, proyectar los embeddings y orquestar el bucle de generación es el llamador.

Cómo funciona export_for_generation

decompose_for_generation() ejecuta model.generate(**inputs, max_new_tokens=2) una vez y engancha model.forward para capturar los kwargs reales de prefill y decode (y los kwargs por submódulo mediante hooks en cada codificador/proyector/modelo de lenguaje si el modelo es multimodal). Por eso funciona con cualquier arquitectura, incluidos modelos solo decodificador, SSM, codificador-decodificador y multimodales, sin código de pegamento por modelo. export_for_generation es una línea sobre él.

La captura ejecuta el modelo de forma inmediata sobre inputs, por lo que se deben pasar valores pequeños pero representativos, como un prompt corto, una sola imagen pequeña o unos pocos fotogramas de audio. El programa exportado no está atado a esos tamaños (las formas dinámicas siguen fluyendo), pero las entradas de captura más pequeñas hacen que decompose_for_generation sea más barato y mantienen la inferencia de formas simbólicas manejable.

Llama a decompose_for_generation directamente para actuar entre la descomposición y la exportación, como ejecutar un forward eager para verificación, intercambiar las entradas de un submódulo o omitir una etapa.

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)

Decodificación de múltiples tokens

Por defecto, el componente decode es un paso de un solo token: un token de consulta contra la caché KV. Por lo tanto, torch.export especializa su eje de secuencia de consulta en 1. Pasa multi_token_decode=True para capturar decode como una decodificación de múltiples tokens: decompose_for_generation() fusiona dos pasos de decodificación consecutivos (se captura con max_new_tokens=3) en una sola pasada, de modo que ese eje se mantiene simbólico. Un único grafo sirve entonces para cualquier longitud de consulta: un token (decodificación ordinaria), muchos tokens a la vez (continuación desde el pasado, por ejemplo, al aceptar un bloque de tokens especulativos) y un prefill normal cuando la caché está vacía.

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

El eje de consulta solo permanece simbólico en una exportación con formas dinámicas (dynamic=True); una exportación estática lo fija en la longitud capturada, generando un grafo multi-token fijo. Se compone con la caché KV estática descrita a continuación: la decodificación fusionada escribe los tokens de cada paso en la caché de tamaño fijo in situ, y la caché gestiona internamente dónde se ubican.

Caché KV estática

Los ejemplos de ExecuTorch de esta sección, incluidas las actualizaciones sin copia y el bucle de decodificación, son exclusivos de XNNPACK. El exportador de MLX rechaza StaticCache; usa DynamicCache con MLX.

generate() crea por defecto una DynamicCache, realocando a medida que la secuencia se extiende: un objetivo en movimiento para un grafo exportado. Una caché estática es un búfer de tamaño fijo, asignado una sola vez y escrito in situ en la posición actual en cada paso. Combinado con un decodificado de múltiples tokens, colapsa la generación en un único grafo exportado: el grafo de decode toma una caché de tamaño fijo y un número variable de tokens de consulta, de modo que un solo grafo sirve tanto para el prompt (caché vacía → prefill) como para cada token generado (caché poblada → decode). Exporte esto reenviando una GenerationConfig con cache_implementation="static" (y un max_cache_len) junto a 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
)

El grafo de decode ahora tiene dos ejes simbólicos: la longitud de la consulta (cuántos tokens se introducen) y la longitud de la caché (max_cache_len, redimensionable en el momento de la carga). dynamic=True marca estos (y todos los demás ejes) como Dim.AUTO, de modo que el grafo exportado acepta cualquier longitud de prompt y tamaño de caché en el momento de la carga.

Actualizaciones in situ sin copia

La caché estática se pasa y se modifica in situ, por lo que un solo búfer lleva el estado a través de los pasos de decodificación sin copias en el host, siempre que el tiempo de ejecución enlace los búferes del llamador en lugar de copiarlos a través de su propia área. Lo que eso requiere es la única parte específica de cada backend que queda:

  • Dynamo — el programa exportado modela la escritura en la caché como una USER_INPUT_MUTATION, por lo que llamar a components["decode"].module()(...) actualiza directamente los tensores de caché que se pasan. Reutiliza los mismos tensores en cada paso; no hay nada que configurar.

  • ONNX Runtime — el grafo de decodificación expone la caché como pares correspondientes de input.<name> / output.<name>. El método CudaSession.set_buffer_sharing de ORT (onnxruntime.transformers.io_binding_helper) vincula cada par a un búfer de dispositivo, de modo que la caché se lee y actualiza in situ a lo largo del bucle, sin transiciones al host.

  • ExecuTorch (XNNPACK) — desactive las asignaciones de planificación de memoria en ExecutorchConfig para que la escritura in situ se realice en el tensor propio del llamador (consulte la referencia para ver qué hace cada flag):

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

    La escritura in-place sin copia también requiere que el llamador asigne los búferes de salida en tiempo de ejecución mediante Method::set_output_data_ptr — no expuesto por el runtime de Python (executorch.runtime.Method solo expone execute/set_inputs/get_outputs). Las banderas anteriores lo configuran, pero la escritura in-place es una ruta exclusiva de C++ (vea el ejemplo de bucle de decodificación de ExecuTorch a continuación). Desde Python, lea la caché actualizada desde las salidas del método en cada paso.

Decode-loop inference examples

El bucle tiene la misma forma en cada backend: es el mismo grafo en todo momento. Se parte de una caché de tamaño fijo vacía, se introduce el prompt completo una sola vez (caché vacía → prefill) y luego se procesa un token a la vez (caché poblada → decodificación). Cada llamada pasa input_ids, una attention_mask causal y position_ids (avanzadas en el número de tokens nuevos en cada paso), además de la caché, y devuelve logits para cada posición de consulta. La posición en la que cada token se ubica en la caché se rastrea internamente mediante la caché estática, por lo que no hay nada adicional que pasar a través de la llamada. La configuración de la caché varía según el runtime (un objeto StaticCache para Dynamo, buffers de dispositivo crudos para ONNX Runtime, matrices del llamador en C++ para ExecuTorch), por lo que cada pestaña construye la suya a continuación. Las pestañas de Dynamo y ONNX Runtime actualizan la caché in situ; la ruta in situ de ExecuTorch está en C++ (su runtime de Python no puede hacerlo, como se mencionó anteriormente).

Dynamo
ONNX Runtime
ExecuTorch XNNPACK

torch.export registra la escritura en la caché estática como USER_INPUT_MUTATION, por lo que el module() del grafo cargado actualiza la StaticCache que pasas directamente: una sola caché lleva el estado a lo largo de todo el bucle, sin necesidad de enlazar nada ni devolverlo. register_pytree_node(StaticCache) permite que torch.export.load descompacte la entrada StaticCache. La caché debe inicializarse de antemano (torch.export integra los K/V asignados en la especificación de entrada, por lo que una caché vacía perezosa no coincidirá), pero el programa guardado incluye sus propios example_inputs, así que reutiliza esa plantilla de StaticCache ya inicializada, restableciéndola a vacía:

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)

Cuantización

Cada configuración de exportación toma un quantizer que cuantiza el modelo durante la exportación, en el grafo FX de torch.export (PT2E o NNCF, para cada backend) o en el modelo ONNX u OpenVINO convertido (ONNX Runtime o NNCF). Consulte Cuantización de exportaciones para ver los cuantizadores, sus backends, la calibración y cómo añadir uno.

Limitaciones y soluciones

torch.export, torch.onnx.export y ExecuTorch presentan limitaciones con ciertos patrones específicos de PyTorch. Los exportadores resuelven estas limitaciones mediante un conjunto reducido de parches reversibles y correcciones a nivel de FX aplicados en puntos bien definidos del flujo de exportación. Nada de esto es visible desde la API pública export, pero lo más común que conviene saber es lo siguiente:

  • FlashAttention y FlexAttention no son exportables en ningún backend. sdpa es la configuración preferida y eager también funciona (más lento). Establece uno de ellos en el modelo antes de llamar a export si está usando otro.
  • grouped_mm se traza sin problemas a través de DynamoExporter y se traduce automáticamente para OnnxExporter. Para ExecutorchExporter con XNNPACK o MLX, el exportador cambia los expertos MoE a batched_mm antes de la exportación.

Próximos pasos

Update on GitHub