Aller au contenu
Kudos AI
Read in English
Construire un modèle de langue

L’attention et l’auto-attention

Requêtes, clés et valeurs construites depuis la base : pourquoi l’attention existe, comment se calcule l’attention par produit scalaire mis à l’échelle, pourquoi elle est divisée par la racine carrée de la dimension, et comment fonctionne le masquage causal, avec chaque matrice calculée et vérifiée.

8 min de lectureKudos AI

Prérequis : La rétropropagation et la descente de gradient

L’attention par produit scalaire mise à l’échelle, construite pas à pas sur l’exemple travaillé de l’article : la matrice des scores, la division par racine de deux, chaque ligne normalisée par softmax, et les valeurs mélangées dans la sortie.

L’auto-attention est le mécanisme qui a rendu possibles les modèles de langue modernes. Elle permet à chaque position d’une séquence de regarder toutes les autres et de décider, par elle-même, lesquelles comptent. Cet article la construit depuis le problème qu’elle résout jusqu’au calcul matriciel complet, en déroulant chaque nombre sur un exemple de trois tokens assez petit pour être vérifié à la main.

A. Le problème que l’attention résout

Pensez à résoudre à quoi renvoie « il » dans une phrase. L’information nécessaire siège ailleurs dans la séquence, et où elle siège dépend entièrement de la phrase. Une approche à fenêtre fixe ne peut pas exprimer « regarde en arrière vers le mot antérieur dont celui-ci dépend réellement ».

L’attention rend cette dépendance apprise et dépendante des données : chaque position calcule, à partir du contenu même des tokens, combien puiser à chaque autre position.

Explorateur des poids d’attention

Une requête portant sur quatre paires clé/valeur.

Poids le plus élevé
29.7%
Étalement de l’attention
0.990
Jetonq · k÷ √dₖPoids
the0.400.2022.0%
cat1.000.5029.7%
sat0.920.4628.5%
down0.200.1019.9%

Vecteur de requête

Mise à l’échelle par √dₖ

Sortie - somme pondérée des vecteurs de valeur

[0.220, 0.297, 0.285, 0.199]

L’attention est une moyenne pondérée, et le softmax en fixe les poids. Baissez la température et elle se concentre sur un seul jeton ; augmentez-la et l’attention se répartit uniformément. Désactiver la mise à l’échelle √dₖ écarte davantage les scores bruts et pousse le softmax vers la saturation : c’est précisément la raison d’être de ce facteur.

B. Requêtes, clés et valeurs

Chaque plongement de token d’entrée xi\mathbf{x}_i est projeté en trois vecteurs par trois matrices de poids apprises :

qi=Wq xi,ki=Wk xi,vi=Wv xi.\mathbf{q}_i = W_q\,\mathbf{x}_i, \qquad \mathbf{k}_i = W_k\,\mathbf{x}_i, \qquad \mathbf{v}_i = W_v\,\mathbf{x}_i .

L’analogie avec une base de données est réellement appropriée :

  • la requête est ce que cette position cherche ;
  • la clé est ce que chaque position annonce d’elle-même ;
  • la valeur est ce que chaque position apporte effectivement si l’on s’y intéresse.

Confronter une requête à une clé mesure la pertinence ; les valeurs sont ce qui se mélange. Séparer « ce qui identifie un token » (clé) de « ce qu’il apporte » (valeur) est ce qui donne au mécanisme sa souplesse - et Wq,Wk,WvW_q, W_k, W_v sont appris par la passe arrière de La rétropropagation et la descente de gradient.

C. L’attention par produit scalaire mise à l’échelle

Le mécanisme entier, pour toutes les positions à la fois :

Attention⁡(Q,K,V)=softmax⁡ ⁣(QK⊤dk)V.\operatorname{Attention}(Q, K, V) = \operatorname{softmax}\!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right)V .

