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

Exporte qualquer PreTrainedModel para ONNX, ExecuTorch ou um programa PyTorch independente, independentemente do runtime de destino.

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

Os exportadores estão localizados dentro do Transformers, em vez de em uma biblioteca de nível inferior, de modo que alterações de arquitetura, novos padrões de atenção e tipos de cache personalizados são suportados no momento da exportação assim que são incorporados ao código de modelagem.

Os exportadores são experimentais. Muitas das correções neste módulo contornam bugs específicos em projetos de origem (Torch, ONNX Script, ONNX Runtime, ExecuTorch) e serão removidas assim que a correção for incorporada à origem. Até que a API se estabilize, considere as correções vinculadas às versões usadas na suíte de testes. Fixe essas versões em ferramentas de produção e espere que novas correções surjam e as antigas desapareçam conforme as alterações de origem forem incorporadas.

ExportarSaídaTempo de execução
DynamoExporterExportedProgramQualquer runtime do PyTorch, compilação AOT
OnnxExporterONNXProgramQualquer runtime ONNX (ORT, TensorRT, OpenVINO)
ExecutorchExporterExecutorchProgramManagerDispositivos móveis e de borda (ExecuTorch)
OpenVINOExporteropenvino.ModelRuntime OpenVINO (CPU/GPU/NPU da Intel)

AutoHfExporter seleciona o exportador adequado a partir de uma configuração, e AutoExportConfig seleciona a classe de configuração adequada a partir de um dicionário. Ambos seguem o mesmo padrão de classe automática do Transformers, o que é útil quando o backend é selecionado em tempo de execução em vez de ser fixado no local da chamada.

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)

Instalação

Instale as dependências do backend para o qual você planeja exportar.

As versões abaixo são aquelas para as quais a suíte de testes do exportador é fixada. Lançamentos mais novos ou mais antigos geralmente funcionam, mas os patches do exportador visam uma superfície de API específica, portanto, para ferramentas de produção, fixe estas versões e espere que HfExporter registre um aviso quando detectar divergência.

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

Exportar um modelo

Todos os exportadores compartilham a mesma interface. Crie um exportador com uma configuração e chame export().

Alterne entre runtimes trocando a classe 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

Passar dynamic=True marca todas as dimensões do tensor como dinâmicas, de modo que o grafo exportado aceita entradas de qualquer tamanho em tempo de execução, sem necessidade de reprocessamento.

Para um controle mais detalhado sobre quais dimensões são dinâmicas, passe explicitamente dynamic_shapes, que é encaminhado diretamente para 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 a geração autoregressiva, o forward do modelo tem formas diferentes na etapa de prefill (prompt completo, sem cache de KV) em relação à etapa de decodificação (token único, cache de KV preenchido). Os exportadores expõem ~HfExporter.export_for_generation, que divide as duas etapas e exporta cada uma.

Para modelos generativos multimodais, o prefill é dividido adicionalmente em um codificador de imagem ou áudio, o modelo de linguagem e lm_head. A descoberta do codificador e do modelo de linguagem usa os acessores get_encoder() (modality="image" ou "audio") e get_decoder(), de modo que qualquer nova arquitetura que utilize esses componentes funciona imediatamente.

Um componente de projetor aparece apenas quando o modelo expõe um sob um nome de atributo (multi_modal_projector, connector, embed_vision, embed_audio). O Qwen2-VL abaixo incorpora seu projetor na torre de visão, de modo que o dicionário de componentes não possui uma chave separada multi_modal_projector. Novas arquiteturas devem alinhar o atributo do projetor a um desses nomes, em vez de expandir a 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}

Os componentes exportados são grafos independentes, não um pipeline de inferência pronto para execução. O chamador é responsável por executar cada codificador, projetar os embeddings e orquestrar o loop de geração.

Como o export_for_generation funciona

