ضبط num_ctx في Ollama ومنع اقتطاع المطالبات
يقتطع Ollama المطالبات الطويلة بصمت. تعرّف إلى سبب اختلاف القيم الافتراضية، واضبط num_ctx لكل طلب أو للخادم، واحسب RAM اللازمة لذاكرة KV قبل رفعه.
وظيفة num_ctx وسبب اقتطاع المطالبة الطويلة
يمثل سياق Ollama عدد الرموز التي يمكن للنموذج المحمّل الاحتفاظ بها في الذاكرة في الوقت نفسه، ويحدد الخيار num_ctx هذه القيمة. يختار Ollama قيمة افتراضية أقل بكثير من الحد الأقصى الذي يعلنه النموذج، لذلك تُقتطع المطالبة الطويلة قبل أن يقرأها النموذج. ولا يخبرك أي شيء في الاستجابة بأن ذلك حدث.
يُدرج Llama 3.1 8B في مكتبة نماذج Ollama مع نافذة سياق تبلغ 128k. لكن الخادم بالإعدادات الافتراضية لن يمنحك هذه القيمة. تعرض وثائق Ollama نفسها قيمًا افتراضية مختلفة في صفحات مختلفة: تذكر الأسئلة الشائعة 4096 رمزاً، ويذكر مرجع Modelfile أن num_ctx تكون افتراضياً 2048، بينما تقول صفحة طول السياق إن القيمة الافتراضية تُختار وفق VRAM المتاحة (ذاكرة الفيديو): 4k عندما تكون أقل من 24 GiB، و32k من 24 إلى 48 GiB، و256k عندما تتجاوز ذلك. كانت كل قيمة من هذه القيم صحيحة في إصدار معين. والدرس المفيد هنا هو أن تقرأ القيمة من الخادم الذي يعمل لديك بدلاً من الوثوق بأي صفحة، بما في ذلك هذه الصفحة.
يحدث الاقتطاع بصمت لأن النموذج يظل يجيب، وتظل الإجابة تبدو سليمة. لكنه كتبها اعتماداً على نهاية الإدخال. وقد يبدو التلخيص الذي يفوّت النصف الأول من مستند ما نتيجة نموذج ضعيف. لكنه يكون عادةً نتيجة نافذة سياق صغيرة.
تحقق من طول سياق Ollama الذي طبّقه خادمك فعلياً
الفحص الذي يعمل على أي إصدار هو prompt_eval_count، أي عدد رموز المطالبة التي يذكر الخادم أنه عالجها. أرسل مطالبة تتجاوز سعة السياق، وسيتوقف هذا العدد عند الحد الأقصى.
sudo apt update && sudo apt install -y jq
LONG=$(python3 -c "print('the quick brown fox jumps over the lazy dog. ' * 2000)")
jq -n --arg p "$LONG" '{model:"llama3.1:8b", prompt:$p, stream:false, options:{num_ctx:4096}}' |
curl -s http://localhost:11434/api/generate -d @- |
jq '{prompt_eval_count, prompt_eval_duration}'تتكون هذه المطالبة من نحو 18,000 كلمة، أي أكثر بكثير من 4096 رمزاً. ستُظهر prompt_eval_count قيمة تقترب من 4096 بدلاً من الاقتراب من العدد الفعلي للرموز، لأن الخادم أسقط الجزء المتبقي. شغّلها مرة أخرى باستخدام "num_ctx":16384، وسيرتفع العدد. إذا أعاد إصدارك خطأً بدلاً من اقتطاع المطالبة، فهذه هي النتيجة نفسها، لكن بإشارة أوضح.
ollama psيحتوي العمود CONTEXT، في الإصدارات التي تعرضه، على طول السياق الذي يعمل به النموذج المحمّل حالياً. ويعرض العمود PROCESSOR المجاور له موضع النموذج. تُعد 100% CPU قيمة طبيعية على VPS لا يحتوي على GPU. ويعني توزيع مثل 30%/70% CPU/GPU على خادم مزود بـGPU أن الأوزان وذاكرة التخزين المؤقت لم تعد تتسع داخل VRAM، ويكون رفع num_ctx هو السبب المعتاد.
journalctl -u ollama --no-pager | grep -i n_ctx | tail -n 5يعرض مشغّل الاستدلال حجم السياق في سطر يحتوي على n_ctx. تتغير الصياغة الدقيقة بين الإصدارات، لذلك تعامل مع غياب السطر على أنه إعادة تسمية، لا دليلاً قاطعاً على أي شيء.
أربعة مواضع لضبط num_ctx
في الطلب. أرسل "options": {"num_ctx": 16384} إلى /api/generate أو /api/chat. يتغلب هذا الإعداد على جميع الإعدادات الأخرى، ويُطبَّق على ذلك الاستدعاء وحده. إذا اختلفت القيمة عن القيمة التي يعمل بها النموذج المحمّل، يعيد الخادم تحميل النموذج أولاً. يمكنك رؤية ذلك في load_duration في الاستجابة، إذ ترتفع المدة من قيمة قريبة من الصفر إلى عدة ثوانٍ كاملة. يظهر الانتظار نفسه عندما يبقى النموذج خاملاً مدة كافية لإلغاء تحميله. لذلك، بعد الاستقرار على حجم سياق معين، من المفيد إبقاء النموذج مقيماً باستخدام keep_alive.
في الجلسة التفاعلية. داخل ollama run، اكتب /set parameter num_ctx 16384. يستمر هذا الإعداد طوال تلك الجلسة.
في Modelfile. يثبّت هذا القيمة في نموذج مُسمّى، ولذلك يحصل عليها كل عميل دون أي تغيير من جهة العميل.
FROM llama3.1:8b
PARAMETER num_ctx 16384ollama create llama3.1-16k -f ./Modelfile
ollama run llama3.1-16kعلى الخادم. يضبط OLLAMA_CONTEXT_LENGTH القيمة الافتراضية لكل طلب لا يتضمن num_ctx الخاص به. في systemd، أضف drop-in بدلاً من تعديل ملف الوحدة.
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_CONTEXT_LENGTH=16384"sudo systemctl daemon-reload
sudo systemctl restart ollama
ollama psتظهر أهمية أولوية الإعدادات خصوصاً عند تصحيح أخطاء عميل يخص شخصاً آخر. يتغلب الطلب الذي يتضمن num_ctx على القيمة الافتراضية للخادم. لذلك قد تلغي واجهة محادثة أو agent يرسل قيمة صغيرة خاصة به تغيير systemd دون إظهار ذلك بوضوح. عندما توجّه coding agent إلى خادم Ollama لديك، تحقق مما يرسله العميل قبل إلقاء اللوم على الخادم.
لماذا لا يمكنك ببساطة ضبط num_ctx على الحد الأقصى للنموذج
تجعل آلية Attention كل token ينظر إلى جميع الـtokens التي سبقته. تُحفظ المفاتيح والقيم المحسوبة للـtokens السابقة حتى لا يُعاد حسابها مع كل token جديد، ويُسمّى هذا المخزن KV cache (ذاكرة التخزين المؤقت للمفاتيح والقيم). يُخصَّص هذا المخزن لكامل num_ctx عند تحميل النموذج، وليس مع نمو المحادثة، لذلك يستهلك السياق الكبير ذاكرته حتى مع prompt مكوّن من سطر واحد.
يوضّح الدليل التعليمي لتكلفة inference من DigitalOcean الحساب في سطر واحد:
kv_bytes_per_token = 2 * layers * kv_heads * head_dim * bytes_per_valueيحسب الرقم 2 المفاتيح والقيم كلّاً على حدة. استخرج الأرقام الأخرى من نموذجك نفسه.
curl -s http://localhost:11434/api/show -d '{"model":"llama3.1:8b"}' |
jq '.model_info | {ctx: ."llama.context_length", layers: ."llama.block_count", heads: ."llama.attention.head_count", kv_heads: ."llama.attention.head_count_kv", embed: ."llama.embedding_length"}'يذكر Llama 3.1 8B وجود 32 طبقة و8 رؤوس للمفاتيح/القيم. ويُحسب بُعد الرأس بقسمة embed على heads، لذلك تكون النتيجة هنا 4096 / 32 = 128، وتنشر بعض النماذج هذه القيمة مباشرة باسم llama.attention.key_length. تستخدم ذاكرة التخزين المؤقت الافتراضية قيم f16، لذلك تكون bytes_per_value مساوية لـ2، ويكون حاصل 2 32 8 128 2 هو 131,072 بايت. وهذا يعادل 128 KiB من ذاكرة التخزين المؤقت لكل token واحد من السياق. اضرب هذه القيمة في طول السياق، وستصبح التكلفة ملموسة.
The data behind this chart
[
{
"label": "4k",
"kv_cache_gib": 0.5,
"total_ram_gib": 5.1
},
{
"label": "8k",
"kv_cache_gib": 1,
"total_ram_gib": 5.6
},
{
"label": "16k",
"kv_cache_gib": 2,
"total_ram_gib": 6.6
},
{
"label": "32k",
"kv_cache_gib": 4,
"total_ram_gib": 8.6
},
{
"label": "64k",
"kv_cache_gib": 8,
"total_ram_gib": 12.6
},
{
"label": "128k",
"kv_cache_gib": 16,
"total_ram_gib": 20.6
}
]هذه الصفوف 6 ناتجة عن الحساب باستخدام الصيغة السابقة، وليست قياسات. يضيف عمود الإجمالي تنزيل 4.9 GB الذي أدرجته مكتبة Ollama لـ llama3.1:8b في August 2026، أي ما يعادل 4.6 GiB، ولا يشمل مخازن compute ولا عملية الخادم نفسها. تعامل معه باعتباره الحد الأدنى.
المهم هو شكل التكاليف. عند 8k، تستهلك ذاكرة التخزين المؤقت 1 GiB، وهي قيمة ضئيلة مقارنة بأوزان النموذج. وعند 128k، وهو كامل سياق النموذج، تستهلك 16 GiB، أي أكثر من ثلاثة أضعاف حجم الأوزان، ليصل الإجمالي إلى نحو 20.6 GiB. لذلك لا يستطيع VPS بسعة 4 GB تحميل هذا النموذج مع سياق مفيد. ويعمل VPS بسعة 8 GB بشكل مريح عند 8k. أما VPS بسعة 16 GB فيمكنه الوصول إلى 32k مع بقاء مساحة لبقية مكونات الخادم. ترتفع كل عتبة من هذه العتبات مع زيادة حجم الأوزان، لذلك إذا كنت تقارن نموذجاً أكبر بهذا النموذج 8B، فإن الحسابات نفسها التي أُجريت لـوسم Qwen 27B على VPS يعمل بالـCPU فقط توضّح ضآلة الذاكرة المتبقية للسياق بين 8 و64 GB.
ما الذي يحدث عندما لا تتسع ذاكرة KV المؤقتة
في VPS يعمل بالمعالج المركزي فقط، تزداد ذاكرة العملية ببساطة. راقبها أثناء تحميل النموذج وأثناء تنفيذ طلب طويل.
free -m
ps -eo rss,comm --sort=-rss | head -n 5تُعرض RSS، أي حجم مجموعة الذاكرة المقيمة، بالكيلوبايت. إذا بدأت مساحة swap المستخدمة في free -m بالازدياد، فقلّل حجم السياق. عندما توجد ذاكرة KV المؤقتة في swap، يتوقف التوليد لثوانٍ لكل رمز، لأن كل رمز جديد يقرأ ذاكرة التخزين المؤقت بأكملها.
إذا نفدت ذاكرة الجهاز بالكامل، تختار النواة أكبر عملية وتنهيها.
sudo dmesg | grep -i "killed process"يعني السطر الذي يقرأ Out of memory: Killed process 1234 (ollama) أن حجم السياق الذي طلبته لا يتسع في الذاكرة. غالباً ما يرفض Ollama الطلب قبل الوصول إلى هذه المرحلة، ثم يفشل الطلب مع رسالة تذكر مقدار الذاكرة المطلوب مقارنة بالذاكرة الحرة.
يكون الفشل أكثر هدوءاً على جهاز مزود بوحدة GPU. تنتقل بعض الطبقات إلى ذاكرة النظام، وتعرض ollama ps توزيع الحمل بين CPU وGPU، وينخفض معدل المعالجة بشدة. يعتمد مقدار الانخفاض على عتادك، لذلك قِس عدد الرموز في الثانية على جهازك عند كل إعداد للسياق، بدلاً من الاعتماد على رقم من جهاز شخص آخر.
يزداد زمن المعالجة المسبقة أسرع من طول المطالبة
المعالجة المسبقة هي العمل المنفَّذ على مدخلاتك قبل ظهور أول رمز في المخرجات. ينتبه كل رمز في المطالبة إلى كل رمز يسبقه، لذلك يزداد إجمالي العمل مع مربع طول المدخلات. تؤدي مضاعفة طول المطالبة إلى زيادة زمن انتظار الرمز الأول بأكثر من الضعف.
تتضمن الاستجابة القياس، لذلك لا يتعين عليك قبول ذلك دون تحقق.
jq -n --arg p "$LONG" '{model:"llama3.1:8b", prompt:$p, stream:false, options:{num_ctx:16384}}' |
curl -s http://localhost:11434/api/generate -d @- |
jq '{tokens: .prompt_eval_count, prefill_seconds: (.prompt_eval_duration/1000000000)}'شغّل ذلك باستخدام prompt قصير، ثم كرّره باستخدام prompt طويل، واقسم عدد الـtokens على الزمن بالثواني في كل حالة. على VPS يعمل باستخدام CPU فقط، يكون prefill عادةً أبطأ جزء في طلب طويل السياق، ولن تتنبأ قيمة tokens per second المحسوبة من prompt قصير بأدائه في هذا الطلب. عندما يتجاوز prefill مهلة timeout الموضوعة أمامه، يكون ذلك عادةً سبب ظهور تجاوز مهلة السياق بدلاً من الإجابة عند إرسال prompt طويل. لذلك حدّد أي طبقة توقفت عن الانتظار قبل تقليص السياق.
يظهر أثر ذلك أكثر ما يظهر مع التزامن. تحتاج كل مطالبة قيد المعالجة إلى ذاكرة تخزين مؤقت خاصة بها، لذلك تكون الذاكرة الموضحة في المخطط أعلاه مخصصة لكل طلب، لا لكل خادم، وقد يشغل طلب طويل الخادم بينما تنتظر الطلبات القصيرة خلفه. اضبط OLLAMA_NUM_PARALLEL عن قصد، واقرأ عدد المستخدمين المتزامنين الذين يمكن لنموذج LLM مستضاف ذاتياً خدمته قبل زيادة القيمتين معاً.
استعادة الذاكرة بهامش أصغر
bytes_per_value في المعادلة هو إعداد تتحكم فيه. توثّق الأسئلة الشائعة في Ollama OLLAMA_KV_CACHE_TYPE، مع كون f16 القيمة الافتراضية عند 2 بايت، إضافة إلى q8_0 عند 1 بايت وq4_0 بقيمة أقل من ذلك. يؤدي الانتقال إلى q8_0 إلى خفض حجم ذاكرة التخزين المؤقت إلى النصف، لذلك يستهلك الصف 32k مقدار 2 GiB بدلاً من 4 GiB. وتحرير الذاكرة من جهة الأوزان نفسها يتيح مساحة من الميزانية ذاتها، كما يوضّح وسم GLM الذي يناسب VPS فعلياً عملية التكميم تدرّجياً إذا كنت تفضّل إجراء هذه المقايضة. توثّق الأسئلة الشائعة أيضاً OLLAMA_FLASH_ATTENTION=1، وقد تحتاج بعض الإصدارات إلى ذلك قبل تفعيل ذاكرة التخزين المؤقت المكمّمة.
[Service]
Environment="OLLAMA_FLASH_ATTENTION=1"
Environment="OLLAMA_KV_CACHE_TYPE=q8_0"تحقّق بدلاً من الافتراض: أعد تشغيل الخدمة، وحمّل النموذج عند نفس num_ctx المستخدم سابقاً، ثم قارن RSS. يعتمد الدعم على النموذج وعلى الواجهة الخلفية، لذلك إذا لم يغيّر الإعداد شيئاً فهذا يعني أن تركيبتك غير مشمولة. تسرد الوثائق هذه الخيارات من دون ضمان نتيجة من حيث الجودة، لذلك اختبر q4_0 باستخدام مطالباتك الخاصة قبل الاعتماد عليه. إذا كانت هذه الإعدادات هي سبب بحثك، فإن Ollama وllama.cpp يعرضانهما بطريقة مختلفة.
طريقة اختيار num_ctx
- اقرأ الحد الأقصى للسياق وعدد طبقات النموذج وعدد رؤوس key/value من
/api/show. - احسب عدد البايتات لكل رمز باستخدام الصيغة، ثم اضرب الناتج في حجم السياق المطلوب.
- أضف حجم الأوزان، وقارن الإجمالي بذاكرة RAM الحرة، واترك 1 GiB على الأقل لبقية الخادم.
- اضبط القيمة، وحمّل النموذج، ثم تحقّق من القيمة المطبقة باستخدام
ollama psوprompt_eval_count. - شغّل حمل العمل الفعلي مع مراقبة
free -m، وخفّض حجم السياق إلى النصف إذا بدأ swap بالتحرك.
تحتاج معظم المهام إلى سياق أصغر مما يحدده المستخدمون. يلائم تلخيص تقرير طويل سياق بحجم 16k. ونادراً ما تتجاوز واجهة retrieval التي تلصق خمس مقاطع من المستندات 8k. أما وكيل البرمجة الذي يقرأ الملفات كاملة، فهو الحالة التي تحتاج فعلاً إلى 64k أو أكثر. وفي هذه الحالة، يجب أن تحدد مواصفات الخادم وفق حجم السياق، لا العكس. إذا كان الخادم جديداً أيضاً، فابدأ من تثبيت Ollama يعمل على VPS، ثم اضبط السياق بعد تحميل النماذج بنجاح.
FAQ
ما طول السياق الافتراضي في Ollama؟
يعتمد ذلك على الإصدار والعتاد، لذلك تحقّق منه بدلاً من افتراضه. يوثّق دليل الأسئلة الشائعة في Ollama قيمة 4096 من الرموز، بينما يوثّق مرجع Modelfile قيمة num_ctx افتراضية مقدارها 2048، وتوثّق صفحة طول السياق قيمة افتراضية تُحدَّد وفق VRAM المتاحة: 4k عندما تكون أقل من 24 GiB، و32k من 24 إلى 48 GiB، و256k عندما تتجاوز 48 GiB. يصل VPS يعمل على CPU فقط إلى الطرف الأصغر من هذا النطاق. تطبع ollama ps السياق المطبّق في الإصدارات التي تتضمن هذا العمود، وتثبت prompt_eval_count في استجابة API القيمة في جميع الإصدارات.
لماذا يتجاهل Ollama بداية المطالبة الطويلة؟
لأن المطالبة كانت أطول من نافذة السياق، ولذلك اقتطعها الخادم قبل أن تصل إلى النموذج، من دون إرجاع خطأ. أرسل المطالبة نفسها مرة أخرى مع قيمة num_ctx أكبر، وراقب ازدياد prompt_eval_count في الاستجابة. إذا لم تتغير هذه القيمة، فهناك مكوّن بينك وبين الخادم يحدّد num_ctx بنفسه، وهذا شائع في واجهات الدردشة وأطر عمل الوكلاء.
ما مقدار RAM الإضافية التي يحتاج إليها num_ctx الأكبر؟
اضرب طول السياق في تكلفة ذاكرة التخزين المؤقت لكل رمز، وهي 2 * layers * kv_heads * head_dim * bytes_per_value. في Llama 3.1 8B عند f16 تبلغ هذه التكلفة 128 KiB لكل رمز، ولذلك تستهلك 32k من الرموز 4 GiB، بينما تستهلك 128k كاملة 16 GiB، إضافةً إلى ذاكرة الأوزان. يُخصَّص التخزين المؤقت عند تحميل النموذج، لذلك تستهلك قيمة num_ctx الكبيرة تلك الذاكرة حتى عندما تبقى مطالباتك قصيرة.
هل تجعل نافذة السياق الأكبر Ollama أبطأ؟
نعم، بطريقتين. يزداد عمل المعالجة الأولية تربيعياً مع طول المطالبة، لذلك تؤخر المدخلة الطويلة ظهور الرمز الأول أكثر مما يوحي به طولها. كما ينافس التخزين المؤقت الأكبر الذاكرة؛ ففي جهاز مزوّد بـGPU يدفع ذلك بعض الطبقات إلى RAM النظام، وفي جهاز يعمل على CPU يدفع النظام نحو استخدام swap. تستهلك قيمة num_ctx الكبيرة التي لا تملؤها أبداً الذاكرة رغم أنها لا تزيد زمن المعالجة الأولية.
هل يمكنني ضبط num_ctx بشكل دائم لنموذج واحد؟
نعم. أنشئ Modelfile يتضمن FROM llama3.1:8b وPARAMETER num_ctx 16384، ثم شغّل ollama create llama3.1-16k -f ./Modelfile. يحصل كل عميل يطلب llama3.1-16k على هذا السياق من دون إرسال أي خيارات. يظل الطلب الذي يتضمن قيمة num_ctx خاصة به هو الغالب، لذلك يحدد هذا قيمة افتراضية وليس حداً أقصى.