DFlash: обещали 15×, мы намерили 2,3×

DFlash: обещали 15×, мы намерили 2,3×

5 февраля вышла статья про DFlash — способ ускорить генерацию у любой языковой модели, не трогая саму модель, — а следом NVIDIA выложила бенчмарки с заголовком «до 15× выше пропускная способность», и это тот тип цифры, ради которой стоит остановить всё и проверить. Мы взяли драфтер DFlash для Gemma 4 26B — он лежит в том же репозитории, откуда качается сама модель, — прогнали на своём стенде и получили 2,31× — ровно до тех пор, пока на карте висит один запрос. После четырёх одновременных запросов DFlash начинает проигрывать обычной модели по суммарной пропускной способности, на восьми — в полтора раза.

Ниже — как это устроено, откуда берутся 15×, и почему наш график расходится с вендорским не потому, что кто-то соврал.

Как DFlash ускоряет генерацию

Обычная модель декодирует авторегрессивно: один проход по всем весам — один токен. На батче из одного запроса это чудовищно неэффективно. Чтобы посчитать единственный токен, GPU обязан прокачать через себя все веса модели, и упирается он не в вычисления, а в пропускную способность памяти. Арифметические блоки при этом почти простаивают.

Спекулятивное декодирование продаёт этот простой. Маленькая «черновая» модель быстро предлагает сразу несколько следующих токенов, а большая проверяет весь блок за один проход — тот самый проход, который она всё равно собиралась сделать ради одного токена. Совпавший префикс принимается целиком, первое расхождение обрубает хвост. Схема математически честная: распределение выхода не меняется, ускорение бесплатное по качеству.

Узкое место тут — сам черновик. Классические методы вроде EAGLE-3 генерируют его тоже авторегрессивно, то есть последовательно: пять черновых токенов — пять маленьких проходов. Идея DFlash в том, чтобы заменить черновую модель блочной диффузионной: она выдаёт весь блок за один проход, параллельно, а не по одному токену. Плюс её кормят внутренними признаками из большой модели, чтобы черновик лучше попадал в контекст. В статье авторы заявляют шестикратное ускорение без потери качества и до 2,5× преимущества над EAGLE-3.

Схематичная иллюстрация: большая стеклянная сфера проверяет ряд стеклянных кубов, последние из них рассыпаются в пыль

В нашем случае черновик — это отдельный диффузионный модуль на 451 МБ, пристёгнутый к MoE-модели на 26B параметров с 4B активных. У нас он попадал в продолжение примерно в четверти случаев, а средняя длина принятого блока вышла 2,06 токена: то есть за один проход большой модели выходит в среднем два токена вместо одного.

Откуда взялись 15×

Цифра настоящая, но её надо читать вместе с условиями. 15× NVIDIA получила на gpt-oss-120b, восьми DGX B300 и TensorRT-LLM, и не «вообще», а в конкретной точке кривой Парето — там, где на каждого пользователя приходится 500–600 токенов в секунду. Это режим предельной отзывчивости, в котором обычное авторегрессивное декодирование почти нежизнеспособно: чтобы выдать столько токенов одному пользователю, приходится держать батч почти пустым и жечь карту впустую. DFlash в этой точке действительно рвёт базовую линию, потому что сравнивают его с заведомо неэффективным режимом работы.

Стоит сдвинуться в нормальный режим — и множитель садится. В той же статье NVIDIA приводит таблицу при сопоставимой конкурентности: в среднем 2,3× для gpt-oss-120b и 2,8× для Llama 3.1 8B. На одиночном запросе и одной карте — 5,8× на Math500 и 4,4× на MBPP для Gemma-4 31B — соседки по семейству той модели, которую взяли мы. То есть вендорские же данные дают разброс от 1,8× до 15× в зависимости от того, где мерить, и 15× — это самый край.

Что получилось у нас

Мы мерили суммарную скорость генерации на своём стенде, наращивая число одновременных запросов. Базовая линия — Gemma 4 26B без спекуляции, вторая колонка — та же модель с DFlash.

Рабочая станция с установленной видеокартой NVIDIA RTX 6000 PRO

