Transformers documentation

Ekspor

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

and get access to the augmented documentation experience

to get started

Ekspor

Ekspor PreTrainedModel apa pun ke ONNX, ExecuTorch, atau program PyTorch mandiri, terlepas dari runtime target.

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

Ekspor berada di dalam Transformers, bukan di pustaka hilir, sehingga perubahan arsitektur, pola perhatian baru, dan tipe cache kustom didukung saat ekspor begitu tersedia di kode pemodelan.

Ekspor ini bersifat eksperimental. Banyak patch di modul ini bekerja untuk menghindari bug spesifik di hulu (Torch, ONNX Script, ONNX Runtime, ExecuTorch) dan akan dihapus begitu perbaikan diterapkan di hulu. Hingga API stabil, anggap patch terikat pada versi yang digunakan dalam suite pengujian. Kunci versi tersebut dalam tooling produksi, dan antisipasi munculnya patch baru serta hilangnya patch lama seiring penerapan perubahan di hulu.

ExporterOutputRuntime
DynamoExporterExportedProgramRuntime PyTorch apa pun, kompilasi AOT
OnnxExporterONNXProgramSemua runtime ONNX (ORT, TensorRT, OpenVINO)
ExecutorchExporterExecutorchProgramManagerPerangkat mobile dan edge (ExecuTorch)
OpenVINOExporteropenvino.ModelRuntime OpenVINO (CPU/GPU/NPU Intel)

AutoHfExporter memilih exporter yang tepat dari sebuah config, dan AutoExportConfig memilih kelas config yang tepat dari sebuah dict. Keduanya mengikuti pola auto-class yang sama di Transformers, yang berguna ketika backend dipilih pada saat runtime alih-alih di-hardcode di titik pemanggilan.

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)

Instalasi

Install dependensi untuk backend yang Anda rencanakan untuk diekspor.

Versi di bawah ini adalah versi yang digunakan oleh suite pengujian exporter. Rilis yang lebih baru atau lebih lama sering kali berfungsi, tetapi patch exporter menargetkan permukaan API tertentu, sehingga untuk tooling produksi, kunci versi ini dan harapkan HfExporter untuk mencatat peringatan ketika mendeteksi penyimpangan.

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

Ekspor model

Semua exporter berbagi antarmuka yang sama. Buat sebuah exporter dengan konfigurasi, lalu panggil export().

Beralih antar runtime dengan mengganti kelas exporter.

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)

Bentuk dinamis

Menyampaikan dynamic=True menandai setiap dimensi tensor sebagai dinamis sehingga graf yang diekspor menerima input berukuran apa pun saat runtime tanpa perlu melacak ulang.

Untuk kontrol yang lebih rinci mengenai dimensi mana yang dinamis, berikan dynamic_shapes secara eksplisit sebagai gantinya, yang diteruskan langsung ke 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)

Model generatif

Untuk generasi autoregresif, forward model memiliki bentuk yang berbeda pada langkah prefill (prompt penuh, tanpa KV cache) dibandingkan langkah decode (satu token, KV cache terisi). Ekspor menampilkan ~HfExporter.export_for_generation, yang memisahkan kedua tahap dan mengekspor masing-masing.

Untuk model generatif multi-modal, prefill juga dipecah menjadi encoder gambar atau audio, model bahasa, dan lm_head. Penemuan encoder dan model bahasa menggunakan accessor get_encoder() (modality="image" atau "audio") dan get_decoder(), sehingga arsitektur baru apa pun yang menggunakan ini langsung berfungsi.

Komponen projector hanya muncul ketika model mengekspos satu di bawah nama atribut (multi_modal_projector, connector, embed_vision, embed_audio). Qwen2-VL di bawah ini menggabungkan projectornya ke dalam vision tower, sehingga dict komponennya tidak memiliki kunci multi_modal_projector yang terpisah. Arsitektur baru harus menyelaraskan atribut projector mereka dengan salah satu nama ini, bukan memperpanjang daftar.

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}

Komponen yang diekspor adalah graf-graf independen, bukan pipeline inferensi yang siap dijalankan. Pemanggil bertanggung jawab untuk menjalankan setiap encoder, memproyeksikan embedding, dan mengorkestrasikan loop generasi.

Cara kerja export_for_generation

decompose_for_generation() menjalankan model.generate(**inputs, max_new_tokens=2) sekali dan mengaitkan model.forward untuk menangkap kwargs prefill dan decode yang sebenarnya (serta kwargs per-submodul melalui hook pada setiap encoder/projector/language model jika modelnya multi-modal). Itulah mengapa cara ini berfungsi untuk arsitektur apa pun, termasuk decoder-only, SSM, encoder-decoder, dan model multi-modal, tanpa perlu kode perekat per-model. export_for_generation adalah satu baris di atasnya.

