Transformers
L'architecture révolutionnaire derrière les LLM
Les Transformers ont révolutionné le Deep Learning depuis leur introduction en 2017. Ils sont au cœur de GPT, BERT, Claude, et de tous les modèles de langage modernes.
"Attention Is All You Need"
Article fondateur de Vaswani et al. (Google, 2017) qui a tout changé.
Avant : RNN/LSTM dominaient le NLP Après : Les Transformers règnent en maîtres
Pourquoi le succès ?
| RNN/LSTM | Transformer |
|---|---|
| Traitement séquentiel | Traitement parallèle |
| Dépendances longues difficiles | Attention globale |
| Lent à entraîner | Rapide (GPU) |
| Contexte limité | Contexte étendu |
Le mécanisme d'attention
L'attention permet à chaque élément de "regarder" tous les autres éléments de la séquence.
Intuition
"Le chat qui dormait sur le canapé s'est réveillé"
Pour comprendre "s'est réveillé", le modèle doit savoir que :
- Le sujet est "chat" (pas "canapé")
- "qui dormait" donne le contexte
L'attention crée des connexions directes entre les mots pertinents.Self-Attention
Chaque mot génère trois vecteurs :
- Query (Q) : "Que cherche ce mot ?"
- Key (K) : "Quelles infos ce mot contient-il ?"
- Value (V) : "Quelle valeur transmettre ?"
Attention
Q₁ ←──────────────────────→ K₁, K₂, K₃, K₄
scores = Q₁ · [K₁,K₂,K₃,K₄]
weights = softmax(scores)
output₁ = weights · [V₁,V₂,V₃,V₄]Formule
Attention(Q, K, V) = softmax(Q · Kᵀ / √dₖ) · V
- Q · Kᵀ : similarité entre queries et keys
- √dₖ : normalisation (dₖ = dimension des keys)
- softmax : convertit en probabilités
- × V : moyenne pondérée des valuesExemple simplifié
Phrase : "Le chat dort"
Le chat dort
Le [0.1 0.6 0.3 ] ← Le regarde surtout "chat"
chat [0.2 0.5 0.3 ]
dort [0.1 0.7 0.2 ] ← "dort" attend "chat" (sujet)
Matrice d'attention (après softmax)Multi-Head Attention
Au lieu d'une seule attention, on en fait plusieurs en parallèle.
┌─────────────────────────────────────────────────┐
│ MULTI-HEAD ATTENTION │
├─────────────────────────────────────────────────┤
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Head 1│ │Head 2│ │Head 3│ │Head 4│ │
│ │syntax│ │semant│ │coref │ │ ... │ │
│ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ │
│ └─────────┴─────────┴─────────┘ │
│ │ │
│ Concatenate │
│ │ │
│ Linear (Wₒ) │
│ ↓ │
│ Output │
│ │
└─────────────────────────────────────────────────┘Chaque "tête" peut capturer un type de relation différent :
- Syntaxe
- Sémantique
- Coréférence
- Position relative
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
self.num_heads = num_heads
self.d_k = d_model // num_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
def forward(self, Q, K, V, mask=None):
batch_size = Q.size(0)
# Projections linéaires
Q = self.W_q(Q).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
K = self.W_k(K).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
V = self.W_v(V).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
# Attention
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attn = torch.softmax(scores, dim=-1)
context = torch.matmul(attn, V)
# Concatenate et projection finale
context = context.transpose(1, 2).contiguous().view(batch_size, -1, self.num_heads * self.d_k)
return self.W_o(context)Architecture complète
Encoder-Decoder original
┌──────────────────────────────────────────────────────────────┐
│ TRANSFORMER │
├────────────────────────┬─────────────────────────────────────┤
│ ENCODER │ DECODER │
│ │ │
│ ┌────────────────┐ │ ┌────────────────┐ │
│ │ Multi-Head │ │ │ Masked Multi- │ │
│ │ Self-Attention │ │ │ Head Attention │ │
│ └───────┬────────┘ │ └───────┬────────┘ │
│ │ │ │ │
│ ┌───────┴────────┐ │ ┌───────┴────────┐ │
│ │ Add & Norm │ │ │ Add & Norm │ │
│ └───────┬────────┘ │ └───────┬────────┘ │
│ │ │ │ │
│ ┌───────┴────────┐ │ ┌───────┴────────┐ │
│ │ Feed Forward │ │ │ Cross-Attention│←── Encoder │
│ └───────┬────────┘ │ └───────┬────────┘ output │
│ │ │ │ │
│ ┌───────┴────────┐ │ ┌───────┴────────┐ │
│ │ Add & Norm │ │ │ Add & Norm │ │
│ └───────┬────────┘ │ └───────┬────────┘ │
│ │ │ │ │
│ × N │ ┌───────┴────────┐ │
│ │ │ Feed Forward │ │
│ │ └───────┬────────┘ │
│ │ │ │
│ │ ┌───────┴────────┐ │
│ │ │ Add & Norm │ │
│ │ └───────┬────────┘ │
│ │ │ │
│ │ × N │
└────────────────────────┴─────────────────────────────────────┘Composants clés
Positional Encoding
Les Transformers n'ont pas de notion d'ordre (contrairement aux RNN). On ajoute des encodages positionnels.
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1).float()
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe.unsqueeze(0))
def forward(self, x):
return x + self.pe[:, :x.size(1)]Feed Forward Network
Deux couches linéaires avec activation :
class FeedForward(nn.Module):
def __init__(self, d_model, d_ff):
super().__init__()
self.linear1 = nn.Linear(d_model, d_ff)
self.linear2 = nn.Linear(d_ff, d_model)
self.gelu = nn.GELU()
def forward(self, x):
return self.linear2(self.gelu(self.linear1(x)))Layer Normalization
nn.LayerNorm(d_model)Connexions résiduelles
# Add & Norm
output = layer_norm(x + sublayer(x))Variantes d'architecture
Encoder-only (BERT)
Bidirectionnel, voit tout le contexte.
[CLS] Le chat dort [SEP]
↓ ↓ ↓ ↓ ↓
┌─────────────────────────┐
│ ENCODER × N │
└─────────────────────────┘
↓ ↓ ↓ ↓ ↓
Représentations contextuellesUtilisations : Classification, NER, Q&A extractif
Decoder-only (GPT, Claude)
Autorégressif, ne voit que le passé (masque causal).
Le → chat → dort → [NEXT]
↓ ↓ ↓ ↓
┌──────────────────────────┐
│ DECODER × N (masked) │
└──────────────────────────┘
↓
PrédictionUtilisations : Génération de texte, LLM
Encoder-Decoder (T5, BART)
Pour les tâches de séquence à séquence.
Utilisations : Traduction, résumé
Masques d'attention
Padding mask
Ignore les tokens de padding.
Séquence : [Le, chat, dort, <PAD>, <PAD>]
Mask : [1, 1, 1, 0, 0 ]Causal mask (GPT)
Empêche de voir le futur.
Le chat dort
Le [1 0 0 ]
chat [1 1 0 ]
dort [1 1 1 ]def causal_mask(size):
mask = torch.triu(torch.ones(size, size), diagonal=1)
return mask == 0 # True = peut voirModèles célèbres
BERT (2018)
Bidirectional Encoder Representations from Transformers
- Encoder-only, bidirectionnel
- Pré-entraîné avec Masked Language Model (MLM)
- Base : 110M paramètres
GPT (2018-2023)
Generative Pre-trained Transformer
| Version | Paramètres | Contexte |
|---|---|---|
| GPT-1 | 117M | 512 |
| GPT-2 | 1.5B | 1024 |
| GPT-3 | 175B | 2048 |
| GPT-4 | ~1T? | 128k |
Autres modèles notables
- T5 : Text-to-Text, encoder-decoder
- RoBERTa : BERT optimisé
- ALBERT : BERT allégé
- LLaMA : Meta, open-source
- Mistral : Français, efficace
- Claude : Anthropic
Vision Transformer (ViT)
Applique les Transformers aux images.
Image 224×224
↓
Découpage en patches 16×16
↓
14×14 = 196 patches
↓
Flatten + Linear projection
↓
[CLS] + 196 patch embeddings + Position
↓
┌─────────────────────────┐
│ TRANSFORMER ENCODER │
└─────────────────────────┘
↓
Classification via [CLS]Scaling Laws
Plus c'est gros, mieux c'est (jusqu'à un certain point).
Performance ∝ (Paramètres)^α × (Données)^β × (Compute)^γ
Règle de Chinchilla :
Tokens d'entraînement ≈ 20 × ParamètresOptimisations modernes
Flash Attention
Attention optimisée pour GPU, réduit la mémoire.
from flash_attn import flash_attn_funcGrouped Query Attention (GQA)
Partage les Key/Value entre groupes de têtes.
Rotary Position Embedding (RoPE)
Encodage positionnel rotatif pour meilleure extrapolation.
Mixture of Experts (MoE)
Active seulement une partie des paramètres.
Input → Router → Expert 1 ← Sélectionné
→ Expert 2
→ Expert 3 ← Sélectionné
→ Expert 4Implémentation simplifiée
import torch
import torch.nn as nn
import math
class TransformerBlock(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.attention = MultiHeadAttention(d_model, num_heads)
self.feed_forward = FeedForward(d_model, d_ff)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
# Self-attention avec connexion résiduelle
attn_output = self.attention(x, x, x, mask)
x = self.norm1(x + self.dropout(attn_output))
# Feed-forward avec connexion résiduelle
ff_output = self.feed_forward(x)
x = self.norm2(x + self.dropout(ff_output))
return x
class GPTModel(nn.Module):
def __init__(self, vocab_size, d_model, num_heads, num_layers, d_ff, max_len):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoding = PositionalEncoding(d_model, max_len)
self.layers = nn.ModuleList([
TransformerBlock(d_model, num_heads, d_ff)
for _ in range(num_layers)
])
self.norm = nn.LayerNorm(d_model)
self.output = nn.Linear(d_model, vocab_size)
def forward(self, x):
seq_len = x.size(1)
mask = causal_mask(seq_len).to(x.device)
x = self.embedding(x)
x = self.pos_encoding(x)
for layer in self.layers:
x = layer(x, mask)
x = self.norm(x)
return self.output(x)Résumé
TRANSFORMER = Attention + Feed Forward + Residual + LayerNorm
Clés du succès :
├── Parallélisation (vs RNN séquentiel)
├── Attention globale (contexte long)
├── Scaling (plus de paramètres = meilleur)
└── Pré-entraînement + Fine-tuning
Variantes :
├── Encoder-only (BERT) : compréhension
├── Decoder-only (GPT) : génération
└── Encoder-Decoder (T5) : seq2seq