Спекулятивное декодирование: от чего на самом деле зависит ускорение

Языковая модель пишет ответ по одному токену за шаг, и на каждом шаге прочитывает все свои веса от начала до конца. Спекулятивное декодирование пытается это обойти: маленькая черновая модель набрасывает несколько токенов вперёд, причём сразу деревом вариантов, а большая проверяет всё это дерево за один свой проход и принимает ту ветку, которая совпала с её собственным выбором. Текст на выходе получается тот же самый, быстрее становится только его получение. Так устроен метод из статьи EAGLE-3 с NeurIPS 2025, и авторы приводят для него ускорение до 6.5 раза.
Звучит как выигрыш без подвоха. Я прогнал этот метод у себя и получил другое, причём всё перечисленное — одна карта, одна пара моделей, один прогон: на коде ускорение в 2.6 раза, на диалоге 1.95, а на запросах, под которые черновая модель не обучалась, — около единицы, то есть выигрыша нет вовсе. Если вдобавок оставить дерево черновиков глубоким, метод уходит в минус.
Разброс от 2.6 до минуса даёт одна и та же реализация без единой подкрутки. Управляет им одна величина, и эта статья про то, какая именно и почему.

Про 6.5 сразу оговорюсь, потому что это первый урок статьи. Столько у авторов даёт лучшая ячейка таблицы — HumanEval на Vicuna-13B, в их собственном фреймворке; средние по моделям у них 5.51 и 4.44. Моя пара моделей другая, карта другая, черновая голова чужая, и складывать эти числа с моими нельзя. Ускорение принадлежит не методу, а стенду, и полные условия своего стенда я выкладываю ниже, прямо перед замерами.
Почему это вообще может работать
Обычно на этом месте цитируют «декодирование упирается в память». Я это померил.
Проход целевой модели на одном токене занимает 44.7 мс. На 192 токенах — 51.8 мс. На 512 — уже 125.6 мс.

То есть до примерно двух сотен токенов проход почти не дорожает. Причина в том, что время съедает не арифметика, а чтение весов через шину памяти: веса читаются один раз независимо от того, сколько позиций вы обрабатываете.
Отсюда весь метод. Проверить дерево из 60 черновиков стоит примерно столько же, сколько сгенерировать один токен. Если из этих 60 вы приняли три — вы получили три токена по цене одного.
Здесь же то, что уменьшает эффект. Замеренный шаг примерно вчетверо дороже того, что предсказывает арифметика памяти: шина загружена на четверть. Разница — накладные расходы реализации, внимание без специализированных ядер, пооперационный запуск CUDA-ядер из Python. Спекуляция делит эти расходы на все принятые токены, поэтому часть измеренного выигрыша — амортизация чужой неоптимальности, а не победа над памятью. На вылизанном стеке вроде vLLM тот же метод даёт меньше, и независимые замеры это подтверждают: 1.3–2 раза там, где авторские фреймворки показывают 4–6.
Длина принятия — величина, через которую считается всё
Длина принятия τ — среднее число токенов, которые цикл выдаёт за один проход целевой модели. У обычной генерации τ = 1 по определению.
Тогда выигрыш описывается так:
Здесь — время обычного шага генерации,
— время проверки всего дерева за один проход целевой модели,
— время одного шага черновой модели,
— глубина дерева.
В числителе то, сколько токенов приносит цикл. В знаменателе то, во что он обходится: одна проверка дерева плюс последовательных проходов черновой модели.
Из формулы сразу видно несимметричное: ширина дерева почти бесплатна, потому что лишние узлы попадают в тот же проход проверки, а вот глубина покупается новыми последовательными запусками черновой модели, и скрыть их нечем.
Разбор цикла по фазам это подтверждает. На дереве из 64 узлов: черновики 8.9 мс, проверка 44.7 мс, выбор пути 0.4 мс, остальное 1.4 мс. Проверка занимает восемь десятых цикла и стоит практически столько же, сколько обычный шаг ради одного токена, — только приносит два с половиной токена.

