English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

ospite
1 / ?
torna alle lezioni

Attenzione Più MLP, Ripetuto

Struttura Pre-Norm del Blocco Transformer


Due Sottolivelli

Un blocco transformer contiene esattamente due sottolivelli, ciascuno che opera su una sequenza di token di forma [batch, seq_len, d_model]:


1. Sottostrato di attenzione multi-head. I token si guardano a vicenda. L'Attività 68 ha trattato questo in dettaglio. La forma dell'output corrisponde alla forma dell'input.

2. Sottostrato MLP feed-forward. Ogni token si trasforma indipendentemente attraverso un percettrone a due strati. Nessun flusso di informazioni tra token. La forma dell'output corrisponde alla forma dell'input.


Entrambi i sottostrati preservano la forma [batch, seq_len, d_model]. Questa preservazione permette di impilare i blocchi: l'output del layer N alimenta l'input del layer N+1 senza acrobazie di forma.


Cosa Contribuisce Ogni Sottostrato

L'attenzione sposta le informazioni tra le posizioni: un token in posizione 17 può estrarre informazioni dalle posizioni 1 attraverso 16. L'MLP trasforma le informazioni all'interno di ogni posizione: la rappresentazione di un token viene rimodellata attraverso funzioni non lineari apprese, ma non vede mai i suoi vicini.


Lo stacking dei blocchi alterna queste due operazioni. L'attenzione del Layer 1 mescola le posizioni. L'MLP del Layer 1 rimodella per-posizione. L'attenzione del Layer 2 mescola di nuovo, ora sulle rappresentazioni rimodellate. Questa alternanza aumenta il potere espressivo con la profondità.


Lo Stack di ANDREA


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

Nota che mlp_dim = 4 × d_model in tutta la famiglia. Questo rapporto vale in quasi ogni transformer moderno. La Sezione 3 spiega il perché.

Nominare i Sottostrati

Un blocco transformer contiene due sottostrati. Nomina i sottostrati in ordine, & per ciascuno indica se sposta informazioni tra posizioni (da token a token) o trasforma informazioni all'interno di una singola posizione (indipendente per token). Una frase per sottostrato.

Perché le Connessioni di Skip Sono Importanti

Il Pattern Residuo

Ogni sublayer avvolge il suo calcolo in una connessione residua. L'output aggiunge indietro l'input:


x = x + Attention(LayerNorm(x))     # sublayer attention
x = x + MLP(LayerNorm(x))           # Sottolivello MLP

All'interno di ciascun sottolivello, la funzione Attention(...) o MLP(...) produce un delta. Il blocco non sostituisce l'input; aggiunge una correzione appresa.


Perché è Importante

Tre ragioni per cui le connessioni residue dominano le architetture profonde:


1. Flusso del gradiente. Durante la backpropagation, l'addizione fornisce ai gradienti un percorso diretto dall'output all'input, bypassando il sottomodulo. Una pila di 12 layer senza residui perderebbe il segnale del gradiente molto prima di raggiungere gli embedding; con i residui, la magnitudine del gradiente rimane utilizzabile attraverso centinaia di layer.


2. Inizializzazione identity. All'inizializzazione, i pesi del sottomodulo producono output piccoli. La connessione residua significa che il layer N inizialmente passa quasi invariato. L'addestramento impara i delta progressivamente da un punto di partenza funzionante.


3. Apprendimento composizionale. Ogni blocco impara un raffinamento, non una sostituzione. Il Layer 1 potrebbe aggiungere informazioni posizionali. Il Layer 2 potrebbe aggiungere struttura sintattica. Il Layer 7 potrebbe aggiungere relazioni semantiche. Il flusso residuo accumula.


Normalizzazione del Layer

Prima di ogni sottomodulo, LayerNorm ridimensiona la rappresentazione di ogni token a media zero e varianza unitaria, poi applica gain e bias appresi per feature: [BLOCK_TYPE CONTENT residuals_and_norm/residual_motivation]


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

La media e la varianza vengono calcolate lungo la dimensione d_model, separatamente per ogni token. Due vettori appresi (gamma, beta, ciascuno di forma [d_model]) ripristinano la scala espressiva. La normalizzazione mantiene le attivazioni in un intervallo numericamente stabile; senza di essa, piccole instabilità durante l'addestramento si accumulano in gradienti NaN.

