MLOps وDevOps لأنظمة الذكاء الاصطناعي

تشغيل نماذج اللغة الكبيرة

18 دقيقة الدرس 8 من 28

تشغيل نماذج اللغة الكبيرة

تشغيل نموذج تعلم آلي تقليدي — شجرة تعزيز التدرج، أو شبكة عصبية صغيرة — يندرج بسهولة ضمن الأنماط التي غطّيناها في الدرسين السادس والسابع: ضع النموذج في حاوية، اكشف نقطة نهاية HTTP، قِس أفقياً على المعالج، وراقب الكمون والانجراف. أما نماذج اللغة الكبيرة (LLMs) فتكسر كل افتراض من هذه الافتراضات. نموذج واحد بسبعين مليار معامل بدقة 16-bit يشغل نحو 140 غيغابايت من ذاكرة GPU، وينتج الرموز واحداً تلو الآخر في حلقة انحدارية. مسار الاستدلال مقيّد بعرض نطاق الذاكرة لا بالحوسبة. يعني التوسع الأفقي توجيه الرموز عبر خوادم GPU متعددة، لا مجرد تشغيل حجرات CPU إضافية. وأنماط الأعطال مختلفة كلياً: أعطال نفاد ذاكرة GPU، واستنفاد KV-cache، وحقن الأوامر في النصوص الحافزة، وأطوال السياق المتفلّتة التي ترفع تكلفتك عشرة أضعاف في صمت.

يتناول هذا الدرس كيف تُشغّل فرق الهندسة الإنتاجية في كبرى شركات التقنية نماذج اللغة الكبيرة فعلياً: حسابات ذاكرة GPU التي يجب أن تستوعبها، والأكوام البرمجية الأكثر انتشاراً، واستراتيجيات التخزين المؤقت التي تُخفّض التكلفة 30–70%، والبنية التحتية لإدارة النصوص الحافزة اللازمة للشحن الموثوق عبر إصدارات النماذج المختلفة.

حسابات ذاكرة GPU

قبل تحديد حجم أي كتلة GPU، نفّذ حساب الميزانية. صيغة الذاكرة الساكنة (معاملات النموذج فقط) بالبايت هي:

model_bytes = num_parameters × bytes_per_parameter

خيارات الدقة الشائعة وتكاليفها: FP32 = 4 بايت (للتدريب فقط)، BF16/FP16 = 2 بايت (الاستدلال القياسي)، INT8 = 1 بايت (الكمية)، INT4 = 0.5 بايت (كمية قوية مع خسارة في الجودة). نموذج 70B بدقة BF16 = 70 × 10⁹ × 2 = 140 غيغابايت. تمتلك بطاقة H100 SXM5 الواحدة 80 غيغابايت HBM3، لذا فإن الحد الأدنى للنشر هو إعداد متوازٍ بمعالجَين (Tensor-Parallel).

لكن وزن المعاملات ليس سوى جزء من الميزانية. خلال الاستدلال، ينمو KV-cache ديناميكياً. كل رمز في نافذة السياق يحجز ذاكرة لمتجهات Key وValue في كل طبقة انتباه:

kv_bytes_per_token = 2 × num_layers × num_kv_heads × head_dim × bytes_per_element

بالنسبة لـ Llama-3-70B (80 طبقة، 8 رؤوس GQA، head_dim=128، BF16): 2 × 80 × 8 × 128 × 2 = 327,680 بايت ≈ 0.32 ميغابايت لكل رمز لكل تسلسل. بدفعة من 32 تسلسلاً متزامناً بسياق 4k لكل منها: 32 × 4096 × 0.32 ≈ 42 غيغابايت — فوق وزن النموذج البالغ 140 غيغابايت. هذا هو السبب في كون الاستدلال مقيداً بذاكرة GPU لا بالحوسبة: تضبط معاملات التشغيل لإيداع التسلسلات في الحيّز المتبقي بعد تحميل النموذج.

