Przejdź do treści
9/30Rozdział 9 z 30

Attention i blok transformer wyprowadzone ze średniej

Zacznij od najtańszego podsumowania kontekstu — średniej — zobacz, gdzie zawodzi, i wyprowadź wzór attention z naprawy.

Na tej stronie

Trafiasz tu z tokenizerem z rozdziału 7, tabelą embedding z rozdziału 8 i celem, który im towarzyszy: mając dotychczasowe tokens, przypisać prawdopodobieństwo następnemu.

Brakuje środka. Aby przewidzieć token tt, model potrzebuje jednego wektora podsumowującego wszystko, co było wcześniej, a nic z tego, co zbudowałeś, takiego wektora nie daje. Embedding token t1t-1 nim nie jest — to model bigramowy i nie może wiedzieć, że zdanie zaczęło się od pytania. Konkatenacja wszystkich poprzednich embeddings też nim nie jest: ich liczba zmienia się na każdym kroku, a stała macierz wag nie może przyjąć wejścia o zmiennej długości.

A więc: jeden wektor o stałym rozmiarze, podsumowujący zmienną liczbę wektorów. To cały problem, a attention jest tym, co dostajesz, rozwiązując go w możliwie najbardziej leniwy sposób, a potem naprawiając dwie rzeczy, które się psują.

Odpowiedź, którą miała dziedzina, i dlaczego jej tu nie budujemy

Link do sekcji: Odpowiedź, którą miała dziedzina, i dlaczego jej tu nie budujemy

Od 1997 do około 2017 roku podsumowaniem był stan rekurencyjny: utrzymuj wektor h\mathbf{h} i aktualizuj go przy każdym token, ht=f(ht1,xt)\mathbf{h}_t = f(\mathbf{h}_{t-1}, \mathbf{x}_t). Stały rozmiar, zmienne wejście, dokładnie właściwy kształt.

Zawiódł na trzy sposoby, a architektura z tego rozdziału odpowiada na wszystkie trzy. Backpropagation przez TT kroków mnoży TT jakobianów, więc gradient zanika albo eksploduje — choroba, którą rozdział 5 mierzył wewnątrz pojedynczego węzła tanh\tanh. LSTM1 zaprojektowano dokładnie przeciw temu i przesunął użyteczny zakres z dziesiątek kroków do setek, nie zmieniając faktu, że informacja z token 5 dociera do token 500 tylko wtedy, gdy przetrwa 495 sekwencyjnych aktualizacji. Całe źródło musiało zmieścić się w jednym wektorze: w tłumaczeniu sequence-to-sequence2 encoder kompresuje wejście do swojego stanu końcowego. Bahdanau, Cho i Bengio nazwali to wąskie gardło i naprawili je w 2014 roku, trzy lata przed transformer, pozwalając decoderowi brać ważoną sumę wszystkich stanów encodera z wagami, które sam obliczał.3 Wszystko poniżej to ta idea zastosowana przez sekwencję do samej siebie, z usuniętą rekurencją. I aktualizacja jest sekwencyjna z konstrukcji: ht\mathbf{h}_t potrzebuje ht1\mathbf{h}_{t-1}, a GPU z dziesięcioma tysiącami rdzeni nic z tym nie zrobi. Architektura, która wygrała, nie jest oczywiście mądrzejsza; jest tą, której kosztownym krokiem jest mnożenie macierzy.

Drugi klasyczny bias indukcyjny, konwolucja — przesuwanie jednego małego filtra po całym wejściu, aby cecha wykryta gdziekolwiek była wykryta wszędzie — też nie jest tu budowany; jest niemal dokładnie właściwy dla obrazów i zostaje przekazany kursowi o widzeniu. Ani rekurencja, ani konwolucja nie wracają po tej stronie, dlatego żadna z nich nie dostaje rozdziału: rozdział 1 obiecał, że pominięcia będą deklarowane, a nie przemilczane.

Najbardziej oczywistą funkcją, która z dowolnej liczby wektorów zwraca jeden wektor, jest średnia:

ct=1ti=1txi\mathbf{c}_t = \frac{1}{t}\sum_{i=1}^{t} \mathbf{x}_i

Dowolna liczba wejść, stały rozmiar wyjścia, różniczkowalne, darmowe. Tabela embedding plus ta średnia plus warstwa liniowa do słownika to kompletny model językowy w piętnastu liniach. Jest też fatalny, a sposób, w jaki jest fatalny, stanowi całe wyprowadzenie.

Poniższy korpus to jeden megabajt Szekspira, 1 115 394 znaków, przepuszczony przez byte-level BPE tokenizer typu zbudowanego w rozdziale 7, ze słownikiem 1024: 459 760 tokens po 2,43 znaku każdy, podział 90/10. Każdy model ma szerokość 128, widzi 128 tokens i trenuje przez 3000 kroków AdamW przy 10310^{-3} z batchem 64. Perplexity podano na zbiorze walidacyjnym.4

modelparametryvalidation perplexity
tylko bieżący token, bez żadnego kontekstu263 16859.71
plus jednolita średnia wszystkiego przed nim263 168248.07
plus wyuczone embeddings pozycji279 552245.93
jednolita średnia dodana do token zamiast go zastępować263 16860.45

Przeczytaj drugi wiersz dwa razy. Uśrednianie kontekstu nie pomaga trochę; sprawia, że model jest cztery razy gorszy niż ignorowanie kontekstu całkowicie. Są dwa powody, oba możliwe do udowodnienia, a nie tylko empiryczne.

Średnia nie widzi kolejności. Dodawanie jest przemienne, więc przetasowanie okna zostawia podsumowanie bez zmian — nie w przybliżeniu:

order.pyPYTHON
A = torch.tril(torch.ones(T, T))
A = A / A.sum(1, keepdim=True)          # rows of the averaging matrix
y = x[torch.randperm(T)]                # the same tokens, shuffled
print((A[-1] @ x - A[-1] @ y).abs().max().item())
TEXT
2.9802322387695312e-08

Szum zmiennoprzecinkowy na sumie o zmienionej kolejności: oba podsumowania są tym samym wektorem. Model, którego jedynym widokiem kontekstu jest średnia, nie odróżni pies ugryzł człowieka od człowiek ugryzł psa. Trzeci wiersz dowodzi, że nie da się tego naprawić przez dodanie pozycji do wejść — wyuczony embedding pozycji na każdym token przed uśrednieniem dał 2,14 punktu ze 188. Pozycje trafiają do sumy, a suma je zapomina.

I średnia topi teraźniejszość. Na pozycji 100 bieżący token stanowi jedną setną podsumowania. To ma tanią naprawę, którą już masz: zachowaj token i dodaj do niego podsumowanie — residual connection z rozdziału 6, a czwarty wiersz pokazuje, co robi. Po naprawieniu rozcieńczenia jednolita średnia nie wnosi niczego: 60.45 wobec baseline 59.71. Każdy token tam jest, ważony równo, a równe ważenie jest tym samym co brak informacji.

Problemem nie jest uśrednianie. Problemem są wagi.

Średnia to mnożenie macierzy, a maska to softmax

Link do sekcji: Średnia to mnożenie macierzy, a maska to softmax

Uśrednianie po rosnącym prefiksie wygląda jak pętla. Jest jednym mnożeniem przez dolnotrójkątną macierz, której wiersze sumują się do jednego — i także, dokładnie, softmax:

mechanics.pyPYTHON
loop = torch.stack([x[:t + 1].mean(0) for t in range(T)])   # the obvious version

A = torch.tril(torch.ones(T, T))
A = A / A.sum(1, keepdim=True)
mat = A @ x                                                  # the same thing

S = torch.zeros(T, T).masked_fill(torch.tril(torch.ones(T, T)) == 0, float("-inf"))
soft = F.softmax(S, dim=-1) @ x                              # and the same thing again
TEXT
loop vs matmul   max |diff| = 5.960464477539063e-08
loop vs softmax  max |diff| = 5.960464477539063e-08

the averaging matrix A (rows sum to 1, upper triangle is zero):
  1.000 0.000 0.000 0.000 0.000 0.000
  0.500 0.500 0.000 0.000 0.000 0.000
  0.333 0.333 0.333 0.000 0.000 0.000
  0.250 0.250 0.250 0.250 0.000 0.000
  0.200 0.200 0.200 0.200 0.200 0.000
  0.167 0.167 0.167 0.167 0.167 0.167

Trzy nazwane komponenty transformer są teraz na ekranie. Trójkąt to causal mask, wymuszona przez cel: gdyby pozycja tt mogła widzieć pozycję t+1t{+}1, odpowiedź byłaby w wejściu — przeciek, który rozdział 6 kazał ci audytować, tylko wewnątrz architektury. Softmax to sposób implementacji maski: ustawienie zakazanych wpisów na -\infty wysyła je dokładnie do zera i normalizuje to, co zostaje, więc maskowanie i normalizacja są jedną operacją. (Użyj -\infty, nie -1e9: to wartość, którą maskowanie oznacza, przetrwa rzutowanie do float16 jako -\infty i oszczędza ci decyzji, czy wybrana stała jest dość duża dla zakresu, w którym akurat jesteś — a to pudełko zmiennoprzecinkowe z rozdziału 2 zadaje pytanie, na które nie musisz odpowiadać.) A scores są wolnym parametrem. Jednolita średnia jest tym, co dostajesz, gdy każdy dozwolony score jest tą samą liczbą; wpisz tam dowolne liczby, a softmax zamieni je w prawidłowe wagi.

Reszta tego rozdziału to jedno pytanie: skąd biorą się te liczby?

Nie mogą być zwykłymi parametrami. Wyuczona macierz T×TT \times T byłaby identyczna dla każdego zdania — mogłaby zakodować „spójrz cztery tokens wstecz”, ale nigdy „spójrz na rzeczownik, do którego odnosi się ten zaimek”. Waga łącząca pozycję tt z pozycją ii musi zależeć od tego, co jest na obu pozycjach, bo relewantność jest relacją, nie własnością: słowo to nie jest samo w sobie relewantne, jest relewantne dla czegoś.

Najtańszą funkcją dwóch wektorów zwracającą liczbę jest iloczyn skalarny z rozdziału 1. Oceń pozycję ii dla pozycji tt jako xtxi\mathbf{x}_t \cdot \mathbf{x}_i i mechanizm działa — źle, na dwa sposoby, które wymuszają całą resztę. Iloczyn skalarny wektora z samym sobą jest jego normą do kwadratu, więc każdy token attendowałby głównie do siebie. A relacja byłaby symetryczna: jeśli to attenduje mocno do zwierzę, wtedy zwierzę attenduje mocno do to, co jest fałszywe w języku, gdzie przymiotnik potrzebuje rzeczownika znacznie bardziej niż rzeczownik potrzebuje przymiotnika.

Daj więc każdemu token dwie role, jako dwie wyuczone liniowe mapy: czego ta pozycja szuka, qt=Wqxt\mathbf{q}_t = W_q\mathbf{x}_t, czyli query; oraz czym oferuje, że da się ją znaleźć, ki=Wkxi\mathbf{k}_i = W_k\mathbf{x}_i, czyli key. Score qtki\mathbf{q}_t \cdot \mathbf{k}_i i symetria znika, bo WqWkW_q \neq W_k: token może reklamować jedno, a szukać czegoś innego.