Lisez-le en quatre étapes : scorer chaque requête contre chaque clé (QK⊤QK^\top), mettre à l’échelle par dk\sqrt{d_k}, normaliser chaque ligne pour qu’elle somme à 1 (softmax), puis prendre la moyenne des valeurs pondérée en conséquence.

Raschka note que cela s’appelle l’attention par produit scalaire mise à l’échelle, et que c’est le mécanisme employé dans le transformeur originel et dans la famille GPT.

D. Le dérouler complètement

Trois tokens, dk=2d_k = 2. Pour garder l’arithmétique vérifiable, nous prenons QQ et KK déjà projetés et égaux, avec des valeurs distinctes :

Q=K=[100111],V=[100231].Q = K = \begin{bmatrix}1 & 0\\ 0 & 1\\ 1 & 1\end{bmatrix}, \qquad V = \begin{bmatrix}1 & 0\\ 0 & 2\\ 3 & 1\end{bmatrix} .

Étape 1 - les scores d’attention. L’entrée (i,j)(i,j) vaut qi⋅kj\mathbf{q}_i \cdot \mathbf{k}_j :

QK⊤=[101011112].QK^{\top} = \begin{bmatrix}1 & 0 & 1\\ 0 & 1 & 1\\ 1 & 1 & 2\end{bmatrix} .

Vérifions-en une : ligne 3, colonne 3 vaut q3⋅k3=(1)(1)+(1)(1)=2\mathbf{q}_3\cdot\mathbf{k}_3 = (1)(1)+(1)(1) = 2, le plus grand score de la matrice - la requête du token 3 correspond le mieux à sa propre clé.

Étape 2 - mise à l’échelle. On divise par dk=2≈1.4142\sqrt{d_k} = \sqrt2 \approx 1.4142 :

QK⊤2=[0.707100.707100.70710.70710.70710.70711.4142].\frac{QK^\top}{\sqrt2} = \begin{bmatrix}0.7071 & 0 & 0.7071\\ 0 & 0.7071 & 0.7071\\ 0.7071 & 0.7071 & 1.4142\end{bmatrix} .

Étape 3 - softmax sur chaque ligne. Pour la ligne 1, l’exponentiation donne e0.7071=2.0281e^{0.7071} = 2.0281, e0=1e^{0} = 1, e0.7071=2.0281e^{0.7071} = 2.0281, de somme 5.05625.0562. En divisant :

[2.02815.0562, 15.0562, 2.02815.0562]=[0.4011, 0.1978, 0.4011].\left[\tfrac{2.0281}{5.0562},\ \tfrac{1}{5.0562},\ \tfrac{2.0281}{5.0562}\right] = [0.4011,\ 0.1978,\ 0.4011] .

Les trois lignes :

A=[0.40110.19780.40110.19780.40110.40110.24830.24830.5035].A = \begin{bmatrix} 0.4011 & 0.1978 & 0.4011\\ 0.1978 & 0.4011 & 0.4011\\ 0.2483 & 0.2483 & 0.5035 \end{bmatrix} .

Chaque ligne somme à 1 - ce sont de véritables pondérations. La ligne 3 met 0.50350.5035 sur la position 3, ce qui correspond au score le plus fort de l’étape 1.

Étape 4 - valeurs pondérées. On multiplie AA par VV. Ligne 1, première composante :

0.4011(1)+0.1978(0)+0.4011(3)=0.4011+1.2033=1.6044.0.4011(1) + 0.1978(0) + 0.4011(3) = 0.4011 + 1.2033 = 1.6044 . Attention⁡(Q,K,V)=[1.60440.79671.40111.20331.75871.0000].\operatorname{Attention}(Q,K,V) = \begin{bmatrix} 1.6044 & 0.7967\\ 1.4011 & 1.2033\\ 1.7587 & 1.0000 \end{bmatrix} .

Chaque ligne de sortie est un mélange des trois vecteurs de valeurs, dosé par une pertinence apprise.

