Sari la conținut
9/30Capitolul 9 din 30

Attention și blocul transformer, derivate dintr-o medie

Pornește de la cea mai ieftină sinteză a contextului — media — vezi unde eșuează și lasă formula attention să apară din reparație.

Pe această pagină

Ajungi aici cu un tokenizer din Capitolul 7, un tabel de embedding din Capitolul 8 și obiectivul care vine cu ele: date fiind tokens de până acum, pune o probabilitate pe următorul.

Ce lipsește este mijlocul. Pentru a prezice token tt, modelul are nevoie de un vector care rezumă tot ce vine înainte, iar nimic din ce ai construit nu produce unul. embedding-ul lui token t1t-1 nu este acesta — acela este un model bigramă și nu poate ști că propoziția a început cu o întrebare. Nici concatenarea tuturor embedding-urilor anterioare nu este: numărul lor se schimbă la fiecare pas, iar o matrice de ponderi fixă nu poate primi o intrare de lungime variabilă.

Așadar: un vector de dimensiune fixă, care rezumă un număr variabil de vectori. Aceasta este toată problema, iar attention este ce obții rezolvând-o în cel mai leneș mod posibil și apoi reparând cele două lucruri care se strică.

Răspunsul pe care îl avea domeniul și de ce nu îl construim

Link către secțiunea: Răspunsul pe care îl avea domeniul și de ce nu îl construim

Din 1997 până aproximativ în 2017, rezumatul era o stare recurentă: păstrează un vector h\mathbf{h} și actualizează-l la fiecare token, ht=f(ht1,xt)\mathbf{h}_t = f(\mathbf{h}_{t-1}, \mathbf{x}_t). Dimensiune fixă, intrare variabilă, exact forma potrivită.

A eșuat în trei feluri, iar arhitectura acestui capitol răspunde la toate trei. Backpropagating prin TT pași înmulțește TT Jacobians, deci gradientul dispare sau explodează — boala pe care Capitolul 5 a măsurat-o într-un singur nod tanh\tanh. LSTM1 a fost proiectat exact împotriva acestui lucru și a împins intervalul utilizabil de la zeci de pași la sute, fără să schimbe faptul că informația de la token 5 ajunge la token 500 doar supraviețuind la 495 de actualizări secvențiale. Întreaga sursă trebuia să încapă într-un singur vector: în traducerea sequence-to-sequence2, un encoder comprimă intrarea în starea sa finală. Bahdanau, Cho și Bengio au numit acel blocaj și l-au reparat în 2014, cu trei ani înainte de transformer, lăsând decoder-ul să ia o sumă ponderată din toate stările encoder-ului, cu ponderi pe care le calcula singur.3 Tot ce urmează este acea idee, aplicată de o secvență asupra ei însăși, cu recurența ștearsă. Iar actualizarea este secvențială prin construcție: ht\mathbf{h}_t are nevoie de ht1\mathbf{h}_{t-1}, iar un GPU cu zece mii de nuclee nu poate face nimic cu asta. Arhitectura care a câștigat nu este evident mai inteligentă; este cea al cărei pas scump este o înmulțire de matrici.

Celălalt bias inductiv clasic, convoluția — glisezi un filtru mic peste toată intrarea, astfel încât o trăsătură detectată oriunde să fie detectată peste tot — nu este construit nici aici; este aproape exact potrivit pentru imagini și este delegat unui curs de viziune. Nici recurența, nici convoluția nu mai reapar după această pagină, motiv pentru care niciuna nu primește un capitol: Capitolul 1 a promis că omisiunile vor fi declarate, nu ascunse.

Cea mai evidentă funcție care primește un număr variabil de vectori și returnează un singur vector este media:

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

Oricâte intrări, dimensiune de ieșire fixă, diferențiabilă, gratuită. Tabelul de embedding plus această medie plus un strat liniar către vocabular formează un model lingvistic complet în cincisprezece linii. Este și groaznic, iar felul în care este groaznic reprezintă întreaga derivare.

Corpusul de mai jos este un megabyte de Shakespeare, 1.115.394 de caractere, printr-un byte-level BPE tokenizer de tipul celui construit în Capitolul 7, cu un vocabular de 1024: 459.760 tokens la 2,43 caractere fiecare, împărțit 90/10. Fiecare model are lățimea 128, vede 128 tokens și se antrenează 3000 de pași de AdamW la 10310^{-3}, cu un batch de 64. Perplexitatea este pe split-ul reținut.4

