Трансформер не равен attention. Блок чередует смешивание позиций и нелинейное преобразование каждой позиции; residual оставляет прямой путь, normalization управляет масштабом. Глубина возникает из многократного повторения этой простой грамматики — и почти всё, что кажется в трансформере магией, при разборке оказывается свойством маршрута, а не свойством одной формулы.

Два вида обмена в одном тензоре

Пусть XRn×dX\in\mathbb R^{n\times d} — представления nn токенов, по строке на позицию. С этим прямоугольником можно сделать ровно две принципиально разные вещи: перемешать строки или перемешать столбцы. Self-attention занимается первым: позиция получает взвешенную смесь значений других позиций. Feed-forward network, или MLP, занимается вторым, обрабатывая каждую строку независимо и одинаково:

MLP(x)=W2ϕ(W1x+b1)+b2,xRd.\operatorname{MLP}(x)=W_2\,\phi(W_1x+b_1)+b_2,\qquad x\in\mathbb R^{d}.

Attention смешивает по длине, MLP смешивает по каналам. Ни одно из преобразований не заменяет другое. Внимание, если смотреть на него при замороженной карте весов, есть линейная комбинация values: никакой новой нелинейной функции признаков оно не производит. MLP умеет строить нелинейные комбинации, но для него пятый токен и первый живут в разных вселенных.

В pre-norm записи блок выглядит так:

