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

Pourquoi votre LLM bloque à 5 utilisateurs

Une personne suffit, cinq ralentissent : batching, cache KV, prefill et profondeur de file expliquent combien d’utilisateurs votre serveur LLM peut gérer.

Pourquoi un LLM auto-hébergé ralentit-il lorsque davantage d’utilisateurs arrivent ?

Un LLM auto-hébergé se bloque avec 5 utilisateurs simultanés parce que le serveur génère encore une seule réponse à la fois. Les 4 autres requêtes attendent dans une file. La documentation d’Ollama est claire sur la valeur par défaut : OLLAMA_NUM_PARALLEL correspond au « nombre maximal de requêtes parallèles que chaque modèle traite simultanément, valeur par défaut : 1 ». Rien n’est défaillant. 4 personnes sur 5 attendent simplement leur tour.

La solution consiste rarement à utiliser une machine plus puissante. Il faut un moteur de serving capable de traiter de nombreuses requêtes lors de la même passe avant du modèle, ainsi que suffisamment de mémoire disponible pour conserver le contexte de chaque conversation pendant ce traitement. Les 2 éléments sont nécessaires. C’est toutefois le second qui détermine réellement votre limite.

Les deux phases que traverse chaque requête

Le prefill lit l’intégralité du prompt en une seule fois et construit le cache d’attention correspondant. Tous les tokens du prompt passent ensemble dans le modèle. Le prefill consiste donc en une grande multiplication matricielle et dépend du débit de calcul. Le decode génère ensuite la réponse un token à la fois. À chaque token, il faut relire tous les poids du modèle depuis la mémoire, alors que le calcul effectué sur ce seul token est limité. Le decode dépend donc de la bande passante mémoire.

Cette asymétrie explique pourquoi le batching est efficace. Pour décoder la requête d’un utilisateur, le système lit par exemple 5 GB de poids par token, tandis que la plupart des unités de calcul restent inactives. Avec une deuxième requête, le moteur lit les mêmes 5 GB une seule fois, puis calcule deux tokens à partir de ces données. Le deuxième utilisateur ajoute presque aucun temps de calcul. Traiter les requêtes strictement l’une après l’autre annule cet avantage.

Deux mesures décrivent l’expérience de l’utilisateur. Le TTFT (time to first token) correspond au temps d’attente dans la file, auquel s’ajoute le prefill. L’ITL (inter-token latency) correspond à l’intervalle entre les tokens transmis en streaming. Il est déterminé par le decode. Un serveur lent l’est généralement sur l’une de ces deux mesures, et les corrections à appliquer ne sont pas les mêmes.

Le batching statique fait attendre tout le monde la réponse la plus lente

Le batching statique est la version naïve. C’est ce que vous obtenez lorsque vous regroupez vous-même les requêtes dans le code de l’application. Le moteur collecte N requêtes, les exécute ensemble et conserve chaque slot occupé jusqu’à la fin de la génération la plus longue du groupe.

Un utilisateur qui demande un résumé de 1,200 tokens maintient quatre réponses d’une ligne bloquées dans le batch, car celui-ci ne libère aucun slot tant que son membre le plus lent n’a pas terminé.

Deux coûts en résultent. Les séquences terminées continuent d’occuper des slots sans effectuer de calcul utile. Le débit effectif diminue donc lorsque les longueurs de sortie varient, ce qui est fréquent avec les conversations. Une requête qui arrive juste après la formation du batch doit attendre que tout le batch soit traité avant même de commencer le prefill. Son TTFT dépend donc du texte long demandé par un autre utilisateur.

Le continuous batching admet et retire les requêtes à chaque token

Le continuous batching planifie l’exécution à l’échelle d’une seule étape de décodage. Après chaque étape, le scheduler supprime les séquences qui viennent d’émettre leur token d’arrêt, puis admet les requêtes en attente dans les slots libres. Une réponse qui se termine à l’étape 40 libère son slot à l’étape 40, et non à la fin d’un batch.