modelparametriperplexitate de validare
doar token-ul curent, fără context deloc263.16859,71
plus media uniformă a tot ce vine înainte263.168248,07
plus learned position embeddings279.552245,93
media uniformă adăugată la token în loc să îl înlocuiască263.16860,45

Citește al doilea rând de două ori. Medierea contextului nu ajută puțin; face modelul de patru ori mai prost decât ignorarea completă a contextului. Două motive, ambele demonstrabile, nu doar empirice.

Media nu poate vedea ordinea. Adunarea este comutativă, deci amestecarea ferestrei lasă rezumatul neschimbat — nu aproximativ:

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

Zgomot de floating-point pe o sumă reordonată: cele două rezumate sunt același vector. Un model a cărui singură vedere asupra contextului este o medie nu poate distinge câinele l-a mușcat pe om de omul l-a mușcat pe câine. Rândul trei dovedește că acest lucru nu se repară adăugând poziții la intrări — un learned position embedding pe fiecare token înainte de mediere a cumpărat 2,14 puncte din 188. Pozițiile intră în sumă, iar suma le uită.

Și media îneacă prezentul. La poziția 100, token-ul curent este o sutime din rezumat. Asta are o reparație ieftină pe care o deții deja: păstrează token-ul și adaugă rezumatul la el — o conexiune reziduală, din Capitolul 6, iar rândul patru arată ce face. Cu diluarea reparată, media uniformă nu contribuie cu nimic: 60,45 față de un baseline de 59,71. Fiecare token este acolo, ponderat egal, iar ponderarea egală este același lucru cu lipsa informației.

Problema nu este medierea. Sunt ponderile.

Media este o înmulțire de matrici, iar masca este un softmax

Link către secțiunea: Media este o înmulțire de matrici, iar masca este un softmax

Medierea peste un prefix în creștere arată ca o buclă. Este o singură înmulțire cu o matrice triunghiulară inferior ale cărei rânduri însumează unu — și, totodată, exact un 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

Trei componente numite ale unui transformer sunt acum pe ecran. Triunghiul este masca cauzală, impusă de obiectiv: dacă poziția tt ar putea vedea poziția t+1t{+}1, răspunsul ar fi în intrare — scurgerea pe care Capitolul 6 ți-a spus să o auditezi, doar că în interiorul arhitecturii. softmax este felul în care masca este implementată: setarea intrărilor interzise la -\infty le trimite exact la zero și normalizează ce rămâne, deci mascarea și normalizarea sunt o singură operație. (Folosește -\infty, nu -1e9: este valoarea pe care mascarea o înseamnă, supraviețuiește unui cast la float16 ca -\infty și te scutește să decizi dacă constanta aleasă este suficient de mare pentru intervalul în care se întâmplă să fii — ceea ce este cutia de floating-point din Capitolul 2 punând o întrebare la care nu trebuie să răspunzi.) Iar scorurile sunt parametrul liber. Media uniformă este ce obții când fiecare scor permis este același număr; pune orice numere acolo și softmax le transformă în ponderi valide.

Restul acestui capitol este o singură întrebare: de unde vin acele numere?

Nu pot fi parametri simpli. O matrice T×TT \times T învățată ar fi identică pentru fiecare propoziție — ar putea codifica „uită-te patru tokens în urmă”, dar niciodată „uită-te la substantivul la care se referă acest pronume”. Ponderea care leagă poziția tt de poziția ii trebuie să depindă de ce se află în ambele poziții, fiindcă relevanța este o relație, nu o proprietate: cuvântul it nu este relevant intrinsec, este relevant pentru ceva.

Cea mai ieftină funcție care primește doi vectori și returnează un număr este produsul scalar din Capitolul 1. Marchează poziția ii pentru poziția tt ca xtxi\mathbf{x}_t \cdot \mathbf{x}_i și mecanismul funcționează — prost, în două moduri care forțează tot restul. Produsul scalar al unui vector cu el însuși este norma lui pătrată, deci fiecare token ar acorda attention în principal lui însuși. Iar relația ar fi simetrică: dacă it acordă attention puternic lui animal, atunci animal acordă attention puternic lui it, ceea ce este fals despre limbaj, unde un adjectiv are nevoie de substantivul său mult mai mult decât substantivul are nevoie de adjectiv.

Așa că dă-i fiecărui token două roluri, ca două hărți liniare învățate ale lui: ce caută această poziție, qt=Wqxt\mathbf{q}_t = W_q\mathbf{x}_t, query; și ce oferă ca să fie găsită, ki=Wkxi\mathbf{k}_i = W_k\mathbf{x}_i, key. Scorul qtki\mathbf{q}_t \cdot \mathbf{k}_i și simetria dispare, fiindcă WqWkW_q \neq W_k: un token poate anunța un lucru și căuta altul.

