Attention и трансформеры в NLP: что спрашивают на собеседованиях
Введение
Attention — один из важнейших механизмов, который является основной развития современного DL. Вопросы, связанные с этим термином, часто встречаются в интервью на вакансии DL-инженера.
В статье подготовили частые вопросы про Attention с собеседований и ответы на них. Вопросы разбиты на тематические модули, а ответы на них спрятаны под спойлерами — прежде чем раскрыть их, попробуйте ответить сами.
Классический Attention
❓ Почему RNN теряет контекст при длинных последовательностях?
- Ограниченный буфер памяти. Вся история сжата в один вектор скрытого состояния ⇒ детали теряются по мере роста длины контекста.
- Затухающие/взрывающиеся градиенты.
При прохождении градиента через длинную цепочку матриц производные на каждом шаге умножаются:
— если они меньше единицы, сигнал быстро затухает (vanishing gradient);
— если больше единицы, градиент растёт (exploding). (статьи [1][2])
В обоих случаях ранняя информация теряется.

- Последовательная природа и длинный путь зависимости.
Влияние ранних токенов проходит через\(O(n)\) шагов. По мере продвижения скрытое состояние многократно преобразуется и частично перезаписывается ⇒ дальние зависимости теряются; При обучении это усиливается затухающими/взрывающимися градиентами. Эти проблемы решаются механизмом внимания (Attention).
❓ Как работает классический механизм внимания, предложеннный в статье Bahdanau [3] ?
Идея: По текущему скрытому состоянию декодера и информации о всей входной последовательности хочется строить новое скрытое состояние, из которого можно точно предсказать следующее слово. Самый простой вариант получить из всего контекста один вектор — усреднить, но тогда все слова будут одинаково важны. А в реальности это не так: при обработке предложения «I am a student» для слова «I» гораздо важнее «student» , чем «a».
Примечание
В оригинальной статье Bahdanau [3] энкодер — BiRNN; здесь для простоты объяснения механизма внимания показан однонаправленный вариант. Формулы внимания остаются теми же.

Механизм внимания (attention) позволяет сделать «умное усреднение» контекста:
- На каждом шаге декодирования вычисляется взвешенная сумма скрытых состояний входных токенов относительно текущего скрытого состояния декодера;
- Чем важнее конкретное входное слово для текущего шага, тем больший вес оно получает — то есть модель «обращает больше внимания» на него;
- Веса нормируются так, чтобы их сумма равнялась единице.
В итоге получается контекстный вектор, отражающий, на что именно модель сфокусировалась.

Пример работы классического механизма внимания:
Пусть есть входное предложение
Наша цель — определить, на какие части входа стоит «обратить внимание», чтобы корректно предсказать следующее слово.
- Считаем матрицу соотношений \(scores = qK^T\)
i-ая компонента произведения оценивает сонаправленность запроса и ключа: чем ближе вектора, тем сильнее сигнал. - Получаем веса \(a=softmax(qK^T)\)
Softmax нормализует оценки, превращая их в вероятностное распределение (сумма = 1) — модель «распределяет внимание» по токенам, что делает контекст интерпретируемым. - Получаем вектор контекста: \(c = α К\)
Веса умножаются на \(K\), и получаем контекстный вектор — представление всей входной последовательности с точки зрения \(q\).
❓ Какие ограничения у «attention поверх RNN»?
Attention улучшает доступ к контексту, но RNN остаётся последовательной: скрытые состояния энкодера/декодера считаются шаг-за-шагом, что мешает полной параллелизации и масштабированию.
Это верно и для двунаправленных RNN: они также прогоняют последовательность по порядку: только слева→направо и справа→налево отдельно. То есть проблема параллелизации никуда не исчезает.
Self-Attention и Transformer
❓Как работает Self-Attention: в чем идея, как считается?
Механизм Self-attention — ключевой компонент архитектуры трансформеров, предложенный в статье «Attention Is All You Need»[4]. Он позволяет каждому токену в последовательности оценивать значимость других токенов перед формированием выходного представления. Это происходит путём вычисления специальных весов для всех токенов относительно каждого отдельного токена, благодаря чему модель может учитывать контекст всей последовательности.
Формула Scaled Dot-Product Attention выглядит следующим образом:
Где \(Q\), \(K\), \(V\) — обучаемые матрицы, проекции исходных векторов слов.
При расчёте self-attention операции выполняются не над исходными векторами слов, как в классическом Attention, а над их обучаемыми проекциями — векторами запросов (Q), ключей (K) и значений (V). Они получаются из входных эмбеддингов текста и через матрицы \(W_Q\), \(W_K\) проецируются в пространство размерности \(d_k\). \(d_k\) — размерность ключей и запросов в одной голове внимания (на схеме ниже это вторая размерность матриц \(W_Q,\) \(W_K\))
Смысл матриц \(Q\), \(K\), \(V\) можно объяснить аналогией с задачей информационного поиска. Имея вектор запроса q, для каждого ключа k берём столько сигнала из v, насколько его ключ k похож на запрос q.

