Как работают языковые модели? Думаю, это один из самых актуальных вопросов в последние годы. Я регулярно задаюсь этим вопросом и постоянно читаю материалы по работе трансформеров. Из всего, что я узнал, самый сложный, по моему мнению, механизм в работе LLM - внимание (attention)
Введение
Привет, Хабр! В этой статье я постараюсь рассказать, как механизм внимания помогает языковым моделям обрабатывать тысячи слов контекста и почему с этим не справлялись в свое время обычные RNN. В конце статьи я расскажу про проблемы классического attention и современные адаптации.
Для начала вспомним про токенизацию, векторизацию и работу LSTM. Примем слово за отдельный токен и напишем для него embed.

Проблема: мы имеем одинаковый входной вектор для одного и то же слова (ключ) в разных контекстах.
В первом предложении, когда LSTM дойдет до слова «ключ», в её скрытом состоянии уже будет учтено слово «гаечный», поэтому общий вектор уже будет смещён в сторону, связанную с инструментами. Затем модель обновит состояние с учётом вектора ключа и пойдёт дальше. Скрытое состояние в таких сетях - общий (единый) вектор контекста.
Но как проблема контекста разрешалась в последнем случае, когда нужное слово стоит на первом месте? Для того чтобы понять смысл слова «ключ», нужно знать, какие слова идут после него. В этом случае на помощь приходила Bi-directional RNN: одна сеть читала слева направо, а другая - справа налево. После чего их скрытые состояния конкатенировались.

Но почему такая технология не сохранилась? На выходе мы получали единый вектор контекста, который содержал в себе информацию всех предыдущих слов. У этого подхода есть несколько проблем:
Чтобы получить вектор 100-го слова, RNN требовалось последовательно прогнать 99 предыдущих
Дальние зависимости. Этот механизм работал хорошо, когда связанные слова стояли недалеко друг от друга, но если одно слово стоит в начале, а др��гое в конце, то такой алгоритм размывал контекст и не сохранял смысл
С такими проблемами мы бы не смогли скармливать моделями огромные корпусы текста. Превратить весь текст "Войны и мир" в один вектор без потери контекста уже не кажется такой простой задачей.
Трансформеры
В механизме внимания, который был представлен в 2017 году, авторы постарались учесть эти проблемы, поэтому в трансформерах есть блок Attention, в котором эмбеддинги могут передавать информацию друг другу:

Замечание: классический self-attention в современных моделях претерпел изменения, но ключевые концепции сохранились (обо всём дальше). Ссылка на оригинальную статью: «Attention Is All You Need»
Можно сказать, что на этом шаге смысл каждого токена (слова) перетекает к каждому другому слову в предложении. Таким образом, механизм внимания обновляет вектор каждого конкретного слова так, чтобы он больше коррелировали с другими словами в предложении. Учитывая большое количество таких слоев внимания (например, в GPT-3 их было 96), мы получаем обновленные векторы для каждого слова:

Что такое Q, K, V
Давайте подробнее посмотрим на пример обновления смысла слов. Для простоты предположим, что только прилагательные обновляют состояние для существительных в предложении: «Ржавый ключ с трудом открыл старый замок». Назовём этот механизм «голова внимания».
Сначала нам нужно получить вектор для каждого слова и обновить его с учетом позиции. Это нужно для того, чтобы нарушить инвариантность к перестановкам механизма Self-Attention, позволив модели различать последовательность слов и понимать синтаксические и семантические отношения, зависящие от порядка (например, кто является субъектом, а кто объектом действия). Таким образом, в трансформерах мы имеем вектор, который содержит информацию о значении и позиции (position embeddings)

Если вы что-нибудь слышали о self-attention, то наверняка встречали обозначения Q, K и V. Давайте попробуем разобраться.
Представьте, что в нашей «голове внимания» каждое существительное задаёт своеобразный вопрос, например: «Есть ли тут прилагательные?». На что слова «ржавый» и «старый» отвечают: «Да».

Этот вопрос кодируется в вектор, который обозначается буквой Q (query):

