SSD Nodes Learn Hosting plans →
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-22

توازي Ollama: الفرق بين NUM_PARALLEL وMAX_QUEUE

هل ينتظر طلب Ollama الثاني أم يفشل؟ تعرّف إلى دور 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 الحالية حتى August 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 بعد ضبط المتغير، فهذا الإعداد لا يصل إلى الخادم، ويشرح القسم التالي السبب.

إذا لم تتسع VRAM لأوزان النموذج مع KV cache، ينقل Ollama بعض الطبقات إلى ذاكرة RAM النظام، وتعمل هذه الطبقات على CPU. طبقات CPU أبطأ بكثير من طبقات GPU، لذلك يؤدي ذلك إلى إبطاء كل طلب، بما في ذلك الطلب الوحيد الذي بدأت به. لذلك قد يؤدي رفع التوازي إلى خفض معدل المعالجة بدلاً من رفعه. ومع نموذج كبير بما يكفي، تحسم الأوزان وحدها المسألة قبل بدء حساب الخانات، ولهذا فإن استضافة نموذج بحجم Kimi K3 ذاتياً تتعلق بعدد البطاقات التي لديك، لا بعدد الخانات التي تضبطها.

ollama ps

يعرض عمود PROCESSOR القيمة 100% GPU عندما يتسع كل شيء. ويعني تقسيم مثل 35%/65% CPU/GPU أن جزءاً من النموذج يعمل على CPU. يتضمن عمود SIZE ذاكرة KV cache، لذلك يزداد عند رفع عدد الخانات وإعادة تحميل النموذج. ارفع قيمة OLLAMA_NUM_PARALLEL، وأعد التشغيل، وأرسل طلباً واحداً، ثم شغّل ollama ps مرة أخرى: هذه هي تكلفة الذاكرة لتغييرك، مقاسةً بدلاً من تقديرها. إذا أظهر القياس أن النموذج لم يعد يتسع، فتذكر أن الأوزان تمثل النصف الآخر من الميزانية نفسها، وأن الانتقال من إصدار fp16 إلى إصدار q8 أو q4 يحرر غالباً VRAM أكبر من التكلفة التي تسببها الخانة التي كنت تحاول إضافتها.

يتضاعف طول السياق مع عدد الخانات، لذلك يجب اختيارهما معاً. السياق الكبير مع أربع خانات يعني أربعة سياقات كبيرة. إذا كنت تضبط أيضاً نافذة السياق num_ctx لنموذجك، فغيّر أحدهما في كل مرة، وإلا فلن تعرف أيهما ملأ البطاقة.

كيفية ضبط هذه المتغيرات لكي تستمر بعد إعادة التشغيل

يعمل Ollama على Linux كخدمة systemd. يؤدي تشغيل export OLLAMA_NUM_PARALLEL=4 في shell إلى عدم تغيير أي شيء، لأن systemd يشغّل الخدمة ببيئته الخاصة ولا يرى shell الذي تستخدمه. استخدم ملف 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
wait