Ce mécanisme n’a rien d’inhabituel. llama-server documente -cb, --cont-batching comme « whether to enable continuous batching (a.k.a dynamic batching) (default: enabled) », et vLLM est conçu autour de ce principe. Ollama traite également les requêtes en parallèle. Par défaut, la limite est simplement fixée à 1. C’est pourquoi de nombreuses personnes concluent que leur matériel ne peut pas gérer la concurrence, alors que c’est leur configuration qui l’a désactivée.

Les résultats publiés sur le continuous batching sont généralement mesurés sur des cartes datacenter qui disposent à la fois de ressources de calcul inutilisées et de dizaines de gigaoctets pour le cache. La tendance de ces résultats s’applique aussi à votre serveur. Leur ampleur, en revanche, ne se transpose pas directement. La section suivante consacrée à la mémoire l’explique.

Le prefill entre en concurrence avec le décodage pour les mêmes ressources de calcul

Lorsqu’une nouvelle requête arrive alors que quatre réponses sont en cours de streaming, son prompt doit d’abord être traité en prefill, une opération qui sollicite fortement le calcul. Si le scheduler attribue une étape distincte à ce prefill, les quatre utilisateurs en streaming ne reçoivent aucun token pendant cette étape. Avec un prompt long, cela provoque une pause visible dans chaque fenêtre ouverte. C’est ce que l’on désigne par un ralentissement du serveur lorsqu’un autre utilisateur clique sur Send.

Le chunked prefill découpe un prompt long en plusieurs parties et mélange chaque partie avec les décodages en cours au sein de la même étape. Le guide de réglage de vLLM énonce directement le compromis : des budgets de chunks plus petits « améliorent l’ITL, car moins de prefills ralentissent les décodages », tandis que des valeurs plus élevées « améliorent le time to first token (TTFT), car davantage de tokens de prefill peuvent être traités dans un batch ». Vous devez choisir l’expérience à privilégier : celle de la personne qui attend le début de la réponse ou celle des personnes qui regardent le texte défiler.

La longueur du prompt détermine l’importance de cet impact. Un prompt de 6,000 tokens avec une réponse de 200 tokens représente 6,000 tokens de travail de prefill contre 200 étapes de décodage. Le chat augmenté par récupération et les longs prompts système vous placent tous deux dans cette situation. Le prefill n’est alors plus une différence négligeable : c’est l’opération que les utilisateurs attendent. Le prefix caching est utile lorsque la partie longue se répète : vLLM expose --enable-prefix-caching, qui réutilise le cache d’un préfixe de prompt partagé au lieu de le recalculer pour chaque requête.

La mémoire qui s’épuise en premier est le cache KV

Chaque token de chaque conversation active laisse un vecteur de clé et un vecteur de valeur dans chaque couche du modèle. C’est le cache KV (cache key/value). Il évite au décodage de recalculer l’intégralité du prompt pour chaque nouveau token. Sa taille par token est fixée par l’architecture du modèle : 2 (une clé et une valeur), multiplié par le nombre de couches, le nombre de têtes key/value, la dimension des têtes, puis le nombre d’octets par valeur. Relevez ces nombres dans le config.json du modèle.

Faites le calcul une fois et la limite ne sera plus mystérieuse. Un modèle 8B courant avec 36 couches, 8 têtes key/value et une dimension de tête de 128, avec un cache stocké sur 16 bits, consomme 2 36 8 128 2 octets par token. Cela représente 147,456 octets, soit environ 144 KiB. Une conversation de 8,192 tokens nécessite donc environ 1.2 GB de cache. Cinq conversations en nécessitent environ 6 GB, en plus des poids du modèle. C’est la véritable réponse à la question du nombre d’utilisateurs que le serveur peut prendre en charge.

