Tout le carnet
Architecture IA18 min

vLLM transforme la mémoire GPU en système d'exploitation pour LLM

Une API de génération peut recevoir trois requêtes en même temps : un chat court de 600 tokens, un résumé juridique de 18 000 tokens, et une extraction structurée qui demande peu de sortie mais impose un...

Quand la carte calcule encore mais que la latence décroche

Une API de génération peut recevoir trois requêtes en même temps : un chat court de 600 tokens, un résumé juridique de 18 000 tokens, et une extraction structurée qui demande peu de sortie mais impose un long contexte. Le GPU n’est pas forcément saturé en calcul. Pourtant le premier utilisateur attend, le résumé occupe la mémoire, l’extraction bloque dans la file, puis le p95 de latence explose sans que le serveur HTTP soit le vrai coupable.

Vue éditoriale du système : vLLM transforme la mémoire GPU en système d'exploitation pour LLM
Vue éditoriale du système : vLLM transforme la mémoire GPU en système d'exploitation pour LLM

La thèse est simple : vLLM accélère l’inférence parce qu’il déplace le problème du « modèle lent » vers un noyau de gestion mémoire. Le service LLM performant n’est pas seulement celui qui charge un bon modèle sur une bonne carte. C’est celui qui ordonnance les tokens, les pages de KV cache et la file d’attente comme un mini système d’exploitation GPU.

Appelons ce modèle le noyau KV.

Le lecteur repart avec une grille de décision : reconnaître quand la lenteur vient du prefill, du decode, de la pression mémoire ou de l’admission ; savoir quoi mesurer avant d’ajouter des GPU ; décider quand accepter, retarder, réduire, router ou refuser une requête. La carte mentale tient en trois primitives : pages mémoire, scheduler de tokens, contrôle de pression.

La fausse croyance coûte cher : croire que le goulot d’étranglement principal est le modèle, le serveur HTTP ou le nombre de GPU. Ces éléments comptent, mais ils masquent souvent la cause réelle de la dégradation : la mémoire vivante du KV cache, sa fragmentation, et la manière dont les phases prefill et decode se concurrencent.

Un LLM autoregressif ne répond pas comme une fonction classique. Il lit d’abord le prompt, puis génère la suite token par token. À chaque étape, il conserve des états d’attention pour ne pas recalculer tout l’historique. Ces états sont les clés et valeurs du mécanisme d’attention : le KV cache. Plus le contexte est long, plus cette mémoire grossit. Plus les sorties durent, plus elle reste occupée. Plus les requêtes sont hétérogènes, plus l’ordonnancement devient difficile.

Le paradoxe vient de là : un GPU peut encore avoir du calcul disponible, mais ne plus pouvoir servir correctement parce que le système ne sait plus placer, réutiliser ou libérer la mémoire utile au bon moment. Le problème n’est plus seulement « combien de FLOPS ? », mais « quelles séquences avancent maintenant, avec quels blocs KV, sous quel budget de latence ? ».

Le cache KV est une mémoire vivante, pas un détail CUDA

Le cache KV est souvent présenté comme une optimisation interne. C’est une erreur de niveau d’abstraction. En production, il devient une ressource de premier rang, au même titre qu’une connexion base de données, une partition Kafka ou un pool de workers.

Trois définitions suffisent pour poser le modèle.

Le prefill est la phase où le modèle traite le prompt d’entrée. Si le prompt contient 12 000 tokens, le système doit encoder cet historique avant de produire le premier token visible. Cette phase influence fortement le TTFT, le time_to_first_token.

Le decode est la phase où le modèle génère la réponse, token après token. Chaque nouveau token consulte l’historique déjà stocké dans le KV cache. Cette phase influence le TPOT, le temps par token de sortie, et donc la fluidité du streaming.

Le KV cache est la mémoire GPU qui garde les états nécessaires à cette génération. Il grossit avec les tokens actifs : tokens du prompt, tokens générés, séquences concurrentes. Il n’est pas seulement grand ; il est dynamique.

Cette distinction change la manière de diagnostiquer une panne. Une requête avec un prompt très long mais une sortie courte peut avoir un mauvais TTFT et un TPOT acceptable. Une requête avec un prompt court mais une sortie de 2 000 tokens peut avoir un bon TTFT puis dégrader le decode de tout le batch. Un trafic multi-tenant peut afficher un débit global correct tout en offrant une expérience médiocre à un tenant prioritaire.