Jedna rzecz nadal jest zła. Ważona suma była po samych xi\mathbf{x}_i, co zmusza rzecz, która zostaje skopiowana, by była tą samą rzeczą, która zostaje dopasowana. Dopasowanie chce cech identyfikujących token; kopiowanie chce cech użytecznych dalej. Naucz więc trzecią mapę, vi=Wvxi\mathbf{v}_i = W_v\mathbf{x}_i, czyli value, i sumuj je.

Wzór jest teraz księgowością:

Attention(Q,K,V)=softmax ⁣(QKdk+M)V\mathrm{Attention}(Q, K, V) = \mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}} + M\right)V

z MM jako causal mask, zerem na przekątnej i pod nią oraz -\infty powyżej. W kodzie to trzydzieści linii, z czego dwadzieścia to kształty:

attention.pyPYTHON
class Head(nn.Module):
    """One head of causal self-attention."""

    def __init__(self, d_model, d_head, block):
        super().__init__()
        self.q = nn.Linear(d_model, d_head, bias=False)      
        self.k = nn.Linear(d_model, d_head, bias=False)      
        self.v = nn.Linear(d_model, d_head, bias=False)      
        self.d_head = d_head
        self.register_buffer("mask", torch.tril(torch.ones(block, block)).bool())

    def forward(self, x):
        T = x.shape[1]
        q, k, v = self.q(x), self.k(x), self.v(x)
        s = q @ k.transpose(-2, -1) / math.sqrt(self.d_head)          
        s = s.masked_fill(~self.mask[:T, :T], float("-inf"))          
        w = F.softmax(s, dim=-1)                                      
        return w @ v                                                  

Score, maskuj, normalizuj, mieszaj. Cała reszta to projekcja.

Dzielenie przez pierwiastek kwadratowy i przed czym broni

Link do sekcji: Dzielenie przez pierwiastek kwadratowy i przed czym broni

Prawie każde wyjaśnienie dk\sqrt{d_k} mówi „żeby softmax się nie nasycał”, co jest prawdą i niczego nie wyjaśnia. Argument to dwie linie wariancji z rozdziału 2. Jeśli wpisy q\mathbf{q} i k\mathbf{k} są niezależne, mają średnią zero i wariancję jeden, każdy iloczyn qjkjq_j k_j ma wariancję jeden, a wariancje rzeczy niezależnych się dodają:

Var(qk)=j=1dkVar(qjkj)=dk\mathrm{Var}(\mathbf{q}\cdot\mathbf{k}) = \sum_{j=1}^{d_k}\mathrm{Var}(q_j k_j) = d_k

Więc scores mają odchylenie standardowe dk\sqrt{d_k}. Zmierzone na dwudziestu tysiącach losowych par:

TEXT
     d     Var(q.k)         std   sqrt(d)
     4        3.975       1.994     2.000
    16       16.071       4.009     4.000
    64       64.249       8.016     8.000
   256      253.065      15.908    16.000
  1024     1015.562      31.868    32.000

Dlaczego to ma znaczenie: softmax jest wrażliwy na skalę w sposób, w jaki warstwa liniowa nie jest. Podwojenie wejścia warstwy liniowej podwaja jej wyjście; pomnożenie scores przez dziesięć przed softmax zamienia miękką mieszankę w twardy wybór. Jeden wiersz 64 scores, z dzieleniem i bez niego:

dkd_knajwiększa waga, bez dzieleniaentropiaeffective tokensnajwiększa waga, z dzieleniementropiaeffective tokens
40.2052.94419.00.0813.75842.9
160.4381.6925.40.0753.84946.9
640.4890.8742.40.0853.67339.4
2560.99990.00071.00.1433.54734.7
10241.00000.00001.00.1323.64438.3

„Effective tokens” to wykładnik entropii: po ilu pozycjach wiersz naprawdę uśrednia. Bez dzielenia, przy dk=256d_k = 256, świeżo zainicjalizowana head attenduje do dokładnie jednego token z 64, wybranego wyłącznie przez losowanie.

To złe w przód i jeszcze gorsze w tył, w kształcie, który rozdział 5 już mierzył na tanh\tanh. Softmax przywiązany do jednego wpisu ma prawie zerową pochodną: przekątna jego jakobianu to wi(1wi)w_i(1-w_i), zero na obu krańcach. Na dwóch tysiącach losowych wierszy:

dkd_kiwi(1wi)\sum_i w_i(1-w_i) bez dzieleniaz dzieleniemwiersze nasycone (największa waga powyżej 0.99)
40.84270.95680.2 % → 0.0 %
640.29400.960917.9 % → 0.0 %
2560.14060.960949.1 % → 0.0 %
10240.06810.961170.4 % → 0.0 %

Przy dk=1024d_k = 1024 siedem wierszy na dziesięć jest zamrożonych, zanim zacznie się trening, a head, która startuje zamrożona, nie może nauczyć się, na co patrzeć. Po dzieleniu ta wielkość jest płaska na 0.96 przy każdej szerokości i nic się nie nasyca.

Teraz część, której nikt nie publikuje: czy to zmienia końcowe perplexity? Usuń dzielenie i trenuj przy czterech szerokościach head:

szerokość headbez dzieleniadzielone przez dk\sqrt{d_k}dzielone przez dkd_k
cztery heads, dk=32d_k = 3237.2938.0737.89
jedna head, dk=128d_k = 12848.5146.1045.99
jedna head, dk=256d_k = 25665.3747.53
jedna head, dk=512d_k = 51267.0649.15
jedna head, dk=1024d_k = 102476.6959.17

Pierwsze dwa wiersze pochodzą z budżetu 3000 kroków powyżej; ostatnie trzy to krótszy run — 1500 kroków, batch 32, jedna head, bez normalizacji przed projekcjami — z obiema wersjami w identycznych ustawieniach.