Деление на \(\sqrt{d_k}\) в формуле выше удерживает масштаб произведения \(QK^T\), которое может получиться слишком большим. Такие логиты внимания перенасыщают softmax, делая его распределение непроходимым для градиентов: softmax резко отдаёт почти весь вес одному токену, остальные близки к 0, и градиент там тоже ~0. В этом случае обучение будет проходить нестабильно.

После получения матриц \(Q\), \(K\), \(V\) считаем веса внимания для токена как сходство \(Query\) с каждым \(Key\) (нормируются в сумму 1 с softmax). Затем веса используются как коэффициенты при взвешивании всех \(Value\), давая новое скрытое состояние, как на схеме ниже.

❓ Чем Self-attention отличается от классического механизма внимания, предложенный в статье [3] (Bahdanau)?
В классическом attention декодер «смотрит» на скрытые состояния энкодера.
В self-attention каждый токен «смотрит» на все токены внутри той же последовательности (включая себя) — то есть контекст формируется без отдельного энкодера и декодера, внутри одного слоя.
❓ Какая сложность работы self-attention и почему? Почему архитектуры на self-attention (например, Transformer) масштабируются лучше, чем RNN, хотя теоретическая сложность выше?
Сложность работы self-attention: Матрица \(Q K^T\) имеет размер n×n: взаимодействуют все пары входных позиций. Это даёт \(O(n^2)\) умножений и хранение промежуточных тензоров. Масштабирование Transformer-like vs. RNN: Для каждого вектора слова нужно независимо посчитать скалярное произведение со всеми векторами слов — \(O(n²)\) от длины текста, но все из них можно делать параллельно. В RNN тратилось \(O(n)\), но все операции были последовательными.
❓ Как работает и зачем используется Multi‑Head Attention в трансформерах?
- Одна «голова» внимания может фокусироваться лишь на одном типе взаимосвязей и генерирует одно распределение весов.
- Multi‑head разбивает пространство признаков на h подпространств и обучает отдельные матрицы \(W_Q\), \(W_K\) и \(W_V\) для каждой головы, позволяя каждой фокусироваться на разных аспектах последовательности.

- Выходы всех голов конкатенируются и проходят через общую линейную матрицу.

Таким образом Multi-Head Attention = несколько «голов» внимания, которые считаются параллельно.
При этом каждая голова имеет свои матрицы (\(W_Q\), \(W_K\), \(W_V\)), то есть свой способ смотреть на контекст. А значит, она может выучить свой тип сигнала: например, одна ловит синтаксис, другая — coreference, третья — позиционные связи.
За счёт того, что у каждой головы свои проекции в меньшую размерность (\(d_k\)), мы можем держать много таких голов, не делая слой слишком тяжёлым.
Это даёт гибкость: модель может одновременно смотреть на вход под разными углами, а не через одну единственную матрицу внимания.
❓ Почему в трансформерах используют LayerNorm вместо BatchNorm?
- BatchNorm считает среднее и дисперсию по батчу. Это ок, если у всех объектов одинаковая структура, как например в картинках. Но в тексте это так не работает: предложения разной длины, мы добиваем их паддингами. Из-за этого статистики BatchNorm начинают зависеть от количества паддинга, а не от содержимого ⇒ нормализация становится шумной и нестабильной.

