Главная / Статьи / Как удвоить скорость декодирования Qwen3.8-Flash-Next с MTP на видеокартах RTX

Как удвоить скорость декодирования Qwen3.8-Flash-Next с MTP на видеокартах RTX

Как собрать форк llama.cpp со спекулятивным декодированием MTP и подобрать флаги, чтобы примерно вдвое ускорить генерацию Qwen3.8-Flash-Next на системах с несколькими RTX.

1248 слов

Многие говорят, что 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 покрывает сторону поставщика.

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