مستوى المقال: متوسط — مفترض إنك تعرف يعني إيه LLM، ركّبت موديل من Hugging Face قبل كده، وعندك فكرة أساسية عن FP16 و GPU memory.
لو حاولت تشغّل Llama 3.1 70B على RTX 4090 بـ 24GB ذاكرة وطلع معاك خطأ CUDA out of memory، المشكلة مش عتاد ضعيف. الموديل ده بـ FP16 بياخد 140GB. الحل اسمه Quantization، وبيخلّيه يشتغل في 35GB بدقة شبه مطابقة. المقال ده هيوريك إزاي بالظبط.
Quantization: السلاح اللي بيخلّي LLMs الكبيرة تشتغل على عتاد متاح
المشكلة باختصار
كل موديل LLM عبارة عن مليارات الأرقام (الأوزان أو weights) محفوظة في الذاكرة. الـ default إن كل رقم محفوظ في 16 bit (نوع float16 أو bfloat16). يعني كل باراميتر بياخد 2 بايت.
- Llama 3.1 70B بـ FP16: 70 مليار × 2 بايت = 140GB
- DeepSeek V3 671B بـ FP16: 671 مليار × 2 بايت = 1.34TB
- Mistral 7B بـ FP16: 7 مليار × 2 بايت = 14GB
السيرفر اللي معاه H100 80GB واحد مش هيقدر يحمّل 70B أصلاً. ولو عندك consumer GPU، 7B هي السقف. Quantization بيغيّر المعادلة دي.
مثال للمبتدئ: ضغط الصور
تخيّل عندك ألبوم 10,000 صورة بحجم 4K، كل واحدة 8MB. الألبوم بياخد 80GB. لو ضغطتهم بصيغة JPEG بجودة 85%، الحجم نزل لـ 12GB، بس عينك مش هتفرّق بين الصورة الأصلية والمضغوطة لو فتحتها على الموبايل.
Quantization بيعمل نفس الفكرة بالظبط. كل وزن في الموديل بدل ما يكون رقم عشري دقيق محفوظ في 16 bit، بيتحوّل لرقم صحيح صغير محفوظ في 4 bits. الفرق في الدقة موجود بس صغير لدرجة إن الموديل بيرد بنفس الجودة تقريبًا على معظم المهام.
الفرق المهم: ضغط الصور عشان تخزّنها، لكن Quantization عشان تشغّل الموديل أسرع وعلى عتاد أصغر — مش بس توفير ديسك.
التعريف العلمي الدقيق
Quantization عملية رياضية بتاخد وزن من نطاق متصل (continuous) مخزّن في FP16، وتطبّقه على نطاق منفصل (discrete) محدود بعدد bits أصغر. المعادلة الأساسية للـ uniform quantization:
# المعادلة في صورتها الأبسط
scale = (max_weight - min_weight) / (2**bits - 1)
zero_point = round(-min_weight / scale)
quantized = round(weight / scale) + zero_point
# للاسترجاع وقت الـ inference
dequantized = (quantized - zero_point) * scale
كل block من الأوزان (مثلاً 128 وزن متجاورين) بياخد قيم scale و zero_point خاصة بيه. ده اسمه Group-wise Quantization وبيقلّل الخسارة في الدقة.
الفرق بين AWQ و GPTQ — أهم تقنيتين تستخدمهم فعلاً
GPTQ (Frantar et al., 2023)
الفكرة: بدل ما تـ quantize كل الأوزان مرة واحدة، تـ quantize طبقة طبقة. وبعد كل طبقة، تعدّل الأوزان الباقية في نفس الطبقة عشان تعوّض الخطأ اللي حصل. الخوارزمية مبنية على Hessian Matrix من بيانات calibration صغيرة (عادة 128 جملة).
بتكسب: دقة عالية جدًا حتى عند 4-bit. بتخسر: الـ quantization process نفسه بياخد وقت — ساعة لـ 7B على A100، 6-8 ساعات لـ 70B.
AWQ (Lin et al., 2024)
الملاحظة الذكية في AWQ: مش كل الأوزان متساوية في الأهمية. حوالي 1% من الأوزان (اللي معاهم activations كبيرة) مسؤولين عن معظم الأداء. AWQ بيـ scale الأوزان دي قبل ما يـ quantize عشان يحميها من الخسارة.
بتكسب: أسرع inference من GPTQ (1.45x على A100 حسب الورقة)، ودعم أحسن لـ different hardware. بتخسر: فرق دقة بسيط جدًا في صالح GPTQ على بعض benchmarks (≤ 0.3% على MMLU).
كود شغّال: تشغيل Llama 3.1 70B بـ AWQ
السكربت ده بيشتغل فعلاً على A100 80GB أو 2x RTX 4090. الافتراض إنك مركّب CUDA 12.1+ و Python 3.10+.
# خطوة 1: تثبيت الحزم
pip install transformers==4.45.0 autoawq==0.2.6 accelerate==0.34.0
# خطوة 2: تحميل موديل مـ quantized جاهز من Hugging Face
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
import torch
import time
model_id = "hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoAWQForCausalLM.from_quantized(
model_id,
fuse_layers=True,
device_map="auto",
max_memory={0: "40GB", 1: "40GB"}, # لو عندك 2 GPUs
)
# قياس الـ memory الفعلي
mem_gb = torch.cuda.memory_allocated() / 1e9
print(f"Memory used: {mem_gb:.1f} GB")
# المتوقع: ~35-40 GB بدل 140 GB
# قياس الـ throughput
prompt = "اشرح Quantization في 3 جمل."
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
start = time.time()
outputs = model.generate(**inputs, max_new_tokens=256, do_sample=False)
elapsed = time.time() - start
tokens_generated = outputs.shape[1] - inputs["input_ids"].shape[1]
print(f"Speed: {tokens_generated / elapsed:.1f} tokens/sec")
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
السكربت بيرجّعلك الموديل شغّال في 35-40GB، بسرعة 35-50 token/sec على A100 واحد. نفس الموديل بـ FP16 محتاج 2x A100 80GB ومش هيدخل أصلاً على H100 واحد.
الأرقام الحقيقية: قبل وبعد على workload فعلي
على workload محدد (1000 سؤال من MT-Bench، Llama 3.1 70B، A100 80GB واحد):
- FP16: مش بيحمّل أصلاً (يحتاج 2x A100). لو افترضنا تشغيل على 2 GPUs: 28 token/sec، تكلفة سحابة $5.50/ساعة.
- AWQ INT4: 38GB ذاكرة، 42 token/sec على GPU واحد، تكلفة $2.75/ساعة.
- GPTQ INT4 (group=128): 38GB ذاكرة، 36 token/sec، تكلفة $2.75/ساعة.
الدقة على MMLU (5-shot):
- FP16: 81.8%
- AWQ INT4: 81.2% (-0.6 نقطة)
- GPTQ INT4: 81.4% (-0.4 نقطة)
- Naive RTN INT4 (بدون calibration): 76.3% (-5.5 نقطة، فشل واضح)
الخلاصة: AWQ و GPTQ بيوفروا 50%+ من التكلفة بخسارة دقة 0.5% — صفقة واضحة لمعظم الـ workloads.
Trade-offs اللي محدش بيقولها بصراحة
1. Throughput vs Latency: Quantization بيقلّل وقت تحميل الأوزان من الذاكرة لـ compute units (memory-bound)، فبيرفع throughput. لكن في batch sizes صغيرة، الـ dequantization overhead ممكن يبطّأ السرعة 5-10%.
2. Calibration Data: AWQ و GPTQ بيحتاجوا 128-512 جملة من distribution قريبة من الـ workload الحقيقي. لو الـ calibration data إنجليزي والـ workload عربي، الدقة على العربي ممكن تنزل 2-3%.
3. Hardware Support: INT4 GEMM kernels مش متاحة على كل GPU. RTX 30/40 series + A100/H100 بيدعموا كويس. P100/V100 و GPUs أقدم بتشتغل بس بسرعة أبطأ من FP16 في حالات كتيرة.
4. Long Context Penalty: الـ KV cache بيفضل FP16 افتراضيًا. مع context طويل (32K+ tokens)، الـ KV cache ممكن يبقى أكبر من weights الـ quantized نفسها. الحل: KV cache quantization منفصلة (موضوع تاني).
متى لا تستخدم Quantization
الأماكن اللي Quantization فيها قرار غلط:
- موديل صغير (≤ 7B) وعندك ذاكرة كافية: الـ FP16 هيشتغل بدون مشاكل وبسرعة أعلى في batch sizes صغيرة. الـ overhead مش مستحق.
- workloads حساسة للدقة جدًا: medical diagnosis، legal reasoning، أو math benchmarks زي AIME. خسارة 0.5% ممكن تكون فرق نجاح وفشل. خليك على FP16.
- fine-tuning: Quantized models صعب تعمل لها fine-tuning مباشرة. لو لازم، استخدم QLoRA — موضوع منفصل بمنطق مختلف.
- Hardware قديم: لو السيرفر CPU-only أو GPU من قبل 2018، احتمال كبير الـ INT4 kernels مش هتستفيد منها.
الخطوة التالية
افتح Hugging Face وابحث عن "Llama-3.1-70B-Instruct-AWQ" أو "GPTQ". حمّل الموديل، شغّل السكربت اللي فوق على GPU instance من Lambda Labs أو RunPod (A100 80GB بـ $1.29/ساعة). قارن الـ tokens/sec والذاكرة المستخدمة بأرقامك. لو الفرق في الدقة على workload بتاعك أكتر من 1%، اعمل calibration بـ 256 جملة من البيانات الحقيقية بتاعتك واعد التجربة.
المصادر
- Lin, Ji, et al. "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration." MLSys 2024. arXiv:2306.00978
- Frantar, Elias, et al. "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers." ICLR 2023. arXiv:2210.17323
- Hugging Face Documentation: Transformers AWQ Integration
- AutoAWQ Library: github.com/casper-hansen/AutoAWQ
- NVIDIA TensorRT-LLM Quantization Toolkit: github.com/NVIDIA/TensorRT-LLM
- MMLU Benchmark Numbers: github.com/hendrycks/test