Но как мы получили вектор запроса, если ранее вы считали только векторы для токенов? Для вычисления этого вектора существует матрица Wq, которая умножается на embed слова:

Таким образом, получившийся вектор 'подсвечивает' определенное свойство слова. Можно сказать, Wq - фильтр, который выделяет смысл в соответствии с текущей головой внимания.
Посмотрим на упрощенный пример для нашей Attention Head, которая специализируется на синтаксических связях «существительное - прилагательное».
Пусть наше упрощенное пространство признаков имеет 3 оси:
Ось X: «Предметность» (существительное ли это?)
Ось Y: «Свойство» (прилагательное ли это?)
Ось Z: «Семантика открывания» (связь с действием)
Вектор для слова 'замок' имеет следующий вид:
а матрица весов (Wq) после обучения получила значения:
Входной вектор попадает в матрицу Wq.
Задача Wq в этой голове - сформировать запрос: "Я существительное, покажите мне мои определения".
Вектор поворачивается и масштабируется так, чтобы значение по оси Y стало максимальным, поэтому получившийся вектор Q равен:
Таким образом мы получаем вектор, который можно понимать как "Я смотрю только туда, где лежат прилагательные (ось Y). Меня не интересуют другие предметы (X) или глаголы (Z)".
Матрица Wk
У нас есть вопросы, осталось найти ответы. За них отвечает буква K (key). По своему смыслу ключи - это потенциальные ответы на запросы Q. Для этого существует матрица Wk, которая также применяется ко всем словам, чтобы они могли (или не могли) ответить на наш запрос.
На этом шаге мы надеемся, что матрица Wk сформирует векторы так, чтобы при сопоставлении Q и K мы поняли, какие слова наиболее связаны. Для нашего примера:

Наибольшее значение мы получили на пересечении слов 'старый' и 'замок'.
Если снова вернуться к упрощенной модели, то слово 'старый' могло бы иметь вектор:
а матрица ключей могла бы выглядеть так:
Тогда вектор K для слова 'старый' имел вид:
Теперь мы имеем два вектора, скалярное произведение которых равно:
Для функции Softmax это дало бы значение, близкое к 1. Значения для остальных слов были бы очень маленькими.
Кстати, о Softmax. После получения каких-то значений во всех ячейках матрицы мы хотим, чтобы сумма в каждой строке равнялась 1, поэтому применяем эту функцию построчно. В итоге мы получаем нормализованные значения, которые показывают, насколько слово слева соответствует слову сверху. Это называется Attention pattern.
В оригинальной статье «Attention Is All You Need» эта формула представлена так:

Произведение ключей на запросы дополнительно делится на корень из размерности пространства. Это нужно для того чтобы предотвратить попадание функции Softmax в область насыщения, где градиенты становятся исчезающе малыми из-за слишком больших значений скалярного произведения, что сделало бы обучение модели нестабильным или невозможным
Матрица Wv
На текущем шаге мы знаем, какие слова больше всего связаны между собой. Теперь нам нужно как то передать значение слов, чтобы изменить вектор. Для этого существует отдельная матрица Wv.
Можно сказать, что values отвечает только за смысл, который нам необходимо передать слову, а матрица Wv обучилась выделять эту полезную нагрузку.
Для этого мы умножим матрицу внимания на векторы значений (Values). В результате вклад слов, которые больше всего связаны с нашим словом, вырастет, а вклад остальных - практически обнулится.

