# المصدّرون

صدّر أي [PreTrainedModel](/docs/transformers/main/ar/main_classes/model#transformers.PreTrainedModel) إلى ONNX أو ExecuTorch أو برنامج PyTorch مستقل، بغض النظر عن وقت التشغيل المستهدف.

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

توجد أدوات التصدير داخل مكتبة Transformers بدلاً من مكتبة فرعية لاحقة، لذا تُدعم تغييرات البنية وأنماط الانتباه الجديدة وأنواع ذاكرة التخزين المؤقت المخصصة عند التصدير فور توفرها في كود النمذجة.

> [!WARNING]
> المُصدِّرات تجريبية. تعمل العديد من التصحيحات في هذا الوحدة على تجاوز أخطاء محددة في المصادر الأصلية (Torch، ONNX Script، ONNX Runtime، ExecuTorch)، وستُزال بمجرد اعتماد الإصلاح في المصدر الأصلي. حتى تستقر واجهة برمجة التطبيقات، تعامل مع التصحيحات على أنها مرتبطة بالإصدارات المستخدمة في مجموعة الاختبارات. ثبّت تلك الإصدارات في أدوات الإنتاج، وتوقع ظهور تصحيحات جديدة واختفاء القديمة مع اعتماد التغييرات في المصدر الأصلي.

| مُصدِّر               | المخرجات                     | وقت التشغيل                                    |
| ---------------------- | -------------------------- | ------------------------------------------ |
| `DynamoExporter`     | `ExportedProgram`          | أي وقت تشغيل لـ PyTorch، تجميع مسبق       |
| `OnnxExporter`       | `ONNXProgram`              | أي وقت تشغيل ONNX (ORT، TensorRT، OpenVINO) |
| `ExecutorchExporter` | `ExecutorchProgramManager` | الأجهزة المحمولة وأجهزة الحافة (ExecuTorch)       |
| `OpenVINOExporter`   | `openvino.Model`           | بيئة تشغيل OpenVINO (معالج CPU/وحدة معالجة رسومية GPU/وحدة معالجة عصبية NPU من Intel)       |

يختار `AutoHfExporter` المُصدِّر المناسب من ملف الإعداد، بينما يختار `AutoExportConfig` فئة الإعداد المناسبة من قاموس. وكلاهما يتبع نمط فئة auto في Transformers، وهو أمر مفيد عندما يتم اختيار الخلفية في وقت التشغيل بدلاً من تثبيتها في موقع الاستدعاء.

```python
from transformers.exporters import AutoExportConfig, AutoHfExporter

export_config_dict = {"export_format": "onnx", "dynamic": True}
config = AutoExportConfig.from_dict(export_config_dict)
exporter = AutoHfExporter.from_config(config)

onnx_program = exporter.export(model, inputs, config=config)
```

## التثبيت

قم بتثبيت التبعيات الخاصة بالخلفية التي تخطط للتصدير إليها.

> [!TIP]
> الإصدارات أدناه هي الإصدارات التي تُثبَّت عليها مجموعة اختبارات المُصدِّر. غالبًا ما تعمل الإصدارات الأحدث أو الأقدم، لكن تصحيحات المُصدِّر تستهدف واجهة برمجة تطبيقات محددة، لذا في أدوات الإنتاج ثبِّت هذه الإصدارات وتوقّع أن يسجّل `HfExporter` تحذيرًا عند اكتشافه انحرافًا.

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

```bash
pip install transformers "torch==2.12.0" "onnx==1.21.0" "onnxscript==0.7.0" onnxruntime
```

```bash
pip install transformers "torch==2.12.0" "executorch==1.3.1"
```

يتطلب macOS 14 أو أحدث على Apple Silicon. يُضمَّن MLX في عجلات ExecuTorch الليلية لنظام macOS ARM64؛ وهو ليس حزمة أو إضافة منفصلة. تم التحقق من الزوج الليلي التالي مع اختبارات مُصدِّر MLX. ثبّته من سجل ExecuTorch الليلي القياسي:

```bash
pip install transformers
pip install \
  "executorch==1.6.0.dev20260924" \
  "torch==2.15.0.dev20260924" \
  --extra-index-url https://download.pytorch.org/whl/nightly/cpu
```

قم بتثبيت `torch` صراحةً لأن عجلات ExecuTorch اليومية لا تُعلن عنه كاعتمادية. أما لبناء المصدر، فراجع [دليل تثبيت MLX](https://docs.pytorch.org/executorch/main/backends/mlx/mlx-overview.html).

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

## تصدير نموذج

تشارك جميع مُصدِّرات البيانات نفس الواجهة. أنشئ مُصدِّر بيانات باستخدام إعداد، ثم استدعِ [export()](/docs/transformers/main/ar/main_classes/exporters#transformers.exporters.HfExporter.export).

يمكنك التبديل بين بيئات التشغيل عن طريق استبدال فئة المُصدِّر.

```python
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import DynamoExporter, DynamoConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer("Hello, world!", return_tensors="pt")

exporter = DynamoExporter()
config = DynamoConfig(dynamic=True)
exported = exporter.export(model, inputs, config=config)

# run the exported graph directly
outputs = exported.module()(**inputs)
```

```python
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import OnnxExporter, OnnxConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer("Hello, world!", return_tensors="pt")

exporter = OnnxExporter()
config = OnnxConfig(dynamic=True)
onnx_program = exporter.export(model, inputs, config=config)

# save and load with ONNX Runtime
onnx_program.save("model.onnx")

import onnxruntime as ort

session = ort.InferenceSession("model.onnx")
ort_inputs = {k: v.numpy() for k, v in inputs.items()}
outputs = session.run(None, ort_inputs)
```

يحدد [backend](/docs/transformers/main/ar/main_classes/exporters#transformers.exporters.ExecutorchConfig.backend) الهدف: `xnnpack` (الافتراضي) للمعالج المركزي، أو `mlx` لمعالج الرسوميات في Apple Silicon، أو `cuda` لمعالج رسوميات يدعم CUDA. اختر [خيار التثبيت](#installation) المقابل. يتطلب تنفيذ MLX وكيل MLX ومكتبات Metal الخاصة به؛ كما يؤدي طلب CUDA في بيئة غير مجهزة بدعم CUDA إلى حدوث `RuntimeError`.

```python
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import ExecutorchExporter, ExecutorchConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer("Hello, world!", return_tensors="pt")

exporter = ExecutorchExporter()
config = ExecutorchConfig(backend="xnnpack", dynamic=True)  # Use "mlx" on Apple Silicon.
et_program = exporter.export(model, inputs, config=config)

# save for on-device deployment
et_program.save("model.pte")

# load and run via the ExecuTorch Python runtime
from executorch.runtime import Runtime

program = Runtime.get().load_program("model.pte")
method = program.load_method("forward")
outputs = method.execute(list(inputs.values()))
```

```python
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import OpenVINOExporter, OpenVINOConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer("Hello, world!", return_tensors="pt")

exporter = OpenVINOExporter()
config = OpenVINOConfig(dynamic=True)
ov_model = exporter.export(model, inputs, config=config)

ov_model.save("model.xml")

# compile and run on CPU (or "GPU" / "NPU" if available)
import openvino as ov
compiled = ov.Core().compile_model(ov_model, "CPU")
ov_inputs = {k: v.numpy() for k, v in inputs.items()}
outputs = compiled(ov_inputs)
```

## الأشكال الديناميكية

يُعلِّم تمرير `dynamic=True` على كل بُعد من أبعاد المصفوفة (Tensor) بأنه ديناميكي، مما يتيح للمخطط المُصدَّر قبول مدخلات بأي حجم في وقت التشغيل دون الحاجة إلى إعادة التتبع.

للحصول على تحكم دقيق في الأبعاد الديناميكية، مرّر `dynamic_shapes` صراحةً بدلاً من ذلك، حيث يتم تمريرها مباشرة إلى [torch.export.export](https://pytorch.org/docs/stable/export.html).

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import DynamoExporter, DynamoConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer(["Hello, world!", "Hi"], padding=True, return_tensors="pt")

batch = torch.export.Dim("batch", min=1, max=32)
seq = torch.export.Dim("seq", min=1, max=2048)

exporter = DynamoExporter()
config = DynamoConfig(
    dynamic_shapes={"input_ids": {0: batch, 1: seq}, "attention_mask": {0: batch, 1: seq}},
    # Emit data-dependent shape guards as runtime asserts instead of failing the export when a
    # guard wouldn't hold across the explicit symbolic range. Most LLMs need this under fine-grained
    # ``Dim(min=, max=)`` bounds. Not needed with ``dynamic=True`` / ``Dim.AUTO``, where torch.export
    # infers shape relations instead of verifying them against user-stated bounds.
    prefer_deferred_runtime_asserts_over_guards=True,
)
exported = exporter.export(model, inputs, config=config)
```

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import OnnxExporter, OnnxConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer(["Hello, world!", "Hi"], padding=True, return_tensors="pt")

batch = torch.export.Dim("batch", min=1, max=32)
seq = torch.export.Dim("seq", min=1, max=2048)

exporter = OnnxExporter()
config = OnnxConfig(
    dynamic_shapes={"input_ids": {0: batch, 1: seq}, "attention_mask": {0: batch, 1: seq}},
    # Emit data-dependent shape guards as runtime asserts instead of failing the export when a
    # guard wouldn't hold across the explicit symbolic range. Most LLMs need this under fine-grained
    # ``Dim(min=, max=)`` bounds. Not needed with ``dynamic=True`` / ``Dim.AUTO``, where torch.export
    # infers shape relations instead of verifying them against user-stated bounds.
    prefer_deferred_runtime_asserts_over_guards=True,
)
onnx_program = exporter.export(model, inputs, config=config)
```

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import ExecutorchExporter, ExecutorchConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer(["Hello, world!", "Hi"], padding=True, return_tensors="pt")

batch = torch.export.Dim("batch", min=1, max=32)
seq = torch.export.Dim("seq", min=1, max=2048)

exporter = ExecutorchExporter()
config = ExecutorchConfig(
    backend="xnnpack",  # Use "mlx" on Apple Silicon.
    dynamic_shapes={"input_ids": {0: batch, 1: seq}, "attention_mask": {0: batch, 1: seq}},
    # Emit data-dependent shape guards as runtime asserts instead of failing the export when a
    # guard wouldn't hold across the explicit symbolic range. Most LLMs need this under fine-grained
    # ``Dim(min=, max=)`` bounds. Not needed with ``dynamic=True`` / ``Dim.AUTO``, where torch.export
    # infers shape relations instead of verifying them against user-stated bounds.
    prefer_deferred_runtime_asserts_over_guards=True,
)
et_program = exporter.export(model, inputs, config=config)
```

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import OpenVINOExporter, OpenVINOConfig

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
inputs = tokenizer(["Hello, world!", "Hi"], padding=True, return_tensors="pt")

batch = torch.export.Dim("batch", min=1, max=32)
seq = torch.export.Dim("seq", min=1, max=2048)

exporter = OpenVINOExporter()
config = OpenVINOConfig(
    dynamic_shapes={"input_ids": {0: batch, 1: seq}, "attention_mask": {0: batch, 1: seq}},
    # Emit data-dependent shape guards as runtime asserts instead of failing the export when a
    # guard wouldn't hold across the explicit symbolic range — most LLMs need this under fine-grained
    # ``Dim(min=, max=)`` bounds. Not needed with ``dynamic=True`` / ``Dim.AUTO``, where torch.export
    # infers shape relations instead of verifying them against user-stated bounds.
    prefer_deferred_runtime_asserts_over_guards=True,
)
ov_model = exporter.export(model, inputs, config=config)
```

## النماذج التوليدية

في التوليد الذاتي التكراري، يكون لشكل `forward` الخاص بالنموذج أشكال مختلفة في خطوة التمهيد (الموجّه الكامل، بدون ذاكرة KV) مقارنةً بخطوة فك الترميز (رمز واحد، ذاكرة KV ممتلئة). تُتيح أدوات التصدير `~HfExporter.export_for_generation`، والتي تقسم المرحلتين وتصدر كلًا منهما على حدة.

بالنسبة إلى النماذج التوليدية متعددة الوسائط، ينقسم التمهيد (prefill) بشكل إضافي إلى مُشفِّر للصور أو الصوت، ونموذج اللغة، و`lm_head`. يعتمد اكتشاف المُشفِّر ونموذج اللغة على مُعرِّفات الوصول [get_encoder()](/docs/transformers/main/ar/main_classes/model#transformers.PreTrainedModel.get_encoder) (مع `modality="image"` أو `"audio"`) و[get_decoder()](/docs/transformers/main/ar/main_classes/model#transformers.PreTrainedModel.get_decoder)، بحيث تعمل أي بنية معمارية جديدة تستخدم هذه المُعرِّفات دون أي إعداد مسبق.

يظهر مكوّن المُسقط (projector) فقط عندما يكشف النموذج عن مكوّن يحمل اسم خاصية (`multi_modal_projector`، `connector`، `embed_vision`، `embed_audio`). في النموذج Qwen2-VL أدناه، يتم دمج المُسقط في برج الرؤية (vision tower)، لذا فإن قاموس المكوّنات الخاص به لا يحتوي على مفتاح منفصل باسم `multi_modal_projector`. يجب على البنى المعمارية الجديدة محاذاة خاصية المُسقط لديها مع أحد هذه الأسماء بدلاً من توسيع القائمة.

```python
from transformers import AutoModelForImageTextToText, AutoProcessor
from transformers.exporters import DynamoExporter, DynamoConfig

model = AutoModelForImageTextToText.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
messages = [{"role": "user", "content": [{"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"}, {"type": "text", "text": "Describe this image."}]}]
text = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
inputs = processor(text=text, images=messages[0]["content"][0]["url"], return_tensors="pt").to(model.device)

exporter = DynamoExporter()
config = DynamoConfig(dynamic=True)
components = exporter.export_for_generation(model, inputs, config=config)
# components = {"image_encoder": ExportedProgram, "language_model": ExportedProgram, "lm_head": ExportedProgram, "decode": ExportedProgram}
```

```python
from transformers import AutoModelForImageTextToText, AutoProcessor
from transformers.exporters import OnnxExporter, OnnxConfig

model = AutoModelForImageTextToText.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
messages = [{"role": "user", "content": [{"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"}, {"type": "text", "text": "Describe this image."}]}]
text = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
inputs = processor(text=text, images=messages[0]["content"][0]["url"], return_tensors="pt").to(model.device)

exporter = OnnxExporter()
config = OnnxConfig(dynamic=True)
components = exporter.export_for_generation(model, inputs, config=config)
# components = {"image_encoder": ONNXProgram, "language_model": ONNXProgram, "lm_head": ONNXProgram, "decode": ONNXProgram}
```

```python
from transformers import AutoModelForImageTextToText, AutoProcessor
from transformers.exporters import ExecutorchExporter, ExecutorchConfig

model = AutoModelForImageTextToText.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
messages = [{"role": "user", "content": [{"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"}, {"type": "text", "text": "Describe this image."}]}]
text = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
inputs = processor(text=text, images=messages[0]["content"][0]["url"], return_tensors="pt").to(model.device)

exporter = ExecutorchExporter()
config = ExecutorchConfig(backend="xnnpack", dynamic=True)  # Use "mlx" on Apple Silicon.
components = exporter.export_for_generation(model, inputs, config=config)
# components = {"image_encoder": ExecutorchProgramManager, "language_model": ..., "lm_head": ..., "decode": ...}
```

```python
from transformers import AutoModelForImageTextToText, AutoProcessor
from transformers.exporters import OpenVINOExporter, OpenVINOConfig

model = AutoModelForImageTextToText.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-2B-Instruct")
messages = [{"role": "user", "content": [{"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"}, {"type": "text", "text": "Describe this image."}]}]
text = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
inputs = processor(text=text, images=messages[0]["content"][0]["url"], return_tensors="pt").to(model.device)

exporter = OpenVINOExporter()
config = OpenVINOConfig(dynamic=True)
components = exporter.export_for_generation(model, inputs, config=config)
# components = {"image_encoder": openvino.Model, "language_model": openvino.Model, "lm_head": openvino.Model, "decode": openvino.Model}
```

> [!WARNING]
> المكوّنات المُصدَّرة هي رسوم بيانية مستقلة، وليست خط أنابيب استنتاج جاهزًا للتشغيل. يتحمّل المُستدعي مسؤولية تشغيل كل مشفّر، وإسقاط التضمينات، وتنسيق حلقة التوليد.

### كيف يعمل `export_for_generation`

[decompose_for_generation()](/docs/transformers/main/ar/main_classes/exporters#transformers.exporters.utils.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` مباشرةً للتدخل بين التفكيك والتصدير، مثل تنفيذ تمرير عager للتحقق، أو تبديل مدخلات وحدة فرعية، أو تخطي مرحلة.

```python
from transformers.exporters.utils import decompose_for_generation

components = decompose_for_generation(model, inputs)
# {"image_encoder": (submodel, fwd_kwargs), "language_model": (...), ..., "decode": (...)}

exported = {}
for name, (submodel, subinputs) in components.items():
    eager_outputs = submodel(**subinputs)  # sanity-check the eager forward before exporting
    exported[name] = exporter.export(submodel, subinputs, config=config)
```

### فك ترميز متعدد الرموز

بشكل افتراضي، يُنفَّذ مكوّن `decode` كخطوة **رمز واحد** — أي رمز استعلام واحد مقابل ذاكرة KV — لذا يُخصّص `torch.export` محور تسلسل الاستعلام الخاص به إلى 1. مرّر `multi_token_decode=True` لالتقاط `decode` كخطوة فكّ تشفير **لأكثر من رمز** بدلاً من ذلك: يدمج [decompose_for_generation()](/docs/transformers/main/ar/main_classes/exporters#transformers.exporters.utils.decompose_for_generation) خطوتين متتاليتين من فكّ التشفير (يُلتقطان باستخدام `max_new_tokens=3`) في تمريرة أمامية واحدة، بحيث يبقى ذلك المحور رمزياً. ثم تخدم مخططة واحدة جميع أطوال الاستعلام — رمز واحد (فكّ تشفير عادي)، أو عدة رموز دفعة واحدة (استكمال من الماضي، مثل قبول مجموعة من الرموز التخمينية)، أو تعبئة مسبقة عادية عندما تكون الذاكرة المؤقتة فارغة.

```python
from transformers.exporters import DynamoExporter, DynamoConfig

exporter = DynamoExporter()
config = DynamoConfig(dynamic=True)
components = exporter.export_for_generation(model, inputs, config=config, multi_token_decode=True)
# components["decode"] now accepts a variable number of query tokens
```

```python
from transformers.exporters import OnnxExporter, OnnxConfig

exporter = OnnxExporter()
config = OnnxConfig(dynamic=True)
components = exporter.export_for_generation(model, inputs, config=config, multi_token_decode=True)
# components["decode"] now accepts a variable number of query tokens
```

```python
from transformers.exporters import ExecutorchExporter, ExecutorchConfig

exporter = ExecutorchExporter()
config = ExecutorchConfig(backend="xnnpack", dynamic=True)  # Use "mlx" on Apple Silicon.
components = exporter.export_for_generation(model, inputs, config=config, multi_token_decode=True)
# components["decode"] now accepts a variable number of query tokens
```

يبقى محور الاستعلام رمزيًا فقط عند التصدير بأشكال ديناميكية (`dynamic=True`)؛ أما التصدير الثابت فيجمّده عند الطول المُلتقط، مما ينتج عنه رسم بياني متعدد الرموز ثابت. يتكامل هذا مع ذاكرة KV الثابتة أدناه — ففك الترميز المدمج يكتب رموز كل خطوة في ذاكرة التخزين المؤقت ذات الحجم الثابت في مكانها، وتتعامل ذاكرة التخزين المؤقت داخليًا مع تحديد مواقعها.

### ذاكرة KV الثابتة

> [!NOTE]
> أمثلة ExecuTorch في هذا القسم، بما في ذلك التحديثات بدون نسخ ودورة فك الترميز، هي **خاصة بـ XNNPACK فقط**. يرفض مُصدِّر MLX `StaticCache`؛ استخدم `DynamicCache` مع MLX.

ينشئ `generate()` كاش `DynamicCache` افتراضياً، مع إعادة تخصيص الذاكرة كلما امتدّت التسلسلات — وهو هدف متحرك بالنسبة لرسم مُصدَّر. أما الكاش **الثابت** فهو مخزن ذاكرة بحجم ثابت، يُخصَّص مرة واحدة ويُكتب فيه في الموقع الحالي في كل خطوة. وعند دمجه مع [فك ترميز متعدد الرموز](#multi-token-decode)، ينهار توليد النص في رسم مُصدَّر واحد: حيث يستقبل رسم `decode` كاش بحجم ثابت وعددًا *متغيرًا* من رموز الاستعلام، فيخدم الرسم الواحد كلًا من الموجه (كاش فارغ → تعبئة مسبقة) وكل رمز مُولَّد (كاش مملوء → فك ترميز). صدّره عن طريق تمرير `GenerationConfig` مع `cache_implementation="static"` (و`max_cache_len`) إلى جانب `multi_token_decode=True`:

```python
from transformers import GenerationConfig
from transformers.exporters import DynamoExporter, DynamoConfig

exporter = DynamoExporter()
gen_config = GenerationConfig(cache_implementation="static", max_cache_len=2048)
components = exporter.export_for_generation(
    model, inputs, config=DynamoConfig(dynamic=True), generation_config=gen_config, multi_token_decode=True
)
```

```python
from transformers import GenerationConfig
from transformers.exporters import OnnxExporter, OnnxConfig

exporter = OnnxExporter()
gen_config = GenerationConfig(cache_implementation="static", max_cache_len=2048)
components = exporter.export_for_generation(
    model, inputs, config=OnnxConfig(dynamic=True), generation_config=gen_config, multi_token_decode=True
)
```

```python
from transformers import GenerationConfig
from transformers.exporters import ExecutorchExporter, ExecutorchConfig

exporter = ExecutorchExporter()
gen_config = GenerationConfig(cache_implementation="static", max_cache_len=2048)
components = exporter.export_for_generation(
    model, inputs, config=ExecutorchConfig(backend="xnnpack", dynamic=True), generation_config=gen_config, multi_token_decode=True
)
```

أصبح لمخطط `decode` محوران رمزيان الآن — طول الاستعلام (عدد الرموز التي تُدخلها) وطول الكاش (`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` حتى يمكن تنفيذ الكتابة في الموقع داخل المصفوفة الخاصة بالمستدعي (راجع المرجع لمعرفة وظيفة كل علم):

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

  > [!NOTE]
  > يتطلب الكتابة في الموقع دون نسخ (zero-copy) أيضاً من المستدعي ربط مخازن المخرجات وقت التشغيل عبر `Method::set_output_data_ptr` — **وهو أمر غير معروض في بيئة التشغيل الخاصة بلغة بايثون** (فإن `executorch.runtime.Method` يعرّض فقط `execute`/`set_inputs`/`get_outputs`). تُهيّئ الأعلام المذكورة أعلاه هذا الإعداد، لكن الكتابة في الموقع هي مسار **مخصص حصرياً للغة C++** (انظر مثال حلقة فك الترميز في ExecuTorch أدناه). من بايثون، اقرأ ذاكرة التخزين المؤقت المحدّثة من مخرجات الطريقة في كل خطوة.

أمثلة على الاستدلال في حلقة فك الترميز

تتخذ الحلقة الشكل نفسه على جميع المنصات الخلفية — فهي *نفس* الرسم البياني طوال العملية. ابدأ من ذاكرة مؤقتة ثابتة الحجم فارغة، ثم مرّر الموجه الكامل مرة واحدة (ذاكرة مؤقتة فارغة → مرحلة التهيئة المسبقة)، ثم رمزًا واحدًا في كل مرة (ذاكرة مؤقتة ممتلئة → مرحلة فك الترميز). تمرّر كل استدعاء `input_ids`، وقناع انتباه سببي `attention_mask`، و`position_ids` (تُزاد بعدد الرموز الجديدة في كل خطوة)، بالإضافة إلى الذاكرة المؤقتة، وتُعاد منها قيم اللوجيت لكل موضع استعلام. يتتبع الكاش الثابت داخليًا موضع كل رمز في الذاكرة المؤقتة، لذا لا حاجة إلى تمرير أي شيء إضافي عبر الاستدعاء. يختلف إعداد الذاكرة المؤقتة حسب وقت التشغيل (كائن `StaticCache` لـ Dynamo، وذاكرة أجهزة خام لـ ONNX Runtime، ومصفوفات المستدعي في C++ لـ ExecuTorch)، لذلك يبني كل تبويب إعداداته أدناه. تحدّث تبويبا Dynamo وONNX Runtime الذاكرة المؤقتة في مكانها؛ أما المسار في المكان نفسه في ExecuTorch فهو مكتوب بلغة C++ (فلا يمكن لبيئة تشغيل Python الخاصة به ذلك، كما ذُكر أعلاه).

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:

```python
import copy
import torch
from transformers import StaticCache
from transformers.exporters.exporter_dynamo import register_pytree_node

register_pytree_node(StaticCache)
exported = torch.export.load("decode.pt2")
decode = exported.module()   # runs on the device its inputs / cache live on (CUDA here)

# the artifact carries an initialized StaticCache template — reuse it (reset to empty)
_, example_kwargs = exported.example_inputs
past_key_values = copy.deepcopy(example_kwargs["past_key_values"])
past_key_values.reset()

def causal_mask(positions, cache_len):   # [1, 1, len(positions), cache_len]
    return (torch.arange(cache_len, device="cuda")[None, :] <= positions[:, None])[None, None]

# prefill: the whole prompt in one call
positions = torch.arange(prompt_len, device="cuda")
logits = decode(input_ids=prompt_ids, attention_mask=causal_mask(positions, max_cache_len),
                position_ids=positions[None], past_key_values=past_key_values).logits
next_token = logits[:, -1:].argmax(-1)

# decode: query=1 buffers reused in place
input_ids = torch.empty((1, 1), dtype=torch.long, device="cuda")
position_ids = torch.empty((1, 1), dtype=torch.long, device="cuda")
attention_mask = torch.empty((1, 1, 1, max_cache_len), dtype=torch.bool, device="cuda")
slots = torch.arange(max_cache_len, device="cuda")
for position in range(prompt_len, max_cache_len):
    input_ids.copy_(next_token)
    position_ids.fill_(position)
    attention_mask[0, 0, 0].copy_(slots <= position)
    logits = decode(input_ids=input_ids, attention_mask=attention_mask,
                    position_ids=position_ids, past_key_values=past_key_values).logits
    next_token = logits[:, -1:].argmax(-1)
```

يعمل ONNX Runtime على الرسم البياني كما هو؛ ويتم تحديث ذاكرة التخزين المؤقت في الموقع باستخدام `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`:

```python
import torch
import onnxruntime as ort
from onnxruntime.transformers.io_binding_helper import CudaSession, TypeHelper

def causal_mask(positions, cache_len):
    return (torch.arange(cache_len, device="cuda")[None, :] <= positions[:, None])[None, None]

session = ort.InferenceSession("decode.onnx", providers=["CUDAExecutionProvider"])
cuda = CudaSession(session, torch.device("cuda"))

# fresh device cache buffers built from each cache input's own shape/dtype; share each
# input.<name>/output.<name> pair on one buffer so the update lands in place
cache = {}
for i in session.get_inputs():
    if not i.name.startswith("input."):
        continue
    name = i.name[len("input.") :]
    dims = [max_cache_len if isinstance(d, str) and not d.isdigit() else int(d) for d in i.shape]
    cache[name] = torch.zeros(dims, dtype=TypeHelper.ort_type_to_torch_type(i.type), device="cuda")
    cuda.set_buffer_sharing(f"input.{name}", f"output.{name}")
cache_feed = {f"input.{name}": buf for name, buf in cache.items()}

vocab_size = next(o.shape[-1] for o in session.get_outputs() if o.name.endswith("logits"))

# prefill: the whole prompt in one call
positions = torch.arange(prompt_len, device="cuda")
cuda.allocate_buffers({"logits": (1, prompt_len, vocab_size)})
out = cuda.infer({"input_ids": prompt_ids, "attention_mask": causal_mask(positions, max_cache_len),
                  "position_ids": positions[None], **cache_feed})
next_token = out["logits"][:, -1:].argmax(-1)

# decode: query=1 buffers reused in place
cuda.allocate_buffers({"logits": (1, 1, vocab_size)})
input_ids = torch.empty((1, 1), dtype=torch.long, device="cuda")
position_ids = torch.empty((1, 1), dtype=torch.long, device="cuda")
attention_mask = torch.empty((1, 1, 1, max_cache_len), dtype=torch.bool, device="cuda")
slots = torch.arange(max_cache_len, device="cuda")
for position in range(prompt_len, max_cache_len):
    input_ids.copy_(next_token)
    position_ids.fill_(position)
    attention_mask[0, 0, 0].copy_(slots <= position)
    out = cuda.infer({"input_ids": input_ids, "attention_mask": attention_mask,
                      "position_ids": position_ids, **cache_feed})
    next_token = out["logits"][:, -1:].argmax(-1)
```

يعتمد وقت التشغيل على الجهاز في ExecuTorch على لغة C++، ويعتمد تحديث الكاش في الموقع على `Method::set_output_data_ptr` — **غير معرّض عبر وقت التشغيل بلغة بايثون** (فإن `executorch.runtime.Method` يعرّض فقط `execute`/`set_inputs`/`get_outputs`)، لذا فإن مسار فك الترميز بدون نسخ هو مسار حصري لـ C++. اربط كل **مخرَج** تم تغييره في الكاش مع مخزن **المدخلات** المطابق له في الكاش، وستصل قيم K/V/العداد الجديدة إلى مخازن `StaticCache` الخاصة بالمستدعي دون أي نسخ. وكما هو الحال في التبويبات الأخرى، تأتي الأشكال والأحجام من المخرَج نفسه — هنا من `method_meta` الخاص بالبرنامج (`input_tensor_meta`/`output_tensor_meta` → `TensorInfo::nbytes()`)، إذ لا يوجد وقت تشغيل بلغة بايثون يمكن الاستعلام منه في كود C++:

```cpp
#include <executorch/extension/data_loader/file_data_loader.h>
#include <executorch/extension/tensor/tensor_ptr.h>
#include <executorch/runtime/executor/method.h>
#include <executorch/runtime/executor/program.h>

using namespace executorch::runtime;
using executorch::extension::FileDataLoader;
using executorch::extension::make_tensor_ptr;

// load the exported decode and its `forward` method (Result error-checks elided for brevity)
auto loader = FileDataLoader::from("decode.pte");
auto program = Program::load(&loader.get());

// method-execution memory: a fixed arena for bookkeeping + one buffer per the method's memory plan
std::array<uint8_t, 4 * 1024 * 1024> arena;
MemoryAllocator method_allocator(arena.size(), arena.data());
auto meta = program->method_meta("forward");
std::vector<std::vector<uint8_t>> planned(meta->num_memory_planned_buffers());
std::vector<Span<uint8_t>> planned_spans;
for (size_t i = 0; i < planned.size(); ++i) {
    planned[i].resize(meta->memory_planned_buffer_size(i).get());
    planned_spans.push_back({planned[i].data(), planned[i].size()});
}
HierarchicalAllocator planned_allocator({planned_spans.data(), planned_spans.size()});
MemoryManager memory_manager(&method_allocator, &planned_allocator);
auto decode = std::move(program->load_method("forward", &memory_manager).get());

// shapes and sizes come from method_meta — no external config. Inputs are [ids, mask, position_ids,
// cache×N]; outputs are [returned cache×N, logits, mutated cache×N].
const size_t num_cache_tensors = meta->num_inputs() - 3;
const size_t logits_out_idx = num_cache_tensors;
std::vector<size_t> cache_nbytes(num_cache_tensors), cache_out_idx(num_cache_tensors);
for (size_t i = 0; i < num_cache_tensors; ++i) {
    cache_nbytes[i] = meta->input_tensor_meta(3 + i)->nbytes();
    cache_out_idx[i] = num_cache_tensors + 1 + i;   // mutated cache = the last N outputs
}
const size_t logits_nbytes = meta->output_tensor_meta(logits_out_idx)->nbytes();

// one set of StaticCache buffers (K/V + per-layer counters) is reused across steps, bound in place each call
auto forward = [&](const TensorPtr& input_ids, const TensorPtr& mask, const TensorPtr& position_ids) {
    decode.set_input(EValue(*input_ids), 0);
    decode.set_input(EValue(*mask), 1);
    decode.set_input(EValue(*position_ids), 2);
    for (int i = 0; i < num_cache_tensors; ++i)
        decode.set_input(EValue(*cache_tensor[i]), 3 + i);
    // bind each mutated-cache output onto that same input tensor's data → the write lands in place, zero copies
    for (int i = 0; i < num_cache_tensors; ++i)
        decode.set_output_data_ptr(cache_tensor[i]->mutable_data_ptr(), cache_nbytes[i], cache_out_idx[i]);
    decode.set_output_data_ptr(logits_data, logits_nbytes, logits_out_idx);
    decode.execute();                 // cache updated in place; logits written to logits_data
    return argmax_last(logits_data);  // greedy pick
};

// prefill the whole prompt, then decode one token per step — the cache carries in place across all calls
int64_t next_token = forward(prompt_ids, prompt_mask, prompt_positions);
for (int64_t position = prompt_len; position < max_cache_len; ++position) {
    next_token = forward(make_tensor_ptr({1, 1}, &next_token, ScalarType::Long),
                         causal_mask(position),  // [1, 1, 1, max_cache_len] bool
                         make_tensor_ptr({1, 1}, &position, ScalarType::Long));
}
```

## التكميم

تستقبل كل إعداد تصدير `quantizer` يقوم بتكميم النموذج أثناء التصدير، إما على مخطط `torch.export` FX (سواء PT2E أو NNCF، لكل واجهة خلفية) أو على نموذج ONNX أو OpenVINO المُحوَّل (ONNX Runtime أو NNCF). راجع [تكميم التصديرات](./exporters_quantization) للاطّلاع على مُكمِّمات التكميم، وواجهاتها الخلفية، والمعايرة، وكيفية إضافة مُكمِّم.

## القيود والحلول البديلة

لكلٍّ من `torch.export` و`torch.onnx.export` وExecuTorch بعض العيوب الدقيقة المرتبطة بأنماط PyTorch محددة. تعمل أدوات التصدير على تجاوز هذه العيوب من خلال مجموعة صغيرة من التصحيحات القابلة للعكس وإصلاحات على مستوى FX تُطبَّق في نقاط محددة بوضوح ضمن سير عمل التصدير. لا يظهر أيٌّ من ذلك من واجهة `export` العامة، لكن أكثر الأمور شيوعًا التي ينبغي معرفتها:

- لا يمكن تصدير FlashAttention وFlexAttention على أي من الخوادم الخلفية. يُفضَّل استخدام `sdpa`
يعمل الإعداد و`eager` أيضًا (ببطء أكبر). اضبط أحدهما على النموذج قبل استدعاء `export` إذا كان يستخدم شيئًا آخر.
- يتتبع `grouped_mm` بسلاسة عبر `DynamoExporter` ويُترجم تلقائياً من أجل `OnnxExporter`.
في حالة استخدام `ExecutorchExporter` مع XNNPACK أو MLX، يقوم المُصدِّر بتبديل خبراء MoE إلى `batched_mm` قبل التصدير.

## الخطوات التالية

- أضِف دعم التصدير لهندسة أو واجهة خلفية جديدة باستخدام سجلات التصحيح والإصلاح في
[توسيع مُصدِّرات البيانات](./exporters_extend).
- قم بتكميم تصدير، أو أضف مُكمِّم، في [تكميم التصديرات](./exporters_quantization).

