Математика глубокого обучения
KV-кэш
Почему генерация ограничена памятью, а не арифметикой — и что из этого следует
Наблюдение, из которого всё следует
При авторегрессионной генерации на шаге ключи и значения для позиций уже были посчитаны на предыдущих шагах, и они не изменились: зависит только от токена . Значит их можно сохранить.
Экономия арифметики огромна: без
Но за это платится памятью, и вот тут начинается интересное.
Сколько это байт
Двойка — за и ; — число слоёв; — суммарная ширина ключей. Ни одного множителя лишнего, и весь смысл в том, что входит линейно, а веса от не зависят вовсе.
Отсюда точка пересечения. Веса стека — примерно (четыре квадратные проекции внимания плюс MLP с четырёхкратным расширением), значит
Потяните длину контекста и посмотрите, когда синяя полоса перерастает серую.
- кэш
- 2048 МБ
- веса
- 12288 МБ
- сравняются при
- 24576 токенов
- выигрыш против пересчёта
- ×1366
- операций на элемент кэша
- 1 на элемент
Каждая голова со своими ключами: операций на прочитанный элемент кэша ровно одна. Это худший возможный случай для арифметической плотности.
Числа для , , fp16:
| конфигурация | на токен | при | сравняются с весами |
|---|---|---|---|
| MHA, | МБ | ГБ | |
| GQA, | МБ | ГБ | |
| MQA, | МБ | ГБ |
Полмегабайта за токен — это та цифра, из-за которой длинный контекст дорог не по вычислениям, а по памяти на каждого пользователя одновременно.
Главное число: одна операция на элемент
Теперь самое важное, и это не про размер. Посчитаем арифметическую плотность внимания на шаге декодирования: сколько умножений-сложений приходится на один прочитанный из памяти элемент.
За шаг внимание в одном слое читает чисел кэша и делает умножений-сложений. Отношение:
При обычном MHA () это ровно единица. Одно умножение на одно прочитанное число.
Почему это приговор: у современных ускорителей отношение арифметической производительности к пропускной способности памяти — порядка ста–трёхсот операций на прочитанный байт. Значит при плотности вычислитель простаивает, ожидая память, и ускорение арифметики не даёт ничего. Генерация ограничена чтением кэша.
| схема | плотность | |
|---|---|---|
| MHA | ||
| GQA, | ||
| GQA, | ||
| MQA | при |
Вот и полное объяснение, зачем существуют MQA и GQA — то, что урок 080 оставил без причины. Они не ускоряют арифметику (её столько же) и не уменьшают число голов (запросов по-прежнему ). Они делят объём читаемой памяти на , и ровно во столько же раз поднимают плотность. Экономия кэша — приятное следствие, а причина в плотности.
Почему обучение — другая задача
Стоит сопоставить, потому что интуиция из обучения здесь подводит.
| обучение | генерация | |
|---|---|---|
| токенов за проход | тысячи | один |
| матричные операции | матрица × матрица | матрица × вектор |
| арифметическая плотность | высокая | около единицы |
| что ограничивает | арифметика | память |
| помогает FlashAttention | да | мало |
| помогает GQA | почти нет | сильно |
Причина в третьей строке. При обучении веса читаются один раз на весь батч, поэтому их чтение амортизируется; при генерации токен один, и каждое число весов и кэша используется ровно один раз. Умножение матрицы на вектор ограничено памятью по природе, а не из-за реализации.
Отсюда и батчирование запросов при сервинге: несколько независимых последовательностей одновременно возвращают плотность, потому что веса читаются один раз на всю группу. Кэш при этом не амортизируется — он у каждой последовательности свой, и именно он ограничивает, сколько запросов уместится.
Что ещё делают с кэшем
| приём | идея | цена |
|---|---|---|
| GQA / MQA | группы вместо голов | немного выразительности |
| квантование кэша в int8 | вдвое меньше байт | небольшая потеря качества |
| скользящее окно | хранить только последние | забывание дальше |
| PagedAttention | страницы вместо непрерывных буферов | сложность реализации |
| выгрузка в CPU | память дешевле | пропускная способность падает |
Первые четыре снижают либо объём чтения, либо фрагментацию — то есть работают против того же узкого места. Последний пункт — противоположная ставка, и она проигрывает как раз потому, что проблема в пропускной способности, а не в ёмкости.
Итог
- Кэш превращает пересчётов на шаг в — выигрыш растёт линейно по контексту.
- Он растёт линейно по , а веса не растут вовсе, поэтому существует вычислимая точка , где кэш становится больше модели.
- Арифметическая плотность внимания при декодировании равна , то есть единице для MHA. Это и есть причина существования GQA и MQA.
- Генерация ограничена памятью, обучение — арифметикой. Приёмы для одного почти не помогают другому.
Источники
- Shazeer — Fast Transformer Decoding, One Write-Head is All You Need — MQA и разбор того, почему декодирование ограничено памятью
- Ainslie и др. — GQA, Training Generalized Multi-Query Transformer Models — Компромисс между MHA и MQA
- Kwon и др. — Efficient Memory Management for LLM Serving with PagedAttention — Что с кэшем делают в реальном сервинге
Проверки
0 из 2Что ограничивает генерацию
Отметьте все верные утверждения о KV-кэше и декодировании.
Бюджет кэша и плотность
Реализуйте
cache_facts(layers, heads, head_dim, groups, context, bytes_per_value). Обозначим (ширина модели) и (суммарная ширина ключей). Верните[cache_megabytes, crossover, intensity, speedup]:cache_megabytes= , делённое на . Двойка — за ключи и значения;crossover= — длина контекста, при которой кэш сравняется с весами стека, оценёнными как элементов;intensity= — умножений-сложений на один прочитанный из кэша элемент;speedup— отношение работы без кэша к работе с кэшем на один новый токен в одном слое:
Числитель отличается от знаменателя одним множителем во втором слагаемом: без кэша ключи и значения пересчитываются для всех позиций, с кэшем — для одной.
Проверить себя можно так:
intensityобязана равняться , и при это ровно единица.Загрузка редактора…
Ctrl/⌘ + Enter