После этого мы сложим все взвешенные векторы значений и прибавим получившийся результат к исходному вектору слова. Теперь наш «ключ» в векторном пространстве стал не просто ключом, а «ржавым ключом», так же как «замок» стал «старым замком». Таким образом, мы изменили вектор с учётом только зависимых слов и их смысла.
Если возвращаться к упрощённому примеру с замком, то предположим, что вектор для «старости» имеет вид:
а матрица Wv:
Тогда 'чистый' вектор для старости имеет вид:
Теперь рассчитаем результат внимания для слова «замок» (Attention Output). Предположим, веса внимания распределились как 0.9 (к слову «старый») и 0.1 (к самому себе):
Тогда x_final = (2, 0.1, 0.5) + (0, 4.51, 0) = (2, 4.61, 0.5)
Подведем промежуточный итог:
Всё, что мы увидели выше, можно назвать одной головой внимания, которая параметризована тремя матрицами:
Матрицей запросов (Query);
Матрицей ключей (Key);
Матрицей значений (Values).
Всё это необходимо для того, чтобы определить, какие слова наиболее связаны друг с другом. При генерации механизм внимания помогает правильно выбрать следующее слово. Например, если бы нам необходимо было предсказать слово «замок», то на слове «старый» мы бы задали вопрос: что стоит до него и как влияет? Скорее всего, внимание обратилось бы на слова «ключ» и «открыл», тогда «ключ» + «открыл» + «старый» повысили бы вероятность предсказания слова «замок».
Проблемы и развитие
К сожалению, каким бы прорывным ни был этот механизм, он имеет свои недостатки, которые не позволили сохранить его в современных LLM в неизменном виде. Возможно, вы уже догадались о некоторых из них: 00
Квадратичная сложность вычислений: как мы увидели, для последовательности длины n нужно вычислить n*n попарных сходств. Так при n=1000 количество операций = 1M, а при n=100.000 кол-во операций = 10 млрд - это слишком много даже для GPU. Что уж тут говорить про контекстное окно в 1млн токенов
Квадратичные требования к памяти: Хранение матрицы внимания n* n потребляет огромную память. При n=8000 и использовании float32 мы бы использовали примерно 268 MB памяти на один слой внимания (а их десятки и сотни), поэтому даже GPT-3 с 96 слоями и окном в 2048 токенов требовал специальной оптимизации памяти
Распыление внимания: при большом n распределение softmax становится слишком плоским и неинформативным. Мы бы просто получали 0 у всех значений
Отсутствие локальности: это одновременно и плюс и минус. Чаще всего слова, которые влияют на смысл находятся в пределах небольшого окна из 10-15 слов.
Но с момента выхода статьи появилось множество других механизмов, которые решали часть проблем. Давайте посмотрим на некоторые из них.
MQA / GQA
Алгоритм MQA (Multi-Query) и GQA (Grouped-Query) был направлен на решение проблемы бутылочного горлышка пропускной способности памяти. Как вы помните, в классическом декодировании одним из основных ограничений была скорость загрузки кэшированных состояний ключей и значений (KV-cashe) из видеопамяти (HBM) в вычислительные ядра. Поэтому автора сосредоточились на уменьшении размера KV-cache для повышения пропускной способн��сти.
В стандартной архитектуре трансформеров каждый токен проецируется в H независимых подпространств для Query, Key, Value (мы видели это ранее), где H - количество голов, в котором есть свои матрицы Wq, Wk, Wv. То есть состояние для одного токена с количеством головом H=4 будет выглядеть так:
Голова (H) | Query | Key | Value |
|---|---|---|---|
Head 1 | q1 | k1 | v1 |
Head 2 | q2 | k2 | v2 |
Head 3 | q3 | k3 | v3 |
Head 4 | q4 | k4 | v4 |
Размер KV-кэша (на токен): 2 H * d_head, что равно 2 * D, где D - общая размерность модели.
Вычисление механизма внимания для каждой головы i происходит по формуле: Attention(qi, Ki, Vi) = Softmax( (qi Ki^T) / √d ) Vi, где d - размерность модели
В предложенном в 2019 году метод MQA заключается в использовании общих ключей и значений для всех голов запросов.
Мы сохраняем H проекций для Q, но сжимаем K и V до одной проекции. То есть вместо подобранных связок Q1-K1-V1, где каждый элемент обучен выделять только нужную информацию и выбрасывать все остальное, мы имеем универсальный ключ, который должен ответить на любой вопрос.
Такой подход перекладывает всю нагрузку на Q, которые должны быть обучены так, чтобы дать максимальное произведение с нужной частью универсального вектора K.
Куда делался вектор V? Он также стал универсальным, так как в процессе обучения получал градиенты от всех голов одновременно. Это позволяет 'упаковывать' признаки так, чтобы они не перекрывались.
Пример:
Голова Q1 выучила маску [1, 1, 0, 0], чтобы уметь распознавать части речи. Тогда при умножении она увидит только первые два числа 0.9, 0.1.
Обновленное состояние для токена:
Голова | Query | Key | Value |
|---|---|---|---|
Head 1 | q1 | K | V |
Head 2 | q2 | K | V |
Head 3 | q3 | K | V |
Head 4 | q4 | K | V |
Размер KV-кэша (на токен): 2 × 1 × d_head = (2 × D) / H
Сжатие: В H раз по сравнению с MHA.
Метод GQA (2023) представляет собой интерполяцию между MHA и MQA. Вместо того чтобы сжимать все K, V в одну голову, мы группируем головы и назначаем каждой группе уникальную пару K, V.
Группа | Голова | Query | Key | Value |
|---|---|---|---|---|
Group A | Head 1 | q1 | k_A | v_A |
Head 2 | q2 | k_A | v_A | |
Group B | Head 3 | q3 | k_B | v_B |
Head 4 | q4 | k_B | v_B |
Размер KV-кэша для одного токена в методе группированного запроса (GQA) вычисляется по формуле: 2 × G × d_head.
Выигрыш в памяти: Мы сокращаем объем требуемой памяти для KV-кэша в H / G раз по сравнению со стандартным много головным вниманием (MHA)
Недостатки MQA и GQA:
Падение качества для MQA: Из-за того, что вектор K один на всех, возникает интерференция. Разные головы Query пытаются "вытащить" из одного вектора K противоречивую информацию.
Обучение с нуля сложнее, так как матрица W_K должна принять градиенты от всех голов
Гипер-параметр в GQA: в этом методе появился настраиваемый параметр G (кол-во групп), который нужно будет учитывать при обучении
DeepSeek Native Sparse Attention (NSA)
Статья о NSA — одна из наиболее революционных в 2025 году. NSA сокращает количество вычислений для каждого запроса, группируя ключи и значения в блоки и обрабатывая их по трём направлениям: сжатые крупнозернистые токены, выборочно сохранённые мелкозернистые токены и скользящее окно для локального контекста.
Ссылка на оригинал: Native Sparse Attention: Hardware-Aligned and Natively Trainable Sparse Attention

