Всё, что вы хотели знать про локальные LLM, но боялись заинференсить

от автора

Хардкорный NIAH или почему у Gemma 26B память рыбки, какой Qwen лучше, и что показали 8 моделей на RTX 5090

Привет, Хабр.

Меня зовут Артём Хакимов, я работаю в команде GPUGO.

Большая часть наших пользователей, которая не создаёт ИИ-подружек, работает с LLM — кто-то дообучает, кто-то гоняет инференс. И у всех одни и те же вопросы: «что быстрее?», «сколько контекста влезет?», «какой квант брать?».

В какой-то момент надоело отвечать «зависит от…» — сел и прогнал всё сам. Собрал стенд, взял 8 конфигураций моделей, погонял llama-bench на контекстах от 4K до 256K. Но скорость — это половина дела: модель может влезать в 128K и генерировать 200 tok/s, а на деле не найти нужный факт в тексте. Поэтому написал свой NIAH-бенчмарк — не классический «найди одну иголку», а с тройным хопом и пятью дистракторами-ловушками. Подробнее о нём расскажу ниже, пара результатов заставила перепроверить скрипт дважды.

Спойлер: Qwen3.6-27B Dense набрал 98% на NIAH до 128K, обогнав собственную MoE-версию на 35B (что неудивительно, если вы в теме). А Gemma 4, которая идеально работает на коротких контекстах, почему-то на 64K полностью разваливается — 0 из 10.

Что тестируем и зачем

Задача простая: понять, какую модель поставить на RTX 5090 с 32 ГБ VRAM для повседневного инференса. Не абстрактно «лучшую по бенчмаркам Artificial Analysis», а конкретно — что быстрее генерирует, что влезает в память, и что не теряет голову на длинных контекстах.

Мне были интересны три вещи. Во-первых, сырая скорость: сколько токенов в секунду на prompt processing и на генерации. Во-вторых, потолок контекста: на каком размере KV-кэш перестаёт влезать в 32 ГБ. И в-третьих, качество длинного контекста: одно дело влезть в память, другое — реально найти нужную информацию в 128K токенов.

Стенд

Все замеры сделаны на инфраструктуре GPUGO.

  • GPU: NVIDIA RTX 5090, 32 ГБ GDDR7 (Blackwell)

  • CPU: 24 vCPU, 55 ГБ RAM

  • Хранилище: 474 ГБ ADATA LEGEND 960 NVMe

  • Инференс-движок: llama.cpp (сборка b9128, CUDA arch sm_120)

  • Бенчмарк скорости: llama-bench с flash attention, batch 2048, ubatch 512

  • Бенчмарк качества: собственный NIAH v3 (triple-hop + 5 distractors)

  • Кванты: Q4_K_M и UD-Q4_K_XL от Unsloth

Участники

Восемь конфигураций — пять моделей в двух квантах:

Модель

Архитектура

Параметры

Активные

Квант

Qwen3.6-35B-A3B

MoE

35B

3B

Q4_K_M

Qwen3.6-27B

Dense

27B

27B

Q4_K_M

Qwen3.6-27B

Dense

27B

27B

UD-Q4_K_XL

Gemma 4 26B-A4B

MoE

26B

4B

UD-Q4_K_XL

Gemma 4 26B-A4B

MoE

26B

4B

UD-Q4_K_M

Mistral Small 3.1 24B

Dense

24B

24B

Q4_K_M

Mistral Small 3.1 24B

Dense

24B

24B

UD-Q4_K_XL

Devstral Small 2 24B

Dense

24B

24B

Q4_K_M

Devstral Small 2 24B

Dense

24B

24B

UD-Q4_K_XL

Devstral — это файнтюн Mistral Small 3.1 под код и инструкции. Архитектура та же, веса другие. Интересно посмотреть, что это меняет.

DeepSeek-R1-Distill-Qwen-32B тоже участвовал, но на текущем стенде показал ~6 tok/s на генерации и зависания на 16K — в итоговую таблицу не попал. В принципе вот и все, можем приступать.

Часть 1. Скорость: llama-bench

Методика: llama-bench с --flash-attn 1, prompt processing на 4K/16K/32K токенов, генерация 256 токенов. По 1–3 повтора на каждый замер. Знаю, знаю — llama-bench прогнал уже каждый второй автор на Хабре, и вы наверняка видели эти таблицы сто раз. Но хочется иметь честные цифры с одного стенда, одного билда llama.cpp, в одинаковых условиях для всех моделей, иначе сравнивать бессмысленно.

MoE: Qwen3.6-35B-A3B и Gemma 4

