Плотная 27B-модель Qwen3.8 на двух RTX 3090 в llama.cpp из коробки выдаёт 32 токена в секунду. Энтузиаст из Habr показал, что это не предел: те же веса с определёнными флагами запуска разгоняются до 75 ток/с — в 2.3 раза быстрее. Разбираем, какие приёмы для этого нужны и где они ломаются.

Почему 32 ток/с — это не «упёрлись в железо». Плотная модель на каждый токен читает все веса. Файл весит 24.1 ГБ, память 3090 — около 936 ГБ/с. При послойном разбиении карты работают по очереди: 12 ГБ / 936 ГБ/с ≈ 12.9 мс на карту, 25.7 мс на токен, потолок ≈ 39 ток/с. Из коробки — 32.4, то есть 83% от потолка. Сам потолок низкий, и его поднимают две вещи: читать веса на обеих картах одновременно и получать больше одного токена за проход.

РежимГенерация, короткий контекстГенерация @ 130KГенерация @ 240KДва слота параллельноОбработка промпта @ 40KОбработка промпта @ 240KVRAM
-sm layer58.541.434.436.3 + 36.3141076442.4 ГБ
-sm tensor74.758.955.447.4 + 50.3112174343.6 ГБ

Приём 1: MTP-спекулятивное декодирование. В GGUF Qwen3.8 лежит слой Multi-Token Prediction (blk.64.nextn.*) — встроенная draft-модель. По умолчанию llama.cpp его не использует и пишет в лог unused tensor. Флаги: --spec-type draft-mtp --spec-draft-n-max 3 -ctkd q4_0 -ctvd q4_0. Эффект: 32.4 → 58.5 ток/с (послойный режим), ×1.8. Где ломается: KV-кэш черновика — отдельный, -ctk/-ctv на него не действуют; без -ctkd/-ctvd он хранится в f16 и молча ест VRAM. Глубина черновика больше 4 вредит: замеры 2 → 52.8, 3 → 57.3, 4 → 57.6, 6 → 54.3 ток/с. Выигрыш зависит от промпта: на коде и структурированном тексте черновик угадывает чаще, на свободном рассуждении — реже.

Включение MTP-спекулятивного декодирования ускорило генерацию до 58.5 ток/с (×1.8).

Приём 2: тензорный параллелизм. С build 10666 у --split-mode есть значение tensor — тензорный параллелизм через NCCL: обе карты считают каждый слой одновременно, вместо того чтобы работать по очереди. Флаг: -sm tensor. Плюс на контейнере обязательно --ipc=host (или --shm-size). Эффект (обе колонки с MTP, три прогона на точку, разброс ±1 ток/с): -sm layer даёт 58.5 на коротком контексте, -sm tensor — 74.7; на 130K — 41.4 против 58.9; на 240K — 34.4 против 55.4; два слота параллельно — 36.3+36.3 против 47.4+50.3. Обработка промпта при этом замедляется: −20% на 40K, −11% на 130K, −3% на 240K. Для интерактивной работы выгодно; для batch-прогонов коротких промптов — не факт. Где ломается: без --ipc=host первый же decode падает с CUDA error: unhandled system error в ggml_backend_cuda_comm_allreduce_nccl. -ts не работает: llama_params_fit is not implemented for SPLIT_MODE_TENSOR. Автоподбора нет, размер контекста подбирайте руками и проверяйте по nvidia-smi. backend sampling not supported в логе — не ошибка: сэмплирование уезжает на CPU, на скорости не сказалось. -sm row на этой конфигурации не работает вообще: error loading model: device CUDA0 does not support split buffers.

Приёмы 1 и 2 вместе (одни веса, один промпт): без MTP — 32.4 (layer) и 48.2 (tensor); с MTP — 58.5 (layer) и 74.8 (tensor). Итог: 74.8 ток/с на генерации, два запроса по 262 144 токена одновременно, качество на авторских тестах не изменилось.

Приём 3: выбор сборки GGUF. Два файла с одной меткой «Q6» могут быть устроены по-разному, и меньший не обязательно быстрее. Автор сравнил четыре сборки одной модели: unsloth Q8_0, bartowski Q8_0, bartowski Q6_K_L, orcarouter Uncensored (абляция). Качество: все четыре — 13/16 на авторском наборе, провалили одни и те же три задачи. Скорость в один день, одни аргументы: unsloth Q8_0 — 57.6, bartowski Q6_K_L — 60.2 ток/с. Гипотеза: таблицы тензоров двух файлов различаются — у bartowski Q6_K_L 37 типов квантования, у unsloth UD-Q6_K — 3; token_embd / output: Q8_0/Q8_0 против Q6_K/Q8_0. Это дымовой тест, не бенчмарк; perplexity не мерил.

Стенд автора: 2× RTX 3090 (24 ГБ, PCIe 4.0 x16, без NVLink), Threadripper 3960X, 121 ГБ RAM, Ubuntu 26.04, драйвер 595.71.05, llama.cpp — образ ghcr.io/ggml-org/llama.cpp:full-cuda, build 10666. На PCIe x8/x4 all-reduce дороже, результат может быть другим. Для практики: если у вас похожая конфигурация, попробуйте MTP и -sm tensor — это может дать значительный прирост без апгрейда железа.