Внимание для текущего токена собирается из трех источников:
Global Branch (Статичная): Всегда удерживает в фокусе "якоря" - первые токены последовательности (например, системный промпт), гарантируя следование инструкциям.
Local Branch (Линейная): Использует механизм Sliding Window, охватывая фиксированное окно соседей (например, последние 2048 токенов) для поддержания грамматики.
Selected Branch (Динамическая): Самая сложная часть, отвечающая за поиск фактов в глубокой истории.
Главная идея NSA заключается в том, что внимание не должно быть монолитным и любой момент времени токену реально нужна лишь часть окружения. Поэтому итоговое внимание для токена t формируется как сумма трех независимых ветвей:
Давайте детально разберем, как именно функционирует самая сложная и инновационная часть этой триады - Selected Branch. Этот процесс превращает линейный поиск по памяти в выборку, проходя через несколько этапов оптимизации:
Чтобы эффективно работать с огромным контекстом (100k+ токенов), NSA отказывается от поштучной обработки. Весь массив ключей K в памяти (KV-Cache виртуально разбивается на блоки фиксированного размера B (32 или 64 токена). Для каждого такого блока вычисляется сжатый представитель (Representative Key) - компактная матрицы ключей, которая помещается в быструю память.
Когда модели нужно сгенерировать текущий токен, в дело вступает маршрутизатор. Он выполняет сканирование сжатых ключей, пока локальная и глобальная ветви готовят свои данные:
Вычисляется скалярное произведение запроса со всеми сжатыми ключами. Это дешевая операция, позволяющая грубо оценить релевантность каждого блока.
Top-K Selection: Алгоритм выбирает N блоков с наивысшими оценками. Например, из 1000 блоков могут быть выбраны только блоки №5 и №42, содержащие релевантную информацию для текущего контекста.
После того как мы нашли блоки с полезной информацией, система извлекает из глобальной памяти (HBM) полные, несжатые ключи и значения токенов из выбранного блока.
В этот момент происходит объединение данных:
Берем токены из Global Branch
Берем токены из Local Branch
Берем токены из Selected Branch
К этому объединенному набору данных применяется стандартный механизм Softmax-внимания. Модель одновременно читает системные промпты, ближайших соседей и найденные куски из прошлого, формируя итоговый взвешенный вектор контекста, где каждый источник информации конкурирует за внимание.
Недостатки:
Потеря информации при сжатии (Lossy Compression)
Сложность обучения роутера
Неэффективность на коротких контекстах
Другие алгоритмы внимания
Я постарался вкратце описать несколько более современных механизмов внимания, которые используются сегодня. Конечно, существуют и другие варианты реализации, но их я представлю в общей таблице без деталей реализации:
Механизм | Принцип работы | Вычислительная сложность | преимуще-ства | Недостатки |
|---|---|---|---|---|
FlashAttention (v1 / v2) | Точное вычисление внимания через тайлинг блоков в SRAM и пересчет промежуточных активаций в backward pass, минуя сохранение матрицы в HBM. | Time: O(N^2)$Space: O(N) | Ускорение обучения и инференса за счет минимизации обращений к медленной глобальной памяти видеокарты. Позволяет увеличить длину контекста без аппроксимации | Требует низкоуровневого программирования (CUDA/Triton) и ручного управления иерархией памяти GPU. Зависимость от архитектуры ускорителя |
Sparse Attention ( BigBird) | Использование разреженных графов связности. Матрица внимания вычисляется только для определенных индексов: локальное окно + глобальные токены + случайные связи (Random). | Time: O(N) Space: O(N) | Теоретически позволяет обрабатывать последовательности, превышающие возможности плотного внимания, сохраняя универсальную аппроксимацию (Turing Complete). | Современные GPU оптимизированы под плотные матричные операции. Нерегулярный доступ к памяти часто нивелирует теоретический выигрыш в FLOPs. |
Linear Attention | Аппроксимация Softmax ядрами Использует ассоциативность матричного умножения для смены порядка операций: (QKT)V→Q(KTV) | Time: O(N \ d^2) Space: O(N \ d + d^2) | Позволяет проводить инференс в режиме RNN (с фиксированным состоянием), что обеспечивает O(1) затрат на шаг генерации. | аппроксимация Softmax не гарантирует сохранения качества. Возможна численная нестабильность градиентов. |
Sliding Window Attention (SWA) | Наложение маски на матрицу внимания. Токенвидит только токены в диапазоне [i-w, i]. Глобальная связность достигается через композицию слоев | Time: O(N \ w) Space: O(N \ w) | Эффективен для задач с сильной локальной зависимостью | Прямая связь между удаленными токенами отсутствует. Для захвата глобального контекста требуется большая глубина сети, что увеличивает задержку |
Transformer-XL (Recurrence) | Обработка последовательности сегментами. Скрытые состояния предыдущего сегмента кэшируются (без градиентов) и используются как расширенный контекст для следующего сегмента. Вводит относительные позиционные кодировки. | Time: O(N \ L_seg) Space: O(N_{cache}) | Позволяет модели учитывать историю, превышающую длину обучающего окна. Устраняет проблему фрагментации контекста на границах сегментов. | Градиенты не протекают между сегментами, что ограничивает способность модели обучаться на зависимостях, превышающих длину одного сегмента. |
Заключение
Механизм Attention превратил трансформеры из простого переводчика текстов в универсальные модели, способные обрабатывать книги и кодовые базы целиком. Как мы выяснили, за этим стоит не магия, а математика и изобретательная инженерия.
От полного перебора мы пришли к методам, которые сжимают, квантуют, разреживают и маршрутизируют потоки данных, чтобы вместить миллионы токенов в ограниченную память видеокарты. Механизм внимания перестал быть статичной формулой. И, судя по появлению таких технологий, как Native Sparse Attention, мы находимся только в начале пути к созданию по-настоящему эффективной машинной памяти.
Мой Telegram
Спасибо за прочтение
