التزامن في Ollama: شرح NUM_PARALLEL وMAX_QUEUE
هل ينتظر الطلب الثاني أم يُرفض؟ تعرّف إلى دور OLLAMA_NUM_PARALLEL وOLLAMA_MAX_QUEUE وخطر HTTP 503، ولماذا تستهلك كل خانة متوازية VRAM إضافية.
ماذا يحدث لطلب Ollama الثاني أثناء توليد الطلب الأول
تحدّد 3 متغيرات بيئية التزامن في Ollama، وبالإعداد الافتراضي يخدم نموذج محمّل واحد طلباً واحداً في كل مرة. لا يُرفض الطلب الثاني، ولا يتلقى إجابة جزئية. بل ينتظر في قائمة انتظار حتى يتوفر موضع، ثم يُنفَّذ بالسرعة المعتادة.
للطلب الوارد 3 نتائج محتملة. يبدأ فوراً في موضع شاغر. أو ينتظر في قائمة الانتظار. أو تكون قائمة الانتظار ممتلئة، فيرفضه الخادم مع HTTP 503. وتحدّد ذلك المتغيرات OLLAMA_NUM_PARALLEL وOLLAMA_MAX_QUEUE وOLLAMA_MAX_LOADED_MODELS.
الإعداد الافتراضي آمن، وهو أيضاً سبب إبلاغ مستخدم ثانٍ عن أن الخادم «تجمّد» رغم عدم وجود أي عطل. تتطلب إضافة المواضع تعديل سطرين فقط. لكن العامل الحاسم هو الذاكرة. يحتاج كل موضع متوازٍ إلى ذاكرة تخزين مؤقت خاصة بالمفاتيح/القيم (KV cache)، وهي كتلة الذاكرة التي يحتفظ فيها النموذج بالرموز التي عالجها مسبقاً. إذا أضفت مواضع من دون إضافة VRAM (ذاكرة الفيديو على GPU)، فسيتحول الرد البطيء إلى فشل في تحميل النموذج.
ما الذي يتحكم فيه كل من OLLAMA_NUM_PARALLEL وOLLAMA_MAX_QUEUE وOLLAMA_MAX_LOADED_MODELS
هذه هي القيم الافتراضية في إصدارات Ollama الحالية حتى أغسطس 2026. تحقّق من قيمتك بدلاً من الاعتماد على الرقم الوارد هنا، باستخدام سطر السجل الموضّح أدناه.
OLLAMA_NUM_PARALLELهو عدد الطلبات التي يعالجها نموذج محمّل في الوقت نفسه. القيمة الافتراضية هي 1، لذلك تُخدَم الطلبات واحداً تلو الآخر.OLLAMA_MAX_LOADED_MODELSهو عدد النماذج المختلفة التي تبقى مقيمة في الذاكرة في الوقت نفسه. القيمة الافتراضية هي 0، ما يعني أن Ollama يختار القيمة تلقائياً: ثلاثة نماذج لكل GPU، وثلاثة نماذج على الجهاز الذي لا يحتوي على GPU.OLLAMA_MAX_QUEUEهو عدد الطلبات التي يمكن أن تبقى قيد الانتظار. القيمة الافتراضية هي 512. ويُرفض الطلب الذي يصل بعد امتلاء قائمة الانتظار مباشرةً.
تساوي أسوأ حالة لاستهلاك الذاكرة حاصل ضرب القيمتين الأوليين. فوجود نموذجين محمّلين بأربع خانات لكل منهما يعني تخصيص ثماني خانات من ذاكرة KV cache، كلها مقيمة في الوقت نفسه، وستحاول Ollama تلبية هذا الاستخدام. على جهاز يحتوي على GPU واحد، يكون من الأفضل عادةً الاحتفاظ بنموذج واحد ومنحه عدة خانات، لأن الحساب يبقى بسيطاً ويمكن إجراؤه ذهنياً.
لماذا تستهلك كل خانة متوازية جزءاً من VRAM
عندما يحمّل Ollama نموذجاً، يشغّل عملية runner مستقلة. هناك وسيطان يمررهما مهمان هنا: -c هو إجمالي السياق الذي يخصّص له runner ذاكرة KV cache، و-np هو عدد التسلسلات المتوازية. يضبط Ollama قيمة -c على طول السياق لكل طلب مضروباً في عدد الخانات. ثم يقسم runner هذا الإجمالي بالتساوي بين الخانات، لذلك يظل كل طلب يحصل على طول السياق الذي طلبته.
هذا هو القيد الكامل، وهو سبب عدم مجانية التشغيل المتوازي. الانتقال من خانة واحدة إلى أربع خانات يتطلب أربعة أضعاف KV cache عند استخدام طول السياق نفسه لكل طلب. لا تتم مشاركة أي مساحة بين الخانات، ولا تُعار حصة الخانة الخاملة لخانة مشغولة، لأن التقسيم يُحدَّد عند بدء runner.
يمكنك قراءة الأرقام الفعلية بدلاً من الأرقام التي قصدت ضبطها:
journalctl -u ollama --no-pager -n 500 | grep "starting llama-server"يحتوي هذا السطر على سطر أوامر runner الكامل، بما في ذلك -c و-np. إذا كانت قيمة -np تساوي 1 بعد ضبط المتغير، فهذا يعني أن الإعداد لا يصل إلى الخادم، ويتناول القسم التالي سبب ذلك.
إذا لم تتسع أوزان النموذج مع ذاكرة KV cache في VRAM، ينقل Ollama بعض الطبقات إلى ذاكرة النظام RAM، وتعمل هذه الطبقات على CPU. تكون طبقات CPU أبطأ بكثير من طبقات GPU، لذلك يؤدي هذا إلى إبطاء كل طلب، بما في ذلك الطلب الوحيد الذي بدأت به. لذلك قد يؤدي رفع التشغيل المتوازي إلى خفض معدل المعالجة بدلاً من رفعه.
ollama psيقرأ العمود PROCESSOR القيمة 100% GPU عندما يتسع كل شيء في VRAM. ويعني التقسيم مثل 35%/65% CPU/GPU أن جزءاً من النموذج يعمل على CPU. يتضمن العمود SIZE ذاكرة KV cache، لذلك يزداد عند رفع عدد الخانات وإعادة تحميل النموذج. ارفع OLLAMA_NUM_PARALLEL، وأعد التشغيل، وأرسل طلباً واحداً، ثم شغّل ollama ps مرة أخرى: هذه هي تكلفة الذاكرة لتغييرك، مقيسة بدلاً من تخمينها.
يتضاعف طول السياق مع عدد الخانات، لذلك يجب اختيارهما معاً. يعني استخدام سياق كبير مع أربع خانات استخدام أربعة سياقات كبيرة. إذا كنت تضبط أيضاً نافذة السياق num_ctx لنموذجك، فغيّر أحد الخيارين في كل مرة، وإلا فلن تعرف أيهما ملأ بطاقة الرسوميات.
كيفية ضبط هذه المتغيرات بحيث تبقى بعد إعادة التشغيل
يعمل Ollama على Linux كخدمة systemd. لا يؤدي تشغيل export OLLAMA_NUM_PARALLEL=4 في الصدفة إلى تغيير أي شيء، لأن systemd يبدأ الخدمة ببيئته الخاصة ولا يرى الصدفة التي تستخدمها. استخدم ملف drop-in.
sudo systemctl edit ollama.serviceأضف ما يلي في المحرر الذي يفتح:
[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_MAX_LOADED_MODELS=1"
Environment="OLLAMA_MAX_QUEUE=32"ثم أعد التحميل وأعد التشغيل:
sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environmentيعرض systemctl show ما سيمرره systemd إلى العملية. إذا كان متغيرك مفقوداً هناك، فهذا يعني أن ملف drop-in لم يُحفَظ أو أن daemon-reload لم يُنفَّذ. تحقّق أيضاً من جهة الخادم نفسه:
journalctl -u ollama --no-pager | grep "server config" | tail -1يسجّل Ollama بيئته بالكامل عند بدء التشغيل في سطر تكون قيمة الرسالة فيه server config. تمثل تلك الخريطة الحقيقة الفعلية. وهي أسرع طريقة لحسم ما إذا بدأ تطبيق المتغير أم لا.
يحتفظ النموذج المحمّل مسبقاً بعدد الفتحات الذي بدأ به، لأن القيمة تُثبَّت في عملية runner عند بدء التشغيل. يؤدي إجراء إعادة التشغيل أعلاه إلى إلغاء تحميل كل شيء، لذلك يعيد الطلب التالي تحميل النموذج بالإعداد الجديد ويتحمل زمن التحميل مرة واحدة. أما مدة بقاء النموذج محمّلاً بعد ذلك فهي إعداد مستقل، وتتناولها فقرة إبقاء نموذج Ollama محمّلاً بين الطلبات.
ما يبدو عليه التنفيذ والمعالجة في قائمة الانتظار والرفض من جهة العميل
أرسل عدة طلبات في الوقت نفسه وقِس زمنها. يشغّل هذا الأمر ثمانية طلبات بث بالتوازي، ويطبع الحالة والتوقيتات لكل طلب:
for i in $(seq 1 8); do
curl -s -o /dev/null \
-w "req$i http=%{http_code} ttfb=%{time_starttransfer}s total=%{time_total}s\n" \
http://127.0.0.1:11434/api/generate \
-d '{"model":"llama3.2:3b","prompt":"Explain what a KV cache is.","stream":true}' &
done
waitttfb هو الزمن اللازم لوصول أول بايت من البث، وهو قريب من زمن وصول أول رمز (TTFT)، لأن أول جزء مُرسَل يتضمن الرمز الأول.
التنفيذ بالتوازي. يعرض كل طلب قيمة ttfb متشابهة، وترتفع قيمة total لجميع الطلبات معاً. تتشارك الخانات قيد التشغيل في GPU، لذلك تكون كل إجابة أبطأ مما لو شُغِّلت منفردة، مع إكمال عدد أكبر من الإجابات في الدقيقة. هذه هي الحالة التي تحصل عليها عند زيادة OLLAMA_NUM_PARALLEL.
المعالجة في قائمة الانتظار. تستجيب الطلبات الأولى بسرعة، بينما تعرض الطلبات اللاحقة قيمة ttfb كبيرة، ثم تبدأ التوليد بالمعدل المعتاد. الانتظار ناتج عن قائمة الانتظار، وليس عن النموذج. يرى المستخدم الذي يراقب نافذة محادثة فترة صمت طويلة، ثم يظهر النص بالسرعة الكاملة. هذا النمط، أي البدء البطيء ثم التوليد السريع، يدل على وجود قائمة انتظار، وليس على تحميل زائد على GPU.
الرفض. يحصل العميل على http=503 فوراً تقريباً، ويكون نص الاستجابة:
{"error":"server busy, please try again. maximum pending requests exceeded"}تعني هذه الرسالة أن قائمة الانتظار كانت ممتلئة عند وصول الطلب. ولا تخبرك بأي شيء عن VRAM أو عن النموذج.
هناك حد مهم يجب توضيحه: لا ينشر Ollama عمق قائمة الانتظار. يعرض ollama ps ونقطة النهاية /api/ps النماذج المحمّلة، وليس الطلبات المنتظرة. لذلك تقيس قائمة الانتظار من جهة العميل، عبر مراقبة الزمن اللازم لوصول أول بايت، أو تحصي استجابات 503 عند المكوّن الموجود أمام Ollama.
لماذا يكون ضبط MAX_QUEUE بقيمة أصغر أفضل غالباً
تبدو قائمة انتظار بسعة 512 سخية، لكنها تكاد تكون عديمة الفائدة عند وجود slot واحد. ينتظر الطلب 300 خلف 299 عملية توليد مكتملة. ويستغرق ذلك دقائق في أفضل الأحوال. يتخلى كل HTTP client عن الانتظار قبل ذلك بوقت طويل، لذلك يرى المستدعي مهلة انتظار من جهة client. لا توضّح هذه المهلة سبب المشكلة، ولا تمنح نظام المراقبة شيئاً ينبه إليه.
اضبط قائمة الانتظار على قيمة تقارب ما يستطيع خادمك معالجته ضمن مهلة انتظار client. عندها يتحول تجاوز السعة إلى استجابة 503 فورية. وتفيدك استجابة 503: إذ يمكن لـreverse proxy إعادة المحاولة، ويمكن لـclient زيادة فترة الانتظار تدريجياً، ويمكن للوحة المعلومات عدّها، ويمكن للشخص قراءتها. احسب القيمة استناداً إلى قياساتك الفعلية. إذا استغرقت عملية التوليد نحو عشر ثوانٍ وكان client ينتظر 60 ثانية، فيمكن معالجة نحو 6 طلبات لكل slot ضمن هذه المهلة. أما قائمة الانتظار الأعمق بكثير من ذلك فلا تنتج إلا مهلات انتظار.
متى تضع queue أمام Ollama
تعمل queue المدمجة وفق مبدأ الوارد أولاً يصرف أولاً (FIFO)، ولا تعرف شيئاً عن الجهة التي ترسل الطلب. يكفي ذلك عندما يتحدث تطبيق واحد مع خادم واحد، لأن إضافة بنية تحتية ستضيف حالات فشل فقط. استخدم مكوّناً أمامها عندما ينطبق أحد الشروط التالية.
- تحتاج إلى الأولوية. يجب ألا تنتظر محادثة تفاعلية خلف مهمة تلخيص دفعية. لا تدعم queue الخاصة بـOllama الأولويات، لذلك يجب إبقاء العمل الدفعي خارجها وإرساله تدريجياً.
- تحتاج إلى الإنصاف. يمكن لعميل واحد ملء queue بالكامل، وعندها يحصل جميع العملاء الآخرين على 503.
- تحتاج إلى بقاء العمل بعد إعادة التشغيل. توجد queue في ذاكرة الخادم. عند إعادة تشغيل Ollama، يختفي كل طلب ينتظر.
- تحتاج إلى عمليات إعادة محاولة فعلية مع backoff، مع تسجيلها في مكان يمكنك فحصه لاحقاً.
الخيار الخفيف هو reverse proxy. في nginx، يحدّد limit_conn الحد الأقصى للاتصالات المتزامنة، ويحدّد limit_req معدل الوصول لكل عميل، لذلك يرفض proxy الطلبات الزائدة ولا تصل إلى queue الخاصة بـOllama. أما الخيار الأثقل فهو job queue مع قاعدة بيانات أمام worker يستدعي Ollama. هذا هو الخيار المناسب عندما يجب أن تبقى الطلبات بعد إعادة تشغيل العملية. يتطلب ضبط الحجم لحركة المرور الفعلية دراسة مستقلة: يشرح تخطيط LLM مستضاف ذاتياً للمستخدمين المتزامنين الحسابات اللازمة، بينما يغطّي تشغيل Ollama على VPS التثبيت الأساسي الذي تفترضه هذه المتغيرات.
عندما يكون الحل المناسب خادماً مختلفاً
هناك حد لا يمكنك تجاوزه بضبط الإعدادات. يقسّم Ollama ذاكرة KV cache إلى خانات متساوية وثابتة عند تحميل النموذج. لا يمكن استخدام ذاكرة الخانة الخاملة من الخانة المشغولة، ولا يمكن تغيير عدد الخانات من دون إلغاء تحميل النموذج. يناسب هذا التصميم مستخدماً واحداً، أو فريقاً صغيراً، أو coding agent.
تعمل الخوادم المصممة لعدد كبير من المستخدمين المتزامنين بطريقة مختلفة. فهي تخصص ذاكرة KV cache في صفحات صغيرة عند الطلب، وتضيف الطلبات الواردة إلى batch قيد التشغيل، لذلك تتبع الذاكرة الطلب الفعلي بدلاً من التقسيم الثابت. إذا كان هدفك تشغيل عدد كبير من المستخدمين المتزامنين على GPU واحد، فإن هذا الاختلاف المعماري أهم من أي قيمة لـ OLLAMA_NUM_PARALLEL. المقارنة بين Ollama وvLLM هي الموضع المناسب لاتخاذ هذا القرار. لكن لا تنتقل لمجرد المبدأ؛ فتشغيل خادم مختلف يتطلب جهداً تشغيلياً أكبر، وإذا كانت حركة الشبكة لديك تقتصر على بضعة أشخاص، فإن السلوك المدمج هو الخيار الصحيح.
قِس معدل المعالجة لديك والزمن حتى ظهور الرمز الأول
تأتي أرقام الرموز في الثانية المنشورة من وحدة GPU والنموذج ومستوى التكميم وطول السياق والموجّه الخاص بشخص آخر. ولا يطابق أيٌّ من ذلك إعدادك، لذلك تعامل مع أي رقم تقرؤه باعتباره مؤشراً تقريبياً، وقِس أداء الجهاز الموجود أمامك.
يعرض Ollama أزمنة التنفيذ في كائن JSON النهائي لكل استجابة. eval_count هو عدد الرموز المُولَّدة، وeval_duration هو الزمن المستغرق في توليدها، بوحدة النانوثانية.
sudo apt install -y jq
curl -s http://127.0.0.1:11434/api/generate \
-d '{"model":"llama3.2:3b","prompt":"Explain what a KV cache is.","stream":false}' \
| jq '{prompt_eval_count, eval_count, eval_duration, tokens_per_second: (.eval_count / (.eval_duration / 1000000000))}'شغّل ذلك بفتحة واحدة، ثم شغّله مرة أخرى عند مستوى التزامن الذي تتوقعه فعلياً، وقارن الرقمين اللذين يحددان مدى رضا المستخدمين: الزمن حتى ظهور الرمز الأول، وعدد الرموز في الثانية لكل طلب. ينخفض معدل المعالجة لكل طلب دائماً عند إضافة الفتحات. والسؤال هو ما إذا كان هذا الانخفاض يتجاوز الحد الذي يقبله المستخدمون. يشرح قياس عدد الرموز في الثانية على نموذج LLM محلي الطريقة بمزيد من التفصيل، بما في ذلك كيفية إبقاء الموجّه ثابتاً بين عمليات التشغيل.
نقطة نهاية عامة ذات قائمة انتظار كبيرة هدف لهجوم حجب الخدمة
يضع OLLAMA_HOST=0.0.0.0:11434 واجهة API على جميع الواجهات، ولا يوفّر Ollama مصادقة مضمّنة. ستقبل نقطة نهاية مفتوحة مع قائمة الانتظار الافتراضية 512 طلباً قيد الانتظار من أي شخص يعثر عليها. ولا يتطلب ملء قائمة الانتظار هذه من المهاجم إلا تكلفة ضئيلة: مطالبات طويلة، وعدم وجود تسجيل دخول، وعدم وجود حد لمعدل الطلبات، وعدم وجود فاتورة. بعد ذلك، يحصل مستخدموك على استجابات 503 أو ينتظرون وقتاً طويلاً، ويظل الجهاز مشغولاً طوال هذه المدة.
أبقِ المستمع على loopback، والوصول إليه عبر نفق SSH أو شبكة خاصة، أو ضع المصادقة وتحديد معدل الطلبات أمامه. يشرح تأمين نقطة نهاية Ollama API الخيارين. اضبط قائمة الانتظار بعد إتمام ذلك، لأن طول قائمة الانتظار إعداد للسعة، ولا يوفر أي حماية.
FAQ
لماذا ينتظر طلب Ollama الثاني حتى يكتمل الطلب الأول؟
لأن OLLAMA_NUM_PARALLEL تكون قيمته الافتراضية 1، ولذلك يعالج النموذج المحمّل طلباً واحداً في كل مرة، بينما تنتظر الطلبات الأخرى بالترتيب. يُبقي الطلب المنتظر اتصال HTTP مفتوحاً ولا يرسل أي بايتات حتى تتوفر خانة، وهذا يبدو للعميل مماثلاً تماماً لبطء النموذج. يظهر الفرق في نمط التوقيت: التوقف الطويل ثم ظهور النص بالسرعة الكاملة يعني وجود طابور، أما التدفق البطيء منذ أول token فيعني أن النموذج بطيء. ارفع عدد الخانات باستخدام systemd drop-in، ثم أعد تشغيل الخدمة.
ماذا تعني الرسالة "server busy, please try again. maximum pending requests exceeded"؟
هذا خطأ تجاوز سعة طابور Ollama، ويُعاد مع حالة HTTP 503. وصل عدد الطلبات المنتظرة إلى OLLAMA_MAX_QUEUE، وقيمته الافتراضية 512، ولذلك رُفض أحدث طلب بدلاً من إضافته إلى الطابور. لا يتعلق ذلك بخطأ في الذاكرة ولا بخطأ في النموذج. يؤدي رفع سعة الطابور فقط إلى جعل المتصلين ينتظرون مدة أطول قبل الرفض نفسه. لذلك تتمثل الحلول الفعلية في زيادة عدد الخانات إذا كانت VRAM المتاحة تكفي، أو تقليل الحمل الوارد، أو وضع طابور أمامي يمكنه إعادة المحاولة وتحديد الأولويات.
هل يؤدي رفع OLLAMA_NUM_PARALLEL إلى جعل Ollama أسرع؟
لا. يتيح ذلك تشغيل عدد أكبر من الطلبات في الوقت نفسه، لكن كل طلب منها يصبح أبطأ مما لو عولج منفرداً، لأنها تتشارك GPU واحداً. كما يضاعف ذلك حجم KV cache، لأن Ollama يبدأ runner بإجمالي context يساوي طول السياق مضروباً في عدد الخانات. إذا لم تعد النتيجة تتسع في VRAM، ينقل Ollama بعض الطبقات إلى CPU، فتتباطأ كل الطلبات، بما فيها الطلبات المنفردة التي لا تنافس غيرها. افحص ollama ps بعد التغيير، وتأكد من أن العمود PROCESSOR لا يزال يعرض 100% GPU.
هل أحتاج إلى إعادة تشغيل Ollama بعد تغيير هذه المتغيرات؟
نعم. يقرأ الخادم هذه المتغيرات عند بدء التشغيل، ويحافظ النموذج قيد التشغيل على عدد الخانات الذي ثُبّت في عملية runner عند تشغيلها. عدّل drop-in باستخدام sudo systemctl edit ollama.service، ثم شغّل sudo systemctl daemon-reload وsudo systemctl restart ollama. تحقق باستخدام systemctl show ollama --property=Environment، ثم افحص السطر server config في journalctl -u ollama، إذ يسرد هذا السطر البيئة التي حمّلها الخادم فعلياً.
كم عدد خانات التوازي التي ينبغي ضبطها؟
ابدأ بالقيمة 1 وارفعها خطوة واحدة في كل مرة. بعد كل خطوة، أعد تشغيل Ollama، وأرسل طلباً واحداً لتحميل النموذج، ثم شغّل ollama ps. توقّف عند آخر قيمة يظل فيها PROCESSOR يعرض 100% GPU، ويترك فيها العمود SIZE هامشاً لطول السياق الأكبر الذي تقدمه. بعد ذلك، قِس الزمن حتى ظهور أول token وعدد tokens في الثانية عند هذا الإعداد وتحت مستوى التوازي الفعلي لديك، ثم ارجع خطوة واحدة إذا انخفضت سرعة الطلب الواحد إلى مستوى لا يتقبله المستخدمون.