Przy dk=32d_k = 32 dzielenie nie jest nic warte, a run bez niego jest minimalnie z przodu. To nie licencja, by je wyrzucić, bo przy 256 jest warte 18 punktów perplexity, a przy 1024 — 17. Mechanizm widać w samych scores:

dkd_kstd score przy initpo 1500 krokach, bez dzieleniapo 1500 krokach, z dzieleniemwiersze nasycone, bez dzieleniaz dzieleniem
25610.49121.672.1391.9 %0.8 %
51215.13836.852.6698.7 %1.3 %
102421.155147.463.4499.9 %16.5 %

Head bez dzielenia się nie regeneruje. Ucieka: odchylenie standardowe jej scores rośnie z 21 przy inicjalizacji do 5147, entropia attention spada do zera, a 99,9 % wierszy kładzie ponad 0,99 swojej wagi na jeden token. Gdy head staje się twardym selektorem, jej gradient jest prawie zerowy i nic jej nie cofa, więc załamanie jest stabilne. Head z dzieleniem siedzi po tym samym treningu przy odchyleniu standardowym score 3,44, czyli przy miękkiej mieszance, którą nadal można zmienić.

Vaswani i in. mówią dokładnie to i nic więcej — podejrzewają, że iloczyny „rosną co do wartości bezwzględnej dla dużych wartości dkd_k” i dzielą.5 Słowo duże niesie ciężar, a tabele mówią, gdzie duże się zaczyna: nic przy 32, wszystko do 256.

Więcej niż jedna opinia i dwie trzecie, o których nikt nie mówi

Link do sekcji: Więcej niż jedna opinia i dwie trzecie, o których nikt nie mówi

Jedna head to jeden wiersz softmax na pozycję, więc zawiera jedną odpowiedź na pytanie „co jest tu relewantne”. Przewidzenie słowa po the w the animal that crossed the wet street wymaga jednocześnie slotu składniowego, podmiotu i poprzedniego token, a jeden rozkład prawdopodobieństwa nie może być skoncentrowany w trzech miejscach. Uruchom więc kilka heads równolegle, każdą o szerokości dmodel/hd_{\text{model}}/h, skonkateno­wuj i wymieszaj jeszcze jedną macierzą WoW_o: podzieliłeś szerokość, nie dodałeś do niej.

Attention robi też dokładnie jedną rzecz — przenosi informację między pozycjami. Każda operacja w kodzie powyżej jest liniowa wzdłuż osi cech, a rozdział 5 udowodnił, czym jest stos map liniowych. Dlatego każdy blok niesie też mały MLP stosowany do każdej pozycji niezależnie, rozszerzający szerokość czterokrotnie i wracający, z GELU pośrodku. Podział pracy warto zapamiętać: attention miesza między pozycjami, feed-forward network liczy wewnątrz pozycji.

Pełna drabina, każdy wiersz dodaje jeden element do wiersza nad nim:

modelparametryvalidation perplexity
jednolita średnia, dodana279 55260.45
jedna attention head, zastępująca token328 70455.47
jedna attention head, dodana328 70446.10
cztery heads zamiast jednej345 21643.21
plus feed-forward network476 92839.87
plus LayerNorm — kompletny blok477 69638.07

Wyuczone wagi biją jednolite o 14 punktów perplexity, co jest całym argumentem tego rozdziału w jednym wierszu. Cztery heads kupują kolejne 3 za 16 512 dodatkowych parametrów. A ta sama head jest warta o 9 punktów więcej dodana niż zastępująca: attention wnosi informację, nie decyduje, czym jest pozycja.

Teraz gdzie naprawdę siedzą parametry, co zaskakuje ludzi, którzy widzieli tylko diagram:

szerokośćheadsattentionfeed-forwardłącznie na blok
128465 664 (33.2 %)131 712 (66.6 %)197 888
768122 360 064 (33.3 %)4 722 432 (66.6 %)7 085 568
40963267 112 960 (33.3 %)134 238 208 (66.7 %)201 367 552

Dwie trzecie każdego transformer block to feed-forward network, w każdej skali, bo attention ma cztery macierze d×dd \times d, a MLP ma odpowiednik ośmiu. Cokolwiek model wie, większość parametrów, które to trzymają, znajduje się w MLP per pozycja.

Residuals i LayerNorm, odziedziczone z rozdziału 6

Link do sekcji: Residuals i LayerNorm, odziedziczone z rozdziału 6

LayerNorm zbudowano i zmierzono w rozdziale 6, a ten rozdział używa go tak, jak tam został zostawiony; residual connections nazwano i ablatowano tam, a tu są wbudowane. Wiersze „dodane, nie zastępujące” powyżej to residual connections, warte 188 punktów perplexity dla średniej i 9 dla jednej head. LayerNorm7 normalizuje każdy przykład po jego cechach, a rozdział 6 podał powody, dla których to on, a nie BatchNorm, przetrwał tutaj — brak zależności od batcha, brak statystyk kroczących, identyczność w treningu i inference, obojętność na długość sekwencji — i każdy z nich staje się wymaganiem, gdy generujesz jeden token naraz dla jednego użytkownika, dokąd prowadzi rozdział 13. Kosztuje 768 parametrów i kupuje 1,8 punktu perplexity.

block.pyPYTHON
class Block(nn.Module):
    def forward(self, x):
        x = x + self.att(self.ln1(x))     
        x = x + self.ff(self.ln2(x))      
        return x

Spójrz, gdzie siedzi normalizacja: na wejściu każdej podwarstwy, z residual path od wejścia do wyjścia nigdy nienormalizowaną. To jest pre-norm. Artykuł z 2017 roku robi odwrotnie, x = LayerNorm(x + Att(x))post-norm, który umieszcza LayerNorm na samej residual path.