- LayerNorm нормализует каждую позицию по признакам отдельно, поэтому не зависит от размера батча и корректно работает на последовательностях разной длины.

❓ Почему применяют skip connection (residual) связи в трансформерах?
- В очень глубоких сетях градиент на ранних слоях может практически занулиться. На обратном проходе он на каждом слое умножается на производные активаций и веса. Если эти множители по модулю меньше 1, то после многих слоёв значение градиента становится настолько маленьким, что ранние слои почти не обучаются (vanishing gradient).
- Добавляя к выходу слоя его вход (F(x) + x), мы создаём прямой путь для градиентов, где сумма линейна и не приводит к затуханию. Производная от суммы будет всегда с единицей ⇒ градиенты будут не меньше единицы, и процесс затухания не пойдёт. Это облегчает обучение и делает сеть стабильнее.
- В трансформерах остаточные связи также сохраняют исходную информацию: self‑attention может изменять порядок и структуру представлений, а skip‑связь напоминает модели о первоначальном состоянии.

❓ Что делает полносвязный блок FeedForward в трансформерах?
Формула выглядит так:
\(FFN(x) = max(0, xW_1+ b1)W2 + b2\)
Это двухслойный MLP, который применяется к каждому токену независимо:
- Сначала линейное преобразование увеличивает размерность, затем применяется нелинейность, используя ReLU.
- Второе линейное преобразование возвращает размерность к исходной, что позволяет соединить признаки из разных голов.
Этот блок вводит дополнительную нелинейность и позволяет модели учить более сложные комбинации признаков.
Маски, обучение и генерация
❓ Что такое маски в контексте обучения трансформеров? Как и для чего они используются?
- Предложения в батче имеют разные длины, поэтому их выравнивают, добавляя PAD‑токены. Например, предложения ниже выравнены до длины 6:
- [«Мама», «мыла», «раму», «PAD», «PAD», «PAD»]
- [«а», «и», «б», «сидели», «на», «трубе»]
Эти токены не несут смысла и должны быть проигнорированы слоями во время обучения.
- Для этого формируют маску: бинарную матрицу, где позиции, соответствующие PAD, отмечены как «запрещённые», такая маска подаётся в слой внимания.
- При вычислении внимания сначала считаем \(Q·Kᵀ\).
Потом к этим оценкам прибавляем маску, умноженную на большое отрицательное число в ячейках, куда смотреть нельзя: \(masked = \text{scores} + \text{mask} \cdot (-10^{9})\)

После применения softmax эти ячейки получают нулевую вероятность и не влияют на результат.

- В декодере дополнительно используется каузальная маска, чтобы модель не смотрела в будущее (то есть на токены справа): элементы выше главной диагонали также устанавливаются в −∞.
❓ Как получить распределение токенов по словарю при генерации?
Для этого нужна голова (LM Head):
- Пусть на вход было seq_len токенов, тогда выходы из трансформера — это матрица размерности [seq_len, d_model]. Здесь d_model — внутренняя размерность эмбеддингов/скрытого состояния трансформера (например 768 в BERT Base).
- Чтобы получить (seq_len + 1) слово, понадобится линейный слой размерности [d_model, vocab_size]. Эти веса обучаются вместе с моделью. Часто их инициализируют так же, как эмбеддинги токенов.
- После матричного умножения получим размерность [seq_len, vocab_size].
- К получившемуся вектору (его элементы называются логитами) применяется softmax, и получается распределение по словарю!

❓ Что такое температура в генерации и как она влияет на выбор слов?
- Температурное масштабирование делит логиты на коэффициент T, после чего применяется softmax.
\(logits’ = logits/T\)
\(pi = softmax(logits′)\)
- При большом T распределение сглаживается: высоковероятные токены перестают доминировать, и вывод становится более разнообразным.
- При маленьком T распределение становится острым, и модель предпочитает наиболее вероятные токены, делая выводы более детерминированными.
Пример для наглядности:

