Performance
Le cache KV (Key-Value) expliqué
Comment le cache KV accélère l'inférence, comment calculer sa taille, et comment le compresser : GQA, MLA, quantification q8_0/fp8, SWA, PagedAttention.
Le cache KV est une optimisation fondamentale qui évite de recalculer les vecteurs Key et Value des tokens précédents à chaque étape de génération, accélérant drastiquement l’inférence. C’est aussi le poste mémoire qui décide de la longueur de contexte qu’on peut tenir.
Le problème de la génération autorégressive
Un LLM génère du texte un token à la fois. À chaque nouveau token, le mécanisme d’attention doit considérer tous les tokens précédents. Sans optimisation, cela implique de recalculer les mêmes valeurs encore et encore.
Étape 1: "Bonjour" → Calcule K,V pour "Bonjour"
Étape 2: "Bonjour je" → Recalcule K,V pour "Bonjour" + calcule pour "je"
Étape 3: "Bonjour je suis" → Recalcule K,V pour "Bonjour", "je" + calcule "suis"
...
→ Complexité quadratique O(n²) en nombre de tokens
La solution : le cache KV
L’idée est simple : stocker (cacher) les vecteurs Key et Value une fois calculés, puis les réutiliser aux étapes suivantes. Seul le nouveau token nécessite un calcul.
Étape 1: "Bonjour" → Calcule K₁,V₁ → Cache: [K₁,V₁]
Étape 2: + "je" → Calcule K₂,V₂ → Cache: [K₁,K₂], [V₁,V₂]
Étape 3: + "suis" → Calcule K₃,V₃ → Cache: [K₁,K₂,K₃], [V₁,V₂,V₃]
...
→ Complexité linéaire O(n) par nouveau token
Le cache KV échange mémoire contre calcul : l’inférence devient linéaire au lieu de quadratique, mais le cache peut devenir volumineux pour de longues séquences.
Calcul de la taille du cache KV
La taille du cache dépend de plusieurs facteurs architecturaux du modèle :
Taille_KV = 2 × n_layers × n_heads × head_dim × seq_len × batch_size × bytes
| Modèle | Couches | Têtes | Cache/token (FP16) | Cache 4K tokens |
|---|---|---|---|---|
| Qwen3-4B | 36 | 8 (GQA) | ~144 Ko | ~0,6 Go |
| Qwen3.6-27B (dense) | 64 | 8 (GQA) | ~262 Ko | ~1,0 Go |
| Qwen3.6-35B-A3B (MoE) | 48 | 4 (GQA) | ~98 Ko | ~0,4 Go |
| Llama 4 Scout (17B/109B MoE) | 48 | 8 (GQA) | ~196 Ko | ~0,8 Go |
| DeepSeek V3 (MoE 671B) | 61 | MLA — ~70 Ko / token | ~0,3 Go |
À noter : un MoE comme Qwen3.6-35B-A3B a un cache KV plus petit qu’un dense de taille proche, parce qu’il a moins de têtes K/V et moins de couches d’attention par token.
Optimisations du cache KV
1. Quantification du cache
Au lieu de stocker les K/V en FP16, on peut les quantifier, réduisant la mémoire de 2x à 4x. C’est l’optimisation la plus accessible.
# llama.cpp
./llama-cli -m model.gguf \
--cache-type-k q8_0 \ # Quantifie les Keys
--cache-type-v q8_0 \ # Quantifie les Values
--flash-attn # Requis pour quantifier V
# vLLM
vllm serve model --kv-cache-dtype fp8_e5m2
En pratique, q8_0 (llama.cpp) et fp8_e5m2 (vLLM) sont le bon défaut : ~41,7 Kio/token, ce qui double le contexte tenable à VRAM constante. À titre d’exemple, lors de nos tests cela permet de tenir 82 227 tokens dans 3,3 Gio de cache. Les variantes plus agressives (q4_0, 3 bits) sont à éviter : la perte de qualité devient visible, surtout en extraction de données et en tool-calling.
2. Grouped-Query Attention (GQA)
GQA partage les vecteurs K/V entre plusieurs têtes Query. Au lieu d’avoir 32 K/V pour 32 têtes, on peut n’en avoir que 8, réduisant le cache de 4x. Tous les modèles 2026 (Qwen3.6, Llama 4, Gemma 3) utilisent GQA.
3. MLA — Multi-head Latent Attention (DeepSeek V2/V3)
MLA compresse K et V dans un espace latent de très basse dimension (typiquement 512), puis projette à la volée vers les K/V de chaque tête lors du calcul d’attention.
MHA (Llama 2) : cache 2 × n_heads × head_dim par token
Llama 2 70B → ~2,5 Mo / token
GQA (Llama 3, Qwen3) : cache 2 × n_kv_heads × head_dim par token
Qwen3.6-27B → ~262 Ko / token (×10 vs MHA)
MLA (DeepSeek V3) : cache d_latent + d_rope par token
DeepSeek V3 671B → ~70 Ko / token (×35 vs MHA équivalente)
Le cache KV de DeepSeek V3 est plus petit qu’un Llama 8B alors que le modèle pèse 671B paramètres. Cela permet des contextes très longs (128K) sur du matériel modeste, au prix d’une décompression supplémentaire à chaque step — opération fusionnée avec FA3/FA4 pour rester quasi gratuite.
Empiriquement, K et V sont fortement redondants : leur rang effectif est très inférieur à n_heads × head_dim. MLA exploite cette redondance via une décomposition de bas rang, similaire à l’idée de LoRA pour le fine-tuning.
4. Sliding Window Attention (SWA)
Limite l’attention aux W derniers tokens uniquement. Le cache ne dépasse jamais W tokens, permettant des contextes théoriquement infinis. Mistral Small 3.2 alterne SWA / attention complète tous les 4 layers pour préserver la qualité tout en bornant le cache.
5. PagedAttention (vLLM)
Inspiré de la pagination mémoire des OS, PagedAttention alloue le cache par blocs non-contigus, évitant le gaspillage de mémoire pré-allouée. C’est ce qui permet à vLLM de supporter des centaines de requêtes concurrentes sans réservation pessimiste.
6. KIVI et quantification 2-bit du KV cache
KIVI (2024) propose une quantification asymétrique du KV cache : K en per-channel et V en per-token. Cette asymétrie reflète l’observation que K a des outliers stables par channel, alors que V est plus uniforme.
- 2-bit KIVI : -7x mémoire vs FP16, perte de qualité < 1 % en perplexité.
- 4-bit KIVI : -4x mémoire, perte indétectable sur la plupart des benchmarks.
- Supporté nativement dans vLLM 0.7+ et SGLang.
7. CacheBlend et prefix caching
Les serveurs modernes (vLLM, SGLang, TensorRT-LLM) supportent le prefix caching : quand plusieurs requêtes partagent un même préfixe (system prompt, contexte RAG, conversation multi-tour), le KV cache de ce préfixe est calculé une seule fois et réutilisé. Sur les workloads d’agents (où le system prompt est massif), le speedup peut atteindre x5.
Combiner les techniques
En production sur Qwen3.6-27B avec un contexte 128K, la combinaison GQA + KIVI 4-bit + Flash Attention 4 + prefix caching divise typiquement la VRAM nécessaire au KV cache par 6 à 8x par rapport à un cache FP16 naïf, permettant de passer d’un H200 à un simple RTX 5090.
Sur RTX 3090 (24 Go), c’est cette logique qui permet de servir Qwen3.6-27B quantifié (int4 AutoRound, vLLM) avec un contexte de 81k tokens, alors qu’un cache FP16 naïf saturerait la carte bien avant.