The capture menjalankan model secara eagerly pada inputs, sehingga berikan nilai yang kecil namun representatif, seperti prompt pendek, satu gambar kecil, atau beberapa frame audio. Program yang diekspor tidak terikat pada ukuran tersebut (bentuk dinamis tetap mengalir), tetapi input capture yang lebih kecil membuat decompose_for_generation lebih murah dan menjaga inferensi bentuk simbolik tetap dapat ditangani.

Panggil decompose_for_generation secara langsung untuk bertindak antara dekomposisi dan ekspor, seperti menjalankan eager forward untuk verifikasi, mengganti input dari submodule, atau melewati tahap tertentu.

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 multi-token

Secara default, komponen decode adalah langkah single-token — satu token query terhadap KV cache — sehingga torch.export menspesialisasikan sumbu query-sequence-nya menjadi 1. Lewati multi_token_decode=True untuk menangkap decode sebagai decode multi-token alih-alih: decompose_for_generation() menggabungkan dua langkah decode yang berurutan (ditangkap dengan max_new_tokens=3) menjadi satu forward, sehingga sumbu tersebut tetap simbolik. Satu graf kemudian melayani setiap panjang query — satu token (dekoding biasa), banyak token sekaligus (kelanjutan-dari-masa-lalu, misalnya menerima sepotong token spekulatif), dan prefill biasa ketika cache kosong.

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

Sumbu query hanya tetap simbolik pada ekspor bentuk dinamis (dynamic=True); ekspor statis membekukannya pada panjang yang ditangkap, menghasilkan graf multi-token yang tetap. Ini berpadu dengan KV cache statis di bawah — decode yang digabungkan menulis token dari setiap langkah ke dalam cache berukuran tetap secara in-place, dan cache menangani di mana token tersebut ditempatkan secara internal.

Cache KV statis

Contoh ExecuTorch pada bagian ini, termasuk pembaruan zero-copy dan loop decode, bersifat hanya untuk XNNPACK. Ekspor MLX menolak StaticCache; gunakan DynamicCache dengan MLX.

generate() secara default membangun DynamicCache, yang dialokasikan ulang saat urutan diperpanjang — sebuah target yang bergerak untuk graf yang diekspor. Cache statis adalah buffer berukuran tetap, yang dialokasikan sekali dan ditulis di tempat pada posisi saat ini di setiap langkah. Digabungkan dengan multi-token decode, hal ini meruntuhkan proses generasi menjadi satu graf yang diekspor: graf decode menerima cache berukuran tetap dan jumlah token query yang variabel, sehingga satu graf melayani baik prompt (cache kosong → prefill) maupun setiap token yang dihasilkan (cache terisi → decode). Ekspor dengan meneruskan GenerationConfig dengan cache_implementation="static" (dan max_cache_len) bersama 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
)

Graf decode sekarang memiliki dua sumbu simbolik — panjang query (jumlah token yang Anda masukkan) dan panjang cache (max_cache_len, dapat diubah ukurannya saat pemuatan). dynamic=True menandai sumbu-sumbu ini (dan setiap sumbu lainnya) sebagai Dim.AUTO, sehingga graf yang diekspor menerima panjang prompt dan ukuran cache apa pun saat pemuatan.

Pembaruan di tempat tanpa salinan

Cache statis diteruskan dan dimutasi di tempat, sehingga satu buffer membawa state melintasi langkah-langkah decode tanpa salinan host — selama runtime mengikat buffer pemanggil alih-alih menyalin melalui arena miliknya sendiri. Yang dibutuhkan adalah satu-satunya bagian per-backend yang tersisa:

  • Dynamo — program yang diekspor memodelkan penulisan cache sebagai USER_INPUT_MUTATION, sehingga memanggil components["decode"].module()(...) akan memperbarui tensor cache yang Anda berikan secara langsung. Gunakan kembali tensor yang sama di setiap langkah; tidak ada yang perlu dikonfigurasi.

  • ONNX Runtime — graf decode mengekspos cache sebagai pasangan input.<name> / output.<name> yang cocok. CudaSession.set_buffer_sharing dari ORT (onnxruntime.transformers.io_binding_helper) mengikat setiap pasangan ke satu buffer perangkat, sehingga cache dibaca dan diperbarui di tempatnya selama loop tanpa perjalanan bolak-balik ke host.

  • ExecuTorch (XNNPACK) — matikan alokasi perencanaan memori pada ExecutorchConfig agar penulisan in-place dapat dilakukan pada tensor milik pemanggil (lihat referensi untuk mengetahui fungsi masing-masing flag):

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

    Penulisan in-place tanpa salinan juga memerlukan pemanggil untuk mengikat buffer output pada waktu runtime melalui Method::set_output_data_ptr — tidak ditampilkan oleh runtime Python (executorch.runtime.Method hanya mengekspos execute/set_inputs/get_outputs). Flag di atas mengonfigurasinya, tetapi penulisan in-place adalah jalur hanya C++ (lihat contoh loop decode ExecuTorch di bawah). Dari Python, baca kembali cache yang diperbarui dari output metode pada setiap langkah.

