# エクスポート

ターゲットランタイムを問わず、任意の[PreTrainedModel](/docs/transformers/main/ja/main_classes/model#transformers.PreTrainedModel)を ONNX、ExecuTorch、またはスタンドアロンの PyTorch プログラムにエクスポートできます。

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

エクスポート機能は下流のライブラリではなく Transformers 本体に実装されているため、アーキテクチャの変更、新しいアテンションパターン、カスタムキャッシュタイプは、モデリングコードに反映された時点でエクスポート時にサポートされます。

> [!WARNING]
> エクスポート機能は実験的なものです。このモジュール内の多くのパッチは、特定のアップストリームのバグ（Torch、ONNX Script、ONNX Runtime、ExecuTorch）を回避するために用意されており、アップストリームで修正が反映され次第、削除されます。APIが安定するまで、これらのパッチはテストスイートで使用するバージョンに依存しているものとして扱ってください。本番環境のツールでは、これらのバージョンを固定してください。また、アップストリームの変更が反映されるにつれて、新しいパッチが追加され、古いパッチが削除されることを想定してください。

| エクスポート               | 出力                     | ランタイム                                    |
| ---------------------- | -------------------------- | ------------------------------------------ |
| `DynamoExporter`     | `ExportedProgram`          | PyTorch ランタイム全般、AOT コンパイル       |
| `OnnxExporter`       | `ONNXProgram`              | 任意の ONNX ランタイム（ORT、TensorRT、OpenVINO） |
| `ExecutorchExporter` | `ExecutorchProgramManager` | モバイルおよびエッジデバイス（ExecuTorch）       |
| `OpenVINOExporter`   | `openvino.Model`           | OpenVINO ランタイム（Intel CPU/GPU/NPU）       |

`AutoHfExporter` は設定から適切なエクスポートクラスを選択し、`AutoExportConfig` は辞書から適切な設定クラスを選択します。どちらも Transformers の auto クラスパターンに従っており、バックエンドをハードコードせず実行時に選択する場合に便利です。

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

## インストール

エクスポート先のバックエンドの依存関係をインストールします。

> [!TIP]
> 以下に示すバージョンは、エクスポート用のテストスイートが固定しているものです。新しいリリースや古いリリースでも動作する場合がありますが、エクスポート用のパッチは特定の API 表面を対象としているため、本番環境のツールではこれらのバージョンを固定してください。`HfExporter` は、バージョンの乖離を検出すると警告をログに出力します。

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

Apple Silicon 搭載の macOS 14 以降が必要です。MLX は macOS ARM64 向けの ExecuTorch nightly wheels に含まれており、個別のパッケージや extra ではありません。以下の nightly の組み合わせは、MLX エクスポートのテストで検証されています。標準の ExecuTorch nightly レジストリからインストールしてください:

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

nightly ExecuTorch の wheel は `torch` を依存関係として宣言していないため、`torch` を明示的にインストールしてください。ソースからのビルドについては、[MLX インストールガイド](https://docs.pytorch.org/executorch/main/backends/mlx/mlx-overview.html)を参照してください。

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

## モデルのエクスポート

すべてのエクスポートツールは同じインターフェースを共有しています。設定を使用してエクスポートツールを作成し、[export()](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.HfExporter.export)を呼び出します。

エクスポートークラスを切り替えることで、ランタイムを切り替えます。

```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/ja/main_classes/exporters#transformers.exporters.ExecutorchConfig.backend) でターゲットを選択します: CPU には `xnnpack` (デフォルト)、Apple Silicon GPU には `mlx`、CUDA 対応 GPU には `cuda` を指定します。それぞれに対応する [インストールオプション](#installation) を選択してください。MLX の実行には MLX デリゲートとその Metal ライブラリが必要です。CUDA 対応でない環境で CUDA を要求すると、`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)
```

## 動的な形状

`dynamic=True` を渡すと、すべてのテンソル次元が動的としてマークされ、エクスポートされたグラフは、再トレースなしで実行時に任意サイズの入力を受理できるようになります。

どの次元を動的にするかを細かく制御するには、代わりに明示的な`dynamic_shapes`を渡し、[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)
```

## 生成モデル

自己回帰生成では、モデルの`forward`は、プリフィル段階（プロンプト全体、KVキャッシュなし）とデコード段階（単一トークン、KVキャッシュあり）で異なる形状になります。エクスポートツールは`~HfExporter.export_for_generation`を公開しており、これにより両方の段階を分割してそれぞれをエクスポートします。

マルチモーダル生成モデルでは、prefill はさらに画像または音声エンコーダー、言語モデル、`lm_head` に分割されます。エンコーダーと言語モデルの検出には [get_encoder()](/docs/transformers/main/ja/main_classes/model#transformers.PreTrainedModel.get_encoder)（`modality="image"` または `"audio"`）および [get_decoder()](/docs/transformers/main/ja/main_classes/model#transformers.PreTrainedModel.get_decoder) アクセサーが使用されるため、これらのアクセサーを利用する新しいアーキテクチャは、追加の対応なしでそのまま動作します。

プロジェクターコンポーネントは、モデルが特定の属性名（`multi_modal_projector`、`connector`、`embed_vision`、`embed_audio`）でプロジェクターを公開した場合にのみ表示されます。以下の Qwen2-VL はプロジェクターをビジョンタワーに統合しているため、そのコンポーネント辞書には独立した `multi_modal_projector` キーがありません。新しいアーキテクチャでは、リストを増やすのではなく、プロジェクターの属性をこれらの名前のいずれかに合わせる必要があります。

```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]
> エクスポートされたコンポーネントは独立したグラフであり、実行可能な推論パイプラインではありません。各エンコーダーの実行、埋め込みの射影、および生成ループのオーケストレーションは、呼び出し側の責任です。

### `export_for_generation` の仕組み

[decompose_for_generation()](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.utils.decompose_for_generation) は `model.generate(**inputs, max_new_tokens=2)` を一度実行し、`model.forward` にフックを仕込んで、実際の prefill と decode の kwargs（マルチモーダルモデルの場合は、各エンコーダー/プロジェクター/言語モデルにフックを仕込んでサブモジュールごとの kwargs も）を取得します。そのため、モデルごとの個別対応なしに、decoder-only、SSM、encoder-decoder、マルチモーダルモデルを含むあらゆるアーキテクチャで動作します。`export_for_generation` はこれの上にある一行のラッパーです。

キャプチャは`inputs`に対してモデルをイージリーモードで実行するため、短いプロンプト、1枚の小さな画像、数フレームの音声など、小さくても代表的な値を渡してください。エクスポートされたプログラムはこれらのサイズに固定されるわけではありません（動的な形状は引き続きフローします）が、キャプチャ時の入力を小さくすることで`decompose_for_generation`のコストを下げ、シンボリック形状の推論を扱いやすく保つことができます。

`decompose_for_generation`を直接呼び出すことで、分解とエクスポートの間に介入できます。例えば、検証用のeager forwardを実行したり、サブモジュールの入力を差し替えたり、特定のステージをスキップしたりできます。

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

### マルチトークンデコード

デフォルトでは、`decode` コンポーネントは**単一トークン**のステップです。つまり、KV キャッシュに対して 1 つのクエリトークンを処理します。そのため、`torch.export` はクエリシーケンスの軸を 1 に特化します。`multi_token_decode=True` を渡すことで、代わりに**マルチトークン**デコードとして`decode` をキャプチャできます。[decompose_for_generation()](/docs/transformers/main/ja/main_classes/exporters#transformers.exporters.utils.decompose_for_generation) は、連続する 2 つのデコードステップ（`max_new_tokens=3` でキャプチャされます）を 1 つのフォワードにマージするため、その軸はシンボリックのままになります。これにより、単一のグラフがすべてのクエリ長に対応できます。1 トークン（通常のデコード）、一度に多数のトークン（過去の続きからの生成、例えば投機的トークンのチャンクを受け入れる場合）、そしてキャッシュが空の場合の通常のプリフィルです。

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

クエリ軸がシンボリックな状態を維持するのは、`dynamic=True`による動的シェイプのエクスポートの場合のみです。静的エクスポートでは、取得された長さに固定され、固定のマルチトークングラフが生成されます。これは以下の静的KVキャッシュと組み合わせることができ、マージされたデコードは各ステップのトークンを固定サイズのキャッシュにインプレースで書き込み、キャッシュが内部でその配置先を管理します。

### 静的 KV キャッシュ

> [!NOTE]
> このセクションの ExecuTorch の例（ゼロコピー更新とデコードループを含む）は、**XNNPACK 専用**です。MLX エクスポートでは `StaticCache` は拒否されるため、MLX では `DynamicCache` を使用してください。

`generate()` はデフォルトで `DynamicCache` を構築し、シーケンスが伸びるたびに再割り当てを行います。これはエクスポートされたグラフにとって移動するターゲットとなります。**static** キャッシュは固定サイズのバッファであり、一度だけ割り当てられ、各ステップで現在の位置にインプレースで書き込まれます。[multi-token decode](#multi-token-decode) と組み合わせることで、生成を単一のエクスポート済みグラフに集約できます。`decode` グラフは固定サイズのキャッシュと *可変* な数のクエリトークンを受け取るため、1 つのグラフでプロンプト（空のキャッシュ → prefill）と各生成トークン（埋められたキャッシュ → decode）の両方を処理できます。`GenerationConfig` に `cache_implementation="static"`（および `max_cache_len`）を指定し、`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
)
```

`decode` グラフには現在、2 つのシンボリック軸があります。クエリ長（入力するトークン数）と、キャッシュ長（`max_cache_len`、ロード時にリサイズ可能）です。`dynamic=True` を指定すると、これら（およびその他のすべての軸）が `Dim.AUTO` としてマークされるため、エクスポートされたグラフはロード時に任意のプロンプト長とキャッシュサイズを受け入れるようになります。

#### ゼロコピーによるインプレース更新

静的キャッシュは渡され、その場で変更されるため、ホストへのコピーなしで1つのバッファがデコードステップ間で状態を保持します。ただし、ランタイムが呼び出し元のバッファをコピーするのではなく、それらをバインドする場合に限ります。これを実現するために必要な唯一のバックエンド固有の部分は次のとおりです。

- **Dynamo** — エクスポートされたプログラムは、キャッシュの書き込みを`USER_INPUT_MUTATION`としてモデル化します。そのため、`components["decode"].module()(...)`を呼び出すと、直接渡したキャッシュテンソルが更新されます。各ステップで同じテンソルを再利用してください。設定は不要です。

- **ONNX Runtime** — デコードグラフは、キャッシュを `input.<name>` / `output.<name>` の対応するペアとして公開します。ORT の `CudaSession.set_buffer_sharing`（`onnxruntime.transformers.io_binding_helper`）は、各ペアを 1 つのデバイスバッファにバインドするため、ホストとの往復なしに、ループ内でキャッシュをその場で読み取り、更新します。

- **ExecuTorch (XNNPACK)** — `ExecutorchConfig` のメモリプランニング割り当てを無効にし、インプレース書き込みが呼び出し元のテンソルに直接反映されるようにします（各フラグの役割についてはリファレンスを参照してください）：

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

  > [!NOTE]
  > ゼロコピーのインプレース書き込みでは、呼び出し側が `Method::set_output_data_ptr` を介して実行時に出力バッファをバインドする必要があります — **Python ランタイムからは公開されていません**（`executorch.runtime.Method` が公開するのは `execute`/`set_inputs`/`get_outputs` のみです）。上記のフラグで設定は行われますが、インプレース書き込みは **C++ 専用**のパスです（以下の ExecuTorch のデコードループの例を参照）。Python からでは、各ステップでメソッドの出力から更新されたキャッシュを読み戻してください。

デコードループ推論の例

ループの構造はすべてのバックエンドで同一です。一貫して*同じ*グラフが使用されます。空の固定サイズキャッシュから開始し、プロンプト全体を一度に投入します（空のキャッシュ → prefill）。その後、1トークンずつ処理します（キャッシュが埋まった状態 → decode）。各呼び出しでは、`input_ids`、因果的な`attention_mask`、`position_ids`（各ステップで新規トークン数だけ進める）に加えてキャッシュを渡し、すべてのクエリ位置に対するlogitsが返されます。各トークンがキャッシュ内のどこに配置されるかは、静的キャッシュが内部で追跡するため、呼び出しに追加で引き回すものは必要ありません。キャッシュのセットアップ方法はランタイムごとに異なります（Dynamoでは`StaticCache`オブジェクト、ONNX Runtimeでは生のデバイスバッファ、ExecuTorchではC++の呼び出し元配列）。そのため、各タブでは以下に独自の構築方法を示しています。DynamoとONNX Runtimeのタブではキャッシュをインプレースで更新します。ExecuTorchのインプレースパスはC++で実装されています（上記の通り、そのPythonランタイムではインプレース更新ができないためです）。

torch.export は静的キャッシュへの書き込みを `USER_INPUT_MUTATION` として記録するため、読み込まれたグラフの `module()` が **直接**渡された `StaticCache` を更新します。これにより、バインドや外部へのスレッドバックを一切行わずに、1つのキャッシュがループ全体で状態を保持できます。`register_pytree_node(StaticCache)` により、`torch.export.load` は `StaticCache` 入力をアンフラット化できます。キャッシュは**事前に初期化する必要があります**（torch.export は割り当てられた K/V を入力仕様へ焼き込むため、遅延初期化された空のキャッシュは一致しません）— ただし、保存されたプログラムには独自の `example_inputs` が含まれているため、そのすでに初期化済みの `StaticCache` テンプレートを再利用し、空にリセットしてください。

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

ONNX Runtime はグラフをそのまま実行します。インプレースなキャッシュの更新は、ORT の `CudaSession`（`onnxruntime.transformers.io_binding_helper`）によって行われます。これは ORT の io-binding に対する薄いラッパーです。`set_buffer_sharing` は、キャッシュの `input.<name>` とそれに対応する `output.<name>` を**1 つ**のデバイスバッファにバインドするため、変更された K/V/カウンタはそのまま入力に書き戻されます。`allocate_buffers` は、残りの（共有されない）出力を割り当てます。ここでは `logits` のみです。そして `infer(feed_dict)` は、CUDA テンソルをポインタでバインドして実行します。キャッシュバッファは、グラフ自身の入力メタデータ（`get_inputs()` の shape と type）から直接取得されるため、モデル設定は不要です。唯一のシンボリックな軸（キャッシュ長）は `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)
```

ExecuTorch のオンデバイスランタイムは C++ で実装されており、インプレースなキャッシュ更新は `Method::set_output_data_ptr` に依存しています。この API は **Python ランタイムからは公開されていません**（`executorch.runtime.Method` が公開しているのは `execute`/`set_inputs`/`get_outputs` のみです）。そのため、ゼロコピーのデコードは C++ 専用のパスとなります。変更されたキャッシュの **output** を、対応するキャッシュの **input** バッファにバインドすると、新しい K/V/カウンタはコピーなしで呼び出し元の `StaticCache` バッファに格納されます。他のタブと同様に、形状とサイズはアーティファクト自体から取得します。ここではプログラムの `method_meta`（`input_tensor_meta`/`output_tensor_meta` → `TensorInfo::nbytes()`）を使用します。C++ 側にはクエリできる Python ランタイムが存在しないためです：

```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));
}
```

## 量子化

すべてのエクスポート設定は、エクスポート時にモデルを量子化する`quantizer`を受け取ります。量子化は、`torch.export`のFXグラフ上（すべてのバックエンドでPT2EまたはNNCF）または変換後のONNXまたはOpenVINOモデル上（ONNX RuntimeまたはNNCF）で行われます。量子化器、そのバックエンド、キャリブレーション、および追加方法については、[エクスポートの量子化](./exporters_quantization)を参照してください。

## 制限事項と回避策

`torch.export`、`torch.onnx.export`、ExecuTorch は、それぞれ特定の PyTorch パターンにおいて課題を抱えています。エクスポートツールは、エクスポートフロー内の明確に定義されたポイントで、少数の可逆的なパッチと FX レベルの修正を適用してこれらを回避します。これらは公開されている `export` API から直接は見えませんが、知っておくべき一般的な事項は以下の通りです。

- FlashAttention と FlexAttention は、どのバックエンドでもエクスポートできません。`sdpa` が推奨
`eager` を設定しても動作します（ただし遅くなります）。モデルがそれ以外の設定を使用している場合は、`export` を呼び出す前に、そのいずれかをモデルに設定してください。
- `grouped_mm` は `DynamoExporter` によるトレースを正常に追跡でき、`OnnxExporter` 向けに自動変換されます。
XNNPACKまたはMLXのいずれかを使用する`ExecutorchExporter`では、エクスポート前にMoEエキスパートが`batched_mm`に置き換えられます。

## 次のステップ

- patch および fix レジストリで、新しいアーキテクチャまたはバックエンドのエクスポートサポートを追加します
[エクスポート機能の拡張](./exporters_extend)。
- [エクスポートの量子化](./exporters_quantization)で、エクスポートを量子化するか、量子化器を追加します。

