Transformers documentation
تصدير الكمّ
تصدير الكمّ
يمكن لكل إعداد تصدير أن يستقبل quantizer، يقوم بتكميم النموذج أثناء التصدير. وتوجد أدوات التكميم في transformers.exporters.quantizers، وينفّذ كلٌّ منها في نقطة ثابتة من عملية التصدير: إما على رسم torch.export FX، قبل أي تحويل، أو على النموذج الذي يحوله أحد الخوادم الخلفية.
| مُكمِّم | يكمّم | Dynamo | ONNX | ExecuTorch | OpenVINO |
|---|---|---|---|---|---|
| PT2EQuantizer | مخطط FX، مع تكميم تصدير PyTorch 2 (PT2E) | 🟢 | 🟢 | 🟢 | 🟢 |
| NNCFTorchFXQuantizer | رسم بياني FX، مع NNCF | 🟢 | 🟢 | 🟢 (الأوزان فقط) | 🟢 |
| OnnxRuntimeQuantizer | نموذج ONNX، باستخدام onnxruntime.quantization | 🔴 | 🟢 | 🔴 | 🔴 |
| NNCFOnnxQuantizer | نموذج ONNX، مع NNCF | 🔴 | 🟢 | 🔴 | 🔴 |
| NNCFOpenVINOQuantizer | نموذج OpenVINO، مع NNCF | 🔴 | 🔴 | 🔴 | 🟢 |
تفحص عملية التصدير مُكمِّم الأرقام (quantizer) مقابل واجهة الخلفية (backend) والبرامج الحزمة المثبتة قبل التتبع، لذا فإن أي عدم تطابق يؤدي إلى فشل مبكر. وكما هو الحال مع مُصدِّرات البيانات (exporters)، تُدرج كل وحدة كمّ (quantizer) إصدارات الحزم التي تم اختبارها عليها، وتُصدر تحذيرًا إذا كانت إصداراتك مختلفة.
يُكمّم المثال أدناه مصنّف المشاعر إلى int8 باستخدام ONNX Runtime، ثم يشغّله.
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from transformers.exporters import OnnxConfig, OnnxExporter, OnnxRuntimeQuantizer
model_id = "distilbert/distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(model_id)
texts = [
"A wonderful, heartfelt film.",
"The plot was dull and the acting worse.",
"I would watch it again tomorrow.",
"Two hours I will never get back.",
]
calibration_dataset = [tokenizer(text, return_tensors="pt") for text in texts]
inputs = tokenizer("An absolute delight from start to finish.", return_tensors="pt")
config = OnnxConfig(dynamic=True, quantizer=OnnxRuntimeQuantizer(calibration_dataset=calibration_dataset))
onnx_program = OnnxExporter().export(model, inputs, config)
logits = onnx_program(**inputs)[0]
print(model.config.id2label[int(logits.argmax())]) # POSITIVEPT2E
PT2EQuantizer يغلّف مكمّم PT2E، سواءً من torchao أو من وقت التشغيل المستهدف، وينفذه على رسم FX (prepare_pt2e → المعايرة → convert_pt2e)، لذا يعمل مع كل واجهة خلفية. يحقن كل مكمّم PT2E عمليات التكميم/إلغاء التكميم الخاصة به، لذا اختر ذلك الذي يدعمه وقت التشغيل لديك:
| المكان الذي ستشغّل فيه | مكمّم PT2E | استيراد من |
|---|---|---|
| PyTorch inductor، ONNX Runtime (QDQ)، OpenVINO | X86InductorQuantizer | torchao.quantization.pt2e.quantizer.x86_inductor_quantizer |
| خلفية ExecuTorch XNNPACK | XNNPACKQuantizer | executorch.backends.xnnpack.quantizer.xnnpack_quantizer |
في ExecuTorch، قم بتغليف XNNPACKQuantizer الخاص بـ ExecuTorch نفسه:
from executorch.backends.xnnpack.quantizer.xnnpack_quantizer import (
XNNPACKQuantizer,
get_symmetric_quantization_config,
)
from transformers.exporters import ExecutorchConfig, PT2EQuantizer
xnnpack = XNNPACKQuantizer().set_global(get_symmetric_quantization_config())
config = ExecutorchConfig(backend="xnnpack", quantizer=PT2EQuantizer(xnnpack, calibration_dataset=calibration_dataset))قم بتكميم النماذج التوليدية عبر ~HfExporter.export_for_generation. هنا، يحتوي calibration_dataset على معاملات التوليد (generate kwargs)، مثل inputs، ويقوم كل مكوّن بالتعاير على المدخلات التي تم التقاطها له. يحتوي نموذج النص فقط على مكوّن prefill ومكوّن decode؛ بينما يقسم النموذج متعدد الوسائط مكوّن prefill الخاص به بشكل إضافي إلى مشفّرات الوسائط (مثل image_encoder)، ومشغّله (projector)، ونموذج اللغة الخاص به language_model، ورأسه اللغوي lm_head (انظر كيف يعمل export_for_generation).
from torchao.quantization.pt2e.quantizer.x86_inductor_quantizer import (
X86InductorQuantizer,
get_default_x86_inductor_quantization_config,
)
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.exporters import DynamoConfig, DynamoExporter, PT2EQuantizer
model_id = "meta-llama/Llama-3.2-1B"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)
prompts = ["The capital of France is", "Photosynthesis turns sunlight into", "The first moon landing was in"]
calibration_dataset = [tokenizer(prompt, return_tensors="pt") for prompt in prompts]
inputs = tokenizer("Once upon a time", return_tensors="pt")
x86 = X86InductorQuantizer().set_global(get_default_x86_inductor_quantization_config())
config = DynamoConfig(dynamic=True, quantizer=PT2EQuantizer(x86, calibration_dataset=calibration_dataset))
components = DynamoExporter().export_for_generation(model, inputs, config) # {"prefill": ..., "decode": ...}في OpenVINO، تبقى أوزان PT2E بدقة كاملة خلف أزواج الكميّة/إلغاء الكميّة، حيث يقوم OpenVINO بتحويلها إلى FakeQuantize وضغطها إلى int8 بنفسه.
ONNX Runtime
OnnxRuntimeQuantizer يشغّل quantize_static افتراضياً (تفعيلات وأوزان int8 QDQ، مُعايرة على calibration_dataset)، كما في المثال أعلاه، مع حُجج OnnxRuntimeStaticConfig. ومع OnnxRuntimeDynamicConfig يشغّل quantize_dynamic (أوزان int8، تفعيلات تُكمَّم وقت التشغيل، دون معايرة).
from transformers.exporters import OnnxRuntimeDynamicConfig, OnnxRuntimeStaticConfig
config = OnnxConfig(
quantizer=OnnxRuntimeQuantizer(OnnxRuntimeStaticConfig(per_channel=True), calibration_dataset=calibration_dataset)
)
config = OnnxConfig(quantizer=OnnxRuntimeQuantizer(OnnxRuntimeDynamicConfig()))NNCF
تعمل مُكمِّمات NNCF الثلاثة المذكورة في الجدول أعلاه على nncf.quantize افتراضيًا (تفعيلات وأوزان من نوع int8، مُعايرة على calibration_dataset، مع model_type=nncf.ModelType.TRANSFORMER ما لم تُمرِّر نوعًا آخر)، باستخدام حُجج NNCFQuantizationConfig. ومع NNCFWeightCompressionConfig تعمل على nncf.compress_weights (أوزان فقط من نوع int8 أو int4، مع AWQ وتقدير المقياس لـ int4، بالإضافة إلى تصحيح GPTQ وLoRA على OpenVINO). يخفض ExecuTorch فقط نتيجة الأوزان فقط من NNCFTorchFXQuantizer.
import nncf
from transformers.exporters import (
DynamoConfig,
NNCFOnnxQuantizer,
NNCFOpenVINOQuantizer,
NNCFTorchFXQuantizer,
NNCFWeightCompressionConfig,
OnnxConfig,
OpenVINOConfig,
OpenVINOExporter,
)
int4 = NNCFWeightCompressionConfig(mode=nncf.CompressWeightsMode.INT4_SYM, group_size=128)
# OpenVINO: int8 activations and weights, or weight-only int4
openvino_int8 = OpenVINOConfig(quantizer=NNCFOpenVINOQuantizer(calibration_dataset=calibration_dataset))
openvino_int4 = OpenVINOConfig(quantizer=NNCFOpenVINOQuantizer(int4))
# ONNX: weight-only int4, as ONNX Runtime's `MatMulNBits`
onnx_int4 = OnnxConfig(quantizer=NNCFOnnxQuantizer(int4))
# Dynamo: int8 on the FX graph
dynamo_int8 = DynamoConfig(quantizer=NNCFTorchFXQuantizer(calibration_dataset=calibration_dataset))
ov_model = OpenVINOExporter().export(model, inputs, openvino_int4)تعمل التفعيلات من نوع Int8 على عمليات ضرب المصفوفات (matmuls) من نوع int8، والتي تصل إلى حد التشبع (saturate) على وحدات المعالجة المركزية التي لا تدعم VNNI (معالجات Intel التي تدعم AVX2 فقط، ومعالجات AMD Zen 2 والأقدم)، لذا يُتوقع انخفاض الدقة هناك. أما ضغط الأوزان فقط فيُبقي عمليات ضرب المصفوفات في نظام الفاصلة العائمة.
المعايرة
تُمثّل خاصية calibration_dataset في مُكمِّم (quantizer) قائمة قابلة للتكرار من مدخلات النموذج، مثل قائمة من مخرجات مُجزِّئ (tokenizer) أو DataLoader تُجمَّع دفعاته لتتوافق مع مدخلات النموذج. وهي خاصية عادية، لذا يمكنك تعيينها بعد إنشاء المُكمِّم (quantizer.calibration_dataset = samples). إذا تركتها فارغة، فسيُجري المُكمِّم الذي يحتاج إلى بيانات عملية المعايرة على مدخلات العينة الخاصة بالتصدير نفسه، مع إصدار تحذير، لأن عينة واحدة قد تُشَوِّه النطاقات المرصودة. أما المُكمِّمات التي لا تحتاج إلى بيانات (كمعايرة ONNX Runtime الديناميكية، وضغط الأوزان بصيغة int8) فلا تقرأ هذه الخاصية إطلاقًا.
وصفة مختلفة لكل مكوّن
مرّر قاموس {component: config} إلى ~HfExporter.export_for_generation لتكميم كل مكوّن على حدة. بالنسبة للنموذج أعلاه، استخدم int8 ثابتًا على prefill و int8 ديناميكيًا أخف على decode:
dynamic_x86 = X86InductorQuantizer().set_global(get_default_x86_inductor_quantization_config(is_dynamic=True))
config = {
"prefill": DynamoConfig(dynamic=True, quantizer=PT2EQuantizer(x86)),
"decode": DynamoConfig(dynamic=True, quantizer=PT2EQuantizer(dynamic_x86)),
}
components = DynamoExporter().export_for_generation(model, inputs, config)يجب أن يسمّي القاموس كل مكوّن ينتجه decompose_for_generation() (يضيف نموذج متعدد الوسائط مشفّراته، وقد يفصل language_model وlm_head). يبقى المكوّن الذي لا يحدد مُكمِّم في ملفه الإعدادي بدقة كاملة. في هذا القاموس، يحمل calibration_dataset الخاص بالمُكمِّم معاملات استدعاء المكوّن الأمامي، وليس معاملات generate.
إضافة مُكمِّم
توجد مُكمِّمات في transformers.exporters.quantizers، فئة واحدة لكل سلسلة أدوات وتمثيل. ورِّث ExportQuantizer وأعلن:
quantizes_fx_graph:Trueلرسمtorch.exportFX، قبل أي تحويل (بحيث تتلقى كل واجهة خلفية النتيجة)، أوFalse(الافتراضي) للنموذج الذي تحوّله واجهة الخلفية (onnx_ir.Model،openvino.Model).supported_formats: صيغ التصدير التي يقوم بتكميمها. يرفض التصدير أي صيغة أخرى قبل التتبع.required_packages، وإختياريًاmin_versionsوtested_versions: ما يستورده. تتحقق عملية التصدير من تثبيتها قبل التتبع، وترفع خطأً إذا كانت الإصدار أدنى من الحد الأدنى، وتحذر عندما يختلف الإصدار عن الإصدار المُختبَر.
ثم نفّذ quantize(model, sample_inputs, export_format). حيث يمثل model رسم FX أو نموذج الخلفية، بينما تمثل sample_inputs مدخلات التصدير الخاصة به. قم بالمعايرة على CalibrationSet: إذ يقرأ مجموعة بيانات المعايرة calibration_dataset الخاصة بالمعاير (أو sample_inputs في حال عدم وجودها) ويحوّل كل عينة إلى مدخلات النموذج الخاصة به عبر تغذية من transformers.exporters.quantizers.utils (traced_inputs لرسم FX، وonnx_feed لـ ONNX، وopenvino_feed لـ OpenVINO).
from transformers.exporters import ExportFormat
from transformers.exporters.quantizers import CalibrationSet, ExportQuantizer
from transformers.exporters.quantizers.utils import onnx_feed
class MyOnnxQuantizer(ExportQuantizer):
supported_formats = (ExportFormat.ONNX,)
required_packages = ("my_toolkit",)
tested_versions = {"my_toolkit": "1.0.0"}
def quantize(self, model, sample_inputs, export_format):
import my_toolkit
names = [graph_input.name for graph_input in model.graph.inputs]
calibration = CalibrationSet(self.calibration_dataset, sample_inputs, lambda sample: onnx_feed(names, sample))
return my_toolkit.quantize(model, calibration_feeds=list(calibration))