Quantization للمحترف: شغّل Llama 70B على 24GB VRAM بدل 140GB
المستوى: محترف — بيفترض إنك متعامل مع PyTorch و Hugging Face Transformers قبل كده، وعارف الفرق بين training و inference، ومعاك خلفية أساسية عن floating point.
لو حاولت تشغّل Llama 3.3 70B على RTX 4090 وطلعتلك CUDA out of memory، المشكلة مش الكرت. الموديل بـ FP16 محتاج 140GB VRAM والكرت معاه 24GB بس. Quantization بينزّل الرقم ده لـ 22GB ويخلّي الموديل يشتغل على نفس الجهاز، بفقد جودة أقل من 1% في معظم الـ benchmarks.
المشكلة باختصار
كل parameter في الموديل بياخد مكان في الذاكرة. Llama 70B فيه 70 مليار parameter. لو كل واحد متخزّن بـ FP16 يعني 2 بايت، الإجمالي = 140GB قبل ما تحسب الـ KV cache و الـ activations. ده بيخلّي تشغيله محصور على H100 بـ 80GB (اتنين على الأقل) أو A100 بـ 80GB. سعر الساعة في AWS p5.48xlarge بيتعدى 98$ /ساعة. لو نزّلت كل parameter لـ 4 بت بدل 16، الحجم بقى ربع، والكرت اللي بـ 1500$ بقى يكفي.
المثال البسيط: ليه JPEG بيشتغل أصلاً
قبل ما ندخل في التفاصيل التقنية، خد المثال ده. لو عندك صورة RAW من كاميرا بتاخد 48MB، وضغطتها لـ JPEG بجودة 90% بقت 4MB. الفرق بين الصورتين عين الإنسان مش ملاحظاه فعلياً، لكن الحجم اتقسم على 12. الفكرة في Quantization هي نفسها: الموديل فيه أرقام كتير قريبة من بعضها (الأوزان غالباً بين -1 و +1)، فبدل ما نخزّنها بدقة 16 بت، نخزّنها بدقة 4 بت، ونحتفظ بمعامل scale يرجّعها لقيمتها التقريبية وقت الاستخدام.
التعريف العلمي الدقيق
Quantization هي عملية تحويل قيمة من تمثيل floating-point عالي الدقة (FP32 أو FP16) إلى تمثيل integer منخفض الدقة (INT8 أو INT4)، مع الاحتفاظ بمعامل تحجيم (scale) ونقطة صفر (zero-point) لإعادة بناء القيمة الأصلية بشكل تقريبي. المعادلة الأساسية:
q = round(x / scale) + zero_point
x_dequantized = (q - zero_point) * scale
الفقد (quantization error) = x - x_dequantized، وكل ما قلّت عدد البتات، الفقد بيزيد. الذكاء كله في اختيار scale و zero-point بحيث الفقد يبقى أقل ما يمكن على توزيع الأوزان الفعلي للموديل.
الأنواع الأربعة الشائعة بالأرقام
على Llama 3.3 70B، الأرقام المنشورة من Hugging Face ومن ورقة GPTQ:
- FP16 (16-bit): الـ baseline. حجم 140GB. MMLU score حوالي 81.6%.
- INT8 (8-bit, bitsandbytes): حجم 70GB. MMLU 81.4%. سرعة inference أبطأ من FP16 بحوالي 30% بسبب dequantization.
- INT4 (GPTQ 4-bit): حجم 35GB تقريباً. MMLU 80.7%. تحسّن في latency لـ memory-bound workloads.
- INT4 (AWQ 4-bit): حجم 35GB. MMLU 80.9%. أسرع من GPTQ في الـ throughput على نفس الكرت.
- Q4_K_M (GGUF عبر llama.cpp): حجم 42GB. MMLU 80.5%. بيشتغل على CPU + GPU مختلطين.
الفرق العملي بين GPTQ و AWQ و GGUF
التلاتة بينزّلوا الموديل لـ 4 بت، لكن كل واحد بيختار يحافظ على إيه:
- GPTQ: post-training quantization بيستخدم Hessian من dataset صغير لتحديد الأوزان الحساسة. بياخد ساعتين على H100 لـ 70B model. أفضل خيار لو محتاج جودة عالية على GPU واحد.
- AWQ (Activation-aware Weight Quantization): بيحمي الأوزان اللي بترتبط بـ activations كبيرة. أسرع من GPTQ في inference بحوالي 1.5x لأن الـ kernels مكتوبة CUDA-native في vLLM.
- GGUF (GPT-Generated Unified Format): format من llama.cpp بيدعم split بين CPU RAM و GPU VRAM. مناسب لو كرتك صغير وعندك RAM كتيرة. أبطأ في الـ throughput لكن مرن جداً.
كود تشغيل فعلي
تحميل Llama 3.3 70B بـ INT4 عبر bitsandbytes في 4 سطور:
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.3-70B-Instruct",
quantization_config=bnb_config,
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.3-70B-Instruct")
inputs = tokenizer("اشرحلي الـ Quantization في جملتين:", return_tensors="pt").to("cuda")
out = model.generate(**inputs, max_new_tokens=120)
print(tokenizer.decode(out[0], skip_special_tokens=True))
على RTX 4090 بـ 24GB، الكود ده بيشتغل ويعطي output في حدود 18 token/ثانية. nf4 (NormalFloat 4-bit) من ورقة QLoRA أفضل من INT4 العادي لأنه بيستغل التوزيع الطبيعي للأوزان.
سيناريو إنتاج واقعي
افرض إن عندك خدمة chatbot بتخدم 50 ألف مستخدم يومياً، متوسط 4 رسائل لكل مستخدم. على H100 بـ FP16 الفاتورة الشهرية بتقترب من 12,000$. لو حوّلت لـ AWQ على A10G (24GB، 1.2$/ساعة)، الفاتورة بتنزل لحوالي 870$ شهرياً مع latency p95 يزيد 80ms بس. الفارق هنا 13x في التكلفة مقابل اختلاف في جودة الردود أقل من 1.5% على MT-Bench.
الـ Trade-offs اللي لازم تعرفها
- بتكسب: ذاكرة أقل بـ 4x، تكلفة hardware أقل بـ 5-10x، throughput أعلى في memory-bound scenarios.
- بتخسر: دقة محسوبة (1-3% على benchmarks)، صعوبة fine-tuning مباشر (تحتاج QLoRA)، توافق محدود مع بعض الـ kernels.
- الافتراض: الموديل أكبر من 7B parameters. على موديلات أصغر من 3B، الفقد في الجودة بيوصل 8-12% وبيبقى ملحوظ في المهام الـ reasoning.
متى لا تستخدم Quantization
أربع حالات Quantization فيها قرار غلط:
- موديلات صغيرة جداً (أقل من 3B): الفقد بيكون كبير نسبياً وتقدر تشغّل FP16 على أي كرت بـ 8GB.
- مهام طبية أو قانونية: 1% فقد في الجودة ممكن يبقى الفرق بين تشخيص صح وغلط. اختبر على dataset domain-specific قبل الإنتاج.
- Fine-tuning كامل: لازم تستخدم QLoRA أو ترجع لـ FP16/BF16. quantized weights مش قابلة للـ gradient updates مباشرة.
- Latency-critical workloads على GPUs مرتفعة: على H100 بـ FP8 native support، الفرق في latency بين FP8 و INT4 بيقل لأن compute هو الـ bottleneck مش الذاكرة.
الخطوة التالية
افتح كرتك دلوقتي واختبر TheBloke/Llama-3.3-70B-Instruct-AWQ من Hugging Face. شغّله على vLLM بـ --quantization awq وقيس الـ throughput بـ vllm bench serve. لو الفرق في جودة الردود مش محسوس على dataset بتاعك، دلوقتي قللت تكلفتك 10x. لو الفرق ملحوظ، ارجع لـ INT8 الأول قبل ما تستسلم وتعيد لـ FP16.
المصادر
- Frantar et al., "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers", arXiv:2210.17323 (2023).
- Lin et al., "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration", MLSys 2024.
- Dettmers et al., "QLoRA: Efficient Finetuning of Quantized LLMs", NeurIPS 2023 — مصدر nf4.
- Hugging Face Transformers documentation — bitsandbytes integration.
- llama.cpp project — GGUF format specification (ggerganov/llama.cpp on GitHub).
- Meta AI, Llama 3.3 model card — قياسات MMLU الرسمية.
- vLLM documentation — quantization backends و throughput benchmarks.