La concurrence multiplie le contexte, et les outils l’indiquent explicitement. La FAQ d’Ollama précise : « Le traitement parallèle des requêtes pour un modèle donné augmente la taille du contexte selon le nombre de requêtes parallèles. Par exemple, un contexte de 2K avec 4 requêtes parallèles produit un contexte de 8K et une allocation mémoire supplémentaire. » La RAM nécessaire évolue selon OLLAMA_NUM_PARALLEL multiplié par OLLAMA_CONTEXT_LENGTH. Dans llama-server, le contexte demandé avec -c est réparti entre les -np slots. Augmenter uniquement le nombre de slots réduit donc la capacité de chaque requête. Lisez le contexte par slot dans le log de démarrage au lieu de le supposer.

vLLM préalloue la mémoire. --gpu-memory-utilization (0.92 par défaut) correspond à « la fraction de la mémoire GPU à utiliser pour l’exécuteur du modèle ». La mémoire restante après le chargement des poids devient le pool KV paginé. Lorsque ce pool est épuisé, le scheduler évince une requête au lieu de l’échouer :

WARNING 05-09 00:49:33 scheduler.py:1057] Sequence group 0 is preempted by PreemptionMode.RECOMPUTE mode because there is not enough KV cache space.

Dans le moteur V1 de vLLM, le mode de préemption par défaut est RECOMPUTE. Une requête évincée perd donc son cache et effectue de nouveau le prefill lorsqu’elle est admise. Ce travail est réalisé deux fois. La documentation avertit que « la préemption et le recalcul peuvent dégrader la latence de bout en bout ». Cette ligne de log est la meilleure explication d’un temps d’attente beaucoup plus long pour un utilisateur malchanceux alors que la moyenne reste correcte. Définissez disable_log_stats=False pour journaliser le compteur cumulé, ou lisez le compteur de préemption dans les métriques Prometheus exposées par vLLM.

Ce qui change avec 2, 5 et 20 utilisateurs simultanés

Deux utilisateurs. La différence est presque invisible sur un GPU disposant de cache en réserve, car le deuxième flux de décodage s’ajoute au premier avec un surcoût très faible. Sur un VPS utilisant uniquement le CPU et disposant de 4 à 8 GB de RAM, ce n’est pas gratuit : les deux flux partagent les mêmes quelques vCPU et la même bande passante mémoire. Chaque utilisateur obtient donc environ la moitié des tokens par seconde, tandis que la demande de cache double alors que le budget disponible est beaucoup plus faible.

Cinq utilisateurs. C’est à ce stade que les valeurs par défaut ne suffisent plus, et le problème commence par devenir un problème de file d’attente. Avec OLLAMA_NUM_PARALLEL réglé sur 1, quatre personnes attendent celle qui a demandé la réponse la plus longue, puis chacune retrouve une vitesse normale lorsque son tour arrive. Si vous augmentez le nombre de traitements parallèles, le problème change de nature : cinq emplacements avec un contexte de 8K chacun nécessitent un cache de 40K tokens. S’il ne tient pas dans la VRAM, le moteur décharge des couches vers la RAM système. S’il ne tient pas dans la RAM, la machine utilise le swap et le débit en tokens par seconde s’effondre.

Vingt utilisateurs. Vingt personnes dans une interface de chat ne correspondent généralement pas à vingt requêtes simultanées. C’est le point le plus important à comprendre avant d’acheter du matériel. Une personne lit une réponse et réfléchit pendant 20 à 60 secondes entre deux tours. La majeure partie de sa session est donc inactive. Vingt agents ou vingt tâches de résumé de documents correspondent en revanche à vingt flux réellement actifs, sans aucun temps d’inactivité. Il faut alors une autre machine.

Vos utilisateurs sont-ils réellement concurrents, ou seulement connectés ?

