Ли Хунъи, «Машинное обучение» 2026: FlashAttention, KV-кэш и позиционные эмбеддинги
Конспект «внутримодельного» блока курса «Машинное обучение» 2026 профессора Ли Хунъи: почему генерация медленная и как её ускоряют (FlashAttention с «исправляющим множителем», KV-кэш, MQA/GQA/MLA, скользящее окно, attention sink, обрезка кэша, кэширование префикса и цена токенов) и как Transformer узнаёт порядок токенов (синусоиды, ALiBi, RoPE, расширение контекста, NoPE).
Коротко
Заголовок раздела «Коротко»- Спрашивайте о цене ускорения. Ли предлагает рамку на две лекции: меняет ли метод результат внимания (точный или приближённый)? Требует ли он обучать/переделывать модель? И что ещё за него платим?
- FlashAttention точен, подходит к любой модели и почти бесплатен: GPU считает быстро, а медленно перевозит данные между большой памятью (HBM) и крошечной быстрой (SRAM). Если разрешить себе «временно неверный» softmax и потом домножать на поправку, внимание считается за один проход без записи матрицы весов.
- KV-кэш избавляет от пересчёта, но съедает память: в примере с архитектурой Gemma 2 27B — около 0,72 МБ на токен, на A100 80 ГБ влезает лишь ~114 тыс. токенов. Отсюда GQA, MLA, скользящее окно, attention sink, обрезка кэша.
- Кэширование префикса — это деньги: кэшированные входные токены в API в несколько раз дешевле, поэтому всё неизменное в промпте агента ставят в начало, а изменяющееся (дату!) — в конец.
- Позиции: важны относительные расстояния; RoPE кладёт их во вращение и . Контекст расширяют, «сжимая» углы низкочастотных пар (PI, NTK, YaRN, LongRoPE), а работы NoPE и DroPE показывают, что каузальная модель может обходиться без позиционного кодирования.
Как смотреть вместе с курсом
Заголовок раздела «Как смотреть вместе с курсом»| Лекция | Когда смотреть | Наш урок |
|---|---|---|
| FlashAttention | вместе с уроком 5.7.2 (перед ним — 5.7.1 про устройство GPU) | 5.7.1, 5.7.2 |
| KV-кэш | после 4.6.4, вместе с модулем 5.2 и уроком 7.1.2 | 4.6.4, 5.2.1, 5.2.2, 7.1.2 |
| Позиции | после 4.5.2, вместе с 5.5.1 | 4.5.2, 5.5.1, 5.5.2 |
Курс предполагает, что вы уже понимаете устройство Transformer (лекция «Анатомия большой языковой модели» из курса осени 2025 года). Спекулятивное декодирование в этом году не читали — Ли отсылает к лекции 16 курса 2024 года (наш конспект).
Лекция «Ускоряем генерацию, часть 1: FlashAttention»
Заголовок раздела «Лекция «Ускоряем генерацию, часть 1: FlashAttention»»Видео на YouTube · слайды inference.pdf, с. 1–28, учебный Colab.
Префилл и декодирование. Подсказка проходит модель целиком за один раз (prefill), затем токены порождаются по одному (decode). Для токена 4 внимание — это , softmax до , взвешенная сумма .
Метафора Ли. Вычислительные блоки GPU — толпа «многоруких эльфов», считают мгновенно; верстак — встроенная SRAM, крошечная (обычно с десяток мегабайт); склад — HBM (на A100 — 80 ГБ). Всё, что считается, надо перенести со склада на верстак и обратно, и именно перевозка — узкое место. Главное ограничение: на верстак нельзя класть ничего, что растёт с длиной последовательности (в агентах — сотни тысяч), поэтому ключи режут на блоки по .
Наивный softmax — много походов на склад. Чтобы вычесть максимум (ради численной устойчивости), надо сначала просмотреть все ; потом посчитать экспоненты; потом знаменатель; потом нормировать; потом взвешенную сумму — каждый шаг заново читает все блоки.
Трюк: «сначала ошибёмся, потом поправим». Примем максимум первого блока за глобальный и посчитаем частичную сумму . Встретив в следующем блоке больший максимум , не перечитываем старое — домножаем:
Ли называет это «всего лишь одной формулой» — но весь FlashAttention повторяет её снова и снова. Следующий вопрос лектора: обязательно ли сначала вычислить веса внимания, чтобы взять взвешенную сумму? Нет: читаем вместе с ключами и значения и сразу копим выход с «неправильными» весами, а затем поправляем и его:
После последнего блока в точности равен обычному , а матрица весов так и не была записана в память. Побочный эффект: в Hugging Face с FlashAttention нельзя достать веса внимания для анализа — их просто нет.
Что показывает Colab курса. PyTorch scaled_dot_product_attention с бэкендами MATH и FLASH_ATTENTION:
результаты совпадают до ~; на A100 при длине 4096 FlashAttention примерно в 9 раз быстрее (в сохранённом выводе
9,49×). На реальной модели (google/gemma-3-4b-it, eager против sdpa) на коротких входах разницы почти нет — много
времени уходит на полносвязные слои, — а на длинных выигрыш заметен; если удлинить вход ещё в 10 раз — CUDA out of
memory. Переполнился склад, а не верстак: почему — следующая лекция. Обычно PyTorch и так выбирает FlashAttention
по умолчанию, то есть вы им, скорее всего, уже пользуетесь.
Лекция «Ускоряем генерацию, часть 2: KV-кэш»
Заголовок раздела «Лекция «Ускоряем генерацию, часть 2: KV-кэш»»Видео на YouTube · слайды inference.pdf, с. 29–55.
Что такое KV-кэш. После префилла запросы выбрасывают, а ключи и значения хранят: для нового токена нужны только его собственные и старые . Ли шутит, что cache и cash звучат одинаково — и в конце лекции окажется, что кэш действительно про деньги.
Почему кэш переполняет склад. Ключи и значения хранятся для каждого токена, каждой головы и каждого слоя. Пример по архитектуре Gemma 2 27B (если считать, что у всех 32 голов свои ):
то есть на 80 ГБ — около 114 тысяч токенов, а агентам нужно больше. Способы «уместить больше»:
- Делить между запросами. MQA — одна пара на все головы (по словам Ли, качество страдает); GQA — пара на группу голов (Llama, Gemma). Почему делят именно , а не ? Потому что хранятся только .
- Сжимать — MLA (DeepSeek-V2): хранить маленький латентный вектор , а ключи и значения получать умножением на матрицы. Распаковывать не нужно: — достаточно один раз преобразовать запрос, а взвешенную сумму считать в сжатом пространстве и распаковать один раз в конце. Это точное тождество, не приближение; модель нужно обучать так с самого начала.
- Ограничить область внимания. Скользящее окно (например, 4096 токенов) даёт кэшу фиксированный предел, а многослойность позволяет всё равно «видеть» далеко (в GPT-OSS слои с окном чередуются с полными). StreamingLLM: чистое окно ломается на длинных входах, но если всегда держать в окне первые несколько токенов, качество возвращается — веса внимания обязаны суммироваться в 1, и «когда смотреть не на что», модель по привычке смотрит на первый токен (attention sink).
- Выбрасывать неиспользуемые (Scissorhands, H2O): внимание сосредоточено на немногих токенах; то, на что долго никто не смотрит, удаляют. Scissorhands сжимал кэш в 5 раз почти без потерь, но на трудных задачах такая обрезка вредит — тема ещё исследуется.
Кэш между диалогами — это деньги. «Всем привет, я Дацзинь» и «Всем привет, я Сяоцзинь» совпадают в первых пяти токенах — их можно переиспользовать. А дальше даже одинаковый иероглиф «цзинь» уже не совпадёт: представление токена зависит от всего, что было до него. Переиспользуется только точно совпадающий префикс. Поэтому провайдеры делают скидку: в прайсе OpenAI на слайде кэшированный ввод в 10 раз дешевле обычного. Для агентов это золото: в системном промпте всё неизменное (инструменты, правила, личность, список навыков) ставьте в начало, изменчивое (например, текущую дату) — в конец; и шаблоны пишите так, чтобы переменные шли последними («забронируй рейс из x в y» + значения x, y в конце). В работе «Don’t Break the Cache» на долгих агентных задачах кэширование снизило стоимость на 46–80 % в зависимости от модели.
Итоговая таблица Ли:
| Метод | Что делает | Меняет внимание? | Нужно обучать? | Другая цена |
|---|---|---|---|---|
| FlashAttention | меньше перевозок данных | нет | нет | чуть больше вычислений, сложнее понять |
| KV-кэш | хранить посчитанные | нет | нет | память |
| MQA | общие для всех запросов | да | да | может заметно ухудшить модель |
| GQA | общие по группам | да | да | |
| MLA | сжатые | да | да | |
| Скользящее окно | ограничить область внимания | да | по-разному | |
| StreamingLLM | окно + первые токены | да | по-разному | |
| Обрезка KV-кэша | выбросить | да | нет | может заметно ухудшить модель |
| Спекулятивное декодирование | маленькая модель «предсказывает» | нет (теоретически) | нет | лишние вычисления малой модели |
Лекция «Как Transformer узнаёт порядок токенов: абсолютные, относительные, RoPE и вовсе без позиций»
Заголовок раздела «Лекция «Как Transformer узнаёт порядок токенов: абсолютные, относительные, RoPE и вовсе без позиций»»Видео на YouTube · слайды pos.pdf (64 слайда), Colab.
Зачем позиции. Для токена D внимание к «A B C» и к «C B A» даёт одну и ту же взвешенную сумму, а «ты бьёшь меня» и «я бью тебя» — противоположности.
1. Абсолютные позиции. К вектору токена прибавляется вектор позиции. Синусоидальный вариант «Attention Is All You Need» — набор часовых стрелок: высокочастотные пары координат — «секундные», различают соседей; низкочастотные — «часовые», различают далёкие позиции (на слайде: полный оборот за 6,3, 628,3 и 54 410,1 позиции у разных пар). Но важно относительное расстояние: связь «кошка — рыба» не должна зависеть от того, стоит фраза в начале или на 101-й позиции. Если разложить , в синусоидальном варианте не видно, чтобы «позиционный» член зависел только от .
2. Относительные позиции. ALiBi: из оценки внимания вычитается — «чем дальше, тем меньше внимания, вот и всё»; задают вручную, разным головам разный. T5: добавка к оценке зависит от расстояния и обучается.
3. RoPE (Llama, Qwen, Gemma). Каждую пару координат и поворачиваем на угол , где — позиция; в скалярное произведение входит только разность углов , поэтому «кошка» и «рыба» на позициях 1 и 3 дают ту же оценку, что на 101 и 103. Не меняет вычисление внимания и совместим с KV-кэшем. Углы — та же «секундная/часовая» структура, только вместо сложения — вращение. Распространённое мнение «RoPE ослабляет внимание с расстоянием» Ли опровергает со ссылкой на «Round and Round We Go!»: затухания нет, и это не обязательно плохо — запрос можно обучить смотреть ровно на два токена назад.
4. Обучение на коротком — применение к длинному. На обучении ключ поворачивался максимум на , на длинном тесте — на : модель никогда не видела таких направлений. Исправления:
| Метод | Идея | Цена |
|---|---|---|
| Position Interpolation | сжать все позиции в обучающий диапазон (1, 2, 3, 4 → 0,5, 1, 1,5, 2) | нужно дообучение |
| частотный подход | быстрые пары и так «прокрутились» много раз — их не трогаем; сжимаем только медленные | решить, какие пары «быстрые» |
| NTK-aware scaling | плавно: самая быстрая пара не меняется, самая медленная сжимается в раз | идея из поста на Reddit, не из статьи |
| YaRN | «статейная» версия частотного подхода | |
| динамическое масштабирование | сжимать только когда вход длиннее обучающего (иначе короткие тексты портятся) | коэффициент зависит от длины |
| LongRoPE | частотный + динамический подход, коэффициенты ищутся эволюционным поиском | нужен поиск |
5. А нужны ли позиции вообще? В каузальном декодере каждый токен видит только себя и прошлое: в «кошка ест рыбу?» и «рыба ест кошку?» последний токен видит одно и то же множество слов, но промежуточные токены видят разные префиксы, и через слои порядок всё равно «просачивается». NoPE: без позиционного кодирования длина обобщается лучше, чем с APE, T5, ALiBi или RoPE (на их задачах рассуждения и математики). DroPE: позиции помогают сойтись при предобучении, но мешают обобщаться на длину; после обучения их можно убрать и после короткой перекалибровки удлинять контекст без обучения.
Что сделать руками
Заголовок раздела «Что сделать руками»- Онлайн-softmax. Напишите на NumPy функцию, которая считает softmax-взвешенную сумму блоками по формулам
выше, за один проход, и сравните с обычным расчётом на случайных данных (ошибка должна быть около в
float64). - Калькулятор KV-кэша. Посчитайте байты на токен и максимальный контекст на одной RTX 4080 32 ГБ для Qwen3-8B
и Qwen3-32B (возьмите числа слоёв и KV-голов из их
config.json; вычтите память под веса). - Кэш префикса. Отправьте в vLLM с
--enable-prefix-caching20 запросов с одинаковым длинным системным промптом — сначала с датой в начале промпта, потом в конце; сравните время до первого токена.
Словарик
Заголовок раздела «Словарик»| English | Русский | Пояснение |
|---|---|---|
| Prefill / Decode | префилл / декодирование | обработка подсказки / генерация по токену |
| HBM / SRAM | «склад» / «верстак» | большая медленная / маленькая быстрая память GPU |
| online softmax | онлайн-softmax | поблочный softmax с поправками |
| KV Cache | KV-кэш | сохранённые ключи и значения |
| MQA / GQA / MLA | общие / групповые / латентные | способы уменьшить кэш |
| Sliding Window | скользящее окно | внимание только на последние токенов |
| attention sink | «сток внимания» | первые токены, куда уходит «лишнее» внимание |
| prompt caching | кэширование префикса | переиспользование между запросами |
| Positional Embedding | позиционный эмбеддинг | |
| RoPE | вращательное позиционное кодирование | |
| Train short, test long | «учим на коротком — применяем к длинному» | расширение контекста |
| NoPE | без позиционного кодирования | No Positional Embedding |