La métrique globale « tokens par seconde » ment facilement. Elle additionne des situations différentes : attente en file, prefill massif, decode fluide, batch hétérogène, annulations, timeouts. Pour piloter un noyau KV, il faut séparer les phases.

Le noyau KV répond en continu à cinq questions :

  1. Une requête peut-elle entrer sans dépasser le budget mémoire ?
  2. Combien de tokens de prompt peut-on traiter maintenant ?
  3. Quelles séquences en decode doivent avancer au prochain pas ?
  4. Quels blocs KV sont réutilisables, partagés ou libérables ?
  5. Quelle requête doit être retardée ou refusée avant de dégrader tout le service ?

Sans cette boucle, l’équipe finit par traiter les symptômes. Elle réduit la fenêtre de contexte sans savoir si le prefill était le problème. Elle ajoute un worker alors que le batch était mal rempli. Elle change de modèle alors que la fragmentation KV empêchait déjà les requêtes courtes d’entrer. Elle regarde la VRAM totale, mais pas la mémoire KV par token actif.

La bonne unité mentale n’est donc pas la requête HTTP. C’est la séquence active : une suite de tokens qui occupe des blocs, traverse prefill puis decode, avance dans un batch vivant, et finit par libérer sa mémoire.

La panne silencieuse : batch rempli, pages vides, utilisateurs en attente

Le mode de panne classique commence avec une décision raisonnable en apparence : réserver assez de mémoire pour chaque requête selon son maximum possible. Si une requête autorise max_tokens: 2000, le serveur naïf protège ce pire cas. Sur le papier, c’est prudent. En trafic réel, c’est du gaspillage.

Scène du mode de panne : vLLM transforme la mémoire GPU en système d'exploitation pour LLM
Scène du mode de panne : vLLM transforme la mémoire GPU en système d'exploitation pour LLM

Une requête peut demander 2 000 tokens et s’arrêter après 140. Si la mémoire a été réservée comme si les 2 000 tokens allaient forcément exister, 1 860 tokens de capacité ont été immobilisés pour rien. Cette mémoire n’est pas utile à l’utilisateur, mais elle n’est pas disponible pour les autres. Le système paraît plein avant de l’être réellement.

La deuxième panne est la fragmentation. Quand des séquences courtes se terminent et que des séquences longues continuent, la mémoire disponible n’est pas forcément utilisable sous la forme attendue. Il reste des espaces, mais pas toujours au bon endroit ni dans la bonne granularité. Une métrique qui affiche « mémoire libre » peut donner une impression de marge alors que le scheduler ne peut pas placer une nouvelle séquence longue sans risque.

La troisième panne vient du batch statique. Pour exploiter un GPU, il faut grouper les requêtes. Mais si le batch reste figé, les requêtes courtes terminent vite et les longues continuent seules. Les slots libérés ne sont pas immédiatement remplacés. Le GPU peut calculer, mais il calcule moins de travail utile que possible.

Le quatrième piège est le mélange brutal de prefill et de decode. Un long prompt peut monopoliser une partie du système pendant que des requêtes interactives attendent leur prochain token. Pour l’utilisateur en streaming, cette attente est visible : la réponse a commencé, puis le rythme devient irrégulier. Le produit semble instable, même si le débit moyen reste présentable.

Le scénario typique ressemble à ceci :

T0  Trois chats courts entrent, un résumé long arrive, une extraction attend.
T1  Le long résumé lance un prefill coûteux.
T2  Les chats courts terminent leur prefill mais attendent le decode.
T3  Le KV cache grossit avec le résumé et les sorties déjà ouvertes.
T4  Des blocs sont libres, mais dispersés ou insuffisants pour le prochain contexte.
T5  Le TTFT des nouvelles requêtes grimpe, puis les timeouts commencent.

Ce qui casse n’est pas une seule ressource. C’est la coordination entre mémoire, batch et file. Une approche simple échoue parce qu’elle traite chaque requête comme un objet isolé, alors que l’inférence est une compétition permanente entre séquences vivantes.

La vraie question de production devient : faut-il accepter cette requête maintenant ? Une API qui répond toujours 200 OK jusqu’au moment où le GPU tombe en OOM n’est pas robuste. Elle a seulement déplacé la décision de rejet vers le pire endroit possible : au cœur de l’exécution.

PagedAttention transforme l’historique en blocs réutilisables

PagedAttention est souvent résumé comme une optimisation d’attention. Le gain architectural est plus profond : appliquer une idée de pagination au KV cache. Au lieu d’exiger une grande zone contiguë pour chaque séquence, le système découpe la mémoire en blocs et maintient une table qui relie l’ordre logique des tokens aux blocs physiques en GPU.

