Startseite / Artikel / Attention-Mathematik Schritt für Schritt: von der Eingabematrix X bis Softmax und V

Attention-Mathematik Schritt für Schritt: von der Eingabematrix X bis Softmax und V

Einen Attention-Head von Hand durchrechnen: woher Q, K und V kommen, warum Scores durch die Wurzel aus d_k geteilt werden, wie Softmax sie gewichtet, dazu ein NumPy-Beispiel und Hinweise zum KV-Cache.

1565 Wörter

Jedes Mal, wenn ein Sprachmodell ein Token erzeugt, läuft immer wieder dieselbe Berechnung: Attention. Das ist keine Redewendung. Bei jedem Forward Pass, für jedes Token, in jeder Schicht wird dieselbe Arithmetik ausgeführt.

Die meisten Erklärungen bleiben bei der Intuition von „Queries, Keys und Values“ stehen, und die eigentliche Mathematik bleibt eine Blackbox. Dieser Text öffnet sie. Er zeigt, was die Eingabematrix wirklich ist, woher Q, K und V kommen, ein Zahlenbeispiel, klein genug zum Selbstrechnen, und warum das Ganze Self-Attention heißt. Danach sollte es machbar sein, einen einzelnen Attention-Head von Hand nachzuvollziehen.

Das Problem, das Attention löst

Nimm diesen Satz: „The animal didn't cross the street because it was tired“ („Das Tier überquerte die Straße nicht, weil es müde war“).

Wenn das Modell beim Token „it“ ankommt, muss es herausfinden, worauf sich das Wort bezieht. Auf das Tier? Auf die Straße? Das Wort allein verrät nichts, also muss die Antwort aus der umgebenden Sequenz kommen.

Attention löst das, indem jedes Token jedem anderen Token ein Gewicht gibt, eine Zahl dafür, wie stark es auf diese Position schauen soll, während es seine aktualisierte Repräsentation aufbaut. Für „it“ könnten die Gewichte so aussehen:

"it" → "animal"  : 0.65
"it" → "street"  : 0.20
"it" → "tired"   : 0.10
"it" → others    : 0.05

Niemand schreibt diese Zahlen von Hand. Sie ergeben sich aus einer gelernten Berechnung auf Basis dreier Vektoren, Query, Key und Value, und die gesamte Formel passt in eine Zeile:

Attention(Q, K, V) = softmax( QKᵀ / √dₖ ) · V

Alles Weitere packt nur diese Zeile aus.

Schritt 0: die Eingabematrix X

Bevor Q, K oder V existieren können, braucht es eine Eingabematrix, X.

Jedes Token in „The cat sat“ wird auf einen gelernten Embedding-Vektor abgebildet. Bei einer Embedding-Größe von d_model = 4 sieht das so aus:

The → [0.2, 0.5, 0.1, 0.7]
cat → [0.8, 0.3, 0.6, 0.2]
sat → [0.4, 0.9, 0.5, 0.1]

Staple die Vektoren Zeile für Zeile, und du erhältst X mit der Form [n_tokens, d_model].

Ein Detail übersieht man leicht: X ist nicht über alle Schichten gleich. Jede Attention-Schicht nimmt die Ausgabe der vorigen Schicht als ihr eigenes X. In der ersten Schicht ist X nur rohe Token- plus Positionsinformation. Weiter oben im Stack, etwa in Schicht 12 von GPT-3 mit seinen 96 Schichten, kodiert X bereits tiefe kontextuelle Bedeutung aus vielen Attention-Runden.

Schritt 1: Q, K und V berechnen

Niemand legt Queries, Keys und Values von Hand fest. Alle drei werden aus X über drei separate gelernte Gewichtsmatrizen berechnet:

Q = X · WQ
K = X · WK
V = X · WV

WQ, WK und WV sind Parametermatrizen, die nach dem Training eingefroren bleiben. Die Formen passen so zusammen:

X.shape  = [n, d_model]
WQ.shape = [d_model, d_k]   → Q.shape = [n, d_k]
WK.shape = [d_model, d_k]   → K.shape = [n, d_k]
WV.shape = [d_model, d_v]   → V.shape = [n, d_v]

Der Unterschied ist wichtig, deshalb hier als Tabelle:

Symbol Was es ist Hängt von der Eingabe ab?
WQ, WK, WV Gelernte Projektionsmatrizen Nein, nach dem Training fest
Q, K, V Berechnete Aktivierungen Ja, sie folgen dem aktuellen X

Deshalb bekommen „bank“ in „river bank“ (Flussufer) und „bank“ in „deposit at the bank“ (Einzahlung bei der Bank) völlig unterschiedliche Attention-Muster. Der umgebende Kontext erzeugt ein anderes X, das durch dieselben Gewichtsmatrizen läuft und als andere Q-, K- und V-Vektoren herauskommt.

Eine Suchmaschine ist ein praktisches Denkmodell:

  • Q ist das, wonach das Token sucht.
  • K ist das, was jedes Token über sich selbst anbietet.
  • V ist die Information, die bei einem Treffer zurückgegeben wird.

Q und K entscheiden, wohin geschaut wird. V entscheidet, was tatsächlich herausfließt.

Schritt 2: das Skalarprodukt QKᵀ

Nimm Q und multipliziere es mit dem transponierten K. Das ergibt eine quadratische [n, n]-Matrix roher Scores, deren Eintrag in Zeile i, Spalte j angibt, wie stark Token i auf Token j achtet. Bevor Zahlen eingesetzt werden, sieht sie so aus:

Keys
       cat   eats  fish
     ┌──────┬──────┬──────┐
Q cat│  ?   │  ?   │  ?   │
  eat│  ?   │  ?   │  ?   │
 fish│  ?   │  ?   │  ?   │
     └──────┴──────┴──────┘

