SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor

Ollama : gérer la concurrence avec NUM_PARALLEL

Avec OLLAMA_NUM_PARALLEL et OLLAMA_MAX_QUEUE, la deuxième requête attend ou reçoit une erreur HTTP 503. Chaque slot parallèle consomme aussi de la VRAM.

Que devient la deuxième requête Ollama pendant la génération de la première

La concurrence d’Ollama est contrôlée par trois variables d’environnement. Par défaut, un modèle chargé traite une seule requête à la fois. La deuxième requête n’est pas refusée et ne reçoit pas de réponse partielle. Elle attend dans une file jusqu’à ce qu’un emplacement se libère, puis elle s’exécute à vitesse normale.

Une requête entrante a trois issues possibles. Elle démarre immédiatement si un emplacement est libre. Elle attend dans la file. Ou la file est déjà pleine et le serveur la refuse avec le code HTTP 503. Le résultat dépend de OLLAMA_NUM_PARALLEL, OLLAMA_MAX_QUEUE et OLLAMA_MAX_LOADED_MODELS.

La configuration par défaut est sûre. C’est aussi la raison pour laquelle un deuxième utilisateur peut signaler que le serveur est « bloqué » alors que tout fonctionne normalement. Ajouter des emplacements nécessite de modifier deux lignes. Le point critique est la mémoire. Chaque emplacement parallèle a besoin de son propre cache clé/valeur (cache KV), c’est-à-dire la zone mémoire dans laquelle le modèle conserve les tokens qu’il a déjà traités. Ajouter des emplacements sans augmenter la VRAM (mémoire vidéo du GPU) transforme une réponse lente en échec de chargement.

Ce que contrôlent OLLAMA_NUM_PARALLEL, OLLAMA_MAX_QUEUE et OLLAMA_MAX_LOADED_MODELS

Voici les valeurs par défaut des versions actuelles d’Ollama en août 2026. Vérifiez les vôtres au lieu de vous fier aux chiffres indiqués ici, en utilisant la ligne des journaux présentée plus bas.

  • OLLAMA_NUM_PARALLEL indique le nombre de requêtes qu’un modèle chargé peut traiter simultanément. La valeur par défaut est 1 : les requêtes sont donc traitées l’une après l’autre.
  • OLLAMA_MAX_LOADED_MODELS indique le nombre de modèles différents qui restent chargés simultanément. La valeur par défaut est 0, ce qui signifie qu’Ollama choisit la valeur suivante : trois modèles par GPU, et trois modèles sur une machine sans GPU.
  • OLLAMA_MAX_QUEUE indique le nombre de requêtes pouvant rester en attente. La valeur par défaut est 512. La requête reçue lorsque la file est pleine est rejetée immédiatement.

La mémoire maximale à prévoir correspond au produit des deux premières valeurs. Deux modèles chargés avec quatre slots chacun représentent huit allocations de slots pour le KV cache, toutes résidentes simultanément, et Ollama essaiera de satisfaire cette demande. Sur une machine équipée d’un seul GPU, il est généralement préférable de conserver un seul modèle chargé et de lui attribuer plusieurs slots, car le calcul reste facile à faire de tête.

Pourquoi chaque slot parallèle consomme de la VRAM

Lorsqu’Ollama charge un modèle, il lance un processus runner distinct. Deux des arguments transmis sont importants ici : -c correspond au contexte total pour lequel le runner alloue un cache KV, et -np correspond au nombre de séquences parallèles. Ollama définit -c en multipliant la longueur de contexte par requête par le nombre de slots. Le runner répartit ensuite ce total de manière uniforme entre les slots. Chaque requête reçoit donc toujours la longueur de contexte demandée.

C’est toute la contrainte. C’est pourquoi le parallélisme n’est pas gratuit. Passer d’un slot à quatre nécessite quatre fois plus de cache KV avec la même longueur de contexte par requête. Rien n’est partagé entre les slots, et la part d’un slot inactif n’est pas prêtée à un slot actif, car cette répartition est fixe au démarrage du runner.

Vous pouvez lire les valeurs réellement utilisées au lieu de celles que vous pensiez avoir définies :

journalctl -u ollama --no-pager -n 500 | grep "starting llama-server"

Cette ligne contient la ligne de commande complète du runner, notamment -c et -np. Si -np vaut 1 après la définition de la variable, le paramètre n’est pas transmis au serveur. La section suivante explique pourquoi.

Si les poids du modèle et ce cache KV ne tiennent pas dans la VRAM, Ollama déplace certaines couches dans la RAM système. Ces couches s’exécutent alors sur le CPU. Les couches CPU sont beaucoup plus lentes que les couches GPU. Chaque requête devient donc plus lente, y compris la requête unique que vous aviez lancée. Augmenter le parallélisme peut ainsi réduire le débit au lieu de l’augmenter.

