Attention plus MLP, upprepat
Två underlager
En transformer-block innehåller exakt två underlager, var och ett som arbetar på en token-sekvens med formen [batch, seq_len, d_model]:
1. Multi-head attention-sublager. Tokenerna tittar på varandra. Aktivitet 68 täckte detta i detalj. Utformning av utdata matchar indatans form.
2. Feed-forward MLP-sublager. Varje token transformeras oberoende genom en tvålagrig perceptron. Ingen informationsflöde mellan token. Utformning av utdata matchar indatans form.
Båda sublager bevarar formen [batch, seq_len, d_model]. Denna bevarande tillåter att blocken staplas: lager N:s utdata matar lager N+1:s indata utan formakrobatik.
Vad varje sublager bidrar med
Attention flyttar information över positioner: en token på position 17 kan hämta information från positioner 1 till 16. MLP:n transformerar information inom varje position: en tokens representation omformas genom inlärda icke-linjära funktioner, men ser aldrig sina grannar.
Stapling av block varvar dessa två operationer. Lager 1 attention blandar positioner. Lager 1 MLP omformar per position. Lager 2 attention blandar igen, nu över de omformade representationerna. Denna varvning ökar den uttrycksfulla kraften med djupet.
ANDREAs Stapel
| Variant | n_layer | n_head | d_model | mlp_dim |
|---|---|---|---|---|
| ANDREA-12M | 6 | 12 | 384 | 1536 |
| ANDREA-120M | 12 | 12 | 768 | 3072 |
| ANDREA-480M | 16 | 24 | 1536 | 6144 |
Observera mlp_dim = 4 × d_model över hela familjen. Den ratio håller i nästan varje modern transformer. Avsnitt 3 går igenom varför.
Benämna de två underlagren
Varför hopkopplingar är viktiga
Residualmönstret
Varje sublayer omsluter sin beräkning i en residualkoppling. Utgången lägger till indata igen:
x = x + Attention(LayerNorm(x)) # attention sublayer
x = x + MLP(LayerNorm(x)) # MLP-sublager
Inne i varje sublayer producerar funktionen Attention(...) eller MLP(...) en delta. Blocket ersätter inte indata; det lägger till en inlärd korrigering.
Varför detta är viktigt
Tre skäl till varför residualanslutningar dominerar djupa arkitekturer:
1. Gradientflöde. Under backpropagering ger additionen gradienterna en direkt väg från utgången tillbaka till ingången, och kringgår delskiktet. En 12-lagers stapel utan residualer skulle förlora gradientsignalen långt innan den når inbäddningarna; med residualer förblir gradientens magnitud användbar genom hundratals lager.
2. Identitetsinitialisering. Vid initialisering producerar delskiktets vikter små utdata. Residualanslutningen innebär att lager N initialt passerar igenom nästan oförändrat. Träningen lär sig delta progressivt från en fungerande startpunkt.
3. Kompositionell inlärning. Varje block lär sig en förfining, inte en ersättning. Lager 1 kanske lägger till positionsinformation. Lager 2 kanske lägger till syntaktisk struktur. Lager 7 kanske lägger till semantiska relationer. Residualströmmen ackumuleras.
Lager-normalisering
Före varje delskikt skalar LayerNorm varje tokens representation till noll medelvärde & enhetsvarians, och applicerar sedan inlärd per-funktion gain & bias:
y = gamma * (x - mean(x)) / sqrt(var(x) + epsilon) + beta
Medelvärde & varians beräknas över d_model-dimensionen, separat för varje token. Två inlärda vektorer (gamma, beta, varje form [d_model]) återställer expressiv skala. Normalisering håller aktiveringar i ett numeriskt stabilt intervall; utan den snöbollar små träningsinstabiliteter till NaN-gradienter.
Pre-Norm vs Post-Norm
Ett subtilt men kritiskt val
Två sätt att koppla in layer norm i ett residualt sublager:
Post-norm (originalartikel från 2017):
x = LayerNorm(x + Attention(x))
Layer norm sitter efter den residuala additionen. Den residuala strömmen normaliseras i varje lager.
Pre-norm (modern standard, används i ANDREA):
x = x + Attention(LayerNorm(x))
Layer norm sitter före sublager, inuti den residuala grenen. Den residuala strömmen förblir onormaliserad; endast inmatningen till sublager skalas om.
Varför Pre-Norm Vann
Post-norm tränar dåligt utan LR-uppvärmning & noggrann hyperparameterjustering. Gradienterna exploderar i tidiga lager eftersom varje layer norm scramblar den residuala strömmens ackumulerade tillstånd. Den ursprungliga artikeln från 2017 använde post-norm med omfattande justering; efterföljande arbete (GPT-2, LLaMA, ANDREA) standardiserade på pre-norm.
Pre-norm tränar stabilt. Residualströmmen ackumuleras rent över alla lager; endast underlagsinmatningar normaliseras för numerisk stabilitet. Moderna transformatorer använder som standard pre-norm, & ANDREA ärver det valet.
Slutlig blockekvation
Kombinera residualer, layer norm i pre-norm-position & båda underlagen ger ANDREA:s fullständiga block:
def block_forward(x):
x = x + Attention(LayerNorm(x)) # attention-sublager
x = x + MLP(LayerNorm(x)) # MLP-sublager
return x
Två sublager, två residuala additioner, två layer norms (notera: varje sublayer har sin egen layer norm; ANDREA-120M har 24 layer norms över 12 block plus en final vid utgången, så 25 totalt). Upprepa 12 gånger. Det är stommen i ANDREA-120M.
Varför Pre-Norm Stabiliserar Träning
Två Linjära Lager, En Aktivering
Tre Operationer
MLP-underrlaget är en tvålager perceptron med en icke-linjär aktivering mellan lagren:
def mlp_forward(x):
h = x · W_1 + b_1 # expandera: d_model → mlp_dim
h = GELU(h) # icke-linjär aktivering
y = h · W_2 + b_2 # komprimera: mlp_dim → d_model
return y
Tre operationer. Två linjära, en icke-linjär. Den första linjära expanderar bredden; den andra drar ihop tillbaka.
4× Expansionsförhållandet
Moderna transformatorer ställer in mlp_dim = 4 × d_model. ANDREA-120M:
d_model = 768
mlp_dim = 4 × 768 = 3072
W_1 form = [768, 3072] # ~2,36M parametrar
W_2 form = [3072, 768] # ~2,36M parametrar
MLP-parametrar per block = 4,72M (ignorerar bias)
Två MLP:er sitter mellan varje par uppmärksamhetssublager (en per block). Tolv block × 4,72M ≈ 56,6M MLP-parametrar totalt i ANDREA-120M, ungefär hälften av alla parametrar.
Varför 4×
4×-förhållandet uppstod empiriskt. Mindre förhållanden minskar modellkapaciteten. Större förhållanden ger avtagande avkastning per parameter som spenderas. Under årtionden av arkitektursökningar har 4× hållit sig; det förekommer i GPT, BERT, T5, LLaMA & ANDREA.
Nylig forskning (PaLM, Chinchilla) fann att gating-mekanismer (SwiGLU) kan använda 8/3×-expansion med jämförbar kapacitet till lägre kostnad; ANDREA håller sig till klassisk GELU + 4× för enkelhet.
GELU: En mjuk aktivering
Vad GELU beräknar
GELU (Gaussian Error Linear Unit) är den standardaktiveringen mellan MLP-lager i moderna transformatorer. Dess formel:
GELU(x) = x · Φ(x)
Φ(x) är kumulativa fördelningsfunktionen för standardnormalfördelningen: sannolikheten att en standardnormalfördelad slumpvariabel hamnar på eller under x. Beräknas numeriskt:
Φ(x) ≈ 0.5 × (1 + tanh(sqrt(2/π) × (x + 0.044715 × x³)))
Beteende efter region
- För stora positiva x: Φ(x) ≈ 1, så GELU(x) ≈ x. Liksom ReLU.
- För stora negativa x: Φ(x) ≈ 0, så GELU(x) ≈ 0. Liksom ReLU.
- Nära x = 0: Φ(x) ≈ 0.5, så GELU(0) = 0 exakt. Mjuk övergång genom origo.
Till skillnad från ReLU låter GELU vissa negativa inmatningar passera, viktade av Φ(x). En liten negativ inmatning bidrar fortfarande med en liten negativ utmatning, bara mindre än den fulla inmatningen skulle göra.
Varför GELU överträffade ReLU
Empiriskt når transformatorer tränade med GELU lägre förlust än transformatorer tränade med ReLU vid samma parameterantal. Mjukheten runt noll är viktig: ReLU:s hårda avskärning vid noll producerar gradientavbrott; GELU:s mjuka kurva ger renare gradienter för bakåtpropagering.
ANDREA:s träningsmotor microgpt_cuda.cu levererar en handskriven GELU CUDA-kärna. Kärnan använder tanh-approximationen ovan; moderna GPU:er inkluderar tanh som en enkelinstruktionsoperation.
Beräkna MLP-parametrar
Tolv block bildar ANDREA-120M
Från block till modell
ANDREA-120M:s fullständiga forward pass:
def model_forward(token_ids):
x = token_embed(token_ids) + position_embed(positions)
for block_idx in range(n_layer): # 12 block
x = block_forward(x) # attention + MLP med residualer
x = LayerNorm(x) # slutnorm
logits = x · token_embed.T # delade vikter för utdata-projektion
return logits
Sex rader. Huvudmassan finns inuti block_forward, som anropas tolv gånger. Embeddings startar pipelinen; bunden unembedding (samma matris som används för inmatningssökning, transponerad för utdataprojektion) avslutar den.
Djup som komposition
Varje block läser residualströmmen, beräknar en delta och lägger till den igen. Efter tolv pass innehåller strömmen ackumulerade bidrag från varje block. Internt tenderar lagren att specialisera sig:
- Tidiga lager (1-3): syntaktiska mönster, positionell struktur
- Mellersta lager (4-8): ordrelationer, frasgränser
- Sena lager (9-12): semantiskt innehåll, faktisk återkallelse
Denna specialisering uppstår från träningstryck, inte från arkitektoniska val. Samma enhetliga blockdesign producerar specialiserade lager när den tränas på språk.
Totala blockparametrar
| Komponent | Per block | Över 12 block |
|---|---|---|
| Attention-projektioner (4×W) | 2,36M | 28,3M |
| MLP-vikter (W_1 + W_2) | 4,72M | 56,6M |
| Lagernormaliseringar (gamma, beta) | ~3K (försumbar) | ~37K |
| Totalt per block | ~7,1M | ~85M |
85M parametrar i stammen. Lägg till ~13M i tokeninbäddningar (8449 vocab × 768 d_model × 2 för bunden in/ut) plus en final lagernormalisering, & ANDREA-120M:s parametrar landar på ungefär 120M. Blockdesignen står för två tredjedelar; inbäddningar resten.