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 , 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 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 construimDin 1997 până aproximativ în 2017, rezumatul era o stare recurentă: păstrează un vector și actualizează-l la fiecare token, . Dimensiune fixă, intrare variabilă, exact forma potrivită.
A eșuat în trei feluri, iar arhitectura acestui capitol răspunde la toate trei. Backpropagating prin pași înmulțește Jacobians, deci gradientul dispare sau explodează — boala pe care Capitolul 5 a măsurat-o într-un singur nod . 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: are nevoie de , 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.
Cel mai ieftin rezumat posibil
Link către secțiunea: Cel mai ieftin rezumat posibilCea mai evidentă funcție care primește un număr variabil de vectori și returnează un singur vector este media:
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 , cu un batch de 64. Perplexitatea este pe split-ul reținut.4
| model | parametri | perplexitate de validare |
|---|---|---|
| doar token-ul curent, fără context deloc | 263.168 | 59,71 |
| plus media uniformă a tot ce vine înainte | 263.168 | 248,07 |
| plus learned position embeddings | 279.552 | 245,93 |
| media uniformă adăugată la token în loc să îl înlocuiască | 263.168 | 60,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:
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())2.9802322387695312e-08Zgomot 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 softmaxMedierea 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:
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 againloop 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.167Trei componente numite ale unui transformer sunt acum pe ecran. Triunghiul este masca cauzală, impusă de obiectiv: dacă poziția ar putea vedea poziția , 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 le trimite exact la zero și normalizează ce rămâne, deci mascarea și normalizarea sunt o singură operație. (Folosește , nu -1e9: este valoarea pe care mascarea o înseamnă, supraviețuiește unui cast la float16 ca ș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?
Query, key, value
Link către secțiunea: Query, key, valueNu pot fi parametri simpli. O matrice î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 de poziția 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 pentru poziția ca ș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, , query; și ce oferă ca să fie găsită, , key. Scorul și simetria dispare, fiindcă : un token poate anunța un lucru și căuta altul.
Un lucru este încă greșit. Suma ponderată era peste î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ă, , value, și însumează acelea.
Formula este acum contabilitate:
cu masca cauzală, zero pe diagonală și sub ea și deasupra. În cod sunt treizeci de linii, dintre care douăzeci sunt forme:
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 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 și sunt independente, cu medie zero și varianță unu, fiecare produs are varianță unu, iar varianțele lucrurilor independente se adună:
Deci scorurile au deviație standard . Măsurat peste douăzeci de mii de perechi aleatoare:
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.000De 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:
| cea mai mare pondere, neîmpărțită | entropie | tokens efectivi | cea mai mare pondere, împărțită | entropie | tokens efectivi | |
|---|---|---|---|---|---|---|
| 4 | 0,205 | 2,944 | 19,0 | 0,081 | 3,758 | 42,9 |
| 16 | 0,438 | 1,692 | 5,4 | 0,075 | 3,849 | 46,9 |
| 64 | 0,489 | 0,874 | 2,4 | 0,085 | 3,673 | 39,4 |
| 256 | 0,9999 | 0,0007 | 1,0 | 0,143 | 3,547 | 34,7 |
| 1024 | 1,0000 | 0,0000 | 1,0 | 0,132 | 3,644 | 38,3 |
„tokens efectivi” este exponențiala entropiei: peste câte poziții mediază rândul cu adevărat. Neîmpărțit, la , 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 . Un softmax angajat pe o singură intrare are aproape nicio derivată: diagonala Jacobian-ului său este , zero la ambele capete. Peste două mii de rânduri aleatoare:
| neîmpărțit | împărțit | rânduri saturate (cea mai mare pondere peste 0,99) | |
|---|---|---|---|
| 4 | 0,8427 | 0,9568 | 0,2 % → 0,0 % |
| 64 | 0,2940 | 0,9609 | 17,9 % → 0,0 % |
| 256 | 0,1406 | 0,9609 | 49,1 % → 0,0 % |
| 1024 | 0,0681 | 0,9611 | 70,4 % → 0,0 % |
La , ș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 head | neîmpărțit | împărțit la | împărțit la |
|---|---|---|---|
| patru heads, | 37,29 | 38,07 | 37,89 |
| un head, | 48,51 | 46,10 | 45,99 |
| un head, | 65,37 | 47,53 | — |
| un head, | 67,06 | 49,15 | — |
| un head, | 76,69 | 59,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 , î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:
| std scor la init | după 1500 de pași, neîmpărțit | după 1500 de pași, împărțit | rânduri saturate, neîmpărțit | împărțit | |
|---|---|---|---|---|---|
| 256 | 10,49 | 121,67 | 2,13 | 91,9 % | 0,8 % |
| 512 | 15,13 | 836,85 | 2,66 | 98,7 % | 1,3 % |
| 1024 | 21,15 | 5147,46 | 3,44 | 99,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 ” ș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 nimeniUn 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 , concatenează și amestecă cu încă o matrice : 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:
| model | parametri | perplexitate de validare |
|---|---|---|
| medie uniformă, adăugată | 279.552 | 60,45 |
| un attention head, înlocuind token-ul | 328.704 | 55,47 |
| un attention head, adăugat | 328.704 | 46,10 |
| patru heads în loc de unul | 345.216 | 43,21 |
| plus rețeaua feed-forward | 476.928 | 39,87 |
| plus LayerNorm — blocul complet | 477.696 | 38,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ățime | heads | attention | feed-forward | total per bloc |
|---|---|---|---|---|
| 128 | 4 | 65.664 (33,2 %) | 131.712 (66,6 %) | 197.888 |
| 768 | 12 | 2.360.064 (33,3 %) | 4.722.432 (66,6 %) | 7.085.568 |
| 4096 | 32 | 67.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 , 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 6LayerNorm 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.
class Block(nn.Module):
def forward(self, x):
x = x + self.att(self.ln1(x))
x = x + self.ff(self.ln2(x))
return xUită-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 :
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.88Post-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.
Unde este un token?
Link către secțiunea: Unde este un token?Ș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ă sau î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ții | perplexitate la 64 | la 128 | la 256 |
|---|---|---|---|
| deloc | 48,79 | 52,63 | 57,52 |
| learned absolute embeddings | 38,63 | 108,47 | 181,94 |
| sinusoide fixe | 42,96 | 95,26 | 152,25 |
| RoPE | 44,12 | 50,52 | 84,84 |
| ALiBi | 44,95 | 43,51 | 42,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 , 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.
Cât costă
Link către secțiunea: Cât costăDouă tensori din strat au forma , unde este numărul de tokens: scorurile și ponderile după softmax. Tot restul — fiecare proiecție, întregul MLP — este liniar în .
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ă.
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.87Coloanele x4 sunt raportul față de rândul de deasupra, iar o dublare a lui 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ă proiecții query, dar o singură proiecție key și value partajată de toate heads, împărțind acel cache la . Grouped-query attention13 interpolează: heads sunt grupate, fiecare grup partajând un key și un value, deci este attention obișnuit, iar 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”.
Două forme și dimensiunea uneia
Link către secțiunea: Două forme și dimensiunea uneiaLucrarea 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 și o expansiune de patru ori: pentru 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 ; pentru MLP; pentru două LayerNorms — , plus un tabel de tokens de și, pentru poziții absolute, . Pentru forma GPT-2 small — , 12 blocuri, un vocabular de 50.257, un context de 1024, stratul de ieșire partajând ponderile embedding:
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,808Care 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.
La ce se uită de fapt un head
Link către secțiunea: La ce se uită de fapt un headPerplexitatea 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:
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).
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.
| model | parametri | reținut | numește celălalt substantiv |
|---|---|---|---|
| doar token-ul curent | 5.796 | 5,2 % | 5,2 % |
| medie cauzală uniformă | 5.796 | 27,9 % | 50,0 % |
| un head de learned attention | 18.084 | 35,4 % | 64,6 % |
| patru heads | 22.244 | 75,0 % | 15,6 % |
| un bloc transformer | 55.716 | 92,7 % | 4,2 % |
| două blocuri transformer | 105.508 | 100,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.
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.25Blocul 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:
| adjectiv | blocul 2 pe animal | pe street | pe adjectiv | răspuns |
|---|---|---|---|---|
| tired, scared, slow, weak | 0,000 | 0,000 | 1,000 | animal |
| wet, wide, busy, steep | 0,000 | 0,491 | 0,00–0,03 | street |
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:
ours vs nn.MultiheadAttention max |diff| = 1.7881393432617188e-07
ours vs F.scaled_dot_product_attention max |diff| = 1.7881393432617188e-07pe ieșiri a căror magnitudine medie este 0,159: aceeași aritmetică într-o ordine diferită, la precizie float32.
Unde merge mai departe
Link către secțiunea: Unde merge mai departeAi 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?
Surse și metodă
Link către secțiunea: Surse și metodă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.
Referințe
Link către secțiunea: Referințe-
Hochreiter, S. și Schmidhuber, J. Long Short-Term Memory. Neural Computation 9(8), pp. 1735–1780 (1997). ↩
-
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. ↩
-
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. ↩
-
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. ↩
-
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 pe care acest capitol petrece o secțiune măsurând-o. ↩
-
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). ↩
-
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. ↩
-
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. ↩
-
Su, J., Lu, Y., Pan, S., Murtadha, A., Wen, B. și Liu, Y. RoFormer: Enhanced Transformer with Rotary Position Embedding. arXiv:2104.09864 (2021). ↩
-
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. ↩
-
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). ↩
-
Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). ↩
-
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). ↩
-
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. ↩