🔴 Сложный ⏱️ 60 минут

Transformers

📋 Содержание урока

Transformers (трансформеры)

Если бы нужно было выбрать одну-единственную архитектуру, которая определила лицо искусственного интеллекта последних лет, это была бы архитектура трансформера. GPT-4 и GPT-5, Claude, LLaMA, Gemini, BERT, T5, Stable Diffusion в текстовой части, Midjourney — за каждым из этих названий стоит один и тот же архитектурный скелет, придуманный в 2017 году и с тех пор почти не менявшийся в своей основе. Когда ты читаешь про «большую языковую модель на 70 миллиардов параметров», за словом «модель» почти всегда скрывается именно трансформер — стопка одинаковых блоков self-attention и полносвязных слоёв, повторённая десятки или сотни раз. Понимание того, как устроен этот блок и почему он вообще работает, — не факультативная деталь, а буквально пропуск к пониманию всего современного ИИ.

В прошлом уроке ты разобрал self-attention — механизм, который вычисляет для каждого токена взвешенную сумму по всем остальным токенам последовательности через тройку query-key-value и формулу $\mathrm{Attention}(Q,K,V)=\mathrm{softmax}(QK^T/\sqrt{d_k})\cdot V$. Это была важная, но пока изолированная деталь: attention исторически появился как надстройка над рекуррентной сетью (урок 341) — LSTM или GRU (урок 340) по-прежнему обрабатывали последовательность токен за токеном, а attention лишь помогал декодеру «подглядывать» в нужные места входа вместо использования единственного сжатого контекстного вектора. Статья 2017 года «Attention Is All You Need» задала куда более радикальный вопрос: а что, если рекуррентность вообще не нужна? Что, если можно построить всю модель — и энкодер, и декодер — исключительно на attention, полностью выбросив LSTM и GRU из архитектуры?

Ответ оказался не просто «да, можно», а «да, и результат при этом ощутимо лучше и обучается на порядок быстрее». Причина в одном слове — параллелизация. Рекуррентная сеть обрабатывает последовательность строго последовательно: чтобы вычислить состояние на шаге $t$, необходимо уже знать состояние на шаге $t-1$, а для него — состояние на шаге $t-2$, и так далее до самого начала. Ни один графический процессор в мире не ускорит эту цепочку зависимостей — сколько бы тысяч вычислительных ядер ни было доступно одновременно, шаг $t$ физически не может начаться раньше, чем закончится шаг $t-1$. Self-attention устроен принципиально иначе: чтобы вычислить представление токена на позиции $5$, ему не нужно ждать, пока будет вычислено представление токена на позиции $4$, — все токены обрабатываются одновременно через одно и то же матричное умножение. Это отличие — не мелкая техническая деталь, а именно то, что сделало возможным обучение моделей на миллиардах и триллионах токенов текста за разумное время.

Сегодняшний урок разберёт трансформер по частям. Сначала — почему отказ от рекуррентности и переход к полной параллельности стал переломным моментом, с конкретными числами по скорости обучения. Затем — multi-head attention, то есть несколько независимых «голов» внимания, работающих параллельно и способных выучивать разные типы зависимостей между словами одновременно. Дальше — позиционное кодирование: раз self-attention сам по себе не различает порядок токенов (в отличие от рекуррентной сети, где порядок буквально встроен в саму последовательность вычислений), нужно явно добавить информацию о позиции через синусы и косинусы разных частот — ты уже встречал эту формулу в уроке 208 университетского курса про ряды Фурье, где было прямо анонсировано её появление здесь. Затем — архитектура энкодера трансформера, которая опирается сразу на две вещи из прошлых уроков: skip connections (урок 337) и layer normalization (урок 332). И наконец — коротко о декодере и маскированном внимании, которое не даёт модели «подглядывать» в ещё не сгенерированные токены.

К концу урока у тебя будет полная картина архитектуры, которая привела к взрывному масштабированию моделей последних лет: не потому, что кто-то придумал «более умный» алгоритм в абстрактном смысле, а потому, что параллелизация в сочетании с attention физически позволила увеличивать и данные, и размер моделей на порядки, оставаясь в рамках разумного времени обучения. Именно эта архитектура станет фундаментом для следующего урока про BERT, GPT и большие языковые модели.

История

К 2017 году исследователи машинного перевода уже накопили немалый опыт с архитектурой encoder-decoder (урок 341) поверх рекуррентных сетей LSTM или GRU (урок 340), усиленной механизмом внимания (урок 342). Эта комбинация работала заметно лучше, чем простой encoder-decoder без attention, — модель перестала «забывать» начало длинного предложения к его концу. Но узкое место осталось прежним: сама рекуррентность. Даже с attention декодер и энкодер по-прежнему обрабатывали последовательность строго по одному токену за раз, и это накладывало жёсткий потолок на то, насколько быстро вообще можно обучить модель на больших объёмах данных — время обучения росло почти линейно с длиной последовательности просто из-за невозможности распараллелить шаги.

Команда исследователей Google — Ашиш Васвани, Ноам Шазир, Ники Пармар, Якоб Ушкорайт, Ллион Джонс, Айдан Гомес, Лукаш Кайзер и Илья Полосухин — опубликовала в 2017 году статью с намеренно провокационным названием «Attention Is All You Need» («Внимание — это всё, что тебе нужно»). Основная идея была почти дерзкой по меркам того времени: полностью убрать из архитектуры рекуррентные слои и свёрточные слои, оставив только self-attention (урок 342) и обычные полносвязные слои. До этой статьи attention всегда был вспомогательным механизмом поверх рекуррентности; в новой архитектуре он стал единственным механизмом, отвечающим за то, чтобы модель вообще «видела» зависимости между разными частями последовательности.

Результат оказался убедительным сразу по двум направлениям. Во-первых, качество: на задаче машинного перевода с английского на немецкий трансформер большой конфигурации достиг $28{,}4$ BLEU — на тот момент нового рекорда, превзойдя все предыдущие архитектуры, включая ансамбли рекуррентных моделей. Во-вторых, и это не менее важно, скорость обучения: базовая модель достигла конкурентоспособного качества перевода примерно за $12$ часов обучения на $8$ видеокартах NVIDIA P100, а большая модель — примерно за $3{,}5$ дня на тех же $8$ видеокартах, тогда как сопоставимые по качеству рекуррентные архитектуры того времени требовали заметно больше вычислительного времени на сопоставимом или большем количестве оборудования. Статья быстро стала одной из самых влиятельных работ в истории машинного обучения: уже через год на её основе появился BERT (2018), затем GPT (2018) и его последующие версии, T5, а затем и вся современная экосистема больших языковых моделей, включая архитектуры, стоящие за Claude, LLaMA и десятками других систем.

Отказ от рекуррентности: параллелизация как переломный момент

Интуиция

Представь два способа обработать очередь из ста писем. Первый способ — читать их строго по одному, от первого к последнему, и после каждого письма делать пометку, которая понадобится при чтении следующего (например, «это уже третье письмо от этого отправителя»). Такую работу нельзя поручить сразу нескольким помощникам: помощник, читающий письмо номер $50$, обязан сначала узнать пометку, сделанную после письма номер $49$, а та, в свою очередь, зависит от пометки после письма $48$, и так по цепочке до самого начала. Сто помощников за соседними столами тут не помогут — работа физически последовательна.