قاعدة الإبهام للتحجيم: احجز ~20% من إجمالي ذاكرة GPU لوزن النموذج، و~20% للنواة CUDA وعبء الإطار، وخصص ~60% المتبقية لـ KV-cache. اقسمها على السياق الأقصى المتوقع × بايت لكل رمز للحصول على الحد الأقصى للتسلسلات المتزامنة. يُعرّض vLLM المعامل gpu_memory_utilization (الافتراضي 0.90) للتحكم في هذا التقسيم.

أكوام التشغيل الإنتاجية

محركا الاستدلال مفتوحا المصدر الأكثر استخداماً في الإنتاج هما vLLM وTensorRT-LLM. قدّم vLLM مفهوم PagedAttention — مستعيراً مفاهيم نظام التشغيل لإدارة KV-cache في كتل غير متجاورة، مما يلغي التجزئة الداخلية ويتيح التجميع الديناميكي عبر تسلسلات ذات أطوال متغيرة. أما TensorRT-LLM (NVIDIA) فيُجمّع النماذج في نوى CUDA مع دمج النوى وكمية FP8، مستخلصاً آخر 20–40% من إنتاجية الأجهزة على حساب مسار بناء أطول.

لنشر Kubernetes إنتاجي باستخدام vLLM خلف واجهة API متوافقة مع OpenAI، راجع ملف vllm-deployment.yaml في قسم الكود أعلاه. المعاملات الجوهرية التي يجب فهمها: --tensor-parallel-size 2 يشرّح كل مصفوفة وزن عبر GPU-ين باستخدام NCCL all-reduce في كل تمريرة أمامية — يجب أن يكون كلا المعالجَين على نفس NVLink fabric (نفس العقدة). --enable-prefix-caching يُفعّل ذاكرة شجرة Radix التي تخزّن حالات KV المحسوبة لبادئات النصوص الحافزة المتكررة.

# vllm-deployment.yaml — Llama-3-70B على 2x H100 (tensor parallel) apiVersion: apps/v1 kind: Deployment metadata: name: llm-vllm namespace: ml-serving spec: replicas: 1 selector: matchLabels: app: llm-vllm template: metadata: labels: app: llm-vllm spec: containers: - name: vllm image: vllm/vllm-openai:v0.5.4 args: - "--model" - "meta-llama/Meta-Llama-3-70B-Instruct" - "--tensor-parallel-size" - "2" - "--dtype" - "bfloat16" - "--max-model-len" - "8192" - "--gpu-memory-utilization" - "0.88" - "--enable-prefix-caching" - "--port" - "8000" resources: limits: nvidia.com/gpu: 2 memory: 400Gi nodeSelector: cloud.google.com/gke-accelerator: nvidia-h100-80gb

استراتيجيات التخزين المؤقت

تكلفة استدلال نماذج اللغة الكبيرة يهيمن عليها وقت GPU. يُخفّض التخزين المؤقت على مستويات متعددة هذه التكلفة بشكل كبير في أحمال العمل الإنتاجية.

مشاركة بادئة KV-cache (داخل المحرك): يُفعَّل بـ --enable-prefix-caching في vLLM. يخزّن المحرك حالات KV المحسوبة لأي بادئة نص حافز في شجرة Radix. طلب ثانٍ يشارك أول 2,000 رمز يتجاوز مرحلة الـ Prefill لتلك الرموز كلياً — وهي المرحلة الأكثر تكلفة. في شركات تُشغّل خطوط عمل RAG مكثّفة، يُقلّص هذا الأسلوب وقت GPU الفعلي لكل طلب بنسبة 40–70%.

