Les couches d'un transformer,
vue par un informaticien
Complément technique à « Comprendre les IA génératives ». Ce que fait réellement une couche, en termes d'algorithme et de structures de données.
L'analogie du dîner mondain — chaque token dialoguant avec les autres — cache quelque chose de très simple algorithmiquement. Le point de départ, c'est la structure de données.
La structure : un tableau qui ne change jamais de forme
Tout tient dans une matrice X de dimensions
[n_tokens, d_model] — disons 30 lignes (les tokens) sur 4096 colonnes.
Une ligne par token, 4096 nombres flottants par ligne.
Et voici l'essentiel : chaque couche est une fonction de X vers
X, de signature identique. Pas de transformation de format, pas de
structure intermédiaire. Une centaine de fonctions à la queue leu leu, toutes de même
type. Une couche ressemble à ceci :
def couche(X, W): # X : [n, 4096]
X = X + attention(normalise(X), W) # note le +
X = X + mlp(normalise(X), W)
return X # [n, 4096]
Le + n'est pas un détail. Les couches ne s'enchaînent pas comme
g(f(x)) : elles accumulent. X est un bus
partagé — les interprétabilistes disent residual stream — que chaque
sous-couche lit intégralement, puis auquel elle ajoute sa contribution. Deux cents
sous-couches qui écrivent sur un tableau blanc commun, plutôt qu'un pipeline de filtres.
C'est ce qui permet à la couche 80 de lire directement ce qu'a écrit la couche 3.
L'attention : un dict à correspondance floue
C'est là que le dîner mondain devient concret. Pour un informaticien, la meilleure description est : une table associative dont la comparaison de clés est un produit scalaire au lieu d'une égalité de hash, et qui renvoie une moyenne pondérée de toutes les valeurs au lieu d'une seule.
def attention(X, W):
Q = X @ W.q # [n, 4096] @ [4096, 4096] -> [n, 4096] "ce que je cherche"
K = X @ W.k # "ce que j'annonce"
V = X @ W.v # "ce que je fournis"
S = Q @ K.T / sqrt(d_head) # [n, n] : similarité requête/clé
S = S + masque_causal # -inf au-dessus de la diagonale
A = softmax(S, axis=-1) # chaque ligne somme à 1
return (A @ V) @ W.o # moyenne pondérée, puis reprojection
Six multiplications de matrices et un softmax. C'est tout.
Q @ K.Tproduit la matrice[n, n]des affinités : combien le token i doit écouter le token j.- Le
softmaxtransforme ces scores bruts en poids positifs de somme 1 — c'est lui qui fabrique le « il écoute à 70 % ». - Le masque causal met
-infau-dessus de la diagonale, donc poids nul après softmax : un token ne peut pas lire son futur. C'est ce qui rend l'entraînement parallélisable — toutes les positions apprennent simultanément à prédire leur suivant. A @ Vest la lecture proprement dite : chaque ligne du résultat est une combinaison linéaire des valeurs, pondérée par la pertinence.
Le multi-têtes n'ajoute rien de conceptuel : on découpe les 4096 colonnes en 32 tranches de 128, on fait tourner l'algorithme ci-dessus indépendamment sur chaque tranche, on reconcatène. Trente-deux recherches associatives en parallèle, sur trente-deux sous-espaces différents.
Le MLP : là où sont réellement les connaissances
def mlp(X, W):
H = gelu(X @ W.up) # [n, 4096] -> [n, 16384]
return H @ W.down # [n, 16384] -> [n, 4096]
Aucune interaction entre tokens ici : chaque ligne est traitée séparément, comme un
map. On dilate d'un facteur 4, on applique une non-linéarité, on
recomprime.
Répartition des paramètres, pour un modèle de 7 milliards à 32 couches : environ
67 M par couche pour l'attention (4 matrices d×d), 134 M pour le MLP.
Le MLP pèse les deux tiers du modèle. Grossièrement : l'attention
déplace l'information entre positions, le MLP est la mémoire associative qui contient
les faits et les règles. « Paris est la capitale de la France » vit dans des matrices de
MLP, pas dans l'attention.
Le point qui déroute quand on vient du code impératif
Il n'y a aucune structure de contrôle. Pas de if, pas de
boucle sur les données, pas de branche. Le graphe d'exécution est rigoureusement
identique pour « bonjour » et pour une démonstration de topologie — mêmes opérations,
mêmes dimensions, même nombre d'instructions. Seules les valeurs diffèrent.
Ce qui tient lieu de conditionnelle, c'est la saturation du softmax :
quand un score domine largement, la distribution s'approche d'un vecteur one-hot et le
A @ V devient de facto une sélection. Un if émulé par une
fonction continue qui se rapproche d'une marche. C'est aussi pourquoi tout est
dérivable, donc entraînable par descente de gradient — un vrai branchement ne le serait
pas.
Coûts
| Bloc | Complexité | Domine quand |
|---|---|---|
| Attention | O(n² · d) | contexte long |
| MLP | O(n · d²) | contexte court |
Le n² de l'attention explique tout : le prix des grands contextes, et
l'intérêt des architectures hybrides qui remplacent une partie des couches par des
mécanismes en O(n).
Corollaire pratique, si vous faites tourner du local : le cache KV.
Comme le masque causal garantit que les K et V d'un token ne
dépendent jamais de ce qui le suit, on les conserve d'un passage à l'autre. À chaque
nouveau token généré on ne recalcule que sa propre ligne, pas les 30 précédentes. C'est
ce cache qui occupe la mémoire vidéo en plus des poids, et qui croît linéairement avec
le contexte — la vraie raison pour laquelle un contexte de 100 000 tokens sature une
carte bien avant que les poids ne posent problème.