On fait tourner un grand modèle de langage, tout se passe à merveille pendant les premières secondes, puis, d’un coup, la vitesse d’exécution s’effondre et la mémoire du GPU grimpe dans la zone rouge (et croyez-moi, voir sa jauge de VRAM saturer ainsi n’a rien de rassurant).
Le modèle n’a pas changé, le matériel non plus, mais le débit s’écroule et chaque cycle de calcul devient une perte d’argent. On a souvent tendance à accuser le modèle lui-même, mais la réalité est bien plus subtile.
Le véritable goulot d’étranglement de l’intelligence artificielle ne se situe généralement pas dans le “cerveau” du modèle, mais dans sa mémoire à court terme : le Key-Value Cache, ou cache KV.
Si l’on souhaite comprendre pourquoi les modèles s’accélèrent et comment faire tourner des architectures massives sur du matériel abordable, il y a sept notions fondamentales à maîtriser.
Les points clés
- Deux mémoires distinctes : Le LLM sépare ses poids neuronaux permanents de sa mémoire de travail à court terme (le cache KV).
- Pré-remplissage vs Décodage : L’inférence alterne entre une phase initiale lourde en calcul et une phase de génération limitée par les accès mémoire.
- Pas de cache pour les Requêtes (Query) : Seules les clés et les valeurs sont conservées, car les requêtes passées ne servent plus à prédire le futur.
- Le gaspillage de la fragmentation : Les systèmes classiques réservent inutilement d’immenses blocs de mémoire contigus.
- La solution de l’attention paginée : Découper le cache en pages flexibles permet d’optimiser l’espace et de partager les ressources.
- Des leviers d’optimisation concrets : La quantification, le cache de préfixes et le pré-remplissage par blocs transforment les performances.
1. Les deux mémoires d’un modèle de langage
Pour bien appréhender le sujet, il faut d’abord comprendre qu’un LLM s’appuie sur deux types de mémoire bien distincts. D’un côté, nous avons les poids du modèle, qui correspondent aux connexions neuronales permanentes de son cerveau. Ce sont des valeurs fixes qui déterminent la façon dont les signaux sont amplifiés ou atténués.
De l’autre, nous avons le contexte, c’est-à-dire chaque consigne fournie et chaque mot de la conversation en cours. C’est notre mémoire à court terme, le fameux cache KV. Sans ce cache, l’IA serait obligée de relire l’intégralité de la discussion depuis le tout début à chaque fois qu’elle doit générer un seul nouveau jeton (token).
À mesure que la conversation s’allonge, cette mémoire de travail peut d’ailleurs finir par occuper plus d’espace que le cerveau même du modèle.
2. L’inférence en deux temps : pré-remplissage et décodage
L’inférence d’un LLM ne s’effectue pas de manière linéaire ; elle se divise en deux phases distinctes : le pré-remplissage (pre-fill) et le décodage (decode). Lorsque l’on soumet un avant-propos ou un long prompt, on observe un léger temps d’attente avant que les premiers mots ne s’affichent.
C’est la phase de pre-fill. Elle demande énormément de puissance de calcul (compute-bound) car le modèle doit analyser l’intégralité de notre texte d’entrée à travers toutes ses couches pour en construire une représentation mathématique.
Une fois cette étape franchie, on entre dans la phase de décodage, le domaine exclusif du cache KV. Cette phase est quant à elle limitée par la bande passante mémoire (memory-bound). La vitesse de génération dépend de la rapidité avec laquelle le système peut récupérer ce contexte grandissant, jeton après jeton, dans la mémoire du GPU. Si cette mémoire est fragmentée ou pleine, la latence s’envole instantanément.
3. Ce qui se passe sous le capot des Transformers
Pour saisir l’élégance de cette solution, il faut se pencher sur les opérations matricielles qui s’exécutent en arrière-plan. Lorsqu’un Transformer génère un jeton, il calcule trois éléments : une requête (Query, Q), une clé (Key, K) et une valeur (Value, V). La requête correspond au jeton actuel qui cherche à savoir quelles informations passées lui sont pertinentes.
Les clés et les valeurs fournissent les réponses issues de l’historique de la conversation. Dans un système naïf sans mécanisme de cache, pour générer le millième jeton, il faudrait recalculer les clés et les valeurs des 999 jetons précédents. Le cache KV met fin à cette redondance en stockant ces matrices de clés et de valeurs des étapes passées. Le modèle n’a plus qu’à effectuer les calculs pour le seul et unique nouveau jeton qu’il vient de produire.
4. Pourquoi n’existe-t-il pas de cache pour les Requêtes ?
C’est une nuance technique parfois mal comprise, y compris par certains professionnels du domaine : pourquoi ne mettons-nous pas en cache les requêtes (Query) ? Si l’on analyse la structure des matrices, on s’aperçoit que pour prédire le jeton suivant, nous n’avons besoin que de la toute dernière ligne de la matrice de contexte.
Pour obtenir cette ligne spécifique, il nous faut l’intégralité de la matrice des clés et de la matrice des valeurs de toute la discussion, mais uniquement le vecteur de requête du nouveau jeton en cours de traitement.
La requête d’un ancien jeton ne sera plus jamais sollicitée pour prédire les jetons futurs (elle devient donc inutile pour la suite). Mettre en cache les requêtes passées constituerait par conséquent un pur gaspillage de mémoire.
5. Le problème de l’étage d’hôtel et la fragmentation
C’est sans doute l’aspect le plus problématique de la gestion classique de la mémoire. Les systèmes d’inférence traditionnels s’avèrent extrêmement gaspilleurs : ils réservent systématiquement un bloc géant de mémoire contiguë pour chaque requête, calibré sur la longueur maximale possible de la réponse.
C’est le syndrome de l’étage d’hôtel : on réserve un étage entier pour un seul client, sous prétexte qu’il pourrait éventuellement inviter une centaine d’amis plus tard. Si la limite maximale est fixée à 2 000 jetons mais que l’utilisateur n’en génère que 200, 80 % de cet espace alloué reste vide et inutilisable pour les autres requêtes.
Cette fragmentation interne, combinée aux espaces perdus entre les différentes sessions, explique pourquoi l’utilisation réelle du GPU reste souvent très basse alors même que le système prétend manquer de mémoire.
6. L’attention paginée comme parade moderne
Pour remédier à ce gaspillage de ressources, une approche logicielle appelée attention paginée (Paged Attention) a vu le jour. Cette méthode s’inspire directement de la gestion de la mémoire virtuelle dans les systèmes d’exploitation.
Elle consiste à diviser le cache KV en petits blocs de mémoire de taille fixe, que l’on appelle des pages. Plutôt que de requérir un unique bloc contigu massif, ces pages peuvent être réparties n’importe où dans la mémoire du GPU et ne sont allouées qu’au moment précis où elles deviennent nécessaires.
Cela élimine la fragmentation et permet même à plusieurs requêtes de partager certains blocs de cache KV. C’est grâce à cette architecture que des moteurs d’inférence comme vLLM ou SGLang affichent des performances remarquables. Ils appréhendent la mémoire du GPU comme un réservoir dynamique et flexible plutôt que comme des étages d’hôtel rigides, ce qui permet d’accueillir un nombre bien plus important d’utilisateurs simultanés sur un même matériel.
7. Optimiser et configurer son propre cache
Que l’on travaille dans un environnement de production ou que l’on exécute des modèles localement sur une carte graphique grand public, plusieurs leviers permettent d’ajuster ce cache.
En local, la quantification du cache KV se révèle particulièrement efficace : elle compresse les valeurs numériques du cache vers des précisions inférieures (le passage en 8 bits, une astuce simple mais diablement efficace, permet de diviser par deux l’espace requis avec une perte de qualité quasi imperceptible).
Sur des moteurs de production comme vLLM, on peut pousser le paramètre d’utilisation de la mémoire GPU jusqu’à 95 % pour maximiser la densité de requêtes, ou activer le cache de préfixes (prefix caching) afin de sauter la phase de pré-remplissage pour les invites système partagées. De même, l’activation du pré-remplissage par blocs (chunked pre-fill) permet d’éviter les saccades de génération lorsqu’un nouveau prompt volumineux arrive en cours de traitement.
Maîtriser sa propre infrastructure
Le véritable enjeu derrière la compréhension du cache KV est de pouvoir posséder sa propre infrastructure d’intelligence plutôt que de dépendre d’abonnements cloud.
Que l’on se tourne vers vLLM pour son débit brut ou vers SGLang pour des tâches complexes impliquant du parsing JSON et des architectures de type mélange d’experts (MoE), maîtriser le cache KV est ce qui permet de faire tourner un modèle de 30 milliards de paramètres sur une carte graphique qui n’aurait normalement jamais dû pouvoir l’accueillir.
On transforme ainsi un problème de calcul à croissance quadratique en un processus linéaire parfaitement gérable, tout en maintenant une latence minimale et en préservant la confidentialité de nos données.
Si cette exploration technique de la mémoire des LLM vous a apporté des éclairages utiles pour optimiser vos agents locaux ou vos configurations de fine-tuning, n’hésitez pas à vous abonner et à partager en commentaire les limites de mémoire les plus marquantes auxquelles vous avez été confronté dans vos déploiements.