Подписывайтесь:

Блог AST-SoftPro

Qwen3.8-27B на локальном железе: бенчмарк длинного контекста

28.08.2026 9 мин чтения
Qwen3.8-27B на локальном железе: бенчмарк длинного контекста

Зачем бенчить длинный контекст

Когда мы в AST-Soft переводим рабочие сценарии с облачных API на локальные LLM, главный вопрос — не «можно ли запустить 27B-модель на двух RTX 4070», а «как она себя поведёт, когда промпт уедет за 30k токенов». Документы на 60–100k — это повседневность для RAG-ассистента: PDF-книги, репозитории, длинные логи. На коротких контекстах всё летает, а на длинных — графики проваливаются вниз, потому что attention квадратичный.

Мы прогнали одинаковую батарею тестов на двух стендах с Qwen3.8-27B в llama.cpp — Q4_K_M на Ubuntu-сервере с 2×RTX 4070 (24 ГБ VRAM, Xeon E5-2680 v4, 64 ГБ DDR4 ECC) и Q6_K_M на Windows-десктопе с 2×RTX 5060 Ti 16 ГБ (32 ГБ VRAM, i5-12600K, 32 ГБ DDR4). Один и тот же бинарь, одинаковые флаги спекулятивного декодинга, одинаковый набор промптов.

Стенды

ПараметрСтенд A (Ubuntu Server)Стенд B (Windows)
CPU2× Intel Xeon E5-2680 v4 @ 2.40 ГГц, 14c/28t × 2 = 28c/56t, NUMAIntel Core i5-12600K, 10c/16t
RAM64 ГБ DDR4 ECC (4 канала, ~2133 МГц)32 ГБ DDR4
Материнская платаX99 MD8 (серверная, ECC)десктопная LGA1700
GPU2× RTX 4070 12 ГБ, sm_89, без NVLink2× RTX 5060 Ti 16 ГБ, sm_120
VRAM24 ГБ (tensor split)32 ГБ (tensor split)
llama.cppbuild-ubuntu (NCCL off, tensor split, flash-attn on)build-windows (tensor split, flash-attn on)
МодельQwen3.8-27B-Q4_K_M.gguf (17.1 ГБ)Qwen3.8-27B-UD-Q6_K_M.gguf (23.1 ГБ)
KV-cacheq8_0q8_0
СпекуляцияMTP + ngram-mod (драфт-токены со 2 попытками)MTP + ngram-mod
Контекст100 096128 000

Стенд B — две RTX 5060 Ti по 16 ГБ, итого 32 ГБ VRAM, чего хватает под 128k. У стенда A — две карты по 12 ГБ без NVLink, шард через --split-mode tensor, размер контекста упирается в суммарную VRAM (q8 KV-cache на 100k токенов для 27B занимает ~2.6 ГБ). По процессорам: 56 потоков Xeon-сервера vs 16 потоков i5-12600K — для prompt-bound задач (длинный контекст) разница не критична, упираемся в GPU.

Методика

Для каждого размера контекста мы:

  1. Собирали «идеальный» промпт точно на N токенов через /tokenize + /detokenize (важно: 4 символа ≈ 1 токен работает только для английского; кириллица даёт ~2.5 символа на токен, и без пре-токенизации легко уйти в 400 Bad Request).
  2. Отправляли POST /completion с cache_prompt: false — чтобы промпт обрабатывался с нуля, без KV-кеша от прошлых запросов.
  3. Гоняли генерацию 64 токенов (для теста prompt eval) и 256/1024/2048 (для теста спекуляции).

Все числа ниже — из встроенных таймингов llama.cpp (prompt_per_second, predicted_per_second), а не из wall-clock. Это честнее, потому что включает только compute, а не сетевой round-trip.

Откуда TG больше 100 tok/s — про спекулятивный декодинг

Если вы впервые видите 200–420 tok/s у 27B-модели на двух RTX 4070 — реакция «это нереально» правильная. Без спекуляции llama.cpp выдаёт 30–60 tok/s на таком железе. Цифры в таблице ускорены — и вот как именно.

Оба стенда запущены с MTP + ngram-драфтом:

--spec-type draft-mtp,ngram-mod
--spec-draft-n-max 2            # до 2 драфт-токенов за шаг target-модели
--spec-ngram-mod-n-match 24
--spec-ngram-mod-n-min 24
--spec-ngram-mod-n-max 86

