Transformers documentation
بنية الدفع المستمر
بنية الدفع المستمر
يعالج التقطيع التقليدي مجموعة ثابتة من الطلبات معًا وينتظر حتى ينتهي أبطأها قبل بدء المجموعة التالية. وهذا يتسبب في خمول وحدة معالجة الرسوميات (GPU) بين الدُفعات.
مع الدفع المستمر، في كل خطوة من خطوات التوليد، يفحص المجدول الطلبات المكتملة ويستبدلها فورًا بالطلبات المتواجدة في قائمة الانتظار. تُستبعد الطلبات القصيرة بمجرد اكتمالها، وتبقى وحدة المعالجة الرسومية مشغولة طوال الوقت. النتيجة هي زيادة كبيرة في معدل الإنتاجية وانخفاض في زمن الاستجابة المتوسط.
دورة حياة الطلب
يمر الطلب عبر أربع حالات من لحظة الإرسال حتى الاكتمال.
WAIT IN QUEUE LOAD PROMPT INTO KV STREAM OUTPUT DONE
┌────────────────┐ ┌─────────────────────┐ ┌─────────────────┐ ┌────────┐
│ PENDING │───▶│ PREFILLING │───▶│ DECODING │───▶│FINISHED│
│ ○ ○ ○ · · · │ │ ████████░░░░░░░░░░ │ │ → → → → · · │ │ ✓ │
└────────────────┘ └─────────────────────┘ └─────────────────┘ └────────┘
others ahead prompt chunks +1 token/step- قيد الانتظار — الطلب في قائمة الانتظار وينتظر الجدولة.
- التعبئة المسبقة — تتم معالجة رموز الاستعلام في تمريرة أمامية.
- فك الترميز — يتم توليد رموز الإخراج واحدًا تلو الآخر.
- اكتمل — اكتمل التوليد والنتيجة متاحة.
يُنقل الطلب من حالة الانتظار إلى حالة التعبئة المسبقة عندما يجد المجدول ميزانية كافية من الرموز ومساحة كافية في ذاكرة التخزين المؤقت لقبول الطلب. إذا كان النص المطروح طويلاً بحيث لا يتسع في خطوة واحدة، يقسمه المجدول على عدة عمليات تمرير أمامية (تعبئة مسبقة مقسمة).
التعبئة المقطّعة
يعتبر معالجة موجه طويل في خطوة واحدة مكلفة لأنها تمنع الطلبات الأخرى من التوليد. يحل التقطيع المسبق (Chunked prefill) هذه المشكلة من خلال تقسيم عمليات التمهيد الكبيرة على عدة خطوات.
Blocking (long prefill blocks the batch) Step: 1 2 3 4 Req A [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] ← one huge prefill Req B ················ wait ················ Req C ················ wait ················ Chunked (same prompt split and interleave with decode) Step: 1 2 3 4 Req A [▓▓ pre] [▓▓ pre] [▓▓ pre] [→ dec] Req B [→ dec] [→ dec] [▓▓ pre] [→ dec] Req C [→ dec] · idle · [→ dec] · idle ·
عندما يتجاوز مُوجِّه طلب جديد ميزانية الرموز المتاحة (المحددة بواسطة max_batch_tokens في ContinuousBatchingConfig)، يعالج المجدول أكبر عدد ممكن من الرموز ويحتفظ بالباقي. في الخطوات اللاحقة، يستمر من حيث توقف، مع تداخل أعمال التمهيد مع خطوات فك التشفير الجارية. وهذا يحافظ على إنتاجية الدفعة ويقلل زمن الوصول إلى الرمز الأول للطلبات الأخرى.
المجدول
يقرر المجدول أي الطلبات تنضم إلى كل تمريرة أمامية بناءً على ميزتين وحد أقصى للطلبات.
- ميزانية الرموز — الحد الأقصى لعدد رموز الاستعلام التي تتم معالجتها في تمريرة أمامية واحدة، ويُحدَّد بواسطة
max_batch_tokensفي ContinuousBatchingConfig. - ميزانية التخزين المؤقت — العدد الإجمالي لصفحات KV التي يمكن قراءتها في مسار واحد، وهو محدد بحجم التخزين المؤقت الكلي.
- حد الطلبات — الحد الأقصى لعدد الطلبات في تمريرة أمامية واحدة، ويُعيَّن بواسطة
max_requests_per_batch. يتناسب حجم متجه logits مع حجم المفردات، لذا يحدد هذا الحد الذروة القصوى لاستهلاك الذاكرة في النماذج ذات المفردات الكبيرة.
FIFO
FIFOScheduler هو المجدول الافتراضي. يملأ الدفعة حسب ترتيب الأولوية. تُعالَج طلبات فك الترميز النشطة أولاً، ثم طلبات التمهيد النشطة، ثم الطلبات الجديدة المنتظرة حسب ترتيب وصولها. وهذا يُعظّم الإنتاجية عبر دفع الطلبات القائمة إلى الأمام قبل إدخال طلبات جديدة.
PrefillFirst
PrefillFirstScheduler يُكمل عمليات التعبئة المسبقة المقسّمة قبل استئناف فك الترميز. وهذا يقلّل من التجزئة في حالات الاستخدام التي تهيمن عليها الموجهات الطويلة.
اضبط المجدول في ContinuousBatchingConfig.
cb_config = ContinuousBatchingConfig(scheduler_type="prefill_first")إدارة الذاكرة
يتم تقسيم ذاكرة التخزين المؤقت KV إلى صفحات بحيث يمكن للطلبات ذات الأطوال المختلفة مشاركة مجموعة ذاكرة ثابتة دون تجزئة. وتوفر مجموعة مشتركة من مقاطع الذاكرة، المُنفَّذة على شكل CachePool، تلك المقاطع إلى كل مُخصِّص نوع انتباه، الذي يقسمها بدوره إلى كتل وصفحات.
Shared pool (first two chunks reserved for padding / never allocated)
chunk N [ ======================================== ]
| |
full attn: [ FULL BLOCK 0 | FULL BLOCK 1 | … ]
[ page L0 | page L0 | … ] (one page per full layer)
sliding: [ ----------- SLIDING BLOCK 0 ----------- ]
[ page L1 | page L2 | page L3 | … ] (one page per sliding layer)
page = page_size tokens of KV for one layer
block = one page per layer of that attention type
chunk = pool unit sized so full and sliding blocks tile the same bytesيدعم الدُفع المستمر نوعين فقط من الانتباه: full_attention (FullAttentionCacheAllocator) وsliding_attention (SlidingAttentionCacheAllocator). تحصل النماذج الهجينة على مُخصِّص واحد من كل نوع. يأتي نوع الانتباه من النموذج (layer_types / sliding_window)، وليس من ContinuousBatchingConfig.
تحافظ طبقات الانزلاق على نافذة ثابتة من الرموز الحديثة فقط من خلال إعادة تدوير مجموعة صغيرة من الصفحات. ولا تدعم جداول كتل النواة أو مشاركة الكتل.
يحتفظ الكاش بقطعتين من البيانات (chunks) قبل قطع البيانات الفعلية، وذلك لتوسيع عمليات القراءة والكتابة التي لا تستخدمها الطلبات قط. ولا تُخصَّص تلك القطعتان للطلبات أبدًا.
حجم ذاكرة التخزين المؤقت
يستنتج المدير عدد الأجزاء التي تتسع عند بدء التشغيل من ذاكرة وحدة معالجة الرسوميات المتاحة. وهو يأخذ في الاعتبار متجهات KV وأقنعة الانتباه والتفعيلات والفهارس الإدارية، ثم يحدد حجم المسبح بحيث يتناسب مع max_memory_percent من الذاكرة المتاحة. عند عدم ضبطه، يتخذ قيمة افتراضية وفقاً لمعلمات العتاد والتدريج المستمر. تحجز MPS مساحة أقل للمخزن المؤقت، حيث تتشارك وحدة المعالجة المركزية ووحدة معالجة الرسوميات الذاكرة، لتجنب التبديل. يعبر num_blocks عن السعة على شكل كتل نموذج كاملة من page_size من الرموز عند ضبطه صراحةً.
يمكنك تثبيت القيم في ContinuousBatchingConfig.
cb_config = ContinuousBatchingConfig(
page_size=256,
num_blocks=4096,
max_batch_tokens=512,
max_memory_percent=0.8,
)القبول
قبل انضمام طلب إلى الدفعة، يتحقق الجدول الزمني من وجود سعة حرة كافية لكل مُخصِّص نوع انتباه. إذا كان أي مُخصِّص سيواجه نقصاً، يُرفض الطلب ولا يتم تخصيص أي موارد.
لتجنب امتلاء ذاكرة التخزين المؤقت حتى يصبح إفراغ البيانات الخيار الوحيد، يفرض مجدول المهام حدًا أدنى للسلامة safety_margin. عندما تنخفض سعة ذاكرة التخزين المؤقت المتاحة دون safety_margin، يتم تأجيل عمليات المعالجة الأولية الجديدة، بينما تستمر عمليات فك الترميز النشطة فقط. يعتمد مجدول FIFO على قيمة افتراضية 0.15 (أي حجز 15% من السعة كاحتياطي)، بينما تعتمد prefill_first على قيمة افتراضية 0.0. اضبط safety_margin في ContinuousBatchingConfig لضبطه، حيث يؤدي تعيين 0 إلى تعطيل هذا الحد.
التخزين المؤقت للبادئة
عندما يتشارك طلبان بادئةً واحدةً للموجّه، يمكنهما مشاركة الكتل التي تحتوي على قيم KV لتلك البادئة. يُحسب لكل كتلة مكتملة تجزئة مضمون. الطلب اللاحق الذي يطابق البادئة يعيد استخدام الكتلة ويتخطى مرحلة التعبئة المسبقة لتلك الرموز. الكتل المشتركة تخضع لعدّ المراجع، ولا تعود إلى حوض الكتل المتاحة إلا بعد انتهاء جميع الطلبات التي تستخدمها.
allow_block_sharing افتراضيًا True. يكون مشاركة البادئة نشطة فقط عندما تدعم جميع مُخصِّصي الذاكرة المشاركة، لذا تستخدمه فقط النماذج التي تعتمد كليًا على الانتباه الكامل. تُعطَّل هذه الميزة في النماذج الانزلاقية البحتة والنماذج الهجينة. اضبط allow_block_sharing=False للأحمال التي تتضمن أوامر قصيرة وتوليدات طويلة، حيث تتجاوز تكلفة التتبع وفوائد التوفير.
الإخلاء
لا يتم مسح الكتل المحرَّرة وتبقى “مُهيَّأة” مع الحفاظ على محتواها وقيمة التجزئة (hash) سليمةً، بحيث يمكن لمطابقة البادئة اللاحقة إعادة استخدامها. عندما تنخفض مخزونات مجموعة الكتل غير المُهيَّأة، يُنزل المدير الكتل المُهيَّأة الأحدث تدريجياً إلى حالة غير مُهيَّأة، مع إسقاط قيم التجزئة الخاصة بها. تُعدّ ذاكرة البادئة المؤقتة طبقةً تُدار على أساس أفضل جهد، يتخلّى عنها مُخصِّص الذاكرة (allocator) لضمان استمرار تقديم الطلبات الجديدة.
إعادة تعيين ناعمة
يُعدّ إعادة الضبط الناعمة مسار الاحتياط لـ إسناد المهام. عندما يعجز المُدير عن نسخ ذاكرة KV الخاصة بطلب إلى حوض التبادل في وحدة المعالجة المركزية، بسبب تعطيل إسناد المهام إلى وحدة المعالجة المركزية أو امتلاء الحوض، فإنه يُعيد ضبط الطلب ناعمًا بدلاً من ذلك. يقوم بإلحاق الرموز المولّدة للطلب بطلبته الأصلية، ويُحرر كتل ذاكرته المؤقتة، ثم يعيده إلى قائمة الانتظار. تغطي قسم إسناد المهام الطلبات التي يتم اختيارها.
عندما يتوفر مساحة في ذاكرة التخزين المؤقت مرة أخرى، تستأنف الطلب من حيث توقفت مع ترميز سجل التوليد في الاستدعاء.
رسوم CUDA
يتضمن كل خطوة توليد عبئًا على المعالج المركزي (CPU)، بدءًا من تجميع الدفعة (batch) وصولًا إلى إرسال نواة GPU وقراءة النتائج. تُزيل رسوم CUDA هذا العبء عن طريق تسجيل تسلسل تنفيذ GPU الكامل مرة واحدة ثم إعادة تشغيله للدفعات التي تتطابق أشكالها (shapes).
بما أن أشكال الدفعة تتغير في كل خطوة، يتعامل نظام الدفع المستمر مع ذلك عن طريق الحشو والتخزين المؤقت.
- تُملأ أطوال الاستعلامات حتى أقرب مضاعف لـ
q_padding_interval_size. - يتم ملء أطوال KV إلى أقرب مضاعف لـ
kv_padding_interval_size. - تُخزَّن الرسوم البيانية المسجَّلة في ذاكرة مؤقتة.
عندما يتطابق الشكل المُكمّل (padded) لدُفعة (batch) مع رسم بياني مخزّن مؤقتًا، يُعاد تشغيل الرسم البياني دون أي إرسال من وحدة المعالجة المركزية (CPU). أما الأشكال الجديدة فتُطلق التقاط رسم بياني جديد.
يتطلب رسوم CUDA تدفق تحكم ثابتًا، وهي غير متوافقة مع أقنعة الانتباه. يتم اكتشافها تلقائيًا افتراضيًا، ويُعطل استخدامها عندما لا تتحقق الشروط.
الدفعة غير المتزامنة
يستخدم الدفع غير المتزامن زوجين من مخازن الإدخال/الإخراج ودفقين من CUDA لتداخل عمل وحدة المعالجة المركزية (CPU) ووحدة المعالجة الرسومية (GPU).
Sequential CPU ── [prep N] ·········idle········· [prep N+1] ·········idle········· GPU ·········idle········· [compute N] ·········idle········· [compute N+1] Async CPU ── [prep N] [prep N+1] [prep N+2] ── GPU ── ········ [compute N] [compute N+1] ──
بينما يقوم المعالج الرسومي (GPU) بحساب الدفعة N، يقوم المعالج المركزي (CPU) بإعداد الدفعة N+1. ومع ذلك، فإن تداخل العمليتين يتطلب تقريباً ضعف ذاكرة الفيديو (VRAM).
يتطلب الدفع غير المتزامن تفعيل رسوم CUDA، لأن إعادة تشغيل الرسم توفر عناوين المصفوفات المستقرة اللازمة لصحة تداخل التدفقات.
إخلاء
يمكن أن تتضخم الطلبات التي تولّد مخرجات طويلة حتى لا يتبقى في ذاكرة التخزين المؤقت KV أي مساحة لتخصيص الكتلة التالية. وبدلاً من التعطّل، يعيد المدير الطلبات النشطة إلى قائمة الانتظار لتحرير الكتل للطلبات المتبقية.
يعتمد الإزاحة (Offloading) على الطلب. يبلّغ المُجدول عن الطلبات المُتعثّرة، وهي الطلبات النشطة التي فشلت في تخصيص الكتل التي تحتاجها، مع بيان عدد الكتل التي يطلبها كلٌّ منها. يقوم المُدير بإزاحة أقل عدد من الطلبات اللازمة لتحرير ما يكفي من الكتل لتغطية ذلك الطلب. يبدأ الإزاحة من الأحدث أولاً، بحيث تحتفظ الطلبات المجدولة بالفعل في الدفعة الحالية بذاكرة التخزين المؤقت، ولا تُزاح أبداً آخر طلب نشط متبقٍ.
تتسبب حالتان في تفعيل عملية النقل.
- يتم جدولة دفعة، لكن بعض الطلبات النشطة توقفت. يقوم المدير بإزاحة عدد كافٍ من الطلبات المتوقفة بحيث يمكن للدفعة التالية تخصيص كتل للباقي.
- المخزن المؤقت ممتلئ ولا يمكن جدولة أي دفعة على الإطلاق. يقوم المدير بإزاحة الطلبات وإعادة محاولة الجدولة في حلقة ضمن الخطوة نفسها، حتى تتسع دفعة أو حتى لا يمكن إزاحة أي طلب. لم يعد ينتظر المرور الأمامي التالي للتعافي.
يتبع طلب الإزاحة أحد المسارين. عندما يكون cpu_offload_space أكبر من 0.0، ينسخ المدير كتل ذاكرة KV الخاصة بالطلب إلى مخزن CPU مثبّت مُخصَّص مسبقًا، مع تجميع كل طلب يتسع في بركة التبديل في عملية نقل واحدة. بعد أن يصبح مساحة ذاكرة GPU متاحة، ينسخ المدير الكتل مرة أخرى إلى GPU ويستأنف الطلب دون إعادة حساب موجهه والرموز المولَّدة. أما الطلبات التي لا تتسع في البركة، أو كل طلب مُزاح عند تعطيل إزاحة CPU، فتعود إلى إعادة تعيين ناعمة.
الخطوات التالية
- يتناول منشور المدونة حول الدفع المستمر تخزين KV في ذاكرة مؤقتة، والتعبئة المقطّعة، والجدولة الديناميكية، مع أرقام معايير الأداء.
- لمزيد من أمثلة الاستخدام، راجع وثيقة Continuous batching.