Второй способ — сразу разложить все сто писем на столе одновременно и дать каждому из ста помощников по одному письму, попросив каждого также сверяться с содержимым всех остальных девяноста девяти писем сразу (кто от кого, какая тема с какой связана). Здесь работа отдельного помощника не зависит от того, закончил ли сосед, — все сто отчётов можно писать параллельно, лишь бы под рукой были копии всех писем. Ровно в этом и состоит разница между рекуррентной сетью и self-attention: RNN и LSTM (уроки 339–340) — это первый способ, где скрытое состояние $h_t$ обязательно зависит от $h_{t-1}$; self-attention (урок 342) — это второй способ, где представление каждого токена вычисляется одной и той же операцией сразу для всех позиций через матричное произведение $QK^T$.

Формула

Сравнение по трём характеристикам за слой (по числу операций). Пусть $n$ — длина последовательности, $d$ — размерность представления. Для одного слоя self-attention и для одного рекуррентного слоя (LSTM/GRU) три ключевые характеристики таковы:

$$\text{self-attention: сложность } O(n^2 \cdot d), \quad \text{число последовательных шагов } O(1), \quad \text{максимальная длина пути } O(1)$$

$$\text{рекуррентный слой: сложность } O(n \cdot d^2), \quad \text{число последовательных шагов } O(n), \quad \text{максимальная длина пути } O(n)$$

«Число последовательных шагов» — это минимальное количество операций, которые обязаны выполняться строго друг за другом и потому не могут быть распараллелены независимо от объёма доступного оборудования. «Максимальная длина пути» — это наибольшее расстояние (в шагах вычислительного графа), которое должен пройти сигнал (и, соответственно, градиент при обратном проходе), чтобы связать два произвольных токена последовательности.

Разбор примеров

Пример 1 (число последовательных шагов не зависит от мощности оборудования). Возьмём предложение длиной $n=100$ токенов. Для рекуррентной сети скрытое состояние $h_{100}$ определяется формулой $h_t = f(h_{t-1}, x_t)$, поэтому, чтобы вычислить $h_{100}$, необходимо сначала вычислить $h_{99}$, для чего нужен $h_{98}$, и так далее — ровно $100$ последовательных вычислительных шагов, ни один из которых нельзя начать раньше завершения предыдущего. Даже если выделить под задачу видеокарту с десятками тысяч ядер, эти $100$ шагов всё равно будут выполняться друг за другом — параллельным окажется только вычисление внутри одного шага (умножения матриц весов LSTM), а не сама последовательность шагов. Self-attention для той же последовательности из $100$ токенов вычисляет матрицу $QK^T$ размера $100\times100$ одним матричным умножением — операцией, которая по своей природе раскладывается на независимые произведения, легко распределяемые по тысячам ядер GPU одновременно. Число обязательных последовательных шагов здесь — $O(1)$: оно не растёт с длиной последовательности вообще.

Пример 2 (сравнение вычислительной сложности по FLOPs для типичного предложения). Для машинного перевода типичная длина предложения — порядка $n=100$ токенов, а размерность представления в базовой конфигурации трансформера — $d=512$. Посчитаем сложность одного слоя по формуле выше: self-attention — $n^2 \cdot d = 100^2 \cdot 512 = 5\,120\,000$ условных операций; рекуррентный слой — $n \cdot d^2 = 100 \cdot 512^2 = 26\,214\,400$ условных операций. При типичном соотношении $n < d$ (длина предложения меньше размерности представления) self-attention оказывается почти в $5{,}1$ раза экономнее по числу операций на слой, чем рекуррентный слой, — и это без учёта того, что «операции» self-attention параллелятся полностью, а операции рекуррентного слоя нет.

Пример 3 (реальные числа обучения из статьи 2017 года). Базовая конфигурация трансформера достигла конкурентоспособного качества перевода (BLEU $27{,}3$ на английско-немецкой паре) примерно за $12$ часов обучения на $8$ видеокартах NVIDIA P100. Большая конфигурация — за примерно $3{,}5$ суток на тех же $8$ видеокартах, достигнув нового рекорда качества $28{,}4$ BLEU. Для сравнения: предыдущие архитектуры на основе рекуррентных сетей с attention, показывавшие сопоставимое или более низкое качество перевода (например, GNMT — архитектура Google Neural Machine Translation, дававшая около $24{,}6$ BLEU на той же задаче), требовали заметно больше вычислительного времени именно из-за невозможности распараллелить обработку последовательности по временно́й оси — узкое место было не в объёме вычислений на видеокарте, а в вынужденном последовательном порядке этих вычислений.

Почему это важно

Именно здесь находится прямой ответ на вопрос, почему архитектура трансформера привела к взрывному масштабированию моделей последних лет. Дело не в том, что self-attention «умнее» рекуррентности в абстрактном смысле — как показывает пример 2, по чистому числу операций на слой self-attention даже не всегда выигрывает при больших $n$. Дело в том, что современные ускорители — графические и тензорные процессоры (GPU и TPU) — устроены как машины для параллельного выполнения огромного количества независимых операций одновременно, и почти бесполезны для длинных цепочек строго последовательных зависимостей. Рекуррентная сеть структурно не может использовать эту параллельность вдоль временно́й оси, сколько бы ни было ядер в видеокарте. Self-attention структурно может — вся последовательность обрабатывается одним набором матричных умножений. Это отличие превращает время обучения из «растёт примерно линейно с длиной последовательности и упирается в физический потолок скорости одного ядра» в «ограничено в основном объёмом видеопамяти и пропускной способностью шины данных» — а это уже вопрос, который решается покупкой большего числа видеокарт, а не ожиданием более быстрого одного ядра. Именно эта смена узкого места — с «нельзя распараллелить по определению» на «можно распараллелить, если хватит железа» — и открыла дорогу к обучению моделей на несопоставимо больших объёмах текста и с несопоставимо большим числом параметров, чем это было практически осуществимо для рекуррентных архитектур.

Multi-head attention: несколько независимых голов внимания

Интуиция

Формула self-attention из урока 342 — $\mathrm{Attention}(Q,K,V)=\mathrm{softmax}(QK^T/\sqrt{d_k})\cdot V$ — вычисляет ровно одно распределение весов внимания для каждого токена: один-единственный набор чисел, суммирующихся в единицу, определяющий, «куда смотрит» токен. Но естественный язык одновременно содержит несколько разных типов связей между словами: согласование подлежащего и сказуемого, кореференцию местоимений («она» — «девочка»), синтаксическую близость соседних слов, смысловую тематическую связь между словами, далеко расположенными друг от друга. Заставлять одно-единственное распределение внимания обслуживать все эти разные типы связей сразу — всё равно что просить одного переводчика на международной конференции одновременно следить за грамматикой, тоном голоса, культурным контекстом и содержанием доклада: получится компромисс по каждому направлению вместо качественной работы по каждому из них.

Multi-head attention решает эту проблему буквально «в лоб»: вместо одного attention считается сразу несколько — скажем, восемь — независимых attention-блоков параллельно, каждый со своими обучаемыми матрицами проекций для $Q$, $K$, $V$. Поскольку веса каждой «головы» независимы, ничто не мешает одной голове специализироваться на кореференции местоимений, другой — на синтаксической близости, третьей — на дальних смысловых связях. После того как все головы отработали параллельно, их результаты просто склеиваются (конкатенируются) в один вектор и пропускаются через ещё одно линейное преобразование, чтобы вернуть исходную размерность.

Формула

Multi-head attention. Пусть $d_{model}$ — размерность представления токена, $h$ — число голов, $d_k=d_v=d_{model}/h$ — размерность одной головы. Для каждой головы $i=1,\dots,h$ вычисляется отдельный self-attention с собственными обучаемыми матрицами проекций:

$$\mathrm{head}_i = \mathrm{Attention}(QW_i^Q,\, KW_i^K,\, VW_i^V)$$

где $W_i^Q, W_i^K \in \mathbb{R}^{d_{model}\times d_k}$, $W_i^V \in \mathbb{R}^{d_{model}\times d_v}$. Результаты всех голов конкатенируются по последней оси и пропускаются через финальную матрицу проекции $W^O \in \mathbb{R}^{h d_v \times d_{model}}$:

$$\mathrm{MultiHead}(Q,K,V) = \mathrm{Concat}(\mathrm{head}_1,\dots,\mathrm{head}_h)\, W^O$$

В оригинальной статье используется конфигурация $d_{model}=512$, $h=8$, откуда $d_k=d_v=512/8=64$.

Разбор примеров

Пример 1 (размерности сходятся: проверка согласованности архитектуры). При $d_{model}=512$ и $h=8$ получаем $d_k=d_v=512/8=64$. Каждая голова выдаёт вектор размерности $64$ для каждого токена; после конкатенации восьми таких векторов получаем вектор размерности $8\times64=512$ — ровно исходную размерность $d_{model}$. Это не случайность архитектуры, а намеренный выбор: результат multi-head attention должен иметь ту же размерность, что и вход, чтобы его можно было напрямую сложить со входом через skip connection (об этом — в разделе про энкодер).

Пример 2 (разные головы выучивают разные типы зависимостей — качественная иллюстрация). Возьмём предложение «Девочка играла с собакой, которая была очень доброй, и она была счастлива». При обработке токена «она» разные головы attention могут выучить принципиально разные распределения весов. Голова, специализирующаяся на кореференции местоимений, может дать распределение, сильно сосредоточенное на «Девочка»: условно $[0{,}70;\ 0{,}03;\ 0{,}02;\ 0{,}05;\ 0{,}02;\ 0{,}03;\ 0{,}10;\ 0{,}05]$ по восьми предыдущим токенам, где наибольший вес приходится именно на «Девочка». Другая голова, специализирующаяся на ближайшем синтаксическом контексте, может дать совсем другое распределение с максимумом на соседнем слове «счастлива» или на союзе «и»: условно $[0{,}05;\ 0{,}05;\ 0{,}05;\ 0{,}05;\ 0{,}05;\ 0{,}05;\ 0{,}60;\ 0{,}10]$. Если бы у модели была только одна голова, ей пришлось бы находить компромиссное распределение между этими двумя задачами — размывая точность обеих. При восьми независимых головах каждая может специализироваться, а собранный вместе (через конкатенацию) результат содержит информацию сразу обоих типов.

Пример 3 (подсчёт параметров одного блока multi-head attention). Матрицы $W^Q$, $W^K$, $W^V$ на практике реализуются не как $h$ отдельных маленьких матриц, а как единые матрицы размера $d_{model}\times d_{model}$ каждая (поскольку $h\cdot d_k = d_{model}$), которые затем логически «нарезаются» на $h$ голов. При $d_{model}=512$ число параметров одной такой матрицы — $512\times512=262\,144$. Матриц три ($W^Q$, $W^K$, $W^V$), плюс финальная матрица $W^O$ той же размерности $512\times512$. Итого: $4\times262\,144=1\,048\,576$ параметров (без учёта смещений) на один блок multi-head attention — что уже само по себе больше миллиона параметров на один-единственный такой блок в самой первой, самой компактной конфигурации трансформера.

Почему это важно

Multi-head attention — это прямой ответ на ограничение обычного self-attention: одно распределение внимания физически не может одновременно точно отразить несколько разных типов лингвистических связей. Разбивая единое пространство представления размерности $d_{model}$ на $h$ независимых подпространств меньшей размерности $d_k$, архитектура даёт модели возможность выучить несколько параллельных «взглядов» на одну и ту же последовательность практически бесплатно по вычислительным затратам: как показывает арифметика примера 3 (для сравнения смотри задание 26 в практической части), суммарная вычислительная стоимость $h$ голов размерности $d_k=d_{model}/h$ каждая примерно равна стоимости одной головы полной размерности $d_{model}$ — то есть богатство представления, которое даёт multi-head attention, не оплачивается пропорциональным ростом вычислений. Именно многоголовость позволяет трансформеру одновременно улавливать и синтаксис, и семантику, и дальние, и ближние зависимости в одном проходе через один слой.

Позиционное кодирование: как трансформер узнаёт порядок токенов

Интуиция

У self-attention есть скрытая особенность, о которой легко забыть: формула $\mathrm{softmax}(QK^T/\sqrt{d_k})\cdot V$ сама по себе совершенно не различает порядок токенов. Если переставить местами два токена во входной последовательности, каждая пара «запрос-ключ» по-прежнему даст то же самое скалярное произведение, что и раньше, — просто с других позиций. Self-attention обрабатывает вход как множество (набор), а не как упорядоченную последовательность: для него предложения «кот ест рыбу» и «рыбу ест кот» после первого слоя self-attention дали бы одинаковый (лишь переставленный) набор представлений токенов, если бы не было никакой дополнительной информации о позиции. Это резко контрастирует с рекуррентной сетью (уроки 339–340), где порядок токенов встроен в саму структуру вычислений: скрытое состояние $h_t$ по построению зависит от $t$-го шага обработки, и переставить токены местами означало бы буквально изменить последовательность вычислений $h_1, h_2, \dots$.

Раз self-attention не хранит порядок «бесплатно», его нужно добавить явно — до того, как токены попадут в первый слой self-attention. Решение авторов трансформера: к каждому векторному представлению токена (эмбеддингу) прибавляется отдельный вектор той же размерности, кодирующий позицию токена в последовательности, — позиционное кодирование (positional encoding). Этот вектор строится не из обучаемых параметров, а из фиксированных значений синуса и косинуса разных частот — той же самой математической конструкции, с которой ты уже встречался в уроке 208 про ряды Фурье, где было прямо анонсировано её появление именно здесь.

Формула

Позиционное кодирование (Васвани и др., 2017). Пусть $\mathrm{pos}$ — позиция токена в последовательности ($\mathrm{pos}=0,1,2,\dots$), $d$ — размерность модели ($d=d_{model}$), $i=0,1,\dots,d/2-1$ — индекс пары координат. Значения позиционного кодирования на чётных и нечётных координатах вектора задаются формулами:

$$PE(\mathrm{pos}, 2i) = \sin\!\left(\frac{\mathrm{pos}}{10000^{2i/d}}\right), \qquad PE(\mathrm{pos}, 2i+1) = \cos\!\left(\frac{\mathrm{pos}}{10000^{2i/d}}\right)$$

Итоговый вход первого слоя энкодера или декодера — сумма эмбеддинга токена и его позиционного кодирования: $x = \mathrm{embedding}(\mathrm{token}) + PE(\mathrm{pos})$.

Разбор примеров

