Un LLM ne « répond » pas d'un bloc. Il prédit un token, l'ajoute à son propre contexte, recommence — jusqu'à produire une réponse entière un mot (ou fragment de mot) à la fois. Cette mécanique explique presque tout ce qui semble arbitraire dans une API LLM : pourquoi le premier mot met du temps à arriver, pourquoi la suite defile plus vite, pourquoi les quotas se comptent en tokens, et pourquoi un contexte long plombe la latence.

Deux phases, deux profils de calcul

Une inférence se déroule en deux temps bien distincts. Le prefill traite tout le prompt d'entrée en une seule passe : le modèle calcule en parallèle les représentations de chaque token du prompt, puisqu'ils sont tous déjà connus au départ. C'est massivement parallélisable, donc rapide sur GPU.

Le decode est l'inverse. Chaque token généré nécessite sa propre passe complète dans le réseau, et cette passe ne peut commencer qu'une fois le token précédent choisi — impossible de paralléliser, puisque le token N+1 dépend du token N. Générer une réponse de 200 tokens, c'est 200 passes séquentielles.

Prefill t1 t2 t3 t4 1 forward passtout le prompt, en parallèle 1er token compute-bound Decode token N 1 forward pass+ lecture cache KV token N+1 cache KV += 1 token à chaque tour memory-bound
Le prefill traite tout le prompt en une passe parallélisable. Le decode répète une passe séquentielle par token généré, chacune relisant un cache qui grossit à chaque tour — deux régimes de performance radicalement différents pour un seul et même modèle.

Le cache KV : ne pas tout recalculer à chaque tour

L'attention, le mécanisme au cœur d'un transformer, compare chaque token à tous ceux qui le précèdent via des vecteurs clé (K) et valeur (V). Sans optimisation, générer le token N+1 obligerait à recalculer les K/V de tous les tokens précédents — prompt compris — à chaque nouveau tour. Sur une réponse de 500 tokens, ce serait des centaines de recalculs redondants.

Le cache KV évite ça : une fois calculés, les vecteurs K/V de chaque token sont conservés en mémoire GPU. À chaque nouveau tour, le modèle ne calcule que les K/V du token qui vient d'être généré, et les compare au cache déjà accumulé. C'est ce qui rend le decode praticable — mais ça a un coût : ce cache grossit à chaque token et doit être relu intégralement à chaque passe. C'est pour ça que le decode est memory-bound : le GPU passe plus de temps à lire cette mémoire qu'à calculer.

PrefillDecode
TraiteTout le prompt d'un coupUn seul token nouveau par passe
ParallélisableOui, sur tous les tokens du promptNon, chaque token dépend du précédent
GoulotCalcul (compute-bound)Lecture du cache KV (memory-bound)
Forward pass1 seul1 par token généré
Métrique associéeTTFT (time to first token)TPOT (latence entre tokens)
Deux métriques de latence différentes selon la phase, et c'est exactement ce que révèle un flux streamé en SSE côté client : un temps d'attente initial (prefill), puis un défilement token par token (decode).

Pourquoi le contexte long ralentit et coûte plus cher

Le cache KV occupe de la mémoire GPU proportionnelle au nombre de tokens déjà traités — prompt et réponse compris. Plus le contexte est long, plus ce cache est volumineux, plus il faut de temps pour le relire à chaque tour de decode, et moins de requêtes concurrentes tiennent sur le même GPU. Un prompt de 30 000 tokens n'est pas juste « plus de texte à lire » : c'est un cache KV plus lourd, une latence par token plus élevée, et un coût qui grimpe en conséquence.

C'est précisément pour ça qu'un provider LLM facture et limite en tokens plutôt qu'en requêtes — le coût réel de calcul suit le nombre de tokens traités, pas le nombre d'appels (voir rate limiting d'une API LLM). Certains providers proposent un prompt caching qui réutilise directement le cache KV d'un préfixe identique entre deux appels, pour éviter de repayer le prefill à chaque fois — à ne pas confondre avec le cache sémantique, qui évite l'appel entier en matchant des questions proches par similarité, pas en réutilisant des calculs internes au modèle.

Alléger la mémoire : la quantification

Les poids du modèle et le cache KV occupent tous deux de la mémoire GPU, et c'est souvent cette mémoire — pas la puissance de calcul brute — qui limite ce qu'on peut faire tenir et à quelle vitesse on peut le servir. La quantification réduit la précision numérique de ces poids (32 ou 16 bits vers 8 ou 4 bits), ce qui réduit d'autant l'empreinte mémoire et accélère les lectures. C'est le même principe qui permet de finetuner un modèle 7B en 4 bits sur un GPU grand public : moins de bits à lire à chaque passe, plus de marge pour le cache KV, une inférence plus rapide au prix d'une précision numérique légèrement dégradée.

Du logit au mot choisi : l'échantillonnage

À chaque passe, le modèle ne sort pas directement un token : il sort une distribution de probabilité sur tout le vocabulaire (des dizaines de milliers de tokens possibles), un score — un logit — par token candidat. Reste à choisir lequel devient le token suivant.

  • Greedy decoding : toujours prendre le token le plus probable. Déterministe, mais répétitif et parfois terne sur des tâches ouvertes.
  • Température : aplatit ou accentue la distribution avant de tirer au sort. Température basse → proche du greedy ; température haute → plus de diversité, plus de risque de dérive.
  • Top-p / top-k : restreint le tirage aux tokens les plus probables (les k meilleurs, ou ceux qui cumulent p % de probabilité) avant d'échantillonner, pour éviter de piocher dans la longue traîne improbable.

Dès que la température dépasse zéro, la génération n'est plus déterministe : le même prompt peut produire deux réponses différentes à deux appels distincts. C'est une des raisons pour lesquelles un appel LLM n'est pas idempotent par nature — même sans effet de bord côté application, le texte produit lui-même peut varier d'un tour à l'autre.

Le batching : amortir un coût déjà payé

Puisque le decode est memory-bound plutôt que compute-bound, le GPU a du calcul disponible pendant qu'il attend de lire la mémoire. Le batching continu exploite cette marge en traitant plusieurs requêtes en parallèle dans la même passe de decode : chaque requête garde son propre cache KV, mais le coût de lecture mémoire par passe est amorti sur plusieurs sorties simultanées. C'est ce qui permet à un provider de servir beaucoup de trafic sans que la latence par requête explose — au prix d'une latence légèrement variable selon la charge du moment.

FAQ

Pourquoi un LLM répond-il token par token plutôt que d'un coup ?

Parce que la génération est autoregressive : chaque token est produit par une passe qui prend en entrée tous les tokens précédents, prompt et réponse déjà générée compris. Le modèle ne peut pas connaître le token N+1 avant d'avoir choisi le token N.

Pourquoi la génération ralentit-elle sur un contexte long ?

Parce que le cache KV grossit avec le nombre de tokens déjà traités, et qu'il doit être relu intégralement à chaque nouveau token généré. Plus le contexte est long, plus cette lecture mémoire pèse sur chaque passe de decode.

Pourquoi les quotas d'API sont-ils comptés en tokens et pas en requêtes ?

Parce que le coût de calcul réel dépend du nombre de tokens traités en prefill et generés en decode, pas du nombre d'appels — deux requêtes d'une phrase coûtent bien moins qu'une requête avec 20 000 tokens de contexte.