Ein größeres Skalarprodukt bedeutet, dass die beiden Tokens füreinander relevanter sind.

Schritt 3: Skalierung mit √dₖ

Wenn d_k groß ist, explodieren rohe Skalarprodukte. Bei d_k = 512 sind Scores wie [180, 210, 95] normal. Gibst du sie in Softmax, kommt [0.00, 1.00, 0.00] heraus, eine völlig kollabierte Verteilung. Die Gradienten verschwinden dann, und das Training kommt zum Stillstand.

Die Division durch √d_k hält die Scores in einem vernünftigen Bereich. Dieser Division verdankt das Verfahren den Namen Scaled Dot-Product Attention.

Schritt 4: Softmax macht aus Scores Gewichte

Softmax läuft zeilenweise, und jede Zeile wird zu einer Wahrscheinlichkeitsverteilung über alle Tokens. Hier die vollständige Rechnung für die Scores [3.2, 4.0, 1.0, 6.2, 8.0]:

exp(3.2) =     24.53
exp(4.0) =     54.60
exp(1.0) =      2.72
exp(6.2) =    492.75
exp(8.0) =  2,980.96
         ──────────
Σ        =  3,555.56

Token 1:   24.53 / 3555.56 →  0.69%
Token 2:   54.60 / 3555.56 →  1.54%
Token 3:    2.72 / 3555.56 →  0.08%
Token 4:  492.75 / 3555.56 → 13.86%
Token 5: 2980.96 / 3555.56 → 83.83%  ← winner

Token 5 erhält 83,83 %, obwohl es Token 4 nur um 1,8 Punkte voraus ist. Diese nichtlineare Verstärkung erlaubt es dem Modell, sich klar auf die relevantesten Tokens festzulegen.

Stabilitätstipp: Zieh zuerst das Zeilenmaximum ab, wie in np.exp(x - x.max()), und teile erst dann. Ein 64-Bit-Float kann exp(800) nicht darstellen, aber nach der Verschiebung bleiben alle Exponenten im sicheren Bereich, und die resultierenden Wahrscheinlichkeiten sind mathematisch identisch.

Schritt 5: die gewichtete Summe mit V

Der letzte Schritt multipliziert die Gewichte mit V und summiert alles auf. Für „it“ heißt das:

output("it") =
    0.65 × V("animal")
  + 0.20 × V("street")
  + 0.10 × V("tired")
  + 0.05 × V(rest)

Heraus kommt ein neuer Vektor für „it“, der überwiegend Information aus „animal“ enthält, ein wenig aus „street“ und sehr wenig vom Rest. Diese kontextualisierte Repräsentation geht an die nächste Schicht.

Ein vollständiges Beispiel in NumPy

Der Satz lautet hier „cat eats fish“ mit d_model = 2:

import numpy as np

X  = np.array([[1, 2], [3, 1], [2, 4]], dtype=float)
WQ = np.array([[1, 0], [0, 1]], dtype=float)  # identity
WK = np.array([[1, 0], [0, 1]], dtype=float)  # identity
WV = np.array([[0, 1], [1, 0]], dtype=float)  # swaps dimensions

Q, K, V = X @ WQ, X @ WK, X @ WV

# Raw scores for "fish" (Q=[2,4]) against each key:
# fish→cat:  [2,4]·[1,2] = 10
# fish→eats: [2,4]·[3,1] = 10
# fish→fish: [2,4]·[2,4] = 20  ← self-attention dominates

scores = Q @ K.T / np.sqrt(2)
e = np.exp(scores - scores.max(-1, keepdims=True))
weights = e / e.sum(-1, keepdims=True)  # softmax row-wise

output = weights @ V
# output[fish] ≈ [3.997, 2.001] ≈ V(fish)
# fish self-attends at ~99.8% in this toy example

In einem echten trainierten Modell würden WQ und WK diese Symmetrie brechen und sinnvolle Attention zwischen Tokens erzeugen. Die Einheitsmatrizen dienen nur dazu, die Arithmetik lesbar zu halten.

Warum es Self-Attention heißt

Q, K und V stammen alle aus derselben Quellmatrix X. Jedes Token erzeugt aus seiner eigenen Repräsentation eine Query, einen Key und einen Value, und dann achtet jedes Token auf jedes andere in dieser einen Sequenz.

Das unterscheidet Self-Attention, wie in GPT und LLaMA, von Cross-Attention, wie in Encoder-Decoder-Modellen à la T5, wo Q aus einer Sequenz und K und V aus einer anderen stammen.

Praktische Hinweise aus dem Engineering

KV-Cache. K und V bei jedem Generierungsschritt für alle früheren Tokens neu zu berechnen, wäre O(n²). Ein KV-Cache speichert alle bisherigen K- und V-Vektoren und verwendet sie wieder, was die Kosten pro Schritt auf O(n) senkt, im Tausch gegen Speicher, der mit der Sequenzlänge wächst.

Head-Dimension. Die übliche Konfiguration ist d_k = d_model / num_heads. GPT-2 hat 768 / 12 = 64, GPT-3 hat 12288 / 96 = 128.

Grouped Query Attention (GQA). LLaMA-2, Mistral und Gemma lassen mehrere Query-Heads einen einzigen KV-Head teilen. Das senkt den Speicherbedarf des KV-Cache stark und ist ein wichtiger Grund, warum diese Modelle lange Kontexte auf Consumer-Hardware bewältigen.

Attention in einem Satz

Magie steckt nicht dahinter. Attention ist eine gelernte, differenzierbare, weich gewichtete Lookup-Tabelle, die für jede Eingabe neu berechnet wird und Repräsentationen liefert, die die gesamte relationale Struktur des Textes tragen.