decompose_for_generation() executa model.generate(**inputs, max_new_tokens=2) uma vez e faz o hook de model.forward para capturar os kwargs reais de prefill e decode (e os kwargs por submódulo, por meio de hooks em cada encoder/projector/language model, caso o modelo seja multimodal). É por isso que ele funciona para qualquer arquitetura, incluindo modelos apenas de decoder, SSM, encoder-decoder e multimodais, sem a necessidade de código de adaptação específico para cada modelo. export_for_generation é uma linha única sobre ele.

A captura executa o modelo de forma imediata em inputs, portanto, passe valores pequenos, porém representativos, como um prompt curto, uma única imagem pequena ou alguns quadros de áudio. O programa exportado não fica vinculado a esses tamanhos (formas dinâmicas ainda fluem através dele), mas entradas de captura menores tornam decompose_for_generation mais barato e mantêm a inferência de formas simbólicas viável.

Chame decompose_for_generation diretamente para agir entre a decomposição e a exportação, como executar um forward eager para verificação, trocar as entradas de um submódulo ou pular uma 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)

Decodificação de múltiplos tokens

Por padrão, o componente decode é um passo de token único — um token de consulta contra o cache de KV — portanto, torch.export especializa seu eixo de sequência de consulta para 1. Passe multi_token_decode=True para capturar decode como um decode multi-token: decompose_for_generation() mescla dois passos consecutivos de decode (ele captura com max_new_tokens=3) em um único forward, de modo que esse eixo permanece simbólico. Um único gráfico então atende a todos os comprimentos de consulta — um token (decodificação comum), muitos tokens de uma vez (continuação a partir do passado, por exemplo, aceitando um bloco de tokens especulativos) e um prefill simples quando o cache está vazio.

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

O eixo de consulta permanece simbólico apenas em uma exportação com formas dinâmicas (dynamic=True); uma exportação estática o fixa no comprimento capturado, resultando em um gráfico multi-token fixo. Ele se combina com o cache KV estático abaixo — o decodificação mesclada grava os tokens de cada etapa no cache de tamanho fixo no local, e o cache gerencia internamente onde eles são posicionados.

Cache KV estático

Os exemplos de ExecuTorch nesta seção, incluindo atualizações sem cópia e o loop de decodificação, são exclusivos para XNNPACK. O exportador MLX rejeita StaticCache; use DynamicCache com MLX.

generate() cresce um DynamicCache por padrão, realocando à medida que a sequência se estende — um alvo móvel para um grafo exportado. Um cache estático é um buffer de tamanho fixo, alocado uma única vez e gravado no local na posição atual a cada etapa. Combinado com um decodificação multi-token, ele reduz a geração a um único grafo exportado: o grafo decode recebe um cache de tamanho fixo e um número variável de tokens de consulta, de modo que um único grafo atende tanto o prompt (cache vazio → prefill) quanto cada token gerado (cache preenchido → decodificação). Exporte-o encaminhando um GenerationConfig com cache_implementation="static" (e um max_cache_len) junto com 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
)

O grafo decode agora possui dois eixos simbólicos — o comprimento da consulta (quantos tokens você fornece) e o comprimento do cache (max_cache_len, redimensionável no momento do carregamento). dynamic=True marca esses (e todos os outros eixos) como Dim.AUTO, de modo que o grafo exportado aceita qualquer comprimento de prompt e tamanho de cache no momento do carregamento.

Atualizações in-place sem cópia

