English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

invité
1 / ?
retour aux leçons

Requête, Clé, Valeur

Trois Transformations Linéaires à Partir de la Même Entrée

Après l'embedding (activité 4), chaque position porte un vecteur 768-dimensionnel x_t. L'attention commence par produire trois projections distinctes de x :


Q (requête) : que veut savoir cette position ?


K (clé) : que cette position offre-t-elle aux autres positions ?


V (valeur) : quel contenu cette position fournit-elle si elle est prise en compte ?


Chaque projection provient d'une matrice de poids apprise :


Q = x · W_Q     # Forme de W_Q : (d_model, d_k)
K = x · W_K     # Forme de W_K : (d_model, d_k)
V = x · W_V     # Forme de W_V : (d_model, d_k)

Trois matrices, toutes entraînées par rétropropagation. Un modèle apprend : à cette position, quelle requête récupère le mieux le contexte passé utile ? Quelle clé annonce bien le contenu de cette position ? Quelle valeur fournit si sélectionnée ?


Scaled dot-product attention


Une analogie avec une bibliothèque

Imaginez un catalogue de cartes de bibliothèque. Vous entrez avec un sujet en tête (votre query). Chaque carte liste des mots-clés (une key). Quand votre sujet correspond aux mots-clés d'une carte, vous prenez le contenu du livre (une value). L'attention fait cela pour chaque token en parallèle : chaque position interroge chaque autre position, classe l'alignement, & récupère une combinaison pondérée de vecteurs de valeurs.


Dimensions ANDREA-120M


QuantitéValeurNotes
d_model768Taille du vecteur à chaque position
n_head12Têtes d'attention parallèles
d_k64Dimension par tête (= d_model / n_head)
T1024Longueur du contexte

d_k = d_model / n_head = 768 / 12 = 64. Chaque tête voit une tranche 64-dimensionnelle d'un espace complet de 768 dimensions. L'activité 6 (grow_a_language_model_multi_head) couvre en détail la division par tête.

Calculer d_k

Calculez d_k pour deux variantes ANDREA. (a) ANDREA-12M : d_model = 384, n_head = 12. (b) ANDREA-480M : d_model = 1536, n_head = 24. Montrez votre formule d_k = d_model / n_head pour chacune.

Pourquoi diviser par sqrt(d_k)

Une matrice de scores

Une fois que Q & K existent (chacune de forme (T, d_k)), l'attention calcule une matrice de scores :


scores = Q · K^T     # forme : (T, T)

scores[i, j] = à quel point la requête de la position i s'aligne fortement avec la clé de la position j. Chaque paire (i, j) obtient un score : 1024 × 1024 = 1 048 576 scores par tête d'attention par passage avant.


Pourquoi diviser

Les produits scalaires de deux vecteurs unitaires aléatoires de dimension d ont une magnitude de l'ordre de sqrt(d). Sans mise à l'échelle, les scores croissent avec d_k :


- d_k = 64 : produits scalaires typiques de l'ordre de 8.

- d_k = 256 : produits scalaires typiques de l'ordre de 16.

- d_k = 4096 : produits scalaires typiques de l'ordre de 64.


Des scores élevés produisent un softmax pointu (une position domine, les gradients disparaissent ailleurs). L'entraînement s'arrête. Le scaling corrige une magnitude :


scaled_scores = (Q · K^T) / sqrt(d_k)

Pour ANDREA-120M, sqrt(d_k) = sqrt(64) = 8. Chaque score est divisé par 8. Les magnitudes restent à une échelle unitaire approximativement, indépendamment de d_k. Softmax reste bien comporté. Les gradients circulent.


Justification originale de Vaswani

Extrait de Attention Is All You Need (2017) : « Pour de grandes valeurs de d_k, les produits scalaires deviennent grands en magnitude, poussant la fonction softmax dans des régions où elle a des gradients extrêmement petits. » Un diviseur sqrt(d_k) contrebalance cette croissance.


Une vue en code

À l'intérieur de microgpt_cuda.cu, cet échelonnage apparaît comme une division littérale :


scores[i][j] = dot(Q[i], K[j]) * (1.0f / sqrtf(d_k));

Une multiplication float par score. Peu coûteux. Critique.

Échelle à d_model = 4096

Supposons qu'une équipe de recherche construise ANDREA-2B avec d_model = 4096 & n_head = 32. (a) Calculez d_k. (b) Calculez sqrt(d_k). (c) Expliquez en une phrase ce qui se passerait si une équipe oubliait de diviser par sqrt(d_k) à cette échelle.

Pourquoi la position i ne peut pas voir la position j > i

Une contrainte née de la génération

ANDREA génère un token à la fois. À l'inférence, la position 0 produit un premier token, puis la position 1 voit la sortie de la position 0 & produit un second token, & ainsi de suite. Un modèle n'a jamais accès aux tokens futurs pendant la génération.