Пример 1 (полный численный расчёт для нескольких позиций при малой размерности). Возьмём игрушечную размерность $d=4$ (в реальном трансформере $d=512$, но принцип виден и на малом $d$), то есть $i\in\{0,1\}$. Для $i=0$: показатель степени $2i/d = 0/4=0$, значит $10000^0=1$, и частота максимальна — $PE(\mathrm{pos},0)=\sin(\mathrm{pos})$, $PE(\mathrm{pos},1)=\cos(\mathrm{pos})$. Для $i=1$: показатель степени $2i/d=2/4=0{,}5$, значит $10000^{0{,}5}=100$, и частота в $100$ раз ниже — $PE(\mathrm{pos},2)=\sin(\mathrm{pos}/100)$, $PE(\mathrm{pos},3)=\cos(\mathrm{pos}/100)$. Посчитаем векторы для позиций $\mathrm{pos}=0,1,2,3$:

  • $\mathrm{pos}=0$: $[\sin 0,\ \cos 0,\ \sin 0,\ \cos 0] = [0{,}0000;\ 1{,}0000;\ 0{,}0000;\ 1{,}0000]$
  • $\mathrm{pos}=1$: $[\sin 1,\ \cos 1,\ \sin 0{,}01,\ \cos 0{,}01] = [0{,}8415;\ 0{,}5403;\ 0{,}0100;\ 0{,}9999]$
  • $\mathrm{pos}=2$: $[\sin 2,\ \cos 2,\ \sin 0{,}02,\ \cos 0{,}02] = [0{,}9093;\ -0{,}4161;\ 0{,}0200;\ 0{,}9998]$
  • $\mathrm{pos}=3$: $[\sin 3,\ \cos 3,\ \sin 0{,}03,\ \cos 0{,}03] = [0{,}1411;\ -0{,}9900;\ 0{,}0300;\ 0{,}9996]$

Видно, что первая пара координат (высокая частота, $i=0$) меняется быстро от позиции к позиции, а вторая пара (низкая частота, $i=1$) меняется медленно и почти линейно на этом коротком отрезке — ровно как разные гармоники в разложении Фурье из урока 208, где низкие частоты отвечают за плавные, а высокие — за быстро меняющиеся составляющие сигнала. Каждой позиции соответствует уникальный, легко отличимый от соседей набор чисел.

Пример 2 (выполнение обещания урока 208: линейная выразимость сдвига позиции). В уроке 208 (задание 25) было доказано в общем виде, что $PE(\mathrm{pos}+k, 2i)$ выражается как линейная комбинация $PE(\mathrm{pos},2i)$ и $PE(\mathrm{pos},2i+1)$ с коэффициентами, зависящими только от сдвига $k$ (а не от $\mathrm{pos}$), — это прямое следствие формул синуса и косинуса суммы. Проверим это на конкретных числах из примера 1 при частоте $\omega=1$ (случай $i=0$): для $\mathrm{pos}=1$ и сдвига $k=2$ по формуле сложения аргументов

$$\sin(\mathrm{pos}+k) = \sin(\mathrm{pos})\cos(k) + \cos(\mathrm{pos})\sin(k)$$

Подставим $\sin 1 = 0{,}8415$, $\cos 1 = 0{,}5403$, $\sin 2 = 0{,}9093$, $\cos 2 = -0{,}4161$:

$$\sin(1+2) = 0{,}8415\cdot(-0{,}4161) + 0{,}5403\cdot0{,}9093 = -0{,}3502 + 0{,}4913 = 0{,}1411$$

Это в точности совпадает с прямым вычислением $\sin 3 = 0{,}1411$ из примера 1. Практический смысл: сеть может научиться распознавать относительное смещение между двумя позициями (например, «токен на три позиции правее») через простое линейное преобразование их позиционных кодировок, не завися явно от абсолютной позиции, — это упрощает выучивание относительных зависимостей вроде согласования соседних слов вне зависимости от того, где именно в предложении они находятся.

Пример 3 (что было бы, если убрать позиционное кодирование). Возьмём токенизированное предложение «Кот ест рыбу» и его перестановку «Рыбу ест кот». Без позиционного кодирования на вход первого слоя self-attention поступили бы ровно те же три эмбеддинга токенов, просто в другом порядке индексов внутри последовательности, а сама формула $\mathrm{Attention}(Q,K,V)$ оперирует множеством пар «запрос-ключ», не зная, какой индекс к какой позиции относится содержательно, — набор итоговых представлений токенов оказался бы просто переставленной версией того же самого набора чисел. Модель не смогла бы отличить «кто кого ест» — подлежащее от дополнения, — потому что синтаксическая роль слова в русском и многих других языках существенно зависит от порядка слов (или, как минимум, модель лишилась бы одного из важнейших сигналов для этого различения). Добавление $PE(\mathrm{pos})$ разрывает эту симметрию: эмбеддинг «кот» на позиции $0$ получает другую добавку, чем эмбеддинг «кот», окажись он на позиции $2$, — и это отличие сохраняется и используется на всех последующих слоях.

Почему это важно

Позиционное кодирование — это именно та деталь, которая позволяет трансформеру сохранить преимущество полной параллелизации (раздел выше) и при этом не потерять информацию о порядке токенов, которую рекуррентная сеть получала «бесплатно» просто в силу своей последовательной природы вычислений. Выбор конкретно синус-косинусной конструкции с геометрически убывающими частотами — не произвольная деталь: как показано в примере 2, эта конструкция позволяет выражать относительный сдвиг позиции линейным преобразованием, а сочетание высоких и низких частот (пример 1) даёт каждой позиции уникальный, устойчиво различимый «отпечаток», работающий даже для длин последовательностей, не встречавшихся во время обучения, — в отличие от альтернативного подхода с обучаемыми векторами позиций, которые определены только для позиций, виденных при обучении. Связь с рядами Фурье из урока 208 здесь не поверхностная аналогия: и там, и здесь работает одна и та же идея — представить сигнал (в одном случае периодическую функцию, в другом — позицию в последовательности) через набор синусоид разных частот, из которых при необходимости можно восстановить исходную информацию.

Архитектура энкодера: self-attention, skip connections и layer normalization

Интуиция

Один слой энкодера трансформера — это не просто self-attention сам по себе, а аккуратно собранная конструкция из двух подслоёв: multi-head self-attention (разобранный выше) и обычная полносвязная сеть, применяемая независимо к каждой позиции (position-wise feed-forward network). Ключевая деталь в том, как эти подслои встроены в общий поток вычислений: вокруг каждого из них — точно такая же обёртка, что ты уже видел в уроке 337 про ResNet, — skip connection, складывающий вход подслоя с его выходом, за которым следует нормализация (урок 332), только здесь это не batch normalization, а layer normalization, которая нормализует по признакам внутри одного примера, а не по батчу, и потому не зависит от размера батча и длины последовательности. Формула из урока 337 — $y = x + \mathrm{Layer}(\mathrm{Norm}(x))$ — оказывается буквально той же самой формулой, только на месте $\mathrm{Layer}$ теперь стоит либо multi-head attention, либо feed-forward сеть. Один такой энкодерный блок повторяется несколько раз подряд (в оригинальной статье — $N=6$ раз), каждый раз с собственными обучаемыми весами.

Формула

Один слой энкодера трансформера. Пусть $x$ — вход слоя (последовательность векторов токенов). Слой состоит из двух подслоёв, каждый обёрнут в остаточную связь и нормализацию:

$$x_1 = \mathrm{LayerNorm}\big(x + \mathrm{MultiHead}(x,x,x)\big)$$

$$x_2 = \mathrm{LayerNorm}\big(x_1 + \mathrm{FFN}(x_1)\big)$$

где полносвязная сеть внутри слоя задаётся как

$$\mathrm{FFN}(x) = \max(0,\, xW_1+b_1)\,W_2 + b_2$$

с $W_1\in\mathbb{R}^{d_{model}\times d_{ff}}$, $W_2\in\mathbb{R}^{d_{ff}\times d_{model}}$ и типичным значением $d_{ff}=2048$ при $d_{model}=512$ в базовой конфигурации. Обрати внимание, что в вызове $\mathrm{MultiHead}(x,x,x)$ все три аргумента — запросы, ключи и значения — получаются из одного и того же $x$: это и есть self-attention, «внимание на себя», как впервые отмечалось в уроке 342.