Calculez le nombre de requêtes en cours avant de dimensionner quoi que ce soit. Le calcul est simple : le nombre de requêtes en cours est égal au nombre d’utilisateurs multiplié par le nombre de secondes nécessaires pour générer une réponse, puis divisé par le nombre de secondes entre deux tours.

  1. Mesurez d’abord le débit sur votre propre flux unique, pour le prefill comme pour le decode. N’utilisez pas une valeur relevée sur la carte de quelqu’un d’autre : mesurez les tokens par seconde sur votre propre machine et utilisez le résultat obtenu.
  2. Estimez le cycle d’activité. Vingt utilisateurs de chat, 12 secondes de génération par tour et un tour toutes les 90 secondes donnent 20 * 12 / 90, soit environ 2.7 requêtes en cours.
  3. Définissez un nombre de slots légèrement supérieur à cette valeur, puis vérifiez-le par rapport à la mémoire : le nombre de slots multiplié par le contexte par requête doit tenir dans le nombre de tokens de cache réellement disponibles.
  4. Gardez la queue courte afin que le dépassement échoue rapidement et de manière visible.

Le nombre de tokens de cache disponibles correspond à la mémoire libre après chargement des poids, divisée par le coût par token indiqué dans la section précédente. Une carte de 24 GB exécutant un modèle 8B en 16-bit consomme environ 16 GB pour les poids et dispose d’environ 6 GB de cache utilisable avec l’utilisation par défaut, soit environ cinq conversations de 8K. Pour en faire tenir davantage, réduisez le contexte par requête ou stockez le cache en 8-bit (llama-server prend --cache-type-k q8_0). Ces deux options augmentent la concurrence en échange d’un compromis, et il est utile d’en comprendre clairement le coût avant d’investir dans le matériel : à partir de quand un GPU VPS devient rentable face aux tokens d’API.

Quand les valeurs par défaut d’Ollama ne suffisent plus

Augmentez le nombre d’exécutions parallèles dans l’unité de service, car un export effectué dans un shell ne sera pas transmis à un daemon géré par systemd.

sudo systemctl edit ollama.service
[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_CONTEXT_LENGTH=8192"
Environment="OLLAMA_MAX_QUEUE=64"
sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environment
ollama ps

systemctl show doit afficher les trois variables que vous venez de définir. Si ce n’est pas le cas, le drop-in n’a pas été enregistré et aucune autre action ne pourra fonctionner. ollama ps liste ensuite le modèle chargé avec une taille supérieure à celle des seuls poids, car quatre slots de 8,192 tokens réservent 32,768 tokens de cache supplémentaires. Si la colonne PROCESSOR indique qu’une partie du modèle se trouve sur le CPU alors que vous vous attendiez à ce qu’il soit entièrement sur le GPU, vous avez demandé plus de cache que la carte n’en disposait. Réduisez l’une des deux valeurs.

La valeur par défaut de la file d’attente mérite un examen supplémentaire. Ollama met en file d’attente jusqu’à OLLAMA_MAX_QUEUE requêtes, et « la valeur par défaut est 512 ». Au-delà, il répond « avec une erreur 503 indiquant que le serveur est surchargé ». Une file de 512 requêtes sur une machine qui en traite quatre à la fois est une promesse impossible à tenir, car le client en position 300 expire bien avant son tour. Une file courte renvoie une erreur que votre application peut réessayer ou signaler. C’est préférable à un indicateur de chargement qui ne se termine jamais.

Testez le comportement réel. Envoyez deux requêtes au même moment depuis deux terminaux et surveillez-les toutes les deux. Si la seconde ne produit rien avant la fin de la première, le paramètre d’exécution parallèle n’a pas été pris en compte.

Quand un véritable moteur de serving devient rentable

vLLM justifie sa configuration supplémentaire lorsque vous disposez d’un GPU avec de la marge et que plus d’environ quatre requêtes sont réellement en cours de traitement. Son scheduler fonctionne token par token, son cache est paginé afin de réutiliser les fragments libres, et il convertit la VRAM disponible en capacité de traitement simultané au lieu de la laisser inutilisée. En août 2026, l’installation et le lancement documentés tiennent en deux commandes :

uv pip install vllm --torch-backend=auto
vllm serve Qwen/Qwen2.5-1.5B-Instruct
curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "Qwen/Qwen2.5-1.5B-Instruct",
        "messages": [{"role": "user", "content": "Say hello."}]
    }'