Un lucru este încă greșit. Suma ponderată era peste xi\mathbf{x}_i înșiși, ceea ce forțează lucrul care este copiat să fie lucrul care este potrivit. Potrivirea vrea trăsăturile care identifică un token; copierea vrea trăsăturile care sunt utile mai departe. Așa că învață o a treia hartă, vi=Wvxi\mathbf{v}_i = W_v\mathbf{x}_i, value, și însumează acelea.

Formula este acum contabilitate:

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

cu MM masca cauzală, zero pe diagonală și sub ea și -\infty deasupra. În cod sunt treizeci de linii, dintre care douăzeci sunt forme:

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                                                  

Scor, mască, normalizare, amestec. Tot restul este o proiecție.

Împărțirea la rădăcina pătrată și împotriva a ce apără

Link către secțiunea: Împărțirea la rădăcina pătrată și împotriva a ce apără

Aproape fiecare explicație a lui dk\sqrt{d_k} spune „ca să nu se satureze softmax”, ceea ce este adevărat și nu explică nimic. Argumentul are două linii de varianță din Capitolul 2. Dacă intrările lui q\mathbf{q} și k\mathbf{k} sunt independente, cu medie zero și varianță unu, fiecare produs qjkjq_j k_j are varianță unu, iar varianțele lucrurilor independente se adună:

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

Deci scorurile au deviație standard dk\sqrt{d_k}. Măsurat peste douăzeci de mii de perechi aleatoare:

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

De ce contează: softmax este sensibil la scară într-un fel în care un strat liniar nu este. Dublarea intrării unui strat liniar îi dublează ieșirea; înmulțirea scorurilor cu zece înainte de un softmax transformă un amestec moale într-o alegere dură. Un rând de 64 de scoruri, cu și fără împărțire:

dkd_kcea mai mare pondere, neîmpărțităentropietokens efectivicea mai mare pondere, împărțităentropietokens efectivi
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

„tokens efectivi” este exponențiala entropiei: peste câte poziții mediază rândul cu adevărat. Neîmpărțit, la dk=256d_k = 256, un head proaspăt inițializat acordă attention exact unui singur token din 64, ales doar de extragerea aleatoare.

Asta este rău în forward și mai rău în backward, într-o formă pe care Capitolul 5 a măsurat-o deja pe un tanh\tanh. Un softmax angajat pe o singură intrare are aproape nicio derivată: diagonala Jacobian-ului său este wi(1wi)w_i(1-w_i), zero la ambele capete. Peste două mii de rânduri aleatoare:

dkd_kiwi(1wi)\sum_i w_i(1-w_i) neîmpărțitîmpărțitrânduri saturate (cea mai mare pondere peste 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 %

La dk=1024d_k = 1024, șapte rânduri din zece sunt înghețate înainte să înceapă antrenarea, iar un head care începe înghețat nu poate învăța la ce să se uite. Împărțită, cantitatea este plată la 0,96 la fiecare lățime și nimic nu se saturează.

Acum partea pe care nu o publică nimeni: schimbă perplexitatea finală? Șterge împărțirea și antrenează, la patru lățimi de head:

lățime headneîmpărțitîmpărțit la dk\sqrt{d_k}împărțit la dkd_k
patru heads, dk=32d_k = 3237,2938,0737,89
un head, dk=128d_k = 12848,5146,1045,99
un head, dk=256d_k = 25665,3747,53
un head, dk=512d_k = 51267,0649,15
un head, dk=1024d_k = 102476,6959,17

Primele două rânduri vin din bugetul de 3000 de pași de mai sus; ultimele trei sunt o rulare mai scurtă — 1500 de pași, batch de 32, un head, fără normalizare înainte de proiecții — cu ambele variante în setări identice.

La dk=32d_k = 32, împărțirea nu valorează nimic, iar rularea fără ea este foarte puțin înainte. Asta nu este o licență să renunți la ea, fiindcă la 256 valorează 18 puncte de perplexitate, iar la 1024 valorează 17. Mecanismul este vizibil chiar în scoruri:

dkd_kstd scor la initdupă 1500 de pași, neîmpărțitdupă 1500 de pași, împărțitrânduri saturate, neîmpărțitîmpărțit
25610,49121,672,1391,9 %0,8 %
51215,13836,852,6698,7 %1,3 %
102421,155147,463,4499,9 %16,5 %

Head-ul neîmpărțit nu își revine. O ia razna: deviația standard a scorurilor sale trece de la 21 la inițializare la 5147, entropia attention cade la zero și 99,9 % dintre rânduri pun mai mult de 0,99 din pondere pe un singur token. Odată ce un head este un selector dur, gradientul lui este aproape zero și nimic nu îl trage înapoi, deci colapsul este stabil. Head-ul împărțit stă la o deviație standard a scorului de 3,44 după aceeași antrenare, ceea ce este un amestec moale care încă poate fi schimbat.

Vaswani et al. spun exact asta și nimic mai mult — suspectează că produsele „cresc mult în magnitudine pentru valori mari ale lui dkd_k” și împart.5 Cuvântul mult poartă greutatea, iar tabelele spun unde începe mult: nimic la 32, totul până la 256.

Mai mult de o opinie și cele două treimi despre care nu vorbește nimeni

Link către secțiunea: Mai mult de o opinie și cele două treimi despre care nu vorbește nimeni

Un head este un rând softmax per poziție, deci ține un singur răspuns la „ce este relevant aici”. Prezicerea cuvântului după the în the animal that crossed the wet street are nevoie de slotul sintactic, subiect și token-ul anterior în același timp, iar o singură distribuție de probabilitate nu poate fi concentrată în trei locuri. Așa că rulează mai multe heads în paralel, fiecare cu lățimea dmodel/hd_{\text{model}}/h, concatenează și amestecă cu încă o matrice WoW_o: ai partiționat lățimea, nu ai adăugat la ea.

Attention face, de asemenea, exact un singur lucru — mută informație între poziții. Fiecare operație din codul de mai sus este liniară de-a lungul axei de trăsături, iar Capitolul 5 a demonstrat ce este o stivă de hărți liniare. Așa că fiecare bloc poartă și un mic MLP aplicat fiecărei poziții independent, extinzând lățimea de patru ori și revenind, cu un GELU la mijloc. Diviziunea muncii merită memorată: attention amestecă peste poziții, rețeaua feed-forward calculează în interiorul unei poziții.

Scara completă, fiecare rând adăugând o piesă la rândul de deasupra lui:

modelparametriperplexitate de validare
medie uniformă, adăugată279.55260,45
un attention head, înlocuind token-ul328.70455,47
un attention head, adăugat328.70446,10
patru heads în loc de unul345.21643,21
plus rețeaua feed-forward476.92839,87
plus LayerNorm — blocul complet477.69638,07

Ponderile învățate bat ponderile uniforme cu 14 puncte de perplexitate, ceea ce este întregul argument al acestui capitol într-un singur rând. Patru heads mai cumpără încă 3 pentru 16.512 parametri în plus. Iar același head valorează cu 9 puncte mai mult adăugat decât înlocuind: attention aduce informație înăuntru, nu decide ce este o poziție.

Acum unde stau de fapt parametrii, lucru care îi surprinde pe cei care au văzut doar diagrama:

lățimeheadsattentionfeed-forwardtotal per bloc
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

Două treimi din fiecare bloc transformer sunt rețeaua feed-forward, la orice scară, fiindcă attention are patru matrici d×dd \times d, iar MLP are echivalentul a opt. Orice ar ști un model, majoritatea parametrilor care țin acel lucru sunt în MLP-ul per poziție.

Reziduale și LayerNorm, moștenite din Capitolul 6

Link către secțiunea: Reziduale și LayerNorm, moștenite din Capitolul 6

LayerNorm a fost construit și măsurat în Capitolul 6, iar acest capitol îl folosește așa cum a fost lăsat acolo; conexiunile reziduale au fost numite și ablate acolo și sunt construite aici. Rândurile „adăugat, nu înlocuind” de mai sus sunt conexiuni reziduale, valorând 188 de puncte de perplexitate pentru medie și 9 pentru un head. LayerNorm7 normalizează fiecare exemplu peste trăsăturile sale, iar Capitolul 6 a dat motivele pentru care el, și nu BatchNorm, a supraviețuit aici — nicio dependență de batch, nicio statistică rulantă, identic în antrenare și inferență, indiferent la lungimea secvenței — fiecare dintre acestea devine o cerință când generezi câte un token odată pentru un utilizator, acolo unde ajunge Capitolul 13. Costă 768 de parametri și cumpără 1,8 puncte de perplexitate.

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

Uită-te unde stă normalizarea: pe intrarea fiecărui sub-strat, cu calea reziduală de la intrare la ieșire niciodată normalizată. Asta este pre-norm. Lucrarea din 2017 face opusul, x = LayerNorm(x + Att(x))post-norm, care pune un LayerNorm pe însăși calea reziduală.

Xiong et al. au explicat diferența prin gradientul la inițializare, care într-o rețea post-norm este prost scalat cu adâncimea — motivul pentru care transformer-ul original a avut nevoie de warmup al ratei de învățare ca să se antreneze.8 Douăsprezece blocuri, 1000 de pași, rată de învățare 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 fără warmup este de opt ori mai prost, iar post-norm cu warmup se potrivește exact cu pre-norm. Warmup nu este aici o bună practică generală; este un patch pentru o aranjare specifică a normalizării, iar mutarea LayerNorm elimină nevoia lui. De aceea, practic fiecare model de după 2019 este pre-norm și de aceea diagrama din 2017 trebuie citită ca istorie, nu ca specificație.

Șterge position embeddings și modelul tot se antrenează; pur și simplu nu poate spune unde este ceva, iar asta este o simetrie, nu un eșec de antrenare. Nimic din scorul attention nu menționează tt sau ii în sine, deci permutarea intrării permută ieșirea: self-attention este echivariant la permutare. Este orbirea la ordine a mediei într-o deghizare mai bună — masca cauzală restaurează o parte din ordine, fiindcă fiecare poziție vede un prefix diferit, dar într-un prefix toate ordonările sunt la fel.

Patru moduri de a injecta poziția, antrenate pe ferestre de 64 tokens și evaluate la 64, 128 și 256 — dincolo de orice lungime văzuseră:

pozițiiperplexitate la 64la 128la 256
deloc48,7952,6357,52
learned absolute embeddings38,63108,47181,94
sinusoide fixe42,9695,26152,25
RoPE44,1250,5284,84
ALiBi44,9543,5142,49

Learned absolute embeddings — un vector per poziție, adăugat la token — câștigă la lungimea antrenată și apoi cad de pe stâncă, fiindcă poziția 100 nu a fost niciodată într-un batch, iar embedding-ul ei este încă vectorul aleator cu care a început. Sinusoidele, alegerea originală, sunt calculate, nu învățate, din sinusuri și cosinusuri la frecvențe spațiate geometric; lucrarea din 2017 spera că asta va extrapola, iar tabelul spune că nu — funcția este definită la poziția 200, dar modelul nu a învățat niciodată să o citească acolo. RoPE9 nu adaugă nimic și în schimb rotește query și key cu un unghi proporțional cu poziția, în felii bidimensionale; deoarece rotirea ambelor părți ale unui produs scalar în mod egal îl lasă neschimbat, scorul ajunge să depindă doar de tit - i, deci poziția devine relativă pe gratis și nu există un tabel care să se epuizeze. Se degradează, dar se degradează. ALiBi10 este cel mai simplu și cel mai ciudat rezultat de aici: o penalizare liniară a scorului proporțională cu distanța, cu o pantă diferită per head. Perplexitatea lui se îmbunătățește pe măsură ce fereastra crește dincolo de lungimea de antrenare, de la 44,95 la 42,49, fiindcă penalizarea este definită la orice distanță și fiecare head continuă să facă ce a fost antrenat să facă.

Lecția supraviețuiește tabelului: o arhitectură care nu poate reprezenta ceva este o problemă diferită de una care nu a învățat niciodată acel interval, iar a doua este cea care mușcă. Este și mecanismul din spatele fiecărui anunț „am extins contextul la 128K” — acestea sunt aproape întotdeauna rescalări ale unei codări rotative și de aceea Capitolul 16 spune că limita de context se mută, nu dispare.

Dropout este moștenit la fel: apare pe ponderile attention după softmax, pe ieșirea fiecărui sub-strat înainte de adunarea reziduală și pe suma embedding, făcând exact ce a descris Capitolul 6. În rulările mari de pretraining este adesea setat la zero, fiindcă un model care vede fiecare token o singură dată nu este în poziția de a face overfit.

Două tensori din strat au forma n×nn \times n, unde nn este numărul de tokens: scorurile și ponderile după softmax. Tot restul — fiecare proiecție, întregul MLP — este liniar în nn.

Un strat attention, lățime 512, 8 heads, batch de unu, float32, pe un GPU de laptop. Citește cele două coloane în milisecunde doar pentru rapoartele lor: sunt wall clock pe o placă de laptop de 8 GB care coboară de la 1.785 MHz la sub 300 MHz când se încălzește, așa că o rulare rece a aceluiași cod revine de șapte până la zece ori mai repede, iar una ocupată și mai încet. Coloanele în megabytes sunt numărători de bytes ale allocator-ului și nu se mișcă.

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

Coloanele x4 sunt raportul față de rândul de deasupra, iar o dublare a lui nn converge exact la 4 atât pentru timp, cât și pentru memorie — 3,91 la ultimul pas, față de un 4 teoretic. Coloana proiecțiilor este controlul: 4,0 ms la 1024 tokens până la 40,1 ms la 8192, un factor de zece pentru un factor de opt. Liniar, cum s-a promis.

Apoi ultimul rând. Un strat attention, o secvență, fără model în jurul lui, rămâne fără memorie pe un GPU de 8 GB la 16.384 tokens — matricea de scoruri singură ar fi 8 GB, fiind 8 heads ori 16.384 ori 16.384 ori 4 bytes. Nu modelul; un tensor intermediar într-un singur strat.

Acesta este faptul fizic de sub trei capitole ulterioare. De aceea un context window are o limită, pe care Capitolul 16 o transformă într-un preț. De aceea există FlashAttention, calculând același rezultat în tiles fără să stocheze vreodată matricea — o optimizare de memorie înainte să fie una de viteză.11 Și aceasta este aritmetica din spatele prețului unui prompt lung, pe care Capitolul 24 îl plătește într-o buclă de agent — o chestiune separată de cealaltă constatare a acelui capitol, că un model și folosește mai prost un context lung, lucru pe care îl măsoară și refuză să îl dea pe seama acestei formule.

Afișează detaliile

Cele două variante care micșorează cache-ul, numite aici și plătite în Capitolul 13.

Generarea pune în cache cheile și valorile tokens deja procesați — un key și un value per token, per head per strat. Multi-query attention12 păstrează hh proiecții query, dar o singură proiecție key și value partajată de toate heads, împărțind acel cache la hh. Grouped-query attention13 interpolează: heads sunt grupate, fiecare grup partajând un key și un value, deci g=hg = h este attention obișnuit, iar g=1g = 1 este multi-query. Aproape fiecare model open din 2023 încoace îl folosește cu 4 sau 8 grupuri. Niciunul nu există pentru calitate; ambele există pentru dimensiunea acelui cache, iar Capitolul 13 face aritmetica ce îl transformă în „ce model încape în GPU-ul tău”.

Lucrarea din 2017 descrie un encoder-decoder: o stivă care citește sursa cu attention nemascat, o a doua care generează ținta cauzal, și un al treilea tip de attention la mijloc, unde query-urile decoder-ului întâlnesc keys ale encoder-ului. Asta este potrivit pentru traducere, unde intrarea și ieșirea sunt două secvențe.

Ce a câștigat a fost jumătatea decoder-only — o singură stivă, cauzală peste tot, intrarea și ieșirea în aceeași secvență — iar motivul nu este eleganța. „Prezice următorul token” rulează pe orice text, deci setul de antrenare este internetul, nu un corpus paralel, iar totul devine acea singură sarcină: o traducere este un document care conține sursa, apoi ținta; o întrebare și răspunsul ei sunt un document; o conversație cu un tool call la mijloc este un document. Capitolul 11 este despre cum este fabricat ultimul. Encoders nu au dispărut — unul vede toată intrarea deodată, ceea ce vrei când sarcina este să reprezinți un text, nu să îl continui, și de aceea embedding-urile de retrieval din Capitolul 19 vin din encoders, nu din modelul care conversează.

Cu blocul definit, dimensiunea modelului este aritmetică. Per bloc, cu lățimea dd și o expansiune de patru ori: 4d2+4d4d^2 + 4d pentru Wq,Wk,Wv,WoW_q, W_k, W_v, W_o cu biases pe toate patru, așa cum le are GPT-2 — tabelul de mai sus lasă bias-ul în afara a trei dintre ele, de aici 2.304 mai puțini per bloc la d=768d = 768; 8d2+5d8d^2 + 5d pentru MLP; 4d4d pentru două LayerNorms — 12d2+13d12d^2 + 13d, plus un tabel de tokens de V×dV \times d și, pentru poziții absolute, nctx×dn_{\text{ctx}} \times d. Pentru forma GPT-2 small — d=768d = 768, 12 blocuri, un vocabular de 50.257, un context de 1024, stratul de ieșire partajând ponderile 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

Care este dimensiunea publicată a acelui model. Formula nu este o aproximație; este modelul. Observă și că aproape o treime dintr-un model mic este tabelul de embedding, motiv pentru care dimensiunea vocabularului este o decizie arhitecturală, nu una de preprocessing — trade-off-ul pregătit de Capitolul 7.

Perplexitatea este un număr despre un corpus. Ce face un head este o întrebare diferită, iar un model antrenat pe un megabyte de Shakespeare este instrumentul greșit pentru ea: lucrul onest de spus despre harta attention a unui model cu 500.000 de parametri este că în mare parte nu este interpretabilă. Așadar: un limbaj în care întrebarea are un răspuns corect.

Ilustrația clasică este the animal did not cross the street because it was too tired, unde it este animalul, față de …because it was too wet, unde un cuvânt mută referentul la stradă. Acestea sunt scheme Winograd14 — perechi de propoziții identice cu excepția unui singur cuvânt, unde acel cuvânt decide la ce se referă un pronume.

Sunt și rezolvabile trișând, ceea ce tutorialele omit. Dacă cei doi candidați sunt un animal și un loc, tired și wet identifică referentul prin categorie, iar un model care știe doar ce cuvinte sunt prezente răspunde corect fără să știe nimic despre ordine. Măsurat pe acea versiune a sarcinii, cu perechi animal/loc reținute:

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

Bag of words bate transformer-ul. Orice demonstrație construită pe acea propoziție nu dovedește nimic despre attention.

Așa că închide gaura: extrage ambii candidați dintr-un singur pool de șaisprezece substantive, oricare putând apărea în oricare slot, și împarte adjectivele după rol, nu după categorie — patru care fac it să fie cel care traversează (tired, scared, slow, weak), patru care îl fac să fie cel traversat (wet, wide, busy, steep).

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

Antrenează ca predictor next-token obișnuit, evaluează o singură poziție — cuvântul de după so the — și construiește setul reținut din perechi de substantive a căror ordine inversă a fost în antrenare, astfel încât orice știe care două substantive sunt prezente, dar nu care a venit primul, trebuie să răspundă pe dos.

modelparametrireținutnumește celălalt substantiv
doar token-ul curent5.7965,2 %5,2 %
medie cauzală uniformă5.79627,9 %50,0 %
un head de learned attention18.08435,4 %64,6 %
patru heads22.24475,0 %15,6 %
un bloc transformer55.71692,7 %4,2 %
două blocuri transformer105.508100,0 %0,0 %

Șansa între cele două substantive prezente este 50 %. Media uniformă ajunge la 27,9 % și răspunde cu substantivul greșit al perechii exact jumătate din timp — semnătura a ceva care știe ce cuvinte sunt acolo și nimic despre ordinea lor, cum a prezis testul de amestecare cu trei secțiuni în urmă.

Acum harta: attention la poziția care trebuie să numească referentul, mediată peste cele patru heads ale fiecărui bloc, pentru cele două propoziții care diferă printr-un singur cuvânt. O medie uniformă ar pune 0,067 pe fiecare dintre cei cincisprezece tokens vizibili.

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

Blocul 1 este identic în ambele propoziții — 0,70 pe primul substantiv, indiferent care este adjectivul. Asta nu este un eșec, ci o dovadă: în primul strat, query la o poziție este o funcție a token-ului și indicelui propriu acelei poziții, iar the la poziția 14 este același token în ambele propoziții. Un head de primul strat nu poate condiționa pe un cuvânt pe care nu l-a adus încă. Așa că blocul 1 face singurul lucru util disponibil și trage primul substantiv înainte.

Blocul 2 este locul unde propozițiile se despart, iar același rând peste toate cele opt adjective arată regula pe care modelul a găsit-o:

adjectivblocul 2 pe animalpe streetpe adjectivrăspuns
tired, scared, slow, weak0,0000,0001,000animal
wet, wide, busy, steep0,0000,4910,00–0,03street

Pentru un adjectiv al celui care traversează, al doilea bloc își petrece toată ponderea pe adjectiv, fiindcă răspunsul este deja în fluxul rezidual — blocul 1 l-a pus acolo — și tot ce îi trebuie este confirmare. Pentru un adjectiv al celui traversat, merge și aduce celălalt substantiv. Acesta este un circuit cu două salturi: un head mută un candidat înainte, un head dintr-un strat ulterior citește un token care decide dacă îl păstrează. Compoziția peste straturi este mecanismul, și de aceea un bloc a ajuns la 92,7 %, iar două la 100 %.

Este și forma celui mai bine documentat circuit din modele reale. Induction heads — un previous-token head care alimentează un head din stratul următor ce completează tiparul [A][B] … [A] → [B] — sunt ceea ce munca de interpretabilitate de la Anthropic identifică în spatele unei mari părți din in-context learning, iar ele se formează într-un moment identificabil în timpul pretraining. Acest capitol nu încearcă acea analiză: este delegată, cu ambele lucrări în referințe, fiindcă citirea circuitelor dintr-un model real este un domeniu de cercetare, nu o secțiune.

În final, implementarea. Cele treizeci de linii de mai sus, cu ponderile copiate din chiar 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} pe ieșiri a căror magnitudine medie este 0,159: aceeași aritmetică într-o ordine diferită, la precizie float32.

