Запит, Ключ, Значення
Три лінійні відображення з одного й того ж входу
Після вбудовування (активність 4) кожна позиція несе 768-вимірний вектор x_t. Увага починається з створення трьох різних проєкцій x:
Q (запит): що ця позиція хоче знати?
K (ключ): що ця позиція пропонує іншим позиціям?
V (значення): який контент ця позиція надає, якщо на неї звертається увага?
Кожна проєкція походить з матриці вивчених ваг:
Q = x · W_Q # Форма W_Q: (d_model, d_k)
K = x · W_K # Форма W_K: (d_model, d_k)
V = x · W_V # Форма W_V: (d_model, d_k)
Три матриці, всі навчаються через зворотне поширення. Модель навчається: у цій позиції, який запит найкраще витягує корисний минулий контекст? Який ключ добре рекламує вміст цієї позиції? Яке значення передається, якщо вибрано?
Аналогія з бібліотечним каталогом
Уявіть каталог карток бібліотеки. Ви заходите з темою на думці (ваш запит). Кожна картка містить ключові слова (ключ). Коли ваша тема збігається з ключовими словами картки, ви берете зміст книги (значення). Attention виконує це для кожного токена паралельно: кожна позиція запитує кожну іншу позицію, ранжує узгодженість та витягує зважену комбінацію векторів значень.
Розміри ANDREA-120M
| Кількість | Значення | Примітки |
|---|---|---|
| d_model | 768 | Розмір вектора на кожній позиції |
| n_head | 12 | Паралельні увагові головки |
| d_k | 64 | Розмірність на головку (= d_model / n_head) |
| T | 1024 | Довжина контексту |
d_k = d_model / n_head = 768 / 12 = 64. Кожна головка бачить 64-вимірний зріз повного 768-вимірного простору. Активність 6 (grow_a_language_model_multi_head) детально розглядає розподіл на головки.
Обчисліть d_k
Чому ділити на sqrt(d_k)
Матриця оцінок
Як тільки Q та K існують (кожна форма (T, d_k)), увага обчислює матрицю оцінок:
scores = Q · K^T # shape: (T, T)
scores[i, j] = наскільки сильно запит позиції i узгоджується з ключем позиції j. Кожна пара (i, j) отримує один бал: 1024 × 1024 = 1,048,576 балів на голову уваги на один прохід вперед.
Чому ділити
Скалярні добутки двох випадкових одиничних векторів розмірності d мають величину порядку sqrt(d). Без масштабування бали зростають з d_k:
- d_k = 64: типові скалярні добутки порядку 8.
- d_k = 256: типові скалярні добутки порядку 16.
- d_k = 4096: типові скалярні добутки порядку 64.
Великі значення призводять до пікового softmax (одна позиція домінує, градієнти зникають деінде). Навчання зупиняється. Масштабування виправляє величину:
scaled_scores = (Q · K^T) / sqrt(d_k)
Для ANDREA-120M, sqrt(d_k) = sqrt(64) = 8. Кожен бал ділиться на 8. Величини залишаються приблизно на одиничному масштабі незалежно від d_k. Softmax залишається добре поведеним. Градієнти течуть.
Оригінальне обґрунтування Васвани
З Attention Is All You Need (2017): 'Для великих значень d_k скалярні добутки зростають у величині, штовхаючи функцію softmax у регіони, де у неї надзвичайно малі градієнти.' Дільник sqrt(d_k) протидіє цьому зростанню.
Вигляд коду
Усередині microgpt_cuda.cu це масштабування з'являється як буквальний поділ:
scores[i][j] = dot(Q[i], K[j]) * (1.0f / sqrtf(d_k));
Одне множення на float для кожного score. Дешево. Критично.
Масштабування при d_model = 4096
Чому позиція i не може бачити позицію j > i
Обмеження, народжене з генерації
ANDREA генерує один токен за раз. Під час інференсу позиція 0 виробляє перший токен, потім позиція 1 бачить вихід позиції 0 та виробляє другий токен, і так далі. Модель ніколи не має доступу до майбутніх токенів під час генерації.
Навчання повинно відображати це. Якщо під час навчання позиція 5 могла б звертати увагу на позицію 6, модель вивчила б обхідний шлях: 'передбачити токен 6, читаючи токен 6'. Під час інференсу цей обхідний шлях зникає (токен 6 ще не існує). Поведінка моделі під час навчання та інференсу розійшлася б катастрофічно.
Маска
Казуальна маска блокує увагу з будь-якої позиції i до будь-якої позиції j > i. Реалізація: встановити scaled_scores[i][j] = -infinity всюди, де j > i. Після softmax ці елементи стають exp(-inf) = 0. Маска чисто обнуляє увагу до майбутніх позицій.
for i in range(T):
for j in range(T):
if j > i:
scaled_scores[i][j] = -1e9 # фактично -inf
Після softmax (по рядках), кожен рядок сумується до 1, але тільки елементи [0, i] несуть масу ймовірності. Позиція i змішує інформацію тільки з минулих позицій.
Візуалізація маски
Матриця оцінок форми (T, T) з застосованою маскою має вигляд нижньотрикутної структури:
scaled_scores після маски, softmax по рядках:
рядок 0: [1.0, 0, 0, 0, ...] # бачить лише себе
рядок 1: [0.4, 0.6, 0, 0, ...] # бачить позиції 0, 1
рядок 2: [0.2, 0.3, 0.5, 0, ...] # бачить 0, 1, 2
рядок 3: [0.1, 0.2, 0.3, 0.4, ...] # бачить 0, 1, 2, 3
...
Строга нижньотрикутна ймовірнісна дистрибуція на рядок. Майбутнє залишається невидимим.
Чому Transformer тільки з декодером потребує цього
Моделі лише декодера, такі як ANDREA, GPT та LLaMA, мають одну спільну мету: передбачити наступний токен з минулих. Каузальна маска робить цю мету треновною паралельно: кожна позиція одночасно обчислює своє передбачення наступного токена, та жодна позиція не шахрує, заглядаючи вперед.
Маска та Смак
Від Очків до Виводу
Softmax: Від оцінки до ймовірностей
Маскировані, масштабні оцінки все ще охоплюють дійсні числа. Softmax перетворює кожен рядок на розподіл ймовірностей:
A[i][j] = exp(scaled_scores[i][j]) / sum_k exp(scaled_scores[i][k])
Виникають три властивості:
- A[i][j] >= 0 для всіх (i, j).
- sum_j A[i][j] = 1 для кожного рядка i.
- Більші сирі бали виробляють більші ймовірності (монотонно).
Вектор ймовірностей рядка i каже моделі: наскільки позиція i повинна звертати увагу на кожну попередню позицію при обчисленні свого виходу?
Зважена сума V
Остаточний вихід уваги для позиції i:
output[i] = sum_j A[i][j] · V[j]
Кожен вектор значень V[j] важиться з ймовірністю уваги A[i][j], а потім підсумовується. Вихід позиції i комбінує вектори значень з кожної попередньої позиції, важені за релевантністю.
У матричній формі, усі позиції одночасно:
Attention(Q, K, V) = softmax(mask(Q · K^T / sqrt(d_k))) · V
Один рядок. Цілий механізм уваги. Vaswani et al. написали цей рядок у 2017 році; трансформери фундаментально не змінилися з того часу.
Форма виходу на голову
Вихід однієї увагової голови: форма (T, d_k). Для ANDREA-120M: (1024, 64). Усі 12 голів обчислюються паралельно; їхні виходи конкатенуються до (1024, 768) та подаються на фінальну лінійну проєкцію (W_O), а потім до MLP блоку трансформера.
Активність 6 (grow_a_language_model_multi_head) охоплює розподіл на багатоклавову увагу. Активність 7 (grow_a_language_model_transformer_block) охоплює все, що оточує увагу: залишкові з’єднання, нормалізацію шарів, MLP.