Небольшая техническая справка, чтобы дальше все были на одной волне. MoE-модели устроены хитро: параметров в них десятки миллиардов, но при генерации каждого токена работает только маленькая часть, так называемые «активные эксперты». У Qwen это 3B из 35B, у Gemma 4B из 26B. Остальные просто лежат в памяти и ждут своей очереди. Поэтому decode у MoE летит: GPU считает мало, а результат на уровне большой модели (почти).

Модель

PP 4K

PP 16K

PP 32K

PP 128K

TG 256

VRAM 32K

Qwen3.6-35B-A3B Q4_K_M

9 085

8 647

8 171

5 735

274.8

21.8 ГБ

Gemma 4 UD-Q4_K_XL

12 300

11 362

10 175

5 465

222.5

18.3 ГБ

Gemma 4 UD-Q4_K_M

12 360

11 399

10 189

5 449

223.1

18.3 ГБ

Главное из таблицы: Qwen чемпион по decode (~275 tok/s), Gemma по prompt processing (~12 300 tok/s на 4K). Логично: у Qwen меньше активных параметров (3B vs 4B), поэтому генерация быстрее. Gemma эффективнее обрабатывает входной текст, на коротких промптах она в полтора раза быстрее Qwen.

Тут интересный момент — 275 tok/s на генерации это примерно 4 слова в секунду на русском, то есть текст появляется быстрее, чем большинство людей успевает читать. Возникает вопрос: а нужно ли вообще гнаться за скоростью decode дальше этой отметки? Наверное, для чатбота уже нет. А вот для API, где параллельно висят несколько запросов, каждый лишний tok/s экономит какие-никакие деньги.

На практике это определяет сценарий использования. Чатбот, стриминг ответа, интерактивный помощник? Decode важнее, Qwen выигрывает. Быстро переварить длинный документ и выдать короткое резюме? PP важнее, Gemma впереди.

Ещё один плюс Gemma: экономность по памяти. 18 ГБ VRAM на 32K контексте против 22 ГБ у Qwen. Благодаря этому она влезает в 256K контекст при 23 ГБ, самый длинный рабочий контекст на нашем стенде. Правда, как покажет NIAH дальше, «влезть» и «работать» это разные вещи.

Между двумя квантами Gemma (UD-Q4_K_XL и UD-Q4_K_M) разница в пределах шума, можно брать любой.

Dense: Qwen3.6-27B, Mistral Small 3.1, Devstral Small 2

Ещё одна техническая справка. Dense-модели устроены проще: никаких экспертов, все параметры работают на каждый токен. Это обычно даёт лучшее качество на единицу параметров, но за это приходится платить скоростью. GPU вынужден прочитать все 24–27 миллиардов весов для каждого нового токена, и decode почти всегда упирается в пропускную способность памяти.

Модель

PP 4K

PP 16K

PP 32K

TG 256

VRAM 32K

Qwen3.6-27B Q4_K_M

3 804

3 531

3 169

76.5

18.7 ГБ

Qwen3.6-27B UD-Q4_K_XL

3 742

3 482

3 151

73.8

19.5 ГБ

Mistral Small 3.1 Q4_K_M

4 817

4 343

3 809

101.5

19.3 ГБ

Mistral Small 3.1 UD-Q4_K_XL

4 967

4 413

3 824

100.6

19.5 ГБ

Devstral Small 2 Q4_K_M

5 035

4 459

3 836

96.9

19.3 ГБ

Devstral Small 2 UD-Q4_K_XL

5 059

4 474

3 845

96.1

19.5 ГБ

Тут всё предсказуемо. Mistral и Devstral близнецы по скорости (~100 tok/s), потому что Devstral это файнтюн Mistral на том же скелете. Qwen3.6-27B медленнее (~76 tok/s) просто потому что тяжелее: 27B против 24B, больше весов читать на каждый токен.

А вот что првда порадовало: Q4_K_M и UD-Q4_K_XL дают одинаковую скорость на всех Dense-моделях. Разница 1–3%, статистический шум. UD-квант чуть тяжелее по памяти (на 100–750 МБ), но по скорости полный паритет. Ребята из Unsloth сделали крутую работу и их динамическая квантизация даёт лучшее качество весов, и при этом не просаживает скорость. Однозначная доминация во всех смыслах, берем UD-квант и не думаем о компромисах.

256K — потолок

Обе MoE-модели влезли в 256K на 32 ГБ VRAM, но с ньюансами. Gemma экономнее: 23.1 ГБ против 26.6 ГБ у Qwen. У Qwen на 256K остаётся всего ~5 ГБ запаса, так что в реальном сценарии с системным промптом и длинной генерацией можно и упереться.