Ai arhitectura din care este construit fiecare model din restul cursului, iar ea este mai mică decât reputația ei: o medie ponderată ale cărei ponderi sunt învățate, un MLP per poziție care ține două treimi din parametri, două normalizări și două adunări, stivuite.

Ce nu ai este un model care știe ceva, iar stivuirea nu va repara asta de una singură. Două blocuri pe acest corpus ajung la o perplexitate de antrenare de 14,49 și o perplexitate de validare de 40,57, față de 18,77 și 38,07 pentru un bloc — mai multă capacitate, mai bine pe ce a văzut, mai rău pe ce nu a văzut, adică tabelul din Capitolul 6 cu un transformer în el. Distanța dintre acest model și cele cu care vorbesc Capitolele 14 până la 30 nu este arhitecturală. Este același bloc, repetat de mai multe ori, peste mult mai mult text.

Ceea ce o transformă într-o problemă de contabilitate, iar contabilitatea este mai ciudată decât pare. Cât text și de unde îl ia cineva? Câtă aritmetică și cum o estimezi înainte ca banii să fie cheltuiți? Dat un buget fix, este mai bine să faci modelul mai mare sau să îi arăți mai multe date — și există un răspuns corect sau doar o modă? Capitolul 10 răspunde la toate trei prin măsurare și pune un preț pe cea mai ieftină formă utilă a întrebării: cât costă, astăzi, să antrenezi de la zero un model ca GPT-2?


