انتقل إلى المحتوى الرئيسي

الوحدة 8 — التكميم والخدمة بكلفة أقلّ

نموذج 7 مليار وسيط، بدقّة عائمة 16 بت، يشغل 14 غيغابايت من ذاكرة المعالج الرسومي، دون احتساب kv-cache. هذا يعادل عتاد بطاقة رسومات فاخرة (A100 أو H100)، بمشروع بضعة آلاف يورو. لنشر مساعد الدعم على عتاد أرخص — بطاقة استهلاكية بـ 8 غيغابايت، أو حتّى معالج مركزي وحده — نحتاج إلى تخفيض الحاجة إلى الذاكرة: هذا هو التكميم.

المبدأ

أوزان النموذج تُخزَّن عادةً بدقّة 16 بت (float16 أو bfloat16). التكميم يمثّلها بدقّة أدنى: 8 بت، أو 4، أو أحيانًا 3. التمثيل الأخفض يستهلك ذاكرة أقلّ، ويُقلِّص أيضًا زمن الاستدلال لأنّ نقل بيانات أقلّ من الذاكرة إلى وحدات الحساب.

الفكرة الأساسية: لكلّ مجموعة من الأوزان (كتلة، أو قناة، أو مصفوفة كاملة)، نجد أقصى قيمة MM ونربطها بأكبر قيمة يمكن تمثيلها بالدقّة المستهدَفة. ثمّ نُقرِّب كلّ وزن إلى أقرب نقطة تمثيل ممكنة.

wمكمَّم=round(wΔ)Δw_{\text{مكمَّم}} = \text{round}\bigg(\frac{w}{\Delta}\bigg) \cdot \Delta

حيث Δ=M/127\Delta = M / 127 للتكميم إلى 8 بت مثلًا. النتيجة أوزان بدقّة أقلّ لكن مُدلَّة عليها بمعامل قياس بسيط، يُخزَّن أيضًا.

ثلاثة تنسيقات، ثلاث حالات استخدام

الأدبيات المفتوحة تعرض ثلاث عائلات كبرى.

GPTQ (Generalized Post-Training Quantization) هو الرائد للتكميم إلى 4 بت على معالج رسومي. يعمل بعد التدريب، بدون إعادة تدريب: يمرّ على البيانات التدريبيّة (نحو ألف مثال يكفي)، ويُقلّل تقريب كلّ طبقة بالتناوب مع تصحيح الأخطاء التي يُدخلها التكميم. النتيجة: نموذج مُكمَّم إلى 4 بت بفقدان أداء أقلّ من نقطة على معايير معتادة.

AWQ (Activation-aware Weight Quantization) تحسين آخر لنفس المسار. يلاحظ أنّ بعض الأوزان أهمّ من غيرها في التنشيطات النشطة، فيُخفّض دقّتها بحذر أقلّ. أداء مماثل لـGPTQ في العموم، أحيانًا أفضل بلمسة.

GGUF (تنسيق مكتبة llama.cpp) هدفه مختلف: تشغيل على معالج مركزي (CPU) أو بطاقة استهلاكية عبر تكميم مرن (2، 3، 4، 5، 6، 8 بت) مع بنية ملفّ محمولة. GGUF هو الملجأ حين لا يوجد معالج رسومي أصلًا.

الذاكرة اللازمة

خلاصة عمليّة لنموذج 7 مليار وسيط:

الدقّةالحجمالعتاد الأدنى
float3228 غيغابايتمعالج رسومي متعدّد أو نموذج نصف عائم
float16 / bfloat1614 غيغابايتA100 أو H100 (24 غيغا+)
int8 (GPTQ)7 غيغابايتRTX 3060 12 غيغا
int4 (GPTQ/AWQ)4 غيغابايتRTX 3060 8 غيغا أو معالج M-series
GGUF Q4_K_M4.5 غيغابايتCPU حديث بذاكرة 16 غيغا

أضِف نحو 1 إلى 2 غيغابايت لـ kv-cache حسب طول السياق. وستحتاج إلى هامش تشغيلي 20 بالمائة على الأقلّ. لمشروعنا، نموذج 7 مليار وسيط بتكميم 4 بت يُخدَّم على بطاقة استهلاكية بـ 12 غيغابايت، وهذا يفتح باب النشر داخل الشركة على خادم واحد.

vLLM: الخدمة الفعّالة على معالج رسومي

للخدمة على معالج رسومي، أداة الاختيار اليوم هي vLLM. مزيّتها الأساسية هي الحزم المستمرّة (continuous batching): بدل انتظار اكتمال دفعة من الطلبات ثمّ معالجتها معًا، يُدرِج vLLM طلبات جديدة في أيّ لحظة، ويُدير kv-cache عبر تقنية تُشبه إدارة الذاكرة الافتراضية (PagedAttention).

