𝗞𝗩 𝗖𝗮𝗰𝗵𝗲 𝗮𝗻𝗱 𝗣𝗮𝗴𝗲𝗱𝗔𝘁𝘁𝗲𝗻𝘁𝗶𝗼𝗻: 𝗪𝗵𝘆 𝗬𝗼𝘂𝗿 𝗟𝗟𝗠 𝗦𝗲𝗿𝘃𝗲𝗿 𝗦𝗹𝗼𝘄𝘀 𝗗𝗼𝘄𝗻

خادم LLM الخاص بك يعمل ببطء.

لقد قمت بنشر نموذج 70B على أربع وحدات معالجة رسومية من نوع A100. يبدو كل شيء على ما يرام في الساعة 8 صباحاً. وبحلول وقت الغداء، يتضاعف زمن الاستجابة (latency). تتحقق من الذاكرة، فتجد أن معظمها مستهلك بواسطة "tensor buffers". هذه في الواقع حالات مخزنة مؤقتاً (cached states) من محادثات قديمة.

هذه هي مشكلة KV cache. إنها أكبر عنق زجاجة (bottleneck) في تشغيل نماذج LLM في بيئات الإنتاج.

ما هو الـ KV cache؟

يقوم كل نموذج transformer بتوليد الرموز (tokens) واحداً تلو الآخر. لإنشاء رمز جديد، يحتاج النموذج إلى مصفوفات (tensors) الـ Key والـ Value من جميع الرموز السابقة. إعادة حساب هذه المصفوفات في كل مرة أمر بطيء للغاية. بدلاً من ذلك، يقوم المحرك بتخزينها، وهذا التخزين هو الـ KV cache.

مشكلة الذاكرة:

بالنسبة لنموذج Llama 3.1 70B، يحتاج تسلسل واحد مكون من 4096 رمزاً إلى حوالي 1.3 جيجابايت من الذاكرة.

إذا كان لديك 256 مستخدماً في نفس الوقت، فستحتاج إلى 336 جيجابايت من الذاكرة. وهذا أكثر مما يمكن لأربع وحدات A100 استيعابه. ينمو الـ KV cache بسرعة كبيرة لدرجة أنه غالباً ما يستهلك ذاكرة أكثر من أوزان النموذج (model weights) نفسها.

تفشل إدارة الذاكرة التقليدية بسبب:

  • التجزئة الداخلية (Internal fragmentation): تخصص مساحة لـ 4096 رمزاً ولكنك تستخدم 300 فقط، مما يؤدي إلى إهدار 93% من تلك المساحة.
  • عدم وجود مشاركة: مستخدمان لديهما نفس الـ system prompt يقوم كل منهما بتخزين نسخته الخاصة من ذلك الـ prompt.
  • الإخلاء الكلي أو العدم (All-or-nothing eviction): عندما تنفد الذاكرة، يجب عليك نقل التسلسل بأكمله إلى الـ CPU، مما يؤدي إلى توقف الـ GPU.

كيف يعالج PagedAttention هذه المشكلة:

يعمل PagedAttention مثل نظام التشغيل؛ حيث يقوم بتقسيم الـ KV cache إلى كتل صغيرة ثابتة الحجم تسمى صفحات (pages).

وهذا يحل ثلاث مشكلات رئيسية:

  • التخصيص عند الطلب (On-demand allocation): لا يشغل التسلسل صفحات إلا مع نموه، مما يمنع إهدار الذاكرة في سعة غير مستخدمة.
  • دعم البادئة المشتركة (Shared prefix support): يمكن لعدة مستخدمين مشاركة نفس الصفحات الفعلية لـ system prompt مشترك. يستخدم هذا منطق "النسخ عند الكتابة" (copy-on-write) لتوفير كميات هائلة من الذاكرة.
  • الإخلاء دقيق الحبيبات (Fine-grained eviction): عندما تمتلئ الذاكرة، يقوم النظام بنقل صفحات صغيرة إلى الـ CPU بدلاً من نقل تسلسلات ضخمة.

النتيجة:

استخدام PagedAttention (التقنية المستخدمة داخل vLLM) يمكن أن يزيد من معدل الإنتاجية (throughput) بمقدار 2 إلى 4 أضعاف مقارنة بالطرق التقليدية.

متى تستخدمه:

  • التزامن العالي (High concurrency).
  • تسلسلات بأطوال مختلفة.
  • الـ prompts التي تشترك في نفس البداية.

متى تتجنبه:

  • الاستدلال المحلي لمستخدم واحد (Single-user local inference).
  • النماذج الصغيرة جداً.
  • المهام التي يكون فيها كل تسلسل بنفس الطول تماماً.

KV Cache و PagedAttention: ما هي وما أهميتها؟

عند التعامل مع النماذج اللغوية الكبيرة (LLMs)، هناك تحديان رئيسيان يواجهان عملية الاستنتاج (Inference): زمن الاستجابة (Latency) ومعدل الإنتاجية (Throughput). أحد أهم المفاهيم التي تساعد في تحسين هذه الجوانب هو KV Cache، والابتكار الذي جاء لحل مشاكل الذاكرة المرتبطة به هو PagedAttention.

ما هو الـ KV Cache؟