La séquence reste continue pour le modèle. La mémoire physique, elle, peut être dispersée.

Séquence logique A
tokens:  [0..15] [16..31] [32..47] [48..63]

Blocs KV physiques
A -> bloc 7, bloc 2, bloc 19, bloc 4

Cette séparation entre vue logique et placement physique est le cœur du noyau KV. Elle réduit le gaspillage car une requête n’a pas besoin de réserver toute sa sortie maximale dès l’admission. Elle reçoit des blocs à mesure que ses tokens existent. Si elle s’arrête tôt, les blocs non utilisés n’ont jamais été pris.

Elle réduit aussi l’impact de la fragmentation. Un bloc libre peut servir à une autre séquence, même si les blocs précédents de cette séquence sont ailleurs. Le système ne cherche plus un grand segment parfait ; il assemble une continuité logique à partir de morceaux physiques.

Le mécanisme interne peut se lire étape par étape.

Entrée. Une requête arrive avec un prompt, une limite de sortie, une priorité et un tenant. Le gateway vérifie les limites produit : taille maximale, quota, classe de trafic, modèle autorisé.

Transformation. Le tokenizer convertit le prompt en tokens. Le scheduler estime le coût immédiat : tokens de prefill, budget KV initial, place dans la file, priorité relative.

État gardé en mémoire. Pendant le prefill, le système crée les clés et valeurs d’attention pour les tokens du prompt. Ces états sont rangés dans des blocs KV. Une table associe chaque segment logique de la séquence aux blocs physiques alloués.

Sortie. Le decode produit un token, puis un autre. À chaque token, le KV cache s’enrichit. Si la séquence termine, ses blocs deviennent libérables. Si elle partage un préfixe déjà calculé, certains blocs peuvent être référencés plutôt que recalculés.

Effet sur coût et latence. Le GPU sert davantage de séquences utiles avec la même VRAM, le batch peut rester plus dense, et la file attend moins souvent une grande zone mémoire parfaite. Le coût par token utile baisse parce que moins de capacité est immobilisée dans du vide réservé.

Il faut rester précis : PagedAttention ne rend pas gratuit un long contexte. Chaque token actif a un coût mémoire. La pagination améliore l’utilisation et le partage ; elle ne supprime pas la physique du modèle. Un prompt inutilement long reste un prompt inutilement long. Une sortie sans limite reste une sortie dangereuse.

La bonne analogie n’est donc pas « vLLM rend tout plus rapide ». La bonne analogie est : vLLM donne au serveur d’inférence une couche de mémoire virtuelle spécialisée pour les séquences LLM. Cette couche permet ensuite au scheduler de prendre de meilleures décisions.

Le scheduler devient le noyau backend de l’inférence

Dès que la mémoire est découpée en pages, le vrai produit backend devient le scheduler. Il ne s’agit plus seulement de recevoir une requête et d’appeler generate(). Il faut décider qui entre, qui attend, qui avance, qui partage, qui libère, qui dépasse son budget.

Le batching continu change le contrat. Un batch n’est plus une photo prise au début d’un traitement. C’est un objet vivant. À chaque étape de decode, certaines séquences ont terminé, d’autres attendent, d’autres peuvent entrer. Le scheduler remplit les slots au fil de l’eau.

Étape decode    Slot 1     Slot 2     Slot 3
1               chat-A     chat-B     résumé-C
2               chat-A     chat-B     résumé-C
3               fini       chat-B     résumé-C
4               extract-D  fini       résumé-C
5               extract-D  chat-E     résumé-C

Cette dynamique améliore l’utilisation GPU, mais elle introduit une responsabilité : le scheduler peut favoriser le débit au détriment de la latence interactive, ou protéger les chats courts au détriment des jobs longs. Il n’existe pas de réglage neutre. Toute politique de batch est une politique produit.

C’est particulièrement visible dans les applications multi-tenant. Un tenant qui envoie beaucoup de longs contextes peut occuper le prefill et consommer du KV cache, pendant qu’un autre tenant subit un TTFT élevé sur des requêtes courtes. Si la métrique n’est qu’un p95 global, le problème reste invisible. Le noyau KV doit exposer la latence par tenant, par classe de trafic et par taille de prompt.

Une politique d’admission minimale devrait lire au moins :