النتيجة: إنتاجيّة (throughput) أعلى بعشرة أضعاف من مسارات ساذجة، وتأخّر أوّل رمز (time to first token) مستقرّ حتّى تحت حِمل مرتفع.

# تشغيل vLLM كخادم متوافق مع OpenAI API
# vllm serve meta-llama/Llama-3.1-8B-Instruct --quantization awq --gpu-memory-utilization 0.9
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")

reponse = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[
{"role": "system", "content": "أنت مساعد دعم عملاء لشركة بنكية."},
{"role": "user", "content": "كيف أفتح حسابًا جديدًا؟"},
],
temperature=0.6,
max_tokens=200,
)
print(reponse.choices[0].message.content)

الخادم المُشغَّل بـvllm serve يُقلّد واجهة OpenAI. تُبدّل عناوين URL في كود المشروع دون تعديل جوهري، ويصير الانتقال من مِلكيّ إلى داخلي عمليّة تكوين وحدها.

llama.cpp: على المعالج المركزي

للسيناريو الأخير — لا معالج رسومي على الإطلاق، وميزانيّة صفر لعتاد جديد — يوجد llama.cpp. مكتبة C++ محمولة تُشغِّل النماذج بتنسيق GGUF على معالج مركزي (Intel، AMD، Apple Silicon) وأحيانًا على بطاقة استهلاكية عبر مسارات OpenCL أو CUDA مبسَّطة.

الأداء أدنى من vLLM بحوالي 5 إلى 10 أضعاف. لكنّ الاختيار عملي: إذا كان مساعد الدعم يُستعمل لمُطالبات نادرة (بضع مئات في اليوم)، فمعالج مركزي حديث بذاكرة كافية يكفي. لا حاجة لبطاقة رسومية.

# بعد بناء llama.cpp:
./llama-server -m Llama-3.1-8B-Instruct-Q4_K_M.gguf -c 8192 --port 8080

المفاضلة: إنتاجيّة مقابل تأخّر

تخديم اللغة الكبيرة له نقطتا قياس مختلفتان.

التأخّر (latency): زمن الاستجابة الكامل لطلب واحد. يهمّ للاستخدام التفاعليّ.

الإنتاجيّة (throughput): عدد الرموز المُنتَجة في الثانية عبر كلّ الطلبات المتوازية. تهمّ للتشغيل ذي الحمل العالي.

توليفة معالج رسومي واحد قوي (A100) تُعطي إنتاجيّة عالية لكن تأخّرًا لا يختلف كثيرًا عن معالج أضعف. توليفة معالجَين رسوميّين متوسّطَين قد تُعطي إنتاجيّة أعلى لكن تأخّرًا مشابهًا. القرار لمساعد الدعم متوسّط الحمل: أولويّة التأخّر — تجربة المستعمل تنهار بعد ثلاث ثوانٍ. عتاد واحد بذاكرة تكفي، بتكميم 4 بت، بـvLLM بحزم مستمرّة.

قيس قبل أن تُقرّر

لا تختر عتادك بناء على الوسائط النظرية. شغّل نموذجًا مُكمَّمًا على عتادك، أرسل 100 طلبًا تمثيليًا، وقس. عادةً تكون النتائج مفاجئة: نموذج 4 بت على بطاقة استهلاكية 12 غيغا يعطي تجربة تفاعليّة مقبولة لمشروع بضع مئات مستخدم يوميًا، حين ينصح كثيرون بالقفز مباشرة إلى A100.

في الخلاصة

  • التكميم يخفّض ذاكرة النموذج من 14 غيغا (16 بت) إلى 4 غيغا (4 بت) لنموذج 7 مليار وسيط، بفقدان أداء أقلّ من نقطة عادةً.
  • GPTQ وAWQ لمعالجات رسومية، وGGUF لمعالجات مركزية أو بطاقات استهلاكية عبر llama.cpp.
  • vLLM بحزم مستمرّة يزيد الإنتاجيّة عشرة أضعاف؛ llama.cpp يُتيح النشر بلا معالج رسومي على حساب الأداء.
  • المفاضلة الحقيقية بين التأخّر والإنتاجيّة، لا مطلق الأداء؛ لمساعد تفاعليّ الأولوية للتأخّر، وقياس واقعي أفضل من نظريّة عتاديّة.

الوحدة التالية: كيف نقيس نموذجًا لمساعد الدعم قبل نشره — معايير مرجعيّة عامّة، وأحكام بشرية، وجداول تقييم داخليّة.