← Blog

Pourquoi l'inférence LLM est memory-bound (et ce que ça change pour le sizing)

Voici une intuition fausse mais répandue : « pour aller plus vite en génération de texte, il faut un GPU qui calcule plus vite ». En réalité, la vitesse de génération d'un LLM n'est presque pas limitée par la puissance de calcul. Elle est limitée par la bande passante mémoire. On dit que le décodage est memory-bound, et cette propriété se démontre au chiffre près.

Cet article ferme la série entamée avec l'anatomie du fichier de poids et les dimensions d'un modèle. Il s'appuie sur les mesures réelles de Gemma 4 12B (gemma-4-12b-it) sur le serveur d'inférence de Sover.tech, une RTX 5060 Ti 16 Go.

Poids figés, activations volatiles

Deux types de nombres circulent à l'inférence, et il faut les distinguer nettement.

Les poids sont le réglage de la machine. Ils sont lus dans model.safetensors, figés depuis l'entraînement, identiques pour toutes les requêtes. Douze milliards de nombres, chargés une fois en VRAM au démarrage du pod.

Les activations sont ce qui traverse la machine. Ce sont les vecteurs intermédiaires (3840 nombres) calculés à la volée quand un token passe dans les couches, puis jetés. Elles n'existent nulle part sur le disque, elles sont différentes à chaque requête.

Poids Activations
Dans le fichier, puis en VRAMNulle part, calculées à la volée
QuandFigés depuis l'entraînementRecalculées à chaque token
Combien≈ 12 milliards3840 nombres, 48 fois de suite
VarientJamaisÀ chaque requête

Les deux sont des listes de nombres, donc les deux ressemblent à des vecteurs. Mais « vecteur » décrit une forme, pas un rôle. Cette distinction est la clé de tout ce qui suit.

Deux phases, deux régimes : prefill et decode

Répondre à une requête se fait en deux phases aux profils de performance opposés.

Le prefill digère le prompt d'un bloc. Tous les tokens de la question passent en parallèle dans les couches, la carte calcule à fond. C'est cette phase qui détermine le temps avant le premier token (le TTFT). Comme elle traite beaucoup de tokens d'un coup, elle est compute-bound : c'est la puissance de calcul qui la limite.

Le decode produit la réponse token par token. Et pour chaque token généré, il faut relire tous les poids du modèle depuis la VRAM. Pourquoi les relire, alors qu'ils ne changent jamais ? Parce qu'ils sont bien trop volumineux pour tenir dans la mémoire ultra-rapide embarquée sur la puce (les quelques mégaoctets de cache et de registres du GPU). Les 7,6 Go de poids vivent dans la VRAM, et chaque token impose un aller-retour complet vers cette mémoire. C'est cette phase qui fixe le débit en tokens par seconde, autrement dit le temps par token de sortie (le TPOT, ou ITL).

decode, pour chaque token produit :
  relire tous les poids (~7,6 Go)  ->  48 couches d'activations  ->  1 token  ->  on recommence

Le concept qui relie ces deux régimes porte un nom : l'intensité arithmétique, le nombre de calculs (FLOPs) effectués par octet lu depuis la mémoire. Le prefill a une intensité élevée : il lit les poids une fois et les fait travailler sur tous les tokens du prompt à la fois. Le decode a une intensité faible : il relit les mêmes poids pour ne produire qu'un seul token. Quand l'intensité est haute, le goulot est le calcul ; quand elle est basse, c'est la mémoire. Toute la différence entre les deux phases tient là.

Le décodage est une division

Puisque produire un token exige de relire l'intégralité des poids, le débit maximal est simplement la bande passante mémoire divisée par le volume de poids à relire. Sur la RTX 5060 Ti :

  448 Go/s   bande passante VRAM de la carte
÷ 7,6 Go     les poids, relus à chaque token
-----------
≈ 59 tok/s   plafond théorique

  45 tok/s   mesuré en production, soit ~76 % du théorique

Les 24 % manquants partent dans la lecture du KV cache, dans le surcoût d'orchestration (lancement des kernels CUDA, scheduling) et dans le fait qu'on n'atteint jamais la bande passante théorique d'une carte. « Memory-bound » reste donc une simplification utile : le débit réel dépend aussi de la taille du KV cache, de l'efficacité des kernels et de la taille du batch. L'essentiel tient quand même : le plafond théorique n'a pas fait intervenir la puissance de calcul de la carte une seule fois, uniquement sa bande passante. Le décodage est memory-bound, la démonstration tient en une division.

D'où un pouvoir de prédiction sans tester : un modèle deux fois plus gros ira deux fois moins vite sur la même carte, parce qu'il y a deux fois plus de poids à relire à chaque token. Ce n'est pas une tendance, c'est un rapport.