ollama ps

La colonne PROCESSOR affiche 100% GPU lorsque l’ensemble tient dans la VRAM. Une répartition telle que 35%/65% CPU/GPU signifie qu’une partie du modèle s’exécute sur le CPU. La colonne SIZE inclut le cache KV. Elle augmente donc lorsque vous augmentez le nombre de slots et rechargez le modèle. Augmentez OLLAMA_NUM_PARALLEL, redémarrez, envoyez une requête, puis exécutez de nouveau ollama ps : vous mesurez ainsi le coût mémoire de votre modification au lieu de l’estimer.

La longueur de contexte et le nombre de slots se multiplient. Vous devez donc les choisir ensemble. Un contexte important avec quatre slots correspond à quatre contextes importants. Si vous ajustez également la fenêtre de contexte num_ctx de votre modèle, ne modifiez qu’un seul des deux paramètres à la fois. Sinon, vous ne saurez pas lequel a rempli la carte.

Comment définir ces variables pour qu’elles persistent après un redémarrage

Sous Linux, Ollama s’exécute comme un service systemd. Exécuter export OLLAMA_NUM_PARALLEL=4 dans votre shell ne change rien, car systemd démarre le service avec son propre environnement et ne voit jamais celui de votre shell. Utilisez un fichier drop-in.

sudo systemctl edit ollama.service

Ajoutez ceci dans l’éditeur qui s’ouvre :

