Uwaga Plus MLP, Powtarzane
Dwie Podwarstwy
Blok transformera zawiera dokładnie dwie podwarstwy, z których każda działa na sekwencji tokenów o kształcie [batch, seq_len, d_model]:
1. Podwarstwa uwagi wielogłowicowej. Tokeny patrzą na siebie nawzajem. Aktywność 68 omówiła to szczegółowo. Kształt wyjściowy odpowiada kształtowi wejściowemu.
2. Podwarstwa MLP feed-forward. Każdy token przekształca się niezależnie poprzez dwuwarstwowy perceptron. Brak przepływu informacji między tokenami. Kształt wyjściowy odpowiada kształtowi wejściowemu.
Obie podwarstwy zachowują kształt [batch, seq_len, d_model]. To zachowanie pozwala na układanie bloków w stos: wyjście warstwy N zasila wejście warstwy N+1 bez akrobacji z kształtem.
Co wnosi każda podwarstwa
Attention przenosi informacje między pozycjami: token w pozycji 17 może pobrać informacje z pozycji 1 do 16. MLP przekształca informacje w obrębie każdej pozycji: reprezentacja tokena jest kształtowana przez wyuczone nieliniowe funkcje, ale nigdy nie widzi swoich sąsiadów.
Układanie bloków na przemian wykonuje te dwie operacje. Warstwa 1 attention miesza pozycje. Warstwa 1 MLP zmienia kształt dla każdej pozycji. Warstwa 2 attention miesza ponownie, teraz nad zmienionymi reprezentacjami. Ta na przemian rośnie siła wyrazu wraz z głębokością.
Stos ANDREA
| Wariant | 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 |
Zauważ mlp_dim = 4 × d_model w całej rodzinie. To proporcja zachowuje się w niemal każdym nowoczesnym transformerze. Sekcja 3 wyjaśnia dlaczego.
Nazwanie podwarstw
Dlaczego połączenia pomijające są ważne
Wzorzec resztkowy
Każda podwarstwa owija swoje obliczenia w połączenie resztkowe. Wyjście dodaje z powrotem wejście:
x = x + Attention(LayerNorm(x)) # podwarstwa uwagi
x = x + MLP(LayerNorm(x)) # podwarstwa MLP
W każdej podwarstwie funkcja Attention(...) lub MLP(...) produkuje deltę. Blok nie zastępuje wejścia; dodaje wyuczoną korektę.
Dlaczego to ma znaczenie
Trzy powody, dla których połączenia resztowe dominują w głębokich architekturach:
1. Przepływ gradientu. Podczas propagacji wstecznej, dodawanie daje gradientom bezpośrednią ścieżkę od wyjścia z powrotem do wejścia, omijając podwarstwę. Stos 12 warstw bez resztkowych połączeń straciłby sygnał gradientu na długo przed dotarciem do osadzeń; z resztkami, wielkość gradientu pozostaje użyteczna przez setki warstw.
2. Inicjalizacja tożsamościowa. Podczas inicjalizacji, wagi podwarstwy produkują małe wyjścia. Połączenie resztkowe oznacza, że warstwa N początkowo przechodzi prawie niezmieniona. Trening uczy się delt progresywnie od działającego punktu startowego.
3. Uczenie kompozycyjne. Każdy blok uczy się udoskonalenia, nie zastąpienia. Warstwa 1 może dodać informacje pozycyjne. Warstwa 2 może dodać strukturę syntaktyczną. Warstwa 7 może dodać relacje semantyczne. Strumień resztkowy akumuluje.
Normalizacja warstw
Przed każdą podwarstwą, LayerNorm przeskalowuje reprezentację każdego tokenu do zera średniej i wariancji jednostkowej, a następnie stosuje wyuczone wzmocnienie i bias na funkcję:
y = gamma * (x - mean(x)) / sqrt(var(x) + epsilon) + beta
Średnia & wariancja są obliczane wzdłuż wymiaru d_model, oddzielnie dla każdego tokenu. Dwa wyuczone wektory (gamma, beta, każdy o kształcie [d_model]) przywracają ekspresyjną skalę. Normalizacja utrzymuje aktywacje w numerycznie stabilnym zakresie; bez niej małe niestabilności treningowe lawinują do gradientów NaN.
Pre-Norm vs Post-Norm
Subtelny, ale krytyczny wybór
Dwa sposoby podłączenia normalizacji warstw do podwarstwy resztkowej:
Post-norm (oryginalny artykuł z 2017 roku):
x = LayerNorm(x + Attention(x))
Normalizacja warstw następuje po dodaniu resztki. Sam strumień resztkowy jest normalizowany w każdej warstwie.
Pre-norm (nowoczesny standard, używany w ANDREA):
x = x + Attention(LayerNorm(x))
Normalizacja warstwowa znajduje się przed podwarstwą, wewnątrz gałęzi resztkowej. Strumień resztkowy pozostaje nienormalizowany; tylko wejście do podwarstwy jest przeskalowane.
Dlaczego Pre-Norm zwyciężył
Post-norm trenuje się słabo bez rozgrzewki LR i starannego strojenia hiperparametrów. Gradienty eksplodują we wczesnych warstwach, ponieważ każda normalizacja warstwowa miesza nagromadzony stan strumienia resztkowego. Oryginalny artykuł z 2017 roku używał post-norm z intensywnym strojeniem; kolejne prace (GPT-2, LLaMA, ANDREA) ustandaryzowały pre-norm.
Trening pre-norm jest stabilny. Strumień resztkowy akumuluje się czysto we wszystkich warstwach; tylko wejścia podwarstw są normalizowane dla stabilności numerycznej. Nowoczesne transformery domyślnie używają pre-norm, & ANDREA dziedziczy ten wybór.
Równanie Końcowego Bloku
Połączenie resztkowych połączeń, normalizacji warstw w pozycji pre-norm & obu podwarstw daje pełny blok ANDREA:
```python
def block_forward(x):
```
x = x + Attention(LayerNorm(x)) # podwarstwa uwagi
x = x + MLP(LayerNorm(x)) # podwarstwa MLP
return x
Dwie podwarstwy, dwa resztkowe dodatki, dwie normalizacje warstw (uwaga: każda podwarstwa ma własną normalizację warstw; ANDREA-120M ma 24 normalizacje warstw w 12 blokach plus jedną końcową na wyjściu, czyli łącznie 25). Powtórz 12 razy. To pień ANDREA-120M.
Dlaczego Pre-Norm Stabilizuje Trening
Dwie Warstwy Liniowe, Jedna Aktywacja
Trzy Operacje
Podwarstwa MLP to dwuwarstwowy perceptron z nieliniową aktywacją między warstwami:
def mlp_forward(x):
h = x · W_1 + b_1 # rozszerz: d_model → mlp_dim
h = GELU(h) # nieliniowa aktywacja
y = h · W_2 + b_2 # skompresuj: mlp_dim → d_model
return y
Trzy operacje. Dwie liniowe, jedna nieliniowa. Pierwsza liniowa rozszerza szerokość; druga kurczy z powrotem.
Stosunek rozszerzenia 4×
Nowoczesne transformery ustawiają mlp_dim = 4 × d_model. ANDREA-120M:
d_model = 768
mlp_dim = 4 × 768 = 3072
Kształt W_1 = [768, 3072] # ~2.36M parametrów
Kształt W_2 = [3072, 768] # ~2.36M parametrów
Parametry MLP na blok = 4.72M (ignorując biasy)
Dwa MLP znajdują się między każdą parą podwarstw uwagi (jedno na blok). Dwanaście bloków × 4.72M ≈ 56.6M parametrów MLP łącznie w ANDREA-120M, mniej więcej połowa wszystkich parametrów.
Dlaczego 4×
Stosunek 4× wyłonił się empirycznie. Mniejsze stosunki zmniejszają pojemność modelu. Większe stosunki dają malejące zwroty na parametr. Przez dekady poszukiwań architektur, 4× się sprawdził; pojawia się w GPT, BERT, T5, LLaMA i ANDREA.
Najnowsze prace (PaLM, Chinchilla) wykazały, że mechanizmy bramkowania (SwiGLU) mogą używać rozszerzenia 8/3× z porównywalną pojemnością przy mniejszym koszcie; ANDREA pozostaje przy klasycznym GELU + 4× dla prostoty.
GELU: Płynna Aktywacja
Co Oblicza GELU
GELU (Gaussian Error Linear Unit) to standardowa aktywacja między warstwami MLP w nowoczesnych transformerach. Jej formuła:
GELU(x) = x · Φ(x)
Φ(x) to funkcja dystrybuucji skumulowanej standardowego rozkładu normalnego: prawdopodobieństwo, że standardowa zmienna losowa gaussowska spadnie na poziomie x lub poniżej. Obliczana numerycznie:
Φ(x) ≈ 0.5 × (1 + tanh(sqrt(2/π) × (x + 0.044715 × x³)))
Zachowanie w zależności od regionu
- Dla dużych dodatnich x: Φ(x) ≈ 1, więc GELU(x) ≈ x. Podobnie jak ReLU.
- Dla dużych ujemnych x: Φ(x) ≈ 0, więc GELU(x) ≈ 0. Podobnie jak ReLU.
- W pobliżu x = 0: Φ(x) ≈ 0.5, więc GELU(0) = 0 dokładnie. Płynne przejście przez początek.
W przeciwieństwie do ReLU, GELU przepuszcza niektóre ujemne wejścia, ważone przez Φ(x). Małe ujemne wejście nadal wnosi małe ujemne wyjście, tylko mniejsze niż pełne wejście.
Dlaczego GELU przewyższył ReLU
Empirycznie transformery trenowane z GELU osiągają niższą stratę niż transformery trenowane z ReLU przy tej samej liczbie parametrów. Gładkość wokół zera ma znaczenie: ostry próg ReLU przy zerze powoduje nieciągłości gradientu; gładka krzywa GELU zapewnia czystsze gradienty do propagacji wstecznej.
Silnik treningowy ANDREA microgpt_cuda.cu zawiera ręcznie napisane jądro GELU CUDA. Jądro używa powyższego przybliżenia tanh; nowoczesne GPU zawierają tanh jako operację jednorozkazową.
Obliczanie parametrów MLP
Dwanaście bloków składa się na ANDREA-120M
Od bloku do modelu
Pełny forward pass ANDREA-120M:
def model_forward(token_ids):
x = token_embed(token_ids) + position_embed(positions)
for block_idx in range(n_layer): # 12 bloków
x = block_forward(x) # attention + MLP z resztkami
x = LayerNorm(x) # norm finalna
logits = x · token_embed.T # powiązane wagi dla projekcji wyjściowej
return logits
Sześć linii. Główna część znajduje się wewnątrz block_forward, wywoływanego dwanaście razy. Embeddings uruchamiają potok; powiązane unembedding (ta sama macierz używana do wyszukiwania wejścia, transponowana do projekcji wyjścia) kończy go.
Głębokość jako kompozycja
Każdy blok odczytuje strumień resztkowy, oblicza deltę i dodaje ją z powrotem. Po dwunastu przejściach strumień zawiera nagromadzone wkłady z każdego bloku. Wewnętrznie warstwy mają tendencję do specjalizacji:
- Wczesne warstwy (1-3): wzorce syntaktyczne, struktura pozycyjna
- Środkowe warstwy (4-8): relacje między słowami, granice fraz
- Późne warstwy (9-12): treść semantyczna, przywoływanie faktów
Ta specjalizacja wynika z ciśnienia treningowego, a nie z wyborów architektonicznych. Ten sam jednolity projekt bloku produkuje wyspecjalizowane warstwy po treningu na języku.
Całkowita liczba parametrów bloku
| Komponent | Na blok | W 12 blokach |
|---|---|---|
| Projekcje uwagi (4×W) | 2,36M | 28,3M |
| Wagi MLP (W_1 + W_2) | 4,72M | 56,6M |
| Normalizacje warstw (gamma, beta) | ~3 tys. (pomijalne) | ~37 tys. |
| Łącznie na blok | ~7,1M | ~85M |
85M parametrów w pniu. Dodaj ~13M w osadzeniach tokenów (8449 słownictwa × 768 d_model × 2 dla powiązanych wejścia/wyjścia) plus końcową normalizację warstw, & liczba parametrów ANDREA-120M wynosi około 120M. Projekt bloku stanowi dwie trzecie; osadzenia resztę.