في عملية التوليد (Generation) التي تعتمد على التنبؤ بالرمز (Token) التالي، يحتاج النموذج في كل خطوة إلى النظر إلى جميع الرموز السابقة في السياق. لكي يقوم النموذج بآلية "الانتباه" (Attention mechanism)، فإنه يحتاج إلى حساب مصفوفتين لكل رمز: المفاتيح (Keys) والقيم (Values).

بدون استخدام KV Cache، سيضطر النموذج إلى إعادة حساب هذه القيم لكل الرموز في كل مرة يتم فيها توليد رمز جديد. هذا يعني أنه مع زيادة طول النص، ستزداد العمليات الحسابية بشكل أسي، مما يجعل عملية التوليد بطيئة للغاية وغير فعالة.

KV Cache يحل هذه المشكلة عن طريق تخزين (Caching) مصفوفات المفاتيح والقيم للرموز التي تم معالجتها بالفعل. في الخطوة التالية، بدلاً من إعادة الحساب، يقوم النموذج ببساطة باسترجاع القيم المخزنة وإضافة القيم الخاصة بالرمز الجديد فقط. هذا يحول التعقيد الحسابي من تربيعي $O(n^2)$ إلى خطي $O(n)$ بالنسبة لطول التسلسل.

المشكلة: تجزئة الذاكرة (Memory Fragmentation)

على الرغم من أن KV Cache يحسن السرعة الحسابية، إلا أنه يفرض ضغطاً هائلاً على ذاكرة الفيديو (VRAM). فمع زيادة طول السياق (Context length) وزيادة عدد المستخدمين المتزامنين، ينمو حجم KV Cache بسرعة كبيرة.

تكمن المشكلة الأساسية في كيفية تخصيص الذاكرة لهذا التخزين. في الأنظمة التقليدية، يتم تخصيص مساحة متصلة (Contiguous) في الذاكرة لكل طلب (Request) بناءً على أقصى طول سياق محتمل. يؤدي هذا إلى مشكلتين:

  1. التجزئة الداخلية (Internal Fragmentation): يتم حجز مساحة كبيرة جداً مسبقاً، ولكن إذا لم يستخدم المستخدم كامل طول السياق المسموح به، تظل هذه المساحة محجوزة وغير مستغلة.
  2. التجزئة الخارجية (External Fragmentation): مع وجود طلبات بأطوال مختلفة، تصبح الذاكرة مليئة بـ "ثقوب" أو فجوات صغيرة لا يمكن استخدامها لطلبات جديدة تتطلب مساحات متصلة كبيرة.

هذا الهدر في الذاكرة يحد من "حجم الدفعة" (Batch size) الذي يمكن للنظام معالجته، مما يقلل من معدل الإنتاجية الإجمالي.

الحل: PagedAttention

مستوحى من مفهوم الذاكرة الافتراضية (Virtual Memory) في أنظمة التشغيل، قدمت تقنية PagedAttention (التي تُعد حجر الزاوية في مكتبة vLLM) حلاً جذرياً لهذه المشكلة.

بدلاً من محاولة حجز مساحة متصلة وكبيرة لكل طلب، تقوم PagedAttention بتقسيم KV Cache إلى كتل صغيرة ثابتة الحجم تُسمى صفحات (Pages).

كيف يعمل PagedAttention؟

  1. التقسيم إلى كتل: يتم تقسيم الذاكرة إلى كتل صغيرة (Blocks). لا يشترط أن تكون هذه الكتل متصلة في الذاكرة الفيزيائية.
  2. جدول الصفحات (Page Table): يتم استخدام جدول صفحات لتتبع مكان كل كتلة من كتل الـ KV Cache الخاصة بكل طلب. هذا الجدول يعمل كخريطة تربط بين المنطق المتصل (الذي يراه النموذج) والذاكرة الفيزيائية غير المتصلة.
  3. التخصيص الديناميكي: يتم تخصيص الصفحات فقط عند الحاجة إليها (عند توليد رموز جديدة)، مما يلغي الحاجة إلى حجز مساحات ضخمة مسبقاً.

الفوائد الرئيسية:

  • تقليل الهدر: يتم تقليل التجزئة الداخلية إلى الحد الأدنى (فقط في آخر كتلة مستخدمة).
  • زيادة معدل الإنتاجية (Throughput): بفضل الكفاءة العالية في استخدام الذاكرة، يمكن للنظام استيعاب عدد أكبر بكثير من الطلبات المتزامنة (Batch size أكبر) في نفس كمية الذاكرة المتاحة.
  • مشاركة الذاكرة (Memory Sharing): تسمح هذه التقنية بمشاركة الكتل بين الطلبات المختلفة بسهولة، وهو أمر مفيد جداً في حالات مثل "الهندسة الفورية" (Prompt Engineering) حيث تتشارك طلبات متعددة نفس النص التمهيدي.

باختصار، بينما يوفر KV Cache السرعة الحسابية، يوفر PagedAttention الكفاءة في إدارة الذاكرة، مما يجعلهما معاً المحرك الأساسي لتشغيل النماذج اللغوية الكبيرة بكفاءة عالية في بيئات الإنتاج.


Optional learning community: https://t.me/GyaanSetuAi