Aller au contenu

Le cache clé-valeur

L'intuition

Un modèle de langage écrit un jeton à la fois. Pour écrire le mot suivant, il relit tout ce qui précède. Sans précaution, écrire une réponse de mille mots reviendrait à relire le contexte mille fois.

Le cache clé-valeur est l'astuce qui évite ce gaspillage : pour chaque jeton déjà lu, on conserve en mémoire le résultat d'un calcul intermédiaire, de sorte que le jeton suivant puisse l'utiliser sans tout refaire.

Le prix de cette astuce est de la mémoire, et c'est cette mémoire qui limite aujourd'hui la longueur des contextes.

Le mécanisme d'attention

Dans une couche d'attention, chaque jeton produit trois vecteurs :

\[ Q = XW_Q, \qquad K = XW_K, \qquad V = XW_V \]

où \(X\) est la représentation du jeton, de dimension \(d\) (5 120 pour DeepSeek-V4.1-Flash), et \(W_Q\), \(W_K\), \(W_V\) sont des matrices apprises.

Symbole Nom Rôle intuitif
\(Q\) requête (query) ce que ce jeton cherche
\(K\) clé (key) ce que ce jeton offre aux autres
\(V\) valeur (value) l'information que ce jeton transmet
\(d_k\) dimension des clés fixe l'échelle du produit scalaire

La sortie de l'attention est :

\[ \operatorname{Attention}(Q,K,V) = \operatorname{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V \]

En clair : chaque requête compare son vecteur à toutes les clés, transforme ces comparaisons en poids qui somment à 1, et récupère la moyenne pondérée des valeurs correspondantes.

Pourquoi \(\sqrt{d_k}\) ?

Le produit scalaire de deux vecteurs aléatoires de dimension \(d_k\) a une amplitude qui croît comme \(\sqrt{d_k}\). Sans division, la fonction softmax saturerait dès que \(d_k\) devient grand et les gradients disparaîtraient.

Pourquoi il faut un cache

Quand le modèle génère le jeton \(t+1\), seule sa requête est nouvelle. Les clés et valeurs des jetons \(1\) à \(t\) sont identiques à celles calculées à l'étape précédente : elles ne dépendent que des jetons eux-mêmes, pas de la position d'écriture courante.

On les conserve donc. À chaque nouveau jeton, on ajoute une clé et une valeur au cache, et on calcule une seule ligne d'attention au lieu de \(t\).

Sans cache, générer \(n\) jetons coûte \(O(n^3)\) opérations d'attention. Avec cache, \(O(n^2)\). Le cache n'est pas une optimisation marginale : sans lui, la génération longue est impraticable.

Combien cela pèse

Pour une attention multi-têtes classique, la taille du cache est :

\[ \text{octets par jeton} = 2 \times L \times h_{kv} \times d_h \times b \]
Symbole Signification Exemple : DeepSeek-V1 67B
\(2\) on stocke \(K\) et \(V\) —
\(L\) nombre de couches 95
\(h_{kv}\) nombre de têtes clé-valeur 8
\(d_h\) dimension d'une tête 128
\(b\) octets par nombre 2 (BF16)

Soit :

\[ 2 \times 95 \times 8 \times 128 \times 2 = 389\,120\ \text{octets par jeton} \]

380 Kio pour un seul jeton. À un million de jetons de contexte, cela fait 372 Gio — plus que la mémoire de deux GPU H100 réunis, rien que pour le cache d'une seule requête.

À retenir

Le cache clé-valeur croît linéairement avec la longueur du contexte et linéairement avec le nombre de couches. C'est cette double proportionnalité qui rend le contexte d'un million de jetons difficile, bien plus que le coût de calcul de l'attention.

Les trois dimensions de compression

Le rapport technique de DeepSeek formule explicitement le problème comme un produit de trois facteurs, chacun réductible indépendamment :

Dimension Question Techniques connues
Taille d'une entrée combien d'octets par jeton et par couche ? GQA, MQA, MLA, quantification
Dimension séquentielle faut-il une entrée par jeton ? compression de \(m\) jetons en une entrée
Dimension des couches faut-il un cache par couche ? partage entre couches

L'originalité de CSA2 est d'attaquer les trois à la fois :

  • taille d'une entrée : un latent unique de 512 canaux, en FP4 ;
  • dimension séquentielle : compression de ratio 2 dans l'encodeur ;
  • dimension des couches : 4 couches sources sur 40.

Le produit de ces trois réductions est ce qui fait passer de 380 Kio à 890 octets.

Deux caches, deux contraintes

DeepSeek distingue soigneusement deux caches, qui n'obéissent pas aux mêmes règles :

  • le cache global (global KV) — le cache principal plus les clés d'indexeur — qui doit rester en mémoire vive graphique pendant toute la génération. C'est lui qui vaut 890 octets par jeton ;
  • le cache persistant, écrit sur SSD ou en mémoire hôte entre deux tours de conversation, pour éviter de recalculer un préfixe déjà vu. Sa contrainte n'est pas la HBM mais la capacité disque et la bande passante d'entrée-sortie.

Les optimisations qui les visent sont différentes : CSA2 et le FP4 réduisent le premier, SWA Bounded Replay réduit le second. Confondre les deux mène à des comparaisons fausses.


Chapitre suivant : Attention creuse et fenêtre glissante