LYCÉE → PRÉPA · L25

Module L25 · Partie K · Ingénierie de l’IA

Le Transformer en profondeur : de l’équation à l’implémentation, et aux articles qui l’ont fait.

Le module L15 a introduit l’attention. Ici, on démonte entièrement la machine : chaque matrice, chaque gradient, le cache KV, les variantes (encodeur, décodeur, encodeur-décodeur, MQA/GQA, RoPE, FlashAttention), les lois d’échelle, et ce que disent les articles fondateurs — lus avec la méthode de L24. Vue informatique (complexité, mémoire, implémentation) et vue mathématique (formes quadratiques, softmax, gradients, invariances).

Durée : 4 séances · Prérequis : L10, L14, L15, L24. Objectifs : implémenter un transformer décodeur complet en NumPy avec rétropropagation manuelle ; expliquer chaque choix architectural par une raison mathématique ou matérielle ; lire « Attention Is All You Need », les lois d’échelle et FlashAttention ; dimensionner un modèle.

Ce que vous saurez faire à la fin
  • Écrire un bloc transformer (attention multi-têtes causale, MLP, LayerNorm, résiduel) et son gradient, et l’entraîner.
  • Expliquer pourquoi √dk, pourquoi le pré-LN, pourquoi le cache KV, pourquoi GQA, pourquoi FlashAttention.
  • Calculer paramètres, FLOPs, mémoire d’activation et de cache pour un modèle donné.
  • Résumer dix articles clés avec leur contribution et leurs limites.

Fiche de cours · Définitions

Définitions à connaître

Définition (séquence, contexte, causalité). Entrée : une suite de n tokens x₁…xn ∈ V. Le contexte d’une position t est x≤t (décodeur causal) ou toute la séquence (encodeur). Un modèle est causal si sa sortie à la position t ne dépend que de x≤t.
Définition (tête d’attention). Paramètres WQ, WK ∈ ℝd×dk, WV ∈ ℝd×dv. Pour X ∈ ℝn×d : Q = XWQ, K = XWK, V = XWV, S = QKᵀ/√dk + M (masque), A = softmaxlignes(S), sortie AV ∈ ℝn×dv. Multi-têtes : h têtes concaténées puis projetées par WO ∈ ℝhdv×d.
Définition (bloc, pré-LN, post-LN). Post-LN (article original) : x ← LN(x + Attn(x)) ; x ← LN(x + MLP(x)). Pré-LN (pratique moderne) : x ← x + Attn(LN(x)) ; x ← x + MLP(LN(x)). Le flux résiduel x traverse tout le réseau sans normalisation en pré-LN : gradients plus stables.
Définition (cache KV). En génération autorégressive, les K et V des positions passées ne changent pas : on les mémorise (n × L × h × dk × 2 valeurs) pour ne calculer, à chaque nouveau token, que sa requête et son attention sur le cache : coût par token O(n·d) au lieu de O(n²·d).
Définition (MQA, GQA). Multi-Query Attention : une seule paire (K, V) partagée par toutes les têtes ; Grouped-Query : g groupes de têtes partagent leurs K, V. Réduit le cache KV d’un facteur h (MQA) ou h/g (GQA) — la mémoire, pas le calcul, limite l’inférence.
Définition (RoPE). Encodage positionnel rotatif : q et k sont tournés, par paires de dimensions, d’un angle proportionnel à la position (fréquences géométriques) ; le produit scalaire qtᵀks ne dépend que de t − s.
Définition (lois d’échelle). Relations empiriques perte ≈ f(N paramètres, D tokens, C calcul) en lois de puissance ; Chinchilla : à C fixé, N et D doivent croître ensemble, D ≈ 20N.

Fiche de cours · Formules

Formules : calcul, mémoire, gradients

