Ollama : gérer la concurrence avec NUM_PARALLEL
Découvrez quand la deuxième requête Ollama attend ou reçoit une erreur HTTP 503, et pourquoi chaque emplacement NUM_PARALLEL consomme davantage de VRAM.
Ce qui arrive à 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é ne traite qu’une 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 soit disponible, puis elle s’exécute à vitesse normale.
Une requête entrante a trois issues possibles. Elle démarre immédiatement dans un emplacement libre. Elle attend dans la file. Ou la file est déjà pleine et le serveur la refuse avec 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 « ne répond plus » alors qu’aucun problème ne s’est produit. Ajouter des emplacements nécessite seulement deux lignes de modification. Le point critique est la mémoire. Chaque emplacement parallèle a besoin de son propre cache clé/valeur (KV cache), c’est-à-dire le bloc de mémoire qu’un modèle conserve pour 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 valeurs indiquées ici, en utilisant la ligne de journal présentée plus bas.
OLLAMA_NUM_PARALLELindique 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_MODELSindique le nombre de modèles différents qui restent chargés en mémoire simultanément. La valeur par défaut est 0. Ollama choisit alors la valeur suivante : trois modèles par GPU, et trois modèles sur une machine sans GPU.OLLAMA_MAX_QUEUEindique 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 immédiatement rejetée.
Dans le pire des cas, la mémoire nécessaire correspond au produit des deux premières valeurs. Deux modèles chargés avec quatre slots chacun représentent huit allocations de slots de cache KV, toutes résidentes simultanément. 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. Le calcul reste ainsi facile à faire de tête.
Pourquoi chaque slot parallèle consomme de la VRAM
Quand Ollama charge un modèle, il lance un processus runner distinct. Deux des arguments qu’il lui transmet 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 équitablement entre les slots. Chaque requête reçoit donc toujours la longueur de contexte demandée.
C’est toute la contrainte. Elle explique 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. La part d’un slot inactif n’est pas prêtée à un slot actif, car la répartition est fixe au démarrage du runner.
Vous pouvez lire les valeurs réellement utilisées au lieu de celles que vous vouliez définir :
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’atteint pas le 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 une partie des couches vers la RAM système. Ces couches s’exécutent alors sur le CPU. Les couches CPU sont bien plus lentes que les couches GPU. Toutes les requêtes deviennent donc plus lentes, 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. Avec un modèle suffisamment volumineux, les poids seuls tranchent la question avant même tout calcul sur les slots. C’est pourquoi auto-héberger quelque chose de la taille de Kimi K3 revient à parler du nombre de cartes disponibles plutôt que du nombre de slots configurés.
ollama psLa 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. Si cette mesure indique que le modèle ne tient plus, rappelez-vous que les poids représentent l’autre moitié du même budget. Passer d’un build fp16 à un build q8 ou q4 libère souvent davantage de VRAM que n’en consomme le slot supplémentaire.
La longueur de contexte et le nombre de slots se multiplient. Ils doivent donc être choisis ensemble. Un contexte volumineux avec quatre slots correspond à quatre contextes volumineux. Si vous réglez également la fenêtre de contexte num_ctx de votre modèle, modifiez 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 votre shell. Utilisez un fichier drop-in.
sudo systemctl edit ollama.serviceAjoutez 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=Environmentsystemctl 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 depuis le serveur lui-même :
journalctl -u ollama --no-pager | grep "server config" | tail -1Au démarrage, Ollama journalise l’ensemble de son environnement sur une ligne dont le message est server config. Cette map fait foi. 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 avec lequel il a été démarré, car cette valeur est fixée dans le processus runner au lancement. Le redémarrage ci-dessus décharge tous les modèles. La prochaine requête 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 contrôlée séparément, comme expliqué dans conserver un modèle Ollama chargé entre les requêtes.
Ce que les requêtes servies, mises en file d’attente et refusées donnent 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 temps pour 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
waitttfb 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. Toutes les requêtes affichent un ttfb similaire, et total augmente pour chacune au même moment. Le GPU est partagé entre les slots en cours d’exécution. Chaque réponse est donc plus lente que si elle était exécutée seule, mais davantage de réponses sont terminées par minute. C’est le régime obtenu lorsque vous augmentez OLLAMA_NUM_PARALLEL.
Mises en file d’attente. 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 correspond à la file, pas au modèle. Dans une fenêtre de chat, l’utilisateur voit une longue pause sans contenu, puis du texte s’afficher à pleine vitesse. Ce comportement, lent au démarrage puis rapide, caractérise une file d’attente plutôt qu’un GPU saturé.
Refusées. Le client reçoit http=503 presque instantanément, et le corps de la réponse est :
{"error":"server busy, please try again. maximum pending requests exceeded"}Ce message signifie que la file d’attente était pleine au moment de l’arrivée de la requête. Il ne fournit aucune information sur la VRAM ni sur le modèle.
Il existe une limite importante : Ollama ne publie pas la profondeur de la file d’attente. ollama ps et le 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 placé 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. Cela prend au mieux plusieurs minutes. Tous les clients HTTP abandonnent bien avant. L’appelant obtient donc un délai d’attente côté client, qui n’indique pas la cause et ne fournit aucun événement exploitable pour la supervision.
Définissez une file d’attente correspondant approximativement au nombre de requêtes que votre serveur peut traiter avant l’expiration du délai d’attente du client. Les requêtes en trop produisent alors immédiatement une erreur 503. Cette erreur est utile : un reverse proxy peut réessayer, un client peut ralentir ses nouvelles tentatives, un tableau de bord peut la comptabiliser et un administrateur peut la lire. Déterminez cette valeur à partir de vos propres mesures. Si une génération prend environ dix secondes et que votre 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 délais d’attente.
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 qui envoie les requêtes. Pour une application qui communique avec un seul serveur, cela suffit. Ajouter une infrastructure ne ferait qu’ajouter des sources de panne. Utilisez une solution en amont dans les cas suivants.
- Vous avez besoin de priorités. Un chat interactif ne doit pas attendre derrière une tâche de résumé par lots. La file d’Ollama ne gère aucune priorité. Les tâches par lots doivent donc être retenues en dehors d’Ollama et envoyées progressivement.
- Vous avez besoin d’équité. Un client peut remplir la file à lui seul, puis tous les autres reçoivent une erreur 503.
- Vous devez conserver les tâches après un redémarrage. La file réside 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 temporisation progressive, enregistrées dans un emplacement consultable ultérieurement.
La solution légère consiste à utiliser un reverse proxy. Dans 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 excédentaires sont alors refusées par le proxy et n’atteignent jamais la file d’Ollama. La solution complète consiste à placer une job queue adossée à 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. Le dimensionnement pour un trafic réel constitue un exercice distinct : planifier un LLM auto-hébergé pour plusieurs utilisateurs simultanés détaille les calculs, et exécuter Ollama sur un VPS couvre 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 ajustant la configuration. Ollama répartit le KV cache en slots fixes de taille identique lors du chargement du modèle. La mémoire d’un slot inactif ne peut pas être utilisée par un slot actif, et le nombre de slots ne peut pas changer sans décharger le modèle. Cette conception convient bien à 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 KV cache à la demande, par petites pages, et ajoutent les requêtes entrantes à un batch déjà en cours d’exécution. La mémoire suit ainsi la demande réelle au lieu d’être répartie de manière 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 lue comme une simple indication et mesurez les performances de la machine que vous utilisez.
Ollama renvoie les temps d’exécution dans le dernier objet JSON de chaque réponse. eval_count indique le nombre de tokens générés et eval_duration le temps consacré à leur génération, 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 avec le niveau de concurrence réellement attendu, et comparez les deux valeurs 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 peuvent l’accepter. Mesurer le nombre de tokens par seconde avec un LLM local décrit la méthode plus en détail, notamment la façon de conserver le même prompt entre les mesures.
Un endpoint public avec une file d’attente généreuse constitue une cible pour un déni de service
La définition de OLLAMA_HOST=0.0.0.0:11434 expose l’API sur toutes les interfaces, et Ollama n’intègre aucun mécanisme d’authentification. Un endpoint ouvert avec la file d’attente par défaut accepte 512 requêtes en attente de la part de n’importe quelle personne qui le trouve. Remplir cette file ne coûte presque rien à un attaquant : prompts longs, aucune connexion, aucune limite de débit et aucune facturation. Vos propres 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 boucle locale 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 ces deux approches. Ajustez ensuite la file d’attente, car sa longueur est un paramètre de capacité et ne protège rien.
FAQ
Pourquoi ma deuxième requête Ollama attend-elle que la première soit terminée ?
Parce que OLLAMA_NUM_PARALLEL vaut 1 par défaut. Un modèle chargé ne traite donc qu’une requête à la fois, et les autres attendent dans l’ordre. La requête en attente maintient sa connexion HTTP ouverte et n’envoie aucun octet tant qu’un emplacement ne se libère pas. Côté client, cela ressemble à un modèle lent. La forme des temps de réponse permet de faire la distinction : une longue pause suivie de texte généré à 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épassement de file d’attente d’Ollama, renvoyée avec le code d’état 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é refusée au lieu d’être ajoutée à la file. Ce n’est ni une erreur de mémoire ni une erreur de modèle. Augmenter la taille de la file d’attente ne fait qu’allonger l’attente avant le même refus. Les véritables solutions sont d’augmenter le nombre d’emplacements si la VRAM disponible le permet, de réduire la charge entrante, ou de placer une file d’attente en amont capable de réessayer et de gérer les priorités.
Augmenter OLLAMA_NUM_PARALLEL rend-il Ollama plus rapide ?
Non. Cela permet de traiter davantage de requêtes simultanément, mais chacune est plus lente que si elle était traitée seule, car elles partagent le même GPU. Cela multiplie également le cache KV, car Ollama démarre le runner avec un contexte total égal à votre 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 lors de son 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 étape par étape. 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 servez. Mesurez ensuite le délai avant le premier token et le nombre de tokens par seconde avec ce réglage, dans vos conditions réelles de concurrence. Revenez à la valeur précédente si la vitesse par requête descend sous le niveau acceptable pour vos utilisateurs.