L'entraînement doit refléter cela. Si pendant l'entraînement la position 5 pouvait prêter attention à la position 6, un modèle apprendrait un raccourci : « prédire le token 6 en lisant le token 6 ». À l'inférence, ce raccourci disparaît (le token 6 n'existe pas encore). Le comportement entraînement-versus-inférence d'un modèle divergerait de manière catastrophique.


Un Masque

Un masque causal bloque l'attention de toute position i vers toute position j > i. Implémentation : définir scaled_scores[i][j] = -infini partout où j > i. Après softmax, ces entrées deviennent exp(-inf) = 0. Le masque annule proprement l'attention aux positions futures.


pour i dans range(T):
pour j dans range(T):
si j > i:
scaled_scores[i][j] = -1e9   # effectivement -inf

Après softmax (par ligne), chaque ligne somme à 1, mais seules les entrées [0, i] portent la masse de probabilité. La position i mélange l'information uniquement des positions passées.


Visualisation d'un Masque

Une matrice de scores de forme (T, T) avec masque appliqué ressemble à une structure triangulaire inférieure :


scaled_scores après masque, softmax ligne par ligne :

ligne 0 :  [1.0, 0,   0,   0,   ...]   # ne voit que lui-même
ligne 1 :  [0.4, 0.6, 0,   0,   ...]   # voit les positions 0, 1
ligne 2 :  [0.2, 0.3, 0.5, 0,   ...]   # voit 0, 1, 2
ligne 3 :  [0.1, 0.2, 0.3, 0.4, ...]   # voit 0, 1, 2, 3
...

Distribution de probabilité strictement triangulaire inférieure par ligne. Le futur reste invisible.


Pourquoi un Transformer Decoder-Only en a besoin

Les modèles decoder-only comme ANDREA, GPT et LLaMA partagent tous un objectif : prédire le token suivant à partir des précédents. Un masque causal rend cet objectif entraînable en parallèle : chaque position calcule sa propre prédiction du token suivant en même temps, et aucune position ne triche en regardant en avant.

Masque & Saveur

L'activité 2 (intro) a couvert trois saveurs de transformers : encoder-only, encoder-decoder, decoder-only. (a) Quelle saveur utilise un masque causal ? (b) Expliquez en une phrase pourquoi une saveur différente (encoder-only, comme BERT) n'utiliserait PAS de masque causal. (c) Quel objectif un encoder non masqué entraîne-t-il à la place ?

Des scores à la sortie

Softmax : Scores en Probabilités

Les scores masqués et mis à l'échelle varient encore sur les nombres réels. Softmax convertit chaque ligne en une distribution de probabilités :


A[i][j] = exp(scaled_scores[i][j]) / sum_k exp(scaled_scores[i][k])

Trois propriétés en résultent :


- A[i][j] >= 0 pour tout (i, j).

- sum_j A[i][j] = 1 pour chaque ligne i.

- Des scores bruts plus grands produisent des probabilités plus grandes (monotone).


Le vecteur de probabilités de la ligne i indique au modèle : à quel point la position i doit prêter attention à chaque position antérieure lors du calcul de sa sortie ?


Somme pondérée de V

Une sortie d'attention finale pour la position i :


output[i] = sum_j A[i][j] · V[j]

Chaque vecteur de valeur V[j] est pondéré par la probabilité d'attention A[i][j], puis sommé. La sortie de la position i combine les vecteurs de valeur de toutes les positions précédentes, pondérés par leur pertinence.


En forme matricielle, toutes les positions en même temps :


Attention(Q, K, V) = softmax(mask(Q · K^T / sqrt(d_k))) · V

Une ligne. Un mécanisme d'attention complet. Vaswani et al. ont écrit cette ligne en 2017 ; les transformers n'ont pas fondamentalement changé depuis.


Forme de sortie par tête

Sortie d'une tête d'attention : forme (T, d_k). Pour ANDREA-120M : (1024, 64). Toutes les 12 têtes calculent en parallèle ; leurs sorties se concatènent en (1024, 768) & alimentent une projection linéaire finale (W_O), puis passent au MLP d'un bloc transformer.


L'activité 6 (grow_a_language_model_multi_head) couvre une division multi-têtes. L'activité 7 (grow_a_language_model_transformer_block) couvre tout ce qui entoure l'attention : connexions résiduelles, normalisation de couche, MLP.

Synthétiser un pipeline

Synthétisez un pipeline d'attention complet en vos propres mots. Décrivez ce qui arrive à une seule position i (ex. position 5 dans une séquence) du vecteur d'entrée x_5 à la sortie d'attention[5]. Nommez quatre opérations dans l'ordre : (1) projeter en Q/K/V, (2) calculer les scores scalés contre toutes les positions, (3) appliquer le masque causal + softmax, (4) sommer les vecteurs V pondérés par les probabilités. Un court paragraphe.