Paramètres par bloc : attention 4d² (Q, K, V, O) + MLP 2·d·dff (= 8d² si dff = 4d) + LN 4d ≈ 12d² ; total ≈ 12Ld² + 2Vd (embeddings entrée/sortie, souvent liés)
FLOPs par token (passage avant) ≈ 2N + 2·L·n·d (le second terme : attention, quadratique en n) ; entraînement ≈ 3× (avant + arrière) ⇒ 6N·D FLOPs
Cache KV (float16) = 2 · L · n · hkv · dk · 2 octets — LLaMA-2 70B (L = 80, hkv = 8, dk = 128), n = 4 096 : 1,3 Go par séquence
Mémoire d’activation d’entraînement ≈ n·d·L·(34 + 5·h·n/d) octets par séquence (Korthikanti 2022) — l’attention matérialisée domine pour n grand
Gradient de l’attention (une tête, sans masque) : ∂ℓ/∂V = Aᵀ G ; ∂ℓ/∂A = G Vᵀ ; ∂ℓ/∂S = A ⊙ (∂ℓ/∂A − rowsum(∂ℓ/∂A ⊙ A)) ; ∂ℓ/∂Q = (∂ℓ/∂S) K/√dk ; ∂ℓ/∂K = (∂ℓ/∂S)ᵀ Q/√dk G = gradient sur la sortie AV ; la formule de ∂S est le jacobien du softmax ligne par ligne
LayerNorm : y = (x − μ)/σ ; ∂ℓ/∂x = (1/σ)·[g − mean(g) − y·mean(g ⊙ y)] avec g = γ ⊙ ∂ℓ/∂y
RoPE : qt = RΘ,t q, avec RΘ,t = diag(R(tθ₁), …, R(tθd/2)), θi = 10000−2i/d ⇒ qtᵀks = qᵀRΘ,s−tk
Loi de Chinchilla : L(N, D) ≈ E + A/Nα + B/Dβ avec α ≈ 0,34, β ≈ 0,28 ; optimum à C = 6ND fixé : N ∝ C0,5, D ∝ C0,5

Fiche de cours · Théorèmes et démonstrations

Démonstrations à savoir refaire

Théorème 1 (jacobien du softmax et gradient de l’attention). Pour a = softmax(s) (une ligne), ∂ai/∂sj = aiij − aj) ; donc pour un gradient amont g = ∂ℓ/∂a, ∂ℓ/∂s = a ⊙ (g − ⟨g, a⟩).
ai = esi/Z, Z = Σk esk. ∂ai/∂sj = (δijesiZ − esiesj)/Z² = aiδij − aiaj. Règle de la chaîne : ∂ℓ/∂sj = Σi giaiij − aj) = ajgj − ajΣigiai = aj(gj − ⟨g, a⟩). Le terme ⟨g, a⟩ est ce que FlashAttention recalcule à rebours à partir de la sortie O et de son gradient dO : ⟨g, a⟩ = rowsum(dO ⊙ O) — ce qui évite de stocker A (n²). Le reste suit par linéarité : ∂ℓ/∂Q = (∂ℓ/∂S)K/√dk, etc.
Théorème 2 (le cache KV est exact). En décodage causal, la sortie de la position t calculée avec le cache est identique à celle du passage complet sur x≤t.
K et V à la couche ℓ pour la position s ≤ t sont des fonctions de hℓ−1,s, qui par causalité (L15, Théorème 4) ne dépend que de x≤s — donc inchangé quand on ajoute xt+1. La requête qt attend sur K≤t, V≤t : exactement ce que contient le cache. Par récurrence sur les couches, toute la pile est identique. Le coût de génération de n tokens passe de Σt O(t²d) = O(n³d) (recalcul complet) à Σt O(td) = O(n²d).
Théorème 3 (RoPE encode la position relative). Avec qt = Rtq et ks = Rsk où Rt est une rotation par blocs 2×2 d’angles tθi, on a qtᵀks = qᵀRs−tk.
Les rotations d’un même bloc commutent et Rtᵀ = R−t ; R−tRs = Rs−t (angles additifs). Donc qtᵀks = qᵀRtᵀRsk = qᵀRs−tk. Le score ne dépend que de s − t : le modèle généralise mieux à des positions absolues non vues, et l’extension de contexte (interpolation des fréquences) devient possible. Les fréquences géométriques donnent des « horloges » de périodes variées, comme l’encodage sinusoïdal (L15, Théorème 3), mais appliquées multiplicativement à q et k plutôt qu’ajoutées à x.
Théorème 4 (FlashAttention : softmax par blocs). Le softmax d’une ligne peut être calculé par blocs en maintenant (m, ℓ) = (max courant, somme courante des exponentielles) : pour un nouveau bloc de scores sb, m′ = max(m, max sb), ℓ′ = em−m′ℓ + Σ esb−m′, et la sortie partielle O′ = em−m′O + Σ esb−m′vb ; à la fin O/ℓ est exact.
softmax(s)·V = Σj esj−m*vj / Σj esj−m* pour tout m* (invariance par translation du softmax). On accumule numérateur et dénominateur avec un m courant, et l’on renormalise par em−m′ quand le max augmente : chaque terme esj−m devient esj−m′ exactement. Aucune matrice n×n n’est matérialisée : mémoire O(n) pour l’attention, et les blocs tiennent dans la SRAM du GPU (bien plus rapide que la HBM) — d’où une accélération 2–4× malgré un léger surcoût de calcul (Dao et al. 2022).

