Как удвоить скорость декодирования Qwen3.8-Flash-Next с MTP на видеокартах RTX
Как собрать форк llama.cpp со спекулятивным декодированием MTP и подобрать флаги, чтобы примерно вдвое ускорить генерацию Qwen3.8-Flash-Next на системах с несколькими RTX.
Многие говорят, что Qwen3.8-Flash-Next не дотягивает до своих результатов в бенчмарках. В ежедневной работе рядом с Qwen3.8-27B она ведёт себя иначе, чем гласит эта репутация. Для своего размера она не сжигает много токенов на размышления, делает то, что просят, без споров, держит нить длинной сессии и правильно вспоминает более раннюю историю. По сравнению с DeepSeek V4 Flash 0731 это более приятный повседневный ассистент, и легко дойти до того, чтобы сделать её моделью по умолчанию.
Единственная претензия — скорость. На трёх RTX 3090 и RTX 3070 Ti декодирование держится на уровне около 30 токенов в секунду. Это медленная ровная капель, и быстрая машинистка её обгонит.
Известное лекарство есть, и называется оно MTP (multi-token prediction). Это вариант спекулятивного декодирования от DeepSeek: лёгкая черновая голова, прикрученная к модели, предлагает несколько следующих токенов, а полная сеть лишь проверяет их. Проверка стоит гораздо дешевле генерации, так что пять токенов могут появиться за то время, что обычно уходит на один.
Загвоздка в том, что llama.cpp не смёржил MTP для этой модели. После долгих проб и ошибок одна комбинация весов, форка и флагов примерно удвоила пропускную способность с теми же файлами модели, тем же сервером и теми же четырьмя GPU. Рецепт ниже, а после него — о том, почему выпуск этой функции превратился в такую неразбериху.
Возьмите правильные веса
Для основной модели скачайте сборку Q4_K_M на 4,27bpw из GGUF-репозитория AtomicChat.
Этот квант делали с прицелом на MacBook: он рассчитан на объединённую память Apple silicon. Но и на картах RTX он работает отлично, причём ему нужно меньше VRAM, чем сопоставимому кванту от Unsloth. Иногда сборка, упакованная для одной аудитории, оказывается подходящей для другой.
В репозитории есть и вариант поменьше — IQ4_XS на 3,84bpw. Протестировали оба, и меньший заметно хуже. Экономия 0,4 бита на вес не стоит того, что вы теряете.
Голова MTP поставляется отдельным файлом-спутником, который лежит в папке sidecar у Unsloth. Здесь использовалась голова Q8_0. Если VRAM мало, подойдёт и голова Q4_K_M.
Соберите форк
Рабочий код лежит в ветке qwen4exp/mtp форка llama.cpp от danielhanchen. Собственная документация Unsloth отправляет туда же, что говорит о том, где эта функция на самом деле живёт сейчас.
Это обычная сборка через CMake: установите пакеты, склонируйте ветку, сконфигурируйте.
apt-get update
apt-get install pciutils build-essential cmake curl libcurl4-openssl-dev -y
git clone --branch qwen4exp/mtp https://github.com/danielhanchen/llama.cpp
cmake llama.cpp -B llama.cpp/build \
-DBUILD_SHARED_LIBS=OFF -DGGML_CUDA=ON
cmake --build llama.cpp/build --config Release -j --clean-first \
--target llama-cli llama-mtmd-cli llama-server llama-gguf-split
Запустите сервер
Вот команда запуска, которая работает как есть. Она использует четыре GPU, контекстное окно на 200K и черновик MTP, который может предлагать до 5 токенов за шаг.
CUDA_VISIBLE_DEVICES=0,1,2,3 llama-server \
-m .../Qwen3.8-Flash-Next-AD-4.27bpw-Q4_K_M-M64-00001-of-00033.gguf \
--alias Qwen3.8-Flash-Next \
--jinja \
-ngl 99 \
-c 200000 \
--split-mode layer \
--tensor-split 0.9,0.22,1.0,0.75 \
-fit off \
--host 0.0.0.0 --port 8082 \
--metrics \
--temp 1.0 --top-p 0.95 --top-k 20 --min-p 0.0 --repeat-penalty 1.0 \
--reasoning-format none \
--reasoning-effort low \
--parallel 1 \
-md .../mtp-Qwen3.8-Flash-Next-Q8_0.gguf \
--cache-type-k q8_0 --cache-type-v q8_0 \
--spec-type draft-mtp --spec-draft-n-max 5
Основную часть ускорения дают несколько флагов:
--spec-type draft-mtpвключает черновую голову MTP. Пока апстрим не смёржил эту работу, этот флаг знает только форк.--spec-draft-n-max 5позволяет черновику угадывать до пяти токенов вперёд. С этой моделью доля принятых токенов была достаточно высокой, чтобы пять имело смысл.-md ...указывает на файл-спутник с головой MTP, потому что в основной квант она не входит.--parallel 1обязателен. Есть открытый баг: MTP вместе с несколькими параллельными слотами позволяет содержимому одного разговора просачиваться в другой. Один слот сохраняет вывод чистым.--split-mode layerвместе с--tensor-split 0.9,0.22,1.0,0.75раскладывает слои по четырём неравным картам: трём 3090 на 24 ГБ и одной 3070 Ti на 8 ГБ. Эти пропорции подобраны под конкретный набор, так что под своё железо их, скорее всего, придётся подстроить.-fit offзаставляет llama.cpp загружать ровно то, что вы указали, а не принимать собственные решения о памяти.
Что вы получите
На той машине «до» и «после» выглядели так:
Before MTP After MTP
Decode speed ~30 t/s 60–80 t/s
Model weights same same
Server hardware same same
Quality same same
Total VRAM used 76GB ~80 GB
Разницу скорее ощущаешь, чем измеряешь. При 30 токенах в секунду вы смотрите, как модель печатает. При 60–80 вы с ней разговариваете. Те же веса, та же машина, то же утро, а качество вывода не сдвинулось.
Любая система с более чем 80 ГБ VRAM может запустить ровно эту конфигурацию и должна получить такой же прирост.
Почему это до сих пор не смёржено?
Потому что опенсорс иногда складывается неудачно, даже когда код уже существует.
MTP для Qwen3.8-Flash-Next — не нерешённая исследовательская задача. vLLM и SGLang поддерживали его с первого дня. Форки llama.cpp его поставляют, а Unsloth встраивает его в своё десктопное приложение. Демо-видео с 170 токенами в секунду — настоящие.
В апстрим llama.cpp его пытались внести пять отдельных pull request'ов, и ни один не прошёл. Один висел черновиком с конфликтами слияния и без единого ревью. Другой закрыли после того, как контрибьютор отправил чужой код без разрешения автора. Мейнтейнер пояснил, что указать человека в благодарностях — не то же самое, что получить его согласие, и с тех пор лучшая рабочая реализация осталась бесхозной. Третий, самый чистый из всех, на прошлой неделе закрыл сам автор с сообщением, что в апстриме этим сейчас не занимаются. Застрявший PR #27836 и брошенный PR #28610 можно прочитать самостоятельно.
Вокруг этих PR три сложные проблемы. Первая — спор о формате GGUF: три конкурирующих варианта того, где хранить голову MTP. Вторая — баг корректности с параллельными слотами, который отслеживается в issue #28286. Третья — регрессии на бэкендах: та же функция, что удваивает скорость на CUDA, на Vulkan даёт чистый проигрыш.
Ничто из этого не делает мейнтейнеров злодеями. Их правило — основная ветка никогда не должна ломать чей-то продакшен-сервер, а спекулятивное декодирование на модели с гибридным состоянием — пожалуй, самый пугающий дифф, который им можно прислать. И всё же функция, которая работает, и функция, которая выпущена, — два разных инженерных артефакта, и разрыв между ними измеряется неделями ожидания.
Стоит ли пробовать?
Один вечер сборки сделал модель для ежедневной работы вдвое быстрее, а команды выше — это весь рецепт. Если у вас 80+ ГБ VRAM и Qwen3.8-Flash-Next, которая выдаёт токены по капле, усилия того стоят. Собственное руководство Unsloth по MTP покрывает сторону поставщика.
Догонит ли основная ветка — гадать бесполезно. Предпосылки появляются, формат устаканивается, так что, вероятно, кто-то донесёт правильную реализацию до правильной ветки с благословения её авторов. А до тех пор передний край живёт в форках, так что клонируйте соответственно.