Линейное внимание и гибриды
Как убрать softmax и превратить внимание в RNN с памятью фиксированного размера — ядерный трюк, Lightning Attention MiniMax, Gated DeltaNet и gated attention в Qwen3-Next, KDA в Kimi Linear — и почему MiniMax в M2 вернулась к полному вниманию.
Зачем это нужно
Заголовок раздела «Зачем это нужно»Разреженное внимание из прошлого урока экономит вычисления, но не память: NSA и DSA хранят KV-кэш всех прошлых токенов и лишь читают его часть. Если бы все 48 слоёв модели вроде Qwen3-Next были обычным вниманием (2 KV-головы по 256), кэш одной последовательности в миллион токенов весил бы около 92 ГиБ в BF16.
Радикальная альтернатива — память фиксированного размера, как у RNN из урока 3.4.3. Мостом служит линейное вниманиелинейное вниманиеEN linear attention. При обучении оно параллельно, как трансформер. При генерации это RNN: на каждом шаге обновляет состояние постоянного размера, и стоимость токена не зависит от длины контекста.
У этой идеи драматичная история. В 2020 году она казалась курьёзом. В 2025-м сразу три китайские лаборатории построили на ней крупные открытые модели: MiniMax-01 (январь), Qwen3-Next (сентябрь), Kimi Linear (октябрь). А MiniMax в том же октябре отказалась от неё в M2 и подробно объяснила почему. В конце урока мы честно взвесим обе стороны.
Ядерный трюк: убираем softmax
Заголовок раздела «Ядерный трюк: убираем softmax»Причинное softmax-внимание для запроса :
Экспонента «склеивает» и так, что их не разделить: для каждого нового запроса надо заново пройти по всем ключам. Отсюда .
Теперь заменим сходство на скалярное произведение признаков , где — отображение признаковотображение признаков (feature map)EN feature mapФункция φ, которая переводит запрос или ключ в неотрицательные признаки, например φ(x) = elu(x) + 1. с неотрицательными значениями. Это ядерный трюкядерный трюкEN kernel trickЗамена функции сходства sim(q, k) скалярным произведением признаков φ(q)·φ(k). В линейном внимании это позволяет переставить скобки и получить O(n) вместо O(n²).. Тогда
Что произошло: число — скаляр, его можно переставить, и выносится из суммы. Сумма (матрица ) и (вектор) от запроса не зависят, их можно накапливать. Это тот же приём ассоциативности, что и поглощение матриц в MLA: . Первая скобка стоит , вторая — . При и это в 8000 раз меньше.
Katharopoulos и соавторы (2020) взяли : при и иначе. Пример: , . Softmax-сходство . Линейное: , , сходство . Другое число, но тоже положительное и тоже больше для похожих векторов. Статья называлась «Transformers are RNNs» — почему, станет ясно в следующем разделе.
Три формы одного вычисления
Заголовок раздела «Три формы одного вычисления»Одно и то же линейное внимание можно считать тремя способами, и выход будет одинаковым до ошибки округления.
1. Параллельная форма (обучение). Как обычное внимание, только без softmax: матрица , причинная маска, нормировка по строкам. Все позиции считаются сразу — удобно для GPU, но снова .
2. Рекуррентная форма (генерация). На шаге :
Вместо KV-кэша, растущего с каждым токеном, — состояние размера . Для головы 128 это чисел, хоть после десяти токенов, хоть после миллиона.
3. Чанковая формачанковая (блочно-параллельная) формаEN chunkwise parallel formПоследовательность режут на блоки — внутри блока внимание считается матрицами (как при обучении), между блоками передаётся рекуррентное состояние. Так устроены Lightning Attention и ядра fla. (обучение на длинных текстах). Последовательность режут на блоки по токенов. Для блока :
(плюс такая же нормировка). Внутри блока — маленькая матрица на тензорных ядрах, между блоками — состояние. Стоимость : линейная по длине, но с хорошей загрузкой GPU. Именно так устроены Lightning Attention и ядра библиотеки flash-linear-attention.
flowchart LR B1["Блок 1<br/>матрица C×C"] -->|"S₁"| B2["Блок 2<br/>матрица C×C<br/>+ Q₂·S₁"] B2 -->|"S₂"| B3["Блок 3<br/>матрица C×C<br/>+ Q₃·S₂"] B3 -->|"S₃"| B4["…"]
| Форма | Когда | Стоимость на токен | Память |
|---|---|---|---|
| Параллельная | обучение на коротких текстах | растёт с | матрица |
| Рекуррентная | генерация | постоянная, | состояние |
| Чанковая | обучение и prefill на длинных текстах | постоянная, | состояние + блок |
Память должна забывать: затухание и гейты
Заголовок раздела «Память должна забывать: затухание и гейты»В формуле выше каждый прошлый токен входит в с весом 1 навсегда. Память «засоряется»: через миллион токенов в ней каша из всего подряд. Лекарство — затухание: перед записью нового токена старое состояние умножают на . Здесь и дальше для краткости пишем вместо и опускаем знаменатель — почему его можно убрать, скажем чуть ниже.
Слева рекуррентная форма, справа параллельная: маска с затуханием по расстоянию. Так устроены RetNet (Microsoft Research Asia, 2023) и Lightning Attention. Затухание заодно кодирует порядок токенов: чем дальше токен, тем меньше его вес.
Следующий шаг — сделать затухание зависящим от входа: вместо константы . Тогда , и модель сама решает, когда забывать — например, на границе документов. Это гейт, как в LSTM; так работают GLA и Mamba2 (модель пространства состояниймодель пространства состояний (SSM)EN state space model (SSM)Семейство рекуррентных моделей с линейным обновлением состояния (S4, Mamba). Близкий родственник линейного внимания.).
Ещё одна деталь. Знаменатель из статьи 2020 года оказался источником нестабильности, поэтому в современных вариантах (TransNormer, Lightning, GLA, DeltaNet) его обычно убирают, а после внимания ставят нормализацию слоя.
Lightning Attention и MiniMax-01
Заголовок раздела «Lightning Attention и MiniMax-01»Lightning AttentionLightning AttentionEN Lightning AttentionЛинейное внимание с тайлингом — маскированная матрица внутри блока и состояние KᵀV между блоками. Основа MiniMax-01 (7 линейных слоёв на 1 softmax). — линейное внимание с затуханием в чанковой форме, реализованное по принципам FlashAttention: блоки грузятся в быструю память GPU, внутриблочная часть считается матрицами, межблочная — через состояние . Скорость обучения на токен почти не зависит от длины последовательности. (Сам FlashAttention подробно — в уроке 5.7.2.)
MiniMax-01 (январь 2025) стала первой открытой моделью такого масштаба на линейном внимании: 456B параметров, из них 45.9B активных (MoE), 80 слоёв. Чистое линейное внимание плохо ищет по контексту, поэтому архитектура — гибридное вниманиегибридное вниманиеEN hybrid attentionАрхитектура, где большинство слоёв — дешёвые (линейные или со скользящим окном), а каждый k-й слой — полное внимание.: после каждых 7 слоёв Lightning Attention идёт 1 слой обычного softmax-внимания. Модель обучали на контексте 1M токенов, на инференсе она принимает до 4M. MiniMax-M1 (июнь 2025) сохранила гибрид и стала рассуждающей моделью. По оценке авторов, при генерации 100K токенов она тратит около четверти FLOPs DeepSeek-R1.
Почему вообще нужен softmax-слой? Ответ — в ёмкости памяти.
Ёмкость памяти: что теряет фиксированное состояние
Заголовок раздела «Ёмкость памяти: что теряет фиксированное состояние»Матрица — это 1024 числа, но точно хранить она может не больше 32 пар «ключ → значение»: по одной на каждое из 32 ортогональных направлений ключа. Проверим, что происходит дальше. Запишем случайных пар в три вида памяти и попробуем по каждому ключу прочитать значение. Метрика — косинус между прочитанным и настоящим значением (1.0 — идеально).
import numpy as np
rng = np.random.default_rng(0)d = 32 # размер ключа и значения: память S — матрица 32×32
def cos_rows(a, b): return np.sum(a * b, axis=1) / np.linalg.norm(a, axis=1) / np.linalg.norm(b, axis=1)
def experiment(n): """Записываем n пар (ключ, значение), затем по каждому ключу пытаемся прочитать значение.""" K = rng.normal(size=(n, d)) K /= np.linalg.norm(K, axis=1, keepdims=True) # единичные ключи V = rng.normal(size=(n, d))
logits = 20 * K @ K.T # softmax-внимание: хранит ВСЕ пары (KV-кэш) w = np.exp(logits - logits.max(axis=1, keepdims=True)) soft = (w / w.sum(axis=1, keepdims=True)) @ V
S_lin, S_delta = np.zeros((d, d)), np.zeros((d, d)) # линейное внимание: память фиксированного размера for k, v in zip(K, V): S_lin += np.outer(v, k) # просто прибавляем v kᵀ S_delta += np.outer(v - S_delta @ k, k) # дельта-правило: пишем только поправку lin, delta = K @ S_lin.T, K @ S_delta.T # чтение: S k
return cos_rows(soft, V).mean(), cos_rows(lin, V).mean(), cos_rows(delta, V).mean(), cos_rows(delta, V)[-4:].mean()
print(f"{'пар':>4}{'softmax':>9}{'линейное':>10}{'дельта':>8}{'дельта, 4 последних':>21}")for n in [8, 32, 128, 512]: s, l, dl, last = experiment(n) print(f"{n:>4}{s:>9.3f}{l:>10.3f}{dl:>8.3f}{last:>21.3f}")Ожидаемый вывод:
пар softmax линейное дельта дельта, 4 последних 8 1.000 0.899 0.930 0.987 32 1.000 0.723 0.701 0.959 128 1.000 0.437 0.235 0.979 512 1.000 0.249 0.067 0.951Softmax-внимание помнит всё, потому что его «память» — весь KV-кэш, растущий с . Линейная память деградирует, как только пар становится сравнимо с : записи накладываются друг на друга. Это фундаментальный компромисс, и его измеряют задачами ассоциативного поиска вроде MQAR (multi-query associative recall). Отсюда гибриды: дешёвые линейные слои плюс немного полного внимания для точного поиска.
Последний столбец показывает другое свойство: дельта-правило держит свежие записи почти идеально (0.95–0.99), даже когда всё остальное забыто. Его память — не куча, куда всё сваливают, а тетрадь, в которой старое переписывают новым.
Дельта-правило и Gated DeltaNet
Заголовок раздела «Дельта-правило и Gated DeltaNet»дельта-правилодельта-правилоEN delta ruleОбновление памяти S ← S + β(v − S k)kᵀ — сначала читаем, что уже записано по ключу k, и записываем только поправку. Старое значение по тому же ключу стирается. пришло из классики нейросетей (правило Уидроу–Хоффа) и в линейное внимание попало через DeltaNet (Schlag и соавторы, 2021):
Что здесь написано: — то, что память сейчас выдаёт по ключу . Разность — ошибка. Записываем только её со «скоростью обучения» . При и единичном чтение по сразу после записи вернёт ровно . Пример: по ключу записали , потом . Обычное линейное внимание прочитает сумму , дельта-правило — .
Долго дельта-правило считалось только рекуррентно, то есть медленно. В 2024 году Ян Сунлинь (MIT) с соавторами нашли способ параллелить его по длине последовательности. Затем вышла Gated DeltaNetGated DeltaNet (GDN)EN Gated DeltaNetДельта-правило плюс зависящее от входа затухание состояния α_t. Основа Qwen3-Next и Qwen3.5 (три слоя GDN на один слой внимания). (NVIDIA и MIT, ICLR 2025), соединившая затухание и дельта-правило:
отвечает за быстрое забывание всего (новая тема), — за точную перезапись по одному ключу. Kimi Delta AttentionKimi Delta Attention (KDA)EN Kimi Delta Attention (KDA)Вариант Gated DeltaNet от Moonshot с поканальным (а не скалярным) затуханием. Kimi Linear чередует 3 слоя KDA с 1 слоем MLA. (KDA) от Moonshot идёт дальше: вместо скаляра — диагональная матрица , то есть каждый канал памяти забывает со своей скоростью.
Гибриды 2025 года: Qwen3-Next и Kimi Linear
Заголовок раздела «Гибриды 2025 года: Qwen3-Next и Kimi Linear»Qwen3-Next-80B-A3B (сентябрь 2025): 80B параметров, 3B активных, 48 слоёв по схеме , после каждого — MoE с 512 экспертами (10 маршрутизируемых и 1 общий). Нативный контекст 262 144 токена, с YaRN (урок 5.5.1) — около миллиона. По данным Qwen, базовая модель обходит Qwen3-32B-Base, потратив на обучение около 10% её стоимости, а на контекстах больше 32K даёт примерно в 10 раз большую пропускную способность. В феврале 2026 года та же схема 3:1 легла в основу флагмана Qwen3.5.
flowchart LR subgraph R["Повтор × 12"] direction LR G1["Gated DeltaNet<br/>+ MoE"] --> G2["Gated DeltaNet<br/>+ MoE"] --> G3["Gated DeltaNet<br/>+ MoE"] --> A["Gated attention<br/>16 Q / 2 KV голов<br/>+ MoE"] end
Что это даёт по памяти (по конфигурации из карточки модели): слой внимания с 2 KV-головами по 256 хранит 1024 числа на токен.
12 таких слоёв на контексте 262K — 6 ГиБ кэша в BF16 на последовательность; если бы все 48 слоёв были вниманием, было бы 24 ГиБ.
Состояния 36 слоёв Gated DeltaNet (32 головы по ) занимают 72 МиБ в FP32 — при любой длине.
Ещё одна деталь слоёв Gated DeltaNet (и KDA у Kimi): перед вычислением запросов, ключей и значений стоит короткая
причинная depthwise-свёртка по времени с ядром 4 (linear_conv_kernel_dim = 4 в конфигурации Qwen3-Next). Она дёшево
подмешивает к каждому токену три предыдущих; как устроены такие свёртки — в уроке 3.5.1.
Gated attentionвнимание с выходным гейтом (gated attention)EN gated attentionПосле softmax-внимания выход каждой головы поэлементно умножается на сигмоидный гейт σ(xW). Убирает attention sink; работа Qwen — лучшая статья NeurIPS 2025. — отдельная работа Qwen (arXiv:2505.06708), получившая награду за лучшую статью NeurIPS 2025. После обычного внимания выход каждой головы поэлементно умножают на сигмоидный гейт от входа: . Авторы сравнили более 30 вариантов на MoE-моделях 15B и плотных 1.7B, обученных на 3.5 трлн токенов. Гейт добавляет нелинейность между и , делает внимание разреженнее и убирает attention sink (урок 4.4.2, прошлый урок): голове больше не нужно сливать лишнюю массу на первый токен, она может просто «закрыть» свой выход. По данным статьи, в базовой модели на первый токен в среднем приходилось 46.7% внимания, с гейтом — 4.8%. Заодно обучение стало стабильнее и терпимее к большому learning rate.
Kimi Linear (октябрь 2025): 48B параметров, 3B активных, KDA и MLA в пропорции 3:1. Слои MLA работают без позиционного кодирования (NoPE): порядок токенов несут рекуррентные слои KDA. По данным Moonshot, на контексте 1M модель расходует на KV-кэш до 75% меньше памяти и декодирует до 6 раз быстрее, чем такая же модель на полном MLA, а в честном сравнении на одинаковых данных обходит её по качеству. Ядро KDA открыто в библиотеке fla. В 2026 году эта линия дошла до флагмана Kimi K3.
Честный разговор: почему MiniMax вернулась к полному вниманию
Заголовок раздела «Честный разговор: почему MiniMax вернулась к полному вниманию»В октябре 2025 года MiniMax выпустила M2 (230B-A10B) — и в ней полное внимание. Команда, которая первой масштабировала линейное внимание до 456B, отказалась от него. В блоге «Why Did M2 End Up as a Full Attention Model?» объяснено почему, а позже те же доводы вошли в техотчёт серии M2 (arXiv:2605.26494).
1. Качество там, где не смотрели. Во время работы над MiniMax-Text-01 гибрид Lightning и полного внимания выглядел не хуже чистого полного внимания на MMLU, BBH, MATH, LongBench (тесты на знания, рассуждения, школьную и олимпиадную математику и длинный контекст). Но на большем масштабе проявились явные провалы в сложных многошаговых рассуждениях — то есть ровно там, где нужен точный поиск по контексту из нашего эксперимента с памятью.
2. Оценка — узкое место. Стандартные бенчмарки насыщены и не ловят такие слабости. Команда построила прокси-метрики для многошагового поиска, но нельзя быть уверенным, что на ещё большем масштабе они продолжат предсказывать реальные задачи и что нет других скрытых слабостей. Проверить это — значит обучить большую модель целиком.
3. Инфраструктура не готова. Рекуррентные и сжатые состояния чувствительны к пониженной точности. У многих реализаций нет нормального prefix cachingпрефикс-кэш (кэширование префиксов)EN prefix cachingЕсли начало нового запроса совпадает с уже обработанным (системный промпт, история диалога), его KV-кэш берут готовым и prefill делают только для «хвоста». (повторного использования KV-кэша для общего начала разных запросов, урок 7.1.2) — а агенты для кода прогоняют сотни траекторий с одним и тем же репозиторием в контексте, и переиспользование кэша для них критично. Неясно, как сочетать такие слои со спекулятивным декодированием.
Вывод MiniMax: в промышленной системе эффективное внимание пока не обыгрывает полное однозначно, и выигрыш в скорости не окупил рисков для агентных задач. В июне 2026 года, в M3, MiniMax вернулась к экономии — но выбрала не линейное, а разреженное внимание MSA.
При этом Qwen и Moonshot в те же месяцы сделали ставку на гибриды и подтвердили её флагманами Qwen3.5 и Kimi K3. Обе стороны опираются на эксперименты, и спор не закрыт. Что из него стоит вынести инженеру:
| Полное (с GQA или MLA) | Разреженное (NSA, DSA) | Гибрид с линейным | |
|---|---|---|---|
| Точный поиск по контексту | лучший | почти как у полного, если отбор хороший | только в полных слоях |
| KV-кэш | растёт с | растёт с (сжатие помогает) | растёт только в полных слоях (в 4–8 раз медленнее) |
| Вычисления на токен | растут с | растут медленно: дорогое внимание — по ключам, дешёвый отбор — по всем | растут только в полных слоях |
| Зрелость инфраструктуры | высокая | средняя | ниже |
- Бенчмарки молчат о том, чего не измеряют. Проверяйте архитектуру на целевой задаче: агенты, многошаговый поиск, длинный код.
- Инфраструктура — часть архитектуры. Prefix caching, квантование и спекулятивное декодирование решают не меньше, чем loss.
- Гибриды держат полные слои не случайно: именно они отвечают за точное «вспоминание».
Практика
Заголовок раздела «Практика»Задание в браузере
Заголовок раздела «Задание в браузере»Реализуйте линейное внимание тремя способами — параллельно, рекуррентно и блоками, как Lightning Attention, — и убедитесь, что выходы совпадают, а состояние не растёт с длиной.
import numpy as np
def feature_map(x):
"""Отображение признаков линейного внимания: phi(x) = elu(x) + 1.
Поэлементно: x + 1 при x > 0 и exp(x) при x <= 0. Результат всегда положителен,
поэтому «веса» phi(q)·phi(k) тоже положительны — как у softmax.
"""
# TODO: np.where(условие, если_да, если_нет)
...
def linear_attention_parallel(Q, K, V):
"""Причинное линейное внимание в параллельной форме (как при обучении).
Q, K: (T, d_k), V: (T, d_v).
A = phi(Q) @ phi(K).T, обнулить будущее (выше диагонали);
выход строки i: (A[i] @ V) / A[i].sum().
"""
# TODO
...
def linear_attention_recurrent(Q, K, V):
"""Та же функция в рекуррентной форме (как RNN при генерации).
Состояние: S (d_v, d_k) = сумма outer(v_j, phi(k_j)) и z (d_k,) = сумма phi(k_j) по j <= t.
На шаге t: сначала обновить S и z токеном t, затем
out[t] = S @ phi(q_t) / (z @ phi(q_t)).
Возвращает (out, S, z) — выход (T, d_v) и состояние после последнего токена.
"""
T, d_k = Q.shape
d_v = V.shape[1]
S = np.zeros((d_v, d_k))
z = np.zeros(d_k)
out = np.zeros((T, d_v))
# TODO: цикл по t
...
return out, S, z
def linear_attention_chunked(Q, K, V, C):
"""Чанковая форма (идея Lightning Attention): блоки по C токенов.
Внутри блока — параллельная форма с причинной маской,
между блоками — состояние S, z от всех предыдущих блоков.
Для блока rows = [a, b):
A_in = tril(phi(Q_b) @ phi(K_b).T)
числитель = A_in @ V_b + phi(Q_b) @ S.T
знаменатель = A_in.sum(1) + phi(Q_b) @ z
После блока: S += V_b.T @ phi(K_b), z += phi(K_b).sum(0).
Последний блок может быть короче C.
"""
# TODO
...
# Проверьте себя кнопкой «Запустить»:
rng = np.random.default_rng(0)
Q, K, V = rng.normal(size=(3, 6, 4))
print(feature_map(np.array([-1.0, 0.0, 2.0]))) # ожидается [0.3679 1. 3.]
p = linear_attention_parallel(Q, K, V)
r, S, z = linear_attention_recurrent(Q, K, V)
c = linear_attention_chunked(Q, K, V, 4)
if p is not None and c is not None:
print("параллельная = рекуррентная:", np.allclose(p, r))
print("параллельная = чанковая:", np.allclose(p, c))
print("размер состояния:", S.shape) # (4, 4) — не зависит от длины
Затухание: рекуррентная и параллельная формы
Заголовок раздела «Затухание: рекуррентная и параллельная формы»Проверим формулу с из раздела о затухании: RNN-шаги и одна матричная операция с маской дают одно и то же.
import numpy as np
rng = np.random.default_rng(0)T, d = 6, 4Q, K, V = rng.normal(size=(3, T, d)) # массив (3, T, d) распаковывается по первой оси в три матрицы (T, d)gamma = 0.9 # затухание: старое «забывается» на 10% за шаг
# Рекуррентная форма: S_t = γ·S_{t-1} + v_t k_tᵀ, o_t = S_t q_tS = np.zeros((d, d))out_rec = np.zeros((T, d))for t in range(T): S = gamma * S + np.outer(V[t], K[t]) out_rec[t] = S @ Q[t]
# Параллельная форма: O = ((Q Kᵀ) ⊙ D) V, D[i, j] = γ^(i-j) при j <= i, иначе 0i, j = np.arange(T)[:, None], np.arange(T)[None, :]D = np.where(j <= i, gamma ** (i - j), 0.0)out_par = ((Q @ K.T) * D) @ V
print("макс. расхождение форм:", f"{np.abs(out_rec - out_par).max():.1e}")print("маска затухания D:")print(D.round(2))Ожидаемый вывод:
макс. расхождение форм: 1.1e-15маска затухания D:[[1. 0. 0. 0. 0. 0. ] [0.9 1. 0. 0. 0. 0. ] [0.81 0.9 1. 0. 0. 0. ] [0.73 0.81 0.9 1. 0. 0. ] [0.66 0.73 0.81 0.9 1. 0. ] [0.59 0.66 0.73 0.81 0.9 1. ]]Маска — та же причинная маска, только вместо единиц ниже диагонали стоят степени . С гейтом вместо константы в клетках будут произведения , а с дельта-правилом формула усложняется, но идея «матрица при обучении, рекурсия при генерации» сохраняется.
Проверь себя
Заголовок раздела «Проверь себя»- Ядерный трюк позволяет переставить скобки: , и внимание становится линейным по длине.
- Одно вычисление — три формы: параллельная для обучения, рекуррентная для генерации (состояние вместо KV-кэша) и чанковая (Lightning Attention) для длинных текстов.
- Фиксированная память ограничена по ёмкости. Затухание и гейты учат её забывать, дельта-правило — перезаписывать; Gated DeltaNet и KDA объединяют оба приёма.
- Гибриды 3:1 (Qwen3-Next, Kimi Linear) и 7:1 (MiniMax-01) держат немного полного внимания для точного поиска. MiniMax в M2 вернулась к полному вниманию из-за многошаговых рассуждений, сложности оценки и незрелой инфраструктуры — спор не закрыт.
Дальше: модуль 5.6 — соберём всё, что знаем об архитектуре и обучении, в собственную маленькую LLM по мотивам MiniMind.