Разбор примеров

Пример 1 (численный проход одного слоя энкодера на игрушечном векторе). Возьмём вектор представления одного токена размерности $d_{model}=4$: $x=(1{,}0;\ 2{,}0;\ -1{,}0;\ 0{,}0)$. Пусть выход multi-head attention для этого токена (уже посчитанный по формуле выше, условно) равен $F(x)=(0{,}30;\ -0{,}10;\ 0{,}20;\ 0{,}05)$. Складываем через skip connection: $x+F(x) = (1{,}30;\ 1{,}90;\ -0{,}80;\ 0{,}05)$. Применяем layer normalization: среднее по четырём координатам $\mu = (1{,}30+1{,}90-0{,}80+0{,}05)/4 = 2{,}45/4 = 0{,}6125$; дисперсия $\sigma^2 = \frac{1}{4}\sum(z_i-\mu)^2$. Отклонения: $1{,}30-0{,}6125=0{,}6875$, $1{,}90-0{,}6125=1{,}2875$, $-0{,}80-0{,}6125=-1{,}4125$, $0{,}05-0{,}6125=-0{,}5625$; квадраты: $0{,}4727;\ 1{,}6577;\ 1{,}9952;\ 0{,}3164$; сумма $\approx4{,}4420$, $\sigma^2\approx1{,}1105$, $\sigma\approx1{,}0538$. Нормализованный вектор $\hat{x}_i=(z_i-\mu)/\sigma$: $(0{,}6875/1{,}0538;\ 1{,}2875/1{,}0538;\ -1{,}4125/1{,}0538;\ -0{,}5625/1{,}0538) \approx (0{,}652;\ 1{,}222;\ -1{,}341;\ -0{,}534)$ — именно этот вектор (при $\gamma=1,\beta=0$) и подаётся дальше на вход подслоя feed-forward.

Пример 2 (подсчёт параметров feed-forward подслоя и всего слоя энкодера). При $d_{model}=512$ и $d_{ff}=2048$ число параметров матрицы $W_1$ равно $512\times2048=1\,048\,576$, а матрицы $W_2$ — $2048\times512=1\,048\,576$; суммарно (без учёта смещений) $\approx2\,097\,152$ параметра на подслой feed-forward — то есть ровно в два раза больше, чем в самом блоке multi-head attention (пример 3 предыдущего раздела, $1\,048\,576$ параметров). Итого один полный слой энкодера содержит примерно $1\,048\,576+2\,097\,152=3\,145\,728$ параметров (плюс небольшое число параметров масштаба и сдвига двух слоёв layer normalization, которым можно пренебречь на фоне остального). При $N=6$ таких слоёв подряд получаем примерно $6\times3\,145\,728\approx18\,874\,368$ параметров — почти $18{,}9$ миллиона параметров в одном только стеке энкодера базовой конфигурации трансформера, не считая эмбеддингов и выходного слоя.

Пример 3 (почему обёртка в стиле ResNet здесь не менее критична, чем в CNN). Полная архитектура трансформера в крупных языковых моделях достигает десятков, а иногда и сотен слоёв подряд (у некоторых современных моделей счёт идёт на многие десятки блоков энкодера-декодера). Без остаточной связи из урока 337 градиент при обратном проходе через $N$ таких слоёв должен был бы пройти через $N$ последовательных нелинейных преобразований без единого «прямого» пути — ровно та же самая проблема затухающего градиента, что разбиралась в уроке 330, только теперь применительно к блокам self-attention и feed-forward вместо свёрток. Формула $x_1 = x+\mathrm{MultiHead}(x,x,x)$ (до применения normalization) гарантирует слагаемое «$+1$» в производной по $x$, ровно как показано в уроке 337 для остаточного блока ResNet, — именно оно не даёт градиенту исчезнуть на пути через десятки слоёв трансформера.

Почему это важно

Архитектура энкодера трансформера — это не изобретение чего-то принципиально нового поверх self-attention, а грамотная сборка уже знакомых тебе деталей: multi-head attention (этот урок) даёт содержательный механизм смешивания информации между токенами, skip connection (урок 337) даёт градиенту прямой путь в обход каждого подслоя, а layer normalization (урок 332) стабилизирует масштаб активаций независимо от длины последовательности и размера батча — что особенно важно, поскольку длина текстовых последовательностей всегда переменная, в отличие от фиксированных размеров изображений в CNN, где чаще уместна batch normalization. Именно эта комбинация впервые сделала практически осуществимым обучение по-настоящему глубоких моделей внимания — а глубина, как и в случае с ResNet, напрямую конвертируется в выразительную способность модели, если её можно стабильно обучить.

Декодер и маскированное внимание: как трансформер генерирует текст

Интуиция

Урок 341 представил архитектуру encoder-decoder: энкодер сжимает входную последовательность в представление, декодер генерирует выходную последовательность шаг за шагом, опираясь на это представление и на уже сгенерированные им самим токены. Трансформерный декодер продолжает эту логику, но полностью в духе self-attention вместо рекуррентности. У декодера тоже есть стек из $N$ одинаковых слоёв, но в каждом слое — не два подслоя, как в энкодере, а три: маскированное self-attention (декодер смотрит сам на себя, но только на уже сгенерированную часть), attention поверх выхода энкодера (декодер смотрит на входную последовательность целиком — эволюция того самого attention из урока 342, только теперь встроенная в полностью параллельную архитектуру вместо надстройки над LSTM) и feed-forward сеть — все три подслоя обёрнуты в те же skip connection и layer normalization, что и в энкодере.

Ключевая тонкость — маскирование. Когда декодер генерирует токен на позиции $t$, он не имеет права «видеть» токены на позициях $t+1, t+2, \dots$ — они либо ещё не сгенерированы (при реальной генерации текста), либо, при обучении, представляют собой уже известный правильный ответ, подглядывание в который сделало бы задачу тривиальной и бесполезной для обучения модели прогнозировать следующий токен. Self-attention без ограничений позволил бы каждой позиции видеть все остальные позиции сразу — в том числе будущие. Маска решает эту проблему буквально: перед применением softmax к матрице весов внимания все позиции «в будущем» относительно текущей принудительно обнуляются.

Формула

Маскированное self-attention. Модифицируем формулу attention из урока 342, добавляя маску $M$ к скорам до применения softmax:

$$\mathrm{MaskedAttention}(Q,K,V) = \mathrm{softmax}\!\left(\frac{QK^T}{\sqrt{d_k}} + M\right)\cdot V, \qquad M_{ij} = \begin{cases}0, & j\le i \\ -\infty, & j>i\end{cases}$$

где $i$ — индекс позиции-запроса, $j$ — индекс позиции-ключа. После прибавления $-\infty$ к соответствующим элементам и применения softmax вероятность «смотреть» на любую будущую позицию $j>i$ становится ровно нулевой. Attention декодера поверх выхода энкодера (кросс-attention) использует ту же формулу $\mathrm{Attention}(Q,K,V)$ из урока 342 без маски, но с $Q$, идущим из декодера, а $K$ и $V$ — из финального выхода стека энкодера.

Разбор примеров

Пример 1 (численное построение маски для последовательности длины 4). Для последовательности из четырёх токенов маска — это матрица $4\times4$ вида

$$M=\begin{pmatrix}0&-\infty&-\infty&-\infty\\0&0&-\infty&-\infty\\0&0&0&-\infty\\0&0&0&0\end{pmatrix}$$

