بعد درس 102: كيف تعمل LLMs، ننظر في نافذة السياق (context window) بعمق أكبر — كيف تُقاس، ما الذي يستهلكها، وكيف تختار tokenizer مناسبًا لمهمتك. هذا درس عملي للمطوّر.
ما هي نافذة السياق؟
Context window = الحدّ الأقصى لعدد tokens (مدخل + مخرج) التي يقبلها النموذج في طلب واحد.
context_window = input_tokens + output_tokens (≤ الحدّ الأقصى للنموذج)
أمثلة على نوافذ شائعة (تختلف بين النماذج والإصدارات):
- 8K tokens (نماذج قديمة).
- 32K tokens.
- 128K tokens.
- 200K، 500K، 1M tokens (نماذج حديثة بتقنيات مختلفة).
الأرقام تتغيّر مع كل إصدار نموذج. اعتبر أيّ رقم محدّد في وثيقة قديمة على أنّه غير محدّث — تحقّق من بطاقة النموذج (model card) للإصدار الذي تستخدمه.
Tokens ≠ Words — قاعدة عملية
Token = وحدة نصّية صغيرة ينتجها tokenizer النموذج. قد تكون:
- كلمة كاملة، أو جزء كلمة، أو حرف واحد، أو رمز خاص.
القاعدة الذهبية: لا توجد قاعدة عامة للتحويل بين tokens وكلمات. يختلف حسب:
- اللغة (عربي vs إنجليزي vs لغات أخرى).
- الـ tokenizer (BPE، WordPiece، SentencePiece...).
- النصّ نفسه.
تحقق فعلي من الـ Tokenizer
لا تعتمد على أرقام من ذاكرة أو من مقالات قديمة. استخدم tokenizer الـ checkpoint الفعلية:
from transformers import AutoTokenizer
# استبدل باسم النموذج الذي تستخدمه
tokenizer = AutoTokenizer.from_pretrained("your-model-name")
text = "الذكاء الاصطناعي يغيّر طريقة عملنا"
tokens = tokenizer.tokenize(text)
print(f"tokens: {tokens}")
print(f"count: {len(tokens)}")
print(f"ids: {tokenizer.encode(text)}")
المخرج الفعلي يختلف حسب النموذج. هذا هو السلوك الموثوق الوحيد.
الأنماط المعروفة (للاسترشاد لا للقياس الدقيق)
التصحيحات التالية محفوظة من دقّة الموقع. هذه أنماط معروفة، لكن لا تنشر أرقامًا دقيقة دون اختبار على checkpoint الفعلية.
| النموذج / العائلة | Tokenizer | ملاحظات |
|---|---|---|
| BERT / mBERT | WordPiece | مفردات محدودة نسبيًا |
| T5 / mT5 | SentencePiece (Unigram) | متعدد اللغات |
| GPT-2 | byte-level BPE | مفردات إنجليزية أساسًا |
| LLaMA 1 / 2 | SentencePiece (BPE-style) | ليس Unigram — تحقق من الـ checkpoint |
| LLaMA 3 | يختلف حسب الإصدار | تحقق دائمًا |
| نماذج حديثة | متنوعة | لا تفترض — تحقق |
نقاط حاسمة:
- LLaMA 1 و LLaMA 2 و LLaMA 3 تستخدم tokenizers مختلفة. لا تعمّم.
- LLaMA استخدمت SentencePiece بخوارزمية BPE-style، ليس Unigram (تصحيح مهم من الموجة الخامسة).
- لا تنشر أرقام tokens محددة للعربية بدون اختبار.
ما الذي يستهلك السياق؟
┌─ system prompt (تعليمات النظام) ← يستهلك
├─ instructions (تعليمات المطور/المستخدم) ← يستهلك
├─ conversation history (رسائل سابقة) ← يستهلك
├─ user input (المدخل الجديد) ← يستهلك
├─ tool/function results (نتائج الأدوات) ← يستهلك (عند tool calling)
├─ generated response (المخرج) ← يستهلك
└─────────────────────────────────────────
↓
total tokens ≤ context_window
مهم: كل هذه القطع تتنافس على نفس الحيز. رسالة طويلة في history + tool results كبيرة + system prompt مفصّل = سياق ممتلئ بسرعة.
Truncation — ماذا يحدث عند تجاوز الحد؟
عند تجاوز نافذة السياق، الخيارات الشائعة:
1. Truncation: اقطع من البداية (الأقدم) أو النهاية (الأحدث).
السلوك الافتراضي يختلف بين النماذج والـ SDKs.
2. Error: الـ API يرفض الطلب (مع رمز خطأ محدد).
أنت تتحقّق وتتعامل معه برمجيًا.
3. Sliding window: احتفظ بآخر N tokens فقط.
قد يُكلّف النموذج سياقًا مهمًا ضاع.
4. Summarization: لخّص الرسائل القديمة في ملخص قصير.
مفيد لكن يلخّص فقط، لا يحتفظ بكل التفاصيل.
5. Retrieval (RAG): أحضر مقاطع ذات صلة فقط عند الحاجة.
خارج نطاق هذه الموجة (Wave 7).
Token Budgeting — تخطيط السياق
في التطبيقات الإنتاجية، خطّط لـ token budget:
# pseudocode: توزيع ميزانية السياق
total_window = 128_000 # مثال: نموذج بنافذة 128K
budget = {
"system_prompt": 2_000,
"developer_prompt": 1_000,
"conversation": 20_000, # آخر رسائل
"tool_results": 5_000, # نتائج الأدوات
"user_input": 2_000,
"reserved_for_output": 4_000, # max_tokens للمخرج
}
assert sum(budget.values()) <= total_window, "تجاوز الميزانية!"
مبدأ عام: اترك دائمًا حيزًا للمخرج (max_tokens). لا تستهلك النافذة كاملة في المدخل.
طويل ≠ أفضل بالضرورة
نافذة كبيرة لا تعني ذاكرة كاملة:
- الانتباه يضعف على المسافات الطويلة في بعض النماذج (يُسمّى "lost in the middle").
- التكلفة أعلى — مزوّدون يُسعّرون بالـ tokens.
- الكمون أعلى — معالجة نصّ أكبر تأخذ وقتًا أطول.
- الأداء على مهام دقيقة قد لا يتحسّن مع نموّ النافذة.
القاعدة العملية:
ليست كل مشكلة سياق-طويل. أحيانًا الحلّ:
- RAG (إحضار مقاطع ذات صلة) — Wave 7
- ملخصات مضغوطة
- تقسيم المهمّة إلى طلبات أصغر
- اختيار نموذج بنافذة أصغر وأرخص وأسرع
Context Window vs Persistent Application Memory
هذا تمييز حاسم:
Context window:
- عابر (ephemeral): ينتهي بانتهاء الطلب
- محدود بحجم النافذة
- لا يحفظ بين الـ sessions
Persistent application memory:
- أنت تبنيها في تطبيقك (DB، ملفات، vector store...)
- تستمر بين الـ sessions
- اختيارية — النموذج نفسه لا يفرضها
"النموذج يتذكّر محادثتنا السابقة" في المنتجات التجارية = التطبيق يحفظ المحادثة ويمرّرها للنموذج في كل طلب (غالبًا مع ملخصات أو آليات أخرى). ليس خاصية جوهرية في النموذج.
أخطاء شائعة
- "1 token = 4 characters": قاعدة تقريبية لم تنطبق إلا على tokenizer معيّن، وغير دقيقة. لا تستخدمها كأساس لتقدير التكلفة.
- "كل النماذج تستخدم نفس tokenizer": لا. افحص الـ checkpoint الفعلية.
- "نافذة كبيرة تحلّ مشكلة الذاكرة": لا تحلّها وحدها. تحتاج تطبيق يحفظ السياقات بشكل منظّم (RAG، DB...).
- "استهلاك كامل النافذة مقبول": لا. اترك دائمًا حيزًا للمخرج وللأخطاء.
- "LLaMA تستخدم Unigram": خطأ شائع. LLaMA 1/2 استخدمت BPE-style مع SentencePiece. LLaMA 3 قد تختلف. تحقق من الـ checkpoint.
الخطوات التالية
- Decoding و Sampling — كيف يُختار كل رمز من التوزيع.
- Prompting Fundamentals — كيف تبني تعليمات فعّالة.
ملاحظة النطاق: RAG (استرجاع معزّز) و vector databases تُغطّى في الموجات التالية. هذا الدرس حدّه فهم النافذة والـ tokens.