[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_MAX_LOADED_MODELS=1"
Environment="OLLAMA_MAX_QUEUE=32"

Rechargez ensuite la configuration et redémarrez le service :

sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environment

systemctl show affiche ce que systemd transmettra au processus. Si votre variable n’y figure pas, le fichier drop-in n’a pas été enregistré ou daemon-reload n’a pas été exécuté. Vérifiez également directement depuis le serveur :

journalctl -u ollama --no-pager | grep "server config" | tail -1

Au démarrage, Ollama journalise l’intégralité de son environnement sur une ligne dont le message est server config. Cette map constitue la référence. C’est le moyen le plus rapide de vérifier si une variable a bien été prise en compte.

Un modèle déjà chargé conserve le nombre de slots défini lors de son démarrage, car cette valeur est fixée dans le processus runner au lancement. Le redémarrage ci-dessus décharge tous les modèles. La requête suivante recharge donc le modèle avec le nouveau paramètre et ne paie le temps de chargement qu’une seule fois. La durée pendant laquelle le modèle reste ensuite résident est un réglage distinct, décrit dans conserver un modèle Ollama chargé entre les requêtes.

À quoi ressemblent les requêtes servies, mises en file et refusées côté client

Envoyez plusieurs requêtes simultanément et mesurez leur durée. Cette commande exécute huit requêtes de streaming en parallèle et affiche le statut ainsi que les durées de chacune :

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 correspond au délai avant le premier octet du flux. Il est proche du délai avant le premier token (TTFT), car le premier bloc envoyé contient le premier token.

Servies en parallèle. Chaque requête affiche un ttfb similaire, et total augmente simultanément pour toutes. Le GPU est partagé entre les slots actifs. Chaque réponse est donc plus lente que si elle était exécutée seule, mais davantage de réponses se terminent par minute. C’est le régime obtenu lorsque vous augmentez OLLAMA_NUM_PARALLEL.

Mises en file. Les premières requêtes reçoivent rapidement une réponse, tandis que les suivantes affichent un ttfb élevé, puis une génération normale. L’attente vient de la file, pas du modèle. Dans une fenêtre de chat, l’utilisateur voit une longue pause sans contenu, puis le texte s’affiche à pleine vitesse. Cette forme, lente au démarrage puis rapide, indique une file d’attente plutôt qu’un GPU saturé.

Refusées. Le client reçoit http=503 presque immédiatement, et le corps de la réponse est :

{"error":"server busy, please try again.  maximum pending requests exceeded"}

Ce message signifie que la file était pleine au moment de l’arrivée de la requête. Il ne donne aucune information sur la VRAM ni sur le modèle.

Une limite importante : Ollama ne publie pas la profondeur de sa file d’attente. ollama ps et l’endpoint /api/ps indiquent les modèles chargés, pas les requêtes en attente. Vous devez donc mesurer la file côté client, en surveillant le délai avant le premier octet, ou compter les réponses 503 renvoyées par le composant situé en amont.

Pourquoi une valeur plus faible de MAX_QUEUE est souvent préférable

Une file d’attente de 512 paraît généreuse, mais avec un seul slot, elle est presque inutile. La requête 300 attend derrière 299 générations terminées. Dans le meilleur des cas, cela prend plusieurs minutes. Tous les clients HTTP abandonnent bien avant. L’appelant reçoit donc un timeout côté client, qui ne lui indique pas la cause du problème et ne fournit aucune information exploitable à votre supervision.

Définissez la taille de la file en fonction du nombre de requêtes que le serveur peut traiter avant l’expiration du timeout client. Les requêtes en dépassement renvoient alors immédiatement une erreur 503. Une erreur 503 est utile : un reverse proxy peut réessayer, un client peut appliquer un backoff, un tableau de bord peut la comptabiliser et un opérateur peut la lire. Déterminez cette valeur à partir de vos propres mesures. Si une génération prend environ dix secondes et que le client attend soixante secondes, environ six requêtes par slot peuvent être traitées dans ce délai. Une file beaucoup plus longue ne fait alors que produire des timeouts.

Quand placer une file d’attente devant Ollama

La file d’attente intégrée fonctionne selon le principe premier entré, premier sorti (FIFO) et ne sait pas quel client effectue l’appel. Pour une application qui communique avec un seul serveur, cela suffit. Ajouter une infrastructure supplémentaire ne ferait qu’ajouter des sources de panne. Utilisez un composant en amont dans les cas suivants.

  • Vous avez besoin de priorités. Une conversation interactive ne doit pas attendre derrière une tâche de résumé par lots. La file d’attente d’Ollama ne gère pas les priorités. Le travail par lots doit donc être conservé en dehors d’Ollama et envoyé progressivement.
  • Vous avez besoin d’équité. Un seul client peut remplir la file d’attente à lui seul. Les autres clients reçoivent alors une erreur 503.
  • Vous devez conserver les tâches après un redémarrage. La file d’attente se trouve dans la mémoire du serveur. Si vous redémarrez Ollama, toutes les requêtes en attente sont perdues.
  • Vous avez besoin de nouvelles tentatives avec backoff, enregistrées dans un emplacement que vous pourrez consulter ensuite.

La solution légère consiste à utiliser un reverse proxy. Avec nginx, limit_conn limite le nombre de connexions simultanées et limit_req limite le débit d’arrivée par client. Les requêtes en excès sont donc refusées par le proxy et n’atteignent jamais la file d’attente d’Ollama. La solution plus lourde consiste à placer une job queue avec une base de données devant un worker qui appelle Ollama. C’est la solution à utiliser lorsque les requêtes doivent survivre au redémarrage d’un processus. Dimensionner cette architecture pour un trafic réel est un exercice à part : planifier un LLM auto-hébergé pour plusieurs utilisateurs simultanés détaille les calculs, et exécuter Ollama sur un VPS présente l’installation de base supposée par ces variables.

Quand la réponse honnête est un autre serveur

Il existe une limite que vous ne pouvez pas dépasser en modifiant la configuration. Ollama divise le cache KV en emplacements égaux et fixes au chargement du modèle. La mémoire d’un emplacement inactif ne peut pas être utilisée par un emplacement occupé, et le nombre d’emplacements ne peut pas changer sans décharger le modèle. Cette conception convient à une personne, à une petite équipe ou à un agent de programmation.

Les serveurs conçus pour de nombreux utilisateurs simultanés fonctionnent différemment. Ils allouent le cache KV à la demande, sous forme de petites pages, et ajoutent les nouvelles requêtes à un batch déjà en cours d’exécution. La mémoire suit ainsi la demande réelle au lieu d’être répartie selon une division fixe. Si votre objectif est de servir un grand nombre d’utilisateurs simultanés sur un seul GPU, cette différence d’architecture compte davantage que toute valeur de OLLAMA_NUM_PARALLEL. La comparaison entre Ollama et vLLM est l’endroit où prendre cette décision. Ne changez toutefois pas de serveur par principe : un autre serveur demande davantage d’exploitation et, si votre trafic se limite à quelques personnes, le comportement intégré est la bonne réponse.

Mesurez votre propre débit et le délai avant le premier token

Les valeurs publiées en tokens par seconde proviennent du GPU, du modèle, de la quantification, de la longueur du contexte et du prompt d’une autre personne. Aucun de ces paramètres ne correspond aux vôtres. Considérez donc chaque valeur comme une simple indication et mesurez les performances de la machine que vous utilisez.

Ollama renvoie les durées dans l’objet JSON final de chaque réponse. eval_count correspond au nombre de tokens générés et eval_duration au temps passé à les générer, en nanosecondes.

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

Exécutez cette commande avec un seul slot, puis à la valeur de concurrence réellement prévue. Comparez ensuite les deux mesures qui déterminent la satisfaction des utilisateurs : le délai avant le premier token et le nombre de tokens par seconde et par requête. Le débit par requête diminue toujours lorsque vous ajoutez des slots. La question est de savoir s’il diminue davantage que vos utilisateurs ne l’accepteront. Mesurer le nombre de tokens par seconde avec un LLM local présente la méthode plus en détail, notamment la façon de conserver le même prompt entre les exécutions.

Un endpoint public avec une file d’attente généreuse devient une cible de déni de service

Le réglage OLLAMA_HOST=0.0.0.0:11434 place l’API sur toutes les interfaces, et Ollama n’intègre aucune authentification. Un endpoint ouvert avec la file d’attente par défaut acceptera 512 requêtes en attente de la part de toute personne qui le trouve. Remplir cette file ne coûte presque rien à un attaquant : longues invites, aucune connexion, aucune limite de débit et aucune facturation. Vos utilisateurs reçoivent alors des réponses 503 ou doivent attendre longtemps, tandis que la machine reste occupée en permanence.

Laissez le listener sur la loopback et accédez-y via un tunnel SSH ou un réseau privé, ou placez une authentification et une limitation de débit devant celui-ci. Sécuriser un endpoint d’API Ollama couvre les deux approches. Réglez la file d’attente ensuite, car sa longueur est un paramètre de capacité et ne protège rien.

FAQ

Pourquoi ma deuxième requête Ollama attend-elle la fin de la première ?

Parce que OLLAMA_NUM_PARALLEL vaut 1 par défaut. Un modèle chargé traite donc une seule requête à la fois et les autres attendent dans l’ordre. La requête en attente conserve sa connexion HTTP ouverte et n’envoie aucun octet tant qu’un emplacement ne se libère pas. Côté client, cela ressemble exactement à un modèle lent. La forme des temps permet de faire la différence : une longue pause suivie de texte à pleine vitesse indique une file d’attente, tandis qu’un flux lent dès le premier token indique un modèle lent. Augmentez le nombre d’emplacements avec un drop-in systemd, puis redémarrez le service.

Que signifie « server busy, please try again. maximum pending requests exceeded » ?

Il s’agit de l’erreur de débordement de file d’attente d’Ollama, renvoyée avec le statut HTTP 503. Le nombre de requêtes déjà en attente a atteint OLLAMA_MAX_QUEUE, qui vaut 512 par défaut. La nouvelle requête a donc été rejetée au lieu d’être ajoutée à la file. Il ne s’agit ni d’une erreur de mémoire ni d’une erreur de modèle. Augmenter la taille de la file ne fait qu’allonger l’attente avant le même rejet. Les vraies solutions consistent à augmenter le nombre d’emplacements si vous disposez de suffisamment de VRAM, à réduire la charge entrante ou à placer devant Ollama une file capable de réessayer les requêtes et de les traiter par priorité.

Augmenter OLLAMA_NUM_PARALLEL rend-il Ollama plus rapide ?

Non. Cette modification permet de traiter davantage de requêtes simultanément, mais chacune est plus lente que si elle était traitée seule, car elles se partagent un seul GPU. Elle multiplie également le cache KV, car Ollama démarre le runner avec un contexte total égal à la longueur de contexte multipliée par le nombre d’emplacements. Si le résultat ne tient plus dans la VRAM, Ollama déplace des couches vers le CPU et toutes les requêtes ralentissent, y compris une requête seule, sans concurrence. Vérifiez ollama ps après la modification et confirmez que la colonne PROCESSOR contient toujours 100% GPU.

Dois-je redémarrer Ollama après avoir modifié ces variables ?

Oui. Le serveur les lit au démarrage, et un modèle en cours d’exécution conserve le nombre d’emplacements défini dans son processus runner au lancement. Modifiez le drop-in avec sudo systemctl edit ollama.service, puis exécutez sudo systemctl daemon-reload et sudo systemctl restart ollama. Confirmez avec systemctl show ollama --property=Environment, puis vérifiez la ligne server config dans journalctl -u ollama, qui liste l’environnement effectivement chargé par le serveur.

Combien d’emplacements parallèles dois-je définir ?

Commencez par 1 et augmentez la valeur d’un seul niveau à la fois. Après chaque modification, redémarrez Ollama, envoyez une requête pour charger le modèle, puis exécutez ollama ps. Arrêtez-vous à la dernière valeur pour laquelle PROCESSOR contient toujours 100% GPU et où la colonne SIZE conserve une marge suffisante pour le contexte le plus long que vous utilisez. Mesurez ensuite le délai avant le premier token et le nombre de tokens par seconde avec cette valeur, sous votre niveau réel de concurrence. Revenez à la valeur précédente si la vitesse par requête descend en dessous du niveau acceptable pour vos utilisateurs.

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