Transformers documentation
Dışa Aktarıcılar
Dışa Aktarıcılar
Hedef çalışma zamanından bağımsız olarak herhangi bir PreTrainedModel nesnesini ONNX, ExecuTorch veya bağımsız bir PyTorch programına dışa aktarın.
exporter = DynamoExporter() # or OnnxExporter, ExecutorchExporter
config = DynamoConfig(dynamic=True)
exported = exporter.export(model, inputs, config=config)Dışa aktarıcılar, bir aşağı akış kütüphanesi yerine Transformers içinde yer aldığı için mimari değişiklikler, yeni dikkat kalıpları ve özel önbellek türleri, modelleme koduna eklendikleri anda dışa aktarma sırasında desteklenir.
Dışa aktarıcılar deneyseldir. Bu modüldeki yamaların çoğu, belirli üst akış hatalarının (Torch, ONNX Script, ONNX Runtime, ExecuTorch) etrafından dolaşmak için çalışır ve düzeltme üst akışa eklendiği anda kaldırılacaktır. API kararlılaşana kadar, yamaları test paketinde kullanılan sürümlere bağlı olarak ele alın. Üretim araçlarında bu sürümleri sabitleyin ve üst akış değişiklikleri geldikçe yeni yamaların ortaya çıkmasını, eski yamaların ise kaybolmasını bekleyin.
| Dışa Aktarıcı | Çıktı | Çalışma zamanı |
|---|---|---|
DynamoExporter | ExportedProgram | Herhangi bir PyTorch çalışma zamanı, AOT derleme |
OnnxExporter | ONNXProgram | Herhangi bir ONNX çalışma zamanı (ORT, TensorRT, OpenVINO) |
ExecutorchExporter | ExecutorchProgramManager | Mobil ve kenar cihazları (ExecuTorch) |
OpenVINOExporter | openvino.Model | OpenVINO çalışma zamanı (Intel CPU/GPU/NPU) |
AutoHfExporter bir yapılandırmadan doğru dışa aktarıcıyı seçer ve AutoExportConfig bir sözlükten doğru yapılandırma sınıfını seçer. İkisi de Transformers'taki aynı otomatik-sınıf desenini izler; bu, arka uç çağrı noktasında sabit kodlanmak yerine çalışma zamanında seçildiğinde faydalıdır.
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)Kurulum
Dışa aktarmayı planladığınız arka uç için bağımlılıkları yükleyin.
Aşağıdaki sürümler, dışa aktarıcı test takımının sabitlendiği sürümlerdir. Daha yeni veya eski sürümler genellikle çalışır, ancak dışa aktarıcı yamaları belirli bir API yüzeyine yönelik olduğundan, üretim araçları için bu sürümleri sabitleyin ve
HfExporter'ın sapma algıladığında bir uyarı kaydettiğini bekleyin.
pip install transformers "torch==2.12.0"pip install transformers "torch==2.12.0" "onnx==1.21.0" "onnxscript==0.7.0" onnxruntimepip install transformers "torch==2.12.0" "executorch==1.3.1"Apple Silicon üzerinde macOS 14 veya daha yeni bir sürüm gerektirir. MLX, macOS ARM64 ExecuTorch nightly tekerleklerinde dahildir; ayrı bir paket veya ek değildir. Aşağıdaki nightly çifti, MLX dışa aktarıcı testleriyle doğrulanmıştır. Standart ExecuTorch nightly kayıtlı deposundan yükleyin:
pip install transformers
pip install \
"executorch==1.6.0.dev20260924" \
"torch==2.15.0.dev20260924" \
--extra-index-url https://download.pytorch.org/whl/nightly/cpuGece sürümü ExecuTorch tekerlekleri bunu bir bağımlılık olarak beyan etmediği için torch paketini açıkça yükleyin.
Kaynak derlemeleri için MLX kurulum kılavuzuna bakın.
pip install transformers "torch==2.12.0" "openvino==2026.3.1"Bir model dışa aktarın
Tüm dışa aktarıcılar aynı arayüzü paylaşır. Bir yapılandırma ile dışa aktarıcı oluşturun ve export() çağrısını yapın.
Dışa aktarıcı sınıfını değiştirerek çalışma zamanları arasında geçiş yapın.
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)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 hedefi seçer: CPU için xnnpack (varsayılan), Apple Silicon GPU için mlx veya CUDA destekli bir GPU için cuda. Uygun kurulum seçeneğini seçin. MLX çalıştırması, MLX vekilini ve Metal kütüphanelerini gerektirir; CUDA destekli olmayan bir ortamda CUDA istenirse RuntimeError yükseltilir.
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()))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)Dinamik şekiller
dynamic=True parametresinin geçirilmesi, dışa aktarılan grafiğin çalışma zamanında yeniden izleme yapmadan herhangi bir boyutta girdileri kabul etmesi için her tensör boyutunu dinamik olarak işaretler.
Hangi boyutların dinamik olacağına dair ince taneli kontrol için, bunun yerine doğrudan torch.export.export'a iletilen açık dynamic_shapes parametresini geçirin.
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)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)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)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)Üretken modeller
Otonom regresif üretimde, modelin forward fonksiyonu, prefill adımı (tam istem, KV önbelleği yok) ile decode adımı (tek token, dolu KV önbelleği) arasında farklı şekillere sahiptir. Dışa aktarıcılar, her iki aşamayı ayırıp her birini dışa aktaran ~HfExporter.export_for_generation yöntemini sunar.
Çok modlu üretken modellerde, prefill ayrıca bir görüntü veya ses kodlayıcıya, dil modeline ve lm_head bileşenine ayrılır. Kodlayıcı ve dil modelinin keşfi, get_encoder() (modality="image" veya "audio") ve get_decoder() erişimcilerini kullandığından, bu bileşenleri kullanan herhangi bir yeni mimari hazır olarak çalışır.
Bir projeksiyon bileşeni yalnızca model, bir öznitelik adı altında (multi_modal_projector, connector, embed_vision, embed_audio) bir projeksiyon açığa çıkardığında görünür. Aşağıdaki Qwen2-VL, projeksiyonunu görme kulesine (vision tower) dahil eder, bu nedenle bileşen sözlüğünde ayrı bir multi_modal_projector anahtarı yoktur. Yeni mimariler, listeyi büyütmek yerine projeksiyon özniteliklerini bu adlardan biriyle hizalamalıdır.
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}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}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": ...}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}Dışa aktarılan bileşenler bağımsız grafiklerdir, çalışmaya hazır bir çıkarım hattı değildir. Çağıran taraf, her kodlayıcıyı çalıştırmaktan, gömme projeksiyonlarını uygulamaktan ve üretim döngüsünü koordine etmekten sorumludur.
export_for_generation nasıl çalışır
decompose_for_generation() model.generate(**inputs, max_new_tokens=2) komutunu bir kez çalıştırır ve gerçek prefill ile decode kwargs'larını yakalamak için model.forward fonksiyonuna hook ekler (model çok modluysa, her encoder/projector/language model alt modülüne eklenen hook'lar aracılığıyla alt modül bazlı kwargs'ları da yakalar). Bu sayede, model bazlı yapıştırıcı kodlara (glue code) gerek kalmadan, yalnızca decoder'lı, SSM, encoder-decoder ve çok modlu modeller dahil olmak üzere herhangi bir mimari için çalışır. export_for_generation bunun üzerine kurulu tek satırlık bir işlemdir.
Capture, modeli inputs üzerinde aceleci (eager) olarak çalıştırır; bu nedenle kısa bir istem, tek bir küçük görsel veya birkaç ses çerçevesi gibi küçük ama temsilci değerler geçirin. Dışa aktarılan program bu boyutlara bağlı değildir (dinamik şekiller yine de akar), ancak daha küçük capture girdileri decompose_for_generation işlemini daha ucuz hale getirir ve sembolik şekil çıkarımını yönetilebilir tutar.
decompose_for_generation fonksiyonunu doğrudan çağırarak, ayrıştırma ve dışa aktarma işlemleri arasında müdahale edebilirsiniz; örneğin doğrulama için bir eager forward çalıştırabilir, bir alt modülün girdilerini değiştirebilir veya bir aşamayı atlayabilirsiniz.
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)Çoklu jeton çözme
Varsayılan olarak decode bileşeni tek jeton adımdır — KV önbelleğine karşı bir sorgu jetonu —
bu nedenle torch.export, sorgu dizisi eksenini 1'e özelleştirir. Bunun yerine decode bileşenini çoklu jeton olarak yakalamak için multi_token_decode=True parametresini iletin: decompose_for_generation() fonksiyonu, iki ardışık
decode adımını (yakalama max_new_tokens=3 ile yapılır) tek bir ileri geçişte birleştirir, böylece bu eksen
sembolik olarak kalır. Tek bir grafik, her sorgu uzunluğunu karşılar — tek jeton (sıradan çözümleme),
birden fazla jetonun aynı anda işlenmesi (geçmişten devam, örneğin spekülatif jetonların bir bölümünün kabulü) ve
önbellek boşken düz bir ön işleme (prefill).
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 tokensfrom 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 tokensfrom 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 tokensSorgu ekseni yalnızca dinamik şekilli bir dışa aktarmada (dynamic=True) sembolik kalır; statik bir dışa aktarma, bu ekseni yakalanan uzunlukta dondurarak sabit çoklu-jetonlu bir grafik oluşturur. Bu, aşağıdaki statik KV önbelleğiyle birleşir — birleştirilmiş çözümleme, her adımın jetonlarını sabit boyutlu önbelleğe yerinde yazar ve önbellek, jetonların nereye yerleştirileceğini içsel olarak yönetir.
Statik KV önbelleği
Bu bölümdeki ExecuTorch örnekleri, kopyasız güncellemeler ve çözümleme döngüsü dahil olmak üzere, yalnızca XNNPACK içindir. MLX dışa aktarıcısı
StaticCacheöğesini reddeder; MLX ileDynamicCachekullanın.
generate() varsayılan olarak bir DynamicCache büyütür ve dizi uzadıkça yeniden tahsis eder — bu, dışa aktarılmış bir grafik için hareketli bir hedeftir. statik bir önbellek, bir kez tahsis edilen ve her adımda geçerli konuma yerinde yazılan sabit boyutlu bir tampondur. çoklu jeton çözme ile birleştirildiğinde, üretimi tek bir dışa aktarılmış grafiğe indirger: decode grafiği, sabit boyutlu bir önbellek ve değişken sayıda sorgu jetonu alır, böylece tek bir grafik hem istemi (boş önbellek → prefill) hem de üretilen her jetonu (dolu önbellek → decode) karşılar. GenerationConfig içinde cache_implementation="static" (ve bir max_cache_len) ile multi_token_decode=True parametresini ileterek dışa aktarın:
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
)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
)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 grafiği artık iki sembolik eksene sahip — sorgu uzunluğu (kaç token beslediğiniz) ve önbellek uzunluğu (max_cache_len, yükleme sırasında yeniden boyutlandırılabilir). dynamic=True bu eksenleri (ve diğer tüm eksenleri) Dim.AUTO olarak işaretler, böylece dışa aktarılan grafik, yükleme sırasında herhangi bir istem uzunluğunu ve önbellek boyutunu kabul eder.
Sıfır kopyalı yerinde güncellemeler
Statik önbellek dışarıdan geçirilir ve yerinde değiştirilir, böylece bir tampon, host kopyaları olmadan decode adımları arasında durumu taşır — koşullu çalışma zamanı, kendi arena’sı üzerinden kopyalama yapmak yerine çağıranın tamponlarını bağladığı sürece. Bunun gerektirdiği, geriye kalan tek backend’ye özgü kısımdır:
Dynamo — dışa aktarılan program, önbellek yazımını
USER_INPUT_MUTATIONolarak modellediğinden,components["decode"].module()(...)çağrısı, doğrudan ilettiğiniz önbellek tensörlerini günceller. Her adımda aynı tensörleri yeniden kullanın; yapılandırılacak bir şey yoktur.ONNX Runtime — decode grafiği, önbelleği eşleşen
input.<name>/output.<name>çiftleri olarak açığa çıkar. ORT’ninCudaSession.set_buffer_sharing(onnxruntime.transformers.io_binding_helper) yöntemi, her çifti tek bir cihaz tamponuna bağlar; böylece önbellek, host tarafına gidip gelmeden döngü boyunca yerinde okunur ve güncellenir.ExecuTorch (XNNPACK) —
ExecutorchConfigüzerinde bellek planlama tahsislerini kapatın, böylece yerinde yazma işlemi çağıranın kendi tensörüne yazılsın (her bayrağın ne yaptığını görmek için referansa bakın):config = ExecutorchConfig( backend="xnnpack", dynamic=True, alloc_graph_input=False, alloc_graph_output=False, alloc_mutable_buffers=False, )Sıfır kopyalı yerinde yazma, çağrıcının çalışma zamanında
Method::set_output_data_ptraracılığıyla çıktı tamponlarını bağlamasını da gerektirir — Python çalışma zamanında yüzeye çıkmaz (executorch.runtime.Methodyalnızcaexecute/set_inputs/get_outputssunar). Yukarıdaki bayraklar bunu kurar, ancak yerinde yazma sadece C++ için bir yoldur (aşağıdaki ExecuTorch decode döngüsü örneğine bakın). Python'dan, güncellenmiş önbelleği her adımda yöntem çıktılarını okuyarak geri alın.
Decode-loop inference examples
Döngü her arka uçta aynı şekildedir — baştan sona aynı grafiktir. Boş, sabit boyutlu bir önbellekten başlayın, tüm istemi bir kez besleyin (boş önbellek → ön doldurma), ardından her adımda bir token ekleyin (dolu önbellek → çözümleme). Her çağrı input_ids, nedensel bir attention_mask ve position_ids (her adımda yeni token sayısınca ilerletilir) ile önbelleği iletir ve her sorgu konumu için logitler döner. Her tokenın önbellekteki yeri, statik önbellek tarafından içsel olarak izlenir, bu nedenle çağrı boyunca ek bir şey iletmenize gerek yoktur. Önbelleğin nasıl kurulduğu çalışma zamanına göre değişir (Dynamo için StaticCache nesnesi, ONNX Runtime için ham cihaz tamponları, ExecuTorch için C++ tarafında çağıran dizileri), bu yüzden her sekme aşağıda kendi kurulumunu oluşturur. Dynamo ve ONNX Runtime sekmeleri önbelleği yerinde günceller; ExecuTorch’ün yerinde güncelleme yolu C++’tır (yukarıda belirtildiği gibi Python çalışma zamanı bunu yapamaz).
torch.export, statik önbellek yazımını USER_INPUT_MUTATION olarak kaydeder; bu nedenle yüklenen grafiğin module() çağrısı, doğrudan ilettiğiniz StaticCache’i günceller — tek bir önbellek, bağlamaya veya geri aktarılmaya gerek kalmadan tüm döngü boyunca durumu taşır. register_pytree_node(StaticCache), torch.export.load’ün StaticCache girdisini düzleştirilmemiş hale getirmesini sağlar. Önbelleğin önceden başlatılması gerekir (torch.export, ayrılmış K/V’yi girdi belirlemesine gömdüğü için tembel boş bir önbellek eşleşmez) — ancak kaydedilen program kendi example_inputs’ini taşır; bu nedenle, boş olarak sıfırlanmış, zaten başlatılmış StaticCache şablonunu yeniden kullanın:
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 grafiği olduğu gibi çalıştırır; yerinde önbellek güncellemesi, ORT io-binding üzerine ince bir sarmalayıcı olan ORT’nin CudaSession (onnxruntime.transformers.io_binding_helper) ile yapılır. set_buffer_sharing, bir önbellek input.<name> ve ona karşılık gelen output.<name> öğesini tek bir cihaz tamponuna bağlar; böylece değiştirilen K/V/sayaçlar doğrudan girdiye geri yazılır. allocate_buffers, kalan (paylaşılmayan) çıktıları — burada yalnızca logits — tahsis eder; infer(feed_dict) ise CUDA tensörlerinizi işaretçi üzerinden bağlar ve çalıştırır. Önbellek tamponları, grafiğin kendi girdi meta verisinden (get_inputs() şekli ve tipi) doğrudan gelir, bu nedenle model yapılandırmasına gerek yoktur — tek sembolik eksen (önbellek uzunluğu) max_cache_len olur:
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’un cihaz içi çalışma zamanı C++’tır ve yerinde önbellek güncellemesi Method::set_output_data_ptr’ye dayanır — Python çalışma zamanında yüzeye çıkmaz (executorch.runtime.Method yalnızca execute/set_inputs/get_outputs’ü açığa çıkarır), bu nedenle kopyasız çözümleme yalnızca C++ yoluyla yapılır. Değiştirilen her önbellek çıkışını eşleşen önbellek girdi tamponuna bağlayın; yeni K/V/sayaçlar kopyalama yapılmadan çağırıcının StaticCache tamponlarına yerleşir. Diğer sekmelerde olduğu gibi biçimler ve boyutlar artefaktın kendisinden gelir — burada programın method_meta’sı (input_tensor_meta/output_tensor_meta → TensorInfo::nbytes()); C++ tarafında sorgulanacak bir Python çalışma zamanı yoktur:
#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));
}Kantizasyon
Her dışa aktarma yapılandırması, dışa aktarma sırasında modeli torch.export FX grafiğinde (her arka uç için PT2E veya NNCF) ya da dönüştürülmüş ONNX veya OpenVINO modelinde (ONNX Runtime veya NNCF) kuantize eden bir quantizer alır. Kuantizörler, arka uçları, kalibrasyon ve bunlardan birini nasıl ekleyeceğiniz hakkında bilgi için Dışa aktarmaları kuantize etme bölümüne bakın.
Kısıtlamalar ve geçici çözümler
torch.export, torch.onnx.export ve ExecuTorch, belirli PyTorch kalıpları etrafında pürüzlere sahiptir. Dışa aktarıcılar, dışa aktarma akışında iyi tanımlanmış noktalarda uygulanan küçük bir grup geri alınabilir yama ve FX düzeyinde düzeltme ile bu sorunların etrafından dolaşır. Bunların hiçbiri herkese açık export API'sinden görünmez, ancak bilmeniz gereken en yaygın şeyler şunlardır:
- FlashAttention ve FlexAttention hiçbir backend üzerinde dışa aktarılamaz.
sdpatercih edilen ayar olupeagerde çalışır (daha yavaş). Model başka bir ayar kullanıyorsa,exportçağrılmadan önce bunlardan birini modelde ayarlayın. grouped_mm,DynamoExporterüzerinden sorunsuz izlenir veOnnxExporteriçin otomatik olarak çevrilir. XNNPACK veya MLX ileExecutorchExporterkullanıldığında, dışa aktarıcı dışa aktarma öncesinde MoE uzmanlarınıbatched_mmolarak değiştirir.- Sliding-window katmanları (Mistral, Gemma 3) tam bir anahtar-değer önbelleğiyle dışa aktarılır; kayan maske hâlâ pencereyi uygular.
Sonraki adımlar
- Dışa aktarıcıları genişletme bölümündeki yamalar ve düzeltme kayıt defterleri ile yeni bir mimari veya arka uç için dışa aktarma desteği ekleyin.
- Bir dışa aktarmayı kuantize edin veya Dışa aktarmaları kuantize etme bölümünde bir kuantizatör ekleyin.