Модель

Контекст

PP tok/s

TG tok/s

VRAM

Gemma 4 UD-Q4_K_XL

128K

5 465

222.7

20.3 ГБ

Gemma 4 UD-Q4_K_XL

256K

3 454

223.5

23.1 ГБ

Qwen3.6-35B-A3B Q4_K_M

128K

5 735

273.1

23.7 ГБ

Qwen3.6-35B-A3B Q4_K_M

256K

3 980

272.3

26.6 ГБ

Интересное наблюдение: decode практически не деградирует от длины контекста. Обе модели на 256K генерируют с той же скоростью, что и на 4K, как будто им всё равно, сколько текста было до этого. Просадка идёт только в prompt processing: переварить 256K входного текста занимает в ~3.5 раза больше времени, чем 4K. Это ожидаемо, PP линейно зависит от длины промпта.

Mistral и Devstral в этот клуб не попали: при 128K KV-кэш вместе с весами перестаёт влезать в 32 ГБ. Их потолок 64K.

Часть 2. Качество длинного контекста: NIAH v3

Скорость это полдела. Модель может влезать в 128K и генерировать 200 tok/s, но если она не может найти нужный факт в тексте, толку от этого мало. А сценариев, где это критично, полно: суммаризация длинных документов, code review на тысячи строк, RAG по корпоративной базе знаний, анализ логов. Везде, где модель должна не просто принять длинный вход, а реально в нём разобраться.

Классический способ это проверить называется Needle In A Haystack (NIAH). Идея простая: прячем факт («иголку») в длинный текст («стог сена») и спрашиваем модель, может ли она его найти. Тест появился пару лет назад и быстро стал стандартом для оценки длинного контекста. Проблема в том, что стандартный NIAH сегодня слишком прост. Одна иголка, один стог, прямой вопрос. Все современные модели щёлкают его на 100%.

Поэтому я написал свой вариант, NIAH v3: три связанных факта (тройной хоп) и пять дистракторов-ловушек, специально сконструированных чтобы сбить модель с толку.

Как устроен тест

Три факта образуют цепочку, которую нужно восстановить:

  • Fact A (фиксирован на ~10% документа): «Employee ID E-4821 is the project lead for the Orion initiative.»

  • Fact B (перемещается от 5% до 95% — это ось X на тепловых картах): «The Orion initiative is supervised by Division DELTA-9.»

  • Fact C (фиксирован на ~85%): «Division DELTA-9 has an annual operational budget of $2.4 million.»

Чтобы ответить правильно, модель должна пройти три хопа: E-4821 → Orion → DELTA-9 → $2.4M. Но это ещё не всё. В документ вшиты пять дистракторов — ложные факты, специально сконструированные чтобы сбить модель:

  • Ложный сотрудник E-4822 ведёт проект Titan (не Orion)

  • Titan контролируется дивизионом GAMMA-7 (не DELTA-9)

  • У GAMMA-7 бюджет $3.7M (главная ловушка — та же структура фразы, что и правильный ответ)

  • У Orion есть supplemental funding $5.1M (правильная сущность, неправильный тип бюджета)

  • У DELTA-9 есть capital expenditure reserve $8.6M (правильный дивизион, неправильный тип бюджета)

Вопрос: «What is the annual operational budget of the division that supervises the project led by employee E-4821?»

Правильный ответ: $2.4 million. Все остальные числа — ловушки.

Тест прогоняется на контекстах 4K / 8K / 16K / 32K / 64K / 128K, по 10 позиций Fact B на каждом. Итого 60 запросов на модель (или 50 для Mistral/Devstral, у которых 128K не влезает).

Результаты

Модель

4K

8K

16K

32K

64K

128K

Итого

Qwen3.6-27B Q4_K_M

10/10

10/10

10/10

10/10

10/10

9/10

98%

Qwen3.6-27B UD-Q4_K_XL

10/10

10/10

10/10

10/10

10/10

9/10

98%

Devstral-Small-2 UD-Q4_K_XL

8/10

10/10

9/10

9/10

6/10

84%

Qwen3.6-35B-A3B Q4_K_M

9/10

10/10

6/10

10/10

9/10

5/10

82%

Devstral-Small-2 Q4_K_M

8/10

9/10

9/10

9/10

5/10

80%

Mistral-Small-3.1 UD-Q4_K_XL

7/10

8/10

10/10

3/10

2/10

60%

Mistral-Small-3.1 Q4_K_M

6/10

8/10

9/10

2/10

2/10

54%

Gemma4-26B UD-Q4_K_XL

10/10

10/10

6/10