Xiong i in. wyjaśnili różnicę przez gradient przy inicjalizacji, który w sieci post-norm jest źle skalowany z głębokością — to powód, dla którego oryginalny transformer potrzebował warmup learning rate, by w ogóle trenować.8 Dwanaście bloków, 1000 kroków, learning rate 3×1033 \times 10^{-3}:

TEXT
gradient norm per block at initialisation, before any step
  pre-norm    block 1 0.0498 ... block 12 0.0657   ratio last/first  1.32
  post-norm   block 1 0.0977 ... block 12 0.1613   ratio last/first  1.65

  pre-norm,  no warmup          perplexity   37.82
  pre-norm,  200-step warmup    perplexity   37.62
  post-norm, no warmup          perplexity  308.05
  post-norm, 200-step warmup    perplexity   37.88

Post-norm bez warmup jest osiem razy gorszy, a post-norm z warmup dorównuje pre-norm dokładnie. Warmup nie jest tu ogólnie dobrą praktyką; jest łatą na konkretny układ normalizacji, a przesunięcie LayerNorm usuwa potrzebę tej łaty. Dlatego zasadniczo każdy model od 2019 roku jest pre-norm i dlatego diagram z 2017 roku należy czytać jako historię, nie jako specyfikację.

Usuń embeddings pozycji, a model nadal trenuje; po prostu nie potrafi powiedzieć, gdzie cokolwiek jest, i to jest symetria, a nie porażka treningu. Nic w attention score nie wspomina samych tt ani ii, więc permutacja wejścia permutuje wyjście: self-attention jest permutation-equivariant. To ślepota średniej na kolejność w lepszym przebraniu — causal mask przywraca część porządku, bo każda pozycja widzi inny prefiks, ale wewnątrz prefiksu wszystkie uporządkowania wyglądają tak samo.

Cztery sposoby wstrzyknięcia pozycji, trenowane na oknach 64-tokenowych i oceniane przy 64, 128 i 256 — poza jakąkolwiek długością, którą widziały:

pozycjeperplexity przy 64przy 128przy 256
żadnych48.7952.6357.52
wyuczone absolutne embeddings38.63108.47181.94
stałe sinusoidy42.9695.26152.25
RoPE44.1250.5284.84
ALiBi44.9543.5142.49

Wyuczone absolutne embeddings — jeden wektor na pozycję, dodany do token — wygrywają przy długości treningowej, a potem spadają z klifu, bo pozycja 100 nigdy nie była w batchu i jej embedding nadal jest losowym wektorem, od którego zaczynała. Sinusoidy, oryginalny wybór, są liczone, a nie uczone, z sinusów i cosinusów o geometrycznie rozmieszczonych częstotliwościach; artykuł z 2017 roku miał nadzieję, że to będzie ekstrapolować, a tabela mówi, że nie — funkcja jest zdefiniowana na pozycji 200, ale model nigdy nie nauczył się jej tam czytać. RoPE9 niczego nie dodaje, tylko obraca query i key o kąt proporcjonalny do pozycji, w dwuwymiarowych wycinkach; ponieważ równy obrót obu stron iloczynu skalarnego zostawia go bez zmian, score zależy ostatecznie tylko od tit - i, więc pozycja staje się relatywna za darmo i nie ma tabeli, która mogłaby się skończyć. Degraduje się, ale się degraduje. ALiBi10 to najprostszy i najdziwniejszy wynik tutaj: liniowa kara na score proporcjonalna do odległości, z innym nachyleniem na head. Jego perplexity poprawia się, gdy okno rośnie poza długość treningową, z 44.95 do 42.49, bo kara jest zdefiniowana dla dowolnej odległości i każda head dalej robi to, do czego była trenowana.

Lekcja przeżywa tabelę: architektura, która nie potrafi czegoś reprezentować, to inny problem niż taka, która nigdy nie nauczyła się tego zakresu, a gryzie ten drugi. To także mechanika za każdym ogłoszeniem „rozszerzyliśmy context do 128K” — prawie zawsze są to przeskalowania kodowania rotary i dlatego rozdział 16 mówi, że limit context przesuwa się, a nie znika.

Dropout jest dziedziczony tak samo: pojawia się na wagach attention po softmax, na wyjściu każdej podwarstwy przed dodaniem residual oraz na sumie embedding, robiąc dokładnie to, co opisał rozdział 6. W dużych runach pretraining często ustawia się go na zero, bo model, który widzi każdy token raz, nie jest w pozycji do overfit.

Dwa tensory w warstwie mają kształt n×nn \times n, gdzie nn to liczba tokens: scores i wagi po softmax. Wszystko inne — każda projekcja, cały MLP — jest liniowe względem nn.

Jedna warstwa attention, szerokość 512, 8 heads, batch jeden, float32, na GPU w laptopie. Czytaj dwie kolumny milisekund tylko jako stosunki: to wall clock na karcie laptopowej 8 GB, która dławi się z 1785 MHz do poniżej 300 MHz, gdy robi się gorąca, więc zimny run tego samego kodu wraca siedem do dziesięciu razy szybciej, a zajęty jeszcze wolniej. Kolumny megabajtów to liczniki bajtów alokatora i się nie ruszają.

TEXT
  tokens   ms total    ms x4   ms projections   attn matrix MB    peak MB    MB x4
     128      2.246        -            1.324              0.5       14.6        -
     256      2.855     1.27            2.113              2.0       19.2     1.31
     512      5.761     2.02            3.105              8.0       34.4     1.79
    1024     16.414     2.85            4.008             32.0       89.1     2.59
    2048     51.573     3.14            9.989            128.0      296.1     3.32
    4096    225.432     4.37           20.176            512.0     1100.1     3.72
    8192    832.838     3.69           40.106           2048.0     4300.1     3.91
   16384   OUT OF MEMORY                                 8192.0