Pre-Norm vs Post-Norm

Una Scelta Sottile Ma Critica

Due modalità per integrare la layer norm in un sottolivello residuo:


Post-norm (paper originale del 2017):

x = LayerNorm(x + Attention(x))

La layer norm si trova dopo l'addizione residua. Il flusso residuo stesso viene normalizzato in ogni layer.


Pre-norm (standard moderno, usato in ANDREA):

x = x + Attention(LayerNorm(x))

La layer norm si trova prima del sublayer, all'interno del ramo residuo. Il residual stream rimane non normalizzato; solo l'input al sublayer viene ridimensionato.


Perché Pre-Norm Ha Vinto

Post-norm si allena male senza LR warmup e un tuning attento degli iperparametri. I gradienti esplodono nei layer iniziali perché ogni layer norm rimescola lo stato accumulato del residual stream. L'articolo originale del 2017 usava post-norm con un tuning estensivo; lavori successivi (GPT-2, LLaMA, ANDREA) hanno standardizzato su pre-norm.


L'addestramento pre-norm è stabile. Lo stream residuo si accumula in modo pulito attraverso tutti i layer; solo gli input dei sublayer vengono normalizzati per stabilità numerica. I transformer moderni usano di default pre-norm, & ANDREA eredita quella scelta.


Equazione del Blocco Finale

Combinando i residuali, la layer norm in posizione pre-norm, & entrambi i sublayer si ottiene il blocco completo di ANDREA:


```python
def block_forward(x):
```
x = x + Attention(LayerNorm(x))   # sottoclasse attention
x = x + MLP(LayerNorm(x))         # sottoclasse MLP
return x

Due sottoclassi, due addizioni residue, due normalizzazioni di layer (nota: ogni sottoclasse ha la propria normalizzazione di layer; ANDREA-120M ha 24 normalizzazioni di layer attraverso 12 blocchi più una finale in output, quindi 25 totali). Ripeti 12 volte. Questo è il tronco di ANDREA-120M.

Perché Pre-Norm Stabilizza l'Addestramento

ANDREA usa pre-norm: `x = x + Attention(LayerNorm(x))`. Confronta con post-norm: `x = LayerNorm(x + Attention(x))`. Dai una ragione da una prospettiva di flusso dei gradienti per cui pre-norm addestra in modo più stabile di post-norm in stack profondi. Fai riferimento allo stream residuo nella tua risposta.

Due Layer Lineari, Un'Attivazione

Tre Operazioni

Il sottolayer MLP è un perceptron a due layer con un'attivazione non lineare tra i layer:


def mlp_forward(x):
h = x · W_1 + b_1        # espansione: d_model → mlp_dim
h = GELU(h)              # attivazione non lineare
y = h · W_2 + b_2        # contrazione: mlp_dim → d_model
return y

Tre operazioni. Due lineari, una non lineare. La prima lineare espande la larghezza; la seconda la contrae di nuovo.


Il Rapporto di Espansione 4×

I transformer moderni impostano mlp_dim = 4 × d_model. ANDREA-120M:


d_model = 768
mlp_dim = 4 × 768 = 3072
Forma di W_1 = [768, 3072]      # ~2.36M parametri
Forma di W_2 = [3072, 768]      # ~2.36M parametri
Parametri MLP per blocco = 4.72M (ignorando i bias)

Due MLP si trovano tra ogni coppia di sottocameri di attenzione (una per blocco). Dodici blocchi × 4.72M ≈ 56.6M parametri MLP totali in ANDREA-120M, circa la metà di tutti i parametri.


Perché 4×

Il rapporto 4× è emerso empiricamente. Rapporti più piccoli riducono la capacità del modello. Rapporti più grandi producono rendimenti decrescenti per parametro speso. Attraverso decenni di ricerca architetturale, il 4× ha resistito; appare in GPT, BERT, T5, LLaMA e ANDREA.


Lavori recenti (PaLM, Chinchilla) hanno scoperto che i meccanismi di gating (SwiGLU) possono usare un'espansione 8/3× con capacità comparabile a minor costo; ANDREA rimane con il classico GELU + 4× per semplicità.

GELU: Un'Attivazione Fluida

Cosa Calcola GELU

GELU (Gaussian Error Linear Unit) è l'attivazione standard tra i layer MLP nei transformer moderni. La sua formula:


GELU(x) = x · Φ(x)

Φ(x) è la funzione di distribuzione cumulativa della normale standard: la probabilità che una variabile casuale gaussiana standard cada a o sotto x. Calcolata numericamente:


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

Comportamento Per Regione

- Per x grandi positivi: Φ(x) ≈ 1, quindi GELU(x) ≈ x. Come ReLU.

- Per x grandi negativi: Φ(x) ≈ 0, quindi GELU(x) ≈ 0. Come ReLU.

- Vicino a x = 0: Φ(x) ≈ 0.5, quindi GELU(0) = 0 esattamente. Transizione fluida attraverso l'origine.


A differenza di ReLU, GELU lascia passare alcuni input negativi, pesati da Φ(x). Un piccolo input negativo contribuisce ancora a un piccolo output negativo, solo meno dell'input completo.


Perché GELU ha superato ReLU

Sperimentalmente, i transformer addestrati con GELU raggiungono una perdita inferiore rispetto ai transformer addestrati con ReLU con lo stesso numero di parametri. La regolarità intorno allo zero è importante: il taglio netto di ReLU a zero produce discontinuità nei gradienti; la curva regolare di GELU fornisce gradienti più puliti per la backpropagation.


Il motore di addestramento di ANDREA microgpt_cuda.cu include un kernel CUDA GELU scritto a mano. Il kernel usa l'approssimazione tanh sopra; le GPU moderne includono tanh come operazione a istruzione singola.

Calcolo dei Parametri MLP

ANDREA-120M ha `d_model=768`, `mlp_dim=3072` e `n_layer=12`. Calcola il totale dei parametri nelle matrici di peso MLP (`W_1` e `W_2`) su tutti i 12 blocchi. Ignora i bias. Mostra il tuo calcolo aritmetico. Poi indica che frazione dei ~120M parametri totali di ANDREA-120M rappresenta questo (arrotonda a una cifra decimale).

Dodici Blocchi Compongono ANDREA-120M

Dal Blocco al Modello

Il forward pass completo di ANDREA-120M:


def model_forward(token_ids):
x = token_embed(token_ids) + position_embed(positions)
for block_idx in range(n_layer):       # 12 blocchi
x = block_forward(x)               # attention + MLP w/ residuals
x = LayerNorm(x)                       # normalizzazione finale
logits = x · token_embed.T             # pesi condivisi per la proiezione di output
return logits

Sei linee. La parte principale vive dentro block_forward, chiamata dodici volte. Gli embedding avviano il pipeline; l'unembedding legato (la stessa matrice usata per la ricerca input, trasposta per la proiezione output) lo conclude.


Profondità Come Composizione

Ogni blocco legge lo residual stream, calcola una delta, e la aggiunge indietro. Dopo dodici passaggi, lo stream contiene contributi accumulati da ogni blocco. Internamente, i layer tendono a specializzarsi:


- Layer iniziali (1-3): pattern sintattici, struttura posizionale

- Layer medi (4-8): relazioni tra parole, confini di frasi

- Layer finali (9-12): contenuto semantico, richiamo fattuale


Questa specializzazione emerge dalla pressione dell'addestramento, non dalle scelte architettoniche. Lo stesso design di blocco uniforme produce strati specializzati quando addestrato sul linguaggio.


Parametri Totali del Blocco


ComponentePer bloccoSu 12 blocchi
Proiezioni di Attention (4×W)2.36M28.3M
Pesi MLP (W_1 + W_2)4.72M56.6M
Layer norms (gamma, beta)~3K (negligibile)~37K
Totale per blocco~7.1M~85M

85M parametri nel tronco. Aggiungi ~13M negli embedding dei token (8449 vocabolario × 768 d_model × 2 per input/output legati) più una layer norm finale, e il conteggio parametri di ANDREA-120M arriva a circa 120M. Il design del blocco rappresenta due terzi; gli embedding il resto.

Tracciamento di Un Token Attraverso Un Blocco

Un vettore token a 768 dimensioni entra nel blocco 7 di ANDREA-120M. Descrivi cosa gli succede all'interno del blocco (nella struttura pre-norm). Menziona: entrambe le layer norms, entrambi i sublayers, entrambe le addizioni residue, & la forma finale. Indica almeno un punto in cui lo stream residuo rimane intatto & un punto in cui viene modificato.