4/10

0/10

0/10

50%

Тепловые карты NIAH v3 по всем моделям

Recall vs Context Length — все модели

Что тут произошло

Qwen3.6-27B набрал 98% на всём диапазоне, включая 128K. Из 60 запросов ошибся ровно один раз, на 128K при глубине 55%. Посмотрите на тепловую карту: сплошной зелёный ковёр с одним красным пикселем. Для Dense-модели на Q4 кванте это выдающийся результат. Мы специально прогнали оба кванта (Q4_K_M и UD-Q4_K_XL) и получили одинаковые 98%. Это значит, что рассуждение у модели не ломается от квантования: цепочка «найти сотрудника → определить проект → найти дивизион → вытащить бюджет» работает одинаково надёжно в обоих случаях. Для практики это важно: можно смело квантовать и не бояться и в RAG сценариях.

NIAH: Qwen3.6-27B Q4_K_M

Qwen3.6-35B-A3B — нестабилен. 82% — это хуже, чем у его Dense-собрата на 27B. MoE-версия проваливается на 16K (6/10) и на 128K (5/10), хотя на 32K и 64K восстанавливается. Видно, что при определённых позициях иголки в середине документа MoE-роутинг теряет нить рассуждений.

NIAH: Qwen3.6-35B-A3B Q4_K_M

Gemma 4 — поразительная деградация. На 4K и 8K — идеально, 10/10. На 16K начинается провал. На 64K и 128K — ноль из десяти. Абсолютный ноль. При этом по скорости Gemma прекрасно работает на 128K и даже на 256K. Но толку нет: карта зелёная на коротких контекстах и сплошь красная на длинных.

NIAH: Gemma4-26B UD-Q4_K_XL

Что именно идёт не так? Модели ловятся на дистракторы. Вместо правильных $2.4M они отвечают $3.7M (пошли по ложной цепочке E-4822 → Titan → GAMMA-7) или $8.6M (нашли правильный дивизион DELTA-9, но схватили capital expenditure вместо operational budget). И что характерно, отвечают уверенно, в правильном формате, без оговорок. Просто с неправильным числом. Модель не «сломалась», она честно рассуждала, но по неправильным фактам. Это самый коварный тип ошибки: на вид всё выглядит убедительно, и без проверки вы бы не заметили.

Отдельно интересно сравнение Devstral и Mistral. Одна архитектура, одинаковый размер, одинаковая скорость в llama-bench. Но Devstral набрал 80–84%, а Mistral 54–60%. Откуда разница? Devstral это файнтюн Mistral Small 3.1, который в Mistral AI дообучали специально под агентные задачи. По их статье, процесс выглядел так: сначала SFT на синтетических агентных траекториях (модель учится работать с bash, редактировать файлы, следовать многошаговым инструкциям), потом дополнительные раунды оптимизации политики с повторными прогонами. Плюс обучение на нескольких форматах взаимодействия (XML и нативный function calling), чтобы модель не привязывалась к одному формату. Результат виден на тепловых картах: Devstral ровнее держит точность по всему диапазону контекстов, Mistral начинает сыпаться с 32K. Агентный файнтюн, похоже, научил модель аккуратнее следить за цепочкой рассуждений, и это помогает не только в коде.

NIAH: Devstral-Small-2 UD-Q4_K_XL

NIAH: Mistral-Small-3.1 Q4_K_M

Часть 3. Q4_K_M vs UD-Q4_K_XL — есть ли разница?

Один из частых вопросов: стоит ли брать динамический квант UD-Q4_K_XL от Unsloth вместо стандартного Q4_K_M?

По скорости разницы нет. Вообще. Ни на одной модели decode не изменился значимо. Prompt processing тоже в пределах шума. VRAM чуть выше, на 100–750 МБ.

По качеству NIAH тоже нет разницы: оба кванта Qwen3.6-27B набрали 98%, оба кванта Mistral около 55–60%, оба кванта Devstral 80–84%.

Честно говоря, я ожидал увидеть хоть какой-то разрыв. Не увидел. Но тут важно понимать: наш NIAH проверяет retrieval и многошаговое рассуждение, а не тонкости генерации. Вполне возможно, что разница между квантами вылезет на задачах, где важна точность формулировок: сложная математика, генерация кода, нюансы перевода. Там perplexity решает, и UD-Q4_K_XL теоретически должен быть ближе к оригиналу. Но для этого нужен другой бенчмарк, и это тема для отдельного поста.

Часть 4. MTP — Multi-Token Prediction