01 / Implémenter

Une tête d’attention et son gradient, vérifiés par différences finies

Une erreur relative < 10⁻⁶ valide la dérivation. C’est la première chose à faire avant tout entraînement : un gradient faux entraîne quand même « un peu », et l’on perd des jours.

01 / Implémenter

Le bloc complet : multi-têtes, LayerNorm, MLP, résiduel — et un mini-GPT qui apprend

Un modèle de 2 blocs apprend le motif périodique en quelques centaines de pas : c’est une tête d’induction minimale (L15). Chaque ligne de ce code correspond à une équation de la fiche ; rien n’est caché.

02 / Analyser

Vue informatique : où va le calcul, où va la mémoire

Retenir : à l’entraînement, le coût est ≈ 6ND et le calcul domine ; à l’inférence en petit lot, la bande passante mémoire domine (chaque token relit tous les poids) — d’où la quantification (4 bits : 4× moins d’octets), le traitement par lots, et la génération spéculative.

02 / Analyser

Vue mathématique : ce que l’attention peut et ne peut pas représenter

Ce qu’elle fait bien

  • Moyennes conditionnelles : la sortie est toujours dans l’enveloppe convexe des valeurs — une agrégation, pas une transformation ; la non-linéarité vient du softmax (sélection) et du MLP.
  • Recherche associative : une requête retrouve les clés proches ; avec des clés = positions, c’est un accès par adresse ; avec des clés = contenu, une mémoire associative (Hopfield moderne : Ramsauer 2020).
  • Composition en profondeur : deux couches composent des relations (tête d’induction : « ce qui suivait la dernière occurrence du token courant »).

Ses limites démontrées

  • Compter et parité : une couche d’attention à précision finie ne calcule pas la parité de n bits pour n arbitraire (Hahn 2020) ; en pratique, les modèles échouent sur des tâches de comptage long sans chaîne de raisonnement explicite.
  • Profondeur bornée ⇒ classe de circuits bornée : un transformer de profondeur L à précision log n est dans TC⁰ (Merrill & Sabharwal 2023) : il ne peut pas, en un passage, simuler des calculs séquentiels longs (d’où l’intérêt de « penser étape par étape », qui rallonge la séquence = plus de calcul).
  • Dilution : avec n tokens, chaque poids est ≈ 1/n en moyenne ; retrouver une aiguille exige un score très contrasté — les longs contextes dégradent le rappel (« lost in the middle », Liu 2023).

03 / Variantes

Encodeur, décodeur, encodeur-décodeur : quelle architecture pour quelle tâche

FamilleAttentionPré-entraînementTâchesExemples
EncodeurBidirectionnelleTokens masqués (MLM)Classification, extraction, plongements, recherche (RAG !)BERT, RoBERTa, DeBERTa, E5, BGE
DécodeurCausaleToken suivantGénération, dialogue, code, raisonnementGPT, LLaMA, Mistral, Qwen, Claude
Encodeur-décodeurBidirectionnelle + causale + croiséeDébruitage de spansTraduction, résumé, ASR (Whisper), OCR (TrOCR)T5, BART, Whisper, mT5
Vision (ViT)Bidirectionnelle sur patchsSupervisé / auto-supervisé (MAE, DINO)Classification, détection, segmentationViT, DINOv2, SAM
MultimodalCroisée image→texteContrastif (CLIP) puis génératifDescription, VQA, OCR « libre », agentsCLIP, LLaVA, PaliGemma, GPT-4V
Alternatives linéairesRécurrence à état (SSM)Token suivantTrès longs contextes, embarquéMamba, RWKV, hybrides (Jamba)