O cache estático é passado e mutado no local, de modo que um único buffer carrega o estado entre as etapas de decodificação, sem cópias no host — desde que o runtime vincule os buffers do chamador em vez de copiar através de sua própria arena. O que isso exige é a única parte específica de cada backend que resta:

  • Dynamo — o programa exportado modela a gravação no cache como uma USER_INPUT_MUTATION, portanto, chamar components["decode"].module()(...) atualiza diretamente os tensors de cache que você passa. Reutilize os mesmos tensors a cada etapa; não há nada a configurar.

  • ONNX Runtime — o grafo de decodificação expõe o cache como pares correspondentes de input.<name> / output.<name>. O CudaSession.set_buffer_sharing do ORT (onnxruntime.transformers.io_binding_helper) vincula cada par a um único buffer de dispositivo, de modo que o cache é lido e atualizado no local ao longo do loop, sem idas e vindas com o host.

  • ExecuTorch (XNNPACK) — desative as alocações de planejamento de memória no ExecutorchConfig para que a gravação in-place possa ser feita no próprio tensor do chamador (consulte a referência para saber o que cada flag faz):

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

    A gravação in-place sem cópia também exige que o chamador vincule os buffers de saída em tempo de execução via Method::set_output_data_ptr — algo não exposto pelo runtime Python (executorch.runtime.Method expõe apenas execute/set_inputs/get_outputs). As flags acima configuram isso, mas a gravação in-place é um caminho exclusivo de C++ (veja o exemplo de loop de decodificação do ExecuTorch abaixo). A partir do Python, leia o cache atualizado de volta das saídas do método a cada etapa.

Decode-loop inference examples

O loop tem a mesma forma em todos os backends — é o mesmo grafo do início ao fim. Comece com um cache de tamanho fixo vazio, processe o prompt inteiro uma única vez (cache vazio → prefill) e, em seguida, um token de cada vez (cache preenchido → decode). Cada chamada passa input_ids, uma attention_mask causal e position_ids (avançadas pelo número de novos tokens a cada etapa), além do cache, e retorna logits para todas as posições de consulta. A posição em que cada token é armazenado no cache é rastreada internamente pelo cache estático, portanto não há nada extra para passar pela chamada. A configuração do cache varia conforme o runtime (um objeto StaticCache para Dynamo, buffers de dispositivo brutos para ONNX Runtime, arrays do chamador em C++ para ExecuTorch), de modo que cada aba monta a sua própria configuração abaixo. As abas de Dynamo e ONNX Runtime atualizam o cache no local; o caminho no local do ExecuTorch é em C++ (seu runtime em Python não pode, conforme mencionado acima).

Dynamo
ONNX Runtime
ExecuTorch XNNPACK

torch.export registra a gravação no cache estático como USER_INPUT_MUTATION, de modo que o module() do grafo carregado atualiza o StaticCache que você passa diretamente — um único cache carrega o estado em todo o laço, sem nada para vincular ou devolver. register_pytree_node(StaticCache) permite que o torch.export.load desfaça a aplanamento da entrada StaticCache. O cache precisa ser inicializado previamente (torch.export incorpora o K/V alocado na especificação de entrada, então um cache vazio preguiçoso não corresponderá) — mas o programa salvo carrega seus próprios example_inputs, então reutilize esse modelo StaticCache já inicializado, reiniciado para vazio:

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)

Quantização

Cada configuração de exportação aceita um quantizer que quantiza o modelo durante a exportação, no grafo FX do torch.export (PT2E ou NNCF, para todos os backends) ou no modelo ONNX ou OpenVINO convertido (ONNX Runtime ou NNCF). Consulte Quantização de exportações para ver os quantizadores, seus backends, a calibração e como adicionar um.

Limitações e soluções alternativas

torch.export, torch.onnx.export e ExecuTorch apresentam limitações em relação a padrões específicos do PyTorch. Os exportadores contornam essas limitações com um pequeno conjunto de patches reversíveis e correções em nível de FX aplicados em pontos bem definidos do fluxo de exportação. Nenhuma dessas etapas é visível a partir da API pública export, mas os pontos mais comuns a saber são:

  • FlashAttention e FlexAttention não são exportáveis em nenhum backend. sdpa é a configuração preferida e eager também funciona (mais lento). Defina um deles no modelo antes de chamar export se ele estiver usando outra coisa.
  • grouped_mm é rastreado sem problemas pelo DynamoExporter e é traduzido automaticamente para o OnnxExporter. Para o ExecutorchExporter com XNNPACK ou MLX, o exportador substitui os especialistas MoE por batched_mm antes da exportação.

Próximas etapas

Update on GitHub