fitted exponent (log-log slope, last four rows):  time ~ n^1.91   memory ~ n^1.87

Kolumny x4 to stosunek do wiersza powyżej, a podwojenie nn zbiega dokładnie do 4 zarówno dla czasu, jak i pamięci — 3.91 w ostatnim kroku wobec teoretycznych 4. Kolumna projekcji jest kontrolą: 4.0 ms przy 1024 tokens do 40.1 ms przy 8192, czynnik dziesięć dla czynnika osiem. Liniowo, zgodnie z zapowiedzią.

Potem ostatni wiersz. Jedna warstwa attention, jedna sekwencja, bez modelu wokół niej, kończy pamięć na GPU 8 GB przy 16 384 tokens — sama macierz score miałaby 8 GB, bo to 8 heads razy 16 384 razy 16 384 razy 4 bajty. Nie model; jeden tensor pośredni w jednej warstwie.

To fizyczny fakt pod trzema późniejszymi rozdziałami. Dlatego context window w ogóle ma limit, który rozdział 16 zamienia w cenę. Dlatego istnieje FlashAttention, liczące ten sam wynik w kaflach bez kiedykolwiek przechowywania macierzy — optymalizacja pamięci, zanim jest optymalizacją szybkości.11 I to arytmetyka za ceną długiego prompt, którą rozdział 24 płaci w pętli agent — osobna sprawa od innego odkrycia tamtego rozdziału, że model także gorzej używa długiego context, co mierzy i odmawia zrzucenia winy na ten wzór.

Pokaż szczegóły

Dwa warianty zmniejszające cache, nazwane tutaj i opłacone w rozdziale 13.

Generowanie cache'uje keys i values tokens już przetworzonych — jeden key i jedna value per token, per head per layer. Multi-query attention12 zachowuje hh projekcji query, ale pojedynczą projekcję key i value współdzieloną przez wszystkie heads, dzieląc ten cache przez hh. Grouped-query attention13 interpoluje: heads są grupowane, każda grupa współdzieli jeden key i value, więc g=hg = h to zwykłe attention, a g=1g = 1 to multi-query. Prawie każdy otwarty model od 2023 roku używa tego z 4 lub 8 grupami. Żadne z nich nie istnieje dla jakości; oba istnieją dla rozmiaru tego cache, a rozdział 13 robi arytmetykę, która zamienia to w „który model mieści się w twoim GPU”.

Artykuł z 2017 roku opisuje encoder-decoder: jeden stos czyta źródło z niemaskowanym attention, drugi generuje cel causalnie, a trzeci rodzaj attention pośrodku to miejsce, gdzie queries decodera spotykają keys encodera. To właściwe dla tłumaczenia, gdzie wejście i wyjście są dwiema sekwencjami.

Wygrała połowa decoder-only — jeden stos, causal wszędzie, wejście i wyjście w tej samej sekwencji — a powodem nie jest elegancja. „Przewiduj następny token” działa na dowolnym tekście, więc zbiorem treningowym jest internet zamiast korpusu równoległego, a wszystko staje się tym jednym zadaniem: tłumaczenie to dokument zawierający źródło, potem cel; pytanie i odpowiedź są dokumentem; rozmowa z tool call pośrodku jest dokumentem. Rozdział 11 jest o tym, jak wytwarza się to ostatnie. Encodery nie zniknęły — jeden widzi całe wejście naraz, czego chcesz, gdy zadaniem jest reprezentować tekst, a nie go kontynuować, i dlatego retrieval embeddings z rozdziału 19 pochodzą z encoderów, a nie z modelu, który czatuje.

Gdy blok jest zdefiniowany, rozmiar modelu to arytmetyka. Na blok, przy szerokości dd i czterokrotnym rozszerzeniu: 4d2+4d4d^2 + 4d dla Wq,Wk,Wv,WoW_q, W_k, W_v, W_o z biasami na wszystkich czterech, jak ma GPT-2 — tabela powyżej zostawia bias poza trzema z nich, stąd 2304 mniej na blok przy d=768d = 768; 8d2+5d8d^2 + 5d dla MLP; 4d4d dla dwóch LayerNorm — 12d2+13d12d^2 + 13d, plus tabela token V×dV \times d i, dla pozycji absolutnych, nctx×dn_{\text{ctx}} \times d. Dla kształtu GPT-2 small — d=768d = 768, 12 bloków, słownik 50 257, context 1024, warstwa wyjściowa współdzieląca wagi embedding:

TEXT
  token embeddings     50,257 x 768 = 38,597,376
  position embeddings   1,024 x 768 =    786,432
  one block                             7,087,872
  12 blocks                            85,054,464
  final LayerNorm         2 x 768 =        1,536
  total (weights tied)                124,439,808

Co jest opublikowanym rozmiarem tego modelu. Wzór nie jest przybliżeniem; jest modelem. Zauważ też, że prawie jedna trzecia małego modelu to tabela embedding, dlatego rozmiar słownika jest decyzją architektoniczną, a nie preprocessingiem — trade-off ustawiony w rozdziale 7.

Perplexity to liczba o korpusie. To, co robi jedna head, jest innym pytaniem, a model trenowany na megabajcie Szekspira jest złym instrumentem do jego zadawania: uczciwie o attention map modelu z 500 000 parametrów można powiedzieć, że w większości nie jest interpretowalna. Więc: język, w którym pytanie ma poprawną odpowiedź.