Trei explicații ale acestui material sunt mai bune decât aceasta la ce își propun. Acest capitol este scris ca să fie citit alături de ele. The Illustrated Transformer de Jay Alammar este cea mai bună imagine a fluxului de date desenată vreodată. The Annotated Transformer de la Harvard NLP este lucrarea din 2017 cu cod executabil intercalat linie cu linie. Let's build GPT: from scratch, in code, spelled out de Andrej Karpathy construiește același model live în două ore, iar scara de ablations de mai sus este aceeași coloană vertebrală măsurată pe un corpus diferit. Pentru întrebarea de interpretabilitate pe care acest capitol doar o atinge, sursele primare sunt Elhage et al., A Mathematical Framework for Transformer Circuits (2021) și Olsson et al., In-context Learning and Induction Heads (2022), ambele de la grupul de interpretabilitate Anthropic.

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

  2. Sutskever, I., Vinyals, O. și Le, Q. V. Sequence to Sequence Learning with Neural Networks. arXiv:1409.3215 (2014). Encoder-decoder-ul al cărui singur vector de context este blocajul.

  3. Bahdanau, D., Cho, K. și Bengio, Y. Neural Machine Translation by Jointly Learning to Align and Translate. arXiv:1409.0473 (2014). Attention, cu trei ani înainte de transformer.

  4. Perplexitatea este exponențiala cross-entropy medii per token, din Capitolul 8. Fiecare număr de aici folosește același tokenizer și același split de validare, singura condiție în care două perplexități pot fi comparate.

  5. Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł. și Polosukhin, I. Attention Is All You Need. arXiv:1706.03762 (2017). Secțiunea 3.2.1 este singura propoziție despre dk\sqrt{d_k} pe care acest capitol petrece o secțiune măsurând-o.

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

  7. Ba, J. L., Kiros, J. R. și Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). Introdus și măsurat în Capitolul 6; folosit aici neschimbat.

  8. Xiong, R., Yang, Y., He, D., Zheng, K., Zheng, S., Xing, C., Zhang, H., Lan, Y., Wang, L. și Liu, T.-Y. On Layer Normalization in the Transformer Architecture. arXiv:2002.04745 (2020). Analiza gradientului din spatele pre-norm și argumentul că warmup este un simptom.

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

  10. Press, O., Smith, N. A. și Lewis, M. Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation. arXiv:2108.12409 (2021). Rezultatul de extrapolare reprodus mai sus.

  11. Dao, T., Fu, D. Y., Ermon, S., Rudra, A. și 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. și Sanghai, S. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023).

  14. Levesque, H. J., Davis, E. și Morgenstern, L. The Winograd Schema Challenge. KR (2012). Construcția din spatele propoziției animal / street pe care o folosește fiecare tutorial despre attention.

Gata să lași LIA să aleagă?

Construiește cu toate modelele AI într-un singur loc — începe gratuit azi.