Zum Inhalt springen
9/30Kapitel 9 von 30

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 tt vorherzusagen, braucht das Modell einen Vektor, der alles davor zusammenfasst, und nichts, was du gebaut hast, liefert einen solchen. Das embedding von token t1t-1 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 bauen

Von 1997 bis etwa 2017 war die Zusammenfassung ein rekurrenter Zustand: Halte einen Vektor h\mathbf{h} und aktualisiere ihn bei jedem token, ht=f(ht1,xt)\mathbf{h}_t = f(\mathbf{h}_{t-1}, \mathbf{x}_t). Feste Größe, variable Eingabe, exakt die richtige Form.

Er scheiterte auf drei Arten, und die Architektur dieses Kapitels beantwortet alle drei. Backpropagation über TT Schritte multipliziert TT Jacobi-Matrizen, sodass der gradient verschwindet oder explodiert — die Krankheit, die Kapitel 5 in einem einzelnen tanh\tanh-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: ht\mathbf{h}_t braucht ht1\mathbf{h}_{t-1}, 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 gibt

Die offensichtlichste Funktion, die aus einer variablen Anzahl von Vektoren einen Vektor zurückgibt, ist der Durchschnitt:

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

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 10310^{-3} mit einem Batch von 64. Die Perplexity wird auf dem zurückgehaltenen Split gemessen.4

ModellParameterValidierungs-Perplexity
nur der aktuelle token, überhaupt kein Kontext263.16859,71
plus der gleichmäßige Durchschnitt von allem davor263.168248,07
plus gelernte position embeddings279.552245,93
gleichmäßiger Durchschnitt zum token addiert, statt ihn zu ersetzen263.16860,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:

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

Floating-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:

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

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

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

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