{
  "request_id": "req_7f42",
  "tenant_id": "acme",
  "task_type": "structured_extraction",
  "prompt_tokens": 18400,
  "max_tokens": 700,
  "priority": "normal",
  "stream": true,
  "deadline_ms": 8000
}

Ce payload contient les informations que le scheduler ne peut pas deviner. prompt_tokens indique le coût de prefill. max_tokens borne la croissance potentielle du KV cache. priority donne une politique explicite. tenant_id permet d’appliquer des quotas. deadline_ms évite de servir trop tard une réponse qui n’a plus de valeur produit.

La requête suit alors un chemin concret :

  1. Admission. Le gateway refuse immédiatement si prompt_tokens dépasse la limite du tenant ou du modèle. Il peut aussi réduire le contexte via une étape de compression contrôlée, mais seulement si le produit l’autorise.
  2. Mise en file. Le router classe la requête : chat court, résumé long, extraction structurée, job batch. Chaque classe porte un budget de TTFT, de TPOT et de mémoire.
  3. Prefill. Le scheduler décide si le prompt peut être traité en une fois ou par morceaux. Un long prefill peut être découpé pour ne pas bloquer le decode des séquences déjà ouvertes.
  4. Allocation KV. Les blocs sont alloués progressivement. La table de blocs suit la séquence logique.
  5. Decode. La réponse sort token par token. Le batch continu réinsère de nouvelles séquences quand des slots se libèrent.
  6. Libération. À la fin, les blocs KV sont relâchés. Une annulation client doit produire la même libération qu’une fin normale.
  7. Émission des métriques. Le système publie TTFT, TPOT, queue time, tokens générés, blocs KV utilisés, statut final et raison de rejet éventuelle.

Ce chemin montre pourquoi le scheduler n’est pas un détail d’infrastructure. Il porte des décisions de produit : latence interactive, équité entre tenants, coût par token, priorité des tâches, forme des refus.

Trois files dans une seule latence : admission, prefill, decode

Quand un utilisateur dit « c’est lent », il décrit une expérience unique. Le système, lui, doit la découper en trois files.

La première est la file d’admission. Elle décide si la requête a le droit d’entrer. Sans admission control, les requêtes impossibles atteignent le GPU, consomment du temps de scheduling et échouent trop tard. Une bonne admission refuse avant d’allouer.

La deuxième est la file de prefill. Elle porte le coût du prompt. Les longs contextes, les documents complets, les conversations agentiques et les instructions répétées se manifestent ici. Si le TTFT grimpe avec la taille du prompt, il faut regarder cette file avant d’accuser le decode.

La troisième est la file de decode. Elle porte le coût de la génération token par token. Si le premier token arrive vite mais que le streaming devient irrégulier, le problème se situe souvent dans le batch decode, la pression KV ou l’hétérogénéité des sorties.

Le batching continu relie ces files, mais ne les fusionne pas magiquement. Un système mature garde une visibilité séparée. Sinon, l’équipe optimise le mauvais étage.

Prenons les trois profils de trafic :

chat_court
  prompt_tokens: 650
  max_tokens: 300
  priority: high

resume_long
  prompt_tokens: 22000
  max_tokens: 1200
  priority: normal

extraction_structuree
  prompt_tokens: 9000
  max_tokens: 250
  priority: normal

Le chat court doit protéger son TTFT. Le résumé long peut tolérer plus de queue, mais il ne doit pas monopoliser le prefill. L’extraction structurée a un contexte assez lourd, mais une sortie bornée ; elle peut être rentable si l’admission est stricte et si le decode reste court.

Une approche naïve les traite comme trois requêtes /generate. Le noyau KV les traite comme trois profils de coût.

Le résumé long peut être placé dans une classe moins prioritaire ou routé vers un pool batch. Son prefill peut être fragmenté pour ne pas interrompre trop longtemps le decode interactif. Le chat court peut être admis rapidement avec une limite stricte sur max_tokens. L’extraction peut être acceptée si le budget KV actif le permet, ou retardée si la pression mémoire dépasse un seuil.

Le point décisif : l’architecture ne cherche pas seulement à maximiser le débit. Elle cherche à préserver une zone de fonctionnement. Dans cette zone, le TTFT reste dans le budget, le TPOT ne tremble pas, la file ne s’accumule pas sans limite, les OOM restent absents, les refus sont explicites.

Le noyau KV doit donc devenir une boucle de contrôle :

mesurer -> décider -> exécuter -> libérer -> vérifier

