C’est l’une des questions d’entretien technique les plus classiques de l’ingénierie LLM. Pour y répondre avec précision, il faut disséquer la nature autorégressive des modèles et la manière dont l'attention calcule l'état caché final à chaque étape de génération.
Les LLM fonctionnent de manière autorégressive : chaque jeton est prédit séquentiellement, un par un, en se basant sur l'ensemble des jetons précédents. Un passage avant (forward pass) sur n jetons produit n états cachés, mais seul le dernier est projeté sur l'espace du vocabulaire pour générer le jeton suivant.
Pour comprendre pourquoi seules les clés (K) et les valeurs (V) sont mises en cache tandis que les requêtes (Q) sont jetées, suivons le déroulement complet avec un prompt d’exemple de 10 jetons.
1. La phase de pré-remplissage (Prefill)
Tous les jetons du prompt traversent le modèle en un seul passage avant et en parallèle grâce au masquage causal. À chaque couche, chacune des 10 positions génère un vecteur de requête (Q), une clé (K) et une valeur (V).
L’attention à chaque position s’exécute contre toutes les positions précédentes et actuelles. Cette phase est extrêmement lourde en calculs et détermine le temps de la première réponse, connu sous le nom de TTFT (Time to First Token).
2. La génération du premier jeton de sortie (Jeton 11)
Pour prédire le 11e jeton, seul l’état caché du 10e jeton est nécessaire. Cet état est extrait, projeté vers la dimension du vocabulaire, puis transformé en logits et soumis à un échantillonnage.
L'état caché final correspond à la dernière ligne de la sortie du bloc d'attention. Pour comprendre d'où elle vient, il faut examiner la matrice d'attention.
3. La structure de la matrice d'attention
Le produit QK^T pour un prompt de 10 jetons génère une matrice de 10 \times 10. La ligne i contient le produit scalaire de la requête i avec l'ensemble des clés.
Pour la 10e position, la ligne contient :
Q_{10} \cdot K_1, \quad Q_{10} \cdot K_2, \quad \dots, \quad Q_{10} \cdot K_{10}
On observe ici que seul Q_{10} est requis. Les vecteurs Q_1 à Q_9 appartiennent aux lignes 1 à 9, qui ont déjà été traitées, converties en logits pour les jetons précédents, et définitivement abandonnées.
Cette dernière ligne d'attention est ensuite multipliée par la pile complète des vecteurs de valeurs (V_1 à V_{10}). L'état caché final dépend donc exclusivement de trois éléments : Q_{10}, l'ensemble des clés (K_1 à K_{10}), et l'ensemble des valeurs (V_1 à V_{10}).
4. La génération du jeton 12 et l'invariance du cache
Lorsque le jeton 11 est ajouté pour générer le jeton 12, le modèle a besoin de l'état caché de la ligne 11. Mathématiquement, l'attention calcule Q_{11} contre K_1 jusqu’à K_{11}, le tout multiplié par V_1 jusqu’à V_{11}.
Grâce au masquage causal, les clés et les valeurs des jetons précédents ne changent jamais : K_1 à K_{11} et V_1 à V_{11} sont rigoureusement identiques à ce qui a été produit lors des étapes antérieures.
5. La logique du cache KV
Puisque les vecteurs K et V du passé restent inchangés et sont constamment réutilisés pour chaque nouveau jeton, il est indispensable de les stocker dans un cache pour éviter de les recalculer.
À l'inverse, le vecteur de requête Q est strictement éphémère. À chaque étape de décodage, un nouveau jeton arrive, génère un unique vecteur Q pour cette position précise, qui sert immédiatement au calcul de l'attention avant d'être jeté. Il n'est donc d'aucune utilité de le placer en cache.
Au-delà du cache KV, l'infrastructure de serving des LLM intègre d'autres niveaux d'optimisation essentiels, tels que la mise en cache des préfixes au niveau du serveur, la gestion des prompts facturés par les fournisseurs, et les caches sémantiques capables de court-circuiter entièrement le modèle pour les requêtes similaires.
Par Aghilas AZZOUG