Условия замера
Тезис этой статьи — что цифра ускорения принадлежит стенду. Тогда стенд надо выложить целиком.
Железо. Бесплатная Kaggle T4. Это Turing: нет bf16, не заводится FlashAttention-2, соотношение памяти и арифметики совсем не то, что у карт, на которых меряют авторы. Машина вдобавок общая, и сосед по хосту влияет на числа. Какая-то часть разрыва с авторскими цифрами — просто про карту, и я не могу сказать, какая именно.
Черновая голова чужая. Под Qwen3 у авторов готовой нет, я взял ту, которую сам репозиторий указывает в таблице весов, — от команды AngelSlim в Tencent. Её копию я выложил на Kaggle, чтобы ноутбук запускался, ничего не скачивая руками. Это главный конфаундер всего разбора: от обучающей смеси головы длина принятия зависит почти целиком. Всё, что дальше сказано про домен, — про то, на чём училась эта голова, а не про EAGLE-3 как метод.
Batch = 1. Латентность одного запроса, а не пропускная способность. При большом батче вычислители загружены и без спекуляции, проверять черновики уже нечем, и выигрыш схлопывается: у самих авторов 4–6 раз при batch = 1 против 1.38 в SGLang при batch = 64.
Базовый вариант. Ускорение считается относительно чего-то, и это «что-то» решает половину дела. Я меряю против обычного цикла генерации из того же репозитория: та же модель, тот же буфер кэша, единственное отличие — спекуляция. Относительно штатного generate из transformers те же прогоны дают 1.79 вместо 2.03 в среднем по всем наборам, потому что у него другой бэкенд внимания. Числа в таблице ниже даны по наборам и против первого базового варианта; среднее по четырём английским наборам выходит 2.3.
Маленькая цель. У Qwen3-1.7B шаг дёшев, поэтому черновая голова на её фоне обходится дороже, чем обходилась бы рядом с моделью на 70 миллиардов параметров.
Протокол. Декодирование жадное, температура нулевая. Из каждого набора берутся пять вопросов, ровно 192 новых токена на прогон — конец последовательности подавляется, чтобы все сравниваемые способы делали одинаковую работу и время делилось на одно и то же число токенов. Разброс между вопросами внутри набора показан усами на графике; конфигурации в срезах по форме дерева меряются по три раза.
Замер
Наборы я взял не самодельные: репозиторий EAGLE везёт с собой ровно те, на которых считает статью. Плюс пятый, свой, русскоязычный.
Набор | τ | Ускорение |
|---|---|---|
HumanEval (код) | 3.82 | 2.60 |
GSM8K (арифметика) | 3.68 | 2.46 |
Alpaca (инструкции) | 3.19 | 2.17 |
MT-Bench (диалог) | 2.91 | 1.95 |
Русскоязычный | 1.38 | ≈0.95 |

В разбросе между английскими наборами уже виден механизм: код и арифметика ускоряются заметно лучше диалога. Текст с жёсткой структурой предсказывать легче, и черновая модель угадывает чаще.
Последнюю строку надо разбирать отдельно.
Почему вне домена выигрыш исчезает
Длина принятия падает с 3.4 до 1.4. Формула дальше считает сама: числитель ужался почти втрое, знаменатель остался прежним.
Важная оговорка. Мой русскоязычный набор одновременно вне домена черновой головы и на другом языке. Разделить эти два обстоятельства этот прогон не может. Голову обучало сообщество на англоязычных диалогах, поэтому «дело в данных обучения» — версия правдоподобная, но не доказанная. Проверить её мог бы англоязычный набор вне домена или голова, обученная на русскоязычных данных; ни того ни другого у меня нет.
Что установлено твёрдо: там, где черновая модель угадывает редко, метод не даёт никакого преимущества.
Ловушка глубины
Теперь про 0.95 в таблице. Эта строка снята на стартовой форме дерева — 60 узлов, глубина 7. И вот что выясняется, если форму подобрать.
Срез по глубине при фиксированной ширине: τ растёт 2.51 → 2.78 → 2.91 → 3.04 → 3.09 → 3.12 и упирается в плато. А ускорение при этом падает с 2.29 при глубине 2 до 1.82 при глубине 10. Глубина покупает длину принятия, которая не окупается: каждый уровень — это ещё один последовательный проход черновой модели, и платите вы за него всегда, а окупается он редко.