Python

S'exécute dans votre navigateur. La première exécution télécharge l'environnement Python (~10 Mo), puis il est mis en cache.

Son exécution reproduit les deux matrices et affiche matches: True True.

E. Pourquoi diviser par la racine carrée de la dimension

La mise à l’échelle n’est pas cosmétique. Un produit scalaire de deux vecteurs de dimension dkd_k somme dkd_k termes : sa magnitude croît donc avec dkd_k - à peu près comme dk\sqrt{d_k} pour des composantes indépendantes de variance unité.

De grands scores posent problème au softmax. Quand les entrées grandissent, le softmax approche un vecteur one-hot : un poids proche de 1 et le reste proche de 0. Dans ce régime saturé, ses gradients sont minuscules et l’apprentissage cale. Diviser par dk\sqrt{d_k} maintient les scores dans une plage où le softmax reste sensible et où les gradients continuent de circuler.

Avec dk=64d_k = 64, les scores non mis à l’échelle seraient environ huit fois plus grands que les scores mis à l’échelle - largement de quoi saturer.

La figure ci-dessous est cette même tête, avec deux réglages. Tourner la requête du jeton trois la fait pivoter dans le plan où vivent ses clés, et seule la troisième ligne de la matrice réagit : les clés et les valeurs ne bougent pas, et c’est précisément ce qui fait d’une requête une requête. Augmentez ensuite la dimension. Avec la division, il ne se passe rien du tout : la matrice en dimension 512 est celle ci-dessus, à la dernière décimale près. Coupez la division et recommencez : la ligne s’effondre sur un seul jeton. Cet effondrement est toute la raison d’être de la racine carrée.

Interactif : orientez une requête, puis changez la dimension

Seule la requête du jeton 3 bouge. Clés et valeurs restent où la leçon les a mises.

Poids d’attention, ligne par ligne

jeton 10.40110.19780.4011jeton 20.19780.40110.4011jeton 30.24830.24830.5035jeton 1jeton 2jeton 3
Diviseur
1.4142
Poids maximal
0.5035
Étalement ligne 3
1.496 bits
Sortie ligne 3
1.76, 1.00

Avec la division, la matrice d’attention ne dépend pas du tout de d_k : faites glisser la dimension de 2 à 512, aucun poids ne bouge. Cette invariance est tout le propos de la racine carrée : elle maintient les scores à une taille constante alors que les produits scalaires bruts croissent comme sqrt(d_k), et le softmax reste dans la plage où il a encore un gradient à rendre. La ligne 3 est étalée sur 1.496 bits sur les 1,585 possibles.

F. Le masquage causal

Un modèle qui engendre du texte de gauche à droite ne doit pas voir le futur. Si la position 2 pouvait s’intéresser à la position 3, le modèle serait entraîné avec accès à la réponse et échouerait au moment de la génération, quand le futur n’existe réellement pas.

L’attention causale l’empêche par masquage : avant le softmax, tout score en j>ij > i est mis à −∞-\infty, de sorte que e−∞=0e^{-\infty} = 0 et que ces positions reçoivent un poids nul. Les poids restants se renormalisent pour sommer à 1.

En l’appliquant à nos scores mis à l’échelle :

Acausal=[1.0000000.33020.669800.24830.24830.5035].A_{\text{causal}} = \begin{bmatrix} 1.0000 & 0 & 0\\ 0.3302 & 0.6698 & 0\\ 0.2483 & 0.2483 & 0.5035 \end{bmatrix} .

