Mesurer les tokens par seconde d’un LLM local
Mesurez le débit réel de votre LLM local avec un test de concurrence, puis trouvez le seuil de tokens/s à partir duquel un GPU loué devient rentable.
Pourquoi le nombre de tokens par seconde détermine la rentabilité d’un GPU
Le nombre de tokens par seconde correspond au débit auquel votre serveur produit du texte. C’est cette valeur qui permet de déterminer si la location d’un GPU coûte moins cher qu’une API facturée au token. Un serveur GPU est facturé à l’heure, qu’il soit utilisé ou non. Une API hébergée est facturée au token. Le GPU n’est donc rentable que si vous maintenez un débit de sortie suffisamment élevé pendant la majeure partie des heures facturées.
Vous devez donc effectuer une mesure, et non reprendre une valeur trouvée ailleurs. Cette page définit les quatre valeurs à relever, fournit les commandes qui permettent de les obtenir et explique comment les utiliser dans un calcul pour prendre une décision.
Pourquoi le débit publié en tokens par seconde n’est pas le vôtre
DigitalOcean a publié en juillet 2026 des chiffres de débit pour un seul NVIDIA H200 exécutant llama3.3-70b-instruct en FP8 (virgule flottante sur 8 bits) avec vLLM. Ces chiffres sont utiles, mais ils ne sont pas les vôtres.
Les données de ce graphique
[
{
"config": "H200, one stream",
"tok_s": "47"
},
{
"config": "H100, saturated",
"tok_s": "236"
},
{
"config": "H200, saturated",
"tok_s": "2,036"
},
{
"config": "H200, saturated, in+out",
"tok_s": "4,071.6"
}
]Chaque ligne ci-dessus est reprise de cette page. Deux lignes correspondent à la valeur basse d’une plage indiquée sur cette page : considérez-les donc comme un plancher. Aucun chiffre de ce tableau n’a été mesuré par nos soins.
Commencez par les deux dernières lignes. Le chiffre mis en avant est de 4,071.6 tok/s, tandis que le débit de sortie seul est de 2,036 tok/s. Le chiffre mis en avant compte ensemble les tokens d’entrée et de sortie. Ce test utilisait 1,024 tokens d’entrée pour 1,024 tokens de sortie : la sortie représente donc presque exactement la moitié du chiffre mis en avant. Cette distinction est importante, car la sortie est la partie la plus lente. Le prefill, c’est-à-dire la lecture du prompt, traite tous les tokens d’entrée en un seul passage. Le decode, c’est-à-dire la génération de la réponse, produit un token à la fois. Un débit total moyen combine ainsi une opération peu coûteuse et une opération coûteuse.
Examinez maintenant la première ligne. Le même H200, lorsqu’il ne traite qu’une requête à la fois, produit 47 tok/s. Le chiffre en charge est donc plus de quarante fois supérieur avec le même matériel. Cet écart s’explique par le fait qu’une étape de decode unique laisse le GPU attendre la mémoire pendant la majeure partie de son temps. Les requêtes concurrentes occupent ce temps d’attente. La deuxième ligne, à 236 tok/s, correspond à un seul H100 exécutant le même modèle. Il est limité par le KV cache, c’est-à-dire le cache des clés et des valeurs : la mémoire conservée sur la carte pour chaque conversation traitée. Une carte de 80 GB peut contenir moins de requêtes concurrentes pour un modèle 70B. Elle atteint donc plus rapidement sa limite de saturation.
Si vous changez de modèle ou le ratio entre les tokens d’entrée et de sortie, tous les chiffres ci-dessus changent. Les chiffres publiés servent à définir vos attentes, pas votre budget. La même règle s’applique lorsque vous évaluez honnêtement les performances d’un VPS sur le stockage et le réseau.
Les quatre chiffres qui comptent
- Délai jusqu’au premier token, TTFT. Il s’agit du délai entre l’envoi d’une requête et l’arrivée du premier token généré. Il comprend le temps de prefill et le temps d’attente dans la file. C’est la mesure que l’utilisateur ressent directement.
- Tokens générés par seconde et par flux. Cette mesure indique la vitesse d’écriture d’une réponse une fois sa génération commencée. Au-delà d’environ 20 tok/s, la génération est déjà plus rapide que la lecture de la plupart des utilisateurs. Une vitesse supérieure apporte donc peu de gains.
- Débit total de sortie à saturation. Il s’agit de la somme des débits de tous les flux simultanés lorsque le serveur est pleinement chargé. C’est la mesure de capacité, et c’est elle qui détermine le coût du GPU.
- TTFT p50 et p99 en charge. p50 correspond à la requête médiane. p99 correspond à la valeur sous laquelle se situent 99 requêtes sur 100. La mise en file d’attente apparaît toujours d’abord dans p99.
Les deux premières mesures s’améliorent lorsque le serveur est peu sollicité. La troisième s’améliore lorsque le serveur est très sollicité. Elles évoluent donc en sens opposés. C’est pourquoi aucun chiffre unique ne suffit à décrire un serveur de serving.
Définissez les longueurs d’entrée et de sortie avant de mesurer
Le débit dépend de la structure du trafic. Un prompt de 4,000 tokens avec une réponse de 50 tokens correspond à une charge principalement liée au prefill. Un prompt de 200 tokens avec une réponse de 2,000 tokens correspond à une charge principalement liée au decode. Le même serveur peut afficher un nombre de tokens par seconde très différent dans ces deux cas. Choisissez donc un ratio, notez-le à côté de chaque valeur relevée et ne comparez jamais des valeurs correspondant à des ratios différents. Un ratio de 1,024 tokens en entrée pour 1,024 tokens en sortie constitue une valeur par défaut raisonnable, car plusieurs fournisseurs publient leurs résultats avec ce ratio. Si vous connaissez votre trafic réel, utilisez ses caractéristiques réelles.
Forcez également la longueur de sortie. Un modèle qui rencontre son token d’arrêt après 60 tokens produit une exécution plus courte, impossible à comparer à une exécution complète : le TTFT en représente une part plus importante, ce qui réduit le débit de sortie mesuré par le benchmark, même si chaque requête se termine plus rapidement. L’option --ignore-eos du client de benchmark vLLM force chaque requête à générer exactement le nombre de tokens demandé. Les deux exécutions restent ainsi comparables. Le choix du modèle influe davantage sur ces valeurs que n’importe quelle option : faire tenir un modèle Qwen 3 sur le GPU d’un VPS unique traite l’aspect mémoire de ce choix.
Mesurez d’abord un seul flux
Commencez par le cas le plus simple. Cela sert de contrôle de cohérence et définit une limite supérieure. Ollama affiche ses propres mesures.
ollama run llama3.1:8b --verbose "Write 200 words about disk scheduling."La ligne à lire est eval rate : elle indique le nombre de tokens de sortie par seconde. prompt eval rate correspond au débit de préremplissage, et load duration au temps passé à charger le modèle dans la VRAM. Lors du premier appel après un démarrage à froid, load duration est élevé ; total duration est donc trompeur. Exécutez la commande deux fois et lisez le second résultat. Par défaut, Ollama décharge un modèle inactif après cinq minutes. Une longue pause entre les exécutions vous ramène donc au démarrage à froid.
Les mêmes champs sont disponibles dans l’API, ce qui facilite leur exploitation dans un script.
curl -s http://127.0.0.1:11434/api/generate -d '{
"model": "llama3.1:8b",
"prompt": "Write 200 words about disk scheduling.",
"stream": false
}' | jq '{eval_count, eval_duration,
tok_s: (.eval_count / (.eval_duration / 1000000000))}'eval_duration est exprimé en nanosecondes. En le divisant par 1,000,000,000, vous obtenez des secondes. C’est exactement la conversion recommandée par la documentation de l’API Ollama pour calculer le nombre de tokens par seconde. Si le serveur n’est pas encore démarré, auto-héberger un LLM avec Ollama sur un VPS explique l’installation et l’unité systemd.
La TTFT nécessite une requête en streaming, et curl peut mesurer cette durée.
curl -s -o /dev/null -N \
-w 'ttfb=%{time_starttransfer}s total=%{time_total}s\n' \
http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"Qwen/Qwen3-8B",
"messages":[{"role":"user","content":"Write 200 words about disk scheduling."}],
"stream":true,"max_tokens":256}'time_starttransfer correspond à l’arrivée du premier octet du corps de la réponse. Pour une complétion de chat en streaming, cet octet appartient au premier événement envoyé par le serveur. Il contient soit le premier token de contenu, soit un delta contenant uniquement le rôle, envoyé juste avant celui-ci. Considérez donc cette valeur comme une TTFT à un événement près. Elle est suffisamment précise pour comparer deux exécutions sur le même serveur.
Les mesures avec un seul flux donnent deux fois une image trop favorable de la machine. La TTFT est alors la meilleure possible, car aucune requête ne vous précède dans la file d’attente. Le débit par flux est également le meilleur possible, car toute la carte traite une seule requête. Ces deux valeurs ne permettent pas de savoir quelle charge la machine peut réellement supporter.
Comment exécuter un balayage de la concurrence ?
Un balayage exécute une charge fixe avec un niveau de concurrence croissant et enregistre les résultats à chaque étape. vLLM fournit le client nécessaire. Celui-ci utilise l’API OpenAI et fonctionne donc aussi avec Ollama et tout autre service compatible avec OpenAI.
vllm bench serve \
--backend openai-chat \
--base-url http://127.0.0.1:8000 \
--endpoint /v1/chat/completions \
--model Qwen/Qwen3-8B \
--dataset-name random \
--random-input-len 1024 \
--random-output-len 1024 \
--ignore-eos \
--num-prompts 160 \
--max-concurrency 16 \
--percentile-metrics ttft,tpot,itl,e2el \
--metric-percentiles 50,99--max-concurrency limite le nombre de requêtes en cours et correspond à la variable balayée. --num-prompts indique le nombre total de requêtes envoyées. Gardez cette valeur proche de dix fois la concurrence pour obtenir une moyenne stable. Le récapitulatif affiche Output token throughput (tok/s): et Total token throughput (tok/s):, puis Mean TTFT (ms):, Median TTFT (ms): et P99 TTFT (ms): sous l’intitulé Time to First Token.
La sortie n’indique pas le débit par flux, mais il suffit d’effectuer une division. Mean TPOT (ms): correspond au temps moyen par token de sortie après le premier. Ainsi, 25 ms par token correspondent à 40 tokens par seconde et par flux. Le débit de sortie divisé par la concurrence donne le même résultat.
Répétez ensuite l’opération en enregistrant chaque exécution.
for C in 1 4 16 32 64 128; do
vllm bench serve \
--backend openai-chat \
--base-url http://127.0.0.1:8000 \
--endpoint /v1/chat/completions \
--model Qwen/Qwen3-8B \
--dataset-name random \
--random-input-len 1024 \
--random-output-len 1024 \
--ignore-eos \
--num-prompts $(( C * 10 )) \
--max-concurrency "$C" \
--percentile-metrics ttft,tpot,itl,e2el \
--metric-percentiles 50,99 \
--save-result --result-filename "sweep-c$C.json"
doneLire les fichiers JSON enregistrés
Chaque exécution écrit un fichier. Extrayez donc en une seule fois les champs qui vous intéressent de tous les fichiers.
for f in sweep-c*.json; do
jq -r --arg f "$f" \
'[$f, .output_throughput, .total_token_throughput,
.median_ttft_ms, .p99_ttft_ms] | @tsv' "$f"
doneoutput_throughput correspond au nombre de tokens de sortie par seconde. total_token_throughput réintègre les tokens d’entrée. Avec un ratio de 1:1, sa valeur est donc proche du double. p99_ttft_ms existe uniquement parce que --metric-percentiles incluait 99. Si vous demandez un percentile qui n’a pas été demandé, jq affiche null.
Que montre réellement un test de montée en concurrence ?
Les données de ce graphique
[
{
"label": "1 stream",
"per_stream_tok_s": 92,
"total_tok_s": 92,
"ttft_p50_ms": 48,
"ttft_p99_ms": 61
},
{
"label": "4 streams",
"per_stream_tok_s": 88,
"total_tok_s": 352,
"ttft_p50_ms": 71,
"ttft_p99_ms": 96
},
{
"label": "16 streams",
"per_stream_tok_s": 71,
"total_tok_s": 1136,
"ttft_p50_ms": 152,
"ttft_p99_ms": 244
},
{
"label": "32 streams",
"per_stream_tok_s": 54,
"total_tok_s": 1728,
"ttft_p50_ms": 287,
"ttft_p99_ms": 498
},
{
"label": "64 streams",
"per_stream_tok_s": 34,
"total_tok_s": 2176,
"ttft_p50_ms": 611,
"ttft_p99_ms": 1240
},
{
"label": "128 streams",
"per_stream_tok_s": 18,
"total_tok_s": 2304,
"ttft_p50_ms": 1490,
"ttft_p99_ms": 3820
}
]Ces 6 lignes illustrent la forme produite par un test de montée en concurrence sur un petit serveur GPU loué, avec des ordres de grandeur plausibles. Elles ne correspondent pas à une mesure de votre serveur et ne proviennent pas d’un fournisseur. Exécutez la boucle ci-dessus et remplacez-les par vos propres valeurs.
Analysez la forme, car c’est elle qui se généralise. Avec un seul flux, le serveur produit au total 92 tokens par seconde, avec un TTFT p99 de 61 ms. À 128 streams, le débit total atteint 2304 tokens par seconde, soit vingt-cinq fois plus, tandis que chaque flux individuel tombe à 18 tokens par seconde et que le TTFT p99 atteint 3820 ms. Le débit total augmente parce que le batching transforme les attentes mémoire inactives en travail utile. La vitesse par flux diminue parce que les mêmes ressources de calcul sont désormais partagées.
Le dernier doublement est révélateur. Passer de 64 à 128 flux ajoute moins de six pour cent au débit total, tandis que le TTFT p99 est environ multiplié par trois. Cela signifie que les requêtes sont mises en file d’attente au lieu d’être exécutées, soit parce que le cache KV est plein, soit parce que le serveur a atteint une limite de scheduling telle que max_num_seqs de vLLM. Le point de fonctionnement utile se situe plus tôt : avec 32 flux, le serveur fournit encore 1728 tokens par seconde, soit 75 pour cent de son débit maximal, à 54 tokens par seconde et par flux, avec un TTFT p99 de 498 ms. Retenez ce point comme capacité. Le sommet de la courbe ne permet pas de servir les utilisateurs.
Ollama et vLLM ne mesurent pas la même chose
Lancez ce test avec un serveur Ollama par défaut : le total évoluera à peine. OLLAMA_NUM_PARALLEL vaut 1 par défaut. Une seule requête s’exécute donc à la fois, tandis que les autres attendent. C’est cette file d’attente qui fait augmenter le TTFT p99, alors que le volume total généré reste stable. Augmentez cette valeur avant toute mesure.
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_NUM_PARALLEL=8"Redémarrez avec sudo systemctl restart ollama, puis vérifiez que le modèle tient toujours. Chaque slot parallèle reçoit sa propre part de la fenêtre de contexte. La documentation d’Ollama indique donc qu’un contexte de 2K avec 4 requêtes parallèles alloue 8K. Si vous augmentez suffisamment le nombre de slots, le modèle déborde de la VRAM. Vérifiez ollama ps : une colonne PROCESSOR affichant une valeur telle que 48%/52% CPU/GPU indique qu’une partie du modèle se trouve sur le CPU. Le débit diminue alors lorsque vous augmentez la concurrence, au lieu d’augmenter. Au-delà du nombre de slots parallèles, les requêtes sont mises en file jusqu’à OLLAMA_MAX_QUEUE, qui vaut 512 par défaut. Ensuite, le serveur renvoie une erreur 503.
vLLM utilise le continuous batching. Il ajoute donc les nouvelles requêtes au batch en cours lorsque des slots se libèrent. Sa courbe continue d’augmenter jusqu’à épuisement d’une ressource, généralement le cache KV. Une limite configurée telle que max_num_seqs ou la surcharge liée à l’ordonnancement sur le CPU peut toutefois la stabiliser plus tôt. Ollama est optimisé pour un modèle, une machine et une configuration rapide. Les deux moteurs donnent donc des résultats différents pour le même test. C’est le véritable sujet de Comparaison d’Ollama et de vLLM comme moteurs de serving. Notez le moteur et la version qui ont produit chaque valeur.
Cinq façons de mesurer le mauvais indicateur
- Le client est distant. Effectuer le benchmark depuis votre ordinateur portable via Internet ajoute votre aller-retour réseau à chaque TTFT. Vous mesurez donc votre connexion domestique. Exécutez le client dans la même région que le serveur.
- Le modèle était froid. La première requête inclut le chargement des poids. Avec vLLM, elle peut aussi inclure la capture du graphe. Envoyez un lot de préchauffage, puis ignorez le résultat.
- Le prefix caching a répondu à votre place. vLLM active le prefix caching automatique par défaut. Envoyer plusieurs fois le même prompt mesure donc le cache au lieu du prefill, et le TTFT devient une fraction de sa valeur réelle.
--dataset-name randomévite ce problème, car chaque prompt est différent. Pour vérifier ce comportement, démarrez le serveur avec--no-enable-prefix-caching. - Les sorties étaient courtes. Avec des réponses de 32 tokens, le TTFT domine chaque requête et votre nombre de tokens par seconde décrit en réalité le prefill. Utilisez
--ignore-eosavec une longueur de sortie réaliste. - Vous n’avez indiqué que la concurrence 1. Elle donne le TTFT le plus favorable dans le tableau. Elle constitue la bonne donnée d’entrée pour le coût uniquement si vos requêtes réelles ne se chevauchent jamais. Si elles se chevauchent, le coût dépend du débit que vous maintenez avec la concurrence générée par votre trafic réel. Mesurez donc également à ce niveau.
Transformez votre mesure en décision
Prenez le débit de sortie mesuré par votre sweep au niveau de concurrence atteint par votre trafic réel, puis comparez-le au prix par token. Ce chiffre correspond au débit saturé uniquement si suffisamment de requêtes se chevauchent pour maintenir le batch à pleine capacité ; une charge qui envoie une seule requête à la fois doit utiliser le débit en flux unique. Le seuil de rentabilité se calcule par une simple division, après avoir intégré le prix des tokens d’entrée associés à chaque token de sortie :
cost_per_million_output = price_out + price_in * (input_tokens / output_tokens)
break_even_tok_s = (price_per_hour / cost_per_million_output) * 1000000 / 3600Faisons le calcul avec les tarifs de DigitalOcean de juillet 2026. Leur endpoint d’inférence dédié H200 coûtait $4.47 par heure, tandis que Llama 3.3 70B serverless coûtait $0.65 par million de tokens d’entrée et $0.65 par million de tokens de sortie. Avec le ratio 1:1 utilisé précédemment, chaque million de tokens de sortie s’accompagne d’un million de tokens d’entrée. Le prix effectif est donc de $1.30 par million de tokens de sortie. Ainsi, 4.47 divisé par 1.30 donne environ 3.44 millions de tokens de sortie par heure. En divisant par 3,600 secondes, on obtient environ 955 tokens de sortie par seconde, soit environ 1,910 tokens par seconde en comptant ensemble l’entrée et la sortie. Ces tarifs sont ceux de DigitalOcean. Le calcul est le nôtre. Lorsque nous avons vérifié le 10 octobre 2026, l’endpoint H200 coûtait toujours $4.47 par heure, mais DigitalOcean avait déprécié Llama 3.3 70B serverless le 18 août 2026 et orientait les utilisateurs vers Llama 4 Maverick, affiché à $0.25 par million de tokens d’entrée et $0.87 par million de tokens de sortie. Il s’agit d’un modèle différent. Il faut donc effectuer son propre sweep avant que la comparaison soit pertinente.
Le mot déterminant est soutenu. Atteindre 955 tokens de sortie par seconde en saturation pendant deux heures par jour ne signifie pas maintenir 955 tokens de sortie par seconde, car vous payez aussi les vingt-deux autres heures. Louer la carte sous forme de GPU Droplet ne réduit pas ce seuil : le 10 octobre 2026, les tarifs des Droplets de DigitalOcean indiquaient le H200 à $4.47 par heure à la demande, soit le même prix que l’endpoint. Le moins cher de leurs NVIDIA Droplets, le RTX 4000 à $0.76 par heure, ainsi que le L40S ou le RTX 6000 à $1.57 par heure, sont trop petits pour héberger un modèle 70B en FP8. Avec les chiffres précédents, un H200 qui atteint 2,036 tokens de sortie par seconde en saturation doit donc atteindre en moyenne environ 47 pour cent de ce débit maximal pendant chaque heure facturée, simplement pour égaler le tarif de $0.65 par million de tokens en entrée et en sortie. En dessous de ce niveau, le prix par token est plus avantageux. Ce sont généralement les heures GPU inutilisées, et non les tokens générés lentement, qui compromettent l’auto-hébergement.
Votre décision repose donc sur deux données. Le sweep vous donne le plafond. Votre profil de trafic vous donne la part de ce plafond que vous utilisez réellement. Multipliez-les, puis utilisez le résultat pour comparer le GPU VPS au seuil de rentabilité d’une API facturée au token et déterminer le résultat correspondant à votre volume.
FAQ
Quel est un bon nombre de tokens par seconde pour un LLM auto-hébergé ?
Il y a deux réponses, car cette métrique sert à deux usages. Pour une personne qui lit la sortie, plus d’environ 20 tokens de sortie par seconde et par flux dépasse déjà la vitesse de lecture. Aller plus vite n’apporte donc rien. Pour le coût, le nombre important est le débit total de sortie que vous pouvez maintenir avec votre charge réelle. Un bon débit est celui qui vous permet d’atteindre votre seuil de rentabilité. Avec un tarif de $0.65 par million de tokens, en entrée comme en sortie, sur une machine facturée $4.47 par heure, ce seuil se situe autour de 955 tokens de sortie par seconde maintenus, avec un ratio de 1:1 et les tarifs de juillet 2026. Un seul flux sur un modèle volumineux ne l’atteint jamais. C’est la raison d’être du batching.
Pourquoi le débit de mon serveur Ollama reste-t-il stable lorsque j’ajoute des requêtes concurrentes ?
OLLAMA_NUM_PARALLEL vaut 1 par défaut. Le serveur traite donc une requête à la fois par modèle et place les autres en file d’attente, jusqu’à OLLAMA_MAX_QUEUE (512 par défaut), avant de renvoyer une erreur 503. Le débit total de sortie reste stable tandis que le TTFT p99 augmente. C’est le signe d’une file d’attente, et non d’un GPU saturé. Définissez la variable dans un drop-in systemd, puis redémarrez le service. Vérifiez ensuite ollama ps, car chaque slot parallèle multiplie le contexte alloué et peut placer une partie du modèle sur le CPU.
Dois-je mesurer le délai avant le premier token ou le nombre de tokens par seconde ?
Les deux, car ces valeurs évoluent en sens inverse lorsque la charge augmente. Le TTFT correspond à ce que ressent l’utilisateur, tandis que le débit de sortie saturé détermine ce qui apparaît sur votre facture. Relevez le TTFT p50 et p99 à chaque niveau de concurrence, puis choisissez le niveau le plus élevé pour lequel le TTFT p99 reste acceptable. Indiquez le débit obtenu à ce niveau comme votre capacité, et non le maximum situé au sommet de la courbe.
Un nombre plus élevé de tokens par seconde signifie-t-il toujours un coût inférieur par token ?
Non. Le coût par token correspond au prix horaire divisé par le nombre de tokens effectivement produits par la machine pendant cette heure. Un serveur rapide qui reste inactif la majeure partie de la journée conserve donc un coût par token élevé. C’est le taux d’utilisation qui le détermine, et non la vitesse maximale. Vérifiez également les unités. Un débit total de tokens annoncé inclut les tokens d’entrée. Avec un ratio entrée-sortie de 1:1, il est donc proche du double du débit de sortie. Si l’API à laquelle vous le comparez facture aussi les tokens d’entrée, calculez le prix des deux catégories avant de comparer.