Attention en het transformer block, afgeleid uit een gemiddelde
Begin met de goedkoopste samenvatting van context — het gemiddelde — meet de fouten en laat attention uit de reparatie volgen.
Op deze pagina
Je komt hier aan met een tokenizer uit Hoofdstuk 7, een embedding-tabel uit Hoofdstuk 8, en het doel dat daarbij hoort: gegeven de tokens tot nu toe, zet een waarschijnlijkheid op de volgende.
Wat ontbreekt is het midden. Om token te voorspellen heeft het model één vector nodig die alles ervoor samenvat, en niets wat je hebt gebouwd produceert er een. De embedding van token is het niet — dat is een bigrammodel, en dat kan niet weten dat de zin met een vraag begon. Een concatenatie van alle vorige embeddings is het ook niet: hun aantal verandert bij elke stap, en een vaste gewichtsmatrix kan geen input met variabele lengte aannemen.
Dus: één vector met vaste grootte, die een variabel aantal vectoren samenvat. Dat is het hele probleem, en attention is wat je krijgt als je het op de luiest mogelijke manier oplost en daarna de twee dingen repareert die stukgaan.
Het antwoord dat het vakgebied had, en waarom we het niet bouwen
Link naar de sectie: Het antwoord dat het vakgebied had, en waarom we het niet bouwenVan 1997 tot ongeveer 2017 was de samenvatting een recurrente toestand: bewaar een vector en werk die bij bij elke token, . Vaste grootte, variabele input, precies de juiste vorm.
Het faalde op drie manieren, en de architectuur van dit hoofdstuk beantwoordt ze alle drie. Backpropagating door stappen vermenigvuldigt Jacobianen, waardoor de gradient verdwijnt of explodeert — de ziekte die Hoofdstuk 5 mat binnen één -node. De LSTM1 was precies daartegen ontworpen en duwde het bruikbare bereik van tientallen stappen naar honderden, zonder het feit te veranderen dat informatie van token 5 token 500 alleen bereikt door 495 sequentiële updates te overleven. De hele bron moest in één vector passen: bij sequence-to-sequence-vertaling2 comprimeert een encoder de input tot zijn eindtoestand. Bahdanau, Cho en Bengio benoemden die bottleneck en repareerden hem in 2014, drie jaar vóór de transformer, door de decoder een gewogen som van alle encoder-toestanden te laten nemen met gewichten die hij zelf berekende.3 Alles hieronder is dat idee, toegepast door een reeks op zichzelf, met de recurrence verwijderd. En de update is per constructie sequentieel: heeft nodig, en een GPU met tienduizend cores kan daar niets mee. De architectuur die won is niet overduidelijk slimmer; het is degene waarvan de dure stap een matrixvermenigvuldiging is.
De andere klassieke inductive bias, convolutie — schuif één klein filter over de hele input, zodat een feature die ergens wordt gedetecteerd overal wordt gedetecteerd — wordt hier ook niet gebouwd; hij is bijna precies goed voor beelden en wordt gedelegeerd aan een vision-cursus. Noch recurrence noch convolutie komt na deze pagina terug, en daarom krijgt geen van beide een hoofdstuk: Hoofdstuk 1 beloofde dat weglatingen zouden worden uitgesproken in plaats van stilgehouden.
De goedkoopste samenvatting die er is
Link naar de sectie: De goedkoopste samenvatting die er isDe meest voor de hand liggende functie van een variabel aantal vectoren die één vector teruggeeft is het gemiddelde:
Willekeurig veel inputs, vaste outputgrootte, differentieerbaar, gratis. Een embedding-tabel plus dit gemiddelde plus een lineaire laag naar de vocabulary is een volledig taalmodel in vijftien regels. Het is ook verschrikkelijk, en hoe het verschrikkelijk is, is de hele afleiding.
Het corpus hieronder is één megabyte Shakespeare, 1.115.394 tekens, door een byte-level BPE-tokenizer van het soort dat in Hoofdstuk 7 is gebouwd met een vocabulary van 1024: 459.760 tokens met elk 2,43 tekens, 90/10 gesplitst. Elk model is 128 breed, ziet 128 tokens en traint 3000 stappen AdamW op met een batch van 64. Perplexity staat op de held-out split.4
| model | parameters | validation perplexity |
|---|---|---|
| alleen de huidige token, helemaal geen context | 263.168 | 59,71 |
| plus het uniforme gemiddelde van alles ervoor | 263.168 | 248,07 |
| plus geleerde position embeddings | 279.552 | 245,93 |
| uniform gemiddelde toegevoegd aan de token in plaats van die te vervangen | 263.168 | 60,45 |
Lees de tweede rij twee keer. De context middelen helpt niet een beetje; het maakt het model vier keer slechter dan de context volledig negeren. Twee redenen, beide bewijsbaar in plaats van empirisch.
Het gemiddelde kan geen volgorde zien. Optelling commuteert, dus het husselen van de window laat de samenvatting onveranderd — niet ongeveer:
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-08Floating-point-ruis op een opnieuw geordende som: de twee samenvattingen zijn dezelfde vector. Een model waarvan de enige kijk op de context een gemiddelde is, kan the dog bit the man niet onderscheiden van the man bit the dog. Rij drie bewijst dat dit niet te repareren is door posities aan de inputs toe te voegen — een geleerde position embedding op elke token vóór het middelen kocht 2,14 punten van 188. De posities gaan de som in, en de som vergeet ze.
En het gemiddelde verdrinkt het heden. Op positie 100 is de huidige token een honderdste van de samenvatting. Daarvoor heb je al een goedkope reparatie: behoud de token en tel de samenvatting erbij op — een residual connection, uit Hoofdstuk 6, en rij vier laat zien wat die doet. Met de verdunning gerepareerd draagt het uniforme gemiddelde helemaal niets bij: 60,45 tegenover een baseline van 59,71. Elke token zit erin, gelijk gewogen, en gelijke weging is hetzelfde als geen informatie.
Het probleem is niet het middelen. Het zijn de gewichten.
Het gemiddelde is een matrixvermenigvuldiging, en het mask is een softmax
Link naar de sectie: Het gemiddelde is een matrixvermenigvuldiging, en het mask is een softmaxMiddelen over een groeiende prefix lijkt op een lus. Het is één vermenigvuldiging met een lower-triangular matrix waarvan de rijen tot één sommeren — en ook, exact, een 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.167Drie benoemde componenten van een transformer staan nu op het scherm. De driehoek is het causal mask, afgedwongen door het doel: als positie positie kon zien, zou het antwoord in de input zitten — het lek waarvoor Hoofdstuk 6 je liet auditen, maar dan binnen de architectuur. De softmax is hoe het mask wordt geïmplementeerd: verboden entries op zetten stuurt ze precies naar nul en normaliseert wat overblijft, dus masken en normaliseren zijn één operatie. (Gebruik , niet -1e9: het is de waarde die het masken betekent, het overleeft een cast naar float16 als , en het bespaart je de beslissing of de constante die je koos groot genoeg is voor het bereik waarin je toevallig zit — wat de floating-point-doos van Hoofdstuk 2 een vraag laat stellen die je niet hoeft te beantwoorden.) En de scores zijn de vrije parameter. Het uniforme gemiddelde is wat je krijgt wanneer elke toegestane score hetzelfde getal is; zet er willekeurige getallen neer en de softmax maakt er geldige gewichten van.
De rest van dit hoofdstuk is één vraag: waar komen die getallen vandaan?
Query, key, value
Link naar de sectie: Query, key, valueHet kunnen geen gewone parameters zijn. Een geleerde -matrix zou voor elke zin identiek zijn — hij zou „kijk vier tokens terug” kunnen coderen, maar nooit „kijk naar het zelfstandig naamwoord waar dit voornaamwoord naar verwijst”. Het gewicht dat positie aan positie koppelt moet afhangen van wat er op beide posities staat, want relevantie is een relatie, geen eigenschap: het woord it is niet intrinsiek relevant, het is relevant voor iets.
De goedkoopste functie van twee vectoren die een getal teruggeeft is het inproduct uit Hoofdstuk 1. Score positie voor positie als en het mechanisme werkt — slecht, op twee manieren die al het andere afdwingen. Het inproduct van een vector met zichzelf is zijn kwadratische norm, dus elke token zou vooral aan zichzelf attend. En de relatie zou symmetrisch zijn: als it sterk attend op animal, dan attend animal sterk op it, wat onwaar is over taal, waar een bijvoeglijk naamwoord zijn zelfstandig naamwoord veel harder nodig heeft dan het zelfstandig naamwoord het bijvoeglijk naamwoord.
Geef elke token dus twee rollen, als twee geleerde lineaire maps ervan: waar deze positie naar zoekt, , de query; en wat hij aanbiedt om op gevonden te worden, , de key. Score en de symmetrie is weg, omdat : een token kan één ding adverteren en naar iets anders zoeken.
Eén ding klopt nog niet. De gewogen som liep over de zelf, waardoor het ding dat wordt gekopieerd hetzelfde moet zijn als het ding dat wordt gematcht. Matchen wil de features die een token identificeren; kopiëren wil de features die downstream nuttig zijn. Leer dus een derde map, , de value, en sommeer die.
De formule is nu boekhouding:
met het causal mask, nul op en onder de diagonaal en erboven. In code zijn het dertig regels, waarvan twintig shapes:
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, mask, normaliseer, mix. Al het andere is een projectie.
De deling door de vierkantswortel, en waartegen die beschermt
Link naar de sectie: De deling door de vierkantswortel, en waartegen die beschermtBijna elke uitleg van zegt „om te voorkomen dat de softmax satureert”, wat waar is en niets uitlegt. Het argument bestaat uit twee regels variantie uit Hoofdstuk 2. Als de entries van en onafhankelijk zijn met gemiddelde nul en variantie één, heeft elk product variantie één, en varianten van onafhankelijke dingen tellen op:
Dus de scores hebben standaardafwijking . Gemeten over twintigduizend willekeurige paren:
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.000Waarom dat ertoe doet: de softmax is schaalgevoelig op een manier waarop een lineaire laag dat niet is. De input van een lineaire laag verdubbelen verdubbelt zijn output; scores met tien vermenigvuldigen vóór een softmax verandert een zachte blend in een harde keuze. Eén rij van 64 scores, met en zonder de deling:
| grootste gewicht, ongedeeld | entropie | effectieve tokens | grootste gewicht, gedeeld | entropie | effectieve tokens | |
|---|---|---|---|---|---|---|
| 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 |
„Effectieve tokens” is de exponent van de entropie: over hoeveel posities de rij echt middelt. Ongedeeld, bij , attend een vers geïnitialiseerde head op precies één token van de 64, gekozen door niets anders dan de random draw.
Dat is slecht forward en erger backward, in een shape die Hoofdstuk 5 al mat op een . Een softmax die zich aan één entry heeft gecommitteerd heeft bijna geen afgeleide: de diagonaal van zijn Jacobiaan is , nul aan beide uiteinden. Over tweeduizend willekeurige rijen:
| ongedeeld | gedeeld | rijen verzadigd (grootste gewicht boven 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 % |
Bij zijn zeven van de tien rijen bevroren voordat training begint, en een head die bevroren begint kan niet leren waar hij naar moet kijken. Gedeeld is de grootheid vlak op 0,96 bij elke breedte en niets satureert.
Nu het deel dat niemand publiceert: verandert het de uiteindelijke perplexity? Verwijder de deling en train, bij vier head-breedtes:
| head-breedte | ongedeeld | gedeeld door | gedeeld door |
|---|---|---|---|
| vier heads, | 37,29 | 38,07 | 37,89 |
| één head, | 48,51 | 46,10 | 45,99 |
| één head, | 65,37 | 47,53 | — |
| één head, | 67,06 | 49,15 | — |
| één head, | 76,69 | 59,17 | — |
De eerste twee rijen komen uit het budget van 3000 stappen hierboven; de laatste drie zijn een kortere run — 1500 stappen, batch van 32, één head, geen normalisatie vóór de projecties — met beide varianten onder identieke instellingen.
Bij is de deling niets waard en loopt de run zonder deling heel licht voor. Dat is geen licentie om haar te laten vallen, want bij 256 is ze 18 punten perplexity waard en bij 1024 is ze 17 waard. Het mechanisme is zichtbaar in de scores zelf:
| score-std bij init | na 1500 stappen, ongedeeld | na 1500 stappen, gedeeld | rijen verzadigd, ongedeeld | gedeeld | |
|---|---|---|---|---|---|
| 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 % |
De ongedeelde head herstelt niet. Hij loopt weg: de standaardafwijking van zijn scores gaat van 21 bij initialisatie naar 5147, de attention-entropie valt naar nul, en 99,9 % van de rijen zet meer dan 0,99 van hun gewicht op één token. Zodra een head een harde selector is, is zijn gradient bijna nul en trekt niets hem terug, dus de collapse is stabiel. De gedeelde head zit na dezelfde training op een score-standaardafwijking van 3,44, wat een zachte blend is die nog kan worden veranderd.
Vaswani et al. zeggen precies dit en niet meer — ze vermoeden dat de producten „groot in magnitude worden voor grote waarden van ” en delen.5 Het woord groot draagt gewicht, en de tabellen zeggen waar groot begint: niets bij 32, alles bij 256.
Meer dan één mening, en de twee derde waar niemand over praat
Link naar de sectie: Meer dan één mening, en de twee derde waar niemand over praatEén head is één softmax-rij per positie, dus hij bevat één antwoord op „wat is hier relevant”. Het woord voorspellen na the in the animal that crossed the wet street heeft tegelijk het syntactische slot, het onderwerp en de vorige token nodig, en één kansverdeling kan niet op drie plaatsen geconcentreerd zijn. Draai dus meerdere heads parallel, elk met breedte , concateneer, en mix met nog één matrix : je hebt de breedte gepartitioneerd, niet eraan toegevoegd.
Attention doet ook precies één ding — het verplaatst informatie tussen posities. Elke operatie in de code hierboven is lineair langs de feature-as, en Hoofdstuk 5 bewees wat een stack lineaire maps is. Daarom draagt elk block ook een kleine MLP die op elke positie onafhankelijk wordt toegepast, de breedte met vier uitbreidt en weer terugkomt, met een GELU in het midden. De taakverdeling is het onthouden waard: attention mixt over posities, het feed-forward network rekent binnen een positie.
De volledige ladder, waarbij elke rij één onderdeel toevoegt aan de rij erboven:
| model | parameters | validation perplexity |
|---|---|---|
| uniform gemiddelde, toegevoegd | 279.552 | 60,45 |
| één attention head, vervangt de token | 328.704 | 55,47 |
| één attention head, toegevoegd | 328.704 | 46,10 |
| vier heads in plaats van één | 345.216 | 43,21 |
| plus het feed-forward network | 476.928 | 39,87 |
| plus LayerNorm — het complete block | 477.696 | 38,07 |
Geleerde gewichten verslaan uniforme met 14 punten perplexity, wat het hele argument van dit hoofdstuk in één rij is. Vier heads kopen nog eens 3 voor 16.512 extra parameters. En dezelfde head is toegevoegd 9 punten meer waard dan vervangend: attention brengt informatie naar binnen, het beslist niet wat een positie is.
Nu waar de parameters echt zitten, wat mensen verrast die alleen het diagram hebben gezien:
| breedte | heads | attention | feed-forward | totaal per block |
|---|---|---|---|---|
| 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 |
Twee derde van elk transformer block is het feed-forward network, op elke schaal, omdat attention vier -matrices heeft en de MLP het equivalent van acht. Wat een model ook weet, de meeste parameters die het vasthouden zitten in de MLP per positie.
Residuals en LayerNorm, geërfd uit Hoofdstuk 6
Link naar de sectie: Residuals en LayerNorm, geërfd uit Hoofdstuk 6LayerNorm is gebouwd en gemeten in Hoofdstuk 6, en dit hoofdstuk gebruikt het zoals het daar werd achtergelaten; residual connections werden daar benoemd en geablateerd, en worden hier gebouwd. De rijen „toegevoegd, niet vervangend” hierboven zijn residual connections, goed voor 188 punten perplexity bij het gemiddelde en 9 bij één head. LayerNorm7 normaliseert elk voorbeeld over zijn features, en Hoofdstuk 6 gaf de redenen waarom het hier overleefde en BatchNorm niet — geen afhankelijkheid van de batch, geen lopende statistieken, identiek in training en inference, onverschillig voor sequentielengte — elk daarvan wordt een vereiste wanneer je één token tegelijk genereert voor één gebruiker, waar Hoofdstuk 13 op uitkomt. Het kost 768 parameters en koopt 1,8 punten perplexity.
class Block(nn.Module):
def forward(self, x):
x = x + self.att(self.ln1(x))
x = x + self.ff(self.ln2(x))
return xKijk waar de normalisatie zit: op de input van elke sub-layer, met het residual path van input naar output nooit genormaliseerd. Dat is pre-norm. Het paper uit 2017 doet het omgekeerde, x = LayerNorm(x + Att(x)) — post-norm, dat een LayerNorm op het residual path zelf zet.
Xiong et al. verklaarden het verschil via de gradient bij initialisatie, die in een post-norm network slecht schaalt met diepte — de reden waarom de oorspronkelijke transformer een learning-rate warmup nodig had om überhaupt te trainen.8 Twaalf blocks, 1000 stappen, learning rate :
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 zonder warmup is acht keer slechter, en post-norm met warmup matcht pre-norm exact. Warmup is hier geen algemeen goede praktijk; het is een patch voor een specifieke ordening van de normalisatie, en de LayerNorm verplaatsen neemt de noodzaak ervoor weg. Daarom is vrijwel elk model sinds 2019 pre-norm, en daarom moet het diagram uit 2017 als geschiedenis worden gelezen in plaats van als specificatie.
Waar is een token?
Link naar de sectie: Waar is een token?Verwijder de position embeddings en het model traint nog steeds; het kan alleen niet zeggen waar iets is, en dat is een symmetrie in plaats van een trainingsfout. Niets in de attention-score noemt of zelf, dus het permuteren van de input permuteert de output: self-attention is permutation-equivariant. Het is de order-blindness van het gemiddelde in een betere vermomming — het causal mask herstelt enige volgorde, omdat elke positie een andere prefix ziet, maar binnen een prefix zijn alle ordeningen gelijk.
Vier manieren om positie te injecteren, getraind op windows van 64 tokens en geëvalueerd op 64, 128 en 256 — voorbij elke lengte die ze zagen:
| posities | perplexity bij 64 | bij 128 | bij 256 |
|---|---|---|---|
| helemaal geen | 48,79 | 52,63 | 57,52 |
| geleerde absolute embeddings | 38,63 | 108,47 | 181,94 |
| vaste sinusoïden | 42,96 | 95,26 | 152,25 |
| RoPE | 44,12 | 50,52 | 84,84 |
| ALiBi | 44,95 | 43,51 | 42,49 |
Geleerde absolute embeddings — één vector per positie, toegevoegd aan de token — winnen op de getrainde lengte en vallen daarna van een klif, omdat positie 100 nooit in een batch zat en zijn embedding nog steeds de random vector is waarmee hij begon. Sinusoïden, de oorspronkelijke keuze, worden berekend in plaats van geleerd, uit sinussen en cosinussen op geometrisch gespreide frequenties; het paper uit 2017 hoopte dat dat zou extrapoleren, en de tabel zegt van niet — de functie is gedefinieerd op positie 200, maar het model heeft nooit geleerd haar daar te lezen. RoPE9 voegt niets toe en roteert in plaats daarvan query en key met een hoek evenredig aan positie, in tweedimensionale slices; omdat beide kanten van een inproduct gelijk roteren het inproduct onveranderd laat, hangt de score uiteindelijk alleen af van , dus positie wordt gratis relatief en er is geen tabel die op kan raken. Het degradeert, maar het degradeert. ALiBi10 is het eenvoudigste en vreemdste resultaat hier: een lineaire penalty op de score evenredig aan afstand, met een andere helling per head. De perplexity verbetert wanneer de window voorbij de trainingslengte groeit, van 44,95 naar 42,49, omdat de penalty op elke afstand is gedefinieerd en elke head blijft doen waarvoor hij is getraind.
De les overleeft de tabel: een architectuur die iets niet kan representeren is een ander probleem dan een architectuur die dat bereik nooit heeft geleerd, en de tweede is degene die bijt. Het is ook de machinerie achter elke aankondiging „we hebben de context uitgebreid naar 128K” — dat zijn bijna altijd herschalingen van een rotary encoding, en ze zijn waarom Hoofdstuk 16 zegt dat de contextlimiet verschuift in plaats van verdwijnt.
Dropout wordt op dezelfde manier geërfd: het verschijnt op de attention-gewichten na de softmax, op de output van elke sub-layer vóór de residual addition, en op de embedding-som, en doet precies wat Hoofdstuk 6 beschreef. In grote pretraining-runs staat het vaak op nul, omdat een model dat elke token één keer ziet niet in de positie is om te overfitten.
Wat het kost
Link naar de sectie: Wat het kostTwee tensors in de laag hebben shape , waarbij het aantal tokens is: de scores en de gewichten na de softmax. Al het andere — elke projectie, de hele MLP — is lineair in .
Eén attention-laag, 512 breed, 8 heads, batch van één, float32, op een laptop-GPU. Lees de twee millisecondekolommen alleen voor hun verhoudingen: het is wall clock op een 8 GB-laptopkaart die van 1.785 MHz naar onder 300 MHz throttlet wanneer hij heet wordt, dus een koude run van dezelfde code komt zeven tot tien keer sneller terug en een drukke nog trager. De megabytekolommen zijn allocator-byte counts en bewegen niet.
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.87De x4-kolommen zijn de verhouding tot de rij erboven, en een verdubbeling van convergeert naar precies 4 voor zowel tijd als geheugen — 3,91 bij de laatste stap tegenover een theoretische 4. De projectiekolom is de controle: 4,0 ms bij 1024 tokens naar 40,1 ms bij 8192, een factor tien voor een factor acht. Lineair, zoals beloofd.
Dan de laatste rij. Eén attention-laag, één sequentie, geen model eromheen, raakt door het geheugen heen op een 8 GB-GPU bij 16.384 tokens — alleen de scorematrix zou 8 GB zijn, want 8 heads maal 16.384 maal 16.384 maal 4 bytes. Niet het model; één tussenliggende tensor in één laag.
Dat is het fysieke feit onder drie latere hoofdstukken. Het is waarom een context window überhaupt een limiet heeft, wat Hoofdstuk 16 omzet in een prijs. Het is waarom FlashAttention bestaat, dat hetzelfde resultaat in tiles berekent zonder ooit de matrix op te slaan — een geheugenoptimalisatie voordat het een snelheidsoptimalisatie is.11 En het is de rekenkunde achter de prijs van een lange prompt, die Hoofdstuk 24 betaalt in een agent-loop — een aparte kwestie naast de andere bevinding van dat hoofdstuk, dat een model een lange context ook slechter gebruikt, wat het meet en weigert aan deze formule te wijten.
Details tonen
De twee cache-verkleinende varianten, hier benoemd en betaald in Hoofdstuk 13.
Generatie cachet de keys en values van de tokens die al verwerkt zijn — één key en één value per token, per head per laag. Multi-query attention12 behoudt query-projecties maar één key- en value-projectie gedeeld door alle heads, waardoor die cache door wordt gedeeld. Grouped-query attention13 interpoleert: heads worden gegroepeerd, elke groep deelt één key en value, dus is gewone attention en is multi-query. Bijna elk open model sinds 2023 gebruikt het met 4 of 8 groepen. Geen van beide bestaat voor kwaliteit; beide bestaan voor de grootte van die cache, en Hoofdstuk 13 doet de rekenkunde die dat omzet in „welk model past in je GPU”.
Twee shapes, en de grootte van één
Link naar de sectie: Twee shapes, en de grootte van éénHet paper uit 2017 beschrijft een encoder-decoder: één stack die de bron leest met ongemaskerde attention, een tweede die het doel causally genereert, en een derde soort attention in het midden waar de queries van de decoder de keys van de encoder ontmoeten. Dat is juist voor vertaling, waar input en output twee sequenties zijn.
Wat won was de decoder-only helft — één stack, overal causal, input en output in dezelfde sequentie — en de reden is geen elegantie. „Voorspel de volgende token” werkt op elke tekst, dus de trainingsset is het internet in plaats van een parallel corpus, en alles wordt die ene taak: een vertaling is een document met eerst bron en dan doel, een vraag en zijn antwoord zijn een document, een gesprek met een tool call in het midden is een document. Hoofdstuk 11 gaat over hoe dat laatste wordt gemaakt. Encoders zijn niet verdwenen — eentje ziet de hele input in één keer, wat je wilt wanneer de taak is een tekst te representeren in plaats van hem voort te zetten, en daarom komen de retrieval embeddings van Hoofdstuk 19 uit encoders en niet uit het model dat chat.
Nu het block is gedefinieerd, is modelgrootte rekenkunde. Per block, met breedte en een viermaal-expansie: voor met biases op alle vier, zoals GPT-2 ze heeft — de tabel hierboven laat de bias bij drie ervan weg, vandaar 2.304 minder per block bij ; voor de MLP; voor twee LayerNorms — , plus een token-tabel van en, voor absolute posities, . Voor de shape van GPT-2 small — , 12 blocks, een vocabulary van 50.257, een context van 1024, de outputlaag die de embedding-gewichten deelt:
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,808Dat is de gepubliceerde grootte van dat model. De formule is geen benadering; ze is het model. Merk ook op dat bijna een derde van een klein model de embedding-tabel is, en daarom is vocabulary-grootte een architecturale beslissing en geen preprocessing-beslissing — de trade-off die Hoofdstuk 7 opzette.
Waar een head echt naar kijkt
Link naar de sectie: Waar een head echt naar kijktPerplexity is een getal over een corpus. Wat één head doet is een andere vraag, en een model getraind op één megabyte Shakespeare is het verkeerde instrument daarvoor: het eerlijke om te zeggen over de attention-map van een model met 500.000 parameters is dat die meestal niet interpreteerbaar is. Dus: een taal waar de vraag een juist antwoord heeft.
De klassieke illustratie is the animal did not cross the street because it was too tired, waarbij it het dier is, tegenover …because it was too wet, waar één woord de referent naar de straat verplaatst. Dit zijn Winograd-schemas14 — zinparen die identiek zijn op één woord na, waarbij dat woord beslist waar een voornaamwoord naar verwijst.
Ze zijn ook oplosbaar door vals te spelen, en dat is het deel dat de tutorials overslaan. Als de twee kandidaten een dier en een plek zijn, identificeren tired en wet de referent via categorie, en een model dat alleen weet welke woorden aanwezig zijn krijgt het goed zonder iets over volgorde te weten. Gemeten op die versie van de taak, met held-out dier/plek-paren:
uniform causal average held-out referent accuracy 100.0 %
one transformer block held-out referent accuracy 91.7 %De bag of words verslaat de transformer. Elke demonstratie die op die zin is gebouwd bewijst niets over attention.
Dicht het gat dus: trek beide kandidaten uit één pool van zestien zelfstandige naamwoorden, waarvan elk in beide slots kan verschijnen, en splits de bijvoeglijke naamwoorden op rol in plaats van categorie — vier die it de oversteker maken (tired, scared, slow, weak), vier die het de overgestokene maken (wet, wide, busy, steep).
the {x} did not cross the {y} because it was too {adj} , so the {ref} waited .Train als gewone next-token predictor, score één positie — het woord na so the — en bouw de held-out set uit zelfstandige-naamwoordparen waarvan de omgekeerde volgorde in training zat, zodat alles wat weet welke twee zelfstandige naamwoorden aanwezig zijn maar niet welke eerst kwam, achterstevoren moet antwoorden.
| model | parameters | held-out | noemt het andere zelfstandig naamwoord |
|---|---|---|---|
| alleen huidige token | 5.796 | 5,2 % | 5,2 % |
| uniform causal gemiddelde | 5.796 | 27,9 % | 50,0 % |
| één head van geleerde attention | 18.084 | 35,4 % | 64,6 % |
| vier heads | 22.244 | 75,0 % | 15,6 % |
| één transformer block | 55.716 | 92,7 % | 4,2 % |
| twee transformer blocks | 105.508 | 100,0 % | 0,0 % |
Kans tussen de twee aanwezige zelfstandige naamwoorden is 50 %. Het uniforme gemiddelde landt op 27,9 % en antwoordt precies de helft van de tijd met het verkeerde zelfstandig naamwoord van het paar — de signatuur van iets dat weet welke woorden er zijn en niets over hun volgorde, zoals de shuffle-test drie secties geleden voorspelde.
Nu de map: de attention op de positie die de referent moet noemen, gemiddeld over de vier heads van elk block, voor de twee zinnen die door één woord verschillen. Een uniform gemiddelde zou 0,067 op elk van de vijftien zichtbare tokens zetten.
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.25Block 1 is identiek in beide zinnen — 0,70 op het eerste zelfstandig naamwoord, wat het bijvoeglijk naamwoord ook is. Dat is geen falen maar een bewijs: in de eerste laag is de query op een positie een functie van de eigen token en index van die positie, en the op positie 14 is dezelfde token in beide zinnen. Een first-layer head kan niet conditioneren op een woord dat hij nog niet heeft opgehaald. Dus doet block 1 het enige nuttige dat beschikbaar is en sleept het eerste zelfstandig naamwoord naar voren.
Block 2 is waar de zinnen uit elkaar gaan, en dezelfde rij over alle acht bijvoeglijke naamwoorden toont de regel die het model vond:
| bijvoeglijk naamwoord | block 2 op animal | op street | op het bijvoeglijk naamwoord | antwoord |
|---|---|---|---|---|
| tired, scared, slow, weak | 0,000 | 0,000 | 1,000 | animal |
| wet, wide, busy, steep | 0,000 | 0,491 | 0,00–0,03 | street |
Voor een oversteek-bijvoeglijk naamwoord besteedt het tweede block al zijn gewicht aan het bijvoeglijk naamwoord, omdat het antwoord al in de residual stream zit — block 1 heeft het daar geplaatst — en alles wat nodig is, is bevestiging. Voor een overgestoken-bijvoeglijk naamwoord gaat het in plaats daarvan het andere zelfstandig naamwoord ophalen. Dat is een two-hop circuit: één head verplaatst een kandidaat naar voren, een head in een latere laag leest een token die beslist of hij hem behoudt. Compositie over lagen is het mechanisme, en daarom haalde één block 92,7 % en twee blocks 100 %.
Het is ook de shape van het best gedocumenteerde circuit in echte modellen. Induction heads — een previous-token head die een head in de volgende laag voedt die het patroon [A][B] … [A] → [B] voltooit — zijn wat Anthropic's interpretability-werk identificeert achter een groot deel van in-context learning, en ze vormen zich op een identificeerbaar moment tijdens pretraining. Dit hoofdstuk probeert die analyse niet: ze wordt gedelegeerd, met beide papers in de referenties, omdat circuits uit een echt model lezen een onderzoeksveld is en geen sectie.
Tot slot de implementatie. De dertig regels hierboven, met hun gewichten gekopieerd uit PyTorch zelf:
ours vs nn.MultiheadAttention max |diff| = 1.7881393432617188e-07
ours vs F.scaled_dot_product_attention max |diff| = 1.7881393432617188e-07op outputs waarvan de gemiddelde magnitude 0,159 is: dezelfde rekenkunde in een andere volgorde, op float32-precisie.
Waar dit hierna naartoe gaat
Link naar de sectie: Waar dit hierna naartoe gaatJe hebt de architectuur waar elk model in de rest van deze cursus uit is opgebouwd, en die is kleiner dan haar reputatie: een gewogen gemiddelde waarvan de gewichten worden geleerd, een MLP per positie die twee derde van de parameters vasthoudt, twee normalisaties en twee optellingen, gestapeld.
Wat je niet hebt is een model dat iets weet, en stapelen lost dat op zichzelf niet op. Twee blocks op dit corpus halen een training perplexity van 14,49 en een validation perplexity van 40,57, tegenover 18,77 en 38,07 voor één block — meer capaciteit, beter op wat het heeft gezien, slechter op wat het niet heeft gezien, wat de tabel van Hoofdstuk 6 is met een transformer erin. De afstand tussen dit model en de modellen waarmee Hoofdstukken 14 tot 30 praten is niet architecturaal. Het is hetzelfde block, vaker herhaald, over enorm veel meer tekst.
Dat maakt het een boekhoudprobleem, en de boekhouding is vreemder dan ze lijkt. Hoeveel tekst, en waar haalt iemand die vandaan? Hoeveel rekenwerk, en hoe schat je dat voordat het geld is uitgegeven? Gegeven een vast budget, is het beter om het model groter te maken of het meer data te tonen — en is er een juist antwoord, of alleen een mode? Hoofdstuk 10 beantwoordt alle drie door meting, en zet een prijs op de goedkoopste nuttige vorm van de vraag: wat kost het vandaag om een model als GPT-2 vanaf nul te trainen?
Bronnen en methode
Link naar de sectie: Bronnen en methodeDrie uitleggen van dit materiaal zijn beter dan deze in waarvoor ze bedoeld zijn, en dit hoofdstuk is geschreven om naast ze te worden gelezen. Jay Alammars The Illustrated Transformer is de beste afbeelding van de dataflow die ooit is getekend. Harvard NLP's The Annotated Transformer is het paper uit 2017 met lopende code regel voor regel ertussen. Andrej Karpathy's Let's build GPT: from scratch, in code, spelled out bouwt hetzelfde model live in twee uur, en de ladder van ablations hierboven is dezelfde ruggengraat gemeten op een ander corpus. Voor de interpretability-vraag die dit hoofdstuk alleen aanraakt, zijn de primaire bronnen Elhage et al., A Mathematical Framework for Transformer Circuits (2021) en Olsson et al., In-context Learning and Induction Heads (2022), beide van Anthropic's interpretability-groep.
Referenties
Link naar de sectie: Referenties-
Hochreiter, S. en Schmidhuber, J. Long Short-Term Memory. Neural Computation 9(8), pp. 1735–1780 (1997). ↩
-
Sutskever, I., Vinyals, O. en Le, Q. V. Sequence to Sequence Learning with Neural Networks. arXiv:1409.3215 (2014). De encoder-decoder waarvan de enkele contextvector de bottleneck is. ↩
-
Bahdanau, D., Cho, K. en Bengio, Y. Neural Machine Translation by Jointly Learning to Align and Translate. arXiv:1409.0473 (2014). Attention, drie jaar vóór de transformer. ↩
-
Perplexity is de exponent van de gemiddelde cross-entropy per token, uit Hoofdstuk 8. Elk getal hier gebruikt dezelfde tokenizer en dezelfde validation split, wat de enige voorwaarde is waaronder twee perplexities überhaupt mogen worden vergeleken. ↩
-
Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł. en Polosukhin, I. Attention Is All You Need. arXiv:1706.03762 (2017). Sectie 3.2.1 is die ene zin over waar dit hoofdstuk een sectie aan besteedt om te meten. ↩
-
Shazeer, N., Mirhoseini, A., Maziarz, K., Davis, A., Le, Q., Hinton, G. en Dean, J. Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer. arXiv:1701.06538 (2017). ↩
-
Ba, J. L., Kiros, J. R. en Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). Geïntroduceerd en gemeten in Hoofdstuk 6; hier ongewijzigd gebruikt. ↩
-
Xiong, R., Yang, Y., He, D., Zheng, K., Zheng, S., Xing, C., Zhang, H., Lan, Y., Wang, L. en Liu, T.-Y. On Layer Normalization in the Transformer Architecture. arXiv:2002.04745 (2020). De gradient-analyse achter pre-norm, en het argument dat warmup een symptoom is. ↩
-
Su, J., Lu, Y., Pan, S., Murtadha, A., Wen, B. en Liu, Y. RoFormer: Enhanced Transformer with Rotary Position Embedding. arXiv:2104.09864 (2021). ↩
-
Press, O., Smith, N. A. en Lewis, M. Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation. arXiv:2108.12409 (2021). Het hierboven gereproduceerde extrapolatieresultaat. ↩
-
Dao, T., Fu, D. Y., Ermon, S., Rudra, A. en 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. en Sanghai, S. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). ↩
-
Levesque, H. J., Davis, E. en Morgenstern, L. The Winograd Schema Challenge. KR (2012). De constructie achter de animal / street-zin die elke attention-tutorial gebruikt. ↩