Mesurer la pression KV, la latence par phase, les tokens actifs, la fragmentation, les requêtes en attente. Décider l’admission, le chunking, la priorité, le routage ou le refus. Exécuter avec PagedAttention et batching continu. Libérer à chaque fin, erreur ou annulation. Vérifier que la distribution réelle respecte les budgets de production.

Une boucle sans rejet n’est pas une boucle de contrôle. C’est une file d’attente optimiste.

Le contrat de production du noyau KV

Le contrat de production du noyau KV doit être écrit avant le benchmark. Sinon, le benchmark ne mesure qu’une impression.

Vue de la décision d'architecture : vLLM transforme la mémoire GPU en système d'exploitation pour LLM
Vue de la décision d'architecture : vLLM transforme la mémoire GPU en système d'exploitation pour LLM

Un contrat utile ne dit pas seulement « utiliser vLLM ». Il fixe les budgets qui rendent l’inférence exploitable :

service: generation-api
model_class: instruct-llm
traffic_classes:
  chat_short:
    max_prompt_tokens: 2000
    max_output_tokens: 500
    priority: high
    p95_ttft_ms: 900
    p95_tpot_ms: 80
  long_summary:
    max_prompt_tokens: 24000
    max_output_tokens: 1500
    priority: normal
    queue_policy: delay_or_route
    p95_ttft_ms: 5000
  structured_extraction:
    max_prompt_tokens: 12000
    max_output_tokens: 400
    priority: normal
    p95_ttft_ms: 2500
guards:
  reject_without_tenant_id: true
  reject_if_prompt_exceeds_class_budget: true
  reject_if_kv_pressure_above_percent: 92
  cancel_releases_kv_blocks: required
observability:
  emit_per_request:
    - time_to_first_token
    - time_per_output_token
    - queue_time
    - prompt_tokens
    - generated_tokens
    - kv_blocks_allocated
    - finish_reason
    - rejection_reason

Ce contrat force une conversation saine. Le backend ne promet pas seulement une route disponible. Il promet un comportement sous contrainte : limites de tokens, classes de trafic, priorités, métriques, refus.

La matrice de décision devient alors l’outil quotidien.

Symptôme observéMétrique à lireCause probableAction vLLMCritère de rejet
TTFT élevétime_to_first_token par taille de promptprefill saturéchunked prefill / admission controlprompt trop long hors budget
TPOT instabletokens_per_second par batchdecode mal ordonnancécontinuous batchingbatch trop hétérogène
OOM intermittentKV cache utilisé / blocs libresfragmentation ou contexte trop largePagedAttention / limites de tokensrequête non admissible
Latence multi-tenantlatence par tenantabsence de quotaspriorités / budgetstenant throttlé

Cette matrice évite deux erreurs symétriques. La première consiste à juger vLLM sur un seul débit global. La seconde consiste à attendre de vLLM une correction de problèmes qui relèvent de l’API produit : quotas absents, prompts illimités, tenants non isolés, jobs batch mélangés avec des interactions temps réel.

Les tests doivent donc refléter la production. Un benchmark avec prompts homogènes est trop confortable si le produit mélange conversations courtes, longs contextes et sorties longues. Il faut rejouer une distribution de charge : tailles de prompt variées, sorties bornées et non bornées, bursts, annulations, timeouts client, préfixes répétés, tenants concurrents.

Les critères de vérification doivent être stricts :

  • refuser une conclusion basée seulement sur le débit global ;
  • mesurer TTFT et TPOT séparément ;
  • lire p95 et p99, pas seulement la moyenne ;
  • suivre les OOM, timeouts, annulations et raisons de rejet ;
  • mesurer la pression KV par token actif ;
  • vérifier que l’annulation libère réellement les blocs ;
  • tester les limites max_tokens avec des sorties qui atteignent le plafond ;
  • comparer la latence par classe de trafic et par tenant.

Le point le plus souvent négligé est la libération. Une requête qui se termine normalement, échoue, expire ou est annulée doit rendre ses blocs KV observables comme libérés. Sans cela, le système peut réussir les tests courts et se dégrader lentement sous trafic réel.

Quand vLLM doit refuser plutôt que compenser

vLLM améliore fortement l’utilisation mémoire et l’ordonnancement, mais il ne rend pas acceptable une architecture sans discipline de contexte. Le noyau KV peut mieux placer les blocs ; il ne peut pas rendre gratuite une requête qui transporte trop d’historique inutile.

Il y a des cas où la bonne décision n’est pas d’ajuster le scheduler, mais de changer le produit.

