Seifeddine Guizani — Quand on commence à déployer des grands modèles de langage en production, on réalise vite que l’évaluation de la qualité des réponses ne représente que la moitié du chemin. L’autre moitié, c’est l’infrastructure. Si un utilisateur doit attendre dix secondes devant un écran figé pour obtenir une réponse, l’expérience utilisateur est gâchée.
Au début, j’avais tendance à penser qu’il suffisait d’ajouter des GPU pour tout résoudre, mais la réalité opérationnelle m’a vite rattrapé. L’optimisation de l’inférence est un exercice d’équilibriste complexe où chaque milliseconde et chaque mégaoctet comptent.
Voici les principaux enseignements que je tire de mes tests d’infrastructure et de mes déploiements récents :
- La latence est bicéphale : Elle se divise entre le traitement initial du prompt (phase de Prefill, qui régit le temps d’attente initial ou TTFT) et la génération des jetons suivants (phase de Decode, qui détermine le débit d’affichage).
- Le compromis coût/vitesse est inévitable : Optimiser pour le débit global (throughput) se fait presque toujours au détriment de la latence individuelle de l’utilisateur.
- Le paysage logiciel se restructure : Avec la mise en mode maintenance officielle de Hugging Face TGI, le choix se resserre autour de vLLM pour la flexibilité et de TensorRT-LLM pour la performance brute sur matériel NVIDIA.
- La mémoire vRAM se planifie au mégaoctet près : Les poids du modèle représentent une charge statique prévisible, mais dimensionner dynamiquement le cache KV (Key-Value) est le véritable défi pour éviter les crashs de type Out of Memory (OOM).
Les métriques de performance essentielles en production
Pour évaluer l’efficacité avec laquelle un moteur d’inférence distribue ses jetons sous différentes charges de trafic, nous devons suivre plusieurs métriques fondamentales de niveau jeton (token) et de niveau requête :
- Time to First Token (TTFT) : C’est le délai entre la soumission du prompt par l’utilisateur et la réception du tout premier jeton de sortie. C’est la métrique la plus importante pour la réactivité perçue dans une application conversationnelle comme un chatbot. Un standard de réactivité se situe sous les 200 à 500 millisecondes.
- Inter-Token Latency (ITL) / Time Per Output Token (TPOT) : Le temps moyen nécessaire pour générer chaque jeton suivant. C’est ce qui détermine la vitesse de défilement du texte à l’écran. Un bon objectif se situe entre 10 et 30 millisecondes par jeton (soit 33 à 100 jetons par seconde), ce qui dépasse confortablement la vitesse de lecture humaine moyenne d’environ 10 jetons par seconde.
- Throughput (Débit) : Le volume de données traitées par unité de temps, mesuré en Tokens par Seconde (TPS) ou en Requêtes par Seconde (RPS). On privilégie un débit élevé pour les tâches asynchrones de traitement par lots.
- Efficacité matérielle et financière : Le pourcentage d’utilisation du GPU et de la vRAM, mis en relation avec le coût financier réel calculé pour un million de jetons générés.
Le cycle de vie d’une requête : Prefill et Decode
Pour bien comprendre ces métriques, il faut analyser comment fonctionne l’inférence sous le capot. Elle s’exécute en deux phases distinctes :
Soumission du Prompt ──► [ PHASE DE PREFILL ] ──► Envoi du premier jeton ──► [ PHASE DE DECODE ] ──► Fin de la requête
(Détermine le TTFT) ─────────────────────────────────────────── (Détermine l’ITL/TPOT)
La phase de Prefill consiste pour le serveur à ingérer et à calculer l’ensemble du bloc de contexte initial en un seul passage hautement parallélisé pour initialiser le cache Key-Value (KV). Cette étape dépend énormément de la longueur du prompt. Si vous soumettez un document de 10 000 jetons, le TTFT va s’envoler car le GPU doit traiter l’intégralité du texte avant de pouvoir générer le premier mot.
La phase de Decode fonctionne de manière autorégressive. Le modèle prend le jeton précédemment généré, met à jour le cache KV et produit un nouveau jeton unique à chaque cycle de calcul. Cette phase est fortement limitée par la bande passante mémoire et la taille des paramètres du modèle. Un grand modèle (comme un 70B) mettra beaucoup plus de temps à décoder qu’un petit modèle (comme un 8B) car des milliards de poids doivent être lus depuis la mémoire du GPU à chaque caractère produit.
Le dilemme fondamental : Latence vs Débit (Throughput)
C’est une loi immuable de l’infrastructure de calcul : chercher à maximiser le débit dégrade presque toujours la latence, et vice-versa. Nous devons constamment arbitrer entre le confort d’un utilisateur unique (latence faible) et l’optimisation financière globale (débit élevé).
- Pour privilégier la latence : Le serveur doit traiter la requête immédiatement. Le GPU travaille sur un lot de requêtes minuscule (parfois une seule), ce qui laisse une partie de ses cœurs de calcul inactifs, gaspillant des cycles processeurs coûteux.
- Pour privilégier le débit : Le serveur regroupe des dizaines de requêtes d’utilisateurs différents au sein d’un grand lot (batch). Les cœurs d’exécution du GPU sont saturés à 100 %, ce qui maximise le nombre total de jetons générés par seconde. Cependant, chaque utilisateur doit attendre dans une file d’attente que le lot se forme, ce qui ralentit la réponse individuelle.
Si l’on trace cette relation sur un graphique, on observe une courbe en forme de crosse de hockey. Lorsque l’on augmente la concurrence d’utilisateurs, la latence reste d’abord stable. Mais dès que la mémoire ou les limites de calcul du GPU sont saturées, le système atteint un point de rupture : le débit plafonne et la latence s’envole de manière exponentielle.
Les leviers techniques pour équilibrer ce compromis
Pour gérer cette tension, les moteurs modernes implémentent des mécanismes avancés :
- Le batching continu (Continuous Batching) : Contrairement au batching statique traditionnel qui bloque le traitement de nouvelles requêtes tant que l’ensemble du lot en cours n’est pas terminé, le batching continu injecte de nouvelles requêtes et extrait les réponses finalisées directement au niveau de chaque itération de jeton. Cela permet de multiplier le débit par 10 sous forte charge tout en limitant l’impact sur la latence.
- Le Prefill fractionné (Chunked Prefill) : Le traitement d’un très long prompt entrant peut monopoliser le GPU et figer momentanément la génération de texte pour tous les autres utilisateurs. Le fractionnement consiste à découper ces prompts massifs en morceaux plus petits (par exemple 512 jetons à la fois) afin de lisser l’activité de pré-remplissage au milieu du flux de décodage des autres requêtes actives.
- Le décodage spéculatif (Speculative Decoding) : Un petit modèle “brouillon” ultra-rapide génère une suite de 4 à 5 jetons à haute vitesse. Le modèle principal, beaucoup plus lourd, vérifie ensuite l’ensemble de ces propositions en une seule étape parallèle sur le GPU. Cela permet de réduire l’ITL de 20 % à 40 % sans sacrifier la précision du grand modèle, au prix d’une légère surconsommation de mémoire vRAM.
Choisir sa stratégie selon l’application
| Type d’usage | Métrique cible | Applications typiques | Stratégie d’infrastructure |
|---|---|---|---|
| Optimisé Latence | TTFT faible, TPOT bas | Assistants vocaux interactifs, autocomplétion de code en temps réel | Petite taille maximale de lots, précision FP8, décodage spéculatif |
| Optimisé Débit | TPS élevé, RPS maximal | Analyse et synthèse de documents PDF par lots, pipelines d’évaluation (LLM-as-a-Judge) | Taille de lots maximale, saturation du GPU au-dessus de 90 %, files d’attente asynchrones |
Comparatif des frameworks d’inférence en production
Le choix de l’architecture logicielle hébergeant le modèle détermine de manière critique ses performances opérationnelles. Sur un matériel standard de production, tel qu’un GPU NVIDIA H100, les principaux moteurs open-source affichent des profils de latence et des spécialisations bien distincts.
| Métrique / Dimension | TensorRT-LLM | SGLang | vLLM |
|---|---|---|---|
| Vitesse brute du TTFT | Le plus rapide (gagnant) | Rapide (hautement optimisé) | Modéré |
| Latence de décodage (ITL) | Modérée (surcharge par étape plus élevée) | La plus basse (gagnante) | Basse et très stable |
| Gestion du contexte / RAG | Graphes statiques peu adaptés aux tailles fluides | Le meilleur via le cache de préfixes en arbre (Radix tree) | Bonne via le cache de préfixes par blocs |
| Complexité d’intégration | Très élevée (compilation explicite du modèle) | Faible (compatible nativement avec Python/OpenAI) | Minimale (le standard simple d’accès) |
Les comportements sous charge
La hiérarchie des frameworks évolue selon l’intensité du trafic :
- À faible concurrence (1 à 10 requêtes simultanées) : TensorRT-LLM domine nettement sur le plan du TTFT et de la latence de bout en bout car il compile le modèle en graphes d’exécution CUDA optimisés pour un matériel ultra-spécifique.
- À forte concurrence (plus de 50 requêtes simultanées) : SGLang et vLLM limitent très efficacement la dégradation des performances grâce à leur gestion du batching continu. Le système RadixAttention de SGLang gère par exemple extrêmement bien les conversations multi-tours complexes, empêchant la latence de queue (P99) de s’effondrer.
vLLM vs Hugging Face TGI
Bien que ces deux moteurs s’appuient sur le batching continu, ils répondent à des objectifs d’architecture très différents. Un point important : Hugging Face a officiellement placé son projet TGI en mode maintenance (dépôt en lecture seule, réservé aux corrections historiques) et conseille désormais de migrer vers vLLM ou SGLang pour les nouveaux projets. On peut explorer leurs documentations respectives sur vLLM et sur Hugging Face TGI.
Voici les différences d’implémentation constatées en pratique :
- Architecture globale : vLLM s’appuie sur une boucle d’événements principalement écrite en Python avec des noyaux CUDA personnalisés hautement optimisés. TGI utilise un routeur de requêtes écrit en Rust connecté à un moteur d’inférence en Python.
- Gestion de la mémoire : vLLM utilise sa technologie brevetée PagedAttention pour organiser le cache Key-Value en blocs virtuels, éliminant ainsi toute fragmentation de la mémoire. TGI utilise une allocation par blocs qui s’avère moins efficace en cas de forte concurrence.
- Compatibilité matérielle : vLLM supporte un écosystème très large (NVIDIA, AMD, Google TPU, AWS Neuron, Intel Gaudi). TGI reste principalement axé sur les architectures NVIDIA.
- Débit global et efficacité GPU : Sous forte charge, vLLM fournit un débit de pointe 2 à 24 fois supérieur à celui de TGI. Grâce à PagedAttention, vLLM consomme 19 % à 27 % de vRAM en moins que TGI. Cela libère de l’espace pour augmenter le taux d’utilisation du GPU jusqu’à 85-92 % (contre 68-74 % pour TGI) et traiter beaucoup plus de requêtes parallèles sur la même puce.
- Profils de latence : À très faible charge, TGI conserve un léger avantage grâce à des noyaux de prefill optimisés qui lui procurent un TTFT 1,3 à 2 fois plus rapide que vLLM. Cependant, dès que l’on passe la barre des 50 requêtes concurrentes, la tendance s’inverse : vLLM conserve une latence de queue (P99) très stable autour de 2,8 secondes, tandis que TGI grimpe à 3,5 secondes et commence à montrer des signes de saturation.
- Cas spécifique des très longs contextes (RAG) : C’est ici que la version v3 de TGI brille. Pour traiter des documents massifs ou des pipelines de recherche documentaire s’étendant sur plus de 200 000 jetons, l’optimisation du cache de préfixes de TGI s’avère extrêmement robuste. Sur des échanges successifs portant sur un fichier gigantesque, TGI peut se montrer jusqu’à 13 fois plus rapide que vLLM en évitant de recalculer inutilement le contexte à chaque question.
- Fonctionnalités avancées : vLLM propose des mécanismes de décodage spéculatif plus avancés (permettant des réductions de latence de 20 à 35 % via un modèle de relecture) ainsi qu’une intégration étroite avec Outlines pour imposer des grammaires structurées (sorties JSON strictes). TGI, quant à lui, offre un déploiement clé en main sans configuration complexe pour tous les modèles hébergés sur le Hub Hugging Face.
vLLM vs NVIDIA TensorRT-LLM
Ces deux moteurs représentent l’état de l’art pour servir des modèles open-weight en entreprise, mais ils répondent à deux philosophies d’infrastructure opposées :
- vLLM privilégie l’agilité et la simplicité de déploiement. C’est un moteur dynamique qui intercepte les poids du modèle à la volée, lance sa boucle d’événements et gère la mémoire de manière transparente. Il offre un démarrage instantané et une compatibilité multi-constructeurs.
- TensorRT-LLM privilégie l’optimisation matérielle absolue. C’est un framework compilé Ahead-of-Time (AOT). Il impose de passer par une étape de construction préalable (via l’utilitaire
trtllm-build) pour générer un fichier binaire compressé.enginespécifiquement adapté au modèle de GPU cible. On peut approfondir sa mise en œuvre en consultant la documentation de TensorRT-LLM.
Le tableau ci-dessous résume ces différences structurelles :
| Dimension | vLLM | TensorRT-LLM |
|---|---|---|
| Modèle d’exécution | Runtime dynamique (démarrage immédiat) | Compilation AOT (génération d’un fichier .engine matériel) |
| Écosystème matériel | Multi-constructeurs (NVIDIA, AMD, TPU, AWS, Intel) | Exclusivement NVIDIA (Ampere, Hopper, Blackwell) |
| Mise en service d’un modèle | Instantanée (chargement direct depuis Hugging Face Hub) | Complexe (configuration des dimensions et compilation requises) |
| Couche de déploiement | Serveur d’API compatible OpenAI intégré nativement | Packagé historiquement dans Triton, désormais disponible via trtllm-serve |
Sur le plan des performances pures sur GPU NVIDIA (comme les puces H100 ou Blackwell), TensorRT-LLM l’emporte grâce à la fusion de noyaux CUDA optimisés au niveau matériel : il délivre un débit de pointe 15 % à 30 % supérieur à celui de vLLM. Sa gestion de la phase de prefill lui permet également d’afficher un TTFT (P95) plus rapide de 150 à 200 ms. Pendant la phase de décodage, l’émission des jetons reste d’une stabilité remarquable, oscillant de manière quasi constante entre 9 et 12 ms par jeton.
Cependant, cette quête de performance se heurte au problème de la barrière de compilation. Avec vLLM, si vous décidez de passer d’un modèle Llama à un modèle Qwen, il vous suffit de modifier une ligne de texte dans votre script de lancement. Le serveur est opérationnel en quelques minutes. Avec TensorRT-LLM, chaque changement implique de recompiler le moteur. Si vous devez étendre la fenêtre de contexte de 4 000 à 32 000 jetons, vous devez recompiler. Si vous migrez vos instances d’un parc de GPU A100 vers des H100, l’ancien moteur échouera et vous devrez générer un nouveau binaire adapté aux spécificités physiques de la nouvelle puce.
*(C’est pour cette raison que beaucoup d’équipes techniques adoptent une approche hybride : elles prototypent et valident leurs itérations de modèles rapidement sur vLLM, puis convertissent uniquement le modèle final stable sur TensorRT-LLM pour optimiser les coûts de production à grande échelle).*
Les performances constatées sur les API managées (Frontier Models)
Si vous choisissez de consommer des modèles via des API cloud plutôt que de gérer votre propre infrastructure, les benchmarks de latence révèlent des choix architecturaux bien distincts de la part des grands éditeurs :
- La catégorie ultra-rapide (ex : GPT-4o-mini, Gemini 1.5 Flash) : Conçus spécifiquement pour la réactivité, ces modèles affichent un TTFT impressionnant de 150 à 250 ms et un ITL inférieur à 10 ms. Le texte s’affiche presque instantanément.
- La catégorie standard (ex : GPT-5.2, Mistral Large, Claude 4.5 Sonnet) : Plus lourds et complexes, ces modèles offrent de grandes capacités de raisonnement mais au prix d’une latence accrue. Comptez en moyenne un TTFT de 400 à 600 ms et un ITL de 15 à 25 ms.
- La catégorie des modèles de raisonnement (ex : OpenAI o1/o3, Grok Reasoning) : Ces architectures paient une taxe de latence très lourde. En raison de l’exécution en arrière-plan d’une chaîne de pensée (Chain of Thought) invisible pour l’utilisateur, le TTFT apparent s’étire fréquemment sur des durées allant de 3 à plus de 7 secondes avant que les premiers mots lisibles ne commencent à s’afficher à l’écran.
Sous le capot : Calculer précisément la mémoire GPU (vRAM)
Pour dimensionner correctement vos serveurs et éviter les redoutés crashs de type *Out of Memory* (OOM), il est impératif de comprendre comment les différents composants de l’inférence se répartissent la mémoire vRAM du GPU. Le calcul s’appuie sur une formule simple :
Mémoire Totale Requise = Mémoire des poids du modèle + Allocation du Cache KV + Marge système & noyaux CUDA
Le calcul de l’empreinte statique (les poids du modèle)
La place occupée par les poids du modèle en mémoire est fixe et dépend directement de sa taille en paramètres et de son niveau de quantification (la précision des données) :
$$\text{Mémoire (en Go)} = \frac{\text{Nombre de paramètres (en milliards)} \times \text{Octets par paramètre}}{1\text{ milliard d’octets}}$$
Selon le format de précision retenu, le besoin en octets par paramètre évolue ainsi :
- FP16 / BF16 (16 bits) : 2 octets par paramètre. Un modèle de 7 milliards de paramètres (7B) nécessite $7 \times 2 = 14$ Go de vRAM.
- INT8 / FP8 (8 bits) : 1 octet par paramètre. Un modèle 7B nécessite $7 \times 1 = 7$ Go de vRAM.
- INT4 / FP4 (4 bits) : 0,5 octet par paramètre. Un modèle 7B nécessite $7 \times 0,5 = 3,5$ Go de vRAM.
Le calcul de la mémoire dynamique (le cache KV)
C’est la partie la plus complexe. Le cache KV stocke les représentations mathématiques des jetons passés pour éviter de recalculer toute l’histoire de la conversation à chaque étape. Il croît linéairement selon le nombre d’utilisateurs actifs et la longueur de leurs échanges :
$$\text{Cache KV (Octets)} = 2 \times \text{Octets de précision} \times \text{Couches (Layers)} \times \text{Têtes d’attention} \times \text{Dimension de tête} \times \text{Total de jetons}$$
(Le multiplicateur initial de 2 correspond au stockage conjoint des matrices de clés “Key” et de valeurs “Value”).
Heureusement, la plupart des architectures récentes (comme Llama 3, Mistral ou Qwen) intègrent la technologie Grouped-Query Attention (GQA). GQA permet de réduire considérablement l’empreinte du cache KV en associant moins de têtes de clés et de valeurs par rapport aux têtes de requêtes (par exemple, 8 têtes au lieu de 32), réduisant ainsi ce besoin en mémoire dynamique de près de 75 %.
Simulation concrète : Llama 3 8B en BF16
Imaginons que nous souhaitions héberger un modèle Llama 3 8B en précision BF16 avec un contexte de 8 192 jetons par utilisateur. Écrivons un court script Python pour modéliser précisément l’impact de cette architecture neuronale sur notre mémoire :
# Configuration de l'architecture de Llama 3 8B
params = 8.03 * 10**9 # Nombre total de paramètres
bytes_per_param = 2 # Précision BF16 (2 octets)
num_layers = 32 # Nombre de couches réseau
kv_heads = 8 # Têtes KV avec Grouped-Query Attention
head_dim = 128 # Dimension de chaque tête d'attention
context_length = 8192 # Limite de contexte par requête
# 1. Calcul du poids statique du modèle
weight_mem_gb = (params * bytes_per_param) / (10**9)
# 2. Calcul du cache KV pour un utilisateur unique totalement saturé
kv_cache_per_user_bytes = 2 * bytes_per_param * num_layers * kv_heads * head_dim * context_length
kv_cache_per_user_gb = kv_cache_per_user_bytes / (10**9)
print(f"Poids statiques du modèle : {weight_mem_gb:.2f} Go")
print(f"Empreinte du cache KV par session utilisateur active : {kv_cache_per_user_gb:.2f} Go")
Ce script renvoie les valeurs suivantes :
- Poids statiques du modèle : 16,06 Go
- Cache KV par utilisateur actif : 0,07 Go
Si nous décidons de déployer cette configuration sur une carte graphique accessible de 24 Go de vRAM (comme un GPU NVIDIA L4 ou un RTX 4090) :
- Le modèle consomme 16,06 Go pour charger ses poids.
- Le système d’exploitation et les pilotes CUDA s’octroient une marge d’environ 1,5 Go.
- Il nous reste environ 6,5 Go de vRAM disponible pour stocker dynamiquement notre cache KV.
En divisant notre espace libre par la consommation d’une session ($6,5 / 0,07$), nous constatons que cette unique carte graphique peut soutenir sans problème jusqu’à 92 utilisateurs simultanés saisissant du texte à la limite de leur fenêtre de contexte avant d’atteindre la saturation de la mémoire.
Afin d’éviter tout incident en production, les serveurs d’inférence modernes s’appuient sur deux mécanismes de contrôle essentiels :
- gpu_memory_utilization (sous vLLM) : Par défaut, vLLM s’approprie 90 % de la vRAM disponible dès son lancement. Il y loge les poids fixes du modèle, évalue l’espace restant, et segmente immédiatement toute cette portion de mémoire restante en une grille ordonnée de blocs PagedAttention pour le cache KV.
- max_model_len : Si vous chargez un modèle supportant nativement des fenêtres de contexte gigantesques (comme 128 000 jetons), mais que vos besoins applicatifs se limitent à 8 000 jetons, vous devez impérativement configurer ce paramètre. Si vous le laissez libre, le serveur réservera d’immenses blocs de mémoire pour parer aux requêtes les plus longues, ce qui limitera lourdement la concurrence d’utilisateurs admissibles sur vos machines.
Tour d’horizon des techniques d’optimisation de l’inférence
Ces optimisations s’articulent autour de quatre couches d’ingénierie fondamentales pour réduire la latence, accroître le débit et comprimer l’empreinte mémoire des modèles.
1. La quantification (réduction de la précision)
La quantification réduit l’espace de stockage numérique des poids et des activations, ce qui permet d’accélérer les flux de transfert vers le processeur.
- FP8 (virgule flottante sur 8 bits) : C’est le standard actuel des architectures NVIDIA Hopper (H100) et Blackwell. Il divise l’occupation mémoire par deux par rapport au FP16, avec une dégradation de précision quasiment imperceptible sur la qualité des réponses.
- INT4 / FP4 (précision sur 4 bits) : Compresse les modèles à 25 % de leur taille originale. Un modèle massif de 70B paramètres (qui requiert environ 140 Go de vRAM au format standard) peut ainsi loger dans moins de 40 Go de mémoire, le rendant exécutable sur des cartes grand public, au prix d’une perte légère de nuances dans les raisonnements complexes.
- AWQ et GPTQ (quantification sensible aux activations) : Ces approches mathématiques protègent le premier pour cent de poids considérés comme essentiels pour la cohérence globale du modèle (poids saillants) en les maintenant en haute précision, tout en compressant agressivement les 99 % restants.
2. Les optimisations algorithmiques et de compilation
Ces techniques modifient la façon dont le modèle calcule ses résultats ou dont le GPU compile son code d’exécution.
- FlashAttention (v1, v2 et v3) : Cette technique repense la gestion mathématique de l’attention au sein de la mémoire SRAM rapide intégrée directement sur le GPU. Elle évite au processeur graphique d’effectuer des allers-retours incessants et lents pour lire et écrire ses grandes matrices d’attention dans la mémoire globale vRAM. Cela permet d’accélérer le traitement des prompts volumineux (phase de prefill) d’un facteur 2 à 4.
- La fusion de noyaux (Kernel Fusion) : Très présente dans TensorRT-LLM et SGLang, cette méthode regroupe des dizaines d’opérations mathématiques successives en une seule instruction de calcul exécutée directement sur le GPU, évitant les temps d’attente induits par le déplacement des variables d’une couche réseau à une autre.
3. Optimisation de l’ordonnancement dynamique
- FlashInfer : Une bibliothèque spécialisée de noyaux GPU hautes performances, développée pour accélérer le décodage et les calculs d’attention des moteurs de service, en particulier sur des architectures de requêtes complexes.
4. Optimisation des couches de mémoire cache
- Le cache automatique de préfixes (RadixAttention / Prefix Caching) : Si de multiples utilisateurs partagent une même consigne système complexe ou interrogent le même document de référence, le serveur calcule l’attention de ce texte initial une seule fois, puis fige son cache KV en mémoire. Les utilisateurs suivants sautent intégralement l’étape de prefill associée, ramenant leur TTFT à un niveau proche de zéro.
Aide à la décision : par quoi commencer ?
| Votre problème | La solution à privilégier | Résultat attendu |
|---|---|---|
| Délai d’affichage initial trop long (TTFT élevé) | Cache de préfixes automatique / FlashAttention | Réponses quasi instantanées sur de longs documents ou architectures RAG. |
| Vitesse d’affichage du texte trop lente (ITL élevé) | Décodage spéculatif / Quantification FP8 | Le flux de texte s’affiche nettement plus vite que la vitesse de lecture humaine. |
| Crashes fréquents par manque de mémoire (OOM) | PagedAttention / Quantification INT4 | Possibilité de charger de plus grands modèles ou d’accepter plus d’utilisateurs sur des GPU plus abordables. |
Architecture de déploiement à grande échelle
Parce que les LLM requièrent une allocation exclusive et complète de la vRAM de cartes graphiques coûteuses pour fonctionner, leurs architectures d’hébergement s’écartent radicalement des schémas de microservices web classiques.
1. L’architecture monolithique à nœud unique (Single-Node)
C’est l’approche la plus accessible. L’intégralité du moteur d’inférence s’exécute sur une seule machine équipée de quelques GPU physiques locaux.
[ Client Applicatif ] ──► [ Passerelle API / Load Balancer ]
│
▼
[ Instance de Moteur d’Inférence ]
(vLLM / SGLang / TRT-LLM)
│
┌────────────┴────────────┐
▼ ▼
[ GPU 0 (vRAM) ] [ GPU 1 (vRAM) ]
Un serveur HTTP ou gRPC sert de passerelle d’accès, intercepte les requêtes, orchestre le batching continu et distribue le calcul directement sur les GPU installés dans le même châssis. C’est l’architecture idéale pour les outils internes d’entreprise, les flux de trafic modérés ou les phases de prototypage.
Cependant, cette structure présente une limite physique évidente : le partage de ressource. Si un utilisateur soumet une requête géante nécessitant une phase de prefill très longue, les ressources de calcul des puces physiques locales seront mobilisées, ralentissant ou suspendant momentanément la génération de jetons (decode) pour tous les autres utilisateurs connectés à ce serveur.
2. L’architecture désagrégée de séparation Prefill et Decode (PD Split)
Pour éliminer ce phénomène d’engorgement, les infrastructures de production à forte charge séparent physiquement les deux phases d’inférence en les exécutant sur des machines distinctes.
[ Requête Utilisateur ]
│
▼
[ Routeur Central ]
│
┌────────────────────────┴────────────────────────┐
▼ ▼
[ Nœuds de Prefill ] [ Nœuds de Decode ]
(Haute puissance : H100 / B200) (Haute bande passante : A100 / L40)
– Traitement des invites – Production des jetons en flux continu
– Construction des matrices KV
│ ▲
└──────────► [ Transfert du Cache KV ] ──────────┘
(Réseau ultra-rapide InfiniBand/RoCE)
Le routeur oriente les requêtes vers un premier pool de serveurs spécialisés dans le calcul pur (équipés de puces rapides comme les H100 ou B200) pour digérer les prompts et construire les matrices du cache KV. Cet état est ensuite transféré via des liens réseaux ultra-rapides vers un second pool de serveurs optimisés pour la bande passante mémoire (comme des instances L40S ou A100) qui se chargent exclusivement de délivrer le flux de jetons aux utilisateurs. C’est un choix d’architecture très élégant qui garantit une expérience utilisateur fluide et sans saccades, même lors de fortes vagues de connexions.
3. Déploiement distribué (modèles volumineux)
Pour les architectures de modèles trop imposantes pour tenir sur une seule carte physique, les ingénieurs découpent le modèle selon deux axes :
- Tensor Parallelism (TP) : Découpe les matrices d’une même couche d’un modèle pour les répartir sur plusieurs GPU d’une même machine physique. Les puces devant échanger des données à chaque étape de calcul intermédiaire, cette méthode requiert des interconnexions physiques ultra-rapides intégrées à la carte mère, comme le système NVLink de NVIDIA.
- Pipeline Parallelism (PP) : Découpe le modèle de manière séquentielle, en attribuant des groupes de couches consécutives à différentes machines du réseau. Le serveur A exécute par exemple les couches 1 à 20, puis transmet ses résultats intermédiaires au serveur B qui prend le relais pour les couches 21 à 40.
4. Arbitrage d’hébergement : Auto-hébergement vs API Serverless
Votre choix opérationnel dépendra principalement de vos exigences de confidentialité et de vos compétences en ingénierie d’infrastructure :
- Le modèle auto-hébergé (Self-Hosted) : Vous louez des instances GPU (comme des nœuds d’octets H100 chez AWS, GCP ou des fournisseurs spécialisés comme RunPod) et pilotez l’infrastructure globale. L’orchestration s’effectue généralement sous Kubernetes via des opérateurs spécialisés (vLLM-operator ou KServe), l’accès aux flux étant régulé par des passerelles d’API classiques de type Kong ou Envoy.
- Le modèle managé (Serverless) : Vous déléguez toute la complexité matérielle en vous connectant à des plateformes spécialisées (comme Together AI, Groq ou DeepInfra) via des appels API REST classiques. La facturation s’effectue simplement à l’usage (par exemple, 0,30 $ pour un million de jetons), vous épargnant les tracas liés à la maintenance d’instances physiques.
Guide de déploiement d’un LLM sur Kubernetes
Le déploiement d’un modèle de langage à grande échelle sur Kubernetes s’éloigne des pratiques d’hébergement web habituelles. Pour que cela fonctionne de manière optimale, nous devons concevoir notre architecture autour de contraintes matérielles strictes.
1. Sélection de la cible matérielle
| Carte GPU | Capacité de vRAM | Cas d’usage cibles | Modèles open-weight types |
|---|---|---|---|
| NVIDIA B200 / B100 | 192 Go HBM3e | Grappes d’entreprise à échelle maximale, traitement natif FP4/FP8 | Llama 3 405B, DeepSeek-V3/R1 |
| NVIDIA H100 / H200 | 80 Go / 141 Go | Le standard pour la production, inférence ultra-rapide en format FP8 | Llama 3 70B, Qwen-2.5 72BN |
| NVIDIA A100 | 40 Go / 80 Go | Ancien standard d’infrastructure, très robuste pour les calculs en 16 bits | Llama 3 8B, Mistral 7B |
| NVIDIA L4 / L40S | 24 Go / 48 Go | Choix économique, idéal pour les versions quantifiées ou à faible trafic | Modèles quantifiés de tailles 8B à 70B |
2. Modèle d’architecture de production
Un déploiement résilient s’appuie sur des conteneurs isolés et des mécanismes de routage de trafic adaptés :
[ Requête Utilisateur ] ──► [ Passerelle d’Ingress / API Gateway ] (Kong / Envoy)
│
▼
[ Cluster de Pods Kubernetes ]
(Piloté par KServe ou vLLM Operator)
│
┌───────────────────┴───────────────────┐
▼ ▼
[ Réplique de Nœud A ] [ Réplique de Nœud B ]
– Moteur : vLLM / SGLang – Moteur : vLLM / SGLang
– Matériel : 8x H100 GPUs – Matériel : 8x H100 GPUs
– Modèle : Llama 3 70B (FP8) – Modèle : Llama 3 70B (FP8)
Les serveurs vLLM ou SGLang sont encapsulés dans des conteneurs Docker et exposent des interfaces REST compatibles avec les protocoles d’OpenAI. Afin de fluidifier les phases de mise à l’échelle (scaling), les fichiers de poids lourds ne doivent pas être récupérés en direct depuis le réseau lors de la création d’un pod : un conteneur d’initialisation (initContainer) se charge de copier au préalable ces gigaoctets de données depuis un seau de stockage de type S3 vers des espaces de stockage SSD locaux haute vitesse NVMe.
3. Élaboration des fichiers de configuration
Voici un modèle de fichier Dockerfile robuste et pré-optimisé pour héberger vos moteurs d’inférence en production :
# Récupération de l'image officielle vLLM pré-compilée et optimisée pour CUDA
FROM vllm/vllm-openai:latest
# Configuration du jeton Hugging Face pour l'accès aux modèles privés ou restreints
ENV HF_TOKEN="votre_token_hf_ici"
# Exposition du port réseau du service d'API
EXPOSE 8000
# Commande d'initialisation intégrant les optimisations de performance recommandées
ENTRYPOINT ["python3", "-m", "vllm.entrypoints.openai.api_server"]
CMD ["--model", "meta-llama/Meta-Llama-3-70B-Instruct", "--tensor-parallel-size", "8", "--max-model-len", "8192", "--gpu-memory-utilization", "0.90", "--enable-chunked-prefill"]
Pour soumettre ce conteneur à notre cluster Kubernetes, nous devons rédiger un manifeste de déploiement qui explicite l’allocation des cartes GPU à l’aide de paramètres spécifiques :
apiVersion: apps/v1
kind: Deployment
metadata:
name: llama-serving
spec:
replicas: 2
template:
spec:
containers:
- name: vllm-privacy-engine
image: votre-registre-prive/vllm-llama3:latest
resources:
limits:
nvidia.com/gpu: "8" # Allocation d'un nœud physique complet de 8 GPU pour le Tensor Parallelism
memory: 500Gi
cpu: "64"
requests:
nvidia.com/gpu: "8"
memory: 256Gi
cpu: "32"
ports:
- containerPort: 8000
4. Bonnes pratiques matérielles et logicielles
Pour assurer la pérennité de ces environnements, voici trois aspects cruciaux à surveiller :
- La bande passante réseau inter-nœuds (Multi-Node TP) : Si un modèle particulièrement massif nécessite d’être découpé sur plusieurs serveurs physiques distincts, la vitesse du réseau interne devient le facteur limitant principal. Assurez-vous d’utiliser des instances cloud disposant de connexions réseau optimisées, comme les puces AWS EFA ou le protocole gVNIC de GCP configuré en grappes multi-canaux, pour éviter que les échanges de données ne ralentissent les phases de calcul de vos processeurs graphiques.
- La réduction des temps de démarrage à froid (Cold Starts) : Le chargement d’un modèle non quantifié de 70 milliards de paramètres (représentant environ 140 Go de données) depuis un seau de stockage classique prend généralement de 10 à 15 minutes, ce qui paralyse l’action des mécanismes d’auto-scaling de Kubernetes (HPA). Pour contourner cette limite, mettez en place des volumes persistants (PVC) partagés s’appuyant sur des systèmes de fichiers réseau à très haut débit, à l’image d’AWS FSx for Lustre ou de Google Filestore, permettant aux nouveaux conteneurs d’accéder instantanément aux poids de calcul déjà montés.
- La gestion des sondes de santé (Health & Readiness Probes) : Les requêtes d’évaluation réseau classiques (/healthz) se révèlent insuffisantes pour valider l’état d’un conteneur de LLM. Vos sondes Kubernetes doivent impérativement interroger le chemin d’accès d’évaluation propre du moteur d’inférence (comme les endpoints
/healthou/v1/modelsde vLLM) pour s’assurer que les poids du modèle ont bien achevé leur chargement en vRAM et que la structure logique de votre cache PagedAttention est entièrement initialisée et prête à recevoir du trafic de production.