Transformers documentation

エクスポート

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

エクスポート

ターゲットランタイムを問わず、任意のPreTrainedModelを ONNX、ExecuTorch、またはスタンドアロンの PyTorch プログラムにエクスポートできます。

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

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

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

エクスポート出力ランタイム
DynamoExporterExportedProgramPyTorch ランタイム全般、AOT コンパイル
OnnxExporterONNXProgram任意の ONNX ランタイム(ORT、TensorRT、OpenVINO)
ExecutorchExporterExecutorchProgramManagerモバイルおよびエッジデバイス(ExecuTorch)
OpenVINOExporteropenvino.ModelOpenVINO ランタイム(Intel CPU/GPU/NPU)

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

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)

インストール

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

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

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

モデルのエクスポート

すべてのエクスポートツールは同じインターフェースを共有しています。設定を使用してエクスポートツールを作成し、export()を呼び出します。

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

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)

動的な形状

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

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

生成モデル

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

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

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

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}

エクスポートされたコンポーネントは独立したグラフであり、すぐに実行できる推論パイプラインではありません。 呼び出し元は、各エンコーダーの実行、埋め込みの射影、および生成ループのオーケストレーションを担当する必要があります。

export_for_generation の動作

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を実行したり、サブモジュールの入力を差し替えたり、特定のステージをスキップしたりできます。

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() は、連続する 2 つのデコードステップ(max_new_tokens=3 でキャプチャします)を 1 つのフォワードにマージするため、その軸はシンボリックのままになります。これにより、単一のグラフがすべてのクエリ長に対応できます。1 トークン(通常のデコーディング)、複数トークン同時(過去からの継続、例えば投機的トークンのチャンクを承認する場合)、そしてキャッシュが空の場合の通常のプリフィルです。

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

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

静的 KV キャッシュ

このセクションの ExecuTorch の例(ゼロコピー更新およびデコードループを含む)は、XNNPACK 専用です。MLX エクスポートでは StaticCache は拒否されるため、MLX では DynamicCache を使用してください。

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

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 のメモリプランニング割り当てを無効にし、インプレース書き込みが呼び出し元のテンソルに直接反映されるようにします(各フラグの詳細はリファレンスを参照してください):

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

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

Decode-loop inference examples

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

Dynamo
ONNX Runtime
ExecuTorch XNNPACK

torch.export records the static-cache write as a USER_INPUT_MUTATION, so the loaded graph’s module() updates the StaticCache you pass in directly — one cache carries state across the whole loop with nothing to bind or thread back out. register_pytree_node(StaticCache) lets torch.export.load unflatten the StaticCache input. The cache has to be initialized up front (torch.export bakes the allocated K/V into the input spec, so a lazy blank cache won’t match) — but the saved program carries its own example_inputs, so reuse that already-initialized StaticCache template, reset to empty:

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)

量子化

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

制限事項と回避策

torch.export、torch.onnx.export、ExecuTorch はそれぞれ、特定の PyTorch パターンまわりに課題があります。エクスポートツールは、エクスポートフロー内の明確に定義されたポイントで、少数の可逆的なパッチと FX レベルの修正を適用してこれらを回避しています。これらはパブリックな export API から見えませんが、よく知っておくべき点として:

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

次のステップ

  • エクスポート機能の拡張の「パッチとレジストリの修正」を参照して、新しいアーキテクチャまたはバックエンドのエクスポートサポートを追加します。
  • エクスポートの量子化で、エクスポートを量子化するか、量子化器を追加します。
Update on GitHub