Une réponse contenant un tableau choices signifie que le serveur est opérationnel et que le modèle est chargé. En charge, les deux paramètres importants sont --max-num-seqs, le « nombre maximal de séquences à traiter lors d’une même itération », et --max-num-batched-tokens, le « nombre maximal de tokens pouvant être traités lors d’une même itération ». Le premier limite le traitement simultané. Le second définit le budget de prefill par blocs décrit plus haut.

Avec moins d’environ quatre requêtes en cours de traitement, ou sur une machine dépourvue de GPU compatible, vLLM ajoute de la complexité pour un gain limité. Il attend une carte de type CUDA et réserve la majeure partie de la mémoire au démarrage, ce qui constitue un mauvais compromis sur un VPS doté de 4 à 8 GB de RAM. Dans ce cas, préférez un modèle plus petit avec un contexte plus court et une file d’attente que vous contrôlez. différences entre Ollama et vLLM comme moteurs de serving présente ce choix en détail, et exécuter Qwen 3 8B sur un VPS montre les besoins d’un modèle de taille intermédiaire avant l’ajout d’un seul utilisateur supplémentaire.

Le compromis que la sagesse populaire masque

Le continuous batching augmente le débit total et améliore généralement la latence médiane, car une requête en file d’attente démarre plus tôt. La latence de queue évolue dans l’autre sens, mais ce point est rarement mentionné.

Chaque séquence supplémentaire dans un step ajoute un peu de travail. L’ITL augmente donc pour tout le monde à mesure que le batch se remplit. Le prefill d’une nouvelle requête utilise une partie d’un step dont les utilisateurs en streaming auraient autrement bénéficié. Sous pression sur le cache, le scheduler préempte des requêtes, ce qui renvoie une requête partiellement générée au début de son prefill.

Une interface de chat montre les latences de queue, pas les moyennes. Un stream qui s’interrompt pendant deux secondes au milieu d’une phrase semble défaillant, même si le temps total jusqu’à la fin est bon. Mesurez le TTFT p95 et l’ITL p95 avec la charge prévue, et considérez la moyenne de tokens par seconde comme une mesure de capacité, pas comme une description de l’expérience utilisateur.

Le réglage pratique découle de ce constat. Limitez la concurrence légèrement en dessous de ce que la mémoire permet, afin que le moteur n’ait jamais besoin de préempter. Une file d’attente courte et prévisible vaut mieux qu’un batch profond qui provoque du thrashing, car un utilisateur qui attend quatre secondes puis bénéficie d’un stream fluide est plus satisfait qu’un utilisateur dont la requête démarre immédiatement mais s’interrompt deux fois.

Points à vérifier en cas de lenteur

Chaque requête est traitée normalement, mais l’attente est longue. Il s’agit d’une file d’attente, pas d’un problème de vitesse. Vérifiez d’abord le paramètre de parallélisme. Le modèle traite correctement les requêtes, une par une.

Ollama renvoie HTTP 503. La file d’attente est pleine. Soit la machine a réellement atteint sa capacité, soit OLLAMA_MAX_QUEUE est volontairement bas pour limiter la charge, ce qui est son objectif.

Le nombre de tokens par seconde s’effondre sous la charge sur une machine équipée d’un CPU. Exécutez vmstat 1 pendant le problème. Des valeurs non nulles dans les colonnes si et so indiquent que la machine utilise le swap. Les poids sont donc lus sur le disque à chaque token. Aucune modification de configuration ne peut résoudre ce problème. Réduisez la taille du modèle ou le nombre de slots.

Un utilisateur sur dix attend beaucoup plus longtemps que les autres. Recherchez preempted dans le journal vLLM. La préemption et le recalcul associé sont généralement en cause. Cela signifie que le cache est surchargé pour la longueur de contexte autorisée.