Возьмём сырые скоры (до softmax) для позиции-запроса $i=2$ (третий токен, считая от нуля): $QK^T/\sqrt{d_k}=[1{,}2;\ 0{,}5;\ 2{,}0;\ 1{,}8]$. После прибавления соответствующей строки маски $[0;\ 0;\ 0;\ -\infty]$ получаем $[1{,}2;\ 0{,}5;\ 2{,}0;\ -\infty]$. Применяя softmax, вес четвёртой позиции становится ровно $0$ (поскольку $e^{-\infty}=0$), а веса первых трёх позиций пересчитываются заново так, чтобы в сумме давать единицу уже только между собой — например, $[0{,}31;\ 0{,}15;\ 0{,}54;\ 0{,}00]$. Токен на позиции $2$ «видит» только себя и предыдущие два токена, но не токен на позиции $3$.

Пример 2 (кросс-attention как прямое развитие attention из урока 342). В классическом encoder-decoder с рекуррентностью и attention (урок 341–342) декодер на каждом шаге генерации вычислял attention-веса между своим текущим скрытым состоянием (одним вектором) и всеми скрытыми состояниями энкодера, полученными последовательно через LSTM. В трансформерном декодере ровно та же идея — декодер должен «заглядывать» во всю входную последовательность целиком при генерации каждого выходного токена, — но реализована как отдельный подслой multi-head attention, где $Q$ строится из представлений декодера (на всех уже сгенерированных позициях сразу, поскольку внутри слоя всё считается параллельно), а $K$ и $V$ — фиксированный на всё время генерации выход стека энкодера. Это устраняет узкое место единственного сжатого контекстного вектора из базовой формулы урока 341: декодер получает прямой доступ к представлениям каждого токена входной последовательности по отдельности, а не к одному усреднённому вектору.

Пример 3 (пошаговая генерация с маской: перевод «I love ML» → «Я люблю МО»). Энкодер обрабатывает «I love ML» целиком параллельно и выдаёт представления всех трёх токенов. Декодер начинает с токена <START>; на первом шаге генерации маска разрешает видеть только позицию $0$ (сам <START>), кросс-attention даёт доступ ко всем трём представлениям энкодера, и декодер предсказывает «Я». На втором шаге вход декодера — уже <START>, Я, маска разрешает позициям $0$ и $1$ видеть только друг друга (но не будущее), и модель предсказывает «люблю». Формально при обучении все шаги считаются одним параллельным проходом благодаря маске — не нужно, как в рекуррентном декодере, реально дожидаться токена $t-1$, чтобы вычислить представление для токена $t$ на этапе обучения: маска гарантирует, что информация о будущих токенах просто не участвует в вычислении текущей позиции, хотя технически все позиции обрабатываются одновременно.

Почему это важно

Маскирование — это то, что позволяет декодеру трансформера сохранить и авторегрессионный характер генерации (каждый следующий токен строится только на основе предыдущих, как и должно быть при реальной генерации текста), и при этом полную параллельность обучения: во время обучения весь целевой текст подаётся на вход декодера одним куском, а маска гарантирует, что предсказание позиции $t$ математически не может использовать информацию о позиции $t+1$ и далее, даже притом что физически все позиции обрабатываются в одном матричном умножении. Без маски self-attention декодера тривиально «подсмотрел» бы правильный ответ и обучение свелось бы к бессмысленному копированию. Полная архитектура энкодер-декодер трансформера — это, таким образом, прямое развитие идеи урока 341: два стека блоков вместо двух рекуррентных сетей, соединённые кросс-attention вместо единственного контекстного вектора, с добавленным маскированием в декодере, чтобы сохранить корректность авторегрессионной генерации. Забегая вперёд: многие современные большие языковые модели, включая семейство GPT, используют только «половину» этой архитектуры — стек декодерных блоков с маскированным self-attention, вовсе без отдельного энкодера, — но принцип маскирования остаётся ровно тем же самым; об этом пойдёт речь в следующем уроке.

Практика: 30 заданий

Базовые задания (1–10)

Задание 1: Рекуррентной сети требуется $n=200$ последовательных шагов, чтобы обработать последовательность длины $200$. Сколько последовательных шагов требуется self-attention для той же последовательности (асимптотически)?


Задание 2: При $d_{model}=512$ и числе голов $h=8$ найди размерность одной головы $d_k$.


Задание 3: После конкатенации результатов $8$ голов размерности $64$ каждая, какова размерность итогового вектора до применения $W^O$?


Задание 4: Вычисли $PE(0,0)$ и $PE(0,1)$ по формулам позиционного кодирования.


Задание 5: При $d=512$ найди период (длину волны) синуса для координаты с $i=0$.


Задание 6: Для игрушечной feed-forward сети с $d_{model}=8$, $d_{ff}=32$ найди число параметров матриц $W_1$ и $W_2$ (без смещений).


Задание 7: Вход энкодера $x=(2{,}0;\ -1{,}0)$, выход подслоя $F(x)=(0{,}5;\ 0{,}2)$. Найди результат skip connection $x+F(x)$ до применения layer normalization.


Задание 8: Для вектора $z=(2{,}5;\ -0{,}8)$ из задания 7 найди среднее $\mu$ и дисперсию $\sigma^2$ для layer normalization.


Задание 9: Для последовательности длины $4$ и текущей позиции-запроса $i=1$ (считая с нуля) укажи, элементы маски для каких позиций-ключей $j$ равны $-\infty$.


Задание 10: Для $n=10$, $d=100$ сравни, что больше: сложность self-attention $n^2 d$ или сложность рекуррентного слоя $n d^2$.


Средние задания (11–20)

Задание 11: При $d=4$, $\mathrm{pos}=5$ найди $PE(5,0)$ и $PE(5,1)$ (то есть $i=0$).


Задание 12: При $d=4$, $\mathrm{pos}=5$, $i=1$ найди $PE(5,2)$ и $PE(5,3)$.


Задание 13: Используя $\sin1=0{,}8415$, $\cos1=0{,}5403$, $\sin2=0{,}9093$, $\cos2=-0{,}4161$, проверь равенство $\sin(1+2)=\sin1\cos2+\cos1\sin2$ и сравни с прямым значением $\sin3=0{,}1411$.


Задание 14: Для $d_{model}=256$, $h=4$ найди суммарное число параметров всех четырёх матриц multi-head attention ($W^Q,W^K,W^V,W^O$, каждая $d_{model}\times d_{model}$).


Задание 15: Для $d_{model}=256$, $d_{ff}=1024$ найди число параметров feed-forward подслоя ($W_1$ и $W_2$, без смещений) и суммарное число параметров всего слоя энкодера (сложи с ответом задания 14).


Задание 16: Используя результат задания 15, найди суммарное число параметров стека из $N=6$ одинаковых слоёв энкодера.


Задание 17: Базовый трансформер обучался $12$ часов на $8$ GPU. Если бы обучение шло на $1$ GPU без каких-либо потерь эффективности от распараллеливания по данным, сколько примерно часов заняло бы обучение при том же общем числе вычислений (чисто для интуиции, без учёта пределов параллельной эффективности)?


Задание 18: При каком соотношении между $n$ и $d$ сложность self-attention $n^2d$ превышает сложность рекуррентного слоя $nd^2$?


Задание 19: Построй полную маску $M$ (в терминах $0$ и $-\infty$) для последовательности длины $3$.


Задание 20 (машинное обучение): Почему при обучении декодера нельзя просто убрать маску, если и так известны все правильные токены целевой последовательности?


Продвинутые задания (21–30)