Идея: на каждом forward-проходе target-модель проверяет не один свой токен, а пачку драфт-токенов, предложенных MTP-головой и ngram-матчером. Если драфт угадал — токены идут в выход без дополнительных forward-ов. Чем длиннее генерация, тем точнее работает ngram (он смотрит на N=24 предыдущих токенов и ищет повторяющиеся паттерны в собственной истории выхода).

Доказательство — прямо в логе llama.cpp (/tmp/llama-server-q4.log):

task 142  (tg=1024):  draft acceptance = 0.99270,  mean len = 2.98
task 489  (tg=2048):  draft acceptance = 0.99901,  mean len = 60.21
task 7184 (длинная):  draft acceptance = 0.72284,  mean len = 3.36

Строка mean len = 60.21 в task 489 — это средняя длина удачно угаданной драфт-пачки. На пике ngram выдавал по 60 токенов за один шаг target-модели, и target выполнил всего 34 шага вместо 2048. Поэтому 420 tok/s — это темп выдачи токенов клиенту, а не скорость target forward-pass как такового.

В таблице ниже «TG tok/s» означает именно predicted_per_second из ответа llama.cpp — общее число токенов, попавших в predicted_n, делённое на predicted_ms. То есть «сколько токенов в секунду доходит до пользователя», включая драфт-угадывания.

Если нужен «честный» target-only темп, надо гонять тот же бенч с --spec-type none. Тогда 27B-Q4 на 2×4070 выдаёт ~60 tok/s на коротком контексте и проседает до 30 на pp 100k. Разница в 7 раз между 420 и 60 — и есть вклад спекулятивного декодинга.

Prompt Processing: как деградирует скорость

Это главная таблица, ради которой всё затевалось. pp tok/s — сколько токенов промпта модель обрабатывает в секунду. На коротких контекстах она близка к пику пропускной способности attention-слоёв, на длинных падает из-за O(n²).

Промпт (токены)Стенд A (Q4, 2×4070)Стенд B (Q6, 2×5060 Ti)Δ A vs B
512880535+64%
2 0481 023588+74%
8 1921 041628+66%
16 384968631+53%
32 768825600+38%
65 536913531+72%
98 304818477+71%
100 000 (max A)811
120 000 (max B)448

Парадокс: Q4 на двух 4070 обгоняет Q6 на двух 5060 Ti на всех размерах, кроме совсем коротких. На 512 токенов стенд B не успевает даже раскочегариться — MTP-драфт не набрал статистику для ngram-матчинга, генерация всего 64 токена, спекуляция не помогает. На длинных промптах 2×4070 в среднем на 60–70% быстрее по пропускной способности.

Деградация pp на 100k относительно пика (1041 tok/s на 8k):

  • Стенд A: с 1041 до 811 → –22%
  • Стенд B: с 631 до 448 → –29%

Это лучше, чем «идеальные» O(n²)-ожидания — благодаря flash-attn и q8 KV-cache attention реально близок к O(n·√n). Но всё равно ощутимо: 100k промпт на стенде A обрабатывается 130 секунд, на стенде B — 208 секунд на 98k и 272 секунды на 120k.

Генерация: токены в секунду на выходе

Генерация — другая история. Здесь важна per-token latency, и она зависит от того, насколько эффективно работает спекулятивный декодер (MTP + ngram). Чтобы не путать с PP, в этом разделе две отдельные таблицы: сначала tg на длинном промпте (показывает, как растущий KV-cache давит на декод-шаг), потом tg на коротком промпте (показывает, как ngram-драфт набирает статистику).

Генерация на длинном контексте (фиксированный tg=64)

ТестСтенд A tg tok/sСтенд B tg tok/sКомментарий
pp 8k + tg 646274короткая генерация на длинном контексте
pp 32k + tg 646273то же, но KV вдвое больше
pp 65k + tg 647152
pp 100k + tg 6450

Генерация на коротком промпте (фиксированный pp=512)

ТестСтенд A tg tok/sСтенд B tg tok/sКомментарий
pp 512 + tg 256196191короткая генерация, драфт ещё не разогнался
pp 512 + tg 102485300драфт набрал статистику на стенде B; на A — просадка из-за CPU-самплера (CPU fallback на 2×GPU, см. лог)
pp 512 + tg 2048420328пик спекуляции на обоих стендах

