KV Cache و PagedAttention: ليه vLLM بيخدم طلبات أكتر بـ 8x على نفس الـ GPU
المستوى المطلوب: محترف. المقال ده بيفترض إنك مشتغلت قبل كده مع Transformer attention، عندك خلفية عن inference servers زي vLLM أو TGI، وفاهم الفرق بين prefill و decode. لو لسه في البداية، ابدأ بمقال "Embeddings للمبتدئ" قبل ده.
لو شغّلت Llama 3 70B على A100 80GB وكنت متوقع تخدم 50 مستخدم متوازي، وفي الواقع السيرفر بيرفض المستخدم رقم 16، الموديل مش هو اللي بياكل الـ 80GB. KV Cache هو اللي بياكلها — وأغلبها بيتهدر في تجزئة الذاكرة (memory fragmentation) قبل ما توصل لحدود الـ hardware أصلاً.
المشكلة باختصار
كل token جديد في الـ generation محتاج يعمل attention على كل الـ tokens اللي قبله. بدل ما الموديل يعيد حساب الـ Key و Value لكل token قديم في كل خطوة، بيخزّنهم مرة واحدة في KV Cache. الميكانيكية دي بتخفّض التعقيد من O(N²) لـ O(N) في الـ decode، لكن بثمن غالي: الذاكرة بتتراكم خطياً مع طول السياق وعدد الطلبات.
الفكرة بمثال بسيط قبل ما ندخل في الرياضيات
تخيّل مكتبة فيها أمين بيرد على أسئلة الزوار. كل ما حد يدخل ويسأل، بدل ما يرجع للأرفف من الأول، بيكتب الإجابات اللي طلعت من قبل في دفتر ملاحظات على المكتب. الدفتر بيوفّر وقت رهيب، لكن مساحة المكتب محدودة. لو 10 زوار في نفس الوقت، كل واحد محتاج صفحة دفتر خاصة بيه، والمكتب بيمتلي بسرعة. KV Cache هو دفتر الملاحظات، والـ GPU memory هو المكتب.
التعريف العلمي الدقيق والحساب
في كل layer من الـ Transformer، بنخزّن tensors الـ Key و Value لكل token. الحجم لكل token:
size_per_token = 2 (K+V) × num_layers × num_kv_heads × head_dim × dtype_bytesلـ Llama 3 70B بـ 80 layer، 8 KV heads (Grouped-Query Attention)، head_dim=128، و precision FP16:
size_per_token = 2 × 80 × 8 × 128 × 2 = 327,680 bytes ≈ 320 KB
سياق 4K → 1.28 GB لكل طلب
سياق 16K → 5.12 GB لكل طلب
سياق 32K → 10.2 GB لكل طلبعلى A100 80GB وبعد ما تحمّل الموديل بـ AWQ quantization (35GB)، الباقي 45GB. ده يكفي 4 طلبات بسياق 32K في عالم مثالي. مع تجزئة contiguous allocation الكلاسيكية، بيقعد على طلبين فقط.
السبب البنيوي للـ fragmentation
قبل PagedAttention، السيرفرز كانت بتحجز buffer مستمر contiguous في الذاكرة لكل طلب بناءً على max_tokens المتوقعة. لو طلب طلب 2048 توكن وانتهى عند 200، الـ 1848 الباقية بتفضل محجوزة فاضية لحد ما الـ batch بيخلص. ده مش bug — ده تصميم.