Задание 21: Выведи общий коэффициент линейной комбинации: покажи, что $PE(\mathrm{pos}+k,2i) = PE(\mathrm{pos},2i)\cos(k\omega) + PE(\mathrm{pos},2i+1)\sin(k\omega)$, где $\omega=1/10000^{2i/d}$.


Задание 22 (машинное обучение): Вход слоя энкодера $x=(0{,}5;\ -2{,}0;\ 1{,}0;\ 0{,}5)$, выход multi-head attention $F(x)=(0{,}1;\ 0{,}3;\ -0{,}2;\ 0{,}0)$. Найди результат $z=x+F(x)$, затем $\mu$ и $\sigma^2$ для layer normalization.


Задание 23 (машинное обучение): Для крупной модели с $d_{model}=12288$, $h=96$, $N=96$ слоёв, $d_{ff}=4\cdot d_{model}=49152$ оцени порядок суммарного числа параметров всех блоков multi-head attention и feed-forward (по формуле $12\cdot d_{model}^2$ параметров на слой, без эмбеддингов).


Задание 24 (машинное обучение): Предложение из двух токенов «кот спит» без позиционного кодирования и его перестановка «спит кот». Объясни, почему после одного слоя self-attention без $PE$ множества итоговых представлений токенов совпадут (с точностью до перестановки), хотя смысл предложений разный.


Задание 25 (машинное обучение): Сравни максимальную длину пути ($O(1)$ для self-attention против $O(n)$ для рекуррентного слоя) для $n=1000$ и объясни, как это связано со способностью модели выучивать дальние зависимости между токенами.


Задание 26 (машинное обучение): Покажи, что суммарная вычислительная стоимость $h$ голов размерности $d_k=d_{model}/h$ каждая (при вычислении $QK^T$ для одной головы) примерно равна стоимости одной головы полной размерности $d_{model}$.


Задание 27: Игрушечный пример с двумя головами для предложения из трёх токенов. Голова 1 даёт для токена 3 веса внимания $[0{,}1;\ 0{,}1;\ 0{,}8]$ по значениям $V_1=[(1,0),(0,1),(2,2)]$ (каждое значение — вектор размерности 2). Голова 2 даёт веса $[0{,}5;\ 0{,}3;\ 0{,}2]$ по тем же значениям $V_2=[(1,0),(0,1),(2,2)]$. Найди выход каждой головы и результат их конкатенации.


Задание 28 (машинное обучение): Объясни, почему декодер трансформера использует layer normalization, а не batch normalization, при генерации текста батчами переменного размера (вплоть до батча размера $1$ при пошаговой генерации).


Задание 29: Используя ответ задания 16 (стек энкодера $\approx4{,}72$ млн параметров при $d_{model}=256$, $h=4$, $d_{ff}=1024$, $N=6$) и зная, что один слой декодера содержит дополнительный блок кросс-attention той же размерности, что и self-attention (задание 14: $262\,144$ параметра), оцени суммарное число параметров стека декодера с $N=6$ слоями.


Задание 30 (машинное обучение): Своими словами объясни смысл названия статьи «Attention Is All You Need», опираясь на всё разобранное в уроке: параллелизацию, multi-head attention и позиционное кодирование.


Частые ошибки

  • Считают, что multi-head attention — это просто «attention, применённый несколько раз подряд». На самом деле все головы работают параллельно и независимо друг от друга на одном и том же входе, а не последовательно одна за другой; их результаты объединяются один раз в самом конце через конкатенацию и линейную проекцию $W^O$ (формула раздела про multi-head attention).

  • Путают позиционное кодирование с обучаемым эмбеддингом токена. $PE(\mathrm{pos})$ — это фиксированная, заранее вычисляемая по формуле синуса и косинуса функция от позиции, не содержащая обучаемых параметров, тогда как эмбеддинг токена — это обучаемый вектор, зависящий от содержимого токена. Итоговый вход слоя — их сумма, а не конкатенация и не замена одного другим.

  • Считают, что self-attention сам по себе «знает» порядок токенов, раз он смотрит на всю последовательность целиком. «Видеть всю последовательность целиком» и «знать порядок токенов» — разные вещи: без явного позиционного кодирования self-attention обрабатывает вход как неупорядоченное множество (задание 24), и порядок приходится добавлять отдельно.

  • Путают маскирование в декодере с обычным dropout или каким-либо видом регуляризации. Маска в маскированном self-attention не выбрасывает случайные связи для борьбы с переобучением (как dropout, урок 333) — она детерминированно и всегда запрещает позиции «видеть» будущие позиции, обеспечивая корректность авторегрессионной генерации (задание 20), и не зависит от режима обучения или инференса.

  • Думают, что skip connection и layer normalization в трансформере — это опциональные детали для небольшого улучшения качества. Как и в ResNet (урок 337), без остаточной связи градиент при обратном проходе через десятки слоёв практически затухает (урок 330), а без layer normalization активации в разных слоях могут иметь несопоставимый масштаб, что дестабилизирует обучение, — обе детали структурно необходимы для того, чтобы глубокий трансформер вообще обучался.

  • Считают, что декодер трансформера обязательно должен иметь блок кросс-attention и отдельный энкодер. Полная encoder-decoder архитектура (как в этом уроке) действительно использует кросс-attention, но многие современные большие языковые модели используют только decoder-only архитектуру — стек блоков с маскированным self-attention без отдельного энкодера и без кросс-attention; об этом пойдёт речь в следующем уроке.

Главное запомнить

  • Трансформер полностью отказывается от рекуррентности (LSTM/GRU, уроки 339–340) в пользу self-attention (урок 342), что делает обработку всей последовательности параллельной вместо строго последовательной — число обязательных последовательных шагов падает с $O(n)$ до $O(1)$.

  • Параллелизация не про меньшее число вычислений в абстрактном смысле (пример 2 раздела про параллелизацию), а про принципиальную возможность распределить вычисления по тысячам ядер GPU/TPU одновременно — именно это резко сократило время обучения и открыло дорогу масштабированию моделей.

  • Multi-head attention считает несколько независимых блоков self-attention параллельно ($h=8$ голов в базовой конфигурации), каждая голова может специализироваться на своём типе зависимости между токенами, а результаты объединяются конкатенацией и финальной линейной проекцией $W^O$.

  • Self-attention сам по себе не различает порядок токенов (обрабатывает последовательность как множество) — позиционное кодирование через $PE(\mathrm{pos},2i)=\sin(\mathrm{pos}/10000^{2i/d})$ и $PE(\mathrm{pos},2i+1)=\cos(\mathrm{pos}/10000^{2i/d})$ явно добавляет информацию о позиции, обещанную ещё в уроке 208 про ряды Фурье.

  • Сдвиг позиции на $k$ выражается линейным преобразованием позиционного кодирования с коэффициентами $\cos(k\omega)$ и $\sin(k\omega)$, зависящими только от сдвига — прямое следствие формул синуса и косинуса суммы, доказанное ещё в уроке 208.

  • Один слой энкодера — два подслоя (multi-head self-attention и feed-forward), каждый обёрнут в skip connection (урок 337) и layer normalization (урок 332): формула $y=x+\mathrm{Layer}(\mathrm{Norm}(x))$ из урока 337 буквально повторяется здесь дважды подряд внутри одного слоя.

  • Декодер добавляет маскированное self-attention (запрещает видеть будущие позиции через $M_{ij}=-\infty$ при $j>i$) и кросс-attention поверх выхода энкодера — прямое развитие идеи attention из урока 342, устраняющее узкое место единственного контекстного вектора из базовой архитектуры урока 341.

  • Полная архитектура энкодер-декодер трансформера — это эволюция схемы encoder-decoder из урока 341: те же роли «понять вход — сгенерировать выход», но обе части построены на self-attention и multi-head attention вместо LSTM/GRU, с добавленным маскированием и кросс-attention вместо единственного сжатого вектора.

  • Без skip connections и layer normalization глубокий трансформер (десятки слоёв) страдал бы от той же проблемы затухающего градиента, что и глубокие сети без ResNet-подобных остаточных связей (урок 330, урок 337) — эти два компонента структурно необходимы, а не опциональны.