La ligne 1 ne s’intéresse qu’à elle-même, son poids est donc forcé à 11. La ligne 2 se répartit entre les positions 1 et 2 - notez que ce ne sont pas les valeurs non masquées de la ligne 2, 0.19780.1978 et 0.40110.4011 ; la position 3 étant retirée, les deux restantes sont renormalisées par leur propre somme, 0.1978+0.4011=0.59890.1978 + 0.4011 = 0.5989, donnant 0.33020.3302 et 0.66980.6698. (Reporter les valeurs arrondies à la main donne 0.33030.3303 et 0.66970.6697 ; les chiffres ci-dessus viennent du calcul en pleine précision.) La ligne 3, qui n’a jamais eu le droit de voir au-delà de la position 3, est inchangée.

Raschka note aussi qu’un masque de dropout est souvent appliqué aux poids d’attention pendant l’entraînement, afin de réduire le surajustement.

G. Plusieurs têtes

Un calcul d’attention capture un type de relation. L’attention multi-têtes en exécute plusieurs en parallèle avec des Wq,Wk,WvW_q, W_k, W_v distincts, puis concatène les sorties et les reprojette vers le bas. Différentes têtes peuvent se spécialiser - l’une suivant des dépendances syntaxiques, l’autre des liens thématiques à plus longue portée - et le modèle n’est pas contraint de comprimer toutes les relations dans une pondération unique.

À retenir

  • L’attention laisse chaque position décider, à partir du contenu, combien puiser à chaque autre position.
  • Chaque token est projeté en une requête, une clé et une valeur ; les requêtes se confrontent aux clés, et ce sont les valeurs qui se mélangent.
  • Le mécanisme est softmax⁡(QK⊤/dk)V\operatorname{softmax}(QK^\top/\sqrt{d_k})V - scorer, mettre à l’échelle, normaliser, mélanger.
  • Le diviseur dk\sqrt{d_k} prévient la saturation du softmax et garde des gradients exploitables.
  • Le masquage causal met les scores futurs à −∞-\infty, de sorte que les poids se renormalisent sur le seul passé.
  • L’attention multi-têtes en exécute plusieurs en parallèle pour capturer différentes relations.

La suite

L’attention est le cœur du transformeur, mais un modèle de langue opérationnel a aussi besoin de tokenisation, de plongements, d’information positionnelle, de blocs à propagation avant et d’un objectif d’entraînement. Ces pièces sont couvertes dans le reste de cette série, et le pendant en IA classique de « fouiller l’espace des possibles » est développé dans La recherche adversariale et le minimax.

Références et lectures complémentaires

  • Sebastian Raschka, Build a Large Language Model (From Scratch), Manning, 2025· Bibliothèque de référence Kudos AI

Les œuvres protégées par le droit d’auteur sont citées à titre de référence uniquement et ne sont pas hébergées ici ; veuillez consulter l’éditeur pour y accéder.

Lecture associée

10 min de lectureConstruire un modèle de langue

L’architecture du transformeur

Assembler un GPT à partir de l’attention : projections multi-têtes, normalisation de couche déroulée à la main, pourquoi les connexions de raccourci sauvent le gradient, l’expansion x4 du réseau à propagation avant, et un décompte de paramètres qui reproduit exactement les 124 millions de GPT-2 small.

IA générativeApprentissage profondTraitement du langage naturel
8 min de lectureConstruire un modèle de langue

La tokenisation et les plongements

Comment le texte devient des nombres sur lesquels un modèle peut s’entraîner : construire un vocabulaire, pourquoi le codage par paires d’octets n’a jamais besoin d’un token inconnu, la couche de plongement comme une consultation qui est prouvablement un one-hot fois une matrice, et pourquoi la position doit être réinjectée à la main.

IA générativeTraitement du langage naturelApprentissage profond
8 min de lectureConstruire un modèle de langue

Le préentraînement et l’affinage

Comment la prédiction du mot suivant transforme du texte non annoté en supervision, pourquoi l’entropie croisée n’est que l’opposé de la log-probabilité moyenne, ce que mesure vraiment la perplexité, et pourquoi un modèle qui complète le texte avec fluidité ne sait toujours pas suivre une instruction.

IA générativeApprentissage profond
← Retour à tous les articles