الوحدة 9 — الكلفة التربيعية والانتباه الفعّال
كلّ ما بنيناه في الوحدات السابقة يعمل بشكل رائع على تسلسلات من بضع مئات من الجتونات. لكنّ عصر النماذج التي تدّعي 32k جتونًا و128k وأكثر يواجه عقبة رياضية بحتة: مصفوفة الانتباه حجمها ، ومعها تكبر الذاكرة والحوسبة بشكل تربيعي في طول التسلسل.
هذه الوحدة تفكّك هذا القيد بالأعداد، ثمّ تعرض الحلول التي جعلت السياقات الطويلة ممكنة: FlashAttention الذي أعاد كتابة الانتباه بوعي للكاش، والأنماط الإفرادية والمنزلقة التي تُضحّي بجزء من الانتباه لتربح الكثير من الذاكرة.
من أين يأتي بالضبط
في كلّ طبقة، من أجل حزمة ورؤوس وبُعد رأس وطول تسلسل :
- مصفوفة الأوزان لها حجم .
- حوسبة الجداء: نحو عملية.
- الذاكرة: بايتات في float32، أو في float16.
خذ حالة عمليّة: حزمة ، رؤوس ، بُعد رأس ، طول ، float16.
- الذاكرة للأوزان: جيغابايت. لطبقة واحدة.
- في نموذج بـ32 طبقة: 206 جيغابايت للأوزان فحسب.
هذا الحساب يُوضّح لماذا لا يمكن تشغيل الانتباه الساذج على 8192 جتونًا بحزمة 4 على GPU من 80 جيغابايت: مصفوفة الأوزان وحدها لا تدخل.
FlashAttention: احسب دون أن تكتب الأوزان
FlashAttention، الذي طرحه Dao وزملاؤه في 2022 ثمّ 2023 (v2)، يقلب الاستراتيجية. الملاحظة الأساسية: الذاكرة لا الحوسبة هي العنق.
في وحدة معالجة رسومية حديثة (A100، H100)، الحساب أسرع بعشرات المرّات من نقل البيانات من الذاكرة الرئيسية (HBM) إلى الذاكرة الفائقة السرعة داخل النواة (SRAM). خوارزميات كتيرة على الانتباه تكتب مصفوفة الضخمة في HBM، ثمّ تعيد قراءتها. FlashAttention يُلغي هذا التخزين تمامًا: يُجزّئ التسلسل إلى كتل صغيرة، ويحسب الانتباه كتلةً كتلةً في SRAM، ولا يخرج إلى HBM إلّا المخرج النهائي.
النتيجة العددية:
- الذاكرة: من إلى ذاكرة إضافية بجانب . مصفوفة الأوزان لا تُخزَّن أبدًا بالكامل.
- الحوسبة: عدد العمليات لم يتغيّر — ما زال — لكنّ زمن الجدار قد انخفض بمعدّل 2 إلى 4 مرّات لأنّ التبادل مع HBM اختفى.
- الدقّة: FlashAttention ليس تقريبًا. يُنتج نفس نتيجة الانتباه القياسي، إلى دقّة float بضع مراتب.
في بايتورش الحديث، FlashAttention مبني داخل torch.nn.functional.scaled_dot_product_attention:
import torch
import torch.nn.functional as F
Q = torch.randn(4, 12, 8192, 64, device="cuda", dtype=torch.float16)
K = torch.randn(4, 12, 8192, 64, device="cuda", dtype=torch.float16)
V = torch.randn(4, 12, 8192, 64, device="cuda", dtype=torch.float16)
with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=False):
out = F.scaled_dot_product_attention(Q, K, V, is_causal=True)
print(out.shape) # torch.Size([4, 12, 8192, 64])
الاختلاف مع الانتباه المكتوب بيدنا هو أنّ SDPA يختار خلف الكواليس النواة المناسبة (Flash أو الإفرادي على الإفرادي أو الرياضي) حسب توفّر العتاد.
لا تعمل FlashAttention على float32 في أغلب التجهيزات، لأنّها تستفيد من نوى Tensor Cores التي تقبل float16 وbfloat16 فقط. على تدريب float32 كامل الدقّة، sdp_kernel يعود تلقائيًّا إلى التنفيذ الرياضي — وهذا يعني أنّك تدفع الذاكرة من جديد. تحقّق دائمًا من dtype قبل أن تعوّل على تسريع FlashAttention.
أنماط الانتباه الإفرادية
الحلّ الآخر يقلب الجانب الرياضي بدل الجانب الحوسبي: إذا كنت لن تحتاج كلّ الأوزان ، فلا تحسبها. تُشكّل أنماط الانتباه الإفرادية عائلة كاملة من التقريبات:
- نافذة منزلقة (sliding window): كلّ جتون ينظر إلى جتونات على يمينه ويساره فقط. الكلفة: بدل . تُستعمل في Longformer وMistral.
- انتباه مُستقبِل (dilated): كلّ جتون ينظر إلى الجتون (باعد أُسّي). سياق طويل نظريًّا بكلفة .
- انتباه شامل + محلّي: عدد قليل من الجتونات «العالمية» ترى كلّ شيء (مثل CLS)، والباقي محلّي فقط. تُستعمل في BigBird وLongformer.
- BigBird: مزيج بين نافذة منزلقة، جتونات عشوائية قليلة، وجتونات عالمية. أثبت نظريًّا أنّه لا يفقد قدرات المصفوفة الكاملة على تقريب أيّ دالّة كثيرة الحدود.
بايتورش يدعم القناع الإفرادي عبر attn_mask في scaled_dot_product_attention:
# نافذة منزلقة بعرض 128 جتونًا
n, window = 1024, 128
mask = torch.zeros(n, n, dtype=torch.bool)
for i in range(n):
lo = max(0, i - window)
hi = min(n, i + window + 1)
mask[i, lo:hi] = True
mask = mask.to("cuda")
# مخصّص للتنفيذ الرياضي حاليًّا (Flash يتطلّب أقنعة مبسّطة)
المقايضة الحقيقية للسياقات الطويلة
قد يبدو أنّ الحلّ سهل: نأخذ Mistral أو Longformer، ونحصل على 32k جتون مجّانًا. الواقع أدقّ:
- الأنماط الإفرادية تُقلّل الاعت ماد على المحتوى البعيد. جتون في الموقع 30000 لا يستطيع أن يرى جتونًا في الموقع 100 في نافذة عرضها 4096. لكنّ بعض المهمّات تحتاج هذا الربط (استرجاع من الوثيقة، أسئلة تُشير إلى المقدّمة).
- إبرة في كومة قشّ (needle in a haystack): اختبار وضع معلومة صغيرة داخل نصّ طويل والسؤال عنها. النماذج ذات الانتباه الكامل + FlashAttention تنجح جيّدًا، والنماذج ذات الانتباه المنزلق تفشل حسب موضع الإبرة.
- الاستدلال الفعّال أهمّ من التدريب الفعّال: نموذج مُدرَّب على 32k قد يعمل نظريًّا على 128k في الاستدلال، لكن أداءه يتراجع سريعًا خارج نطاق التدريب. تِقنيّات إطالة السياق (YaRN، LongRoPE) تُعالج جزءًا من هذه المشكلة.
قاعدة اختيار عمليّة تعتمد على الطول:
| الاختيار الأمثل | |
|---|---|
| انتباه كامل + FlashAttention. المشكلة غير موجودة. | |
| إلى | انتباه كامل مع FlashAttention إلزامي، حزمة صغيرة إن لزم. |
| إلى | انتباه كامل ممكن مع تدرّج مسدّس (gradient checkpointing) وflash. |
| ابحث عن نماذج مُدرَّبة على هذا الطول بأنماط إفرادية، أو تقسيم النصّ. |
تِقنيّات مكمّلة للذاكرة
مع الانتباه الفعّال، تبقى ذواكر النموذج الأخرى: الأوزان، وحالات المُحسِّن، والتنشيطات الوسيطة. عد ّة تِقنيّات ضروريّة معه:
- تدرّج مسدّس (gradient checkpointing): لا نحفظ التنشيطات الوسيطة لكلّ الطبقات، بل نُعيد حسابها في الانتشار الخلفي. يُوفّر ذاكرة كبيرة على حساب زمن تدريب زائد بمعدّل 30%.
- ZeRO (DeepSpeed): تُقسِّم الأوزان وحالات المُحسِّن على عدّة GPU. يُصغّر البصمة على كلّ GPU إلى .
- الدقّة المختلطة (bf16, fp16): تُنصّف الذاكرة تلقائيًّا. bfloat16 مُفضَّل على float16 لأنّه لا يعاني من underflow في التدرّجات.
- LoRA وquantization: أدوات الأفياد الفعّال، تحدّث فقط جزءًا صغيرًا من المعاملات.
هذه الأدوات ليست موضوع هذه الدورة، لكنّها الملحقات الطبيعيّة عند العمل على السياق الطويل.
في الخلاصة
- كلفة الانتباه ذاكرةً هي لكلّ طبقة، وهي المُقيّد الأوّل للسياقات الطويلة؛ 8k جتون بحزمة 4 يستهلك عدّة جيغابايت لطبقة واحدة.
- FlashAttention يُلغي تخزين مصفوفة الأوزان في HBM باعتماد حساب كتلي في SRAM؛ يُوفّر ذاكرة ويُسرّع بمعدّل 2 إلى 4، دون فقدان دقّة.
- الأنماط الإفرادية (نافذة منزلقة، مُستقبِل، BigBird) تُخفّض الكلفة إلى أو ، مقابل تضحية بالاعتماد البعيد.
- الاختيار العملي يعتمد على : أقلّ من 8k يكفيه انتباه كامل مع Flash؛ أكثر من ذلك يستلزم بحثًا واعيًا عن معماريّات أو حِيَل ذاكرة إضافيّة.
الوحدة التالية: نجمع كلّ الكتل التي بنيناها ونُدرّب Transformer كاملًا على مهمّة ترجمة التواريخ، ونعرض خرائط الانتباه الحقيقيّة التي يتعلّمها.