❓ Как получить слово из распределения при генерации? Что такое стратегии Top‑K и Top‑P (nucleus) сэмплинга? Как бороться с зацикливанием и для чего нужен Frequency penalty?
Самый простой вариант — можно брать токен с максимальной вероятностью (greedy). Такой подход дает детерминированный генерации, но часто приводит к зацикливанию или однообразному тексту.
В качестве альтернатив greedy используют стохастические стратегии выбора токена, которые дают более разнообразные генерация:
- сэмплинг с изменением температуры (обсудили в вопросе выше);
- Top-K сэмплинг;
- Top-P (nucleus) сэмплинг.
А чтобы избежать зацикливаний, добавляют ограничения:
- frequency penalty: штраф на частоту токена в уже сгенерированном тексте;
- запрет n-gram повторов.
Top-K сэмплинг
Выбирают K самых вероятных токенов, отбрасывают все остальные, затем нормируют вероятности внутри этой топ-K группы так, чтобы они суммировались в 1, и случайно выбирают один токен пропорционально этим вероятностям.
Другими словами, здесь мы не всегда берём самый вероятный токен — сэмплируем из усечённого распределения.K задаётся заранее, и чем он меньше, тем менее разнообразен вывод.

Top‑P (nucleus) сэмплинг
- Выбирается минимальное множество токенов, суммарная вероятность которых ≥ P (например 0.9–0.95). Количество кандидатов зависит от формы распределения.
- Сортируем токены по вероятности и набираем их сверху, пока их суммарная вероятность не станет ≥ P (например, 0.9). Этот минимальный набор токенов и есть так называемое «ядро» (nucleus) — оно покрывает основную массу распределения (например, 90% вероятности).
- После этого, как и в Top-K, нормируем вероятности внутри ядра и выбираем один токен случайно пропорционально этим весам.
Размер ядра при этом динамический: если распределение острое, туда попадут 2–3 токена; если «плоское» — десятки. Это делает Top-P более гибким, чем Top-K.