ذاكرة الدلالات المؤقتة (طبقة التطبيق): ذاكرة التطابق التام (Redis) غير مجدية للغة الطبيعية لأن استعلامين نادراً ما يتطابقان بايتاً ببايت. تُضمّن الذاكرة الدلالية كل استعلام وارد، وتبحث في مخزن متجهات (Redis مع وحدة VSS، أو Qdrant، أو Pinecone) عن أقرب جار ضمن عتبة تشابه جيب التمام (عادةً 0.92–0.97)، وتُعيد الاستجابة المخزّنة إذا كان الجار قريباً بما يكفي. مكتبات مثل GPTCache وLangChain Semantic Cache تُطبّق هذا النمط. في حجم Google وMeta، تُحقق الذاكرة الدلالية معدل إصابة 30–50% في منتجات الدردشة الداعمة.

ذاكرة الاستجابة (CDN/Redis): للنصوص الحافزة الحتمية القابلة للتكرار — توليد التقارير، تلخيص الوثائق بقالب ثابت — خزّن الاستجابة الكاملة مفهرسةً بهاش النص الحافز الدقيق + إصدار النموذج + معاملات التأخذ. Temperature 0 مع seed ثابت يجعل هذا آمناً.

قِس قبل أن تُحسّن: جهّز طبقة التشغيل لإصدار مقياس cache_hit_rate لكل مستوى تخزين مؤقت. ذاكرة دلالية تُصيب 5% من الاستعلامات لكنها تضيف 20 مللي ثانية من عبء التضمين على كل إخفاقة هي في نهاية المطاف عائق لا ميزة. اضبط عتبات التشابه بالنظر إلى جودة الإجابة التي يحكم عليها الإنسان، لا بمعدل الإصابة وحده.

بنية تشغيل نماذج اللغة الكبيرة

LLM Serving Architecture with Caching Layers Client App / API LLM Gateway Auth / Rate-limit Prompt routing Semantic Cache Redis VSS / Qdrant cache hit vLLM Scheduler PagedAttention GPU Pod A H100 × 2 (TP=2) KV-cache 42GB GPU Pod B H100 × 2 (TP=2) KV-cache 42GB Prompt Registry Git-versioned fetch prompt Observability Prom + Grafana Tokens / Latency
بنية تشغيل نماذج اللغة الكبيرة: البوابة ← الذاكرة الدلالية ← جدولة vLLM ← حجرات GPU، مع سجل نصوص حافزة مُصدَر بـ Git يُغذّي البوابة.

إدارة النصوص الحافزة والإصدارات

هندسة النصوص الحافزة على النطاق الإنتاجي هي مشكلة هندسة برمجيات، لا مشكلة بحث. النص الحافز هو كود: له إصدار، وحزمة اختبار، وخط نشر، ومسار تراجع. يُطلق على هذا الانضباط اسم إدارة النصوص الحافزة، وكل منصة LLM جادة تُطبّقه.

يتضمن الحد الأدنى من نظام إدارة النصوص الحافزة أربعة مكونات:

  1. قوالب نصوص حافزة مُصدَرة بـ Git — خزّن النصوص الحافزة كملفات في مستودع (مثلاً prompts/support-chat/v3.jinja2). كل commit هو إصدار. استخدم قوالب Jinja2 أو Handlebars مع متغيرات مُكتَّبة لتملأ طبقة التطبيق الفراغات، لا بالدمج النصي المباشر.
  2. خدمة سجل النصوص الحافزة — واجهة API خفيفة تُقدّم إصدارات النصوص الحافزة المسمّاة بالوسوم. حركة الإنتاج تصل tag: stable؛ حركة الـ Canary تصل tag: canary. يُشير كود التطبيق إلى أسماء النصوص الحافزة لا نصوصها.
  3. خط التقييم — قبل ترقية إصدار نص حافز جديد، نفّذ حزمة تقييم آلية: تأكيدات تطابق دقيق للمخرجات الحتمية، حكم بالنموذج على الجودة المفتوحة، اختبارات انحدار الكمون. أفشل الترقية إذا انخفضت درجات الجودة دون العتبة.
  4. التراجع — لأن تغيير النص الحافز ليس سوى تحديث مؤشر وسم، فالتراجع فوري: اقلب stable للإشارة إلى الإصدار السابق دون لمس كود التطبيق أو إعادة نشر الحجرات.
