Больше интересного про инференс, AI-инфраструктуру и практику с LLM я публикую в Telegram-канале @fuckup_files.
TL;DR
Если у вас стоит vLLM или SGLang и вы упёрлись в задержки на decode-фазе — спекулятивное декодирование (speculative decoding) в 2026 уже не выглядит лабораторной игрушкой. Это нормальный инструмент для memory-bound decode, особенно при низкой и средней параллельности. Сейчас в проде чаще всего встречаются четыре подхода: n-gram/suffix (без обучения, чистая алгоритмика), EAGLE-3 (лёгкая голова, смотрит в активации target-модели, ~2–3× ускорения), native MTP (Multi-Token Prediction — если модель действительно обучена с MTP-головами) и DFLASH (block-diffusion драфтер от Z Lab, который на ряде бенчмарков обходит EAGLE-3). В начале мая Google отдельно выпустил Gemma 4 assistant-драфтеры для speculative decoding — это важно, но их лучше не путать с native MTP-головами внутри target-модели. Ниже разбираю, какой метод когда брать, как это завести в обоих движках и почему рост батча убивает спекуляцию быстрее, чем кажется.
Зачем вообще ускорять decode
Если коротко: инференс LLM делится на две фазы с радикально разным профилем нагрузки на железо.
Prefill — это когда мы прогоняем входной промпт через модель и заполняем KV-кеш. Эта фаза обычно compute-bound: тензорные ядра GPU молотят матрицы, FLOPS-ы летят, всё красиво. На дашборде GPU utilization показывает 90%+, инженер счастлив.
Decode — это когда модель генерирует ответ по одному токену за forward pass. И вот тут начинается боль: каждый токен требует полностью прочитать веса модели из VRAM. На H100 пропускная способность HBM3 примерно 3.35 ТБ/с, и для 70B-модели в FP8 это даёт жёсткий теоретический потолок: даже идеально оптимизированный сервер не сможет выдать больше ~50 токенов/с на одного юзера, просто потому что веса физически не успевают доехать из памяти к ядрам. Вычислители простаивают, ждут данных.
Это и есть memory-bandwidth bound режим. И именно поэтому "GPU utilization 90%" в Datadog обманчив: вычислительные ядра могут быть заняты ожиданием данных, а не полезной работой. Реальную картину лучше смотреть через memory bandwidth, SM occupancy, tokens/sec и latency-метрики, а не через один общий utilization.
Подробнее про то, как это раскладывается по метрикам — в моей предыдущей статье «Как бенчмаркать локальную LLM в 2026: TTFT, TPOT, KV cache, context length и VRAM».
Спекулятивное декодирование решает ровно эту проблему: пока target-модель и так стоит в очереди к памяти, можно потратить часть свободного compute-а на генерацию кандидатов для следующих N токенов и проверить их одним батчированным forward pass'ом target-модели. Если кандидаты угаданы — мы получили несколько токенов за один прогон весов из памяти. Если не угаданы — потеряли compute на драфтер и верификацию. Размен часто в плюс, но только пока ваша нагрузка действительно memory-bound.
Как это работает в одном абзаце
Берём быстрый драфтер — это может быть отдельная мелкая модель, дополнительная "голова" поверх target-а или вообще статистический n-gram-матчер. Драфтер предлагает несколько кандидатов на следующие токены. Target-модель параллельно (одним forward pass'ом, в одном батче) проверяет всю последовательность и говорит: "первые три приняты, четвёртый отвергнут, вместо него выдаю свой". При корректной реализации выход target-модели должен быть алгоритмически lossless относительно обычной авторегрессии. На практике всё равно надо учитывать численные различия, batch effects и особенности sampling-а.
Цена — лишние вычисления на драфтер и на верификацию. На одиночном стриме (батч=1) это очень выгодно: память и так бутылочное горлышко, ядра простаивают, не жалко. На больших батчах вычислительные ресурсы начинают кончаться, и выигрыш постепенно тает. Об этом ниже отдельно.
Карта методов в 2026
Я разделю их по принципу "откуда берутся кандидаты".
N-gram и suffix decoding — без обучения
Самый дешёвый вариант. Драфтер тут не модель, а просто матчинг по тексту.
N-gram ищет повторяющиеся подпоследовательности в самом промпте. Идеально работает на задачах вида "перепиши этот код" или "переведи этот текст" — там модель часто буквально копирует куски ввода. На обычном чате прирост скромный, но не нулевой.
Suffix decoding — следующий шаг: матчит уже не только промпт, но и предыдущие сгенерированные токены, плюс ведёт частотный счётчик и предлагает наиболее вероятные продолжения. Поддерживает древовидное исследование вариантов.
Главное преимущество обоих — никаких весов, никакого обучения, ничего не надо качать. Просто включаете флагом и получаете 1.2–1.5× на подходящих задачах. На неподходящих — почти ноль, но и накладных расходов почти ноль.
В vLLM:
vllm serve meta-llama/Llama-3.1-70B-Instruct \
--speculative-config '{
"method": "ngram",
"num_speculative_tokens": 4,
"prompt_lookup_min": 2,
"prompt_lookup_max": 5
}'
В SGLang это --speculative-algorithm NGRAM. Когда брать: если задача — RAG, рерайтинг, код-ревью или агенты, которые часто пересказывают входные данные.
EAGLE и EAGLE-3 — лёгкая голова поверх target
EAGLE — это уже обученный драфтер, но не отдельная модель, а небольшая надстройка. Идея: вместо того чтобы держать рядом, скажем, 1B-модель и тратить compute на её прогон, мы тренируем компактную "голову", которая берёт скрытые состояния target-модели на промежуточных слоях и предсказывает по ним следующие токены.
EAGLE-3 (NeurIPS 2025) — текущая SOTA-вариация. Голова смотрит на представления target-модели сразу из трёх точек: ранний слой, средний и поздний. Размер драфтерской головы — около 277 МБ, ко-деплоится на той же GPU, лишний VRAM почти не отъедает. По бенчмаркам Spec-Bench — лидер.
Конкретный замер из доков SGLang: EAGLE-3 на Llama-3.1-8B-Instruct на H100 даёт 373 токена/с против 158 токенов/с у голого инференса — это ровно 2.36×.
Минус: EAGLE-3 голову надо где-то взять. Для популярных моделей (Llama-3.1, DeepSeek, Qwen) они уже выложены на HuggingFace. Для нестандартного fine-tune-а придётся обучать самому, и это нетривиальная история — нужен доступ к скрытым активациям target-модели на репрезентативном датасете.
В vLLM:
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--speculative-config '{
"method": "eagle3",
"model": "yuhuili/EAGLE3-LLaMA3.1-Instruct-8B",
"num_speculative_tokens": 5,
"draft_tensor_parallel_size": 1
}'
В SGLang:
python3 -m sglang.launch_server \
--model meta-llama/Meta-Llama-3.1-8B-Instruct \
--speculative-algorithm EAGLE3 \
--speculative-draft-model-path jamesliu1/sglang-EAGLE3-Llama-3.1-Instruct-8B \
--speculative-num-steps 3 \
--speculative-eagle-topk 4 \
--speculative-num-draft-tokens 16 \
--mem-fraction-static 0.7 \
--cuda-graph-max-bs 8 \
--dtype float16
Параметры стоит читать так: num-steps — сколько шагов вперёд драфтер генерирует; eagle-topk — сколько вариантов на каждом шаге держим; num-draft-tokens — итоговый бюджет токенов на верификацию (обычно topk^steps + что-то).
Native MTP — когда драфтер встроен в саму модель
MTP (Multi-Token Prediction) в строгом смысле — концептуально другой подход. Здесь драфтер не отдельная сущность, а часть самой архитектуры target-модели. На этапе обучения модель учат предсказывать не только следующий токен, но и токены через 2, 3, 4 шага вперёд. На инференсе эти "MTP-головы" работают как драфтер.
Главная фишка native MTP — драфтер делит часть состояния с target-моделью. Никакого полноценного второго префиксного прохода, меньше параллельных активаций, ниже накладные расходы. В удачном случае это даёт высокий acceptance-rate и приятную latency-картину.
Минус — модель должна быть обучена с MTP с самого начала. Натянуть MTP на готовую модель пост-фактум нельзя.
Из коробки native MTP сейчас встречается у отдельных семейств вроде DeepSeek-V3/V2, Qwen3-Next, MiMo, ERNIE, LongCat Flash, Pangu Ultra MoE. В vLLM-конфиге это методы deepseek_mtp, qwen3_next_mtp, mimo_mtp, ernie_mtp и так далее, либо обобщённый mtp. Gemma 4 лучше держать отдельной категорией: Google выпустил для неё assistant-драфтеры, которые используются в speculative decoding как малые draft-модели, но это не то же самое, что встроенные MTP-головы внутри target.
vllm serve deepseek-ai/DeepSeek-V3 \
--speculative-config '{
"method": "deepseek_mtp",
"num_speculative_tokens": 2,
"disable_padded_drafter_batch": "False"
}'
В SGLang MTP заводится через тот же EAGLE-флаг:
python3 -m sglang.launch_server \
--model XiaomiMiMo/MiMo-7B-RL \
--speculative-algorithm EAGLE \
--speculative-num-steps 1 \
--speculative-eagle-topk 1 \
--speculative-num-draft-tokens 2 \
--mem-fraction-static 0.7
Это не баг — SGLang переиспользует EAGLE-пайплайн для MTP, потому что архитектурно они близки.
DFLASH — драфтер на block diffusion (свежак февраля 2026)
Самый молодой метод в этом списке и, похоже, главный шум весны. Paper вышел в феврале 2026 от лаборатории Z Lab, а дальше вокруг него быстро появились интеграции в serving-стек.
Главная идея — добить одно неприятное ограничение EAGLE. Какая бы лёгкая EAGLE-голова ни была, она всё равно автогрессивная: чтобы предложить 4 токена вперёд, она должна сделать 4 последовательных шага. Это вынуждает держать драфтерскую архитектуру буквально однослойной — иначе её собственный latency сожрёт весь выигрыш от спекуляции. А однослойный драфтер — это потолок по качеству предсказаний и, как следствие, по acceptance-rate.
DFLASH ставит на место драфтера block diffusion model. Диффузионная модель генерирует блок draft-токенов одним параллельным forward pass. То есть стоимость драфта не обязана расти линейно с числом предлагаемых токенов, как у автогрессивного EAGLE. Это даёт сразу два бонуса: можно сделать драфтер глубже и одновременно предлагать больше токенов вперёд, если под target-модель есть подходящий DFlash checkpoint.
Вторая интересная штука — как DFLASH забирает информацию из target-модели. EAGLE-3 берёт скрытые состояния target из последнего слоя (ну, или из трёх для EAGLE-3) и подаёт их на вход драфтеру. DFLASH идёт дальше: делает feature fusion из множества равномерно засемплированных слоёв target-модели и инжектит результат напрямую в Key/Value-проекции каждого слоя драфтера, кладёт в KV-кеш. То есть кондиционирование на target живёт через всю глубину драфтера, а не один раз на входе. Авторы называют это "persistent conditioning" и связывают именно с этим высокий acceptance.
Что показывают замеры:
- Qwen3-8B на B200 в SGLang, concurrency=1: до 5.1× ускорения
- Тот же Qwen3-8B при concurrency=32: 2.8× — заметная деградация с ростом батча, но не катастрофа
- Qwen3.5-9B: до 4.4× против обычной авторегрессии
- На reasoning-моделях с включённым thinking mode: примерно 4.5× — спекуляция вообще лучше всего окупается на длинных reasoning-цепочках
- На math, code и chat бенчмарках стабильно обходит EAGLE-3 — что нечасто увидишь, EAGLE-3 был SOTA полгода
Запуск в SGLang выглядит так:
python -m sglang.launch_server \
--model-path Qwen/Qwen3-Coder-30B-A3B \
--speculative-algorithm DFLASH \
--speculative-draft-model-path z-lab/Qwen3-Coder-30B-A3B-DFlash \
--speculative-num-draft-tokens 16 \
--attention-backend fa3 \
--mem-fraction-static 0.75 \
--trust-remote-code
Отдельно следите за версией SGLang/vLLM и README конкретного DFlash checkpoint-а: вокруг этого метода интеграции меняются быстро, и часть флагов живёт в экспериментальных ветках раньше, чем попадает в стабильную документацию.
Ограничения, которые надо знать заранее:
- Готовые драфтеры сначала появились вокруг Qwen3 / Qwen3.5 / Qwen3-Coder, но список быстро расширяется: в репозитории Z Lab уже есть checkpoints для Gemma 4, Llama-3.1-8B-Instruct, gpt-oss и других моделей. Поэтому перед выбором метода надо смотреть не только семейство target-модели, но и наличие конкретного matching draft checkpoint.
- Эталонные бенчмарки часто сделаны на B200/современных attention backend-ах. На H100 цифры могут быть скромнее, на consumer-картах типа 4090/3090 — нужно проверять отдельно. Публичные community-цифры полезны как ориентир, но не как основа для capacity planning.
- По dtype и quantization драфтера нужно смотреть конкретный checkpoint: часто стартовая точка — BF16, но это не универсальная гарантия для всех портов и backend-ов.
- В SGLang DFLASH уже есть отдельным алгоритмом
--speculative-algorithm DFLASH. В vLLM ситуация быстро движется: в актуальной документацииdflashуже фигурирует как метод--speculative-config, а Z Lab пишет про core DFlash support в vLLM v0.20.1+ с временными ветками/образами для отдельных архитектур. Поэтому старое правило "DFLASH только в SGLang" уже не стоит держать как абсолют.
Когда брать: если под вашу target-модель есть DFlash checkpoint, вы готовы жить с ограничениями backend-а и у вас latency-sensitive decode при невысокой параллельности — DFLASH стоит проверять первым. Особенно интересно на reasoning-задачах, где длинный decode даёт спекуляции больше пространства для окупаемости.
Что было с Gemma 4 — поучительная история апреля 2026
Эта часть — не маркетинговый разбор, а история реального инцидента, потому что она показывает, как именно живёт спекулятивное декодирование в 2026.
3 апреля Google релизнул Gemma 4. Вокруг релиза почти сразу началась путаница: в обсуждениях много говорили про MTP, но публичные веса target-моделей не давали простого "включил флаг и получил native MTP" сценария. Для vLLM, SGLang и llama.cpp это означало обычный авторегрессивный decode, пока не появились совместимые драфтеры и фиксы в serving-стеке.
Вторая сложность — Gemma 4 использует гибридную attention: чередует sliding-window и full-attention слои. Это делает speculative decoding менее прямолинейным, потому что драфтер, KV-cache и attention backend должны одинаково понимать архитектуру target-модели. Вокруг этого быстро появились EAGLE/assistant/DFlash-чекпойнты и serving-фиксы, но сама история хорошо показывает цену "новая архитектура + day-0 serving".
И вот в начале мая Google официально выложил assistant-драфтеры под Apache 2.0 для Gemma 4 (E2B, E4B, 26B-A4B, 31B) — со словами про speedup до 2× и без потерь качества относительно стандартной генерации. То есть примерно месяц комьюнити и vendor-ы закрывали практический gap между "модель вышла" и "спекулятивное декодирование удобно запустить".
Что из этого стоит вынести: спекулятивное декодирование — это не "включил флаг и всё работает". Оно тесно связано с архитектурой attention в target-модели, с тем, как организован KV-кеш, и с особенностями конкретной версии serving-движка. Если завтра выходит новый MoE с какой-нибудь экзотичной маршрутизацией экспертов — ждите ещё один такой же раунд багфиксов.
Где спекуляция начинает разваливаться
Главный миф — "включил EAGLE-3, получил 2.5× и счастлив всегда". В реальности есть несколько мест, где скорость деградирует или вообще пропадает.
1. Большой батч
Спекулятивное декодирование выгодно, пока вы упираетесь в пропускную способность памяти. На батче=1 это всегда так. На батче 4–8 обычно тоже. На батче 64+ — уже не факт. С ростом батча нагрузка на вычисления в decode растёт линейно (нужно считать attention для каждой позиции каждого юзера), а нагрузка на шину памяти остаётся примерно постоянной — веса читаются один раз на батч. В какой-то точке режим переключается с memory-bound на compute-bound, и любой драфтер начинает только мешать: он добавляет лишние вычисления, которые теперь бьют по уже занятым ядрам.
Это, кстати, объясняет, почему в анонсах speculative decoding часто подчёркивают low-latency, on-device или low-QPS сценарии. Цифра из локального запуска на Apple Silicon или одиночного B200 — не то же самое, что серверный инференс на H100 с 256 параллельными пользователями.
2. Низкий acceptance-rate
Если задача такая, что target-модель часто отвергает кандидатов от драфтера (например, генерация очень разнообразного креативного текста), вы платите compute за драфт и за отвергнутую верификацию, а получаете немного токенов за прогон. Эвристики типа той, что в HF Transformers, динамически подкручивают num_speculative_tokens вниз при rejection — но они не могут сотворить чудо. Если задача плохо предсказуема — спекуляция работает хуже.
Хороший показатель в проде — acceptance length (среднее число токенов, принятых за один draft-verify цикл). На EAGLE-3 здоровый acceptance length для чата это 2.5–3.5. Если он у вас 1.5 — что-то сломано: драфтерская модель не подходит, температура слишком высокая, или вы упёрлись в compute.
3. Pipeline parallelism
В vLLM ≤0.15.0 спекулятивное декодирование несовместимо с pipeline parallelism. Если вы раскатили модель на несколько GPU через PP (а не TP), включить спекуляцию нельзя. Это известное ограничение, которое чинят, но проверяйте релиз-ноуты вашей версии.
Если вы как раз планируете multi-GPU/multi-node инференс — у меня есть отдельная подробная статья: «Распределенный запуск LLM на нескольких GPU с помощью Ray и vLLM».
4. Нестабильные logprobs
Документация vLLM прямо предупреждает: при включённом спекулятивном декодировании logprobs не гарантированы стабильными между запусками и между размерами батча. Если у вас downstream-логика, которая на logprobs опирается (например, классификатор поверх токенов или constrained decoding с определёнными правилами) — это надо тестировать отдельно.
5. KV-кеш и память
EAGLE-голова сама по себе небольшая (~277 МБ), но во время верификации в KV-кеш заходит сразу несколько кандидатов на каждый токен. Эффективный размер KV вырастает в num_speculative_tokens раз для проверяемой части. На длинном контексте это может вытолкнуть вас в OOM, который без спекуляции бы не случился. Стандартное лекарство — снизить gpu_memory_utilization или max_num_seqs.
Подробно про KV-кеш и его размер — в «KV-cache в моделях transformers».
Практический чеклист: как выбрать метод под свой кейс
Не претендую на универсальную истину, но вот как я расставил бы приоритеты.
Если у вас батч=1 или локальный запуск с малой параллельностью:
- Если под вашу модель есть DFlash checkpoint и backend его поддерживает — первым проверьте DFLASH.
- Если модель из native MTP-семьи (DeepSeek-V3, Qwen3-Next, MiMo и похожие) — проверяйте MTP. Если это Gemma 4 — смотрите на
assistant-драфтеры или DFlash/EAGLE-чекпойнты под конкретную версию модели. - Иначе — EAGLE-3, если есть готовая голова.
- Если головы нет и обучать нечем — n-gram. Скромно, но бесплатно.
Если у вас сервинг с высокой параллельностью (батч >32):
- Сначала измерьте, в каком вы режиме. Если уже compute-bound — спекуляция, скорее всего, не поможет, а то и навредит.
- Если всё ещё memory-bound — n-gram/suffix как самый дешёвый по compute вариант.
- EAGLE-3 — только если acceptance-rate в проде стабильно выше 2.5.
Если у вас агенты или RAG с большим повторением промпта:
n-gram даёт самый предсказуемый прирост, потому что много токенов буквально копируется из инпута.
Если вы не знаете, в каком вы режиме:
Замерьте отдельно для своей нагрузки. На vLLM есть встроенные prometheus-метрики vllm:spec_decode_num_accepted_tokens_total и vllm:spec_decode_num_draft_tokens_total — отношение даёт тот самый acceptance length. На SGLang — аналогичные spec_* метрики.
Про сам процесс измерения и подводные камни — в «Как бенчмаркать локальную LLM в 2026».
Сравнительная таблица
| Метод | Ускорение | Доп. VRAM | Нужно обучать | Лучше всего для |
|---|---|---|---|---|
| N-gram | 1.1–1.5× | ~0 | Нет | RAG, рерайтинг, код |
| Suffix | 1.2–1.6× | ~0 | Нет | То же + длинные сессии |
| EAGLE-3 | 2.0–2.5× | ~300 МБ | Да (есть готовые) | Универсал, батч ≤16 |
| Native MTP (DeepSeek, Qwen3-Next, MiMo) | 2.0–3.0× | Низкая/зависит от реализации | Только при тренировке target | Современные MTP-модели, локальный запуск |
| Gemma 4 assistant-драфтеры | До ~2× | Нужен отдельный drafter | Нет, если берёте готовый checkpoint | Gemma 4, локальный и low-QPS inference |
| DFLASH | 3.0–5.1× | Зависит от draft checkpoint | Да (есть готовые для ряда моделей) | Matching checkpoint + SGLang/vLLM, reasoning, B200/H100 |
Цифры — медианные по бенчмаркам и докам vendor-ов на конец апреля – начало мая 2026. Под вашу модель и нагрузку могут отличаться в 1.5 раза в обе стороны.
Частые вопросы
Спекулятивное декодирование меняет качество ответа?
В идеальной формулировке — нет: target-модель верифицирует draft-токены и сохраняет распределение обычной авторегрессии. В реальном serving-е могут всплывать численные различия, batch effects, sampling randomness и особенности конкретной реализации. Поэтому для greedy/temperature=0 обычно ждём совпадения, а для stochastic sampling лучше сравнивать распределение и метрики, а не один конкретный текст.
А почему тогда у меня ответы стали другими после включения EAGLE?
Скорее всего, у вас изменились logprobs из-за другого размера батча или из-за того, что спекулятивный путь включил другую реализацию attention. Если используете temperature>0 — изменилась последовательность семплирования. Это не потеря качества, а другая выборка из того же распределения.
Сколько токенов выставлять в num_speculative_tokens?
Дефолт 4–5 — нормальная отправная точка. Дальше смотрите на acceptance length: если он близок к num_speculative_tokens — можно увеличить. Если в 2 раза меньше — уменьшайте. Слишком большое значение тратит compute впустую и ухудшает latency на rejection-кейсах.
Можно ли использовать спекуляцию с квантованной моделью (FP8, INT4)?
Да, и часто это даёт лучший combined результат — квантование снижает bandwidth-нагрузку на target, спекуляция уменьшает количество прогонов через память. Но проверьте, что квантованная версия драфтера (если он отдельный) совместима по dtype с target.
А если у меня DeepSeek-V3 и я уже использую native MTP — есть ли смысл сверху накатывать EAGLE-3?
Обычно нет. Native MTP уже встроен в архитектуру и использует близкое состояние target-модели. Накатывать сверху ещё один драфтер — это два слоя спекуляции, которые будут конкурировать за compute и acceptance.
А почему DFLASH-цифры из paper иногда не воспроизводятся у меня?
Потому что эталонные бенчмарки завязаны на конкретное железо, attention backend, batch/concurrency и draft checkpoint. На H100 вместо B200 вы можете недосчитаться ощутимой части. На consumer-карте — ещё больше. Плюс некоторые публичные цифры — это проекция от paper "DFLASH в 2.5× быстрее EAGLE-3" на ваш baseline, а не прямой замер. Перед capacity planning меряйте на своей нагрузке и своём железе, без вариантов.
Что с TensorRT-LLM, не пропустил ли я его?
Не пропустили. TensorRT-LLM поддерживает EAGLE и Medusa и обычно выдаёт лучшую сырую скорость на железе NVIDIA, но за это вы платите менее гибким развёртыванием и привязкой к одному вендору. В open-source стеке vLLM и SGLang остаются главными игроками, и именно они получают day-0 поддержку новых драфтерских архитектур — как только что было с Gemma 4.
Что почитать рядом
- vLLM vs SGLang: radix tree против block-level prefix caching — про другой большой вектор оптимизации serving-а.
- KV-cache в моделях transformers — фундамент, без понимания которого MTP-механика выглядит магией.
- Куча лжи: отладка утечки памяти в vLLM — как раз про то, как сломанный KV-кеш на нестандартной архитектуре превращается в кошмар.
- Распределенный запуск LLM на нескольких GPU с помощью Ray и vLLM — если ваш target не лезет в одну карту.
Заинтересовало? Больше практических разборов про LLM, инференс и AI-инфраструктуру — в моём Telegram-канале @fuckup_files.