32 Transformer ★
Контекст
К 2017-му лучшие модели последовательностей — рекуррентные (LSTM, #11) с механизмом внимания (#22). Рекуррентность — узкое место: вычисления строго последовательны по времени (токен t ждёт t−1), поэтому плохо параллелятся, медленны на длинных входах, а длинные зависимости всё равно даются тяжело. Авторы задают дерзкий вопрос: а нужна ли рекуррентность вообще, если внимание и так связывает любые две позиции напрямую?
Идея и механизм
Каждый токен проецируется в три вектора: Query (что я ищу), Key (чем я являюсь), Value (что я несу). Внимание — это извлечение values, взвешенное по совпадению query с keys:
Матрица QK⊤ даёт оценку «насколько токен i релевантен токену j»; softmax превращает оценки в веса; умножение на V собирает взвешенную смесь значений. Каждый токен агрегирует информацию со всех остальных за один шаг.
Multi-head. Вместо одного внимания — несколько параллельных «голов», каждая со своими проекциями; одна ловит синтаксис, другая кореференцию и т.д. Результаты конкатенируются:
Positional encodings. Внимание перестановочно-инвариантно (не знает порядка), поэтому к эмбеддингам добавляют позиционные сигналы (синусоиды разных частот). Блок трансформера: внимание → residual + LayerNorm → позиционный FFN → residual + LayerNorm. Оригинал — encoder-decoder (для перевода); позже разделились decoder-only (GPT) и encoder-only (BERT).
теория вероятностей Почему делим на √dk
Пусть компоненты Q и K — независимые случайные величины со средним 0 и дисперсией 1 (примерно так после нормализации). Оценка внимания — это скалярное произведение query и key размерности dk:
Среднее такой суммы — ноль, а дисперсия складывается из dk независимых слагаемых, у каждого из которых Var(qi ki) = E[qi2]·E[ki2] = 1:
То есть с ростом размерности оценки разрастаются как √ dk . Большие по модулю логиты загоняют softmax в «насыщение» — он становится почти one-hot, и его градиент в неактивных позициях обнуляется (обучение глохнет). Деление на √ dk возвращает дисперсию оценок к 1, удерживая softmax в чувствительной зоне с живыми градиентами. Маленькая константа — но без неё глубокие трансформеры обучались бы плохо.
PyTorch Реализация: self-attention в ~12 строк
import torch
import torch.nn.functional as F
def attention(Q, K, V):
# Q, K, V: (..., seq, d_k)
d_k = Q.size(-1)
scores = Q @ K.transpose(-2, -1) / d_k ** 0.5 # оценки QKᵀ / √d_k
weights = F.softmax(scores, dim=-1) # строки суммируются в 1
return weights @ V # взвешенная сумма values
def multi_head(x, Wq, Wk, Wv, Wo, h):
Q, K, V = x @ Wq, x @ Wk, x @ Wv # проекции (seq, d_model)
split = lambda t: t.view(t.size(0), h, -1).transpose(0, 1)
out = attention(split(Q), split(K), split(V)) # h голов параллельно
out = out.transpose(0, 1).reshape(x.size(0), -1)
return out @ Wo # склейка голов
Почему это важно
Transformer — фундаментальная архитектура почти всего современного ИИ: GPT, BERT, Claude, Vision Transformer (#39), мультимодальные модели. Главное инженерное свойство — параллелизуемость по позициям: нет рекуррентной зависимости, поэтому модель отлично ложится на GPU/TPU и масштабируется на длинные контексты и огромные данные. Связка «Transformer + scaling laws (#37)» и есть вся LLM-революция.
Цена — внимание стоит O(N2) по длине последовательности (каждый-с-каждым), что позже будут чинить FlashAttention (#48) и эффективные варианты.
Связи
Внимание изобрели не здесь — Багданау (2014) добавил его в seq2seq как способ декодеру «смотреть» на нужные слова источника. Transformer довёл идею до предела: убрал рекуррентность вовсе и сделал self-attention единственным механизмом связи токенов. Self-attention = внимание последовательности самой на себя.
LSTM связывал далёкие элементы через рекуррентное состояние — последовательно и с трудом на больших дистанциях. Transformer связывает любые две позиции напрямую за один шаг и параллельно. Тот же класс задач (последовательности), но снято главное ограничение рекуррентности — скорость и длинные зависимости.
Две великие ветви растут прямо отсюда: encoder-only BERT (двунаправленное понимание) и decoder-only GPT (авторегрессионная генерация). Вся современная линейка LLM — это масштабированный Transformer плюс рецепты предобучения и выравнивания.
Квадратичная стоимость внимания O(N2) и его аппетит к памяти — узкое место длинных контекстов. FlashAttention пересчитывает то же самое внимание, но без материализации полной матрицы N×N, делая длинные контексты практичными.
Вопросы пытливого ума
Если attention уже связывает все токены, зачем нужны FFN и много слоёв — разве одного внимания не хватит?
Внимание только перемешивает и маршрутизирует информацию между позициями — по сути берёт линейные комбинации values. Вся пер-токенная нелинейная обработка и бо́льшая часть ёмкости модели сидят в FFN (двухслойный MLP, применяемый к каждому токену отдельно).
А стопка слоёв строит иерархию: ранние слои ловят локальные/синтаксические связи, поздние — абстрактные. Один attention-слой без FFN беспомощен: ему нечем нелинейно преобразовать содержимое, только усреднять чужое.
O(N²) — это фундаментально, или внимание можно сделать дешевле?
Полное внимание сравнивает каждую пару токенов — отсюда N². Но это не священно: есть линейные и разреженные приближения (Linformer, Performer, sparse attention), жертвующие точностью ради скорости.
FlashAttention (#48) идёт другим путём — оставляет внимание точным, но убирает квадратичную память через тайлинг. Для честного полного внимания N² по вычислениям неизбежно; вопрос лишь, готов ли ты на аппроксимацию.
Откуда модель знает порядок слов, если attention перестановочно-инвариантно?
Сама по себе — не знает; без позиционных сигналов трансформер видит «мешок токенов». Порядок инъецируют positional encodings (синусоиды в оригинале).
Это оказалось тонким местом: как кодировать позицию, влияет на обобщение на длинные последовательности — отсюда эволюция к обучаемым и особенно к rotary-эмбеддингам (RoPE), которые кодируют относительные позиции и лучше тянутся на длинный контекст.
Почему несколько голов лучше одной большой той же суммарной размерности?
Разные головы одновременно смотрят в разные подпространства и ловят разные типы связей: одна — согласование, другая — кореференцию, третья — позиционные паттерны. Одна большая голова усредняла бы всё в единый механизм сходства.
Эмпирически головы специализируются — хотя часть из них потом можно безболезненно отрезать (не все одинаково полезны), что породило отдельное направление по прунингу голов.
Что читать в оригинале
Читать целиком и перечитать секции про (multi-head) attention и positional encoding — это важнейший разбор во всём каноне. Сильно помогает один раз реализовать attention руками: это буквально несколько матричных операций (проекции → QKᵀ → масштаб → softmax → ×V).