Обычная генерация работает так: один проход через модель — один токен. MTP (Multi-Token Prediction) меняет правила: модель обучена предсказывать сразу несколько следующих токенов за один проход. Qwen3.6 обучена с 3 шагами MTP — значит, каждый проход пытается выдать 4 токена. Если «черновики» совпали с тем, что модель выдала бы обычным способом — получаем 4 токена за цену одного. Если нет — откатываемся. Чем выше acceptance rate, тем больше выигрыш.

В основной ветке llama.cpp MTP пока не поддерживается. Мы собирали из открытого PR #22673. MTP-слои весят мало (Q8_0, крошечная часть модели) и почти не добавляют VRAM. Главный вопрос — насколько это ускоряет генерацию на практике.

Наши замеры

Модель

Режим

Prompt t/s

Gen t/s

Qwen3.6-35B-A3B

без MTP

251.5

236.1

Qwen3.6-35B-A3B

draft-mtp

196.2

197.6

Qwen3.6-27B

без MTP

241.9

73.0

Qwen3.6-27B

draft-mtp, n_max=3

208.3

92.6

На MoE-модели (35B-A3B) MTP навредил. MoE и так летит на decode за счёт малого числа активных параметров, и overhead от спекуляции съедает весь выигрыш. Модель тратит время на проверку черновиков, которые ей не нужны.

На Dense-модели (27B) мы получили +27%, с 73 до 92.6 tok/s. Неплохо, но далеко от теоретических 2–3x. Наш тест был коротким smoke-замером: один промпт, ~220 токенов генерации. На коротких ответах acceptance rate нестабилен, MTP просто не успевает раскрыться.

В комьюнити результаты интереснее. Пользователь на Reddit собрал GGUF Qwen3.6-27B с прикрученными MTP-слоями (UD-Q4_K_XL база + Q8 MTP) и получил ~2.08x ускорение на наборе из 9 промптов, а на задачах с кодом и математикой acceptance rate доходил до 87–89%, что давало 2.4–2.5x. Там же arkste завернул llama.cpp с MTP в Docker-образ (arkste/llama-swap-mtp:sm120) и показал похожие цифры на Q6_K_XL кванте: 111 tok/s с MTP против 53 без. Правда, у него квант тяжелее нашего, поэтому baseline ниже, зато MTP-слоям проще предсказывать.

А если смотреть шире, llama.cpp с MTP это ещё не потолок для RTX 5090. Есть vLLM с NVFP4 квантизацией, которая использует нативные FP4 тензорные ядра Blackwell. На Qwen3.5-27B люди получают ~5 300 tok/s на prefill и ~80 tok/s на decode при 200K контексте, а на MoE-версии через GPTQ-Int4 в vLLM 194–197 tok/s. Мы пока работаем в llama.cpp и GGUF-экосистеме, но тема vLLM + Blackwell-native кванты заслуживает отдельного исследования.

Сводная таблица

Модель

Тип

TG tok/s

VRAM 32K

Max ctx

NIAH v3

Qwen3.6-35B-A3B Q4_K_M

MoE

274.8

21.8 ГБ

256K

82%

Gemma 4 26B UD-Q4_K_XL

MoE

222.5

18.3 ГБ

256K

50%

Mistral Small 3.1 Q4_K_M

Dense

101.5

19.3 ГБ

64K

54%

Devstral Small 2 Q4_K_M

Dense

96.9

19.3 ГБ

64K

80%

Qwen3.6-27B Q4_K_M

Dense

76.5

18.7 ГБ

128K+

98%

Выводы

Если по-честному, нет одной «лучшей» модели. Есть три сценария — и для каждого свой выбор.

Нужна максимальная скорость — Qwen3.6-35B-A3B. 275 tok/s, это ~4 слова в секунду для русского текста. Для чатбота, для API, для потокового вывода — хватает с запасом.

Нужен длинный контекст с надёжным качеством — Qwen3.6-27B. Да, 76 tok/s — это в 3.6 раза медленнее MoE-версии. Но 98% на NIAH до 128K — это уровень, которого ни одна другая модель в тесте не показала. Для RAG, для суммаризации документов, для code review длинных файлов — это правильный выбор.

Нужно что-то для кода — Devstral Small 2. 97 tok/s, файнтюн под инструкции, 84% на NIAH. Не рекорд ни по одному параметру, но крепкий середнячок без провалов.

Gemma 4 — красивая и быстрая, но с критическим ограничением: не использовать на контекстах больше 8K, если accuracy важна. На коротких задачах — прекрасна.


Сайт компании: https://gpugo.ru/

Наш телеграм-канал: https://t.me/GpuGo

ссылка на оригинал статьи https://habr.com/ru/articles/1056192/