X=X+MHA(LN(X)),Y=X+MLP(LN(X)).\begin{aligned} X'&=X+\operatorname{MHA}\bigl(\operatorname{LN}(X)\bigr),\\ Y&=X'+\operatorname{MLP}\bigl(\operatorname{LN}(X')\bigr). \end{aligned}

Устройство QQ, KK, VV разобрано в уроке про внимание; нам достаточно помнить формулу одной головы

Attn(X)=softmax ⁣(QKdh+M)V,Q=XWQ, K=XWK, V=XWV,\operatorname{Attn}(X)=\operatorname{softmax}\!\Bigl(\frac{QK^\top}{\sqrt{d_h}}+M\Bigr)V, \qquad Q=XW_Q,\ K=XW_K,\ V=XW_V,

где маска MM либо нулевая, либо запрещает смотреть вперёд:

Mij={0,ji,,j>i.M_{ij}=\begin{cases}0,&j\le i,\\[2pt]-\infty,&j>i.\end{cases}

Здесь нас интересует не одна формула, а весь маршрут: что в блоке несёт сигнал, что его преобразует и что удерживает масштаб в рабочем диапазоне.

Рисунок шире экрана — проведите по немуОткрыть целиком ↗
Схема pre-norm блока: вертикальный residual stream, две ветви через LN в MHA и в MLP, два сумматора, подписи числа параметров при d=512
Рис. 77.1. Грамматика pre-norm блока: две ветви и один сквозной путь

Золотая вертикаль — residual stream: путь, по которому представление проходит блок без единого умножения на обучаемую матрицу. Каждая ветвь сначала нормируется, затем преобразуется подслоем и возвращается в поток сложением. Подписаны точные размеры при d=512d=512: MHA — 1 050 624 параметра, MLP — 2 099 712, обе LayerNorm вместе — 2048 чисел.

Бюджет блока: где на самом деле лежат параметры

Внимание получает почти все картинки в популярных статьях, но не большинство весов. Посчитаем честно для d=512d=512, восьми голов и MLP с расширением 4. Проекции WQ,WK,WV,WOW_Q,W_K,W_V,W_O — четыре матрицы 512×512512\times512 плюс четыре вектора смещений:

PMHA=4d2+4d=1050624.P_{\mathrm{MHA}}=4d^2+4d=1\,050\,624 .

MLP состоит из W1Rd×4dW_1\in\mathbb R^{d\times4d}, W2R4d×dW_2\in\mathbb R^{4d\times d} и двух смещений:

PMLP=8d2+5d=2099712.P_{\mathrm{MLP}}=8d^2+5d=2\,099\,712 .

Два слоя LayerNorm добавляют по паре обучаемых векторов длины dd:

PLN=22d=2048.P_{\mathrm{LN}}=2\cdot2d=2048 .

Итого в блоке 31523843\,152\,384 параметра, из которых 66,6% приходится на MLP, 33,3% на внимание и 0,065% на нормировку. Отношение PMLP/PMHAP_{\mathrm{MLP}}/P_{\mathrm{MHA}} при d=512d=512 равно 1,999 и стремится к двум при росте dd: слагаемые с dd исчезающе малы рядом с d2d^2.

Рисунок шире экрана — проведите по немуОткрыть целиком ↗
Слева столбцы параметров MHA и MLP для d 128 256 512 1024 с подписью отношения около двух; справа доля параметров в MLP растёт с 49.9 процента при expansion 2 до 80 процентов при expansion 16
Рис. 77.2. Ширина MLP решает бюджет блока

Слева: при любом dd отношение держится около ×2,00\times2{,}00 — это чистая алгебра, 8d28d^2 против 4d24d^2. Справа: доля блока, лежащая в MLP, как функция расширения. При expansion 2 — 49,9%, при стандартном 4 — 66,6%, при 8 — 80,0%. Разговор «сколько параметров во внимании» бессмыслен без указания ширины MLP.

Residual: право ничего не менять

Каждый подслой обучает не новое представление, а поправку к старому:

y=x+F(x).y=x+F(x).

Разница с записью y=G(x)y=G(x) кажется косметической, но она меняет то, что модели даётся легко. Чтобы ничего не испортить, достаточно F0F\approx0 — нулевая матрица всегда в пределах досягаемости оптимизатора. Чтобы ничего не испортить в записи y=G(x)y=G(x), нужно попасть в тождественное отображение, а единичная матрица среди всех матриц ничем не выделена.

Для градиента разница ещё нагляднее:

yx=I+Fx,\frac{\partial y}{\partial x}=I+\frac{\partial F}{\partial x},

и через стек из LL подслоёв

y(L)x(0)==1L(I+J),J=Fx.\frac{\partial y^{(L)}}{\partial x^{(0)}}=\prod_{\ell=1}^{L}\bigl(I+J_\ell\bigr), \qquad J_\ell=\frac{\partial F_\ell}{\partial x}.

Единичное слагаемое не зависит ни от одного веса: даже если все JJ_\ell выродились, градиент дойдёт до первого слоя. Это не гарантия устойчивой оптимизации — произведение (I+J)\prod(I+J_\ell) прекрасно умеет расходиться, — но короткая дорога в архитектуре есть всегда. Механику самого произведения мы разбирали в уроке про обратное распространение, а похожую идею аддитивной памяти видели у LSTM, где ячейка тоже складывает, а не переписывает.

Обратите внимание, что́ именно тогда заметили: деградировала не тестовая, а обучающая ошибка. Проблема была не в переобучении, а в том, что оптимизатору трудно найти тождественное отображение там, где оно нужно.

Стек как сумма экспоненциально многих путей

Раскроем произведение для линейного случая F(x)=AxF_\ell(x)=A_\ell x:

x(L)==1L(I+A)x(0)=(I+A+<mAmA+)x(0).x^{(L)}=\prod_{\ell=1}^{L}(I+A_\ell)\,x^{(0)} =\Bigl(I+\sum_\ell A_\ell+\sum_{\ell<m}A_mA_\ell+\dots\Bigr)x^{(0)}.

Для L=2L=2 это видно целиком:

(I+A2)(I+A1)=Iпуть длины 0+A1+A2два пути длины 1+A2A1путь длины 2.(I+A_2)(I+A_1)=\underbrace{I}_{\text{путь длины }0} +\underbrace{A_1+A_2}_{\text{два пути длины }1} +\underbrace{A_2A_1}_{\text{путь длины }2}.

Скобки раскрываются в 2L2^L слагаемых, и каждое слагаемое — свой путь: список подслоёв, через которые сигнал прошёл, при том что остальные он обошёл по прямой. Сеть с residual — не одна глубокая цепочка, а ансамбль путей всех длин от нуля до LL.

Отсюда следует практический вывод: обнулить один подслой в обученной сети с residual обычно не смертельно — исчезает часть путей, а не весь сигнал. В сети без residual удаление слоя разрывает единственную цепь, и выход превращается в шум.

Давиденко: глубина как шаг по параметру

За «прибавлением маленькой поправки на каждом шаге» стоит старая вычислительная идея. В 1953 году Дмитрий Фёдорович Давиденко предложил решать нелинейную систему F(x)=0F(x)=0 не итерациями Ньютона из случайной точки, а погружением задачи в семейство

H(x,t)=F(x)(1t)F(x0),t[0,1],H(x,t)=F(x)-(1-t)F(x_0),\qquad t\in[0,1],

где при t=0t=0 решение известно (это x0x_0), а при t=1t=1 стоит исходная задача. Дифференцируя тождество H(x(t),t)=0H(x(t),t)=0 по параметру, он получил обыкновенное дифференциальное уравнение на траекторию решения:

dxdt=[Hx(x,t)]1Ht(x,t),\frac{dx}{dt}=-\bigl[H_x'(x,t)\bigr]^{-1}H_t'(x,t),

которое дальше интегрируется численно. Метод продолжения по параметру превращает «решить» в «доехать маленькими шагами от известного к искомому».

Сравните с residual-стеком:

x(+1)=x()+F(x())dxd=F(x,),  шаг Эйлера с Δ=1.x^{(\ell+1)}=x^{(\ell)}+F_\ell\bigl(x^{(\ell)}\bigr) \quad\Longleftrightarrow\quad \frac{dx}{d\ell}=F(x,\ell),\ \ \text{шаг Эйлера с }\Delta\ell=1 .

Это ровно шаг явного метода Эйлера по «времени глубины». Слои перестают быть независимыми преобразованиями и становятся дискретизацией непрерывного движения представления. Отсюда и характерное наблюдение практиков: в обученных трансформерах соседние блоки часто вносят похожие по направлению поправки, а перестановка двух соседних блоков портит результат меньше, чем можно было бы ожидать от «разных этапов обработки». Та же линия мысли позже дала непрерывные по глубине модели, где число слоёв заменяется шагом интегратора.

Layer normalization: что именно стирается

Для одного вектора позиции xRdx\in\mathbb R^{d}:

μ=1dj=1dxj,σ2=1dj=1d(xjμ)2,\mu=\frac1d\sum_{j=1}^{d}x_j,\qquad \sigma^2=\frac1d\sum_{j=1}^{d}(x_j-\mu)^2, LN(x)j=γjxjμσ2+ε+βj.\operatorname{LN}(x)_j=\gamma_j\,\frac{x_j-\mu}{\sqrt{\sigma^2+\varepsilon}}+\beta_j .

Статистики берутся по каналам внутри одной позиции: ни батч, ни соседние токены не участвуют. Поэтому один пример обрабатывается независимо от других, а на генерации длина контекста не меняет формулу — важное отличие от batch normalization, где статистики зависят от компании, в которой пример оказался.

До обучаемых γ,β\gamma,\beta нормировка стирает ровно две вещи: сдвиг всех координат на константу и общий положительный множитель. Формально для любых a>0a>0, bb

LN(ax+b1)=LN(x),\operatorname{LN}(ax+b\mathbf 1)=\operatorname{LN}(x),

то есть выход зависит только от направления вектора после вычитания среднего. Геометрически LN проецирует xx на гиперплоскость jzj=0\sum_j z_j=0 и растягивает результат до фиксированной длины: при ε=0\varepsilon=0, γ=1\gamma=1, β=0\beta=0

LN(x)22=j=1d(xjμ)2σ2=d,то естьLN(x)2=d.\|\operatorname{LN}(x)\|_2^2=\sum_{j=1}^{d}\frac{(x_j-\mu)^2}{\sigma^2}=d, \qquad\text{то есть}\qquad \|\operatorname{LN}(x)\|_2=\sqrt d .

Все позиции после нормировки живут на одной сфере радиуса d\sqrt d, и подслой получает на вход только направление.

Рисунок шире экрана — проведите по немуОткрыть целиком ↗
Реальная рукописная цифра из набора digits и её преобразование 3x плюс 7: до нормировки картинки и профили каналов разные, после LN профили совпадают, максимальная разность 4.6e-07
Рис. 77.3. Layer normalization стирает сдвиг и масштаб позиции

Реальные данные load_digits: вектор одной цифры длиной d=64d=64 и он же после преобразования 3x+73x+7. До LN профили каналов заметно разные. После LN они совпадают: максимум модуля разности 4,61074{,}6\cdot10^{-7} — остаточный след от ε=105\varepsilon=10^{-5}, а не настоящее различие. Средняя длина вектора после LN равна 8,000, то есть в точности 64\sqrt{64}, при разбросе исходных норм от 46,8 до 76,9.

Pre-norm и post-norm — не вопрос вкуса

Исходная работа 2017 года нормировала после сложения:

X=LN(X+MHA(X)).X'=\operatorname{LN}\bigl(X+\operatorname{MHA}(X)\bigr).

Современный вариант нормирует до подслоя, оставляя поток чистым:

X=X+MHA(LN(X)).X'=X+\operatorname{MHA}\bigl(\operatorname{LN}(X)\bigr).

Разница принципиальна для градиента. В post-norm между входом стека и выходом нет ни одного участка без нормировки: единичный путь проходит через LN на каждом слое и умножается на её якобиан. В pre-norm сквозной путь чист, поэтому градиент до нижних слоёв доходит без накопленного множителя — глубокие модели на post-norm без разогрева learning rate часто просто не стартуют.

Что происходит с сигналом по глубине

Проверим утверждения численно. Возьмём 24 случайных блока (d=64d=64, 16 позиций, выходные проекции масштабированы на 1/2L1/\sqrt{2L}), прогоним через них случайный вход и посмотрим на две величины: относительную норму потока и средний косинус между представлением позиции и её исходным embedding.

Рисунок шире экрана — проведите по немуОткрыть целиком ↗
Слева норма представления по 24 блокам: с residual растёт до 1.10, без residual падает до 0.09; справа косинус с исходным embedding: 0.89 с residual и 0.01 без него
Рис. 77.4. Residual удерживает представление сквозь глубину

С residual (синяя) поток за 24 блока меняет норму всего в 1,10 раза, а косинус с исходным вектором остаётся 0,89: позиция помнит, кто она. Без residual (красная) норма падает до 0,09 от начальной, а косинус — до 0,01, то есть до случайного направления. Post-norm (зелёная) при этой инициализации почти неотличим от pre-norm по forward-статистикам: разница между ними живёт в градиентах, и одного этого графика для выбора порядка нормировки недостаточно.

Косинус 0,89 не значит, что блоки ничего не сделали: 24 поправки заметно повернули вектор, и именно на этом повороте держится вся полезная работа сети. Но исходное содержимое никуда не исчезло — оно осталось главным слагаемым. И наоборот, падение косинуса само по себе не равно потере информации: представление может поворачиваться, сохраняя декодируемый признак. Проверять надо линейным probe, а не глазами.

Лаборатория блока

Сигнал через attention, MLP и residual

Загружается живая иллюстрация…

Веса в лаборатории случайные и фиксированные, обучения нет — измеряется именно маршрут сигнала через архитектуру. Отключайте подслои по одному и следите за тремя числами внизу.

Выключите residual — и на глубине 12 косинус с исходным embedding падает с 0,41 до 0,11, а норма потока проваливается: сеть перестаёт помнить вход. Выключите attention — и нижняя строка отчёта показывает ровно ноль: пути между позициями физически нет, сколько блоков ни ставь. Уберите нормировку и поднимите масштаб подслоя до 2 — за 12 блоков норма вырастает больше чем в миллион раз, и никакая точность float этого не спасёт. Верните pre-LN — и тот же масштаб становится безопасным.

Кто вообще может увидеть первый токен

Измерим маршрут напрямую. Возьмём causal-стек и оценим центральной разностью, насколько сдвинется представление последней позиции, если чуть шевельнуть embedding первой:

g=hn(L)x1hn(x1+ϵu)hn(x1ϵu)2ϵ,ϵ=104.g=\Bigl\|\frac{\partial h_n^{(L)}}{\partial x_1}\Bigr\| \approx\frac{\bigl\|h_n(x_1+\epsilon u)-h_n(x_1-\epsilon u)\bigr\|}{2\epsilon}, \qquad \epsilon=10^{-4}.

С attention при шести блоках получаем g0,90g\approx0{,}90. Без attention — в точности 0,00{,}0: не «маленькое число», а машинный нуль, и так на любой глубине от 1 до 12.

Рисунок шире экрана — проведите по немуОткрыть целиком ↗
Слева логарифмический график чувствительности последней позиции к первому токену: заметная величина с attention и машинный нуль без него; справа столбцы качества линейного probe для четырёх вариантов блока на реальных SMS
Рис. 77.5. Что блок доносит до головы

Слева: зависимость последней позиции от первой существует только при наличии attention; красная линия лежит на машинном нуле на всех глубинах. Справа: линейный probe поверх замороженных случайных блоков на реальных SMS. Синие столбцы — R2R^2 для доли редких слов в сообщении, золотые — accuracy распознавания спама; штриховая линия 0,70 — частота большинства.

Ablation на реальных SMS

Проверим то же самое на настоящем корпусе. Берём коллекцию SMS-сообщений (та же, что в уроке про наивный Байес), оставляем 2272 сообщения длиной не меньше 16 слов, кодируем первые 16 слов случайными фиксированными embeddings, прогоняем через два замороженных случайных блока и обучаем только линейный probe поверх представления последней позиции. Разбиение — 1590 сообщений на обучение и 682 на тест.

Цели две. Первая — доля редких слов в сообщении (в среднем 0,306): чтобы её оценить, последняя позиция обязана что-то знать обо всех остальных. Вторая — метка спама (доля спама 0,300, то есть базовая точность большинства 0,70).

вариантR2R^2 (доля редких слов)accuracy (спам)
полный блок0,850,77
без attention0,070,70
без MLP0,880,80
без positional0,790,78

Однозначно читается одна строка — и это честнее, чем хотелось бы. Без attention R2R^2 падает с 0,85 до 0,07, а спам определяется ровно на уровне частоты большинства: голова видит один последний токен и угадывает «не спам». Без positional embeddings качество проседает умеренно (0,85 → 0,79): смешивание сохраняется, теряется только различение порядка, а обе наши цели от порядка слов почти не зависят.

А вот строка «без MLP» показывает улучшение, и это не опечатка. Случайный необученный MLP не добавляет полезных признаков — он добавляет нелинейный шум поверх аккуратно перемешанных значений. Вывод из такого опыта строго один: эксперимент с замороженными весами измеряет маршрутизацию, а не выразительность. Чтобы измерить вклад MLP, его нужно обучать, и тогда картина меняется на противоположную.

Decoder, encoder и роль маски

Всё сказанное не зависит от того, куда смотрит внимание: маска решает лишь, какие пары позиций разрешены. Decoder-only модель использует causal mask, после LL блоков берёт представление позиции tt и превращает его в logits:

zt=Wvocabht(L)+b,p(wxt)=softmax(zt)w.z_t=W_{\mathrm{vocab}}\,h_t^{(L)}+b,\qquad p(w\mid x_{\le t})=\operatorname{softmax}(z_t)_w .

Обучающая цель — предсказание следующего токена. Во время обучения все позиции считаются параллельно, потому что правильные предыдущие токены известны; при генерации шаги последовательны, и KV-cache экономит пересчёт префикса. Weight tying связывает WvocabW_{\mathrm{vocab}} с матрицей входных embeddings: параметров меньше, а геометрия чтения и предсказания становится общей.

Encoder видит вход двунаправленно и строит контекстные представления для понимания; encoder–decoder добавляет cross-attention, где queries приходят из decoder, а keys и values — из encoder. Граница не абсолютна: decoder можно заставить классифицировать текстовым ответом, но интерфейс цели и цена вывода будут другими.

Цена блока: два разных режима

Сложность считается по двум слагаемым. Проекции и MLP линейны по длине и квадратичны по ширине, карта внимания — наоборот:

FLOPMHA8nd2+4n2d,FLOPMLP16nd2.\mathrm{FLOP}_{\mathrm{MHA}}\approx 8nd^2+4n^2d,\qquad \mathrm{FLOP}_{\mathrm{MLP}}\approx 16nd^2 .

Приравняв квадратичную часть внимания к стоимости MLP, получаем точку баланса 4n2d=16nd24n^2d=16nd^2, то есть n=4dn=4d; при d=512d=512 это n=2048n=2048. Но формула считает операции, а не время. Мы измерили реальный forward в numpy при d=512d=512 и float32.

Рисунок шире экрана — проведите по немуОткрыть целиком ↗
Слева логарифмические кривые измеренного времени MHA и MLP при длинах от 64 до 4096 с эталонными наклонами n и n в квадрате; справа отношение времён пересекает единицу между 512 и 1024
Рис. 77.6. Кто дороже — зависит от режима

При n=64n=64 внимание в пять-шесть раз дешевле MLP: квадратичная часть ещё ничтожна, а плотных умножений у него вдвое меньше. При n=4096n=4096 оно уже впятеро дороже. Отношение пересекает единицу между n=512n=512 и n=1024n=1024 — раньше теоретических 2048, потому что большие плотные умножения MLP лучше используют кэш и векторные регистры, чем цепочка операций внимания. Это одно измерение на одной машине, а не универсальная константа.

Генерация устроена иначе. Без кэша шаг tt пересчитывает весь префикс и стоит порядка 4t2d+8td24t^2d+8td^2, с кэшем — только новую строку:

FLOPtcache4td+8d2.\mathrm{FLOP}_t^{\text{cache}}\approx 4td+8d^2 .

Суммируя по tt до 512 при d=512d=512, получаем разницу в 274 раза.

Практический вывод из графика важнее самих чисел: оптимизировать надо ту часть, которая доминирует в вашем режиме. Ускорение внимания вдвое при n=64n=64 даст меньше десятой доли общего выигрыша. Ту же логику «одинаковые FLOP — разное время» мы уже разбирали на стоимости матричного умножения.

Что меняется с глубиной

Статистическая картина такая: ранние блоки чаще держат лексические и локальные признаки, средние собирают отношения между словами, поздние подстраиваются под выходную цель. Это тенденция, а не расписание, и она разная для разных корпусов и целей обучения.

Инструментов проверки два, и они отвечают на разные вопросы. Linear probe обучает простой классификатор поверх замороженных представлений слоя: если признак декодируется, он там присутствует линейно. Activation patching подменяет активацию одного запуска активацией другого и смотрит, перевернётся ли ответ: это уже вопрос о причинном влиянии. Признак может прекрасно декодироваться и при этом не использоваться моделью.

Визуализации карт внимания обязаны дополняться такими вмешательствами. Отдельная голова — лишь одна ветвь residual stream среди сотен, и красивая диагональ на heatmap сама по себе ничего не доказывает.

Мини-исследование: где хранится признак

Составьте предложения с двумя именами и отношением: «Анна выше Бориса». После нескольких отвлекающих фраз задайте вопрос, кто ниже. Для каждого слоя обучите probe, предсказывающий отношение из представления последнего токена, — получится кривая декодируемости по глубине. Затем возьмите пару примеров с противоположными отношениями и подмените активацию выбранного слоя и позиции. Если ответ переворачивается, участок потока не просто содержит признак, а влияет на результат.

Измеряйте изменение log-odds, а не только итоговый класс: класс меняется скачком и скрывает величину эффекта. И сравнивайте с контролем: тот же патч на случайной позиции того же слоя эффекта давать не должен.

Мини-исследование: свой crossover

Повторите замер из рис. 77.6 на своём железе. Зафиксируйте dd, точность (float32 или float64), число повторов и прогрев; профилируйте MHA и MLP отдельно; постройте отношение времён и найдите свою точку перелома. Затем повторите при расширении 2 и 8. Проверьте предсказание n=4dn=4d и объясните расхождение — оно почти наверняка будет.

Отдельно измерьте генерацию с KV-cache и без него и обязательно напишите unit-тест: logits полного префикса и пошагового декодирования с кэшем должны совпадать в пределах численной погрешности на нескольких длинах. Этот тест ловит самую частую ошибку реализации — рассинхронизацию позиций в кэше. Разговор о том, во что превращается вся эта арифметика при масштабировании модели, ждёт нас в уроке про законы масштабирования.

Блок как система путей

Трансформер — композиция двух операций и двух инфраструктурных опор. Attention смешивает позиции и создаёт единственный канал между ними; MLP перестраивает каналы и добавляет нелинейность, забирая две трети параметров; residual держит сквозной путь, вдоль которого сеть распадается на 2L2^L маршрутов разной длины; LayerNorm убирает сдвиг и масштаб на входе каждой ветви, не трогая сам поток. Понимание блока начинается не с рисунка из сотни прямоугольников, а с отключения частей и трёх контрольных чисел: норма, косинус, влияние соседа.

Задачи