Связь с темами курса

Этот урок напрямую завершает линию, начатую в уроке 341 (encoder-decoder архитектура) и продолженную в уроке 342 (attention mechanism): там attention появился как вспомогательная надстройка поверх рекуррентной сети, здесь он стал единственным механизмом, отвечающим за передачу информации между позициями последовательности, а сама рекуррентность полностью исчезла из архитектуры. Формула $\mathrm{Attention}(Q,K,V)=\mathrm{softmax}(QK^T/\sqrt{d_k})\cdot V$ из урока 342 используется здесь без изменений — трансформер не переизобретает self-attention, а расширяет его до multi-head attention и встраивает в законченную архитектуру энкодера и декодера.

Позиционное кодирование прямо выполняет обещание, данное в уроке 208 университетского курса про ряды Фурье: формулы $PE(\mathrm{pos},2i)=\sin(\mathrm{pos}/10000^{2i/d})$ и $PE(\mathrm{pos},2i+1)=\cos(\mathrm{pos}/10000^{2i/d})$ там уже приводились как иллюстрация практического применения синус-косинусных разложений, а задание 25 того урока доказывало в общем виде свойство линейной выразимости сдвига позиции — то самое свойство, что разобрано здесь в задании 21 и в примере 2 раздела про позиционное кодирование. Это не совпадение обозначений, а одна и та же математическая идея — представление сигнала через набор гармоник разных частот, — применённая в уроке 208 к периодическим функциям, а здесь к позиции токена в последовательности.

Архитектура энкодера трансформера опирается сразу на две конкретные идеи из прошлых уроков глубокого обучения. Skip connection из урока 337 про ResNet используется здесь в буквально той же форме, что и была анонсирована в примере 2 того урока: формула $y=x+\mathrm{Layer}(\mathrm{Norm}(x))$ оборачивает как блок self-attention, так и блок feed-forward внутри одного слоя трансформера, решая ту же самую проблему затухающего градиента (урок 330) при большом числе последовательных слоёв. Layer normalization из урока 332 выбрана вместо batch normalization именно потому, что она не зависит от размера батча и длины последовательности (задание 28 этого урока), что критично для текстовых данных переменной длины — этот выбор был прямо предсказан в уроке 332 как обязательный компонент трансформерной архитектуры.

Интересные факты

  • Статья «Attention Is All You Need» была принята на конференцию NeurIPS 2017 и с тех пор стала одной из самых цитируемых работ в истории компьютерных наук — число цитирований исчисляется уже десятками тысяч, что ставит её в один ряд с самыми влиятельными публикациями за всю историю машинного обучения.

  • Оригинальная архитектура была симметричной: энкодер и декодер по $N=6$ слоёв каждый, с $h=8$ головами внимания и $d_{model}=512$. Современные большие языковые модели используют тот же базовый строительный блок, но масштабируют число слоёв, число голов и размерность представления на порядки — например, крупные модели содержат десятки и сотни слоёв вместо шести, что подтверждается расчётом в задании 23, где масштабирование параметров конфигурации, близкой к реальным крупным моделям, даёт порядок в сотни миллиардов параметров.

  • Числовая константа $10000$ в формуле позиционного кодирования не имеет глубокого теоретического обоснования — это эмпирически подобранное значение, обеспечивающее достаточно широкий диапазон длин волн (от $2\pi$ до примерно $10000\cdot2\pi$) для покрытия как коротких, так и длинных последовательностей встречающимися на практике длинами.

  • Хотя статья называется «Attention Is All You Need», сама по себе архитектура энкодера или декодера тратит заметную долю параметров вовсе не на attention, а на feed-forward подслои: как показано в примере 2 раздела про энкодер, feed-forward подслой при типичном соотношении $d_{ff}=4d_{model}$ содержит вдвое больше параметров, чем блок multi-head attention того же слоя.

Лайфхаки

  • Когда разбираешь новую архитектуру на основе трансформера (BERT, GPT, T5 и десятки других), в первую очередь ищи три вещи: устройство self-attention (сколько голов, какая размерность), тип и место масок (полное внимание или только на прошлое) и порядок нормализации относительно skip connection (до или после подслоя, pre-norm или post-norm) — эти три детали почти полностью определяют поведение архитектуры и объясняют большинство различий между конкретными моделями.

  • При реализации multi-head attention с нуля в коде сначала убедись, что $d_{model}$ делится на $h$ без остатка (задание 2), — это частая причина ошибок размерности при написании собственного кода, поскольку $d_k=d_{model}/h$ должно быть целым числом.

  • Для отладки позиционного кодирования визуализируй матрицу $PE$ как тепловую карту (позиции по одной оси, координаты — по другой): низкочастотные координаты (большие $i$) должны выглядеть как плавные полосы, а высокочастотные (малые $i$) — как быстро чередующиеся полосы, ровно как в примере 1 раздела про позиционное кодирование; если картина выглядит иначе, скорее всего перепутаны индексы $\mathrm{pos}$ и $i$ в формуле.

  • При оценке требуемых вычислительных ресурсов для обучения трансформера ориентируйся не только на общее число FLOPs (формула сложности $O(n^2d)$), но в первую очередь на то, насколько задача параллелится по имеющемуся оборудованию, — как показано в разделе про отказ от рекуррентности, два алгоритма с сопоставимым числом операций могут отличаться по реальному времени обучения на порядки, если один из них принципиально последователен, а другой нет.

  • Изучая маскирование в декодере, не путай два разных типа масок, которые часто используются вместе: маску «не смотреть в будущее» (задание 19) и маску заполнения (padding mask), которая скрывает служебные токены-заполнители, добавленные, чтобы выровнять длины разных последовательностей в одном батче, — обе реализуются одинаково технически (через $-\infty$ до softmax), но решают разные задачи.

Сегодня ты разобрал архитектуру, вокруг которой построен весь современный искусственный интеллект, работающий с текстом. Отказ от рекуррентности в пользу self-attention дал параллелизацию, которая на порядки сократила время обучения; multi-head attention дал возможность выучивать несколько типов зависимостей одновременно вместо одного компромиссного распределения; позиционное кодирование через синус и косинус вернуло информацию о порядке токенов, которую рекуррентность раньше давала бесплатно; skip connections и layer normalization сделали такую глубокую архитектуру вообще обучаемой. Ни одна из этих идей по отдельности не была абсолютно новой — attention, остаточные связи и нормализация уже существовали и разбирались в предыдущих уроках, — но их точная сборка в единую параллелизуемую архитектуру оказалась именно тем недостающим звеном, которое сделало возможным взрывной рост размера моделей и объёма обучающих данных за последние годы. В следующем уроке ты увидишь, как эта архитектура разделилась на два магистральных направления — энкодерные модели вроде BERT, устроенные для понимания текста, и декодерные модели вроде GPT, устроенные для его генерации, — и как обе эти линии в конечном счёте привели к современным большим языковым моделям.

Понял тему? Закрепи в боте! 🚀

Попрактикуйся на задачах и получи персональные рекомендации от AI

💪 Начать тренировку
💬 Есть вопрос? Спроси бота!