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

Attention Plus MLP, Répété

Structure Pre-Norm du Bloc Transformer


Deux sous-couches

Un bloc transformer contient exactement deux sous-couches, chacune opérant sur une séquence de tokens de forme [batch, seq_len, d_model] :


1. Sous-couche d'attention multi-tête. Les tokens se regardent les uns les autres. L'activité 68 a couvert cela en détail. La forme de sortie correspond à la forme d'entrée.

2. Sous-couche MLP feed-forward. Chaque token se transforme indépendamment à travers un perceptron à deux couches. Pas de flux d'information entre tokens. La forme de sortie correspond à la forme d'entrée.


Les deux sous-couches préservent la forme [batch, seq_len, d_model]. Cette préservation permet d'empiler les blocs : la sortie de la couche N alimente l'entrée de la couche N+1 sans acrobaties de forme.


Ce que chaque sous-couche apporte

L'attention déplace l'information entre les positions : un token en position 17 peut extraire des informations des positions 1 à 16. Le MLP transforme l'information au sein de chaque position : la représentation d'un token est remodelée par des fonctions non linéaires apprises, mais ne voit jamais ses voisins.


L'empilement des blocs alterne ces deux opérations. L'attention de la couche 1 mélange les positions. Le MLP de la couche 1 remodèle par position. L'attention de la couche 2 mélange à nouveau, maintenant sur les représentations remodelées. Cette alternation accroît la puissance expressive avec la profondeur.


La Pile d'ANDREA


Varianten_layern_headd_modelmlp_dim
ANDREA-12M6123841536
ANDREA-120M12127683072
ANDREA-480M162415366144

Remarquez que mlp_dim = 4 × d_model dans toute la famille. Ce ratio est vrai dans presque tous les transformers modernes. La section 3 explique pourquoi.

Nommer les sous-couches

Un bloc transformer contient deux sous-couches. Nommez-les dans l'ordre, & pour chacune indiquez si elle déplace l'information entre les positions (token-à-token) ou transforme l'information au sein d'une seule position (indépendant par token). Une phrase par sous-couche.

Pourquoi les connexions de saut sont importantes

Le motif résiduel

Chaque sous-couche enveloppe son calcul dans une connexion résiduelle. La sortie ajoute à nouveau l'entrée :


x = x + Attention(LayerNorm(x))     # sous-couche attention
x = x + MLP(LayerNorm(x))           # Sous-couche MLP

À l'intérieur de chaque sous-couche, la fonction Attention(...) ou MLP(...) produit un delta. Le bloc ne remplace pas l'entrée ; il ajoute une correction apprise.


Pourquoi cela compte

Trois raisons pour lesquelles les connexions résiduelles dominent les architectures profondes :


1. Gradient flow. Lors de la rétropropagation, l'addition donne aux gradients un chemin direct de la sortie vers l'entrée, contournant la sous-couche. Une pile de 12 couches sans résidus perdrait le signal de gradient bien avant d'atteindre les embeddings ; avec les résidus, la magnitude du gradient reste utilisable à travers des centaines de couches.


2. Initialisation d'identité. À l'initialisation, les poids de la sous-couche produisent de petites sorties. La connexion résiduelle signifie que la couche N transmet initialement presque inchangée. L'entraînement apprend les deltas progressivement à partir d'un point de départ fonctionnel.


3. Apprentissage compositionnel. Chaque bloc apprend un raffinement, pas un remplacement. La couche 1 pourrait ajouter des informations positionnelles. La couche 2 pourrait ajouter une structure syntaxique. La couche 7 pourrait ajouter des relations sémantiques. Le flux résiduel s'accumule.


Normalisation de Couche

Avant chaque sous-couche, LayerNorm remet à l'échelle la représentation de chaque token à une moyenne zéro & une variance unitaire, puis applique un gain & un biais appris par feature : [BLOCK_TYPE CONTENT residuals_and_norm/residual_motivation]


y = gamma * (x - mean(x)) / sqrt(var(x) + epsilon) + beta

La moyenne et la variance sont calculées sur la dimension d_model, séparément pour chaque token. Deux vecteurs appris (gamma, beta, chacun de forme [d_model]) restaurent l'échelle expressive. La normalisation maintient les activations dans une plage numériquement stable ; sans elle, de petites instabilités d'entraînement s'accumulent en gradients NaN.

Pre-Norm vs Post-Norm