Klasyczna ilustracja to the animal did not cross the street because it was too tired, gdzie it to animal, w kontraście do …because it was too wet, gdzie jedno słowo przenosi referenta na street. To są schematy Winograda14 — pary zdań identyczne poza jednym słowem, gdzie to słowo decyduje, do czego odnosi się zaimek.

Są też rozwiązywalne oszustwem, i tę część tutoriale pomijają. Jeśli dwaj kandydaci to zwierzę i miejsce, tired i wet identyfikują referenta przez kategorię, a model, który wie tylko, jakie słowa są obecne, odpowiada poprawnie, nie wiedząc nic o kolejności. Zmierzone na tej wersji zadania, z odłożonymi parami zwierzę/miejsce:

TEXT
uniform causal average           held-out referent accuracy 100.0 %
one transformer block            held-out referent accuracy  91.7 %

Bag of words bije transformer. Każda demonstracja zbudowana na tym zdaniu niczego nie dowodzi o attention.

Zamknij więc dziurę: losuj obu kandydatów z jednej puli szesnastu rzeczowników, z których każdy może pojawić się w dowolnym slocie, i podziel przymiotniki według roli zamiast kategorii — cztery czyniące it tym, który przekracza (tired, scared, slow, weak), cztery czyniące go tym, co przekraczane (wet, wide, busy, steep).

TEXT
the {x} did not cross the {y} because it was too {adj} , so the {ref} waited .

Trenuj jako zwykły next-token predictor, oceniaj jedną pozycję — słowo po so the — i zbuduj zbiór odłożony z par rzeczowników, których odwrócona kolejność była w treningu, więc wszystko, co wie, które dwa rzeczowniki są obecne, ale nie wie, który był pierwszy, musi odpowiedzieć odwrotnie.

modelparametryheld-outnazywa drugi rzeczownik
tylko bieżący token57965.2 %5.2 %
jednolita causal average579627.9 %50.0 %
jedna head wyuczonego attention18 08435.4 %64.6 %
cztery heads22 24475.0 %15.6 %
jeden transformer block55 71692.7 %4.2 %
dwa transformer blocks105 508100.0 %0.0 %

Szansa spośród dwóch obecnych rzeczowników to 50 %. Jednolita średnia ląduje na 27.9 % i odpowiada złym rzeczownikiem z pary dokładnie połowę czasu — sygnatura czegoś, co wie, jakie słowa tam są, i nic o ich kolejności, zgodnie z testem tasowania sprzed trzech sekcji.

Teraz mapa: attention na pozycji, która musi nazwać referenta, uśrednione po czterech heads każdego bloku, dla dwóch zdań różniących się jednym słowem. Jednolita średnia położyłaby 0.067 na każdym z piętnastu widocznych tokens.

TEXT
the animal did not cross the street because it was too tired , so the animal waited .
  blk 1  the:0.00 animal:0.70 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.06
         because:0.00 it:0.00 was:0.00 too:0.00 tired:0.00 ,:0.05 so:0.00 the:0.19
  blk 2  the:0.00 animal:0.00 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.00
         because:0.00 it:0.00 was:0.00 too:0.00 tired:1.00 ,:0.00 so:0.00 the:0.00

the animal did not cross the street because it was too wet , so the street waited .
  blk 1  the:0.00 animal:0.70 did:0.00 not:0.00 cross:0.00 the:0.00 street:0.06
         because:0.00 it:0.00 was:0.00 too:0.00   wet:0.00 ,:0.05 so:0.00 the:0.19
  blk 2  the:0.00 animal:0.00 did:0.00 not:0.00 cross:0.03 the:0.00 street:0.49
         because:0.00 it:0.00 was:0.00 too:0.20   wet:0.03 ,:0.00 so:0.00 the:0.25

Blok 1 jest identyczny w obu zdaniach — 0.70 na pierwszym rzeczowniku, niezależnie od przymiotnika. To nie porażka, tylko dowód: w pierwszej warstwie query na pozycji jest funkcją własnego token i indeksu tej pozycji, a the na pozycji 14 jest tym samym token w obu zdaniach. Head pierwszej warstwy nie może warunkować się słowem, którego jeszcze nie pobrała. Dlatego blok 1 robi jedyną użyteczną rzecz dostępną dla siebie i przeciąga pierwszy rzeczownik do przodu.

Blok 2 jest miejscem, gdzie zdania się rozchodzą, a ten sam wiersz przez wszystkie osiem przymiotników pokazuje regułę, którą znalazł model:

przymiotnikblok 2 na animalna streetna przymiotnikuodpowiedź
tired, scared, slow, weak0.0000.0001.000animal
wet, wide, busy, steep0.0000.4910.00–0.03street

Dla przymiotnika crosser druga warstwa kładzie całą wagę na przymiotnik, bo odpowiedź już jest w residual stream — blok 1 ją tam włożył — i potrzebuje tylko potwierdzenia. Dla przymiotnika crossed idzie i pobiera drugi rzeczownik. To obwód dwuskokowy: jedna head przesuwa kandydata do przodu, head w późniejszej warstwie czyta token, który decyduje, czy go zachować. Kompozycja przez warstwy jest mechanizmem i dlatego jeden blok osiągnął 92.7 %, a dwa — 100 %.

To także kształt najlepiej udokumentowanego obwodu w prawdziwych modelach. Induction heads — previous-token head karmiąca head w następnej warstwie, która domyka wzorzec [A][B] … [A] → [B] — są tym, co prace Anthropic o interpretowalności identyfikują za dużą częścią in-context learning, i tworzą się w rozpoznawalnym momencie podczas pretraining. Ten rozdział nie próbuje tej analizy: jest oddelegowana, z obiema pracami w bibliografii, bo czytanie obwodów z prawdziwego modelu to dziedzina badań, nie sekcja.

