FlashAttention
KV cache убрал повторные проекции во время генерации. Но при обработке целого промпта и во время обучения точный attention по-прежнему работает с большой матрицей оценок. FlashAttention вычисляет то же самое внимание, не сохраняя матрицу
Главное ограничение — обмен с памятью
Внимание для последовательности длины
У GPU есть небольшая быстрая память рядом с вычислителями (SRAM) и большая, но более медленная память (HBM). Прямая реализация многократно переносит матрицу
Увеличьте длину с 2K до 8K — в четыре раза. Во сколько раз должны вырасти число оценок и память матрицы? Проверьте ответ, затем повторите рассуждение для следующего доступного размера.
Вычисление softmax по блокам
Обычный softmax вычитает максимум (ради устойчивости) и делит на сумму экспонент — для этого будто бы нужно увидеть всю строку. Но максимум и сумму можно накапливать потоково, блок за блоком. Достаточно поддерживать два скаляра и один вектор:
— бегущий максимум уже просмотренных оценок; — бегущая сумма ; — бегущий выход (ненормированный).
Если в новом блоке находится более высокий максимум, прежние накопления нужно привести к новому масштабу. Для этого достаточно одного множителя
Итоговый результат равен
Блочная обработка
Разбиение на тайлы (tiling) позволяет одному запросу получать ключи блоками. При увеличении текущего максимума множитель
При исходном размере тайла обработайте все четыре блока по одному. Перед каждым шагом решите, потребуется ли пересчитать старые вклады из-за нового максимума. Затем выберите другой размер тайла и проверьте, изменился ли итоговый результат.
Обратите внимание: в этой демонстрации в памяти в каждый момент находятся лишь текущий тайл, два скаляра и вектор-аккумулятор — но не матрица
Путь данных
Теперь сопоставим два пути. Прямая реализация сохраняет квадратную матрицу в HBM, затем читает её для softmax и следующего умножения. FlashAttention переносит в SRAM только текущие блоки
Пройдите обработку от первого до последнего тайла. Перед каждым шагом различайте уже выполненную работу и данные, которые ещё нужно хранить. Должны ли эти два числа расти одинаково?
В этом и состоит экономия: прямая реализация переносит через HBM всю квадратную матрицу
Как менялось следующее ограничение
Базовая математика внимания оставалась прежней. Каждое поколение устраняло одно ограничение и тем самым делало заметнее следующее: сначала обмен с HBM, затем загрузку GPU, наконец — паузы между softmax и матричными умножениями.
Сравните версии по порядку, начиная с FA-1. Для каждого перехода назовите устранённое ограничение и оставшуюся проблему. Проверьте также оборудование: можно ли напрямую сравнивать показанные ускорения между разными устройствами?
FA-1 (2022) — основа алгоритма
Первая версия объединяет всё, что мы разобрали в главах 1–4: тайлы, потоковый softmax и пересчёт весов на обратном проходе. Она убрала матрицу
FA-2 (2023) — больше параллелизма, меньше операций
Все три приёма направлены на то, чтобы GPU меньше простаивал. Матричные умножения выполняют Tensor Cores, а экспоненты, деления и поиск максимума выполняются на обычных CUDA-ядрах. Чем меньше вспомогательной работы и ожидания, тем полнее используется быстрая матричная часть GPU.
- Отложить деление. FA-1 нормировал выход — делил на бегущую сумму
— на каждом блоке. В FA-2 деление выполняется один раз в самом конце. Стоимость одного деления невелика, но во внутреннем цикле эта операция повторяется множество раз. Её перенос сокращает долю вспомогательных вычислений. - Больше параллельных блоков. FA-1 распределял работу по парам «последовательность × голова». Но при длинном контексте последовательностей мало, а сами они велики — независимых задач не хватает, чтобы загрузить все вычислительные блоки. FA-2 дополнительно делит работу по длине запроса: даже одна длинная последовательность даёт достаточно независимых задач для загрузки GPU.
- Каждому варпу — свои строки. Внутри блока работу делят варпы (группы потоков). FA-1 делил между ними ключи
: варпы вычисляли части одного результата, а затем объединяли их через разделяемую память и ждали друг друга. FA-2 делит запросы — теперь у каждого варпа свои строки выхода целиком, поэтому им реже приходится объединять результаты и ждать друг друга.
FA-3 (2024) — совмещение этапов вычисления
Даже в FA-2 шаги идут по очереди: матричное умножение (Tensor Cores) → softmax (обычные ядра) → следующее матричное умножение. Пока обычные ядра считают экспоненты одного тайла, Tensor Cores ждут — и наоборот. На GPU архитектуры Hopper FA-3 выполняет эти этапы внахлёст: пока для тайла
Второе нововведение — FP8, восьмибитный числовой формат. Такие матричные умножения быстрее, но менее точны, поэтому значения квантуют поблочно, контролируя численную ошибку. В этом режиме производительность на H100 достигает примерно 1,2 PFLOPS — уже с компромиссом между скоростью и численной точностью.
Эта идея получила дальнейшее развитие. Например, Flash-Decoding ускоряет генерацию с большим контекстом, распараллеливая работу по ключам. Основной принцип остаётся прежним: не хранить то, что можно пересчитать, и сокращать простои GPU.