На тех же русскоязычных запросах при выбранной форме (глубина 2) замедления уже нет — выходит небольшой плюс, около 1.04–1.10. Длина принятия при этом почти не меняется: 1.38 против 1.36.
Значит замедление — свойство не домена самого по себе, а домена и глубины вместе. При τ около 1.4 шесть лишних проходов черновой модели не отбиваются ничем, и цикл «построить дерево, проверить, отбросить» вырождается в накладные расходы поверх обычного шага.
Домен задаёт, сколько метод может выиграть. Глубина дерева — сколько он может проиграть.
Авторская эвристика автоподбора формы крутит только число узлов и глубину не трогает вовсе. На этой карте она промахивается предсказуемо: перебирает деревья только до 60 узлов, а проход остаётся плоским далеко за этой границей.
Что даёт именно ветвление

Вырожденный случай дерева при ширине 1 — это классическое спекулятивное декодирование цепочкой: одна последовательность, одна проверка. Сравнение при одинаковой глубине и одинаковом числе проходов целевой модели: τ 2.28 против 1.64. Ветвление добавляет 39% к длине принятия за те же деньги.
Это и есть вклад древовидности, отделённый от всего остального.
Большая цель не помогает так, как ожидаешь
Интуиция подсказывает: чем больше целевая модель, тем дешевле на её фоне черновая голова и тем лучше метод окупается. Так я дошёл до замера на паре с Qwen3-4B.
Длина принятия действительно растёт — примерно на 9%, и в домене, и вне его. А ускорение за ней не идёт: разница между парами не выходит за 2–3%, и её знак меняется от прогона к прогону.

Ломается предпосылка. Цена черновой головы не фиксирована: голова выросла вместе с целью, со 137 до 218 миллионов параметров, потому что её скрытый размер расширяется следом за моделью. Как доля цели она ужалась с 8.0% до 5.4%, но знаменатель формулы платит не за долю, а за абсолютное время черновика.
Возобновится ли рост на восьми миллиардах и дальше, этот замер не скажет: такая пара в бесплатную T4 не помещается.
А выдача точно не меняется?
Вторая половина обещания метода — в том, что результат остаётся прежним. Это проверяемо.
При жадном декодировании выдача совпадает потокенно. Расхождения нашлись — 1.8 на тысячу токенов, — и все они приходятся строго на позиции, где два лучших кандидата неразличимы по логиту: разрыв там ровно нулевой против типичных 2.84. Это порядок суммирования в fp16, а не правило приёма.
При сэмплировании сравнивать токены бессмысленно, надо сравнивать распределения. Самое прямое доказательство: я перехватил тензор вероятностей, из которого обе генерации берут первый токен, и они совпадают бит-в-бит.
Дорога к этому выводу оказалась поучительнее самого вывода: мой первый статистический тест уверенно находил различие там, где его нет. Про это будет отдельная статья, потому что ошибка не имеет отношения к EAGLE и повторяется в любой проверке «стало ли иначе».
Что спрашивать про любое заявленное ускорение
Пять вопросов, которые я теперь задаю к любой цифре вида «в N раз быстрее».
Относительно чего? Разница между честным базовым вариантом и удобным легко составляет десятки процентов.
При каком размере батча? Латентность одного запроса и пропускная способность под нагрузкой ведут себя противоположно.
На каком стеке? Если базовый вариант неоптимален, часть выигрыша — амортизация его накладных расходов.
На каких данных? Длину принятия надо мерить на своём распределении запросов. Чужие цифры не переносятся.
При какой форме дерева? Одни и те же запросы дают и минус, и плюс в зависимости от глубины.
Чего здесь нет
Батч больше единицы: реализация в репозитории работает только с batch = 1, а именно батчинг сильнее всего съедает выигрыш спекуляции. Длинные контексты. И обучение своей головы под свой домен — самое интересное продолжение, если верить второму выводу.
Ссылки
Ноутбук на Kaggle — полный прогон со всеми замерами и кодом. Форкается: чтобы проверить на своей модели, меняются две строки.
Англоязычная версия — тот же разбор для международной аудитории.
Репозиторий на GitHub — обе версии, PDF, графики и скрипт, который сверяет числа в тексте с числами прогона.
Код взят из официального репозитория SafeAILab/EAGLE как есть, на пиненном коммите. Черновая голова — AngelSlim/Qwen3-1.7B_eagle3, Apache 2.0; копия на Kaggle для тех, кто запускает оттуда.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.