Ну чё, малютки, ловите парадокс: у тебя простаивает дорогая видюха на 80 ГБ VRAM, генерируя текст медленнее, чем могла бы. GPU способен за один такт перемножить миллиарды чисел, а токены всё равно сыпятся по одному в час по чайной ложке. Спекулятивный декодинг — трюк, который эксплуатирует именно эту дыру: даёт мелкой модели угадывать за большую, а большой — проверять оптом. Итог — ускорение в 2–3 раза, и результат бит-в-бит совпадает с тем, что модель выдала бы сама.
Мелкая черновая модель быстро предлагает несколько токенов вперёд. Большая целевая модель проверяет их все за один проход — так же дёшево, как сгенерить один токен. Верные догадки принимаются, неверные — отбрасываются и переписываются целевой моделью. Итоговый текст математически идентичен тому, что дала бы одна целевая модель без всякого черновика.
Парадокс простаивающего GPU
Раскладка, которую я уже проговаривал в статье про KV-кэш: генерация токен за токеном — это memory-bound процесс. На каждом шаге GPU обязан перечитать из VRAM вообще все веса модели (и KV-кэш), чтобы вычислить один-единственный следующий токен. Арифметика при этом занимает жалкие доли времени — основное узкое место — это скорость шины памяти, а не мощность тензорных ядер.
Результат: на генерации GPU простаивает. Вычислительные блоки ждут, пока подъедут данные из памяти, а могли бы в это время посчитать в разы больше — были бы токены, на которых считать.
Спекулятивный декодинг подсовывает эти токены. Идея простая до неприличия: пусть маленькая быстрая модель набросает черновик на несколько шагов вперёд, а большая модель, вместо генерации по одному, проверит весь черновик разом — благо простаивающей вычислительной мощности для этого с избытком.
Черновик и редактор
Аналогия из редакции: черновик пишет стажёр — быстро, дёшево, местами ошибается. Главный редактор не переписывает всё с нуля — он читает черновик и вычёркивает только то, что неверно. Если стажёр угадал правильно — редактор экономит кучу времени. Если ошибся — редактор всё равно должен был прочитать текст, так что потерь почти нет.
В цифрах: черновая модель (draft) предлагает k токенов вперёд, целевая модель (target) проверяет все k за один проход и для каждой позиции решает — принять токен черновика или заменить своим. Смотри на гонке двух дорожек:
Ключевое: обычный декод тратит один проход GPU на один токен. Спекулятивный тратит один проход GPU на проверку целой пачки, а сколько токенов из неё примут — вопрос отдельный. В худшем случае (черновик ошибся сразу) не проиграли почти ничего. В лучшем — за один проход получили сразу k+1 токен.
Почему проверка почти бесплатна
Вот это и есть весь фокус, объясняющий, откуда берётся ускорение без потери качества. Проверка k черновых токенов — это forward pass целевой модели с последовательностью длины k, поданной параллельно. А раз декод memory-bound, время шага определяется не тем, сколько токенов считать, а тем, что веса читаются из VRAM один раз за проход — независимо, обрабатывается ли на этом проходе один токен или пять.
То есть проверить черновик из 5 токенов стоит примерно как сгенерить 1 токен обычным способом. Если целевая модель согласилась хотя бы с парой из пяти — уже в выигрыше. Именно поэтому у спекулятивного декодинга почти нет минусов: цена входа — один лишний проход весов, а выигрыш растёт с каждым принятым токеном черновика.
Хитрость в том, что позиции 2..k уже известны заранее — их предложил черновик. Целевая модель не должна ждать вывода предыдущего шага, чтобы узнать вход следующего: вся последовательность подаётся сразу, с маской внимания, которая не даёт токену видеть будущее (та же causal-маска, что и при обучении). Технически это тот же трюк, которым тренируют трансформеры — teacher forcing, только тут учитель — черновик, а не размеченные данные.
Математика гарантии: почему это не аппроксимация
Самый частый вопрос: если часть токенов пишет мелкая слабая модель, разве качество не проседает? Ответ — нет, и это доказуемо. Приём называется speculative sampling, и вот его суть без формул:
q(x) по своему распределениюp(x) для того же токенаmin(1, p(x)/q(x)) — чем увереннее в нём целевая модель относительно черновика, тем выше шанс принятьp(x) напрямую, а из скорректированного остатка max(0, p(x) − q(x)), нормализованного зановоШаг 4 — не техническая деталь, а сердце доказательства: он гарантирует, что после исправления итоговое распределение токена математически равно p(x) — тому самому, что выдала бы целевая модель, если бы генерировала сама с нуля, без всякого черновика. Это не «почти то же самое» и не размен качества на скорость — это точная выборка из того же распределения, только более быстрым путём.
Гарантия строгая при обычном сэмплировании (в том числе greedy — жадный выбор самого вероятного токена, это частный случай с температурой → 0). При экзотических стратегиях (beam search, некоторые виды контроля разнообразия) эквивалентность нужно доказывать отдельно — большинство движков включают спекуляцию только там, где она гарантированно бесплатна по качеству.
Сколько реально выигрываем
Выигрыш зависит от трёх вещей: как часто черновик угадывает (acceptance rate, α), сколько токенов он предлагает за раз (k), и насколько он сам дешевле целевой модели. Формула ожидаемого числа принятых токенов за раунд — (1 − α^(k+1)) / (1 − α), классика из оригинальной статьи Leviathan et al. Покрути сам:
Общее наблюдение: высокий acceptance rate решает всё. Он падает на творческих задачах с высокой температурой (там много правдоподобных продолжений, черновику сложнее угадать точный токен) и растёт на предсказуемых текстах — код, повторяющиеся структуры, автодополнение. Отсюда практика: спекулятивный декодинг даёт наибольший буст именно на кодогенерации и структурированном выводе, а на «напиши стих в свободном стиле» выигрыш скромнее.
Зоопарк методов: кто предлагает черновик
Все методы решают одну задачу — где взять дешёвый и точный черновик — но по-разному:
| Метод | Как устроен | Особенность |
|---|---|---|
| Отдельная draft-модель | Меньшая модель того же семейства (например, Llama 1B рядом с Llama 70B) | Просто внедрить, но нужен отдельный набор весов в VRAM и совпадающий токенизатор |
| Prompt lookup / n-gram | Черновик ищется прямо в промпте по совпадению n-грамм, без отдельной модели | Бесплатно по VRAM, отлично работает на задачах вроде «перепиши этот файл», где вывод повторяет вход |
| Medusa | Несколько дополнительных «голов» поверх той же целевой модели, каждая предсказывает свою позицию вперёд | Не нужна отдельная модель — головы дообучаются поверх готовой |
| EAGLE / EAGLE-2 / EAGLE-3 | Лёгкий блок предсказывает не токены, а следующие скрытые состояния целевой модели, из них уже сэмплится токен | Заметно точнее n-gram и Medusa за счёт доступа к внутреннему представлению целевой модели |
| MTP (Multi-Token Prediction) | Черновые головы встроены и обучены вместе с самой моделью с нуля | Так устроены DeepSeek-V3 и Kimi K2.5 — про их архитектуру я писал в разборе MoE |
Тренд последних двух лет — от «поставь рядом отдельную маленькую модель» к «встрой дешёвый черновик прямо в архитектуру». EAGLE и MTP выигрывают именно потому, что черновик работает не вслепую по тексту, а видит внутренности целевой модели.
Подвохи
На больших батчах выигрыш тает. Спекуляция отбивает простаивающую вычислительную мощность GPU. При батче 1 (твой локальный чат) её навалом — грех не воспользоваться. А вот на сервере с батчем 64+ GPU и так уже занят вычислениями под завязку, свободных тактов для проверки черновика не остаётся — декод из memory-bound потихоньку переходит в compute-bound, и спекуляция может даже замедлить throughput вместо ускорения. Поэтому провайдеры включают её адаптивно: для интерактивных запросов с маленьким батчем — да, для батчевой обработки — часто нет.
VRAM на черновик. Отдельная draft-модель — это лишние веса и лишний KV-кэш в памяти, конкурирующие с основной моделью за то самое место, о котором я писал в статье про квантизацию. EAGLE и MTP этой проблемы почти лишены — черновой блок на порядки легче отдельной модели.
Токенизаторы должны совпадать. Черновая и целевая модель обязаны говорить на одном словаре токенов — иначе сверять предсказания токен-в-токен невозможно. На практике это ограничивает выбор draft-модели родственными моделями того же семейства.
Как включить у себя
vLLM — отдельная draft-модель или бесплатный n-gram, флагом при запуске:
# С отдельной draft-моделью
vllm serve meta-llama/Llama-3.1-70B \
--speculative-config '{"model": "meta-llama/Llama-3.2-1B", "num_speculative_tokens": 5}'
# Без отдельной модели — prompt lookup по n-граммам
vllm serve Qwen/Qwen3-8B \
--speculative-config '{"method": "ngram", "num_speculative_tokens": 5, "prompt_lookup_max": 4}'
llama.cpp — вторая модель через -md, длина черновика через --draft-max:
llama-server -m target-70b-q4.gguf -md draft-1b-q4.gguf \
--draft-max 5 --draft-min 1
SGLang — EAGLE встроен нативно, включается флагом:
python -m sglang.launch_server \
--model-path meta-llama/Llama-3.1-70B \
--speculative-algorithm EAGLE \
--speculative-num-steps 5 --speculative-eagle-topk 8
Если модель поддерживает нативный MTP или для неё есть готовый EAGLE-чекпоинт — бери его, выигрыш стабильно выше. Если нет — начни с prompt lookup на n-граммах: он бесплатен по VRAM и уже даёт заметный буст на задачах с повторяющимся выводом (рефакторинг кода, правки по инструкции). Отдельную draft-модель имеет смысл поднимать, только когда точность важнее экономии памяти.
TL;DR
Спекулятивный декодинг — редкий случай в ML, где ускорение достаётся буквально бесплатно: не разменял точность на скорость, а просто нашёл дыру в том, как железо тратит время. В следующий раз, когда локальная модель вдруг застрочит подозрительно быстро — знай, рядом крутится черновик. 🫡


