Учебник «Большие языковые модели»
У нас есть последовательность векторов — эмбеддинги токенов из главы 2. Теперь центральный вопрос: как построить сеть, в которой представление каждого токена обогащается информацией обо всём контексте? Слово «ключ» должно «узнать», что рядом стоит «из-под земли», причём «рядом» может означать и за пятьдесят слов. Ответ индустрии на этот вопрос — архитектура трансформер, предложенная в статье с говорящим названием «Attention Is All You Need» (Васвани и др., 2017). Архитектура задаёт способ вычисления, но способности конкретной модели определяют также данные, цель обучения и последующее дообучение.
До 2017 года последовательности обрабатывали рекуррентными сетями (RNN, LSTM): сеть читает токены по одному, поддерживая вектор скрытого состояния — «конспект» прочитанного. У классической однонаправленной RNN без внимания есть два ограничения. Во-первых, узкое горло: весь прочитанный контекст, хоть тысяча слов, должен уместиться в один вектор фиксированного размера; связи между далёкими токенами доходят ослабленными — сигнал от начала фразы к концу проходит через длинную цепочку преобразований. Во-вторых, и это оказалось решающим для масштабирования, — последовательность вычислений: нельзя обработать токен номер 100, не обработав предыдущие 99. А значит, нельзя загрузить работой десятки тысяч параллельных ядер GPU так же эффективно, как большими матричными операциями трансформера.
Механизм полного внимания решает обе проблемы одним приёмом: пусть каждый токен напрямую сравнит себя со всеми доступными ему позициями и назначит им веса.
Удобная интуиция — «мягкая» ассоциативная таблица. В обычной хеш-таблице запрос (key) либо совпадает с ключом, либо нет, и мы получаем одно значение. Внимание устраивает нечёткий поиск: запрос сравнивается со всеми ключами, каждому назначается вес «насколько подходит», и результат — взвешенная сумма всех значений.
Формально. Каждый токен i с текущим представлением xi ∈ ℝd порождает три вектора тремя обучаемыми матрицами:
qi = WQxi, ki = WKxi, vi = WVxi
Запрос (query) — «что я ищу», ключ (key) — «что во мне можно найти», значение (value) — «что я отдам, если меня выберут». Сходство запроса токена i с ключом токена j измеряется скалярным произведением, а веса внимания получаются нормировкой сходств функцией softmax:
aij = softmaxj ( qi · kj / √dk ) = exp(qi·kj/√dk) / Σm exp(qi·km/√dk)
Веса aij неотрицательны и в сумме дают единицу — это распределение внимания токена i по всем токенам. Новое представление токена — взвешенная сумма значений:
zi = Σj aij vj
В матричной записи для всей последовательности сразу (строки Q, K, V — запросы, ключи и значения всех токенов):
Attention(Q, K, V) = softmax(QKT / √dk) V
Деление на √dk (корень из размерности ключей) — техническая, но важная деталь: скалярные произведения случайных векторов растут как √dk, и без нормировки softmax при больших размерностях «насыщается» — почти весь вес достаётся одному токену, градиенты исчезают, обучение стопорится.
Заметьте, чего мы добились. Путь между любыми двумя токенами — один шаг, как бы далеко они ни стояли: проблема дальних связей снята. Все строки матрицы QKT вычисляются независимо — это одно большое матричное умножение, идеальная пища для GPU: проблема параллелизма снята. Цена — квадратичность: матрица внимания имеет размер n×n, и удвоение длины контекста вчетверо увеличивает объём этих вычислений. Оптимизированные реализации вроде FlashAttention не обязаны хранить всю матрицу в памяти, но число пар для обычного полного внимания остаётся квадратичным. При пошаговой генерации с KV-кэшем картина вычислений отличается; к ней вернёмся в главе 7.
Языковая модель предсказывает следующий токен, поэтому при обучении токену нельзя подглядывать вперёд. Это обеспечивает причинная маска (causal mask): в матрице сходств все элементы выше диагонали (позиции j > i) заменяются на −∞ перед softmax, и вес будущих токенов обнуляется. Токен видит только прошлое. Именно поэтому генерация получается авторегрессионной. В рассматриваемых LLM этот принцип реализован архитектурой decoder-only, в отличие от двунаправленных кодирующих моделей типа BERT. Эти термины не синонимы: декодер модели encoder–decoder тоже может генерировать авторегрессионно.
Далее, одного «взгляда» на контекст мало: токену может быть одновременно важно синтаксическое согласование с одним словом, смысловая связь с другим, кореференция с третьим. Поэтому внимание делают многоголовым (multi-head): в классическом варианте вместо одного внимания размерности d параллельно работают h «голов» размерности dk = d/h, каждая со своими матрицами WQ, WK, WV. Их результаты конкатенируются и проецируются обратно в размерность d. У некоторых голов обнаруживаются устойчивые синтаксические или позиционные паттерны, но интерпретация не однозначна, а часть голов бывает избыточной. Современные варианты MQA и GQA разделяют ключи и значения между группами голов, уменьшая KV-кэш.
Внимание перемешивает информацию между токенами, но почти не перерабатывает её. Поэтому в блоке трансформера за вниманием следует полносвязная сеть (feed-forward network, FFN), применяемая к каждой позиции независимо: два линейных слоя с нелинейностью между ними, внутренняя размерность часто в несколько раз больше d. Если внимание — это «коммуникация», то FFN — «вычисление». Исследования находят фактические ассоциации и в FFN, и в других компонентах; знания распределены по сети, поэтому буквального «хранилища фактов» здесь нет. В части крупных моделей FFN устроена как смесь экспертов (MoE): маршрутизатор активирует для токена небольшое подмножество экспертов, чтобы число параметров росло быстрее, чем вычисления.
Два инженерных элемента делают глубокую башню из таких блоков обучаемой. Остаточные связи (residual connections): выход каждого подслоя прибавляется к его входу, x ← x + Attention(x), x ← x + FFN(x). Сеть учит не преобразование целиком, а поправку к тождественному отображению; градиенты текут по «магистрали» остаточных связей сквозь всю глубину. Нормализация слоя (LayerNorm/RMSNorm) стабилизирует масштаб значений. Ниже показан распространённый pre-norm-вариант, где нормализация стоит перед подслоем:
x = x + MultiHeadAttention(Norm(x)) # общение токенов x = x + FFN(Norm(x)) # переработка на месте
Языковая модель — это эмбеддинги токенов на входе, затем L одинаковых по устройству блоков (в современных моделях L — десятки), затем финальная нормализация и выходная проекция в логиты — по числу на каждый токен словаря; softmax превращает их в распределение вероятностей следующего токена. Вопрос о том, как модель узнаёт порядок токенов (само внимание к порядку безразлично — перестановка входа лишь переставляет выход), решается позиционным кодированием; один из широко используемых вариантов — поворотные позиционные эмбеддинги RoPE, при которых пары координат запросов и ключей поворачиваются на угол, пропорциональный позиции, и скалярное произведение начинает зависеть от относительного расстояния между токенами.
Весь механизм легко пощупать руками. Ниже учебная реализация причинного многоголового внимания на NumPy: без батчей, смещений и dropout, но с выходной проекцией:
import numpy as np
def softmax(x):
e = np.exp(x - x.max(axis=-1, keepdims=True))
return e / e.sum(axis=-1, keepdims=True)
def causal_attention(X, Wq, Wk, Wv, Wo, n_heads):
"""X: (n, d) -- представления n токенов."""
n, d = X.shape
dk = d // n_heads
Q, K, V = X @ Wq, X @ Wk, X @ Wv # (n, d) каждая
mask = np.triu(np.ones((n, n)), k=1) * -1e9 # будущее -> -inf
heads, weights = [], []
for h in range(n_heads):
s = slice(h*dk, (h+1)*dk)
scores = Q[:, s] @ K[:, s].T / np.sqrt(dk) # (n, n)
A = softmax(scores + mask) # веса внимания
heads.append(A @ V[:, s]) # (n, dk)
weights.append(A)
joined = np.concatenate(heads, axis=1) # (n, d)
return joined @ Wo, np.stack(weights) # (n, d), (h, n, n)
# Игрушечный прогон
rng = np.random.default_rng(0)
n, d, h = 6, 32, 4
X = rng.normal(size=(n, d))
Wq, Wk, Wv, Wo = (rng.normal(size=(d, d)) * 0.1 for _ in range(4))
out, A = causal_attention(X, Wq, Wk, Wv, Wo, h)
print(out.shape, A.shape) # (6, 32) (4, 6, 6)
Распечатайте матрицу A[0] для одной головы и убедитесь: она нижнетреугольная (причинность), каждая строка суммируется в единицу (распределение), первая строка — в точности [1, 0, …, 0]: первому токену не на что смотреть, кроме себя.
Полезно один раз посчитать параметры «на салфетке». Для классического плотного блока с обычным многоголовым вниманием: четыре матрицы внимания (WQ, WK, WV и выходная проекция) — 4d2; FFN с внутренней размерностью 4d — примерно 8d2. Итого ≈12d2 на блок, и для модели из L блоков со словарём V:
N ≈ 12 L d2 + V d
Это приближение не учитывает смещения, нормы, отдельную выходную матрицу эмбеддингов, GQA, gated-FFN и MoE; для конкретной архитектуры коэффициенты могут отличаться. Подставим d = 4096, L = 32, V = 128 000: получаем 12·32·40962 ≈ 6,4 млрд плюс ~0,5 млрд в эмбеддингах — модель класса «7B». В этой оценке 4d2 параметров внимания вдвое меньше, чем 8d2 параметров FFN. Из этого соотношения нельзя заключить, где именно модель хранит знания.
Трансформер строится из одинаковых блоков «внимание + FFN» с остаточными связями и нормализацией. Внимание — это мягкий поиск: каждый токен запросом опрашивает ключи всех остальных и собирает взвешенную сумму их значений; формула softmax(QKT/√dk)V — центральная операция архитектуры. Причинная маска запрещает подглядывать в будущее, многоголовость даёт несколько обучаемых проекций, а позиционное кодирование сообщает модели порядок токенов. При обучении позиции можно обрабатывать параллельно, что хорошо соответствует GPU; авторегрессионная генерация при этом остаётся последовательной. Цена обычного полного внимания — квадратичное по длине число пар позиций.
Мы построили машину. В следующей главе — чем и как её кормят: предобучение, функция потерь, данные и законы масштабирования.
Упражнения
1. В коде из раздела 3.5 распечатайте матрицу весов A[0] и проверьте её свойства: треугольность, суммы строк. Уберите маску — что изменится?
2. Уберите деление на √dk и увеличьте d до 512. Посмотрите на строки матрицы A: насколько «острым» стало распределение внимания?
3. Посчитайте по формуле раздела 3.6 приблизительное число параметров модели с d = 8192, L = 64, V = 128 000. К какому классу моделей она относится?