Query, Key, Value
Trzy Liniowe Mapowania z Tego Samego Wejścia
Po osadzeniu (aktywność 4), każda pozycja niesie 768-wymiarowy wektor x_t. Attention zaczyna od wyprodukowania trzech różnych projekcji x:
Q (query): co ta pozycja chce wiedzieć?
K (klucz): co ta pozycja oferuje innym pozycjom?
V (wartość): jaką treść dostarcza ta pozycja, jeśli zostanie uwzględniona?
Każda projekcja pochodzi z wyuczonej macierzy wag:
Q = x · W_Q # Kształt W_Q: (d_model, d_k)
K = x · W_K # Kształt W_K: (d_model, d_k)
V = x · W_V # Kształt W_V: (d_model, d_k)
Trzy macierze, wszystkie trenowane za pomocą propagacji wstecznej. Model uczy się: w tej pozycji, które zapytanie najlepiej pobierze użyteczny poprzedni kontekst? Który klucz dobrze reklamuje treść tej pozycji? Jaką wartość dostarcza, jeśli zostanie wybrana?
Analogia do biblioteki
Wyobraź sobie katalog kartkowy w bibliotece. Wchodzisz z tematem w głowie (twoje zapytanie). Każda karta zawiera słowa kluczowe (klucz). Gdy twój temat pasuje do słów kluczowych na karcie, bierzesz treść książki (wartość). Mechanizm uwagi robi to równolegle dla każdego tokena: każda pozycja zapytuje każdą inną pozycję, ocenia zgodność i pobiera ważoną kombinację wektorów wartości.
Wymiary ANDREA-120M
| Ilość | Wartość | Uwagi |
|---|---|---|
| d_model | 768 | Rozmiar wektora w każdej pozycji |
| n_head | 12 | Równoległe głowy uwagi |
| d_k | 64 | Wymiar na głowę (= d_model / n_head) |
| T | 1024 | Długość kontekstu |
d_k = d_model / n_head = 768 / 12 = 64. Każda głowa widzi 64-wymiarowy wycinek pełnej 768-wymiarowej przestrzeni. Aktywność 6 (grow_a_language_model_multi_head) omawia podział na głowy szczegółowo.
Oblicz d_k
Dlaczego dzielić przez sqrt(d_k)
Macierz Wyników
Gdy Q i K istnieją (każda o kształcie (T, d_k)), mechanizm uwagi oblicza macierz wyników:
scores = Q · K^T # shape: (T, T)
scores[i, j] = jak silnie kwerenda pozycji i zgadza się z kluczem pozycji j. Każda para (i, j) otrzymuje jeden wynik: 1024 × 1024 = 1 048 576 wyników na głowę uwagi na przejście w przód.
Dlaczego dzielić
Iloczyny skalarne dwóch losowych wektorów jednostkowych o wymiarze d mają magnitudę rzędu sqrt(d). Bez skalowania, wyniki rosną wraz z d_k:
- d_k = 64: typowe iloczyny skalarne rzędu 8.
- d_k = 256: typowe iloczyny skalarne rzędu 16.
- d_k = 4096: typowe iloczyny skalarne rzędu 64.
Duże wyniki powodują ostry softmax (jedna pozycja dominuje, gradienty zanikają gdzie indziej). Trening się zatrzymuje. Skalowanie naprawia wielkość:
wyniki_skalowane = (Q · K^T) / sqrt(d_k)
Dla ANDREA-120M, sqrt(d_k) = sqrt(64) = 8. Każdy wynik jest dzielony przez 8. Wielkości pozostają w przybliżeniu na skali jednostkowej niezależnie od d_k. Softmax pozostaje dobrze zachowany. Gradienty płyną.
Oryginalne uzasadnienie Vaswani'ego
Z Attention Is All You Need (2017): 'Dla dużych wartości d_k, iloczyny skalarne rosną w wielkości, spychając funkcję softmax w regiony, gdzie ma ekstremalnie małe gradienty.' Dzielnik sqrt(d_k) przeciwdziała temu wzrostowi.
Widok kodu
W pliku microgpt_cuda.cu to skalowanie pojawia się jako dosłowne dzielenie:
scores[i][j] = dot(Q[i], K[j]) * (1.0f / sqrtf(d_k));
Jedno mnożenie float na wynik. Tanie. Krytyczne.
Skalowanie przy d_model = 4096
Dlaczego pozycja i nie może widzieć pozycji j > i
Ograniczenie wynikające z generowania
ANDREA generuje jeden token na raz. Podczas inferencji pozycja 0 produkuje pierwszy token, potem pozycja 1 widzi wyjście pozycji 0 i produkuje drugi token, i tak dalej. Model nigdy nie ma dostępu do przyszłych tokenów podczas generowania.
Trening musi to odzwierciedlać. Jeśli podczas treningu pozycja 5 mogłaby zwracać uwagę na pozycję 6, model nauczyłby się skrótu: „przewiduj token 6 czytając token 6”. Podczas inferencji ten skrót znika (token 6 jeszcze nie istnieje). Zachowanie modelu trening-vs-inferencja rozeszłoby się katastrofalnie.
Maska przyczynowa
Maska przyczynowa blokuje uwagę z dowolnej pozycji i do dowolnej pozycji j > i. Implementacja: ustaw scaled_scores[i][j] = -nieskończoność tam, gdzie j > i. Po softmax te wpisy stają się exp(-inf) = 0. Maska czysto zeruje uwagę na przyszłe pozycje.
dla i w zakresie(T):
dla j w zakresie(T):
jeśli j > i:
scaled_scores[i][j] = -1e9 # efektywnie -inf
Po softmax (wierszowo), każdy wiersz sumuje się do 1, ale tylko wpisy [0, i] niosą masę prawdopodobieństwa. Pozycja i miesza informacje tylko z poprzednich pozycji.
Wizualizacja maski
Macierz wyników o kształcie (T, T) z zastosowaną maską wygląda jak struktura dolnotrójkątna:
scaled_scores po masce, softmax wiersz po wierszu:
wiersz 0: [1.0, 0, 0, 0, ...] # widzi tylko siebie
wiersz 1: [0.4, 0.6, 0, 0, ...] # widzi pozycje 0, 1
wiersz 2: [0.2, 0.3, 0.5, 0, ...] # widzi 0, 1, 2
wiersz 3: [0.1, 0.2, 0.3, 0.4, ...] # widzi 0, 1, 2, 3
...
Ścisły dolny trójkątny rozkład prawdopodobieństwa na wiersz. Przyszłość pozostaje niewidoczna.
Dlaczego Transformer tylko z dekoderem potrzebuje tego
Modele typu decoder-only, takie jak ANDREA, GPT i LLaMA, mają jeden wspólny cel: przewidzieć następny token na podstawie poprzednich. Maska kauzalna sprawia, że ten cel jest trenowalny równolegle: każda pozycja oblicza swoją własną predykcję następnego tokenu jednocześnie, & żadna pozycja nie oszukuje, zaglądając w przyszłość.
Maska & Smak
Od Wyników do Wyjścia
Softmax: Od wyników do prawdopodobieństw
Maskowane, przeskalowane wyniki nadal obejmują liczby rzeczywiste. Softmax przekształca każdy wiersz w rozkład prawdopodobieństwa:
A[i][j] = exp(scaled_scores[i][j]) / sum_k exp(scaled_scores[i][k])
Wynikają trzy właściwości: Return ONLY translated blocks with their placeholders, preserving format: [BLOCK_TYPE SECTION/STEP] __BLOCK_N__ <translated content>
- A[i][j] >= 0 dla wszystkich (i, j).
- sum_j A[i][j] = 1 dla każdego wiersza i.
- Większe surowe wyniki produkują większe prawdopodobieństwa (monotoniczne).
Wektor prawdopodobieństw wiersza i mówi modelowi: jak bardzo pozycja i powinna zwracać uwagę na każdą poprzednią pozycję podczas obliczania swojego wyjścia?
Ważona suma V
Ostateczny wynik uwagi dla pozycji i:
output[i] = sum_j A[i][j] · V[j]
Każdy wektor wartości V[j] jest ważony przez prawdopodobieństwo uwagi A[i][j], a następnie sumowany. Wynik pozycji i łączy wektory wartości z każdej poprzedniej pozycji, ważone według istotności.
W formie macierzowej, wszystkie pozycje naraz:
Attention(Q, K, V) = softmax(mask(Q · K^T / sqrt(d_k))) · V
Jedna linia. Cały mechanizm uwagi. Vaswani i inni napisali tę linijkę w 2017 roku; transformery nie zmieniły się fundamentalnie od tamtej pory.
Kształt wyjścia na głowę
Wyjście jednej głowy uwagi: kształt (T, d_k). Dla ANDREA-120M: (1024, 64). Wszystkie 12 głów oblicza równolegle; ich wyjścia są konkatenowane do (1024, 768) i przekazywane do ostatecznej projekcji liniowej (W_O), a następnie do MLP bloku transformatora.
Aktywność 6 (grow_a_language_model_multi_head) omawia podział na wiele głów. Aktywność 7 (grow_a_language_model_transformer_block) omawia wszystko, co otacza uwagę: połączenia resztkowe, normalizację warstw, MLP.