Attention croisée (décodeur d’un encodeur-décodeur) : Q vient du décodeur, K et V de l’encodeur — le décodeur « lit » la source à chaque pas. C’est aussi le mécanisme des modèles de diffusion conditionnés par du texte.

Choisir : comprendre/classer/plonger → encodeur (petit, rapide, bidirectionnel) ; produire du texte → décodeur ; transformer une séquence en une autre de nature différente → encodeur-décodeur ; contexte de 10⁶ tokens sous contrainte mémoire → hybride SSM.

04 / Entraîner à l’échelle

Lois d’échelle, données, et le calcul que vous n’avez pas

Leçon d’ingénierie : le pré-entraînement à l’état de l’art coûte 10⁷ à 10⁹ €. Votre levier n’est pas là : il est dans l’affinage (LoRA, quelques heures de GPU), le RAG (L26), les données (L36) et l’évaluation (L32). Entraîner « from scratch » est le dernier recours (L37).

05 / Articles

Les articles à lire, avec ce qu’il faut en retenir

ArticleContributionÀ retenir / limite
Vaswani et al., Attention Is All You Need, NeurIPS 2017 — arXiv:1706.03762Architecture sans récurrence ; attention multi-têtes ; encodage sinusoïdal ; SOTA en traductionPost-LN, warmup indispensable ; le tableau 1 (complexité par couche) explique tout
Devlin et al., BERT, 2018 — 1810.04805Pré-entraînement bidirectionnel par tokens masqués ; affinage universelBase des modèles de plongement et de recherche ; pas génératif
Radford et al., GPT-2 2019 ; Brown et al., GPT-3, 2020 — 2005.14165Décodeur causal à l’échelle ; apprentissage en contexte (few-shot) émergeLa taille change la nature des capacités ; évaluation par prompts
Kaplan et al., Scaling Laws, 2020 — 2001.08361 ; Hoffmann et al., Chinchilla, 2022 — 2203.15556Perte en loi de puissance de N, D, C ; optimum N ≈ D/20Kaplan sous-estimait les données ; les lois dépendent du corpus et de la tokenisation
Xiong et al., On Layer Normalization in the Transformer, ICML 2020 — 2002.04745Pré-LN : gradients bornés à l’initialisation, pas de warmup nécessaireExplique le choix de tous les LLM modernes
Su et al., RoFormer (RoPE), 2021 — 2104.09864Position relative par rotation de q, kExtension de contexte par interpolation (Chen 2023, YaRN)
Dao et al., FlashAttention, NeurIPS 2022 — 2205.14135 ; v2 2023Attention exacte par blocs, IO-aware ; mémoire O(n)Le goulot est la mémoire, pas les FLOPs ; adopté partout
Shazeer, Fast Transformer Decoding (MQA), 2019 — 1911.02150 ; Ainslie et al., GQA, 2023 — 2305.13245Partager K, V entre têtes : cache KV réduitQualité quasi intacte ; standard dans LLaMA-2/3, Mistral
Touvron et al., LLaMA, 2023 — 2302.13971Modèles ouverts entraînés au-delà de Chinchilla ; RMSNorm, SwiGLU, RoPELa recette « moderne » standard
Gu & Dao, Mamba, 2023 — 2312.00752Modèle à état sélectif, linéaire en nCompétitif jusqu’à ~3B ; hybrides attention + SSM en production
Dosovitskiy et al., ViT, ICLR 2021 — 2010.11929Images en patchs = tokens ; transformer pur en visionExige beaucoup de données ou un pré-entraînement auto-supervisé
Olsson et al., In-context Learning and Induction Heads, 2022 — 2209.11895Mécanisme concret de l’apprentissage en contexteInterprétabilité mécaniste : lire les circuits, pas seulement les scores