В отличие от Top‑K, где включаются ровно K кандидатов, Top‑P автоматически регулирует размер «ядра», исключая длинный хвост маловероятных токенов и делая выбор более устойчивым.
Frequency penalty
Частотный штраф уменьшает вероятность повторно появляющихся токенов, чтобы модель не зацикливалась на одних и тех же словах.
Штраф для \(token_i\) вычисляется как
\(penalty (token_i) = α ∗ count(token_i, prefix)\)
где α — коэффициент, а count — число появлений токена \(token_i\) в уже сгенерированной последовательности \(prefix\).
Получаем логиты:
\(logitsi = logits_i − penalty(token_i)\)
Вычитая этот штраф из логитов перед softmax, мы снижаем вероятность часто встречающихся токенов и стимулируем более разнообразную генерацию.
Запрет n-gram повторов
Жёстко запрещаем повторять уже сгенерированные n-граммы: то есть модель просто не может выбрать токен, который приведёт к точному повтору длинного шаблона)
Эффективный инференс
❓ Как можно ускорить Attention на практике?
На практике ускорение Attention — это не про новую архитектуру, а про оптимизацию памяти и пропускной способности GPU.
Главная цель — уменьшить количество обращений к HBM (высокоскоростной памяти видеокарты), где хранятся промежуточные тензоры. Чем меньше операций чтения и записи, тем быстрее работает инференс. Когда говорят «уменьшаем HBM‑трафик», это значит, что модель реже читает и записывает данные из основной памяти GPU, выполняя больше вычислений локально и избегая избыточного копирования тензоров.
Вычисляет \(QK^T\), softmax и умножение на V блочно прямо в локальной памяти (SRAM), не создавая полную n×n матрицу внимания. Это снижает обращения к HBM и уменьшает пиковое потребление памяти — особенно важно при длинных последовательностях.Ограничивает число взаимодействий между токенами локальными окнами или шаблонами разреженности. Меньше пар токенов взаимодействует ⇒ меньше вычислений и ближе к линейной сложности по n. Минус подхода — модель может терять дальние зависимости, если не добавить специальные «глобальные» токены.
Комбинация локальных окон с редкими глобальными связями. Сохраняют способность видеть дальние токены при почти линейной сложности.
На изображени ниже: матрица внимания (\(n \times n\), где каждая клетка показывает, какие токены могут взаимодействовать между собой. Подсвеченные клетки — разрешённые пары «запрос–ключ» (там вычисляется внимание).
- (a) Random attention — случайные связи между токенами, добавляют «мосты» между далекими частями последовательности.
- (b) Window attention — локальные окна: каждый токен видит только соседей.
- (c) Global attention — несколько «глобальных» токенов (например, [CLS]), которые видят всех и видимы всем.
- (d) BigBird — комбинирует все три типа: локальные окна + глобальные + случайные связи, что сохраняет дальние зависимости при почти линейной сложности.

Кейсы
❓Как модель определяет, на какие слова нужно обратить внимание при обработке слова «cat» в предложении «The cat sat on the mat»?
Разберём пошагово предложение «The cat sat on the mat»:
- Формируются Q, K и V: Эмбеддинги, соответствующие токенам слов во входном предложении, преобразуются в три вектора: Query (Q) — запрос, Key (K) — ключ и Value (V) — значение. Для слова «cat» Q описывает, какой контекст ему нужен; для каждого слова «sat», «on», «mat» K представляет его свойства; V содержит их смыслы.
- Вычисляется скалярное произведение \(Q·Kᵀ\): запрос для «cat» умножаем на ключи всех слов. Это даёт оценки, насколько каждое слово релевантно текущему. Если ключ «sat» совпадает с запросом «cat», то его оценка будет высокой.
- Применяется softmax: полученные оценки преобразуются в вероятности (веса), сумма которых равна 1. Так модель распределяет внимание: самые релевантные слова получают больший вес.
- Взвесим значения: для каждого слова умножаем V на соответствующий вес и затем суммируем эти векторы. Например, если «sat» получил большой вес, его значение внесёт наибольший вклад в итоговый контекст для «cat».
Полученный контекстный вектор используется в следующем слое, чтобы предсказать следующее слово или обновить представление токена. Благодаря тому, что self‑attention рассматривает все слова одновременно, модель может выявлять длинные зависимости и не ограничивается порядком в предложении.
❓Напишите код Transformer‑LM (упрощённый)
Частый кейс — на собеседовании предлагают написать упрощенный код блока архитектуры трансформера, либо поправить ошибки в уже имеющемся коде.
Для подготовки к этому полезно посмотреть готовые реализации:
- The Annotated Transformer — пошаговая реализация и объяснения по статье “Attention is all your need”
- Karpathy nanoGPT — минималистичный GPT
- HuggingFace Transformers — промышленная библиотека и best practices
Заключение
В статье были рассмотрены частые вопросы про attention с собеседований и ответы на них, и мы надеемся, что она поможет читателям в подготовке к интервью. Если вы не нашли ответа на какой-то вопрос по теме — смело пишите его в комментариях, расширим туториал вместе!
В следующих статьях в подобном формате расскажу про токенизацию и эмбеддинги, а в tg-канале можно почитать про мой личный опыт с AI/LLM, подписывайтесь!
Ссылки
[1] https://arxiv.org/abs/1211.5063
[2] https://www.superdatascience.com/blogs/recurrent-neural-networks-rnn-the-vanishing-gradient-problem
[3] https://arxiv.org/abs/1409.0473
[4] https://arxiv.org/abs/1706.03762
[5] https://telegra.ph/Pro-normalizaciyu-09-13
[6] https://arxiv.org/abs/2205.14135?utm_source=chatgpt.com
[7] https://arxiv.org/abs/2103.14030?utm_source=chatgpt.com
[8] https://arxiv.org/abs/2004.05150?utm_source=chatgpt.com
[10] Karpathy nanoGPT
[12] Краткая история механизма внимания в NLP