Стенд: одна NVIDIA RTX 6000 PRO, драйвер 610.57.04, CUDA UMD 13.3, инференс в Docker 29.7.2 — llama.cpp server-cuda, сборка 10711. Модель gemma-4-26B-A4B-it-Q4_0, KV-кэш q4_0, 512 токенов на запрос, длина черновика --spec-draft-n-max 4.

Одновременных запросовБаза, ток/сDFlash, ток/сОтношение
1180,8418,02,31×
2325,8519,31,59×
3440,8480,31,09×
4517,3522,31,01×
6674,7581,50,86×
8779,4538,20,69×

График: суммарная скорость генерации против числа одновременных запросов, DFlash обгоняет базовую модель только до четырёх запросов

Читать этот график интереснее не по отношению, а по форме двух кривых. База растёт почти линейно: 181 → 326 → 441 → 517 — карта постепенно загружается, каждый добавленный запрос приносит почти столько же токенов, сколько первый. DFlash выходит на полку сразу после первого запроса и дальше болтается в коридоре 480–580, куда бы мы ни двигали конкурентность. Перелом — около 4,1 запроса. И даже левый край нашего графика вдвое с лишним ниже вендорских 5,8× на соседней модели того же семейства: у NVIDIA — Math500 на своём стенде, у нас — смешанная нагрузка на одной карте.

Важная деталь: проигрывает здесь сервер целиком, а не отдельный пользователь. Скорость одного потока у DFlash не опускалась ниже базовой ни в одной точке развёртки — на восьми параллельных запросах вышел паритет, 98,8 против 97,5 ток/с на поток. Просаживается именно суммарная пропускная способность: карта тратит такты на черновики, которые тут же выбрасываются.

Почему конкурентность съедает ускорение

Обе техники живут за счёт одного и того же ресурса — простаивающих арифметических блоков GPU, — и поэтому не складываются, а конкурируют.

Спекуляция тратит этот простой на черновые токены, часть которых заведомо пойдёт в мусор: блок почти всегда обрубается не на последней позиции, и всё, что за обрывом, посчитано впустую. При одном запросе это ничего не стоит — арифметические блоки всё равно простаивали, выброшенные вычисления бесплатны.

Батчинг тратит тот же простой на чужие настоящие токены. Восемь параллельных запросов гоняют веса через память один раз на всех и считают восемь полезных токенов за проход — ни один не выбрасывается. Как только батч заполняет карту, узкое место переезжает из памяти в вычисления, и вот тогда выброшенные черновые токены перестают быть бесплатными: они отъедают такты у реальной работы. Отсюда и полка на нашем графике, и падение ниже базовой линии.

Это не дефект DFlash, это его границы применимости — и индустрия про них знает. Продакшен-стеки умеют сами отключать спекуляцию при росте конкурентности; порог часто ставят в районе 32 одновременных последовательностей.

Честная оговорка: наш перелом наступает намного раньше типичных для индустрии тридцати с лишним запросов. Это не опровержение вендорских цифр, а другая точка на той же кривой: другое железо — одна карта, а не восемь B300 на Blackwell Ultra, и другой стек — llama.cpp вместо TensorRT-LLM. На восьми B300 с TensorRT-LLM полка окажется заметно правее. Смысл замера не в том, что NVIDIA завысила, а в том, что множитель нельзя переносить со стенда вендора на свой кластер.

Почему прирост есть только на математике и коде

Второе, что видно на замерах: ускорение сильно зависит от того, о чём спрашивать. Внятный прирост мы получили на математических задачах и на генерации кода. На свободном тексте он не просто исчезает, а уходит в минус.

Вот отдельный прогон на том же стенде, разложенный по типам задач: один запрос на карте, 1024 токена, черновик длиной до 15 токенов — заметно жаднее, чем в таблице выше. Третья строка — режим mtp, второй способ спекуляции, который мы гоняли за компанию.

РежимМатематикаКодПрозаСреднееК базе
База188,0189,6189,4189,0
DFlash303,9254,5179,1245,81,30×
MTP211,7175,0143,7176,80,94×

Математика — 1,62×, код — 1,34×, проза — 0,95×: на свободном тексте DFlash уже не помогает, а мешает, и средние 1,30× держатся исключительно на первых двух колонках. У mtp математика вытянула всего 1,13×, а код и проза ушли в минус — в среднем он проигрывает базовой линии, и дальше мы его не рассматривали.