# سجل نصوص حافزة بسيط بـ Python/FastAPI # (فرق الإنتاج تستخدم LiteLLM أو Langfuse لهذا الغرض) # prompts/support-chat/v3.jinja2 # You are a support agent for {{ company_name }}. # Answer in {{ language }}. Tone: {{ tone }}. # Context: {{ context }} # prompt_registry.py import yaml, jinja2 from pathlib import Path from fastapi import FastAPI, HTTPException app = FastAPI() PROMPT_DIR = Path("prompts") def load_tags(): with open(PROMPT_DIR / "tags.yaml") as f: return yaml.safe_load(f) @app.get("/prompt/{name}") def get_prompt(name: str, tag: str = "stable"): tags = load_tags() if name not in tags: raise HTTPException(404, "prompt not found") version = tags[name].get(tag, tags[name]["stable"]) template_path = PROMPT_DIR / name / f"{version}.jinja2" return {"name": name, "version": version, "template": template_path.read_text()}

تُضيف إدارة إصدارات النموذج بُعداً آخر: حين يتغيّر النموذج الأساسي (مثلاً الترقية من Llama-3-70B إلى Llama-3.1-70B)، قد تنحدر أداء النصوص الحافزة الحالية. النمط هو ربط إصدارات النصوص الحافزة بإصدارات النماذج في السجل. لا تُحدّث النموذج بصمت تحت نص حافز لم يُعاد تقييمه.

مقاييس رصد نماذج اللغة الكبيرة الجوهرية

إضافةً إلى مقاييس RED القياسية (المعدل والأخطاء والمدة)، يتطلّب تشغيل نماذج اللغة الكبيرة مستوى ثانياً من المقاييس على مستوى الرموز:

  • الرموز في الثانية (TPS): إنتاجية فك التشفير؛ H100 مع vLLM على Llama-3-70B تُنتج ~400–600 رمز/ثانية عند دفعة 32. الانخفاض عن الخط الأساسي يُشير إلى تخبّط KV-cache أو تقليل السرعة الحراري.
  • وقت أول رمز (TTFT): كمون مرحلة الـ Prefill؛ يهيمن عليه طول النص الحافز. P99 TTFT > 3 ثوانٍ غير مقبول عادةً للاستخدامات التفاعلية.
  • استخدام KV-cache: يُعرضه vLLM عبر نقطة /metrics كمقياس vllm:gpu_cache_usage_perc. استخدام مستمر > 90% يُسبّب طوابير الطلبات؛ قلّل max-model-len أو أضف طاقة GPU.
  • تكلفة الرموز لكل طلب: (prompt_tokens + completion_tokens) × cost_per_token. نبّه حين تتخطى p95 الميزانية — حلقات الوكيل المتفلّتة تظهر هنا أولاً.
هجمات السياق المتفلّت: مستخدم خبيث (أو وكيل في حلقة) يُرسل نصاً حافزاً بـ 128k رمز مع كل طلب يستطيع استنفاد KV-cache وتجويع كل حركة المرور الأخرى. طبّق max_prompt_tokens في طبقة البوابة لا داخل خادم النموذج فحسب. حدّد المعدل بعدد الرموز لا بعدد الطلبات. نبّه حين تتخطى أي جلسة منفردة حصة الرموز (مثلاً 50k رمز في 5 دقائق).

تشغيل نماذج اللغة الكبيرة هو المجال الأعلى تأثيراً في البنية التحتية للذكاء الاصطناعي حالياً. الفرق التي تستثمر في تخطيط ميزانية ذاكرة GPU، والتخزين المؤقت متعدد المستويات، وخطوط النصوص الحافزة المُصدَرة — بدلاً من التعامل مع نماذج اللغة الكبيرة كاستدعاءات API سوداء — هي التي تُقدّم ميزات AI موثوقة وفعّالة من حيث التكلفة على نطاق واسع.