Le TTFT est mauvais alors que le serveur est inactif. Il s’agit du prefill, pas de la concurrence. Les prompts longs nécessitent un temps de traitement réel avant l’apparition du premier token. Vérifiez donc la taille des prompts et le prefix caching avant d’examiner le matériel.

FAQ

Pourquoi mon LLM auto-hébergé ralentit-il lorsqu’une deuxième personne l’utilise ?

Le plus souvent, il ne ralentit pas du tout. Les requêtes sont mises en file d’attente. Ollama fournit OLLAMA_NUM_PARALLEL avec la valeur 1. La deuxième requête attend donc que la première produise son dernier token. Distinguez les deux cas en mesurant le flux d’un utilisateur pendant qu’un autre attend : si le nombre de tokens par seconde est normal dès que le flux démarre, vous avez une file d’attente et augmenter le nombre de requêtes parallèles corrige le problème. Si les deux flux avancent à mi-vitesse, vous partagez réellement la bande passante mémoire. Il s’agit alors d’une limite matérielle.

Combien d’utilisateurs simultanés un petit GPU peut-il servir ?

Comptez la mémoire, pas les utilisateurs. Commencez par les poids, puis ajoutez le cache KV, qui coûte 2 fois le nombre de couches multiplié par le nombre de têtes key/value, puis par la dimension des têtes et le nombre d’octets, pour chaque token et chaque conversation active. Un modèle 8B courant avec 36 couches, 8 têtes key/value et une dimension de tête de 128 consomme environ 144 KiB par token en 16 bits. Une conversation de 8,192 tokens nécessite donc environ 1.2 GB. Une carte de 24 GB qui conserve ce modèle en 16 bits dispose d’environ 6 GB pour le cache, soit environ cinq conversations avec un contexte complet, ou davantage si vous réduisez la taille du contexte.

Le continuous batching ralentit-il la réponse de chaque utilisateur ?

La latence médiane s’améliore généralement, car les requêtes n’attendent plus la fin d’un batch entier. En revanche, la latence de queue augmente. Chaque séquence supplémentaire ajoute du travail à chaque étape de décodage. L’arrivée d’une nouvelle requête prend une partie d’une étape aux utilisateurs dont le flux est en cours, et une requête préemptée doit effectuer le prefill deux fois. Mesurez la latence inter-token p95, et non la moyenne, car une fenêtre de chat rend les pauses visibles alors que la moyenne les masque.

Dois-je augmenter OLLAMA_NUM_PARALLEL ou passer à vLLM ?

Commencez par augmenter le nombre de requêtes parallèles. Cette modification est gratuite et ne nécessite qu’un fichier drop-in. Elle corrige le cas courant où quatre personnes attendent derrière une réponse longue. La mémoire est la limite : les requêtes parallèles multiplient le contexte à conserver. Surveillez donc le transfert de couches vers le CPU. Passez à vLLM lorsque votre GPU dispose de VRAM disponible et que plus d’environ quatre requêtes sont réellement en cours, car c’est à partir de ce seuil que le cache paginé et la planification par token apportent un gain supérieur à leur coût.

Davantage de cœurs CPU corrigeront-ils la lenteur d’un serveur LLM ?

Pas pour la partie la plus visible par les utilisateurs. Le décodage lit le modèle entier en mémoire pour chaque token. Il est donc limité par la bande passante de la RAM, et des cœurs supplémentaires ne servent plus une fois cette bande passante saturée. Le prefill s’adapte en revanche au nombre de cœurs. Davantage de cœurs réduit donc le délai avant le premier token pour les prompts longs. Sur un VPS de 4 à 8 GB, la contrainte principale est généralement la capacité mémoire. La solution efficace consiste plutôt à utiliser un modèle plus petit ou un contexte plus court qu’à ajouter des vCPU.

#vllm#ollama#batching#throughput#self-hosted-ai