Attention und der Transformer-Block, aus einem Durchschnitt hergeleitet
Starte mit der billigsten Kontext-Zusammenfassung – dem Durchschnitt – und leite Attention aus ihrer Reparatur ab.
Auf dieser Seite
Du kommst hier mit einem Tokenizer aus Kapitel 7, einer embedding-Tabelle aus Kapitel 8 und dem dazugehörigen Ziel an: Gib den bisherigen tokens eine Wahrscheinlichkeit für den nächsten token.
Was fehlt, ist die Mitte. Um token vorherzusagen, braucht das Modell einen Vektor, der alles davor zusammenfasst, und nichts, was du gebaut hast, liefert einen solchen. Das embedding von token ist es nicht — das wäre ein Bigramm-Modell, und es kann nicht wissen, dass der Satz mit einer Frage begann. Eine Verkettung aller vorherigen embeddings ist es ebenfalls nicht: Ihre Anzahl ändert sich bei jedem Schritt, und eine feste Gewichtsmatrix kann keine Eingabe variabler Länge aufnehmen.
Also: ein Vektor fester Größe, der eine variable Anzahl von Vektoren zusammenfasst. Das ist das ganze Problem, und Attention ist das, was entsteht, wenn man es auf die faulste mögliche Weise löst und dann die zwei Dinge repariert, die dabei kaputtgehen.
Die Antwort, die das Feld hatte, und warum wir sie nicht bauen
Link zum Abschnitt: Die Antwort, die das Feld hatte, und warum wir sie nicht bauenVon 1997 bis etwa 2017 war die Zusammenfassung ein rekurrenter Zustand: Halte einen Vektor und aktualisiere ihn bei jedem token, . Feste Größe, variable Eingabe, exakt die richtige Form.
Er scheiterte auf drei Arten, und die Architektur dieses Kapitels beantwortet alle drei. Backpropagation über Schritte multipliziert Jacobi-Matrizen, sodass der gradient verschwindet oder explodiert — die Krankheit, die Kapitel 5 in einem einzelnen -Knoten gemessen hat. Das LSTM1 wurde genau dagegen entworfen und schob den nutzbaren Bereich von Dutzenden auf Hunderte Schritte, ohne an der Tatsache etwas zu ändern, dass Information von token 5 token 500 nur erreicht, wenn sie 495 sequenzielle Aktualisierungen überlebt. Die ganze Quelle musste in einen Vektor passen: Bei Sequence-to-Sequence-Übersetzung2 komprimiert ein Encoder die Eingabe in seinen Endzustand. Bahdanau, Cho und Bengio benannten diesen Flaschenhals und beheben ihn 2014, drei Jahre vor dem transformer, indem sie den Decoder eine gewichtete Summe aller Encoder-Zustände nehmen ließen, mit Gewichten, die er selbst berechnete.3 Alles unten ist diese Idee, von einer Sequenz auf sich selbst angewandt, mit gelöschter Rekurrenz. Und die Aktualisierung ist konstruktionsbedingt sequenziell: braucht , und eine GPU mit zehntausend Kernen kann damit nichts anfangen. Die Architektur, die gewonnen hat, ist nicht offensichtlich klüger; sie ist diejenige, deren teurer Schritt eine Matrixmultiplikation ist.
Der andere klassische induktive Bias, Convolution — einen kleinen Filter über die gesamte Eingabe schieben, sodass ein Feature, das irgendwo erkannt wird, überall erkannt wird — wird hier ebenfalls nicht gebaut; er ist für Bilder fast exakt richtig und wird an einen Vision-Kurs delegiert. Weder Rekurrenz noch Convolution tauchen nach dieser Seite wieder auf, weshalb keines von beiden ein Kapitel bekommt: Kapitel 1 versprach, dass Auslassungen erklärt und nicht stillschweigend gemacht werden.
Die billigste Zusammenfassung, die es gibt
Link zum Abschnitt: Die billigste Zusammenfassung, die es gibtDie offensichtlichste Funktion, die aus einer variablen Anzahl von Vektoren einen Vektor zurückgibt, ist der Durchschnitt:
Beliebig viele Eingaben, feste Ausgabegröße, differenzierbar, kostenlos. embedding-Tabelle plus dieser Durchschnitt plus eine lineare Schicht zum Vokabular ist ein vollständiges Sprachmodell in fünfzehn Zeilen. Es ist außerdem furchtbar, und wie es furchtbar ist, ist die ganze Herleitung.
Der Korpus unten ist ein Megabyte Shakespeare, 1.115.394 Zeichen, durch einen Byte-Level-BPE-Tokenizer der Art, die in Kapitel 7 gebaut wurde, mit einem Vokabular von 1024: 459.760 tokens mit je 2,43 Zeichen, 90/10 aufgeteilt. Jedes Modell ist 128 breit, sieht 128 tokens und trainiert 3000 Schritte AdamW bei mit einem Batch von 64. Die Perplexity wird auf dem zurückgehaltenen Split gemessen.4
| Modell | Parameter | Validierungs-Perplexity |
|---|---|---|
| nur der aktuelle token, überhaupt kein Kontext | 263.168 | 59,71 |
| plus der gleichmäßige Durchschnitt von allem davor | 263.168 | 248,07 |
| plus gelernte position embeddings | 279.552 | 245,93 |
| gleichmäßiger Durchschnitt zum token addiert, statt ihn zu ersetzen | 263.168 | 60,45 |
Lies die zweite Zeile zweimal. Den Kontext zu mitteln hilft nicht ein bisschen; es macht das Modell viermal schlechter, als den Kontext vollständig zu ignorieren. Zwei Gründe, beide beweisbar statt empirisch.
Der Durchschnitt kann Reihenfolge nicht sehen. Addition ist kommutativ, also lässt ein Mischen des Fensters die Zusammenfassung unverändert — nicht ungefähr:
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-Rauschen bei einer umgeordneten Summe: Die beiden Zusammenfassungen sind derselbe Vektor. Ein Modell, dessen einziger Blick auf den Kontext ein Durchschnitt ist, kann der Hund biss den Mann nicht von der Mann biss den Hund unterscheiden. Zeile drei beweist, dass sich das nicht dadurch beheben lässt, dass man den Eingaben Positionen hinzufügt — ein gelerntes position embedding auf jedem token vor dem Mitteln brachte 2,14 Punkte von 188. Die Positionen gehen in die Summe ein, und die Summe vergisst sie.
Und der Durchschnitt ertränkt die Gegenwart. An Position 100 ist der aktuelle token ein Hundertstel der Zusammenfassung. Dafür hast du bereits eine billige Reparatur: Behalte den token und addiere die Zusammenfassung dazu — eine Residual Connection, aus Kapitel 6, und Zeile vier zeigt, was sie tut. Ist die Verdünnung repariert, trägt der gleichmäßige Durchschnitt überhaupt nichts bei: 60,45 gegen eine Baseline von 59,71. Jeder token ist darin, gleich gewichtet, und gleiche Gewichtung ist dasselbe wie keine Information.
Das Problem ist nicht das Mitteln. Es sind die Gewichte.
Der Durchschnitt ist eine Matrixmultiplikation, und die Maske ist ein softmax
Link zum Abschnitt: Der Durchschnitt ist eine Matrixmultiplikation, und die Maske ist ein softmaxÜber ein wachsendes Präfix zu mitteln sieht wie eine Schleife aus. Es ist eine Multiplikation mit einer unteren Dreiecksmatrix, deren Zeilen sich zu eins summieren — und außerdem, exakt, ein 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.167Drei benannte Komponenten eines transformer sind jetzt auf dem Bildschirm. Das Dreieck ist die causal mask, erzwungen durch das Ziel: Wenn Position Position sehen könnte, läge die Antwort in der Eingabe — das Leck, das Kapitel 6 dich prüfen ließ, nur diesmal innerhalb der Architektur. Der softmax ist die Art, wie die Maske implementiert wird: Verbotene Einträge auf zu setzen schickt sie exakt auf null und normalisiert, was übrig bleibt, sodass Maskieren und Normalisieren eine Operation sind. (Verwende , nicht -1e9: Es ist der Wert, den das Maskieren bedeutet, es überlebt eine Umwandlung nach float16 als , und es erspart dir die Entscheidung, ob die Konstante, die du gewählt hast, groß genug für den Bereich ist, in dem du dich gerade befindest — womit die Floating-Point-Box aus Kapitel 2 eine Frage stellt, die du nicht beantworten musst.) Und die Scores sind der freie Parameter. Der gleichmäßige Durchschnitt ist das, was du bekommst, wenn jeder erlaubte Score dieselbe Zahl ist; setze beliebige Zahlen dorthin, und der softmax verwandelt sie in gültige Gewichte.
Der Rest dieses Kapitels ist eine Frage: Woher kommen diese Zahlen?
Query, Key, Value
Link zum Abschnitt: Query, Key, ValueSie können keine bloßen Parameter sein. Eine gelernte -Matrix wäre für jeden Satz identisch — sie könnte „vier tokens zurückschauen“ kodieren, aber niemals „schau auf das Nomen, auf das sich dieses Pronomen bezieht“. Das Gewicht, das Position mit Position verbindet, muss davon abhängen, was an beiden Positionen steht, denn Relevanz ist eine Relation, keine Eigenschaft: Das Wort es ist nicht an sich relevant, es ist für etwas relevant.
Die billigste Funktion aus zwei Vektoren, die eine Zahl zurückgibt, ist das Skalarprodukt aus Kapitel 1. Bewerte Position für Position als , und der Mechanismus funktioniert — schlecht, auf zwei Arten, die alles Weitere erzwingen. Das Skalarprodukt eines Vektors mit sich selbst ist seine quadrierte Norm, also würde jeder token hauptsächlich auf sich selbst attend. Und die Relation wäre symmetrisch: Wenn es stark auf Tier attends, dann attends Tier stark auf es, was für Sprache falsch ist, wo ein Adjektiv sein Nomen viel stärker braucht als das Nomen das Adjektiv.
Gib also jedem token zwei Rollen, als zwei gelernte lineare Abbildungen von ihm: wonach diese Position sucht, , die query; und wodurch sie gefunden werden anbietet, , der key. Bewerte , und die Symmetrie ist weg, weil : Ein token kann eine Sache bewerben und nach einer anderen suchen.
Eine Sache ist noch falsch. Die gewichtete Summe lief über die selbst, was erzwingt, dass das, was kopiert wird, dasselbe ist wie das, was gematcht wird. Matching will die Features, die einen token identifizieren; Kopieren will die Features, die downstream nützlich sind. Lerne also eine dritte Abbildung, , den value, und summiere diese.
Die Formel ist jetzt Buchhaltung:
mit als causal mask, null auf und unter der Diagonale und darüber. In Code sind es dreißig Zeilen, zwanzig davon 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 Bewerten, maskieren, normalisieren, mischen. Alles andere ist eine Projektion.
Die Division durch die Quadratwurzel, und wovor sie schützt
Link zum Abschnitt: Die Division durch die Quadratwurzel, und wovor sie schütztFast jede Erklärung von sagt „um zu verhindern, dass der softmax sättigt“, was stimmt und nichts erklärt. Das Argument sind zwei Zeilen Varianz aus Kapitel 2. Wenn die Einträge von und unabhängig sind, Mittelwert null und Varianz eins haben, dann hat jedes Produkt Varianz eins, und Varianzen unabhängiger Dinge addieren sich:
Die Scores haben also Standardabweichung . Gemessen über zwanzigtausend zufällige Paare:
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.000Warum das zählt: Der softmax ist skalenempfindlich auf eine Weise, wie es eine lineare Schicht nicht ist. Eine Verdopplung der Eingabe einer linearen Schicht verdoppelt ihre Ausgabe; Scores vor einem softmax mit zehn zu multiplizieren verwandelt eine weiche Mischung in eine harte Wahl. Eine Zeile mit 64 Scores, mit und ohne Division:
| größtes Gewicht, ungeteilt | Entropie | effektive tokens | größtes Gewicht, geteilt | Entropie | effektive 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 |
„Effektive tokens“ ist das Exponential der Entropie: über wie viele Positionen die Zeile wirklich mittelt. Ungeteilt, bei , attends ein frisch initialisierter Head auf exakt einen token von 64, ausgewählt durch nichts außer die Zufallsziehung.
Das ist vorwärts schlecht und rückwärts noch schlechter, in einer Form, die Kapitel 5 bereits an einem gemessen hat. Ein softmax, der sich auf einen Eintrag festgelegt hat, hat fast keine Ableitung: Die Diagonale seiner Jacobi-Matrix ist , null an beiden Enden. Über zweitausend zufällige Zeilen:
| ungeteilt | geteilt | gesättigte Zeilen (größtes Gewicht über 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 % |
Bei sind sieben von zehn Zeilen eingefroren, bevor das Training beginnt, und ein Head, der eingefroren startet, kann nicht lernen, worauf er schauen soll. Geteilt ist die Größe bei jeder Breite flach bei 0,96, und nichts sättigt.
Jetzt der Teil, den niemand veröffentlicht: Ändert es die finale Perplexity? Lösche die Division und trainiere, bei vier Head-Breiten:
| Head-Breite | ungeteilt | geteilt durch | geteilt durch |
|---|---|---|---|
| vier Heads, | 37,29 | 38,07 | 37,89 |
| ein Head, | 48,51 | 46,10 | 45,99 |
| ein Head, | 65,37 | 47,53 | — |
| ein Head, | 67,06 | 49,15 | — |
| ein Head, | 76,69 | 59,17 | — |
Die ersten zwei Zeilen stammen aus dem 3000-Schritt-Budget oben; die letzten drei aus einem kürzeren Lauf — 1500 Schritte, Batch von 32, ein Head, keine Normalisierung vor den Projektionen — mit beiden Varianten unter identischen Einstellungen.
Bei ist die Division nichts wert, und der Lauf ohne sie liegt minimal vorn. Das ist keine Lizenz, sie fallen zu lassen, denn bei 256 ist sie 18 Perplexity-Punkte wert und bei 1024 sind es 17. Der Mechanismus ist in den Scores selbst sichtbar:
| Score-Std. bei Init | nach 1500 Schritten, ungeteilt | nach 1500 Schritten, geteilt | gesättigte Zeilen, ungeteilt | geteilt | |
|---|---|---|---|---|---|
| 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 % |
Der ungeteilte Head erholt sich nicht. Er läuft davon: Die Standardabweichung seiner Scores steigt von 21 bei der Initialisierung auf 5147, die Attention-Entropie fällt auf null, und 99,9 % der Zeilen legen mehr als 0,99 ihres Gewichts auf einen einzigen token. Sobald ein Head ein harter Selektor ist, ist sein gradient nahezu null, und nichts zieht ihn zurück, also ist der Kollaps stabil. Der geteilte Head sitzt nach demselben Training bei einer Score-Standardabweichung von 3,44, also bei einer weichen Mischung, die noch verändert werden kann.
Vaswani et al. sagen genau das und nicht mehr — sie vermuten, dass die Produkte für große Werte von „betragsmäßig groß werden“, und teilen.5 Das Wort groß trägt die Last, und die Tabellen zeigen, wo groß beginnt: nichts bei 32, alles ab 256.
Mehr als eine Meinung, und die zwei Drittel, über die niemand spricht
Link zum Abschnitt: Mehr als eine Meinung, und die zwei Drittel, über die niemand sprichtEin Head ist eine softmax-Zeile pro Position, also hält er eine Antwort auf „was ist hier relevant“. Das Wort nach der in das Tier, das die nasse Straße überquerte vorherzusagen braucht die syntaktische Leerstelle, das Subjekt und den vorherigen token gleichzeitig, und eine Wahrscheinlichkeitsverteilung kann nicht an drei Stellen konzentriert sein. Lasse also mehrere Heads parallel laufen, jeweils mit Breite , konkateniere und mische mit einer weiteren Matrix : Du hast die Breite partitioniert, nicht erhöht.
Attention tut außerdem genau eine Sache — sie bewegt Information zwischen Positionen. Jede Operation im Code oben ist entlang der Feature-Achse linear, und Kapitel 5 hat bewiesen, was ein Stapel linearer Abbildungen ist. Deshalb trägt jeder Block zusätzlich ein kleines MLP, das auf jede Position unabhängig angewandt wird, die Breite um vier erweitert und wieder zurückkommt, mit einem GELU in der Mitte. Die Arbeitsteilung solltest du dir merken: Attention mischt über Positionen, das Feed-Forward-Netzwerk rechnet innerhalb einer Position.
Die vollständige Leiter, jede Zeile fügt der Zeile darüber ein Stück hinzu:
| Modell | Parameter | Validierungs-Perplexity |
|---|---|---|
| gleichmäßiger Durchschnitt, addiert | 279.552 | 60,45 |
| ein Attention-Head, der den token ersetzt | 328.704 | 55,47 |
| ein Attention-Head, addiert | 328.704 | 46,10 |
| vier Heads statt einem | 345.216 | 43,21 |
| plus das Feed-Forward-Netzwerk | 476.928 | 39,87 |
| plus LayerNorm — der vollständige Block | 477.696 | 38,07 |
Gelernte Gewichte schlagen gleichmäßige um 14 Perplexity-Punkte, was das gesamte Argument dieses Kapitels in einer Zeile ist. Vier Heads kaufen weitere 3 für 16.512 zusätzliche Parameter. Und derselbe Head ist addiert 9 Punkte mehr wert als ersetzend: Attention bringt Information herein, sie entscheidet nicht, was eine Position ist.
Jetzt, wo die Parameter tatsächlich sitzen, was Leute überrascht, die nur das Diagramm gesehen haben:
| Breite | Heads | Attention | Feed-Forward | Summe pro 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 |
Zwei Drittel jedes transformer-Blocks sind das Feed-Forward-Netzwerk, in jeder Skalierung, weil Attention vier -Matrizen hat und das MLP das Äquivalent von acht. Was immer ein Modell weiß: Die meisten Parameter, die es halten, sitzen im MLP pro Position.
Residuals und LayerNorm, geerbt aus Kapitel 6
Link zum Abschnitt: Residuals und LayerNorm, geerbt aus Kapitel 6LayerNorm wurde in Kapitel 6 gebaut und gemessen, und dieses Kapitel nutzt sie so, wie sie dort zurückgelassen wurde; Residual Connections wurden dort benannt und ablatiert und werden hier gebaut. Die „addiert, nicht ersetzend“-Zeilen oben sind Residual Connections, 188 Perplexity-Punkte wert für den Durchschnitt und 9 für einen Head. LayerNorm7 normalisiert jedes Beispiel über seine Features, und Kapitel 6 gab die Gründe, warum sie hier überlebt hat und nicht BatchNorm — keine Abhängigkeit vom Batch, keine laufenden Statistiken, identisch in Training und Inferenz, gleichgültig gegenüber Sequenzlänge — jeder einzelne davon wird zur Anforderung, wenn du für einen einzelnen User jeweils einen token generierst, wo Kapitel 13 landet. Sie kostet 768 Parameter und kauft 1,8 Perplexity-Punkte.
class Block(nn.Module):
def forward(self, x):
x = x + self.att(self.ln1(x))
x = x + self.ff(self.ln2(x))
return xSchau, wo die Normalisierung sitzt: am Eingang jeder Sub-Schicht, während der Residual-Pfad von Eingang zu Ausgang nie normalisiert wird. Das ist pre-norm. Das Paper von 2017 macht das Gegenteil, x = LayerNorm(x + Att(x)) — post-norm, was eine LayerNorm auf den Residual-Pfad selbst legt.
Xiong et al. erklärten den Unterschied über den gradient bei der Initialisierung, der in einem post-norm-Netzwerk schlecht mit der Tiefe skaliert — der Grund, warum der ursprüngliche transformer überhaupt ein Learning-Rate-Warmup brauchte, um zu trainieren.8 Zwölf Blöcke, 1000 Schritte, 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 ohne Warmup ist achtmal schlechter, und post-norm mit Warmup entspricht pre-norm exakt. Warmup ist hier keine allgemein gute Praxis; es ist ein Patch für eine bestimmte Anordnung der Normalisierung, und das Verschieben der LayerNorm entfernt die Notwendigkeit dafür. Deshalb ist praktisch jedes Modell seit 2019 pre-norm, und deshalb sollte das Diagramm von 2017 als Geschichte gelesen werden, nicht als Spezifikation.
Wo ist ein token?
Link zum Abschnitt: Wo ist ein token?Lösche die position embeddings, und das Modell trainiert trotzdem; es kann nur nicht erkennen, wo irgendetwas ist, und das ist eine Symmetrie statt eines Trainingsfehlers. Nichts im Attention-Score erwähnt oder selbst, also permutiert eine Permutation der Eingabe die Ausgabe: Self-Attention ist permutationsäquivariant. Es ist die Ordnungsblindheit des Durchschnitts in besserer Verkleidung — die causal mask stellt etwas Ordnung wieder her, weil jede Position ein anderes Präfix sieht, aber innerhalb eines Präfixes sind alle Reihenfolgen gleich.
Vier Wege, Position einzubringen, trainiert auf 64-token-Fenstern und evaluiert bei 64, 128 und 256 — jenseits jeder Länge, die sie gesehen haben:
| Positionen | Perplexity bei 64 | bei 128 | bei 256 |
|---|---|---|---|
| gar keine | 48,79 | 52,63 | 57,52 |
| gelernte absolute embeddings | 38,63 | 108,47 | 181,94 |
| feste Sinusoide | 42,96 | 95,26 | 152,25 |
| RoPE | 44,12 | 50,52 | 84,84 |
| ALiBi | 44,95 | 43,51 | 42,49 |
Gelernte absolute embeddings — ein Vektor pro Position, zum token addiert — gewinnen bei der trainierten Länge und stürzen dann ab, weil Position 100 nie in einem Batch war und ihr embedding noch der Zufallsvektor ist, als der es begann. Sinusoide, die ursprüngliche Wahl, werden aus Sinus und Kosinus bei geometrisch verteilten Frequenzen berechnet statt gelernt; das Paper von 2017 hoffte, dass das extrapoliert, und die Tabelle sagt, dass es das nicht tut — die Funktion ist bei Position 200 definiert, aber das Modell hat nie gelernt, sie dort zu lesen. RoPE9 fügt nichts hinzu und rotiert stattdessen query und key um einen Winkel proportional zur Position, in zweidimensionalen Scheiben; da beide Seiten eines Skalarprodukts gleich zu rotieren es unverändert lässt, hängt der Score am Ende nur von ab, Position wird also gratis relativ, und es gibt keine Tabelle, die ausgehen kann. Es degradiert, aber es degradiert. ALiBi10 ist das einfachste und seltsamste Ergebnis hier: eine lineare Strafe auf den Score proportional zur Distanz, mit einer anderen Steigung pro Head. Seine Perplexity verbessert sich, wenn das Fenster über die Trainingslänge hinaus wächst, von 44,95 auf 42,49, weil die Strafe für jede Distanz definiert ist und jeder Head weiter tut, wofür er trainiert wurde.
Die Lehre überlebt die Tabelle: Eine Architektur, die etwas nicht darstellen kann, ist ein anderes Problem als eine, die diesen Bereich nie gelernt hat, und das zweite ist das, was beißt. Es ist auch die Maschinerie hinter jeder „wir haben den Kontext auf 128K erweitert“-Ankündigung — das sind fast immer Reskalierungen einer Rotary Encoding, und sie sind der Grund, warum Kapitel 16 sagt, dass sich das Kontextlimit verschiebt, statt zu verschwinden.
Dropout wird auf dieselbe Weise geerbt: Es erscheint auf den Attention-Gewichten nach dem softmax, auf der Ausgabe jeder Sub-Schicht vor der Residual-Addition und auf der embedding-Summe, und tut exakt das, was Kapitel 6 beschrieben hat. In großen Pretraining-Läufen wird es oft auf null gesetzt, weil ein Modell, das jeden token einmal sieht, nicht in der Lage ist, zu overfitten.
Was es kostet
Link zum Abschnitt: Was es kostetZwei Tensoren in der Schicht haben Shape , wobei die Anzahl der tokens ist: die Scores und die Gewichte nach dem softmax. Alles andere — jede Projektion, das ganze MLP — ist linear in .
Eine Attention-Schicht, 512 breit, 8 Heads, Batch von eins, float32, auf einer Laptop-GPU. Lies die beiden Millisekunden-Spalten nur für ihre Verhältnisse: Es ist Wall-Clock auf einer 8-GB-Laptop-Karte, die von 1.785 MHz auf unter 300 MHz drosselt, wenn sie heiß wird, also kommt ein kalter Lauf desselben Codes sieben- bis zehnmal schneller zurück und ein ausgelasteter noch langsamer. Die Megabyte-Spalten sind Allocator-Byte-Zählungen und bewegen sich nicht.
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.87Die x4-Spalten sind das Verhältnis zur Zeile darüber, und eine Verdopplung von konvergiert für Zeit und Speicher exakt gegen 4 — 3,91 im letzten Schritt gegenüber theoretischen 4. Die Projektionsspalte ist die Kontrolle: 4,0 ms bei 1024 tokens zu 40,1 ms bei 8192, ein Faktor zehn für einen Faktor acht. Linear, wie versprochen.
Dann die letzte Zeile. Eine Attention-Schicht, eine Sequenz, kein Modell drumherum, läuft auf einer 8-GB-GPU bei 16.384 tokens aus dem Speicher — allein die Score-Matrix wäre 8 GB groß, nämlich 8 Heads mal 16.384 mal 16.384 mal 4 Byte. Nicht das Modell; ein Zwischentensor in einer Schicht.
Das ist die physikalische Tatsache unter drei späteren Kapiteln. Sie ist der Grund, warum ein context window überhaupt ein Limit hat, das Kapitel 16 in einen Preis verwandelt. Sie ist der Grund, warum FlashAttention existiert, das dasselbe Ergebnis in Tiles berechnet, ohne die Matrix je zu speichern — eine Speicheroptimierung, bevor es eine Geschwindigkeitsoptimierung ist.11 Und sie ist die Arithmetik hinter dem Preis eines langen prompt, den Kapitel 24 in einer agent-Schleife bezahlt — getrennt von der anderen Erkenntnis dieses Kapitels, dass ein Modell langen Kontext auch schlechter nutzt, was es misst und dieser Formel nicht anlastet.
Details anzeigen
Die zwei Cache-schrumpfenden Varianten, hier benannt und in Kapitel 13 bezahlt.
Generierung cached die Keys und Values der bereits verarbeiteten tokens — ein Key und ein Value pro token, pro Head pro Schicht. Multi-query attention12 behält query-Projektionen, aber eine einzelne Key- und Value-Projektion, die alle Heads teilen, und teilt diesen Cache durch . Grouped-query attention13 interpoliert: Heads werden gruppiert, jede Gruppe teilt einen Key und einen Value, sodass gewöhnliche Attention ist und multi-query. Fast jedes offene Modell seit 2023 nutzt es mit 4 oder 8 Gruppen. Keines von beiden existiert wegen Qualität; beide existieren wegen der Größe dieses Cache, und Kapitel 13 macht die Arithmetik, die daraus „welches Modell passt in deine GPU“ macht.
Zwei Formen, und die Größe von einer
Link zum Abschnitt: Zwei Formen, und die Größe von einerDas Paper von 2017 beschreibt einen Encoder-Decoder: ein Stack, der die Quelle mit unmaskierter Attention liest, ein zweiter, der das Ziel kausal generiert, und eine dritte Art von Attention in der Mitte, wo die queries des Decoders auf die keys des Encoders treffen. Das ist richtig für Übersetzung, wo Eingabe und Ausgabe zwei Sequenzen sind.
Gewonnen hat die decoder-only-Hälfte — ein Stack, durchgehend kausal, Eingabe und Ausgabe in derselben Sequenz — und der Grund ist nicht Eleganz. „Den nächsten token vorhersagen“ läuft auf jedem Text, also ist das Trainingsset das Internet statt ein Parallelkorpus, und alles wird zu dieser einen Aufgabe: Eine Übersetzung ist ein Dokument mit Quelle und dann Ziel, eine Frage und ihre Antwort sind ein Dokument, eine Unterhaltung mit einem tool calling in der Mitte ist ein Dokument. Kapitel 11 handelt davon, wie das letzte hergestellt wird. Encoder sind nicht verschwunden — einer sieht die gesamte Eingabe auf einmal, was du willst, wenn die Aufgabe darin besteht, einen Text zu repräsentieren, statt ihn fortzusetzen, und deshalb kommen die retrieval embeddings aus Kapitel 19 von Encodern und nicht von dem Modell, das chattet.
Ist der Block definiert, ist Modellgröße Arithmetik. Pro Block, mit Breite und vierfacher Expansion: für mit Biases auf allen vier, wie GPT-2 sie hat — die Tabelle oben lässt den Bias bei drei davon weg, daher 2.304 weniger pro Block bei ; für das MLP; für zwei LayerNorms — , plus eine token-Tabelle von und, für absolute Positionen, . Für die Form von GPT-2 small — , 12 Blöcke, ein Vokabular von 50.257, ein Kontext von 1024, wobei die Ausgabeschicht die embedding-Gewichte teilt:
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,808Das ist die veröffentlichte Größe dieses Modells. Die Formel ist keine Näherung; sie ist das Modell. Beachte außerdem, dass fast ein Drittel eines kleinen Modells die embedding-Tabelle ist, weshalb die Vokabulargröße eine architektonische Entscheidung ist und keine Preprocessing-Entscheidung — der Trade-off, den Kapitel 7 aufgebaut hat.
Worauf ein Head tatsächlich schaut
Link zum Abschnitt: Worauf ein Head tatsächlich schautPerplexity ist eine Zahl über einen Korpus. Was ein Head tut, ist eine andere Frage, und ein Modell, das auf einem Megabyte Shakespeare trainiert wurde, ist dafür das falsche Instrument: Das Ehrliche über die Attention-Map eines 500.000-Parameter-Modells ist, dass sie größtenteils nicht interpretierbar ist. Also: eine Sprache, in der die Frage eine richtige Antwort hat.
Die klassische Illustration ist das Tier überquerte die Straße nicht, weil es zu müde war, wobei es das Tier ist, gegenüber …weil sie zu nass war, wo ein Wort den Referenten zur Straße verschiebt. Das sind Winograd-Schemata14 — Satzpaare, identisch bis auf ein Wort, wobei dieses Wort entscheidet, worauf sich ein Pronomen bezieht.
Sie sind außerdem durch Schummeln lösbar, und das ist der Teil, den Tutorials auslassen. Wenn die zwei Kandidaten ein Tier und ein Ort sind, identifizieren müde und nass den Referenten über Kategorie, und ein Modell, das nur weiß, welche Wörter vorhanden sind, bekommt es richtig, ohne irgendetwas über Reihenfolge zu wissen. Gemessen auf dieser Version der Aufgabe, mit zurückgehaltenen Tier/Ort-Paaren:
uniform causal average held-out referent accuracy 100.0 %
one transformer block held-out referent accuracy 91.7 %Der Bag of Words schlägt den transformer. Jede Demonstration, die auf diesem Satz aufbaut, beweist nichts über Attention.
Schließe also das Loch: Ziehe beide Kandidaten aus einem Pool von sechzehn Nomen, von denen jedes in jedem Slot erscheinen kann, und teile die Adjektive nach Rolle statt Kategorie — vier, die es zum Überquerenden machen (müde, ängstlich, langsam, schwach), vier, die es zum Überquerten machen (nass, breit, belebt, steil).
the {x} did not cross the {y} because it was too {adj} , so the {ref} waited .Trainiere als gewöhnlichen Next-token-Predictor, score eine Position — das Wort nach also die — und baue das zurückgehaltene Set aus Nomenpaaren, deren umgekehrte Reihenfolge im Training war, sodass alles, was weiß, welche zwei Nomen vorhanden sind, aber nicht, welches zuerst kam, rückwärts antworten muss.
| Modell | Parameter | zurückgehalten | nennt das andere Nomen |
|---|---|---|---|
| nur aktueller token | 5.796 | 5,2 % | 5,2 % |
| gleichmäßiger kausaler Durchschnitt | 5.796 | 27,9 % | 50,0 % |
| ein Head gelernter Attention | 18.084 | 35,4 % | 64,6 % |
| vier Heads | 22.244 | 75,0 % | 15,6 % |
| ein transformer-Block | 55.716 | 92,7 % | 4,2 % |
| zwei transformer-Blöcke | 105.508 | 100,0 % | 0,0 % |
Zufall unter den zwei vorhandenen Nomen liegt bei 50 %. Der gleichmäßige Durchschnitt landet bei 27,9 % und antwortet genau die Hälfte der Zeit mit dem falschen Nomen des Paars — die Signatur von etwas, das weiß, welche Wörter da sind, und nichts über ihre Reihenfolge, genau wie der Shuffle-Test drei Abschnitte zuvor vorhergesagt hat.
Jetzt die Map: die Attention an der Position, die den Referenten nennen muss, gemittelt über die vier Heads jedes Blocks, für die zwei Sätze, die sich durch ein Wort unterscheiden. Ein gleichmäßiger Durchschnitt würde 0,067 auf jeden der fünfzehn sichtbaren tokens legen.
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 ist in beiden Sätzen identisch — 0,70 auf dem ersten Nomen, egal welches Adjektiv kommt. Das ist kein Fehler, sondern ein Beweis: In der ersten Schicht ist die query an einer Position eine Funktion des eigenen token und Index dieser Position, und die an Position 14 ist in beiden Sätzen derselbe token. Ein First-Layer-Head kann nicht von einem Wort abhängen, das er noch nicht geholt hat. Also tut Block 1 das einzig Nützliche, was verfügbar ist, und zieht das erste Nomen nach vorn.
Block 2 ist, wo sich die Sätze trennen, und dieselbe Zeile über alle acht Adjektive zeigt die Regel, die das Modell gefunden hat:
| Adjektiv | Block 2 auf Tier | auf Straße | auf das Adjektiv | Antwort |
|---|---|---|---|---|
| müde, ängstlich, langsam, schwach | 0,000 | 0,000 | 1,000 | Tier |
| nass, breit, belebt, steil | 0,000 | 0,491 | 0,00–0,03 | Straße |
Bei einem Überquerer-Adjektiv legt der zweite Block sein gesamtes Gewicht auf das Adjektiv, weil die Antwort bereits im Residual Stream liegt — Block 1 hat sie dort hingelegt — und alles, was er braucht, ist Bestätigung. Bei einem Überquert-Adjektiv geht er stattdessen los und holt das andere Nomen. Das ist ein Two-Hop-Circuit: Ein Head bewegt einen Kandidaten nach vorn, ein Head in einer späteren Schicht liest einen token, der entscheidet, ob er behalten wird. Komposition über Schichten ist der Mechanismus, und deshalb erreichte ein Block 92,7 % und zwei 100 %.
Es ist auch die Form des bestdokumentierten Circuits in echten Modellen. Induction heads — ein Previous-token-Head, der einen Head in der nächsten Schicht füttert, der das Muster [A][B] … [A] → [B] vervollständigt — sind das, was Anthropics Interpretability-Arbeit hinter einem großen Teil von in-context learning identifiziert, und sie bilden sich zu einem identifizierbaren Moment während des Pretrainings. Dieses Kapitel versucht diese Analyse nicht: Sie wird delegiert, mit beiden Papers in den Referenzen, weil Circuits aus einem echten Modell herauszulesen ein Forschungsfeld ist und kein Abschnitt.
Zum Schluss die Implementierung. Die dreißig Zeilen oben, mit aus PyTorchs eigenen Gewichten kopierten Gewichten:
ours vs nn.MultiheadAttention max |diff| = 1.7881393432617188e-07
ours vs F.scaled_dot_product_attention max |diff| = 1.7881393432617188e-07auf Ausgaben, deren mittlere Magnitude 0,159 ist: dieselbe Arithmetik in anderer Reihenfolge, bei float32-Präzision.
Wohin es als Nächstes geht
Link zum Abschnitt: Wohin es als Nächstes gehtDu hast die Architektur, aus der jedes Modell im Rest dieses Kurses gebaut ist, und sie ist kleiner als ihr Ruf: ein gewichteter Durchschnitt, dessen Gewichte gelernt werden, ein MLP pro Position, das zwei Drittel der Parameter hält, zwei Normalisierungen und zwei Additionen, gestapelt.
Was du nicht hast, ist ein Modell, das irgendetwas weiß, und Stapeln allein wird das nicht beheben. Zwei Blöcke auf diesem Korpus erreichen eine Trainings-Perplexity von 14,49 und eine Validierungs-Perplexity von 40,57, gegen 18,77 und 38,07 bei einem Block — mehr Kapazität, besser auf dem, was es gesehen hat, schlechter auf dem, was es nicht gesehen hat, also die Tabelle aus Kapitel 6 mit einem transformer darin. Der Abstand zwischen diesem Modell und denen, mit denen Kapitel 14 bis 30 sprechen, ist nicht architektonisch. Es ist derselbe Block, öfter wiederholt, über immens viel mehr Text.
Damit wird es ein Buchhaltungsproblem, und die Buchhaltung ist seltsamer, als sie aussieht. Wie viel Text, und wo bekommt ihn überhaupt jemand her? Wie viel Arithmetik, und wie schätzt man sie bevor das Geld ausgegeben ist? Ist es bei einem festen Budget besser, das Modell größer zu machen oder ihm mehr Daten zu zeigen — und gibt es eine richtige Antwort oder nur eine Mode? Kapitel 10 beantwortet alle drei durch Messung und setzt einen Preis auf die billigste nützliche Form der Frage: Was kostet es heute, ein Modell wie GPT-2 von null zu trainieren?
Quellen und Methode
Link zum Abschnitt: Quellen und MethodeDrei Erklärungen dieses Materials sind in dem, wofür sie gemacht sind, besser als diese, und dieses Kapitel ist dazu geschrieben, zusammen mit ihnen gelesen zu werden. Jay Alammars The Illustrated Transformer ist das beste Bild des Datenflusses, das je gezeichnet wurde. Harvard NLPs The Annotated Transformer ist das Paper von 2017 mit laufendem Code, Zeile für Zeile dazwischen. Andrej Karpathys Let's build GPT: from scratch, in code, spelled out baut dasselbe Modell live in zwei Stunden, und die Ablationsleiter oben ist dasselbe Rückgrat, gemessen auf einem anderen Korpus. Für die Interpretability-Frage, die dieses Kapitel nur berührt, sind die Primärquellen Elhage et al., A Mathematical Framework for Transformer Circuits (2021) und Olsson et al., In-context Learning and Induction Heads (2022), beide aus Anthropics Interpretability-Gruppe.
Referenzen
Link zum Abschnitt: Referenzen-
Hochreiter, S. und Schmidhuber, J. Long Short-Term Memory. Neural Computation 9(8), S. 1735–1780 (1997). ↩
-
Sutskever, I., Vinyals, O. und Le, Q. V. Sequence to Sequence Learning with Neural Networks. arXiv:1409.3215 (2014). Der Encoder-Decoder, dessen einzelner context vector der Flaschenhals ist. ↩
-
Bahdanau, D., Cho, K. und Bengio, Y. Neural Machine Translation by Jointly Learning to Align and Translate. arXiv:1409.0473 (2014). Attention, drei Jahre vor dem transformer. ↩
-
Perplexity ist das Exponential der mittleren Cross-Entropy pro token, aus Kapitel 8. Jede Zahl hier verwendet denselben Tokenizer und denselben Validierungssplit, was die einzige Bedingung ist, unter der zwei Perplexities überhaupt verglichen werden dürfen. ↩
-
Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł. und Polosukhin, I. Attention Is All You Need. arXiv:1706.03762 (2017). Abschnitt 3.2.1 ist der eine Satz über , dessen Messung dieses Kapitel einen Abschnitt widmet. ↩
-
Shazeer, N., Mirhoseini, A., Maziarz, K., Davis, A., Le, Q., Hinton, G. und Dean, J. Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer. arXiv:1701.06538 (2017). ↩
-
Ba, J. L., Kiros, J. R. und Hinton, G. E. Layer Normalization. arXiv:1607.06450 (2016). In Kapitel 6 eingeführt und gemessen; hier unverändert verwendet. ↩
-
Xiong, R., Yang, Y., He, D., Zheng, K., Zheng, S., Xing, C., Zhang, H., Lan, Y., Wang, L. und Liu, T.-Y. On Layer Normalization in the Transformer Architecture. arXiv:2002.04745 (2020). Die gradient-Analyse hinter pre-norm und das Argument, dass Warmup ein Symptom ist. ↩
-
Su, J., Lu, Y., Pan, S., Murtadha, A., Wen, B. und Liu, Y. RoFormer: Enhanced Transformer with Rotary Position Embedding. arXiv:2104.09864 (2021). ↩
-
Press, O., Smith, N. A. und Lewis, M. Train Short, Test Long: Attention with Linear Biases Enables Input Length Extrapolation. arXiv:2108.12409 (2021). Das oben reproduzierte Extrapolationsergebnis. ↩
-
Dao, T., Fu, D. Y., Ermon, S., Rudra, A. und 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. und Sanghai, S. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). ↩
-
Levesque, H. J., Davis, E. und Morgenstern, L. The Winograd Schema Challenge. KR (2012). Die Konstruktion hinter dem Tier / Straße-Satz, den jedes Attention-Tutorial verwendet. ↩