Wstęp
Mechanizm attention jest sercem architektury Transformer i podstawą działania wszystkich nowoczesnych dużych modeli językowych. Teoria stojąca za nim bywa abstrakcyjna, dlatego najlepszym sposobem na jej zrozumienie jest prześledzenie konkretnego przykładu obliczeniowego.
W tym wpisie przechodzimy przez pełny przebieg obliczeń na trzywyrazowym zdaniu — od embeddingów aż po rozkład prawdopodobieństwa następnego tokenu — pokazując wszystkie macierze po drodze.
Uwaga: dla przejrzystości pomijamy positional encoding oraz końcową projekcję wyjściową \(W_O\). W rzeczywistych Transformerach oba elementy są niezbędne: positional encoding koduje kolejność tokenów, a \(W_O\) rzutuje sklejone wyjścia z wielu głowic z powrotem do przestrzeni modelu.
Przykład: „cat chases mouse”
Rozważmy zdanie złożone z trzech tokenów: cat chases mouse, oraz słownik o rozmiarze 5:
- token 0: „cat”
- token 1: „chases”
- token 2: „mouse”
- token 3: „quickly”
- token 4: „sleeping”
Krok 1: Embeddingi tokenów
Każdy token reprezentujemy wektorem o wymiarze \(d_{model} = 2\) (w rzeczywistych modelach zazwyczaj 512, 768 lub więcej). Cały słownik mieści się więc w macierzy \(2 \times 5\), której kolumny są embeddingami kolejnych tokenów:
Nasze zdanie to tokeny 0, 1 i 2, więc jego macierz embeddingów \(E\) powstaje przez wybranie trzech pierwszych kolumn \(W_{vocab}\) i zapisanie ich jako wierszy:
gdzie:
- \(E[0] = [1.0,\ 0.0]\) — embedding dla „cat”
- \(E[1] = [0.2,\ 1.0]\) — embedding dla „chases”
- \(E[2] = [0.8,\ 0.0]\) — embedding dla „mouse”
Interpretacja wymiarów
Nie kontrolujemy bezpośrednio tego, co oznacza pojedynczy wymiar, ale można próbować odczytać to post hoc. Klasyczna obserwacja mówi, że embeddingi par takich jak król i królowa czy wujek i ciocia są przesunięte o zbliżony wektor — model zakodował informację o płci wzdłuż konkretnego kierunku w przestrzeni.
W naszym przykładzie można by spekulować, że:
- pierwszy wymiar odpowiada „zwierzęcości” (cat = 1.0, mouse = 0.8, chases = 0.2),
- drugi wymiar odpowiada „akcji/ruchowi” (chases = 1.0, pozostałe = 0.0).
To jednak liczby dobrane pod tezę. W modelach o setkach wymiarów interpretacja jest znacznie trudniejsza i rzadko bywa jednoznaczna.
Krok 2: Macierze wag — Query, Key, Value
Definiujemy trzy macierze wag, które przekształcają embeddingi w reprezentacje Query, Key i Value. Wymiar klucza i wartości oznaczamy standardowo jako \(d_k\) i \(d_v\). Przy pojedynczej głowicy zachodzi \(d_k = d_v = d_{model}\); w multi-head attention każda głowica operuje na \(d_k = d_v = d_{model} / h\), gdzie \(h\) to liczba głowic.
Wybieramy macierze jednostkowe, żeby nie zaciemniać rachunków — w prawdziwych modelach są to wyuczone parametry, a to właśnie one decydują, czego każda głowica szuka w sekwencji.
Krok 3: Obliczenie Q, K, V
Mnożymy embeddingi przez odpowiednie macierze wag. Przy macierzach jednostkowych wszystkie trzy reprezentacje są identyczne z \(E\):
Krok 4: Attention scores
Attention obliczamy według wzoru, który można znaleźć w oryginalnym artykule o attention:
gdzie \(d_k = 2\).
Iloczyn \(QK^T\):
Element \((i, j)\) tej macierzy to iloczyn skalarny zapytania tokenu \(i\) z kluczem tokenu \(j\) — miara tego, jak dobrze do siebie pasują.
Skalowanie przez \(\sqrt{d_k} = \sqrt{2} \approx 1.414\):
Krok 5: Maskowanie i softmax
W architekturze decoder-only (np. GPT, Claude) stosujemy maskę przyczynową (causal mask), która odcina tokenom dostęp do przyszłości — każdy token widzi tylko siebie i pozycje wcześniejsze. To właśnie ta własność umożliwia autoregresyjną generację tekstu.
Dla porównania architektura encoder-only (np. BERT) nie stosuje maski: każdy token widzi całą sekwencję (bidirectional attention).
Dodanie maski (przed softmax):
Uwaga: zapis \(a + (-\infty)\) jest matematycznie nieformalny, ale stanowi standardową konwencję implementacyjną. W arytmetyce zmiennoprzecinkowej
-infto konkretna wartość, dla której \(\exp(-\infty) = 0\), co zeruje zamaskowane pozycje po softmaksie.
Softmax stosujemy niezależnie do każdego wiersza:
Każdy wiersz mówi, jak bardzo dany token „zwraca uwagę” na dostępne pozycje:
- wiersz 0 („cat”) — widzi wyłącznie siebie, więc cała waga trafia na jedyny dostępny token;
- wiersz 1 („chases”) — widzi „cat” i siebie, przy czym mocniej na siebie (0.65) niż na „cat” (0.35);
- wiersz 2 („mouse”) — widzi wszystkie trzy pozycje: najmocniej „cat” (0.40), potem siebie (0.35), najsłabiej „chases” (0.25).
Krok 6: Wyjście — reprezentacje kontekstowe
Ostatnim krokiem samego attention jest pomnożenie wag przez macierz Value:
Otrzymana macierz zawiera reprezentacje kontekstowe każdego tokenu — każdy wiersz jest ważoną kombinacją wektorów Value, a wagi pochodzą z attention scores.
Co się zmieniło?
- „cat” — bez zmian, \([1.0,\ 0.0]\), ponieważ przez maskę widzi tylko siebie;
- „chases” — z \([0.2,\ 1.0]\) na \([0.48,\ 0.65]\): wzrosła „zwierzęcość” (wpływ „cat”), spadła „akcja”;
- „mouse” — z \([0.8,\ 0.0]\) na \([0.73,\ 0.25]\): pojawiła się składowa „akcji” (wpływ „chases”).
Każdy token wchłonął informację o swoim kontekście. „Mouse” niesie teraz ślad tego, że jest gonione, a ta informacja okaże się kluczowa przy predykcji kolejnego tokenu. Przykład jest uproszczony, ale dokładnie ten sam mechanizm — agregacja kontekstu przez ważone sumy — działa w modelach produkcyjnych.
Uwaga: w pełnej architekturze wyjście jest jeszcze mnożone przez macierz projekcji \(W_O \in \mathbb{R}^{d_v \times d_{model}}\), którą tutaj pomijamy.
Krok 7: Sieć feed-forward (FFN)
Po bloku attention następuje sieć feed-forward, stosowana niezależnie do każdej pozycji. W oryginalnej architekturze (Vaswani et al., 2017) są to dwie warstwy liniowe z aktywacją ReLU:
Dla uproszczenia użyjemy minimalnej wersji: jedna warstwa liniowa, bez aktywacji i bez biasu.
Uwaga: pomijamy LayerNorm i połączenia rezydualne, które w prawdziwym Transformerze stabilizują uczenie.
Krok 8: Predykcja następnego tokenu
Rozkład prawdopodobieństwa następnego tokenu odczytujemy z reprezentacji ostatniej pozycji:
To w niej — dzięki attention — zebrał się kontekst całego zdania. Warto jednak zaznaczyć, że pozostałe pozycje nie stają się zbędne: ich klucze i wartości są nadal potrzebne w kolejnych warstwach, a podczas treningu każda pozycja przewiduje swój własny następny token. Odczyt z ostatniego wiersza to sytuacja z etapu generowania.
Projekcja na logity (unembedding)
Do przejścia z reprezentacji na wyniki dla całego słownika wykorzystamy ponownie \(W_{vocab}\). Ten zabieg — użycie tej samej macierzy jako embeddingu i jako unembeddingu — nazywa się weight tying i jest stosowany w wielu prawdziwych modelach; oszczędza parametry i zwykle poprawia jakość.
Z logitów obliczamy prawdopodobieństwa:
| Token | Prawdopodobieństwo |
|---|---|
| cat | 21% |
| chases | 31% |
| mouse | 19% |
| quickly | 18% |
| sleeping | 11% |
Według naszego modelu najbardziej prawdopodobnym kolejnym tokenem jest „chases”, co daje zdanie:
cat chases mouse chases
Wynik jest bez sensu — i to dobry punkt wyjścia do pytania, dlaczego.
Dlaczego to (nie) działa
Ponieważ wagi nie pochodzą z treningu. Dobrałem je ręcznie, tak żeby rachunki dało się prześledzić na kartce. Mógłbym je dostroić pod sensowną odpowiedź, ale wtedy przykład sugerowałby coś nieprawdziwego: że dwa wymiary i kilkanaście parametrów wystarczą do modelowania języka. Nie wystarczą. W prawdziwych modelach parametrów są miliardy i dopiero ich wspólne dopasowanie na ogromnym korpusie sprawia, że predykcje zaczynają mieć sens.
Co natomiast działa niezależnie od wartości wag, to sama struktura obliczeń. Attention pozwala każdemu tokenowi „spojrzeć” na dostępne pozycje i zdecydować, które z nich są istotne dla jego reprezentacji.
W architekturze decoder-only z maską przyczynową (jak w naszym przykładzie):
- każdy token buduje reprezentację wyłącznie na podstawie siebie i pozycji wcześniejszych,
- „mouse” może uwzględnić kontekst z „cat” i „chases”, ale nie odwrotnie,
- to umożliwia generację autoregresyjną: model przewiduje następny token, znając jedynie poprzednie.
W architekturze encoder-only (bez maski):
- każdy token widzi całą sekwencję,
- „cat” może zwracać uwagę na „mouse” i odwrotnie,
- model uczy się relacji dwukierunkowych.
Ta elastyczność pozwala Transformerom uchwycić strukturę i semantykę tekstu bez ręcznego definiowania reguł gramatycznych.
Kluczowe właściwości
Skalowanie przez \(\sqrt{d_k}\)
Dzielenie przez \(\sqrt{d_k}\) powstrzymuje iloczyny skalarne przed zbyt szybkim wzrostem wraz z wymiarowością. Przy dużych wartościach softmax wchodzi w obszar nasycenia — rozkład staje się niemal zerojedynkowy, a gradienty zanikają, co utrudnia uczenie.
Softmax
Softmax normalizuje wyniki tak, by wagi attention w każdym wierszu sumowały się do 1.0. Przy okazji wyostrza różnice: wartości większe stają się po transformacji jeszcze bardziej dominujące.
Macierze Q, K, V
Rozdzielenie na Query, Key i Value daje modelowi swobodę:
- Query — „czego szukam?”
- Key — „po czym można mnie znaleźć?”
- Value — „jaką informację przekazuję?”
Token może szukać jednych cech (Q), być wyszukiwany po innych (K) i przekazywać jeszcze inne (V). Ta separacja dopuszcza relacje asymetryczne — to, że A zwraca uwagę na B, nie oznacza, że B zwraca uwagę na A.
Multi-head attention
W praktyce nowoczesne Transformery używają multi-head attention:
- embeddingi są rzutowane do \(h\) różnych podprzestrzeni (głowic),
- każda głowica ma własne macierze \(W_Q^{(i)}, W_K^{(i)}, W_V^{(i)}\) prowadzące do \(d_k = d_v = d_{model} / h\),
- attention liczony jest równolegle w każdej głowicy,
- wyniki są konkatenowane i rzutowane przez \(W_O\).
gdzie:
Dzięki temu model uczy się kilku typów relacji równocześnie — jedna głowica może śledzić zależności składniowe, inna semantyczne, jeszcze inna pozycyjne.
Podsumowanie
Attention to matematycznie oszczędny sposób modelowania zależności między elementami sekwencji. Kluczowe kroki to:
- embeddingi — reprezentacja tokenów jako wektorów (+ positional encoding w pełnej architekturze),
- transformacje Q, K, V — projekcje do przestrzeni query, key i value,
- attention scores — obliczenie \(QK^T/\sqrt{d_k}\),
- maskowanie — maska przyczynowa (decoder) lub jej brak (encoder),
- softmax — normalizacja do rozkładu prawdopodobieństwa,
- ważona suma Value — reprezentacje kontekstowe,
- projekcja wyjściowa — mnożenie przez \(W_O\) (w pełnej architekturze).
Ten mechanizm, powtórzony w wielu warstwach i wielu głowicach, tworzy architekturę, która zmieniła przetwarzanie języka naturalnego.
Przydatne linki
- Attention is All You Need (oryginalny artykuł)
- The Illustrated Transformer
- Transformer: A Novel Neural Network Architecture for Language Understanding