Skip to content
The loss curve

Chapitre 9 · 14 min

Multi-têtes et résidus

Combine les têtes, normalise les flux résiduels et rapproche ton code d’attention local d’un bloc transformer empilable.

Au chapitre 8, tu as construit une tête d’attention. Elle calculait un motif d’attention : une matrice de qui écoute qui. C’est un seul motif de routage par couche.

Ce n’est pas assez. Le texte contient plusieurs relations simultanées : accord sujet/verbe, résolution de pronoms, modificateurs, similarité sémantique, biais de position. Un modèle avec une seule doit choisir.

La correction est évidente : lancer plusieurs têtes en parallèle. Chaque tête a ses propres W_Q, W_K, W_V, donc chacune apprend un motif différent. On concatène leurs sorties, puis on projette le résultat. C’est l’attention multi-têtes. La permet d'exploiter plusieurs motifs d'attention en parallèle.

Ce chapitre ajoute aussi les connexions résiduelles et la LayerNorm, les deux pièces qui permettent d’empiler des couches sans effondrer l’entraînement. Les et la sont essentielles pour les réseaux profonds.

1. Combiner plusieurs têtes

Tu as déjà une tête d’attention. Le chapitre fournit quatre sorties de têtes différentes, chacune [seq_len × d_head]. La cellule les combine :

  1. Concaténer les sorties sur l’axe des features.
  2. Projeter la matrice concaténée avec W_O.

Code · JavaScript

Le résultat garde la forme de l’entrée, [seq_len × d_model], mais chaque token reflète maintenant plusieurs motifs d’attention.

2. Inspecter les motifs appris

Les quatre matrices d’attention pré-calculées sont affichées en heatmaps. Certaines sont nettes, d’autres diffuses.

Une façon de mesurer la concentration est l’ : H = -Σ p log p. Une entropie basse signifie que la tête se concentre sur peu de tokens.

Code · JavaScript

Attention patterns across heads · log(n) ≈ 1.61 = maximum entropy for 5 tokens

Head 0

thecatsatonmatthecatsatonmatthe, the: 0.240the, cat: 0.172the, sat: 0.211the, on: 0.193the, mat: 0.183cat, the: 0.181cat, cat: 0.218cat, sat: 0.191cat, on: 0.206cat, mat: 0.203sat, the: 0.210sat, cat: 0.198sat, sat: 0.198sat, on: 0.206sat, mat: 0.187on, the: 0.193on, cat: 0.201on, sat: 0.201on, on: 0.195on, mat: 0.210mat, the: 0.193mat, cat: 0.213mat, sat: 0.192mat, on: 0.211mat, mat: 0.192

Head 1

thecatsatonmatthecatsatonmatthe, the: 0.196the, cat: 0.205the, sat: 0.198the, on: 0.193the, mat: 0.207cat, the: 0.197cat, cat: 0.194cat, sat: 0.200cat, on: 0.210cat, mat: 0.198sat, the: 0.197sat, cat: 0.205sat, sat: 0.199sat, on: 0.193sat, mat: 0.206on, the: 0.188on, cat: 0.196on, sat: 0.198on, on: 0.210on, mat: 0.208mat, the: 0.204mat, cat: 0.198mat, sat: 0.201mat, on: 0.202mat, mat: 0.195

Head 2

thecatsatonmatthecatsatonmatthe, the: 0.234the, cat: 0.180the, sat: 0.205the, on: 0.201the, mat: 0.181cat, the: 0.169cat, cat: 0.223cat, sat: 0.192cat, on: 0.197cat, mat: 0.220sat, the: 0.206sat, cat: 0.195sat, sat: 0.202sat, on: 0.200sat, mat: 0.196on, the: 0.217on, cat: 0.192on, sat: 0.201on, on: 0.201on, mat: 0.190mat, the: 0.156mat, cat: 0.229mat, sat: 0.191mat, on: 0.195mat, mat: 0.229

Head 3

thecatsatonmatthecatsatonmatthe, the: 0.215the, cat: 0.194the, sat: 0.200the, on: 0.189the, mat: 0.201cat, the: 0.189cat, cat: 0.205cat, sat: 0.200cat, on: 0.209cat, mat: 0.197sat, the: 0.190sat, cat: 0.203sat, sat: 0.196sat, on: 0.203sat, mat: 0.209on, the: 0.199on, cat: 0.201on, sat: 0.202on, on: 0.203on, mat: 0.196mat, the: 0.185mat, cat: 0.204mat, sat: 0.195mat, on: 0.206mat, mat: 0.210

Tu devrais voir des différences entre têtes. Dans un vrai transformer, on trouve des têtes spécialisées : copie, induction, suivi de noms, syntaxe. On nomme des motifs découverts ; on ne les a pas demandés explicitement.

3. Résiduel + LayerNorm

Brancher une sous-couche directement dans la suivante casserait l’entraînement :

  • Gradient qui disparaît : chaque couche compresse un peu le signal.
  • Dérive des représentations : les magnitudes explosent ou s’écrasent.

La connexion résiduelle corrige le premier point : output = input + sublayer(input). Le gradient a un chemin propre à travers l’addition. La permet aux gradients de circuler proprement à travers les couches profondes.

La LayerNorm corrige le second : après l’addition, on normalise chaque ligne de token à moyenne 0 et écart-type 1. La stabilise l'entraînement en maintenant des échelles d'activation constantes.

output=LayerNorm(input+sublayer(input))\text{output} = \text{LayerNorm}(\text{input} + \text{sublayer}(\text{input}))

Code · JavaScript

Les statistiques de ligne doivent montrer moyenne 0 et écart-type proche de 1. C’est l’invariant que LayerNorm donne à la couche suivante.

Pourquoi ce chapitre compte

Nous avons maintenant les pièces d’un bloc transformer :

  • Attention : extraire de l’information d’autres positions.
  • Multi-têtes : plusieurs routes d’attention.
  • Résiduel + LayerNorm : l’infrastructure pour empiler. Les et la sont essentielles pour les réseaux profonds.

Le prochain chapitre les assemble en bloc complet.

4. Ajouter les helpers résiduels et de normalisation

Ajoute à llm/nn.py :

import math
 
 
def add(a: Matrix, b: Matrix) -> Matrix:
    return [
        [x + y for x, y in zip(row_a, row_b)]
        for row_a, row_b in zip(a, b)
    ]
 
 
def layer_norm(x: Matrix, eps: float = 1e-5) -> Matrix:
    out: Matrix = []
    for row in x:
        mean = sum(row) / len(row)
        var = sum((value - mean) ** 2 for value in row) / len(row)
        denom = math.sqrt(var + eps)
        out.append([(value - mean) / denom for value in row])
    return out
  • add est la connexion résiduelle.
  • layer_norm travaille ligne par ligne, donc chaque token est normalisé indépendamment.
  • eps évite la division par zéro.

Tu as maintenant l’invariant du transformer : chaque sous-couche accepte une matrice et retourne une matrice de même forme.

Recap

  • L’attention multi-têtes lance H attentions en parallèle, concatène et projette. - Des têtes différentes apprennent des motifs différents. - Connexion résiduelle = `output = input
  • sublayer(input)`. - LayerNorm stabilise l’échelle de chaque token. - Ton projet local a maintenant les helpers résiduels et LayerNorm.

Pour aller plus loin

Prochaine étape : le bloc transformer complet — assembler les pièces dans l’unité qu’on empile.