Le KV cache, l'autre occupant de la VRAM

La VRAM ne contient pas que les poids. Elle héberge aussi le KV cache : les clés et valeurs d'attention des tokens déjà vus, gardées pour ne pas les recalculer à chaque nouveau token. Sa taille grandit avec la longueur des conversations et avec le nombre de sessions simultanées.

C'est ici que la grouped-query attention de Gemma 4 12B (8 têtes clé/valeur pour 16 têtes de requête, vue dans l'article sur les dimensions) paie : elle divise par deux l'empreinte du KV cache. Concrètement, sur cette configuration, l'espace laissé libre après le chargement des poids permet de tenir un cache de l'ordre de 254 000 tokens, répartis entre les sessions actives.

Deux conséquences à garder en tête. D'abord, le KV cache est souvent le vrai facteur limitant de la concurrence : ce qui sature une carte, ce n'est pas le nombre d'utilisateurs, c'est la somme de leurs caches. Ensuite, c'est précisément pour le gérer finement que vLLM utilise PagedAttention : le cache est alloué par petits blocs, façon pagination mémoire, au lieu d'un gros bloc contigu réservé par session, ce qui récupère la place autrement gaspillée.

La quantization, et pourquoi le test NVFP4 était prévisible

La quantization stocke chaque poids sur moins de bits. Le modèle en production est quantifié en NVFP4 (4 bits par poids, format nvfp4-pack-quantized visible dans son config.json). Deux effets, qui découlent directement de ce qui précède.

  • Sur la VRAM : moins de bits par poids, donc un fichier plus léger, donc plus de place pour le KV cache. Gain direct.
  • Sur le decode : moins de bits à relire par token, donc un débit plus élevé. Gain direct aussi, tant qu'on réduit vraiment le volume relu.

C'est ce dernier point qui rend un résultat de mesure limpide. En comparant deux quantizations de même largeur (même nombre de bits par poids), on observe un prefill jusqu'à 3 fois plus rapide, mais un decode strictement inchangé. Surprenant au premier abord, parfaitement logique ensuite : à nombre de bits égal, le volume de poids relu à chaque token est identique, donc le débit memory-bound ne peut pas bouger. Le gain de prefill, lui, vient de la phase compute-bound, qui profite d'un calcul mieux packé. Ce n'était pas un résultat bizarre, c'était la seule issue possible.

Où agissent les optimisations de serving

La quantization n'est qu'un levier parmi d'autres, et chacun agit à un endroit précis du pipeline. Savoir de quelle phase relève un problème dit quelle famille regarder :

  • Sur le prefill (compute-bound) : FlashAttention (une attention plus économe en mémoire et en calcul) et le chunked prefill, qui découpe les gros prompts.
  • Sur le decode (memory-bound) : le continuous batching (regrouper les requêtes en vol pour amortir la relecture des poids sur plusieurs sorties à la fois) et le speculative decoding (produire plusieurs tokens par passe).
  • Sur le KV cache : PagedAttention et les techniques de compression ou de partage.
  • À l'échelle du cluster : la prefill/decode disaggregation, qui place les deux phases sur des machines distinctes puisqu'elles n'ont pas les mêmes besoins matériels.

Ce que ça change pour le sizing

Trois règles pratiques tombent de tout ça, directement exploitables quand on dimensionne un serveur d'inférence.

La VRAM se dimensionne sur les paramètres totaux. Il faut loger tous les poids avant de répondre à quoi que ce soit, plus de la marge pour le KV cache. Un modèle de 17 Go quantifié réclame une carte de 32 Go pour laisser respirer les conversations. C'est une contrainte physique, pas une opinion.

Le débit se dimensionne sur la bande passante, pas sur les TOPS. Entre deux cartes, celle qui génère plus vite est celle qui a la plus grande bande passante mémoire, pas celle qui affiche le plus de téraflops. Pour du décodage, un chiffre marketing de puissance de calcul est trompeur.

La quantization est le premier levier, dans les deux sens. Passer en 4 bits gagne à la fois de la VRAM et du débit. Mais une fois à largeur fixée, on ne gratte plus de tokens par seconde en changeant de format : le plafond memory-bound est atteint.

Ces trois points prolongent les décisions d'architecture que j'ai détaillées dans Déployer un LLM on-premise. La mécanique interne du modèle et le choix du matériel ne sont pas deux sujets séparés : le second se déduit du premier. Et si l'idée est de bâtir ce type de stack pour des PME plutôt que pour sa propre infra, c'est exactement ce que je fais chez Sover.tech.