Méthode de lecture : trois passes (L24). Pour chacun, remplir : contribution en 3 phrases, protocole, résultat clé (chiffre), limite avouée, limite non avouée.

TP guidé

TP — nanoGPT sur PC : entraîner, mesurer, modifier (6 h)

Exercices

Exercices auto-corrigés

Exercice 1 — RoPE

Implémentez rope(x, pos, theta_base=10000) qui applique la rotation par paires de dimensions (2i, 2i+1) d’angle pos·θi, θi = base−2i/d, à un vecteur x de dimension d paire. Vérifiez que le produit scalaire ne dépend que de la différence de positions.

Correction
def rope(x, pos, theta_base=10000):
    d = len(x); i = np.arange(d // 2); theta = theta_base ** (-2 * i / d); a = pos * theta
    x1, x2 = x[0::2], x[1::2]
    y = np.empty_like(x); y[0::2] = x1 * np.cos(a) - x2 * np.sin(a); y[1::2] = x1 * np.sin(a) + x2 * np.cos(a)
    return y

Exercice 2 — Softmax par blocs (FlashAttention)

Implémentez attention_par_blocs(q, K, V, taille_bloc) pour une seule requête q : parcourez K, V par blocs en maintenant (m, l, o) selon le Théorème 4, sans jamais former le vecteur complet des scores. Comparez à l’attention directe.

Correction
def attention_par_blocs(q, K, V, taille_bloc):
    dk = len(q); m, l, o = -np.inf, 0.0, np.zeros(V.shape[1])
    for deb in range(0, len(K), taille_bloc):
        s = K[deb:deb + taille_bloc] @ q / np.sqrt(dk)
        m2 = max(m, s.max()); f = np.exp(m - m2) if m > -np.inf else 0.0; e = np.exp(s - m2)
        l = f * l + e.sum(); o = f * o + e @ V[deb:deb + taille_bloc]; m = m2
    return o / l

Exercices

Exercices auto-corrigés (suite)

Exercice 3 — GQA

Implémentez attention_gqa(X, Wq, Wk, Wv, h, g) : h têtes de requêtes, g groupes de K/V (h divisible par g) ; les têtes q0..h/g−1 utilisent le groupe 0, etc. Vérifiez que g = h redonne l’attention multi-têtes classique, et que g = 1 est la MQA. Retournez aussi le nombre de valeurs du cache KV.

Correction
def attention_gqa(X, Wq, Wk, Wv, h, g, causal=True):
    n = len(X); dk = Wq.shape[1] // h; Q, K, V = X @ Wq, X @ Wk, X @ Wv; outs = []
    for i in range(h):
        grp = i // (h // g); q = Q[:, i*dk:(i+1)*dk]; k = K[:, grp*dk:(grp+1)*dk]; v = V[:, grp*dk:(grp+1)*dk]
        S = q @ k.T / np.sqrt(dk)
        if causal: S = S + np.triu(np.full((n, n), -1e9), 1)
        A = np.exp(S - S.max(1, keepdims=True)); A /= A.sum(1, keepdims=True); outs.append(A @ v)
    return np.concatenate(outs, 1), 2 * n * g * dk

Exercice 4 — Dimensionner

Complétez dimensionner(C) qui, pour un budget de calcul C (FLOPs), renvoie (N, D) selon Chinchilla (D = 20N, C = 6ND), puis (d, L) pour un modèle « carré » avec d = 128·L (h = d/128, dff = 4d, V = 32 000) tel que 12Ld² + Vd ≈ N.

Correction
def dimensionner(C, V=32000):
    N = np.sqrt(C / 120); D = 20 * N                      # C = 6·N·20N = 120 N²
    Ls = np.linspace(1, 200, 100000); ds = 128 * Ls; params = 12 * Ls * ds**2 + V * ds
    L = Ls[np.abs(params - N).argmin()]; return N, D, 128 * L, L

Fiche de cours · Exercices corrigés

Exercices corrigés (rédaction)

Exercice 1. Montrer que la sortie d’une tête d’attention est dans l’enveloppe convexe des lignes de V, et en déduire qu’une couche d’attention seule (sans MLP) ne peut pas produire un vecteur de norme supérieure au maximum des normes des valeurs.
Correction. La ligne t de la sortie est Σj atjvj avec atj ≥ 0 et Σj atj = 1 (softmax) : combinaison convexe. Par l’inégalité triangulaire, ‖Σ atjvj‖ ≤ Σ atj‖vj‖ ≤ maxj‖vj‖. L’attention agrège ; la projection WO et le MLP transforment. C’est pourquoi un bloc contient les deux, et pourquoi le MLP porte ≈ 2/3 des paramètres.
Exercice 2. On génère 2 000 tokens avec LLaMA-2 7B en fp16 sur un GPU de 24 Go. Le cache KV tient-il ? Et pour 32 000 tokens ? Que change GQA à 8 têtes KV ?
Correction. Poids : 7·10⁹ × 2 octets = 13,5 Go. Cache par token : 2 × L × h × dk × 2 octets = 2 × 32 × 32 × 128 × 2 = 524 288 octets = 0,5 Mo. 2 000 tokens : 1 Go — total 14,5 Go, ça tient. 32 000 tokens : 16,8 Go + 13,5 Go = 30 Go > 24 Go : ne tient pas. Avec GQA (8 têtes KV) : cache divisé par 4 → 4,2 Go, total 17,7 Go, ça tient. Alternative : quantifier les poids en 4 bits (3,5 Go) et le cache en 8 bits.
Exercice 3. Pourquoi le pré-LN se passe-t-il de warmup alors que le post-LN en a besoin ? Donner l’argument sur la norme du gradient à l’initialisation.
Correction. En post-LN, la sortie de chaque bloc est renormalisée : le gradient qui traverse L blocs est multiplié par L jacobiens de LayerNorm et de sous-couches, et Xiong et al. montrent que sa norme aux dernières couches croît comme O(d·√(ln d)) indépendamment de L tandis que celle des premières décroît — un grand pas initial fait diverger ; le warmup (pas croissant de 0) laisse les poids s’adapter. En pré-LN, le flux résiduel x n’est jamais renormalisé : ∂xL/∂x = I + (termes de sous-couches), de norme O(1) à l’initialisation, et la norme du gradient est bornée par O(√(ln d)/√L) : le pas peut être grand dès le départ. Contrepartie : le flux résiduel grossit avec la profondeur (d’où la LN finale).

Vérification

Pourquoi la génération token par token d’un LLM en lot de 1 est-elle lente sur GPU alors que le calcul par token est faible ?

Deux questions supplémentaires

1. Pourquoi diviser par √dk ? Pour garder la variance des scores à 1 et éviter la saturation du softmax (gradient nul).

2. Que gagne FlashAttention ? La même attention exacte sans matérialiser la matrice n×n : mémoire O(n), et 2–4× plus rapide grâce à la SRAM.

Référence

Les mots à retenir

MotDéfinition
Tête d’attentionsoftmax(QKᵀ/√dk)V : moyenne des valeurs pondérée par la similarité requête-clé.
Pré-LNNormalisation à l’entrée des sous-couches ; flux résiduel non normalisé ; gradients stables.
Cache KVMémorisation des K, V passés en génération ; coût par token linéaire.
MQA / GQAK, V partagés entre têtes (ou groupes) : cache réduit.
RoPERotation de q, k selon la position ; score dépendant de la position relative.
FlashAttentionSoftmax par blocs, sans matrice n×n, adapté à la hiérarchie mémoire.
Loi d’échellePerte en loi de puissance de N, D, C ; Chinchilla D ≈ 20N.
Encodeur / décodeurBidirectionnel (comprendre) / causal (générer) / les deux (transformer une séquence).

Suite

Vous avez démonté le moteur. Maintenant, la voiture : comment on l’utilise en production.

Un transformer seul ne sait rien de vos documents, et se trompe avec assurance. Le chapitre suivant construit un RAG — recherche + génération — et montre comment le mesurer.

← L24SommaireL26 : RAG →