Optimisation inférence LLM
Les techniques concrètes qui déterminent le coût et la latence réels d'un service LLM en production : gestion du KV cache, batching continu, quantization INT8/FP8 et choix entre TensorRT-LLM et vLLM.
Table des matières
Pourquoi l'inférence LLM est un problème d'ingénierie à part entière
Un modèle qui s'entraîne bien ne s'exécute pas forcément bien en production. L'entraînement est un problème de débit brut : on maximise le nombre de tokenstokenIAFragment de texte manipulé par un modèle de langage, généralement plus court qu'un mot — trois à quatre caractères en français. La tarification et la limite de contexte se comptent en tokens.Voir dans le glossaire traités par seconde sur un cluster, sans contrainte de latence individuelle par requête. L'inférenceinférenceIAUtilisation d'un modèle déjà entraîné sur une donnée nouvelle. Peu coûteuse à l'unité mais répétée à chaque requête, elle constitue le coût récurrent d'exploitation.Voir dans le glossaire, elle, doit répondre à des utilisateurs réels avec un budget de temps serré, tout en absorbant une charge de requêtes hétérogènes en longueur et en horaire d'arrivée. Ce chapitre suppose que vous maîtrisez déjà l'architecture TransformerTransformerIAArchitecture introduite en 2017, fondée sur le mécanisme d'attention, qui traite une séquence entière en parallèle. Elle sert de base à tous les grands modèles de langage actuels.Voir dans le glossaire et l'exécution CUDAGPUIAProcesseur graphique parallélisant massivement les calculs matriciels ; indispensable à l'entraînement et à l'inférence des modèles de deep learning.Voir dans le glossaire de base ; il s'attaque spécifiquement aux techniques qui rendent le service d'un LLMLLMIAGrand modèle de langage (Large Language Model) entraîné sur d'énormes corpus pour prédire et générer du texte.Voir dans le glossaire économiquement viable à l'échelle.
L'inférence autoregressive se décompose en deux phases au profil très différent. La phase de prefill traite le promptpromptIAConsigne ou contexte fourni à un modèle de langage pour orienter sa réponse. La qualité du prompt conditionne souvent la qualité du résultat.Voir dans le glossaire entier en une seule passe parallèle : elle est compute-bound, le GPU calcule à pleine charge et la bande passante mémoire n'est pas le facteur limitant. La phase de decode génère les tokens un par un, chaque itération ne recalculant qu'un vecteur par couche pour une seule position : elle est memory-bound, dominée par la lecture des poids et du cache d'attentionattentionIAMécanisme par lequel un modèle pondère l'importance de chaque token du contexte lorsqu'il en traite un autre, quelle que soit la distance qui les sépare.Voir dans le glossaire depuis la HBM plutôt que par la puissance de calcul brute. Cette distinction structure tout ce chapitre : la plupart des optimisations qui suivent ciblent spécifiquement la phase de decode, parce que c'est elle qui détermine le coût réel par token en service continu.
Deux métriques irréconciliables : latence et débit
Un service d'inférence se pilote avec trois métriques, pas une seule.
Le TTFT (time to first token) mesure le temps entre l'envoi du prompt et l'apparition du premier token généré — il dépend surtout de la phase de prefill et de la longueur du prompt. La latence inter-token (inter-token latency, ITL) mesure l'écart entre deux tokens consécutifs pendant le decode — c'est elle qui détermine si un flux de génération paraît fluide ou saccadé à l'utilisateur. Le débit (throughput), en tokens par seconde agrégés sur l'ensemble des requêtes actives, mesure l'efficacité économique du GPU.
Le problème : augmenter le débit et réduire la latence par requête sont des objectifs structurellement opposés. Un plus grand batch de requêtes simultanées améliore l'utilisation du GPU pendant le decode — plus de travail utile par octet de poids lu depuis la HBM — mais chaque requête individuelle attend plus longtemps son tour de calcul, donc la latence inter-token par requête augmente. À l'inverse, traiter chaque requête isolément minimise sa latence mais gaspille l'essentiel de la bande passante mémoire disponible, puisque le GPU relit les mêmes poids pour chaque token sans amortir ce coût sur plusieurs requêtes.
Il n'existe pas de configuration unique optimale. Un chatbot interactif priorise un TTFT et un ITL bas, quitte à sacrifier du débit global. Un pipeline de traitement par lots (résumé de documents en masse, génération de donnéesdonnéesIAEnsemble d'informations structurées ou non utilisées pour entraîner, évaluer ou alimenter un modèle. La qualité, la quantité et la représentativité des données sont les facteurs décisifs pour les performances en apprentissage automatique.Voir dans le glossaire synthétiques) priorise le débit, la latence individuelle n'ayant aucune valeur d'usage. Toute décision de dimensionnement doit partir du SLA réel, pas d'un objectif générique de « GPU le plus rapide possible ».
Le goulot d'étranglement mémoire : le KV cache
Pour éviter de recalculer les projections clé et valeur de tous les tokens précédents à chaque nouveau token généré, chaque couche d'attention conserve en mémoire GPU les tenseurs clé et valeur déjà calculés : c'est le KV cache. Sa taille croît linéairement avec la longueur de séquence et avec le nombre de requêtes actives simultanément, selon approximativement :
taille_kv ≈ 2 × n_couches × n_têtes_kv × dim_tête × longueur_séquence × taille_batch × octets_par_valeur
Pour un modèle de la classe 70 milliards de paramètres avec attention multi-requête groupée (GQA), un contextefenêtre de contexteIAQuantité de texte qu'un modèle peut prendre en compte simultanément : question, documents fournis et historique. Au-delà, les éléments les plus anciens sortent du champ.Voir dans le glossaire de 8k tokens et un batch de 32 requêtes en FP16, le KV cache seul peut représenter plusieurs dizaines de gigaoctets — souvent plus que les poids du modèle eux-mêmes une fois le contexte long et le batch large. C'est ce cache, pas les poids statiques, qui domine généralement la pression mémoire d'un service d'inférence à l'échelle.
Le KV cache grossit dynamiquement à chaque token généré et par requête active. Une estimation de capacité basée uniquement sur la taille des poids conduit systématiquement à un sous-dimensionnement mémoire en production, avec des OOM (out of memory) imprévisibles dès que plusieurs requêtes à contexte long se chevauchent. Le dimensionnement correct part de la longueur de contexte maximale annoncée aux utilisateurs, multipliée par le nombre de requêtes concurrentes visées.
Attention paginée et kernels fusionnés
Deux idées indépendantes réduisent le coût du KV cache et de l'attention elle-même.
FlashAttention fusionne le calcul de l'attention en un seul kernel qui ne matérialise jamais la matrice d'attention complète en HBM. Le calcul est tuilé en blocs qui tiennent dans la mémoire SRAM très rapide du multiprocesseur streaming, avec une reformulation numériquement stable (online softmax) qui évite d'avoir à stocker les scores intermédiaires. Le gain n'est pas dans la réduction du nombre d'opérations arithmétiques, mais dans la réduction drastique des lectures et écritures HBM, qui sont le facteur limitant réel en phase de decode.
PagedAttention, popularisée par vLLM, traite le KV cache non plus comme un tenseur contigu par requête, mais comme une mémoire paginée en blocs de taille fixe, gérée par une table d'indirection — directement inspirée de la pagination mémoire des systèmes d'exploitation. Sans pagination, réserver un buffer contigu au pire cas (longueur de contexte maximale) pour chaque requête gaspille énormément de mémoire dès que les requêtes ont des longueurs réelles très inférieures au maximum. Avec pagination, chaque requête consomme uniquement les blocs dont elle a réellement besoin, et ces blocs peuvent même être partagés entre requêtes qui partagent un préfixe commun (system prompt identique, exemples few-shotFew-shotIATechnique de prompt où l'on fournit quelques exemples entrée-sortie pour guider le modèle sans réentraînement.Voir dans le glossaire identiques).
Ces deux techniques sont complémentaires et aujourd'hui présentes, sous une forme ou une autre, dans la quasi-totalité des moteurs d'inférence sérieux.
Batching continu : ne jamais laisser un slot inactif
Le batching statique classique — regrouper N requêtes, lancer le forward pass, attendre que toutes soient terminées avant de former le batch suivant — souffre d'un défaut structurel : les requêtes n'ont pas la même longueur de génération, donc une requête courte termine tôt mais son slot reste occupé sans produire de travail utile jusqu'à ce que la requête la plus longue du batch se termine.
Le batching continu (continuous batching, aussi appelé in-flight batching chez NVIDIA) résout ce problème au niveau de l'ordonnanceur : à chaque itération de decode, dès qu'une requête termine sa génération, son slot est immédiatement réattribué à une nouvelle requête en attente, sans attendre la fin du batch entier. L'ordonnancement se fait itération par itération plutôt que batch par batch.
Un batch statique de 8 requêtes dont la génération dure entre 50 et 400 tokens : la requête la plus courte libère son slot après 50 itérations mais ce slot reste vide jusqu'à l'itération 400, soit 350 itérations de calcul gaspillé sur ce seul slot. Avec du batching continu, dès l'itération 51, ce slot accueille une nouvelle requête de la file d'attente. Sur des charges réelles avec une distribution large de longueurs de génération, les moteurs de production observent des gains de débit de l'ordre de 2 à 4 fois par rapport au batching statique, à latence individuelle comparable ou meilleure.
Le batching continu suppose que l'ordonnanceur puisse ajouter et retirer des requêtes du batch actif sans reconstruire les tenseurs de poids ni interrompre les requêtes en cours — ce qui n'est possible que combiné à une gestion paginée du KV cache, qui permet d'allouer et de libérer des blocs mémoire indépendamment par requête.
Quantization : INT8, FP8 et au-delà
Réduire la précisionprécisionIAProportion des alertes émises par un modèle qui sont justifiées. Elle s'oppose au rappel : améliorer l'une dégrade l'autre.Voir dans le glossaire numérique des poids (et parfois des activations) diminue directement le volume de données à lire depuis la HBM à chaque token généré — ce qui, en phase de decode memory-bound, se traduit presque linéairement en gain de débit.
Plusieurs approches coexistent :
- Quantization des poids seuls (weight-only, ex. GPTQ, AWQ) : les poids sont stockés en INT4 ou INT8, mais dé-quantifiés à la volée en FP16 pour le calcul matriciel. Le gain porte sur la bande passante mémoire, pas sur le débit de calcul brut.
- Quantization des poids et des activations (ex. SmoothQuant) : poids et activations sont tous deux quantifiés, ce qui permet d'utiliser directement les unités de calcul INT8/FP8 du GPU, avec un gain de débit de calcul en plus du gain mémoire.
- FP8 : format flottant 8 bits supporté nativement par les architectures Hopper et Blackwell, avec deux variantes (E4M3, plus de précision ; E5M2, plus de portée dynamique). Contrairement à l'INT8, FP8 conserve une structure exposant/mantisse qui tolère mieux les distributions de valeurs à forte variance typiques des activations de Transformer, ce qui réduit le besoin de calibration fine par couche.
Sur les GPU compatibles (Hopper, Blackwell), privilégiez FP8 à INT8 pour la quantization des activations : la perte de précision constatée en pratique est généralement plus faible pour un gain de débit comparable, car FP8 gère nativement les valeurs aberrantes (outliers) qui dégradent le plus la quantization INT8 sans techniques de correction supplémentaires (calibration par canal, lissage des activations).
La perte de précision n'est jamais nulle. Elle doit être mesurée sur des benchmarks représentatifs de l'usage réel du modèle — perplexité générale, mais surtout tâches spécifiques au domaine (extraction, raisonnement, code) — avant tout déploiement, et pas seulement sur des benchmarks académiques génériques qui peuvent masquer une dégradation localisée sur une capacité précise.
TensorRT-LLM et vLLM : deux philosophies d'optimisation
Deux familles d'outilstool callingIACapacité d'un agent ou d'un LLM à invoquer des outils externes (API, calcul, recherche) pendant le raisonnement.Voir dans le glossaire dominent le paysage des moteurs d'inférence LLMgrand modèle de langageIAModèle entraîné à prédire le token suivant d'une séquence de texte. Toutes ses capacités apparentes — résumer, traduire, coder — découlent de cette unique tâche.Voir dans le glossaire sur GPU NVIDIA, avec des compromis très différents.
| Critère | TensorRT-LLM | vLLM |
|---|---|---|
| Approche | Compilation d'un moteur binaire optimisé, fusion de kernels au build | Exécution Python avec kernels custom, pas de compilation préalable |
| Itération | Étape de build nécessaire à chaque changement de modèle ou de config | Rechargement quasi immédiat, adapté à l'expérimentation rapide |
| Écosystème | Intégré à la pile NVIDIA (Triton Inference Server, TensorRT) | Intégration native Hugging Face, adoption large côté recherche et startups |
| Attention paginée | In-flight batching natif, gestion mémoire propriétaire | PagedAttention, technique d'origine du projet |
| Parallélisme multi-GPU | Tensor et pipeline parallelism matures, orienté clusters de production | Tensor parallelism supporté, moins outillé pour les topologies complexes |
| Cas d'usage typique | Service à très haut volume, latence contractuelle stricte, infrastructure NVIDIA dédiée | Déploiement rapide, recherche, charge variable, équipes sans expertise CUDA profonde |
Le choix entre les deux n'est pas une question de performance brute isolée — les deux atteignent des débits comparables sur des configurations bien réglées — mais une question d'organisation : TensorRT-LLM exige un pipeline de build et une expertise CUDA/TensorRT pour tirer parti de sa flexibilité de fusion de kernels, tandis que vLLM privilégie la vélocité opérationnelle au prix d'un contrôle plus grossier sur les kernels générés. Évaluez la fréquence de changement de modèle avant de choisir : un modèle stable en production justifie l'investissement du build TensorRT-LLM, un modèle qui change chaque semaine ne le justifie généralement pas.
Checklist de déploiement
- Mesurer TTFT, latence inter-token et débit séparément sur un trafic représentatif, pas seulement le débit agrégé.
- Déterminer la distribution réelle de longueur de contexte et de longueur de génération attendue, pas seulement les valeurs maximales théoriques.
- Dimensionner la mémoire GPU en incluant le KV cache au pic de charge concurrente, pas seulement les poids du modèle.
- Activer le batching continu et une gestion paginée du KV cache avant d'envisager toute autre optimisation de débit.
- Choisir un schéma de quantization en fonction d'un budget de dégradation de précision mesuré sur des tâches représentatives du domaine, pas sur une perplexité générique.
- Valider la latence de queue (p95, p99), pas seulement la latence moyenne : un service qui semble rapide en moyenne peut violer un SLA sur une fraction significative des requêtes.
- Vérifier le comportement sous charge concurrente réaliste avant mise en production, pas uniquement en requête isolée.
Pièges fréquents
Un batch nominal trop grand pour la mémoire disponible au contexte maximal réel provoque des OOM aléatoires en production, alors que les tests en développement — souvent menés avec des contextes courts — ne les révèlent jamais. Le second piège classique est d'ignorer le TTFT au profit du seul débit agrégé : un débit élevé mesuré sur un pipeline batch ne dit rien de l'expérience d'un utilisateur interactif qui attend son premier token.
Une dégradation de précision issue de la quantization peut rester invisible sur un benchmark général (perplexité, QA générique) tout en étant significative sur une capacité spécifique et rarement testée — extraction structurée, arithmétique multi-étapes, respect strict d'un format de sortie. Testez systématiquement les capacités critiques pour votre cas d'usage précis avant et après quantization, avec les mêmes prompts, et ne vous fiez pas à un score agrégé unique.
Enfin, ne sous-estimez pas le coût du warmup : les premières requêtes après démarrage d'un service, avant que les CUDA graphs ne soient capturés et que les caches de kernels ne soient chauds, peuvent afficher des latences très supérieures au régime stable. Un service qui reçoit du trafic réel immédiatement après déploiement sans période de warmup explicite expose cette latence dégradée directement aux premiers utilisateurs.
L'essentiel à retenir
Ce chapitre détaille les techniques qui déterminent le coût et la latence réels d'un service d'inférence LLM en production, au-delà de l'architecture du modèle lui-même. Il explique pourquoi la phase de decode est memory-bound et dominée par la lecture du KV cache, dont la taille croît avec la longueur de contexte et le nombre de requêtes concurrentes. Il présente le batching continu et l'attention paginée (PagedAttention) comme réponses structurelles au gaspillage du batching statique, ainsi que la quantization INT8/FP8 comme levier direct sur la bande passante mémoire. Il compare enfin TensorRT-LLM et vLLM selon des critères opérationnels — vélocité d'itération contre contrôle fin des kernels — plutôt que sur une performance brute isolée.
Questions fréquentes
Le batching continu remplace-t-il la quantization, ou faut-il combiner les deux ?
Quantifier un modèle en INT8 dégrade-t-il toujours la qualité des réponses ?
Pourquoi ne pas simplement augmenter la taille du batch pour améliorer le débit ?
FlashAttention et PagedAttention sont-ils concurrents ou complémentaires ?
Faut-il toujours viser le débit maximal pour un service LLM ?
Le KV cache peut-il être partagé entre plusieurs requêtes ?
Pourquoi mesurer la latence p95/p99 plutôt que la latence moyenne ?
Progression sauvegardée dans votre navigateur.
Quiz de validation
Quiz Player
Quiz de validation
Plusieurs réponses possibles — validez ensuite.
Vrai ou faux.
Quiz indisponible (données invalides).