Закономерности:

  • Короткая генерация (tg 64–256) на длинном контексте проседает — каждый декод-шаг читает весь KV-кэш, а ngram не успевает накопить достаточно токенов для матчинга (драфт принимает 2–3 токена из 5, средняя длина 2.98 на tg=256).
  • Длинная генерация (tg 1024–2048) — ngram-драфт набирает силу: на 2×5060 Ti видим 300 tok/s уже на 1024 (драфт принимает 6+ токенов за шаг), на 2×4070 пик 420 tok/s на 2048.
  • На tg=1024 картина несимметричная: 5060 Ti — 300 tok/s (драфт хорошо матчит), 4070 — 85 tok/s (CPU-fallback самплера на tensor-split). К tg=2048 стенд A выходит на пик 420 tok/s, обгоняя B (328). В реальном сценарии «впихнуть 200-строчную функцию» обе машины выдают её за 4–5 секунд.

Качество: текст, код, tool calling

Скорость — это полдела. Мы прогнали мини-бенч из 8 кейсов на обоих стендах. Поскольку модель одна и та же, разницы в качестве не ожидалось — её и не оказалось: 7 из 8 PASS на обоих.

КатегорияКейсСтенд AСтенд B
ТекстСтолица Бразилии (одно слово)Бразилиа ✅Бразилиа ✅
ТекстЛогика: 3 яблока, отдал половину → 1.51,5 ✅1,5 ✅
ТекстСуммаризация в ≤20 слов13 слов ✅13 слов ✅
КодPython FizzBuzz 1–15def + for + FizzBuzz ✅
КодJavaScript факториал (стрелочная)❌ нет return❌ нет return
КодSQL: топ-3 клиентов по суммеSELECT + JOIN + ORDER BY + LIMIT 3 ✅
Toolsget_weather(city="Москва")вызвал ✅
Toolssearch_web + (опц.) send_emailвызвал search_web ✅

Единственный общий fail — JS-факториал без явного return. Модель выдаёт n => n * factorial(n-1) в одну строку и полагается на неявный return стрелочной функции без фигурных скобок — а это синтаксически некорректно (нет базового случая, и сама запись не вернёт значение для n=0). В реальной кодогенерации это не проблема: модель даёт рабочий код, если просить «с базовым случаем и без рекурсии в одну строку».

Tool calling работает стабильно: /v1/chat/completions с массивом tools корректно отдаёт tool_calls с правильными аргументами. Это критично для агентских сценариев, и Qwen3.8-27B здесь показывает себя как полноценная замена облачным моделям уровня GPT-4o-mini.

Что мы вынесли

  1. Для 27B-квантовок длинный контекст — это про пропускную способность attention, а не про память. 2×4070 с q8 KV уверенно держит 100k без вылета по VRAM. Спекулятивный декодер MTP + ngram даёт 200–400 tok/s на длинной генерации — это уже «комфортная» скорость для интерактивной работы.
  2. Две карты Ada (sm_89) бьют две карты Blackwell (sm_120) на pp tok/s в широком диапазоне размеров промпта. Не потому что «Ada быстрее», а потому что Q4 на 2×GPU с тензорным шардом — это больше совокупной пропускной способности памяти и более низкие задержки на коротких пайплайнах, чем Q6 на тех же двух GPU.
  3. Качество не деградирует при квантовании Q4_K_M vs Q6_K_M на наших тестах — что логично: разница между Q4 и Q6 на русском/английском тексте и коде минимальна и перекрывается эффектом temperature=0.
  4. Tool calling работает out-of-the-box через OpenAI-совместимый эндпоинт. Это снимает главный барьер для перевода агентов с облака на self-hosted.

Когда что выбирать

  • Стенд A (Q4, 2×4070 + 2×Xeon E5-2680 v4): оптимален для RAG-сценариев с длинными документами (10k–100k промпт) и короткими ответами. Дешевле по железу, быстрее на pp. Хорош как self-hosted в офисном сервере (X99 + б/у Xeon — низкий TCO).
  • Стенд B (Q6, 2×5060 Ti 16 ГБ + i5-12600K): выигрывает на длинной генерации (tg > 1000) и на максимальном контексте 128k. Лучше, если важно качество в маргинальных кейсах (сложная математика, редкие языки). Десктопный форм-фактор с DDR4 и одним CPU проще в обслуживании.

В следующей статье — замеры vLLM с FP8-квантованием и сравнение MTP против чистого ngram-драфта. Если у вас есть свои сценарии, которые вы хотели бы увидеть в бенче — пишите, добавим.

AI-Помощник