Заодно эта таблица объясняет, почему здесь 1,30×, а в развёртке выше — 2,31×: дело в длине черновика. Пятнадцать токенов мы взяли из примеров в документации, и это оказался худший из проверенных вариантов: те же 1024 токена на n-max=4 дают 330,6 ток/с против 245,8 на n-max=15. Чем длиннее черновик, тем больше вычислений сгорает при отказе, так что заявленный размер блока — верхняя граница, а не рекомендация.

Механика та же — доля принятых черновиков. Черновая модель угадывает продолжение тем лучше, чем меньше в нём энтропии. В выкладке или в коде следующие токены жёстко заданы синтаксисом и шаблоном: закрывающая скобка, имя переменной, знак равенства, стандартный заголовок функции. В живой прозе на каждом шаге десятки равновероятных продолжений, черновик мажет на второй-третьей позиции, блок обрубается — и мы платим за весь просчитанный блок ради одного-двух принятых токенов. Русский добивает ещё и токенизацией: на то же слово уходит больше токенов, значит больше шансов промахнуться внутри блока.

Это ровно то, что видно в вендорских наборах: DFlash тестируют на Math500, GSM8K, HumanEval и MBPP — самых предсказуемых текстах, какие есть. У самой же NVIDIA на «писательских» задачах множитель падает до 1,8× против 2,6× на коде. Есть и внутреннее ограничение: точность черновика падает к концу блока — по данным авторов второй версии метода, с 99,5% на первой позиции до 87,8% на седьмой.

Побочная находка: квантованный KV-кэш стоит 39% скорости

Пока подбирали настройки, наткнулись на вещь, к DFlash отношения не имеющую, но куда более дешёвую в исправлении. Мы держали KV-кэш квантованным (--cache-type-k/v q4_0) — ради длинного контекста. Замена его на f16 подняла базовую линию со 189,0 до 263,1 ток/с, то есть на 39%, вообще без всякой спекуляции; на восьми параллельных запросах — 917 против 784.

Бесплатным этот выигрыш не бывает: f16-кэш занимает примерно вчетверо больше памяти, и на карте поменьше он вместе с моделью и длинным контекстом просто не поместится — придётся выбирать между контекстом и скоростью. Но порядок величины показателен: прежде чем прикручивать спекуляцию, стоит посмотреть, не отдал ли ты те же 39% на квантовании кэша.

И это же — иллюстрация главного тезиса статьи. На q4_0-кэше лучший вариант DFlash давал 1,75× к базовой линии, на f16 — уже 1,35×. Чем аккуратнее выжат базовый инференс, тем меньше остаётся выжать спекуляции.

Что это меняет

DFlash — хорошая инженерия с узкой областью применения, а не бесплатное ускорение для всех. Он окупается там, где важна скорость для одного пользователя, а не суммарная пропускная способность железа: локальный запуск на одной карте, ассистент в IDE, автодополнение кода, вызовы инструментов у агента. Во всех этих сценариях карта и правда недогружена, и спекуляция забирает себе простой, который иначе пропал бы зря. Причём чем медленнее карта, тем спекуляция выгоднее: чем дороже обходится шаг целевой модели, тем больше окупается черновик.

Там, где на карте живёт очередь из пользователей — то есть в любом публичном сервисе, — выгоднее обычный батчинг, и включённая спекуляция начинает мешать. Вывод для нас практический и скучный: свою конфигурацию надо мерить самому, на своих задачах, своих языках и своей реальной конкурентности. Один множитель из пресс-релиза не переносится никуда.

Где посмотреть на скорость у нас

Мы этот вывод применили к себе. В GPTunneL есть Grom 1.5 — наша собственная модель на нашем железе, и мы гнались там ровно за тем, что чувствует пользователь: первый токен примерно через 500 мс и до 120 токенов в секунду в потоке. Достигнуто это не спекуляцией, а тем, что мы контролируем весь стек: балансировщик разводит запросы по пулам под вес задачи, префикс диалога лежит в памяти GPU, а размер модели подобран под то железо, на котором она реально крутится.