Si les prompts contiennent systématiquement des documents entiers alors que seules quelques sections sont utiles, le problème est en amont : retrieval, compression, sélection de contexte, découpage documentaire. vLLM servira mieux ce trafic, mais le coût restera mauvais.

Si le modèle est trop gros pour la carte, la pagination ne crée pas de VRAM magique. Il faut quantifier, sharder, choisir un modèle plus petit, réduire la fenêtre de contexte ou changer de stratégie de routage.

Si les tenants n’ont pas de quotas, le scheduler devient arbitre d’une politique qui n’existe pas. Il pourra prioriser techniquement, mais pas décider équitablement ce que le produit n’a pas défini.

Si les sorties ne sont pas bornées, le KV cache peut grossir jusqu’à mettre en danger tout le service. max_tokens n’est pas un détail UX ; c’est une limite de sécurité système.

Si les tests ignorent les annulations client, la production révélera une panne invisible : mémoire retenue après abandon, files qui montent, latence qui dérive.

Le critère de maturité est simple : une requête refusée tôt avec une raison claire vaut mieux qu’une requête acceptée qui ralentit tout le monde. Le refus fait partie du contrat.

Le noyau KV doit donc posséder quatre décisions :

ACCEPTER  -> la requête entre dans le budget courant
RETARDER  -> la requête reste en file sans casser le p95 prioritaire
RÉDUIRE   -> le contexte est compressé ou borné selon une règle produit
ROUTER    -> la requête part vers un autre modèle, pool ou mode batch
REFUSER   -> la requête dépasse le contrat et ne doit pas atteindre le GPU

Cette échelle est plus utile qu’un débat abstrait sur la rapidité du moteur. Elle donne aux équipes backend et IA un langage commun. Le modèle n’est pas seul responsable. Le prompt n’est pas seul responsable. Le scheduler n’est pas seul responsable. La boucle complète possède la qualité de service.

La décision finale appartient à la boucle, pas au benchmark

Un bon benchmark vLLM ne cherche pas la plus grande courbe de tokens par seconde. Il cherche la frontière de stabilité. À partir de quelle pression KV le TTFT se dégrade-t-il ? À partir de quelle hétérogénéité le TPOT devient-il instable ? À partir de quelle taille de prompt le chunked prefill devient-il nécessaire ? À partir de quel tenant faut-il appliquer un quota ?

La réponse de production doit tenir dans une boucle observable :

requête annotée
  -> admission par budget
  -> scheduling prefill/decode
  -> allocation de blocs KV
  -> génération token par token
  -> libération garantie
  -> métriques par phase
  -> ajustement des limites

Ce système doit être possédé explicitement. Le backend possède l’admission, les quotas, les statuts d’erreur, la télémétrie et la libération. L’équipe IA possède le choix du modèle, la taille de contexte utile, les formats de prompt et les stratégies de réduction. Le produit possède les classes de trafic, les priorités et les compromis acceptables.

La fermeture d’ingénierie tient en quelques refus nets.

Ne pas valider un serveur parce que la moyenne de latence est bonne si le p99 cache des prompts longs bloquants. Ne pas accepter un test où toutes les requêtes ont la même taille si la production mélange chat, résumé et extraction. Ne pas augmenter max_model_len sans budget par tenant. Ne pas confondre VRAM libre et capacité KV utilisable. Ne pas laisser une annulation client sans trace de libération. Ne pas router tous les workloads vers le même pool si leurs promesses de latence sont incompatibles.

vLLM est puissant précisément parce qu’il rend visible une vérité que beaucoup de services LLM découvrent trop tard : l’inférence n’est pas seulement une exécution de modèle. C’est une discipline d’ordonnancement sous mémoire contrainte.

Le noyau KV donne un système réutilisable : pages mémoire pour réduire le gaspillage, scheduler pour garder le GPU utile, contrôle de pression pour décider avant la panne. Une fois cette boucle en place, la décision devient plus simple. Si le TTFT vient du prefill, on agit sur le contexte, le chunking ou l’admission. Si le TPOT vient du decode, on agit sur le batch et la pression KV. Si la latence vient des tenants, on agit sur les quotas. Si la requête dépasse le contrat, on refuse.

C’est ce qui distingue un serveur LLM qui fonctionne en démonstration d’un service d’inférence exploitable : il sait non seulement générer, mais aussi dire non au bon moment, avec les bonnes métriques, avant que la mémoire GPU ne devienne le point de panne silencieux du produit.