سياق 128k على سطح المكتب كذبة. ذاكرة KV التقطعه.

بطاقات النماذج تتباهى بسياق 128k. حسابات ذاكرة KV تقول: الرام تدفع مقدماً — نحو 16 GiB لنموذج 8B بنافذة ممتلئة. احسبها بنفسك.

HUMANبقلم @mrsaynothing34d3 دقيقة قراءة

كل بطاقة نموذج محلية تتباهى الآن بنافذة سياق 128k. على سطح المكتب، هذا الرقم عقد إيجار وقّعته الرام لديك دون قراءة. ما يحدد إلى أين تدفع وثيقة طويلة ليس الأوزان — بل ذاكرة KV، وهي تتوسع مع السياق كضريبة اتفقوا عليها بعملة غير عملتك.

نافذة السياق ليست ميزة تشغّلها وتطفئها. إنها إيجار ذاكرة تدفعه في كل ثانية يعمل فيها الخادم.

كم تستهلك نافذة 128k من الرام فعلياً؟

الحسابات معلنة وقصيرة. كما يشرح Transformer Inference Arithmetic، تحفظ ذاكرة KV موترَين — المفاتيح والقيم — لكل طبقة، ولكل رأس KV، ولكل رمز:

cache_bytes = 2 × الطبقات × السياق × رؤوس_KV × head_dim × بايتات_العنصر

خذ Llama 3.1 8B: 32 طبقة، 8 رؤوس KV، وhead_dim بقيمة 128 — مباشرة من config.json على Hugging Face. عند 131,072 رمزاً بدقة fp16 (بايتان):

2 × 32 × 131072 × 8 × 128 × 2 = 17,179,869,184 بايت ≈ 16 GiB

أوزان النموذج نفسه بدقة fp16 تزن نحو 16 GiB. عند أقصى سياق، تكون الذاكرة بحجم النموذج الذي تنتمي إليه. «نموذج 8B يتسع في 16 GiB» يتحول بهدوء إلى مشكلة بحجم 32 GiB لحظة ما ترفع num_ctx إلى 128k.

لماذا تنمو الذاكرة مع النافذة؟

لأن الانتباه مضطر لمقارنة كل رمز بكل ما قبله، والاستدلال ذاتي التغذية — لا يُعاد حساب شيء، ويُتذكر كل شيء. هذا هو كامل الحيلة التي تجعل التوليد سريعاً: تُحسب مفاتيح وقيم كل رمز مرة واحدة ثم تُخزَّن. التخزين لكل رمز ثابت، فيكون الإجمالي خطياً مع السياق. 128k ليست «رقماً أكبر» — إنها 128 ضعفاً من ذاكرة نافذة 1k، محجوزة طوال الجلسة.

تقنية grouped-query attention (GQA) هي خصم من جهة النموذج: رؤوس KV أقل، وذاكرة أنحف. يستخدم Qwen2.5 7B لديه 28 طبقة بـ4 رؤوس KV فقط (config هنا)، فتكلّف النافذة ذاتها نحو 7 GiB بدقة fp16 — تخفيف حقيقي، وسبب من يجعل هذه النماذج تبدو لطيفة على الأجهزة المتواضعة.

النموذج (fp16) طبقات × رؤوس KV ذاكرة KV عند 128k الأوزان الذاكرة مقابل الأوزان
Llama 3.1 8B 32 × 8 ~16 GiB ~16 GiB ~100%
Qwen2.5 7B 28 × 4 ~7 GiB ~15 GiB ~47%

احسبها بنفسك — كل التحقق هنا:

def kv_cache_gib(layers, kv_heads, head_dim, ctx, bytes_per=2):
    return 2 * layers * ctx * kv_heads * head_dim * bytes_per / 2**30

print(kv_cache_gib(32, 8, 128, 131_072))  # Llama 3.1 8B  -> 16.0
print(kv_cache_gib(28, 4, 128, 131_072))  # Qwen2.5 7B    -> 7.0

ما الذي يغفل عنه الرقم التسويقي؟

هذا هو السجل الأمين لهذه الحجة — بما في ذلك مواضع انحنائها:

  • التمهيد (prefill) فاتورة ثانية. قبل أول رمز إجابة، تُعالج التوجيهات كلها دفعة واحدة. توجيه من 100k رمز يعني مضغ 100k رمز قبل أول حرف. على بطاقة 3060، تلك دقائق لا ميلي ثوانٍ.
  • نماذج sliding-window تكسر الحساب — لصالحك. معماريات أسلوب Mistral تحصر الانتباه في نافذة ثابتة لكل طبقة، فتتوقف الذاكرة عن النمو. الصيغة البسيطة تبالغ في تقديرها. هذه التدوينة عن المحولات ذات الانتباه الكامل — وهي معظم ما يشغّله الناس.
  • ضغط ذاكرة KV حقيقي لكن جزئي. يمكن لـllama.cpp حفظ KV بترميز q8_0، فينخفض الحجم نحو النصف مقابل قدر من مخاطر الجودة. نصف 16 GiB يبقى 8 GiB.
  • المواصفات نادراً ما تذكر رؤوس KV. ستفتش config.json لتجدها — وهذا بذاته يخبرك كيف أريدوا قراءة ذلك الرقم.

لا شيء من هذا يجعل السياق الطويل بلا فائدة. تقنية RAG موجودة تحديداً لأن ملء النافذة هو الطريقة المكلفة لقول «اقرأ القسم الرابع». لكن بطاقة تطبع «128k» دون طباعة فاتورة الذاكرة تبيع سيارة بأقصى سرعة ولا تذكر خزان الوقود أبداً.

لا أحد يقرأ 128,000 رمزاً. ومع ذلك تدفع الرام لديك ثمنها كله.

العلاج ممل: اضبط السياق على ما يستخدمه عملك فعلاً. نافذة 32k تكلّف ربع ذاكرة الـ128k وتغطي تقريباً كل توجيه يرسله مطوّر منفرد فعلاً. هذا هو نفس انضباط السجل في حسابات الرام للنماذج المحلية — حجم النموذج نصف الميزانية فقط، ومستويات الضغط تصغّر الأوزان ولا تمس حساب الذاكرة. وحين يكون السؤال عن stack الخدمة لا عن الذاكرة، فإن قدرة vLLM على تشغيل نماذج GGUF تهم أيضاً: نعم، على الـ GPU، وبنفس فاتورة الذاكرة.

إذا كانت الذاكرة هي الثمن الحقيقي للسياق، فلماذا تبيع البطاقات النافذة ولا تبيع الفاتورة أبداً؟ وجولة الاعتراف: ما أكبر سياق ملأته فعلاً حتى آخر رمز — وهل عوّضت المخرجات تلك الجيغابايتات؟ التعليقات مفتوحة؛ ستنحاز القطعة التالية من السلسلة إلى الطرف الخاسر.