Transformers documentation
المصدّرون
المصدّرون
صدّر أي 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)، وستُزال بمجرد اعتماد الإصلاح في المصدر الأصلي. حتى تستقر واجهة برمجة التطبيقات، تعامل مع التصحيحات على أنها مرتبطة بالإصدارات المستخدمة في مجموعة الاختبارات. ثبّت تلك الإصدارات في أدوات الإنتاج، وتوقع ظهور تصحيحات جديدة واختفاء القديمة مع اعتماد التغييرات في المصدر الأصلي.
| مُصدِّر | المخرجات | وقت التشغيل |
|---|---|---|
DynamoExporter | ExportedProgram | أي وقت تشغيل لـ PyTorch، تجميع مسبق |
OnnxExporter | ONNXProgram | أي وقت تشغيل ONNX (ORT، TensorRT، OpenVINO) |
ExecutorchExporter | ExecutorchProgramManager | الأجهزة المحمولة وأجهزة الحافة (ExecuTorch) |
OpenVINOExporter | openvino.Model | بيئة تشغيل OpenVINO (معالج CPU/وحدة معالجة رسومية GPU/وحدة معالجة عصبية NPU من Intel) |
يختار AutoHfExporter المُصدِّر المناسب من ملف الإعداد، بينما يختار AutoExportConfig فئة الإعداد الصحيحة من قاموس. يتبع كلاهما نفس نمط الفئة التلقائية في Transformers، وهو مفيد عند اختيار الخلفية في وقت التشغيل بدلاً من تثبيتها في موقع الاستدعاء.
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)التثبيت
قم بتثبيت التبعيات الخاصة بالخلفية التي تخطط للتصدير إليها.
الإصدارات أدناه هي التي تُثبَّت عليها مجموعة اختبارات المُصدِّر. غالبًا ما تعمل الإصدارات الأحدث أو الأقدم، لكن تصحيحات المُصدِّر تستهدف واجهة برمجة تطبيقات محددة، لذا في أدوات الإنتاج ثبِّت هذه الإصدارات وتوقّع أن يسجّل
HfExporterتحذيرًا عند اكتشافه انحرافًا.
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"يتطلب macOS 14 أو أحدث على Apple Silicon. يُضمَّن MLX في عجلات ExecuTorch الليلية لنظام macOS ARM64؛ فهو ليس حزمة أو إضافة منفصلة. تم التحقق من الزوج الليلي التالي مع اختبارات مُصدِّر MLX. ثبِّته من سجل ExecuTorch الليلي القياسي:
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قم بتثبيت torch بشكل صريح لأن عجلات ExecuTorch اليومية لا تعلن عنه كاعتمادية.
لبناء المصدر، راجد دليل تثبيت MLX.
pip install transformers "torch==2.12.0" "openvino==2026.3.1"تصدير نموذج
تشارك جميع مُصدِّرات البيانات نفس الواجهة. أنشئ مُصدِّر بيانات باستخدام إعداد، ثم استدعِ export().
يمكنك التبديل بين بيئات التشغيل عن طريق استبدال فئة المُصدِّر.
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 الهدف: xnnpack (الافتراضي) للمعالج المركزي، أو mlx لوحدة معالجة الرسومات في Apple Silicon، أو cuda لوحدة معالجة رسومات مفعّلة بتقنية CUDA. اختر خيار التثبيت المقابل. يتطلب تنفيذ MLX وكيل MLX ومكتبات Metal الخاصة به؛
وطلب CUDA في بيئة غير مفعّلة بتقنية CUDA يثير RuntimeError.
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)الأشكال الديناميكية
تعيين dynamic=True يحدد كل بُعد من أبعاد المصفوفة (Tensor) على أنه ديناميكي، مما يتيح للرسم البياني المُصدَّر قبول مدخلات بأي حجم وقت التشغيل دون الحاجة إلى إعادة التتبع.
للحصول على تحكم دقيق في الأبعاد الديناميكية، مرّر dynamic_shapes صراحةً بدلاً من ذلك، حيث يتم تمريرها مباشرة إلى torch.export.export.
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)النماذج التوليدية
في التوليد الذاتي التتابعي، يكون لشكل forward الخاص بالنموذج أشكال مختلفة في خطوة التعبئة المسبقة (الموجز الكامل، بدون ذاكرة KV) مقارنةً بخطوة فك الترميز (رمز واحد، ذاكرة KV ممتلئة). تعرض أدوات التصدير ~HfExporter.export_for_generation، والتي تقسم المرحلتين وتصدر كلًا منهما على حدة.
بالنسبة إلى النماذج التوليدية متعددة الوسائط، ينقسم التمهيد (prefill) بشكل إضافي إلى مُشفِّر للصور أو الصوت، ونموذج اللغة، وlm_head. يعتمد اكتشاف المُشفِّر ونموذج اللغة على مُوجِّهَي get_encoder() (مع modality="image" أو "audio") وget_decoder()، بحيث تعمل أي بنية معمارية جديدة تستخدم هذه المُوجِّهات دون أي إعداد مسبق.
يظهر مكوّن المُسقط (projector) فقط عندما يكشف النموذج عن واحد تحت اسم خاصية (multi_modal_projector، connector، embed_vision، embed_audio). في النموذج Qwen2-VL أدناه، يُدمج المُسقط في برج الرؤية، لذا فإن قاموس مكوّناته لا يحتوي على مفتاح multi_modal_projector منفصل. يجب على البنى المعمارية الجديدة محاذاة خاصية المُسقط لديها مع أحد هذه الأسماء بدلاً من توسيع القائمة.
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}المكونات المُصدَّرة هي رسوم بيانية مستقلة، وليست خط أنابيب استدلال جاهزًا للتشغيل. يقع على عاتق المستدعي مسؤولية تشغيل كل مشفّر، وإسقاط التضمينات، وتنسيق حلقة التوليد.
كيف يعمل export_for_generation
decompose_for_generation() runs model.generate(**inputs, max_new_tokens=2) once and hooks model.forward to capture the real prefill and decode kwargs (and the
per-submodule kwargs via hooks on each encoder/projector/language model if the model is
multi-modal). That’s why it works for any architecture, including decoder-only, SSM,
encoder-decoder, and multi-modal models, without per-model glue. export_for_generation is a
one-liner over it.
تقوم عملية الالتقاط بتشغيل النموذج بشكل فوري على inputs، لذا مرّر قيمًا صغيرة لكنها تمثيلية، مثل موجه قصير، أو صورة صغيرة واحدة، أو بعض الإطارات الصوتية. البرنامج المُصدَّر غير مرتبط بتلك الأحجام (فالأشكال الديناميكية لا تزال تتدفق عبره)، لكن مدخلات الالتقاط الأصغر تجعل decompose_for_generation أرخص وتُبقي الاستدلال على الأشكال الرمزية قابلاً للتنفيذ.
استدعِ decompose_for_generation مباشرةً للتدخل بين التفكيك والتصدير، مثل تنفيذ تمرير عاجل للتحقق، أو تبديل مدخلات وحدة فرعية، أو تجاوز مرحلة معينة.
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 —
لذلك يُخصِّص torch.export محور تسلسل الاستعلام إلى 1. مرِّر multi_token_decode=True لالتقاط decode كخطوة فك ترميز متعددة الرموز بدلاً من ذلك: يدمج decompose_for_generation() خطوتين متتاليتين من فك الترميز (يُلتقطان مع max_new_tokens=3) في تمريرة أمامية واحدة، بحيث يبقى ذلك المحور رمزياً. ثم تخدم مخططة واحدة كل طول للاستعلام — رمز واحد (فك ترميز عادي)، أو عدة رموز دفعة واحدة (استكمال من الماضي، مثل قبول مجموعة من الرموز التخمينية)، أو تعبئة مسبقة عادية عندما تكون الذاكرة المؤقتة فارغة.
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 tokensيبقى محور الاستعلام رمزيًا فقط عند التصدير بأشكال ديناميكية (dynamic=True)؛ أما التصدير الثابت فيجمّده عند الطول المُلتقط، مما يمنح رسمًا ثابتًا متعدد الرموز. يتكامل هذا مع ذاكرة KV الثابتة أدناه — حيث تكتب عملية فك الترميز المدمجة رموز كل خطوة في ذاكرة التخزين المؤقت ذات الحجم الثابت في مكانها، وتتعامل الذاكرة داخليًا مع مكان هبوطها.
ذاكرة KV الثابتة
أمثلة ExecuTorch في هذا القسم، بما في ذلك التحديثات بدون نسخ وحلقة فك الترميز، هي خاصة بـ XNNPACK فقط. يرفض مُصدِّر MLX
StaticCache؛ استخدمDynamicCacheمع MLX.
تُنشئ generate() كاش DynamicCache افتراضيًا، مع إعادة تخصيص الذاكرة كلما امتدّت التسلسلات — وهو هدف متحرك
للرسوم المصدَّرة. الكاش الثابت هو مخزن ذاكرة بحجم ثابت، يُخصَّص مرة واحدة ويُكتب فيه في الموقع الحالي في كل خطوة. عند دمجه مع فك ترميز متعدد الرموز ينهار
التوليد في رسم مصدَّر واحد: يتلقى رسم decode كاشًا بحجم ثابت وعددًا متغيرًا من رموز الاستعلام، بحيث يخدم رسم واحد كلًا من الموجه (كاش فارغ → تعبئة مسبقة) وكل رمز مولَّد
(كاش مملوء → فك ترميز). صدّره عن طريق تمرير GenerationConfig مع cache_implementation="static" (وmax_cache_len) إلى جانب multi_token_decode=True:
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 الآن محوران رمزيان — طول الاستعلام (عدد الرموز التي تُدخلها) وطول الكاش (max_cache_len، قابل للتعديل عند التحميل). يُحدد dynamic=True هذه المحاور (وكل المحاور الأخرى) على أنها Dim.AUTO، بحيث يقبل الرسم المُصدَّر أي طول للاستعلام وأي حجم للكاش عند التحميل.
التحديثات في الموقع دون نسخ
يُمرَّر الكاش الثابت ويُعدَّل في مكانه، لذا تحمل وسادة واحدة الحالة عبر خطوات فك الترميز دون نسخ على المضيف — طالما أن وقت التشغيل يربط وسادات المستدعي بدلاً من النسخ عبر ساحة خاصة به. ما يتطلبه ذلك هو الجزء الوحيد المتبقي لكل منصة:
Dynamo — يُمثّل البرنامج المُصدَّر كتابةَ الكاش على أنها
USER_INPUT_MUTATION، لذا فإن استدعاءcomponents["decode"].module()(...)يُحدِّث مُتجهات الكاش التي تمررها مباشرةً. أعد استخدام نفس المُتجهات في كل خطوة؛ لا حاجة لأي إعداد.ONNX Runtime — يكشف رسم فك الترميز عن ذاكرة التخزين المؤقت كأزواج مطابقة من
input.<name>/output.<name>. تربط دالةCudaSession.set_buffer_sharingفي ORT (onnxruntime.transformers.io_binding_helper) كل زوج مع مخزن بيانات واحد على الجهاز، بحيث تُقرأ ذاكرة التخزين المؤقت وتُحدَّث في مكانها عبر الحلقة دون أي رحلات ذهاب وإياب إلى المضيف.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— غير معرّض عبر بيئة التشغيل الخاصة بلغة بايثون (فإنexecutorch.runtime.Methodيعرّض فقطexecute/set_inputs/get_outputs). تُهيّئ الأعلام المذكورة أعلاه هذا الإعداد، لكن الكتابة في الموقع هي مسار مخصص لـ C++ فقط (انظر مثال حلقة فك الترميز في ExecuTorch أدناه). من بايثون، اقرأ ذاكرة التخزين المؤقت المحدّثة من مخرجات الطريقة في كل خطوة.
Decode-loop inference examples
تتخذ الحلقة الشكل نفسه على جميع منصات التشغيل — فهي نفس الرسم البياني طوال العملية. ابدأ من ذاكرة مؤقتة ثابتة الحجم فارغة، ثم مرّر الموجه (prompt) كاملاً مرة واحدة (ذاكرة مؤقتة فارغة → مرحلة التعبئة المسبقة prefill)، ثم رمزاً تلو الآخر (ذاكرة مؤقتة ممتلئة → مرحلة فك الترميز decode). تمرّر كل استدعاء input_ids، وقناع انتباه سببي attention_mask، وposition_ids (تُزاد بعدد الرموز الجديدة في كل خطوة)، بالإضافة إلى الذاكرة المؤقتة، وتُعاد منها قيم اللوجيت (logits) لكل موضع استعلام. يتتبع الكاش الثابت (static cache) داخلياً موضع كل رمز في الذاكرة المؤقتة، لذا لا حاجة لإيصال أي شيء إضافي عبر الاستدعاء. يختلف إعداد الذاكرة المؤقتة حسب بيئة التشغيل (كائن StaticCache لـ Dynamo، مخازن أجهزة خام لـ ONNX Runtime، مصفوفات مستدعي في C++ لـ ExecuTorch)، لذا يبني كل تبويب إعداداته أدناه. تحدّث تبويبا Dynamo وONNX Runtime الذاكرة المؤقتة في مكانها؛ أما المسار في المكان نفسه في ExecuTorch فهو بلغة C++ (فبيئة التشغيل الخاصة به بلغة بايثون لا تستطيع ذلك، كما ذُكر أعلاه).
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)يعمل ONNX Runtime على الرسم البياني كما هو؛ ويتم تحديث ذاكرة التخزين المؤقت في الموقع باستخدام CudaSession الخاص بـ ORT (onnxruntime.transformers.io_binding_helper)، وهو غلاف رفيع فوق io-binding الخاص بـ ORT. يربط set_buffer_sharing مدخل ذاكرة التخزين المؤقت input.<name> ومخرجه المقابل output.<name> بـ مخزن واحد على الجهاز، بحيث تُكتب قيم K/V/العداد المعدّلة مباشرةً إلى المدخل؛ ويخصص allocate_buffers المخرجات المتبقية (غير المشتركة) — وهنا logits فقط؛ بينما يربط infer(feed_dict) متجهات CUDA الخاصة بك عبر المؤشر وينفّذ العملية. تأتي مخازن ذاكرة التخزين المؤقت مباشرةً من بيانات وصفية المدخلات الخاصة بالرسم البياني نفسه (شكل get_inputs() ونوعه)، لذا لا حاجة إلى إعدادات النموذج — حيث يصبح المحور الرمزي الوحيد (طول ذاكرة التخزين المؤقت) max_cache_len:
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 — غير معرّض عبر وقت التشغيل بلغة بايثون (فإن executorch.runtime.Method يعرّض فقط execute/set_inputs/get_outputs)، لذا فإن مسار فك الترميز بدون نسخ هو مسار حصري للغة C++. اربط كل مخرَج للكاش المُعدَّل مع مخزن المدخلات المطابق له في الكاش، وستصل قيم K/V/العداد الجديدة إلى مخازن StaticCache الخاصة بالمستدعي دون أي نسخ. وكما في التبويبات الأخرى، تأتي الأشكال والأحجام من الأداة نفسها — هنا من method_meta للبرنامج (input_tensor_meta/output_tensor_meta → TensorInfo::nbytes())، فلا يوجد وقت تشغيل بلغة بايثون يمكن الاستعلام منه في كود C++:
#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). راجع تكميم التصديرات للاطلاع على مُكمّيمات التصدير، وواجهاتها الخلفية، والمعايرة، وكيفية إضافة واحدة منها.
القيود والحلول البديلة
لكلٍّ من torch.export وtorch.onnx.export وExecuTorch بعض العيوب الدقيقة المتعلقة بأنماط PyTorch محددة. تعمل أدوات التصدير على تجاوز هذه العيوب من خلال مجموعة صغيرة من التصحيحات القابلة للعكس وإصلاحات على مستوى FX تُطبَّق في نقاط محددة بوضوح ضمن سير عمل التصدير. لا يظهر أيٌّ من ذلك من واجهة export العامة، لكن أكثر الأمور شيوعًا التي ينبغي معرفتها هي:
- لا يمكن تصدير FlashAttention وFlexAttention على أي منصة خلفية. يُعد
sdpaالإعداد المفضل، ويعملeagerأيضاً (ببطء أكبر). اضبط أحدهما على النموذج قبل استدعاءexportإذا كان يستخدم إعداداً آخر. - يتتبع
grouped_mmبسلاسة عبرDynamoExporterويُترجم تلقائيًا من أجلOnnxExporter. أما في حالة استخدامExecutorchExporterمع XNNPACK أو MLX، فيقوم المُصدِّر باستبدال خبراء MoE بـbatched_mmقبل التصدير.
الخطوات التالية
- أضِف دعم التصدير لهندسة أو واجهة خلفية جديدة باستخدام التصحيحات وسجلات الإصلاح في توسيع المُصدِّرين.
- قم بتكميم تصدير، أو أضف مُكمِّم، في تكميم التصديرات.