Decode-loop inference examples

Loop-nya memiliki bentuk yang sama di setiap backend — ini adalah grafik yang sama di sepanjang proses. Mulai dari cache berukuran tetap yang kosong, masukkan seluruh prompt sekali (cache kosong → prefill), lalu satu token pada satu waktu (cache terisi → decode). Setiap panggilan meneruskan input_ids, attention_mask kausal, dan position_ids (diperbarui sesuai jumlah token baru pada setiap langkah), ditambah cache, dan mengembalikan logits untuk setiap posisi query. Di mana setiap token ditempatkan dalam cache dilacak secara internal oleh cache statis, sehingga tidak ada hal tambahan yang perlu diteruskan melalui panggilan. Cara cache disiapkan berbeda di setiap runtime (objek StaticCache untuk Dynamo, buffer perangkat mentah untuk ONNX Runtime, array pemanggil di C++ untuk ExecuTorch), sehingga setiap tab membangunnya sendiri di bawah. Tab Dynamo dan ONNX Runtime memperbarui cache di tempat; jalur di tempat ExecuTorch adalah C++ (runtime Python-nya tidak bisa, seperti yang disebutkan di atas).

Dynamo
ONNX Runtime
ExecuTorch XNNPACK

torch.export mencatat penulisan cache statis sebagai USER_INPUT_MUTATION, sehingga module() dari graf yang dimuat memperbarui StaticCache yang Anda lewati secara langsung — satu cache membawa state di seluruh loop tanpa perlu mengikat atau mengembalikan apa pun. register_pytree_node(StaticCache) memungkinkan torch.export.load untuk unflatten input StaticCache. Cache harus diinisialisasi di awal (torch.export membakar K/V yang dialokasikan ke dalam spesifikasi input, sehingga cache kosong yang dimuat secara lazy tidak akan cocok) — tetapi program yang disimpan membawa example_inputs miliknya sendiri, jadi gunakan kembali template StaticCache yang sudah diinisialisasi tersebut, direset menjadi kosong:

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)

Kuantisasi

Setiap konfigurasi ekspor menerima quantizer yang mengkuantisasi model selama ekspor, pada grafik FX torch.export (PT2E atau NNCF, untuk setiap backend) atau pada model ONNX atau OpenVINO yang telah dikonversi (ONNX Runtime atau NNCF). Lihat Mengkuantisasi ekspor untuk daftar quantizer, backend mereka, kalibrasi, dan cara menambahkannya.

Keterbatasan dan solusi

torch.export, torch.onnx.export, dan ExecuTorch masing-masing memiliki keterbatasan di sekitar pola PyTorch tertentu. Eksporernya mengatasi hal ini dengan serangkaian kecil patch yang dapat dibalikkan dan perbaikan pada level FX yang diterapkan pada titik-titik yang terdefinisi dengan baik dalam alur ekspor. Tidak ada dari hal ini yang terlihat dari API export publik, tetapi hal-hal paling umum yang perlu diketahui:

  • FlashAttention dan FlexAttention tidak dapat diekspor di backend mana pun. sdpa adalah pengaturan yang disarankan dan eager juga berfungsi (lebih lambat). Atur salah satunya pada model sebelum memanggil export jika model tersebut menggunakan pengaturan lain.
  • grouped_mm dapat ditelusuri dengan baik melalui DynamoExporter dan diterjemahkan secara otomatis untuk OnnxExporter. Untuk ExecutorchExporter dengan XNNPACK atau MLX, exporter mengganti pakar MoE menjadi batched_mm sebelum ekspor.
  • Lapisan sliding-window (Mistral, Gemma 3) diekspor dengan cache key-value penuh; sliding mask tetap menerapkan jendela tersebut.

Langkah selanjutnya

  • Tambahkan dukungan ekspor untuk arsitektur atau backend baru dengan patch dan perbaiki registri di Memperluas exporter.
  • Kuantisasi sebuah ekspor, atau tambahkan kuantizer, di Kuantisasi ekspor.
Update on GitHub