# Exportadores

Exporte qualquer [PreTrainedModel](/docs/transformers/main/pt/main_classes/model#transformers.PreTrainedModel) para ONNX, ExecuTorch ou um programa PyTorch independente, independentemente do runtime de destino.

```python
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.

> [!WARNING]
> 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.

| Exportar               | Saída                     | Tempo de execução                                    |
| ---------------------- | -------------------------- | ------------------------------------------ |
| `DynamoExporter`     | `ExportedProgram`          | Qualquer runtime do PyTorch, compilação AOT       |
| `OnnxExporter`       | `ONNXProgram`              | Qualquer runtime ONNX (ORT, TensorRT, OpenVINO) |
| `ExecutorchExporter` | `ExecutorchProgramManager` | Dispositivos móveis e de borda (ExecuTorch)       |
| `OpenVINOExporter`   | `openvino.Model`           | Runtime 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 fixo no ponto de chamada.

```python
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.

> [!TIP]
> 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.

```bash
pip install transformers "torch==2.12.0"
```

```bash
pip install transformers "torch==2.12.0" "onnx==1.21.0" "onnxscript==0.7.0" onnxruntime
```

```bash
pip install transformers "torch==2.12.0" "executorch==1.3.1"
```

Exige macOS 14 ou posterior em Apple Silicon. O MLX está incluído nas wheels noturnas do ExecuTorch para macOS ARM64; não é um pacote ou extra separado. O par noturno a seguir foi validado com os testes do exportador MLX. Instale-o do registro noturno padrão do ExecuTorch:

```bash
pip install transformers
pip install \
  "executorch==1.6.0.dev20260924" \
  "torch==2.15.0.dev20260924" \
  --extra-index-url https://download.pytorch.org/whl/nightly/cpu
```

Instale o `torch` explicitamente, pois as wheels noturnas do ExecuTorch não o declaram como dependência. Para compilações a partir do código-fonte, consulte o [guia de instalação do MLX](https://docs.pytorch.org/executorch/main/backends/mlx/mlx-overview.html).

```bash
pip install transformers "torch==2.12.0" "openvino==2026.3.1"
```

## Exportar um modelo

Todos os exportadores compartilham a mesma interface. Crie um exportador com uma configuração e chame [export()](/docs/transformers/main/pt/main_classes/exporters#transformers.exporters.HfExporter.export).

Alterne entre runtimes trocando a classe de exportador.

```python
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)
```

```python
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import OnnxExporter, OnnxConfig

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

exporter = OnnxExporter()
config = OnnxConfig(dynamic=True)
onnx_program = exporter.export(model, inputs, config=config)

# save and load with ONNX Runtime
onnx_program.save("model.onnx")

import onnxruntime as ort

session = ort.InferenceSession("model.onnx")
ort_inputs = {k: v.numpy() for k, v in inputs.items()}
outputs = session.run(None, ort_inputs)
```

[backend](/docs/transformers/main/pt/main_classes/exporters#transformers.exporters.ExecutorchConfig.backend) seleciona o destino: `xnnpack` (o padrão) para CPU, `mlx` para GPU Apple Silicon ou `cuda` para uma GPU com suporte a CUDA. Escolha a [opção de instalação](#installation) correspondente. A execução com MLX requer o delegate MLX e suas bibliotecas Metal; solicitar CUDA sem um ambiente com suporte a CUDA gera um `RuntimeError`.

```python
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import ExecutorchExporter, ExecutorchConfig

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

exporter = ExecutorchExporter()
config = ExecutorchConfig(backend="xnnpack", dynamic=True)  # Use "mlx" on Apple Silicon.
et_program = exporter.export(model, inputs, config=config)

# save for on-device deployment
et_program.save("model.pte")

# load and run via the ExecuTorch Python runtime
from executorch.runtime import Runtime

program = Runtime.get().load_program("model.pte")
method = program.load_method("forward")
outputs = method.execute(list(inputs.values()))
```

```python
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import OpenVINOExporter, OpenVINOConfig

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

exporter = OpenVINOExporter()
config = OpenVINOConfig(dynamic=True)
ov_model = exporter.export(model, inputs, config=config)

ov_model.save("model.xml")

# compile and run on CPU (or "GPU" / "NPU" if available)
import openvino as ov
compiled = ov.Core().compile_model(ov_model, "CPU")
ov_inputs = {k: v.numpy() for k, v in inputs.items()}
outputs = compiled(ov_inputs)
```

## Formas dinâmicas

Passar `dynamic=True` marca todas as dimensões dos tensores como dinâmicas, permitindo que o grafo exportado aceite 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](https://pytorch.org/docs/stable/export.html).

```python
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)
```

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import OnnxExporter, OnnxConfig

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 = OnnxExporter()
config = OnnxConfig(
    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,
)
onnx_program = exporter.export(model, inputs, config=config)
```

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import ExecutorchExporter, ExecutorchConfig

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 = ExecutorchExporter()
config = ExecutorchConfig(
    backend="xnnpack",  # Use "mlx" on Apple Silicon.
    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,
)
et_program = exporter.export(model, inputs, config=config)
```

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import OpenVINOExporter, OpenVINOConfig

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 = OpenVINOExporter()
config = OpenVINOConfig(
    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,
)
ov_model = 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 comparação com a etapa de decode (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()](/docs/transformers/main/pt/main_classes/model#transformers.PreTrainedModel.get_encoder) (`modality="image"` ou `"audio"`) e [get_decoder()](/docs/transformers/main/pt/main_classes/model#transformers.PreTrainedModel.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 dele 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.

```python
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}
```

```python
from transformers import AutoModelForImageTextToText, AutoProcessor
from transformers.exporters import OnnxExporter, OnnxConfig

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 = OnnxExporter()
config = OnnxConfig(dynamic=True)
components = exporter.export_for_generation(model, inputs, config=config)
# components = {"image_encoder": ONNXProgram, "language_model": ONNXProgram, "lm_head": ONNXProgram, "decode": ONNXProgram}
```

```python
from transformers import AutoModelForImageTextToText, AutoProcessor
from transformers.exporters import ExecutorchExporter, ExecutorchConfig

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 = ExecutorchExporter()
config = ExecutorchConfig(backend="xnnpack", dynamic=True)  # Use "mlx" on Apple Silicon.
components = exporter.export_for_generation(model, inputs, config=config)
# components = {"image_encoder": ExecutorchProgramManager, "language_model": ..., "lm_head": ..., "decode": ...}
```

```python
from transformers import AutoModelForImageTextToText, AutoProcessor
from transformers.exporters import OpenVINOExporter, OpenVINOConfig

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 = OpenVINOExporter()
config = OpenVINOConfig(dynamic=True)
components = exporter.export_for_generation(model, inputs, config=config)
# components = {"image_encoder": openvino.Model, "language_model": openvino.Model, "lm_head": openvino.Model, "decode": openvino.Model}
```

> [!WARNING]
> 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()](/docs/transformers/main/pt/main_classes/exporters#transformers.exporters.utils.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.

```python
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 —, de modo que `torch.export` especializa seu eixo de sequência de consulta para 1. Passe `multi_token_decode=True` para capturar o `decode` como um **decode multi-token**: [decompose_for_generation()](/docs/transformers/main/pt/main_classes/exporters#transformers.exporters.utils.decompose_for_generation) mescla duas etapas consecutivas 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), vários 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.

```python
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
```

```python
from transformers.exporters import OnnxExporter, OnnxConfig

exporter = OnnxExporter()
config = OnnxConfig(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
```

```python
from transformers.exporters import ExecutorchExporter, ExecutorchConfig

exporter = ExecutorchExporter()
config = ExecutorchConfig(backend="xnnpack", dynamic=True)  # Use "mlx" on Apple Silicon.
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

> [!NOTE]
> 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 passo. Combinado com um [decodificação multi-token](#multi-token-decode), ele colapsa a geração em 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`:

```python
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
)
```

```python
from transformers import GenerationConfig
from transformers.exporters import OnnxExporter, OnnxConfig

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

```python
from transformers import GenerationConfig
from transformers.exporters import ExecutorchExporter, ExecutorchConfig

exporter = ExecutorchExporter()
gen_config = GenerationConfig(cache_implementation="static", max_cache_len=2048)
components = exporter.export_for_generation(
    model, inputs, config=ExecutorchConfig(backend="xnnpack", 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 por meio 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):

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

  > [!NOTE]
  > 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 por meio de `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.

Exemplos de inferência no loop de decodificação

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 de uma 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 é necessário passar nada extra 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 implementação abaixo. As abas do Dynamo e do 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).

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 necessidade de vincular ou devolver nada. `register_pytree_node(StaticCache)` permite que o `torch.export.load` desfaça o achatamento da entrada `StaticCache`. O cache precisa ser **inicializado previamente** (o torch.export incorpora o K/V alocado na especificação de entrada, portanto 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:

```python
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)
```

O ONNX Runtime executa o grafo como está; a atualização do cache in-place é feita com o `CudaSession` do ORT (`onnxruntime.transformers.io_binding_helper`), um wrapper fino sobre o io-binding do ORT. `set_buffer_sharing` vincula um `input.<name>` do cache e seu `output.<name>` correspondente a **um** buffer de dispositivo, de modo que os K/V/contador mutados sejam gravados diretamente de volta na entrada; `allocate_buffers` aloca as saídas restantes (não compartilhadas) — aqui apenas `logits`; e `infer(feed_dict)` vincula seus tensores CUDA por ponteiro e executa. Os buffers de cache vêm diretamente dos metadados de entrada do próprio grafo (forma e tipo de `get_inputs()`), portanto nenhuma configuração de modelo é necessária — o único eixo simbólico (comprimento do cache) se torna `max_cache_len`:

```python
import torch
import onnxruntime as ort
from onnxruntime.transformers.io_binding_helper import CudaSession, TypeHelper

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

session = ort.InferenceSession("decode.onnx", providers=["CUDAExecutionProvider"])
cuda = CudaSession(session, torch.device("cuda"))

# fresh device cache buffers built from each cache input's own shape/dtype; share each
# input.<name>/output.<name> pair on one buffer so the update lands in place
cache = {}
for i in session.get_inputs():
    if not i.name.startswith("input."):
        continue
    name = i.name[len("input.") :]
    dims = [max_cache_len if isinstance(d, str) and not d.isdigit() else int(d) for d in i.shape]
    cache[name] = torch.zeros(dims, dtype=TypeHelper.ort_type_to_torch_type(i.type), device="cuda")
    cuda.set_buffer_sharing(f"input.{name}", f"output.{name}")
cache_feed = {f"input.{name}": buf for name, buf in cache.items()}

vocab_size = next(o.shape[-1] for o in session.get_outputs() if o.name.endswith("logits"))

# prefill: the whole prompt in one call
positions = torch.arange(prompt_len, device="cuda")
cuda.allocate_buffers({"logits": (1, prompt_len, vocab_size)})
out = cuda.infer({"input_ids": prompt_ids, "attention_mask": causal_mask(positions, max_cache_len),
                  "position_ids": positions[None], **cache_feed})
next_token = out["logits"][:, -1:].argmax(-1)

# decode: query=1 buffers reused in place
cuda.allocate_buffers({"logits": (1, 1, vocab_size)})
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)
    out = cuda.infer({"input_ids": input_ids, "attention_mask": attention_mask,
                      "position_ids": position_ids, **cache_feed})
    next_token = out["logits"][:, -1:].argmax(-1)
```

O runtime on-device do ExecuTorch é em C++, e a atualização do cache in-place depende de `Method::set_output_data_ptr` — **não exposto pelo runtime Python** (`executorch.runtime.Method` expõe apenas `execute`/`set_inputs`/`get_outputs`), portanto o decode zero-copy é um caminho exclusivo de C++. Vincule cada **output** de cache mutado ao buffer de **input** de cache correspondente, e os novos K/V/contador serão colocados nos buffers de `StaticCache` do chamador sem cópias. Assim como nas outras abas, as formas e tamanhos vêm do próprio artefato — aqui, do `method_meta` do programa (`input_tensor_meta`/`output_tensor_meta` → `TensorInfo::nbytes()`), pois o C++ não possui um runtime Python para consultar:

```cpp
#include <executorch/extension/data_loader/file_data_loader.h>
#include <executorch/extension/tensor/tensor_ptr.h>
#include <executorch/runtime/executor/method.h>
#include <executorch/runtime/executor/program.h>

using namespace executorch::runtime;
using executorch::extension::FileDataLoader;
using executorch::extension::make_tensor_ptr;

// load the exported decode and its `forward` method (Result error-checks elided for brevity)
auto loader = FileDataLoader::from("decode.pte");
auto program = Program::load(&loader.get());

// method-execution memory: a fixed arena for bookkeeping + one buffer per the method's memory plan
std::array<uint8_t, 4 * 1024 * 1024> arena;
MemoryAllocator method_allocator(arena.size(), arena.data());
auto meta = program->method_meta("forward");
std::vector<std::vector<uint8_t>> planned(meta->num_memory_planned_buffers());
std::vector<Span<uint8_t>> planned_spans;
for (size_t i = 0; i < planned.size(); ++i) {
    planned[i].resize(meta->memory_planned_buffer_size(i).get());
    planned_spans.push_back({planned[i].data(), planned[i].size()});
}
HierarchicalAllocator planned_allocator({planned_spans.data(), planned_spans.size()});
MemoryManager memory_manager(&method_allocator, &planned_allocator);
auto decode = std::move(program->load_method("forward", &memory_manager).get());

// shapes and sizes come from method_meta — no external config. Inputs are [ids, mask, position_ids,
// cache×N]; outputs are [returned cache×N, logits, mutated cache×N].
const size_t num_cache_tensors = meta->num_inputs() - 3;
const size_t logits_out_idx = num_cache_tensors;
std::vector<size_t> cache_nbytes(num_cache_tensors), cache_out_idx(num_cache_tensors);
for (size_t i = 0; i < num_cache_tensors; ++i) {
    cache_nbytes[i] = meta->input_tensor_meta(3 + i)->nbytes();
    cache_out_idx[i] = num_cache_tensors + 1 + i;   // mutated cache = the last N outputs
}
const size_t logits_nbytes = meta->output_tensor_meta(logits_out_idx)->nbytes();

// one set of StaticCache buffers (K/V + per-layer counters) is reused across steps, bound in place each call
auto forward = [&](const TensorPtr& input_ids, const TensorPtr& mask, const TensorPtr& position_ids) {
    decode.set_input(EValue(*input_ids), 0);
    decode.set_input(EValue(*mask), 1);
    decode.set_input(EValue(*position_ids), 2);
    for (int i = 0; i < num_cache_tensors; ++i)
        decode.set_input(EValue(*cache_tensor[i]), 3 + i);
    // bind each mutated-cache output onto that same input tensor's data → the write lands in place, zero copies
    for (int i = 0; i < num_cache_tensors; ++i)
        decode.set_output_data_ptr(cache_tensor[i]->mutable_data_ptr(), cache_nbytes[i], cache_out_idx[i]);
    decode.set_output_data_ptr(logits_data, logits_nbytes, logits_out_idx);
    decode.execute();                 // cache updated in place; logits written to logits_data
    return argmax_last(logits_data);  // greedy pick
};

// prefill the whole prompt, then decode one token per step — the cache carries in place across all calls
int64_t next_token = forward(prompt_ids, prompt_mask, prompt_positions);
for (int64_t position = prompt_len; position < max_cache_len; ++position) {
    next_token = forward(make_tensor_ptr({1, 1}, &next_token, ScalarType::Long),
                         causal_mask(position),  // [1, 1, 1, max_cache_len] bool
                         make_tensor_ptr({1, 1}, &position, ScalarType::Long));
}
```

## 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](./exporters_quantization) 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` é o preferido
configuração 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

- Adicione suporte de exportação para uma nova arquitetura ou backend com o patch e corrija os registros em
[Estendendo os exportadores](./exporters_extend).
- Quantize um export ou adicione um quantizer em [Quantização de exports](./exporters_quantization).