Drei benannte Komponenten eines transformer sind jetzt auf dem Bildschirm. Das Dreieck ist die causal mask, erzwungen durch das Ziel: Wenn Position tt Position t+1t{+}1 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 -\infty zu setzen schickt sie exakt auf null und normalisiert, was übrig bleibt, sodass Maskieren und Normalisieren eine Operation sind. (Verwende -\infty, nicht -1e9: Es ist der Wert, den das Maskieren bedeutet, es überlebt eine Umwandlung nach float16 als -\infty, 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?

Sie können keine bloßen Parameter sein. Eine gelernte T×TT \times T-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 tt mit Position ii 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 ii für Position tt als xtxi\mathbf{x}_t \cdot \mathbf{x}_i, 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, qt=Wqxt\mathbf{q}_t = W_q\mathbf{x}_t, die query; und wodurch sie gefunden werden anbietet, ki=Wkxi\mathbf{k}_i = W_k\mathbf{x}_i, der key. Bewerte qtki\mathbf{q}_t \cdot \mathbf{k}_i, und die Symmetrie ist weg, weil WqWkW_q \neq W_k: Ein token kann eine Sache bewerben und nach einer anderen suchen.

Eine Sache ist noch falsch. Die gewichtete Summe lief über die xi\mathbf{x}_i 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, vi=Wvxi\mathbf{v}_i = W_v\mathbf{x}_i, den value, und summiere diese.

Die Formel ist jetzt Buchhaltung:

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

mit MM als causal mask, null auf und unter der Diagonale und -\infty darüber. In Code sind es dreißig Zeilen, zwanzig davon Shapes:

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

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

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

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ützt

Fast jede Erklärung von dk\sqrt{d_k} 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 q\mathbf{q} und k\mathbf{k} unabhängig sind, Mittelwert null und Varianz eins haben, dann hat jedes Produkt qjkjq_j k_j Varianz eins, und Varianzen unabhängiger Dinge addieren sich:

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

Die Scores haben also Standardabweichung dk\sqrt{d_k}. Gemessen über zwanzigtausend zufällige Paare:

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

Warum 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:

dkd_kgrößtes Gewicht, ungeteiltEntropieeffektive tokensgrößtes Gewicht, geteiltEntropieeffektive tokens
40,2052,94419,00,0813,75842,9
160,4381,6925,40,0753,84946,9
640,4890,8742,40,0853,67339,4
2560,99990,00071,00,1433,54734,7
10241,00000,00001,00,1323,64438,3

„Effektive tokens“ ist das Exponential der Entropie: über wie viele Positionen die Zeile wirklich mittelt. Ungeteilt, bei dk=256d_k = 256, 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 tanh\tanh gemessen hat. Ein softmax, der sich auf einen Eintrag festgelegt hat, hat fast keine Ableitung: Die Diagonale seiner Jacobi-Matrix ist wi(1wi)w_i(1-w_i), null an beiden Enden. Über zweitausend zufällige Zeilen:

dkd_kiwi(1wi)\sum_i w_i(1-w_i) ungeteiltgeteiltgesättigte Zeilen (größtes Gewicht über 0,99)
40,84270,95680,2 % → 0,0 %
640,29400,960917,9 % → 0,0 %
2560,14060,960949,1 % → 0,0 %
10240,06810,961170,4 % → 0,0 %

Bei dk=1024d_k = 1024 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-Breiteungeteiltgeteilt durch dk\sqrt{d_k}geteilt durch dkd_k
vier Heads, dk=32d_k = 3237,2938,0737,89
ein Head, dk=128d_k = 12848,5146,1045,99
ein Head, dk=256d_k = 25665,3747,53
ein Head, dk=512d_k = 51267,0649,15
ein Head, dk=1024d_k = 102476,6959,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 dk=32d_k = 32 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:

dkd_kScore-Std. bei Initnach 1500 Schritten, ungeteiltnach 1500 Schritten, geteiltgesättigte Zeilen, ungeteiltgeteilt
25610,49121,672,1391,9 %0,8 %
51215,13836,852,6698,7 %1,3 %
102421,155147,463,4499,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 dkd_k „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 spricht

Ein 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 dmodel/hd_{\text{model}}/h, konkateniere und mische mit einer weiteren Matrix WoW_o: 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:

ModellParameterValidierungs-Perplexity
gleichmäßiger Durchschnitt, addiert279.55260,45
ein Attention-Head, der den token ersetzt328.70455,47
ein Attention-Head, addiert328.70446,10
vier Heads statt einem345.21643,21
plus das Feed-Forward-Netzwerk476.92839,87
plus LayerNorm — der vollständige Block477.69638,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:

BreiteHeadsAttentionFeed-ForwardSumme pro Block
128465.664 (33,2 %)131.712 (66,6 %)197.888
768122.360.064 (33,3 %)4.722.432 (66,6 %)7.085.568
40963267.112.960 (33,3 %)134.238.208 (66,7 %)201.367.552

Zwei Drittel jedes transformer-Blocks sind das Feed-Forward-Netzwerk, in jeder Skalierung, weil Attention vier d×dd \times d-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 6

LayerNorm 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.

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

Schau, 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 3×1033 \times 10^{-3}:

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

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

Post-norm 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.

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 tt oder ii 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:

PositionenPerplexity bei 64bei 128bei 256
gar keine48,7952,6357,52
gelernte absolute embeddings38,63108,47181,94
feste Sinusoide42,9695,26152,25
RoPE44,1250,5284,84
ALiBi44,9543,5142,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 tit - i 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.

Zwei Tensoren in der Schicht haben Shape n×nn \times n, wobei nn die Anzahl der tokens ist: die Scores und die Gewichte nach dem softmax. Alles andere — jede Projektion, das ganze MLP — ist linear in nn.

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.

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

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

Die x4-Spalten sind das Verhältnis zur Zeile darüber, und eine Verdopplung von nn 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 hh query-Projektionen, aber eine einzelne Key- und Value-Projektion, die alle Heads teilen, und teilt diesen Cache durch hh. Grouped-query attention13 interpoliert: Heads werden gruppiert, jede Gruppe teilt einen Key und einen Value, sodass g=hg = h gewöhnliche Attention ist und g=1g = 1 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.

Das 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 dd und vierfacher Expansion: 4d2+4d4d^2 + 4d für Wq,Wk,Wv,WoW_q, W_k, W_v, W_o 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 d=768d = 768; 8d2+5d8d^2 + 5d für das MLP; 4d4d für zwei LayerNorms — 12d2+13d12d^2 + 13d, plus eine token-Tabelle von V×dV \times d und, für absolute Positionen, nctx×dn_{\text{ctx}} \times d. Für die Form von GPT-2 small — d=768d = 768, 12 Blöcke, ein Vokabular von 50.257, ein Kontext von 1024, wobei die Ausgabeschicht die embedding-Gewichte teilt:

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

Das 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.

Perplexity 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:

TEXT
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).

TEXT
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.

ModellParameterzurückgehaltennennt das andere Nomen
nur aktueller token5.7965,2 %5,2 %
gleichmäßiger kausaler Durchschnitt5.79627,9 %50,0 %
ein Head gelernter Attention18.08435,4 %64,6 %
vier Heads22.24475,0 %15,6 %
ein transformer-Block55.71692,7 %4,2 %
zwei transformer-Blöcke105.508100,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.

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

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

Block 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:

AdjektivBlock 2 auf Tierauf Straßeauf das AdjektivAntwort
müde, ängstlich, langsam, schwach0,0000,0001,000Tier
nass, breit, belebt, steil0,0000,4910,00–0,03Straß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:

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

1.8×1071.8 \times 10^{-7} auf Ausgaben, deren mittlere Magnitude 0,159 ist: dieselbe Arithmetik in anderer Reihenfolge, bei float32-Präzision.

Du 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?


Drei 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.

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

  2. 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.

  3. 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.

  4. 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.

  5. 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 dk\sqrt{d_k}, dessen Messung dieses Kapitel einen Abschnitt widmet.

  6. 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).

  7. 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.

  8. 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.

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

  10. 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.

  11. 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).

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

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

  14. 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.

Bereit, LIA die Wahl zu überlassen?

Bau mit jedem KI-Modell an einem Ort — starte heute kostenlos.