Последовательность обращает внимание на саму себя: каждый токен строит запрос, ключ и значение тремя линейными отображениями, оценивает все токены и берёт взвешенное среднее значений. В заметке выводится матричная форма с размерами матриц, вручную разбирается пример из трёх токенов, объясняются деление на √d_k, маски и потеря порядка слов, строится многоголовое внимание и обсуждается его квадратичная стоимость.
Предыдущая заметка позволила декодеру рекуррентного переводчика оценивать каждое состояние энкодера и считывать их взвешенное среднее. Сам энкодер по-прежнему рекуррентный: n токенов требуют n последовательных шагов, а связь между двумя далёкими словами проходит через каждый шаг между ними. В этой заметке тот же поиск применяется внутри одной последовательности: каждая позиция оценивает каждую позицию, включая себя, и считывает взвешенное среднее по всем ним — одним матричным произведением. Эта операция называется self-attention (внимание последовательности к самой себе); в виде нескольких параллельных голов (heads) это единственная операция трансформера, которая смешивает позиции (Vaswani et al., 2017). Матричные иллюстрации того же вычисления есть у Alammar (2018) и Jurafsky and Martin.
Обозначения. Последовательность из n токенов с d-мерными векторами — это матрица X∈Rn×d; её строка xi — вектор токена i: эмбеддинг (embedding) или выход предыдущего слоя. Векторы — строки, как в PyTorch. Обучаемые матрицы WQ,WK∈Rd×dk и WV∈Rd×dv дают запрос (query) qi=xiWQ, ключ (key) ki=xiWK и значение (value) vi=xiWV. Оценки внимания (attention scores) обозначаются sij, веса внимания — αij, выходы — yi; h — число голов. Softmax от матрицы берётся по строкам.
В предложении «Кубок не помещается в коричневый чемодан, потому что он слишком большой» он — это кубок; если заменить большой на маленький, то он — это чемодан. В этой схеме Винограда (Winograd schema) (Levesque, Davis and Morgenstern, 2012) статический эмбеддинг даёт слову он один и тот же вектор в обоих предложениях. Нам нужен слой, который переписывает вектор каждого токена с учётом связанных с ним токенов, выбранных по их содержанию и стоящих в любом месте предложения.
Базовый подход: рекуррентность и внимание поверх неё#
Рекуррентный энкодер (заметка об LSTM) смешивает контекст, передавая состояние вдоль последовательности в одном или в обоих направлениях. Внимание (attention) из предыдущей заметки стоит между декодером и энкодером; энкодер остаётся рекуррентным.
Где этот подход не справляется:
Последовательные вычисления. Шагу t нужно состояние шага t−1, поэтому n шагов нельзя выполнить параллельно.
Длинные пути. Сигнал и градиент между позициями i и j проходят через ∣i−j∣ нелинейных обновлений одного состояния фиксированной ширины. Вентили (gates) делают этот путь обучаемым, но не коротким.
Смешивание — не сравнение. Сколько от токена j доходит до токена i, решают гейты по мере продвижения состояния, а не сравнение i с j.
Словарь сравнивает запрос с хранимыми ключами и возвращает значение того ключа, который совпал в точности. Внимание делает совпадение мягким: softmax превращает оценки совпадения в веса, дающие в сумме единицу, а результат — взвешенное среднее всех значений. Поскольку веса гладко зависят от оценок, все три проекции обучаются обратным распространением ошибки.
В предыдущей заметке запросом служит состояние декодера, а состояния энкодера — одновременно ключами и значениями (Bahdanau, Cho and Bengio, 2015). Self-attention берёт все три из одной и той же последовательности (Cheng, Dong and Lapata, 2016, ещё до трансформера). Cross-attention (внимание к выходам энкодера) берёт запросы из одной последовательности, а ключи и значения — из другой, с матрицей весов m×n для m запросов и n ключей; заметка о трансформере помещает его в декодер.
Для одного токена i слой делает четыре вещи (пошаговый разбор в этом же порядке — у Starmer, 2023):
Проекция.qi=xiWQ, и для каждого токена j: kj=xjWK, vj=xjWV. Каждая из них — линейный слой без смещения.
Оценка.sij=qi⋅kj/dk для каждого j, включая j=i.
Нормировка.αij=exp(sij)/∑l=1nexp(sil). Веса положительны и ∑jαij=1.
Смешивание.yi=∑j=1nαijvj.
Одни и те же WQ, WK и WV используются в каждой позиции, поэтому один слой обрабатывает последовательности любой длины. Строки Y — контекстные эмбеддинги: одно и то же слово получает разные векторы в разных предложениях; на них опираются заметка о BERT и семантический поиск.
Оценка через xi⋅xj не работает по двум причинам. Она симметрична, поэтому «местоимение ищет свой антецедент» и обратное отношение были бы одним и тем же. И сам токен обычно побеждает в своей строке: по неравенству Коши–Буняковского xi⋅xj≤∥xi∥∥xj∥≤∥xi∥2, если ∥xj∥≤∥xi∥, поэтому при равных нормах диагональный элемент — наибольшая оценка в каждой строке.
С проекциями оценка равна
sij=dkxiWQWK⊤xj⊤,
то есть обучаемой билинейной форме. Она не обязана быть симметричной, а большая оценка для самого себя становится выученным выбором, а не заложенным заранее предпочтением. Ранг матрицы WQWK⊤ не больше dk: голова сравнивает токены вдоль dk выученных направлений. Отдельная WV разделяет то, что сопоставляется, и то, что передаётся дальше.
Предыдущая заметка сравнила аддитивную, билинейную и скалярную функции оценки (Luong, Pham and Manning, 2015). Здесь скалярное произведение делает всю матрицу оценок одним произведением QK⊤, а нормы несут информацию: запрос с большей нормой даёт более острый softmax, ключ с большей нормой забирает больше веса. Контролировать нужно только типичную величину оценок, которая растёт с dk.
Внимание на основе масштабированного скалярного произведения#
Составив из строк матрицы, получаем матричную форму внимания на основе масштабированного скалярного произведения (scaled dot-product attention):
Элемент (i,j) матрицы QK⊤ равен qi⋅kj; деление на dk удерживает оценки в масштабе, с которым softmax может работать (ниже); softmax даёт αij, а строка i произведения с V равна yi: это четыре шага выше, выполненные сразу для всех токенов.
Матрица
Размер
Смысл
X
n×d
представления токенов
Q,K
n×dk
запросы, ключи
V
n×dv
значения
QK⊤
n×n
оценки «токен — токен»
A=softmax(QK⊤/dk)
n×n
веса внимания, каждая строка даёт в сумме 1
AV
n×dv
по одному вектору с учётом контекста на каждый токен
Рисунок
Размеры матриц в self-attention и путь одной строки
В масштабе для n = 5 токенов, d = 8 и d_k = d_v = 4; каждый маленький квадрат — одно число. Три проекции превращают вход размера n × d в Q, K и V. QKᵀ имеет размер n × n: строка i содержит оценки запроса токена i относительно всех ключей. Softmax работает по строкам, а AV даёт по одному d_v-мерному выходу на токен. Выделенная строка прослеживает токен 2: его входную строку, его запрос, его строку оценок и весов и его выход, в котором смешаны все строки V.
Выход содержит по одной строке на каждый входной токен, поэтому слои можно ставить друг на друга. В батче, дополненном паддингом (padding), тензоры имеют размер [B,n,d], а оценки — [B,n,n].
Проверим вторую строку вручную: y2=0.248(2,1)+0.248(0,3)+0.503(1,1)=(1.000,1.497) с точностью до округления. Dogs берёт 77% своего нового вектора от chase и лишь 4.5% от себя; chase берёт половину из cats. Каждая строка даёт в сумме единицу, столбцы — не обязательно.
Рисунок
Self-attention для одного токена: что куда идёт
Все 4 шага · нажмите ▶, чтобы пройти по одному
Числа разобранного примера, строка токена chase. Каждый токен проецируется в ключ k и значение v, а chase ещё и в запрос q (1). Запрос сравнивается со всеми ключами, включая собственный (2), softmax даёт веса 0.248, 0.248, 0.503 (3), и новый вектор chase — взвешенная сумма значений, (1.000, 1.497) (4). Это тот же механизм, что у внимания в переводчике: запрос вместо состояния декодера, ключи и значения вместо состояний энкодера; новое — все три берутся из одной последовательности линейными проекциями, и так делается для каждого токена одновременно.
Рисунок проводит строку chase через четыре шага раздела От эмбеддингов к Q, K и V: спроецировать (1), оценить (2), нормировать (3), смешать (4). Если положить его рядом с шагом внимания переводчика, это то же вычисление: запрос играет роль состояния декодера, ключи и значения — роль состояний энкодера. Нового две вещи, в янтарной рамке: все три берутся из одной последовательности обучаемыми проекциями, и каждый токен проходит те же четыре шага одновременно с остальными — это и вычисляет матричная форма выше.
Рисунок
Один запрос: от оценок к выходу
Игрушечный пример из заметки, токен «dogs» в роли запроса (d = 4, d_k = d_v = 2). ① Его запрос q₁ = (2, 0) сравнивается с ключами всех трёх токенов, включая его самого. ② Скалярные произведения, делённые на √2, равны 0.00, 2.83, 1.41. ③ Softmax превращает их в веса 0.045, 0.768, 0.187, сумма которых равна единице. ④ Выход y₁ = (0.277, 2.536) — взвешенная сумма векторов значений; синяя ломаная откладывает их один за другим. Поскольку веса положительны и дают в сумме единицу, каждый выход лежит внутри треугольника с вершинами в значениях (пунктир).
Те же входы без проекций, A0=softmax(XX⊤/4), делают мысль предыдущего подраздела наглядной:
оценки XX⊤ симметричны и диагональ побеждает в каждой строке, но сами веса не симметричны: у каждой строки свой знаменатель softmax, поэтому α13=e0.5/5.367=0.307, а α31=e0.5/6.016=0.274. При симметричных оценках влияние dogs на cats и cats на dogs по-прежнему задаётся одним числом, с точностью до нормировки строк. Выученные WQ=WK делают несимметричными сами оценки, а большая оценка токена для самого себя становится выученным выбором.
Рисунок
Веса внимания с обучаемыми проекциями и без них
Строки — запросы, столбцы — ключи; каждая строка даёт в сумме единицу, наибольший вес в каждой строке обведён. Слева: оценки — обычные скалярные произведения входных векторов, softmax(XXᵀ/√4). Оценки XXᵀ симметричны, и диагональный элемент — наибольший в каждой строке, потому что при равных нормах вектор лучше всего совпадает сам с собой. Веса почти симметричны, но не совсем: у каждой строки свой знаменатель, поэтому dogs → cats 0.307, а cats → dogs 0.274. Справа: те же входы с игрушечными проекциями W_Q ≠ W_K, softmax(QKᵀ/√2). Теперь несимметричны сами оценки (dogs → chase 4, chase → dogs 1): «dogs» читает в основном из «chase» (0.768) и сохраняет от себя лишь 0.045.
Предположим, что компоненты q и k независимы, имеют среднее 0 и дисперсию 1 — примерно это дают инициализация и нормализация. Тогда q⋅k=∑c=1dkqckc — сумма dk независимых слагаемых, у каждого из которых среднее E[qc]E[kc]=0 и дисперсия E[qc2]E[kc2]=1. Дисперсии независимых слагаемых складываются, поэтому
Var(q⋅k)=dk,Var(dkq⋅k)=1.
(Vaswani et al., 2017, §3.2.1, сноска 4.) Делитель — размер одной головы dk (64 в базовом трансформере), а не ширина модели d (512).
Почему величина важна: якобиан softmax равен ∂αj/∂sl=αj(δjl−αl). Когда одна оценка доминирует, α близок к one-hot-вектору, все элементы якобиана близки к нулю, и WQ и WK перестают получать градиент. Сводка одним числом — след ∑jαj(1−αj)=1−∑jαj2: 1−1/n для равномерных весов и 0 для весов в виде one-hot-вектора.
Игрушечная симуляция: n=16 ключей, q,k∼N(0,Idk), 2000 испытаний на каждое значение dk, начальное значение генератора (seed) 0.
dk
ст. откл. q⋅k
наибольший вес, без масштаба
наибольший вес, с масштабом
1−∑αj2, без масштаба
1−∑αj2, с масштабом
1
0.99
0.21
0.21
0.87
0.87
4
1.99
0.43
0.23
0.71
0.87
16
4.01
0.69
0.25
0.43
0.87
64
8.02
0.84
0.25
0.22
0.87
512
22.49
0.94
0.25
0.08
0.87
Без масштабирования при dk=64 один ключ уже на инициализации забирает большую часть веса, а мера градиента падает до четверти. В разобранном примере без 2 первая строка была бы (0.016,0.867,0.117) вместо (0.045,0.768,0.187). Масштаб задаёт только начальную точку: обучение по-прежнему может сделать внимание острым, увеличивая нормы запросов и ключей.
Рисунок
Зачем оценки делят на √d_k
Слева: если компоненты q и k независимы, имеют среднее 0 и дисперсию 1, то оценка q·k имеет дисперсию d_k; кривые — её точные плотности при d_k = 4, 16, 64, которые с ростом d_k приближаются к нормальным. Деление на √d_k возвращает каждый случай к дисперсии 1 (синие кривые; пунктирная, d_k = 4, самая острая, две другие почти совпадают). Справа: симуляция с 16 ключами, 2000 случайных испытаний на каждое значение d_k (игрушечная, seed 0). Без масштабирования (красные линии) наибольший вес softmax растёт от 0.21 до 0.94, а мера градиента 1 − Σ αⱼ² падает от 0.87 до 0.08; при d_k = 64 они равны 0.84 и 0.22. С масштабированием (синие линии) обе величины остаются около 0.25 и 0.87 при любом d_k.
Исключаемым оценкам до softmax присваивается −∞: поскольку e−∞=0, их веса в точности равны нулю, а остальные веса строки по-прежнему дают в сумме единицу.
Маска паддинга (padding mask).Столбцы[PAD] маскируются во всех строках: маска размера [B,1,n] распространяется (broadcasting) на все запросы. Выходы запросов-паддингов отбрасываются позже.
Каузальная маска (causal mask). Языковая модель, предсказывающая токен t+1, не должна давать позиции t видеть более поздние позиции. Маскирование j>i (верхний треугольник, размер [n,n]) делает это сразу для всех позиций, поэтому декодер обучается на всей последовательности за один проход; на это опирается заметка о GPT.
В разобранном примере, если считать cats паддингом, строки становятся (0.056,0.944,0), (0.5,0.5,0) и (0.196,0.804,0); с каузальной маской — (1,0,0), (0.5,0.5,0) и (0.140,0.576,0.284).
Рисунок
Маска паддинга и каузальная маска на одних и тех же оценках
Веса внимания игрушечного примера; строки — запросы, столбцы — ключи. Маска присваивает оценке −∞ до softmax (заштрихованные клетки), поэтому её вес в точности равен 0, а остальные веса строки перенормируются так, чтобы их сумма была равна единице. Маска паддинга: ключ «cats» исключён из каждой строки, и первая строка становится 0.056, 0.944, 0. Каузальная маска: верхний треугольник исключён, поэтому первый токен видит только себя, а последняя строка не меняется.
scores = q @ k.transpose(-2, -1) / math.sqrt(d_k) # [B, n, n]scores = scores.masked_fill(mask, float("-inf")) # mask: True там, где ключ запрещёнweights = scores.softmax(dim=-1) # каждая строка даёт в сумме 1 по разрешённым ключамoutput = weights @ v # [B, n, d_v]causal = torch.triu(torch.ones(n, n, dtype=torch.bool), diagonal=1) # True над диагональю
Phuong and Hutter (2022) приводят точный псевдокод для обеих масок. Строка, в которой замаскированы все ключи, даёт 0/0, то есть NaN. Каузальная маска такого не допускает (диагональ разрешена), маска паддинга — тоже, пока в каждой последовательности есть настоящий токен; но при обеих масках и паддинге слева, как при генерации батчами, у запроса-паддинга могут быть замаскированы все ключи, поэтому реализации используют вместо −∞ большое конечное отрицательное число.
Без информации о позициях «cats chase dogs» переставляет строки X и выхода, а у весов — строки и столбцы: dogs получает (0.277,2.536) в обоих предложениях. В общем случае Attention(ΠX)=ΠAttention(X) для любой матрицы перестановки Π, если маски нет (каузальная маска привязана к позициям и это нарушает); доказательство — в заметке о позиционных представлениях. Порядок нужно передать извне: прибавить к X (абсолютные позиции) или встроить в оценки (относительные и ротационные позиции).
Рисунок
Перестановка двух слов лишь переставляет результат
Self-attention без информации о позициях на «dogs chase cats» и на «cats chase dogs». Вторая матрица весов — это первая, у которой строки и столбцы переставлены одинаково, и строки выхода тоже переставлены: вектор, вычисленный для «dogs», равен (0.277, 2.536) в обоих предложениях. Слой не может определить, кто за кем гонится; порядок нужно передать отдельно.
Токену часто нужны сразу несколько отношений: его синтаксическая вершина, антецедент, предыдущий токен. Один softmax может распределить вес по ним, но тогда их значения усредняются в один вектор. Многоголовое внимание (multi-head attention) запускает h вычислений внимания параллельно, каждое со своими проекциями меньшего размера, и объединяет их выходы:
где Q(r)=XWQ(r), K(r)=XWK(r), V(r)=XWV(r) для r=1,…,h, выход Hr головы r лежит в Rn×dv, [⋅;⋅] — конкатенация по оси признаков, а WO∈Rhdv×d.
При dk=dv=d/h все 3h матриц проекций содержат 3h⋅d⋅d/h=3d2 весов, а WO — ещё d2: 4d2 на слой при любом h, как у одной головы полной ширины. В небольшом энкодере для AG News из заметки о трансформере (d=32, h=4, dk=8) это 3⋅4⋅32⋅8+322=4096. Базовый трансформер использует d=512, h=8, dk=dv=64; у BERT-base (d=768, h=12) — 4⋅7682≈2.36 млн весов внимания на слой.
Конкатенация, за которой следует WO, — это сумма по головам. Разобьём WO на h блоков по dv строк, WO(r); тогда, обозначив через A(r) матрицу весов головы r, получаем
MultiHead(X)=r=1∑hA(r)XWV(r)WO(r).
Каждая голова добавляет свой вклад, со своим шаблоном внимания и своим отображением WV(r)WO(r) ранга не выше dv из прочитанного в записываемое (Elhage et al., 2021). В коде головы — одна проекция, у которой затем меняется форма; это быстрее, чем цикл по головам:
# W_Q: [d, h*d_k], h матриц W_Q^(r), поставленных рядом; так же для W_K и W_Vq = (x @ W_Q).view(B, n, h, d_k).transpose(1, 2) # [B, h, n, d_k]; так же для k и vout = attention(q, k, v, mask) # mask: [B, 1, n, n] или [B, 1, 1, n], распространяется на все головы; out: [B, h, n, d_k]out = out.transpose(1, 2).reshape(B, n, h * d_k) # конкатенация головy = out @ W_O # [B, n, d]
Рисунок
Многоголовое внимание: четыре головы размера 8 в модели ширины 32
Каждая голова проецирует один и тот же вход X (n × 32) в собственные запросы, ключи и значения размера d_k = 32/4 = 8, вычисляет собственные веса n × n и возвращает выход n × 8. Четыре выхода конкатенируются обратно в n × 32 и смешиваются через W_O. Шаблоны весов — игрушечные примеры на шести токенах (оценка 4 для одного ключа, 0 для остальных, на целевой ключ приходится около 0.92 веса): головы могут смотреть на предыдущий токен, на следующий токен, на общий первый токен или на одно значимое слово. Параметров на слой: 3 · 4 · 32 · 8 + 32² = 4096 = 4 · 32², столько же, сколько у одной головы полной ширины.
Головы работают бок о бок, и их вклады суммируются; слои ставятся друг на друга, поэтому запросы слоя уже несут контекст из нижних слоёв. Обученные головы смотрят на предыдущий или следующий токен либо следуют синтаксису или кореференции (Clark et al., 2019; Voita et al., 2019), и многие из них можно удалить после обучения с небольшой потерей качества (Michel, Levy and Neubig, 2019).
В матрице оценок n2 элементов на голову, каждый вычисляется за dk умножений, и произведение с V стоит столько же:
Операция
Время
Память
Последовательные шаги
Самый длинный путь
Проекции XWQ,XWK,XWV
O(nd2)
O(nd)
O(1)
—
Оценки QK⊤ и веса ×V
O(n2d)
O(n2) на голову
O(1)
O(1)
Рекуррентный слой, для сравнения
O(nd2)
O(nd)
O(n)
O(n)
Последние два столбца взяты из таблицы 1 в Vaswani et al. (2017). Внимание начинает доминировать, как только n становится небольшим кратным d: n=2d по сравнению с проекциями и около 6d по сравнению с целым блоком, где ширина полносвязного блока (feed-forward) равна 4d. В этом небольшом энкодере (n=128, d=32) матрицы оценок содержат 16384 элемента на голову на пример, а 2n2d≈1.0×106 умножений со сложением (multiply-adds) для QK⊤ и AV уже больше, чем 4nd2≈5.2×105 для WQ, WK, WV и WO. Взамен все n строк получаются одним матричным произведением. Системы с длинным контекстом борются с квадратичным членом: кэширование ключей и значений, точные ядра (kernels), которые никогда не хранят матрицу n×n (Dao et al., 2022), и разреженные, оконные или линеаризованные варианты.
Рисунок
Рекуррентность и self-attention: длина пути и стоимость
(а) В рекуррентном слое информация от токена 1 доходит до токена 6 через 5 обновлений состояния, и 6 шагов должны выполняться один за другим. (б) В self-attention каждый токен напрямую читает каждый другой токен: все 15 пар разделены одним шагом, и все позиции вычисляются одновременно. (в) Цена: на один слой оценки внимания и взвешенная сумма требуют около n²d умножений со сложением против nd² для рекуррентного слоя или проекций; при d = 512 оба члена равны при n = 512 с точностью до постоянных множителей, а для более длинных последовательностей доминирует внимание. Обе оси логарифмические.
Обучим 4-слойный энкодер с 4 головами из заметки о трансформере на AG News и посмотрим на все 16 голов на одном обучающем примере из 49 токенов:
Слой 1. Строки близки к one-hot-векторам на нескольких содержательных токенах; в голове 4 большинство строк выбирают injured, travel, southern или baghdad.
Слой 2. Веса распределены по многим ключам.
Слои 3 и 4. Вертикальные полосы: почти каждый запрос читает одни и те же немногие ключи (некоторые головы слоя 3, все головы слоя 4).
Карты показывают, откуда голова читает, а не на что опирается классификатор.
Квадратичность по n. Удвоение контекста учетверяет работу и память: 65536 весов на последовательность на слой при n=128, h=4; около 109 на голову при n=32000. Точные ядра экономят память, но не работу.
Веса должны давать в сумме единицу. Голова, которой нечего читать, «паркует» вес на разделителях, пунктуации или первом токене (Clark et al., 2019; Xiao et al., 2024).
Только взвешенные средние. Выходы — выпуклые комбинации значений, линейно отображённые через WO, без преобразования каждого токена по отдельности. Заметка о трансформере добавляет полносвязный слой, остаточные связи (residual connections) и нормализацию.
Attention(Q,K,V)=softmax(QK⊤/dk)V, где Q=XWQ, K=XWK, V=XWV: строка i выхода — взвешенное среднее строк значений с весами по тому, насколько запрос i совпадает с каждым ключом.
Размеры: X — n×d, оценки — n×n (m×n для cross-attention), выход — n×dv.
Отдельные WQ и WK позволяют оценкам sij и sji различаться и не дают совпадению токена с самим собой доминировать. Оценки через обычное скалярное произведение симметричны; веса softmax, построенные из них, уже немного различаются, потому что у каждой строки свой знаменатель.
Делим на dk одной головы, потому что Var(q⋅k)=dk; иначе softmax насыщается и его градиент исчезает.
week05_transformer.ipynb. Ячейки нумеруются с 1, считая все ячейки, включая текстовые. Приведённые числа взяты из прогона, сохранённого в курсе; у ячеек, изменённых с тех пор под текущие версии библиотек, сохранённого выхода нет.
Внимание (ячейки 38–40). Одна голова, причём головы слоя вычисляются в цикле Python, а не одной проекцией с изменением формы: проекции nn.Linear(embed_dim, embed_dim // num_heads) без смещения, оценки через torch.bmm, делённые на dk, masked_fill с −∞, softmax, дропаут, bmm с V. MultiHeadAttention конкатенирует головы из nn.ModuleList и применяет nn.Linear(embed_dim, embed_dim) без смещения в роли WO. Маска паддинга tokens == pad_idx повторяется по строкам запросов до размера [B,n,n].
Модель (ячейки 42–45). Четыре слоя, описанные в заметке о трансформере. Сохранённый выход ячейки 45 показывает Linear(32 → 8) для каждой головы и Linear(32 → 32) для WO: 4096 весов внимания на слой.
Карты внимания (ячейки 51–62). 16 матриц весов одного примера из 49 токенов (ячейка 59, у каждой панели своя цветовая шкала) и слой 1, голова 4 с подписями токенов (ячейка 62); они описаны выше и сохранены только как изображения.
Подводные камни. Комментарии о размерах неверны для WV в ячейке 39 (она отображает 32 в 8) и для attention_scores в ячейке 42 (размер [B,n,n,h,L]).
Cross-attention.week04_lstm_attention_transformer.ipynb, ячейки 71–79, сохраняет усреднённые по головам веса однослойного трансформера для перевода, размер [32,31,32] (батч, целевая, исходная последовательность); они обсуждаются в предыдущей заметке.
Self-attention смешивает позиции и больше ничего не делает: у него нет нелинейности для отдельного токена, нет понятия о порядке, а глубокий стек голых слоёв внимания трудно обучать. Заметка о трансформере оборачивает многоголовое внимание остаточными связями и нормализацией слоя (layer normalization), добавляет полносвязную сеть, применяемую к каждой позиции отдельно (position-wise feed-forward), и собирает из блоков энкодер, декодер с каузальным self-attention или энкодер–декодер с cross-attention.
A. Vaswani, N. Shazeer, N. Parmar, J. Uszkoreit, L. Jones, A. N. Gomez, Ł. Kaiser, I. Polosukhin. Attention Is All You Need. NeurIPS, 2017. §3.2.1 (внимание на основе масштабированного скалярного произведения и сноска о дисперсии), §3.2.2 (многоголовое внимание), таблица 1 (сложность, последовательные операции, длина пути).
D. Jurafsky, J. H. Martin. Speech and Language Processing, 3-е изд., черновик от 19 августа 2026 г. Глава 7, Transformers and Pretraining (self-attention, многоголовое внимание, маски).