Это пятая статья серии «AI без магии». В первой разобрали, откуда вообще взялись LLM. Во второй — как модель «думает» по одному токену и почему галлюцинирует. В третьей — как текст превращается в векторы. В четвёртой — как токены разговаривают через attention. Теперь соберём всё вместе и посмотрим на полный блок трансформера — тот самый кубик, из которого складывается GPT.
Где мы остановились
В прошлой статье разобрали attention. На выходе у каждого токена — обновлённый вектор, в котором учтён контекст. «mole» в «shrew mole» теперь ближе к «крот», а в «one mole of CO2» — к «количеству вещества». Хорошо.
Но attention — это только половина блока. У блока есть ещё четыре детали, без которых трансформер не работает: FFN, residual connection, нормализация, и — самое главное — блоков много. Десятки и сотни. У GPT-3 их 96.
В этой статье соберём блок целиком, потом сложим из них стэк, и посмотрим, как через этот стэк прокатывается вектор токена от эмбеддинга до предсказания следующего слова.
FFN: то, что делает каждый токен сам по себе
Attention — операция между токенами. Каждый токен смотрит на других, собирает взвешенную смесь их value-векторов, и записывает результат к себе. Но если задуматься, attention — это, по сути, линейная операция: всё, что он делает, — взвешенное суммирование. Нелинейности почти нет (только softmax внутри весов).
А чтобы модель могла что-то реально вычислять — выводить, что «стеклянный» подразумевает «хрупкий», что «Москва» — это столица России, что после «sin(0) =» должно быть «0» — нужны нелинейные преобразования. И их даёт следующая часть блока — feed-forward network, или FFN. Иногда её называют MLP.
FFN до неприличия простая. Это две линейные проекции с нелинейностью посередине:
Хитрый момент — расширение. Первая проекция увеличивает размерность в 4 раза. Зачем? Шире скрытый слой — больше вычислительной ёмкости, больше «комнат» для распознавания паттернов и хранения фактов. У GPT-3 это превращается в монструозные числа: 12288 × 49152 = ~600 миллионов параметров в одной проекции одного блока. Таких блоков 96, и в каждом две таких матрицы — поэтому FFN съедают около двух третей всех параметров модели.
И тут начинается интересное. В 2020 году Гева и соавторы показали, что FFN — это не «просто нелинейность», как все привыкли думать. Это ассоциативная память. Колонки первой матрицы работают как «ключи», которые активируются на определённых паттернах входа: «прошедшее время», «упоминание Эйфелевой башни», «концепт хрупкости». Строки второй матрицы — «значения», которые при активации добавляют в выход определённую информацию.
То есть когда мы говорим «модель что-то знает» — что Москва столица России, что Жан Вальжан украл хлеб, что хром имеет атомный номер 24 — это знание физически живёт в весах FFN. Не в attention. Attention занимается связями, FFN — знанием. Удобное разделение труда: attention перемешивает токены между собой, FFN перемешивает признаки внутри одного токена.
Residual connection: магистраль через всю сеть
Теперь главный вопрос. У GPT-3 — 96 блоков. У LLaMA-2 70B — 80 блоков. У моделей вроде GPT-4 — наверняка под сотню (точные числа OpenAI не раскрывает). И тут возникает проблема, которую не сразу видно.
Сети из десятков слоёв обучить градиентным спуском в принципе сложно. Сигнал от ошибки на выходе должен дойти обратно до первого слоя. Если каждый слой что-то делает с этим сигналом — умножает на матрицу, пропускает через нелинейность — то на каждом шаге сигнал может затухать (или взрываться). Через 50 слоёв он становится практически нулевым. Первые слои перестают обучаться.
Эта проблема была убийцей глубоких сетей до 2015 года. Решили её в работе ResNet очень простой идеей: вместо того чтобы каждый слой заменял вектор, пусть он добавляет к нему правку.
было: y = sublayer(x)
стало: y = x + sublayer(x)
Это и есть residual connection (или skip connection). Один маленький плюс, который меняет всё.
Что это даёт. Во-первых, при обратном проходе градиент имеет прямую дорогу до начала сети. Через сложение он проходит без изменений — какие бы ужасы ни творились внутри sublayer-а, по этой «магистрали» сигнал доходит до первого слоя в полную силу. Anthropic в статье про circuits называют эту магистраль residual stream — общий канал связи через всю сеть, в который каждый слой что-то пишет, и из которого каждый слой что-то читает.
Во-вторых, при прямом проходе модель может не использовать какой-то слой, если он не нужен. Если sublayer выдаст почти ноль, residual connection просто пропустит вектор насквозь без изменений. Слои становятся опциональными правками — некоторые нужны почти всегда, другие срабатывают только в специфических случаях.
В блоке трансформера residual connection стоят дважды — отдельно вокруг attention и отдельно вокруг FFN:
после attention: x = x + attention(x)
после FFN: x = x + ffn(x)
Один и тот же вектор residual stream проходит насквозь через весь блок, дважды получая правки.
Нормализация: чтобы числа не разлетались
Есть ещё одна тонкость. Каждый раз, когда мы добавляем что-то к residual stream, его магнитуда может расти. Через 96 блоков числа в векторе могут уйти в космос — и сеть взорвётся. Или наоборот — если правки систематически отрицательные, вектор сожмётся в ноль.
Чтобы этого не происходило, после каждого sublayer-а ставится нормализация — операция, которая приводит вектор к стандартному размаху. Самая популярная — LayerNorm: для каждого вектора вычитаем среднее, делим на стандартное отклонение, домножаем на обучаемые параметры. На выходе — вектор с предсказуемой магнитудой, на котором следующий слой может работать стабильно.
Тут есть один технический нюанс, который оказывается важным. В оригинальной статье 2017 года Vaswani et al. ставили норму после residual connection:
post-norm (2017): x = LayerNorm(x + sublayer(x))
Это казалось логичным: сначала добавили правку, потом отнормировали результат. Но через несколько лет выяснилось, что такая схема плохо обучается на большой глубине. Работа Xiong et al. 2020 показала: если переставить норму перед sublayer-ом — обучение становится сильно стабильнее, не нужны хитрые warmup-расписания, можно делать сети глубиной в сотни слоёв:
pre-norm (модерн): x = x + sublayer(LayerNorm(x))
Все современные модели — GPT-2, GPT-3, LLaMA, Claude, Gemini — используют pre-norm. Идейно разница простая: в post-norm градиент при обратном проходе должен пройти через каждую LayerNorm, и это его потихоньку «душит». В pre-norm у градиента есть чистая магистраль через residual без LayerNorm-ов на пути.
Ещё один апгрейд, которого не было в 2017: современные модели вроде LLaMA используют RMSNorm вместо LayerNorm. RMSNorm — упрощённый вариант: пропускает шаг с вычитанием среднего, оставляет только rescaling. Чуть быстрее, чуть проще, на качество почти не влияет. Это типичный пример того, как архитектура продолжает чиститься после 2017-го: убрали лишнюю операцию — стало эффективнее.
Один блок целиком
Собираем всё вместе. Полный блок трансформера в современном стиле — это:
def transformer_block(x):
x = x + multi_head_attention(layer_norm(x)) # sublayer 1
x = x + feed_forward(layer_norm(x)) # sublayer 2
return x
Два sublayer-а, каждый обёрнут в pre-norm и residual. На входе — вектор размера d_model для каждого токена, на выходе — обновлённый вектор того же размера. Размерность сохраняется насквозь, потому что блоки складываются друг за другом и должны стыковаться.
Если убрать residual stream и оставить только sublayer-ы — сеть работать не будет. Если убрать LayerNorm — числа взорвутся через десяток блоков. Если убрать FFN — модель потеряет нелинейность и большую часть знания. Каждый кубик нужен.
Стэк: почему блоков много
Один блок добавляет к residual stream две правки — от attention и от FFN. Этого хватает на одну итерацию «уточнения» вектора. Но язык — штука сложная: чтобы понять предложение, нужно разобраться с синтаксисом, потом со смыслом фразы, потом со стилем, потом со скрытыми отсылками. Один блок столько не вытянет.
Поэтому блоков много. Они складываются в стэк — десятки одинаковых по архитектуре, но с разными весами. Каждый блок видит на входе результат предыдущего и добавляет свою порцию правок к residual stream.
Самая красивая часть: роли блоков никто не программирует. Никто не пишет «блок 1, ты занимайся синтаксисом, блок 50 — семантикой, блок 90 — абстракцией». Это распределение возникает эмерджентно — само, в процессе обучения градиентным спуском. Исследования интерпретируемости (Anthropic, Geva et al. 2021) подтверждают: нижние блоки действительно специализируются на поверхностных паттернах (буквы, морфология, n-граммы), средние — на смысле фраз и фактах, верхние — на абстрактных концепциях и финальном выборе токена.
Глубина и ширина — два независимых рычага масштабирования. Вот размеры нескольких известных моделей для калибровки масштаба:
| Модель | Блоков | Head | d_model |
|---|---|---|---|
| Оригинальный transformer (2017) | 6 | 8 | 512 |
| GPT-2 small | 12 | 12 | 768 |
| GPT-2 XL | 48 | 25 | 1600 |
| GPT-3 (175B) | 96 | 96 | 12288 |
| LLaMA-2 7B | 32 | 32 | 4096 |
| LLaMA-2 70B | 80 | 64 | 8192 |
Когда говорят «модель в N раз больше», обычно имеют в виду число параметров, которое примерно равно N_blocks × d_model² × ~12. Оба рычага делают модель умнее, но по-разному: больше блоков — глубже рассуждения, больший d_model — больше «комнат» для информации в каждом векторе.
Замыкаем круг
Теперь у нас есть всё, чтобы пройти через GPT от начала до конца за один проход. Я набросаю это последовательностью — каждый шаг разобрали в одной из статей серии.
1. Токены. Текст бьётся на куски подходящего размера через BPE — словарь обычно 50-100 тысяч токенов (пост 3).
2. Эмбеддинг. Каждый токен заменяется на вектор размера d_model через таблицу-словарь обучаемых эмбеддингов. Прибавляется позиционное кодирование, чтобы модель знала, в каком порядке идут токены (пост 3).
3. Стэк блоков. Вектор каждого токена проходит через N блоков. Внутри каждого блока — multi-head attention (токены смотрят друг на друга, пост 4) и FFN (каждый токен обрабатывается независимо), оба с pre-norm и residual.
4. Финальная нормализация и unembedding. На выходе последнего блока берётся вектор последнего токена в последовательности (того, после которого мы хотим что-то предсказать), нормализуется и умножается на матрицу-обратную к эмбеддингам — это превращает d_model-вектор в логиты длиной с весь словарь.
5. Softmax → распределение. Логиты пропускаются через softmax, получаем вероятности по всем 50000 токенам словаря: «space» — 0.41, «forward» — 0.18, «moment» — 0.07, и так далее (пост 2).
6. Сэмплируем. Выбираем один токен из распределения (с помощью temperature, top-k и top-p — это уже отдельная история). Дописываем его в контекст. Возвращаемся в шаг 1.
Это и есть полный жизненный цикл одного шага генерации. На длинном ответе вся эта машина прокручивается сотни раз — по разу на каждый токен. Когда вы видите в чате стриминг текста по словам, это буквально темп генерации.
Никакого секретного компонента в архитектуре нет. Всё, что мы разобрали, — это то, что Vaswani et al. описали в одной статье в 2017 году, плюс несколько технических улучшений за следующие годы (pre-norm, RMSNorm, RoPE, чуть другая нелинейность). Магия — не в архитектуре. Магия — в том, что эту довольно простую штуку обучили на гигантском корпусе текстов с задачей предсказывать следующий токен, и в процессе модель сама выработала всё, что мы называем «знанием»: грамматику, факты, способность рассуждать, стиль, чувство юмора, умение писать код. Веса FFN заполнились паттернами, attention-головы выучили типы связей, residual stream научился носить информацию между слоями.
Дальше в серии можно идти вглубь — про обучение и масштабирование, про дообучение и RLHF (как из «дополнялки» получается ChatGPT), про новые архитектурные эксперименты (mixture of experts, mamba, диффузионные текстовые модели). Простор есть.
- Attention Is All You Need Та самая статья 2017 года, в которой блок трансформера впервые был описан в нынешнем виде.
- A Mathematical Framework for Transformer Circuits Откуда взялась идея «residual stream» как общего канала связи между всеми слоями. Полезный язык для разговора об интерпретируемости.
- Transformer Feed-Forward Layers Are Key-Value Memories Очень изящная статья, показывающая, что FFN внутри блока — это не просто «нелинейность», а ассоциативная память, в которой модель хранит факты.
- On Layer Normalization in the Transformer Architecture Почему GPT-2/3, LLaMA и все остальные перешли на pre-norm вместо оригинального post-norm. Короткий ответ — стабильность обучения на большой глубине.