يمثل ttfb الزمن حتى وصول أول بايت من البث، وهو قريب من الزمن حتى وصول أول رمز (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 عن الانتظار قبل ذلك بوقت طويل. لذلك يرى المستدعي مهلة انتظار من جهة العميل، ولا توضح هذه المهلة السبب. كما أنها لا تمنح نظام المراقبة شيئاً يطلق تنبيهاً بشأنه.

اضبط قائمة الانتظار على حجم يقارب ما يستطيع خادمك معالجته ضمن مهلة انتظار العميل. عندها يصبح تجاوز السعة استجابة 503 فورية بدلاً من ذلك. وتكون استجابة 503 مفيدة: يمكن لـ reverse proxy إعادة المحاولة، ويمكن للعميل التراجع، ويمكن للوحة المعلومات إحصاؤها، ويمكن للشخص قراءتها. احسب القيمة اعتماداً على قياساتك الفعلية. إذا استغرقت عملية التوليد نحو عشر ثوانٍ وكان العميل ينتظر ستين ثانية، فيمكن لكل slot معالجة نحو ستة طلبات خلال هذه المهلة. أما قائمة الانتظار الأعمق بكثير من ذلك فلا تنتج إلا مهلات انتظار.

متى تضع قائمة انتظار أمام Ollama

قائمة الانتظار المضمّنة تعمل وفق مبدأ الوارد أولاً يخرج أولاً (FIFO)، ولا تعرف هوية الجهة التي ترسل الطلب. إذا كان تطبيق واحد يتصل بخادم واحد، فهذا يكفي، وإضافة بنية تحتية ستضيف حالات فشل فقط. استخدم مكوّناً أمام Ollama عندما ينطبق أحد الشروط التالية.

  • تحتاج إلى الأولوية. يجب ألا تنتظر محادثة تفاعلية خلف مهمة تلخيص مجمّعة. لا تدعم قائمة انتظار Ollama الأولويات، لذلك يجب احتجاز عمل الدُفعات خارجها وإرساله تدريجياً.
  • تحتاج إلى الإنصاف. يمكن لعميل واحد أن يملأ قائمة الانتظار بالكامل، وعندها يحصل جميع العملاء الآخرين على 503.
  • تحتاج إلى بقاء المهام بعد إعادة التشغيل. توجد قائمة الانتظار في ذاكرة الخادم. عند إعادة تشغيل Ollama، يضيع كل طلب منتظر.
  • تحتاج إلى إعادة محاولات فعلية مع تأخير متزايد، مع تسجيلها في مكان يمكنك فحصه لاحقاً.

الخيار البسيط هو Reverse Proxy. في nginx، يحدّد limit_conn عدد الاتصالات المتزامنة، ويحدّد limit_req معدل وصول الطلبات لكل عميل، لذلك تُرفض الطلبات الزائدة عند الـProxy ولا تصل إلى قائمة انتظار Ollama. أما الخيار الأكثر تعقيداً فهو قائمة مهام مع قاعدة بيانات أمام عامل يستدعي Ollama. هذا هو الخيار المناسب عندما يجب أن تبقى الطلبات بعد إعادة تشغيل العملية. يتطلب ضبط الحجم لحركة المرور الفعلية دراسة مستقلة: يشرح تخطيط LLM مستضاف ذاتياً للمستخدمين المتزامنين الحسابات اللازمة، ويغطي تشغيل Ollama على VPS التثبيت الأساسي الذي تفترضه هذه المتغيرات.

عندما تكون الإجابة الصحيحة هي استخدام خادم مختلف

هناك حد لا يمكنك تجاوزه بضبط الإعدادات. يقسّم Ollama ذاكرة KV cache إلى خانات متساوية وثابتة عند تحميل النموذج. لا يمكن استخدام ذاكرة الخانة الخاملة من الخانة المشغولة، ولا يمكن تغيير عدد الخانات من دون إلغاء تحميل النموذج. يناسب هذا التصميم مستخدماً واحداً، أو فريقاً صغيراً، أو coding agent.

تعمل الخوادم المصممة لعدد كبير من المستخدمين المتزامنين بطريقة مختلفة. فهي تخصص ذاكرة KV cache في صفحات صغيرة عند الطلب، وتضيف الطلبات الواردة إلى batch قيد التشغيل، بحيث تتبع الذاكرة الطلب الفعلي بدلاً من تقسيم ثابت. إذا كان هدفك هو خدمة عدد كبير من المستخدمين المتزامنين على GPU واحد، فإن هذا الاختلاف المعماري أهم من أي قيمة لـ OLLAMA_NUM_PARALLEL. المقارنة بين Ollama وvLLM هي الموضع المناسب لاتخاذ هذا القرار. لكن لا تنتقل إلى خادم آخر لمجرد المبدأ؛ فإدارة خادم مختلف تتطلب جهداً أكبر، وإذا كان عدد مستخدميك بضعة أشخاص، فإن السلوك المضمّن هو الخيار الصحيح.

قِس معدل النقل لديك والوقت حتى أول token

تأتي أرقام tokens في الثانية المنشورة من GPU ونموذج وquantisation وطول context وprompt تخص أشخاصاً آخرين. لا ينطبق أيٌّ من ذلك على جهازك، لذلك تعامل مع أي رقم تقرؤه على أنه مؤشر تقريبي، وقِس أداء الجهاز الذي تستخدمه فعلياً.

يعرض Ollama أزمنة التنفيذ في كائن JSON النهائي لكل استجابة. يبيّن eval_count عدد tokens المُولَّدة، بينما يبيّن eval_duration الوقت المستغرق في توليدها، بوحدة nanoseconds.

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))}'

شغّل ذلك باستخدام slot واحد، ثم شغّله مرة أخرى بدرجة التزامن التي تتوقعها فعلياً. قارن بين الرقمين اللذين يحددان رضا المستخدمين: الوقت حتى أول token، وعدد tokens في الثانية لكل طلب. ينخفض معدل النقل لكل طلب دائماً عند إضافة slots. والسؤال هو ما إذا كان هذا الانخفاض يتجاوز الحد الذي يقبله المستخدمون. يشرح قياس عدد tokens في الثانية على LLM محلي الطريقة بمزيد من التفصيل، بما في ذلك كيفية إبقاء prompt ثابتاً بين عمليات التشغيل.

نقطة نهاية عامة ذات طابور كبير هدف لهجمات حجب الخدمة

يؤدي ضبط 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 status 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 في الثانية عند ذلك الإعداد وتحت مستوى التوازي الفعلي لديك، ثم ارجع خطوة واحدة إذا انخفضت سرعة الطلب الواحد إلى أقل من المستوى الذي يقبله المستخدمون.

#ollama#concurrency#vram#queueing#self-hosted-llm