Na koniec implementacja. Trzydzieści linii powyżej, z wagami skopiowanymi z własnych wag PyTorch:

TEXT
ours vs nn.MultiheadAttention           max |diff| = 1.7881393432617188e-07
ours vs F.scaled_dot_product_attention  max |diff| = 1.7881393432617188e-07

1.8×1071.8 \times 10^{-7} na wyjściach, których średnia wielkość to 0.159: ta sama arytmetyka w innej kolejności, przy precyzji float32.

Masz architekturę, z której zbudowany jest każdy model w reszcie tego kursu, i jest mniejsza niż jej reputacja: ważona średnia, której wagi są uczone, MLP per pozycja trzymający dwie trzecie parametrów, dwie normalizacje i dwa dodania, ułożone w stos.

Nie masz modelu, który cokolwiek wie, a samo dokładanie stosu tego nie naprawi. Dwa bloki na tym korpusie osiągają training perplexity 14.49 i validation perplexity 40.57, wobec 18.77 i 38.07 dla jednego bloku — więcej pojemności, lepiej na tym, co widział, gorzej na tym, czego nie widział, czyli tabela z rozdziału 6 z transformer w środku. Odległość między tym modelem a tymi, z którymi rozmawiają rozdziały 14–30, nie jest architektoniczna. To ten sam blok, powtórzony więcej razy, nad ogromnie większą ilością tekstu.

To czyni sprawę problemem księgowości, a ta księgowość jest dziwniejsza, niż wygląda. Ile tekstu i skąd ktokolwiek go bierze? Ile arytmetyki i jak oszacować ją zanim pieniądze zostaną wydane? Przy stałym budżecie lepiej powiększyć model czy pokazać mu więcej danych — i czy istnieje poprawna odpowiedź, czy tylko moda? Rozdział 10 odpowiada na wszystkie trzy pytania pomiarami i wycenia najtańszą użyteczną formę pytania: ile dziś kosztuje wytrenowanie modelu takiego jak GPT-2 od zera?


Trzy wyjaśnienia tego materiału są lepsze od tego w tym, do czego służą, a ten rozdział napisano do czytania obok nich. The Illustrated Transformer Jaya Alammara to najlepszy rysunek przepływu danych, jaki kiedykolwiek powstał. The Annotated Transformer Harvard NLP to artykuł z 2017 roku z działającym kodem przeplatanym linia po linii. Let's build GPT: from scratch, in code, spelled out Andreja Karpathy'ego buduje ten sam model na żywo w dwie godziny, a drabina ablations powyżej jest tym samym kręgosłupem zmierzonym na innym korpusie. Dla pytania o interpretowalność, którego ten rozdział tylko dotyka, źródłami podstawowymi są Elhage i in., A Mathematical Framework for Transformer Circuits (2021) oraz Olsson i in., In-context Learning and Induction Heads (2022), oba z grupy interpretowalności Anthropic.

  1. Hochreiter, S. and Schmidhuber, J. Long Short-Term Memory. Neural Computation 9(8), pp. 1735–1780 (1997).

  2. Sutskever, I., Vinyals, O. and Le, Q. V. Sequence to Sequence Learning with Neural Networks. arXiv:1409.3215 (2014). Encoder-decoder, którego pojedynczy wektor kontekstu jest wąskim gardłem.

  3. Bahdanau, D., Cho, K. and Bengio, Y. Neural Machine Translation by Jointly Learning to Align and Translate. arXiv:1409.0473 (2014). Attention, trzy lata przed transformer.

  4. Perplexity to wykładnik średniej cross-entropy per token, z rozdziału 8. Każda liczba tutaj używa tego samego tokenizera i tego samego podziału walidacyjnego, co jest jedynym warunkiem, pod którym dwie wartości perplexity w ogóle można porównywać.

  5. Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł. and Polosukhin, I. Attention Is All You Need. arXiv:1706.03762 (2017). Sekcja 3.2.1 to jedno zdanie o dk\sqrt{d_k}, którego pomiarom ten rozdział poświęca sekcję.

  6. Shazeer, N., Mirhoseini, A., Maziarz, K., Davis, A., Le, Q., Hinton, G. and Dean, J. Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer. arXiv:1701.06538 (2017).

  7. Ba, J. L., Kiros, J. R. and Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). Wprowadzone i zmierzone w rozdziale 6; użyte tutaj bez zmian.

  8. Xiong, R., Yang, Y., He, D., Zheng, K., Zheng, S., Xing, C., Zhang, H., Lan, Y., Wang, L. and Liu, T.-Y. On Layer Normalization in the Transformer Architecture. arXiv:2002.04745 (2020). Analiza gradientu stojąca za pre-norm i argument, że warmup jest symptomem.

  9. Su, J., Lu, Y., Pan, S., Murtadha, A., Wen, B. and Liu, Y. RoFormer: Enhanced Transformer with Rotary Position Embedding. arXiv:2104.09864 (2021).

  10. Press, O., Smith, N. A. and Lewis, M. Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation. arXiv:2108.12409 (2021). Wynik ekstrapolacji odtworzony powyżej.

  11. Dao, T., Fu, D. Y., Ermon, S., Rudra, A. and Ré, C. FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness. arXiv:2205.14135 (2022).

  12. Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019).

  13. Ainslie, J., Lee-Thorp, J., de Jong, M., Zemlyanskiy, Y., Lebrón, F. and Sanghai, S. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023).

  14. Levesque, H. J., Davis, E. and Morgenstern, L. The Winograd Schema Challenge. KR (2012). Konstrukcja stojąca za zdaniem animal / street, którego używa każdy tutorial o attention.

Gotowy, żeby to LIA wybierała za Ciebie?

Twórz ze wszystkimi modelami AI w jednym miejscu — zacznij dziś za darmo.