Un choix subtil mais critique
```

Deux façons de connecter la layer norm dans une sous-couche résiduelle :


Post-norm (article original de 2017) :

x = LayerNorm(x + Attention(x))

La layer norm est placée après l'addition résiduelle. Le flux résiduel lui-même est normalisé à chaque couche.


Pré-norm (norme moderne standard, utilisée dans ANDREA) :

x = x + Attention(LayerNorm(x))

La layer norm est placée avant la sous-couche, à l'intérieur de la branche résiduelle. Le flux résiduel reste non normalisé ; seul l'entrée de la sous-couche est rééchelonnée.


Pourquoi la Pré-Norm a Gagné

La post-norm s'entraîne mal sans LR warmup et un réglage minutieux des hyperparamètres. Les gradients explosent dans les couches précoces car chaque layer norm perturbe l'état accumulé du flux résiduel. L'article original de 2017 utilisait la post-norm avec un réglage extensif ; les travaux ultérieurs (GPT-2, LLaMA, ANDREA) ont standardisé sur la pré-norm.


L'entraînement pré-norm est stable. Le flux résiduel s'accumule proprement à travers toutes les couches ; seuls les entrées des sous-couches sont normalisées pour la stabilité numérique. Les transformers modernes utilisent par défaut la pré-norm, & ANDREA hérite de ce choix.


Équation du Bloc Final

En combinant les résiduels, la layer norm en position pré-norm, & les deux sous-couches, on obtient le bloc complet d'ANDREA :


```python
def block_forward(x):
```
x = x + Attention(LayerNorm(x))   # sous-couche d'attention
x = x + MLP(LayerNorm(x))         # sous-couche MLP
return x

Deux sous-couches, deux additions résiduelles, deux normalisations de couche (note : chaque sous-couche a sa propre normalisation de couche ; ANDREA-120M a 24 normalisations de couche sur 12 blocs plus une finale en sortie, soit 25 au total). Répétez 12 fois. C'est le tronc d'ANDREA-120M.

Pourquoi la Pre-Norm Stabilise l'Entraînement

ANDREA utilise la pre-norm : `x = x + Attention(LayerNorm(x))`. Comparez avec la post-norm : `x = LayerNorm(x + Attention(x))`. Donnez une raison du point de vue du flux de gradients expliquant pourquoi la pre-norm entraîne plus stablement que la post-norm dans des piles profondes. Référez-vous au flux résiduel dans votre réponse.

Deux Couches Linéaires, Une Activation

Trois Opérations

La sous-couche MLP est un perceptron à deux couches avec une activation non linéaire entre les couches :


def mlp_forward(x):
h = x · W_1 + b_1        # expansion : d_model → mlp_dim
h = GELU(h)              # activation non linéaire
y = h · W_2 + b_2        # contraction : mlp_dim → d_model
return y

Trois opérations. Deux linéaires, une non linéaire. La première linéaire élargit la largeur ; la seconde la contracte à nouveau.


Le ratio d'expansion 4×

Les transformers modernes définissent mlp_dim = 4 × d_model. ANDREA-120M :


d_model = 768
mlp_dim = 4 × 768 = 3072
Forme de W_1 = [768, 3072]      # ~2.36M params
Forme de W_2 = [3072, 768]      # ~2.36M params
Params MLP par bloc = 4.72M (en ignorant les biais)

Deux MLP sont situés entre chaque paire de sous-couches d'attention (un par bloc). Douze blocs × 4.72M ≈ 56.6M paramètres MLP au total dans ANDREA-120M, environ la moitié de tous les paramètres.


Pourquoi 4×

Le ratio 4× est apparu empiriquement. Des ratios plus petits réduisent la capacité du modèle. Des ratios plus grands produisent des rendements décroissants par paramètre dépensé. Au fil de décennies de recherche d'architecture, le 4× s'est maintenu ; il apparaît dans GPT, BERT, T5, LLaMA, & ANDREA.


Des travaux récents (PaLM, Chinchilla) ont trouvé que les mécanismes de gating (SwiGLU) peuvent utiliser une expansion 8/3× avec une capacité comparable à moindre coût ; ANDREA reste avec le GELU classique + 4× pour la simplicité.

GELU : Une activation fluide

Ce que calcule GELU

GELU (Gaussian Error Linear Unit) est l'activation standard entre les couches MLP dans les transformers modernes. Sa formule :


GELU(x) = x · Φ(x)

Φ(x) est la fonction de répartition de la loi normale standard : la probabilité qu'une variable aléatoire gaussienne standard tombe à ou en dessous de x. Calculée numériquement :


Φ(x) ≈ 0.5 × (1 + tanh(sqrt(2/π) × (x + 0.044715 × x³)))

Comportement Par Région

- Pour de grandes valeurs positives de x : Φ(x) ≈ 1, donc GELU(x) ≈ x. Comme ReLU.

- Pour de grandes valeurs négatives de x : Φ(x) ≈ 0, donc GELU(x) ≈ 0. Comme ReLU.

- Près de x = 0 : Φ(x) ≈ 0.5, donc GELU(0) = 0 exactement. Transition fluide à travers l'origine.


Contrairement à ReLU, GELU laisse passer certaines entrées négatives, pondérées par Φ(x). Une petite entrée négative contribue encore à une petite sortie négative, juste moins que l'entrée complète ne le ferait.


Pourquoi GELU surpasse ReLU

Empiriquement, les transformers entraînés avec GELU atteignent une perte plus faible que ceux entraînés avec ReLU pour le même nombre de paramètres. La douceur autour de zéro est importante : la coupure abrupte de ReLU à zéro produit des discontinuités de gradient ; la courbe lisse de GELU fournit des gradients plus nets pour la rétropropagation.


Le moteur d'entraînement d'ANDREA microgpt_cuda.cu inclut un noyau CUDA GELU écrit à la main. Le noyau utilise l'approximation tanh ci-dessus ; les GPU modernes incluent tanh comme une opération à instruction unique.

Calcul des paramètres MLP

ANDREA-120M a `d_model=768`, `mlp_dim=3072`, & `n_layer=12`. Calculez le nombre total de paramètres dans les matrices de poids MLP (`W_1` & `W_2`) sur les 12 blocs. Ignorez les biais. Montrez votre arithmétique. Puis indiquez quelle fraction des ~120M paramètres totaux d'ANDREA-120M cela représente (arrondir à une décimale).

Douze Blocs Composent ANDREA-120M

Du Bloc au Modèle

Le passage avant complet d'ANDREA-120M :


def model_forward(token_ids):
x = token_embed(token_ids) + position_embed(positions)
for block_idx in range(n_layer):       # 12 blocs
x = block_forward(x)               # attention + MLP avec résiduels
x = LayerNorm(x)                       # norme finale
logits = x · token_embed.T             # poids liés pour la projection de sortie
return logits

Six lignes. Le gros du travail se passe dans block_forward, appelée douze fois. Les embeddings démarrent le pipeline ; unembedding lié (la même matrice utilisée pour la recherche d'entrée, transposée pour la projection de sortie) le termine.


Profondeur Comme Composition

Chaque bloc lit le flux résiduel, calcule un delta, & l'ajoute en retour. Après douze passages, le flux contient les contributions accumulées de chaque bloc. En interne, les couches tendent à se spécialiser :


- Couches précoces (1-3) : motifs syntaxiques, structure positionnelle

- Couches intermédiaires (4-8) : relations entre mots, frontières de phrases

- Couches tardives (9-12) : contenu sémantique, rappel factuel


Cette spécialisation émerge de la pression d'entraînement, et non des choix architecturaux. La même conception de bloc uniforme produit des couches spécialisées lorsqu'elle est entraînée sur le langage.


Nombre total de paramètres par bloc


ComposantPar blocSur 12 blocs
Projections d'attention (4×W)2,36M28,3M
Poids MLP (W_1 + W_2)4,72M56,6M
Normalisations de couches (gamma, beta)~3K (négligeable)~37K
Total par bloc~7,1M~85M

85M paramètres dans le tronc. Ajoutez ~13M dans les embeddings de tokens (8449 vocab × 768 d_model × 2 pour input/output liés) plus une normalisation de couche finale, & le nombre de paramètres d'ANDREA-120M atteint environ 120M. La conception des blocs représente les deux tiers ; les embeddings le reste.

Traçage d'un token à travers un bloc

Un vecteur de token de 768 dimensions entre dans le bloc 7 d'ANDREA-120M. Décrivez étape par étape ce qui lui arrive à l'intérieur du bloc (dans une structure pre-norm). Mentionnez : les deux normalisations de couches, les deux sous-couches, les deux additions résiduelles, & la forme finale. Indiquez au moins un endroit où le flot résiduel reste intact & un endroit où il est modifié.