Есть у меня отдельная машина: материнка под Xeon E5-2696 v4 (22 ядра, 44 потока), 128 ГБ DDR3-1600 и видеокарта GTX 1070 на 8 ГБ. Плата с процессором и памятью обошлась примерно в 35 тысяч рублей, карта — ещё 8 тысяч с рук. Итого — тысяч 43 за всё, железо уровня “давно списано из майнинг-фермы и дата-центра”.
На этой штуке я запустил Qwen3.8-Flash-Next — MoE-модель на 125 миллиардов параметров (~6 млрд активных на токен) — и она реально работает. Не “теоретически запускается с квантованием до состояния овоща”, а выдаёт 14-15 токенов в секунду генерации на контексте 24,5К. Честно, не ожидал.
Под катом — как это возможно, какие движки я сравнивал, и что из тюнинга реально дало прирост.
Почему 125B параметров — это не то же самое, что 125B параметров
Если бы это была плотная (dense) модель на 125 миллиардов параметров, разговор был бы коротким: даже в 4-битном кванте это ~65 ГБ только под веса, и при каждом токене нужно пройти через все эти веса. На GTX 1070 с её 8 ГБ VRAM и ~180 ГБ/с пропускной способности это были бы единицы токенов в минуту, не в секунду.
Qwen3.8-Flash-Next — Mixture-of-Experts: 48 слоёв, 512 маршрутизируемых экспертов на слой, но на каждый токен реально активируется лишь малая часть — порядка 6 млрд параметров-эквивалент вычислений. Это меняет всё: основную массу весов (экспертов, которые сейчас не нужны) можно держать в обычной оперативной памяти или вовсе на диске, и подгружать/считать на CPU только то, что реально потребовалось. GPU в этой схеме обслуживает “горячую” часть — attention, несколько всегда-активных слоёв, и кэш самых часто используемых экспертов.
Поэтому ключевой ресурс здесь — не VRAM, а суммарная память (VRAM + RAM) и количество ядер CPU, которые считают офлоаженных экспертов. 128 ГБ DDR3 — медленной, 1600 МГц, без изысков — но её много, и 22 физических ядра есть кому эту арифметику считать параллельно.
Железо и файлы модели
GPU: NVIDIA GeForce GTX 1070, 8 ГБ VRAM — ~8 000 ₽
CPU: Intel Xeon E5-2696 v4, 22 ядра / 44 потока, 2.2 ГГц
RAM: 128 ГБ DDR3-1600 (материнка + CPU + память — ~35 000 ₽ одним комплектом)
Накопители: модель переехала с 4ТБ HDD на системный NVMe — об этом ниже, разница ощутимая
Квантованные файлы модели — порядка 95-98 ГБ на диске (три шарда GGUF в IQ4_XS/UD-IQ4_XS). В 8 ГБ VRAM из этого влезает меньше десятой части — всё остальное читается с диска при старте и живёт в RAM.
Два движка: llama.cpp и Strata
Гонял одну и ту же задачу (MoE-офлоад экспертов на CPU) на двух инференс-движках.
llama.cpp — классика, --n-cpu-moe держит часть слоёв экспертов на CPU, --override-tensor позволяет точечно докинуть на GPU конкретные тензоры сверх этого.
Strata — движок с явным упором на MoE: адаптивный кэш экспертов в VRAM (--expert-cache auto, сам решает, какие эксперты держать на карте по частоте обращения), и спекулятивное декодирование через MTP-драфтер (Multi-Token Prediction) — лёгкий слой предсказывает несколько токенов вперёд, основная модель их пачкой проверяет, из-за чего при удачном совпадении генерация идёт быстрее, чем токен-за-токеном.
Методика
Для каждого размера запроса (1К / 4К / 16К / 24,5К токенов “уже в контексте”) — 3 прогона со случайным текстом-заполнителем и случайным ID в начале (чтобы не словить кэш промпта), генерация 128 токенов, температура 0. Результат — медиана трёх повторов. Контекст — 32768 токенов (упирается в VRAM на этой карте даже с квантованным int8 KV-кэшем). Скрипты и сырые данные — в репозитории, см. ссылку в конце.
Результаты: llama.cpp, подбор офлоада
Сначала — что получилось выжать из llama.cpp. Три шага: все эксперты на CPU → добавить один слой экспертов целиком на GPU (-ncmoe) → докинуть ещё одну матрицу тензора точечно (-ot).
Генерация, ток/с (медиана):
Запрос | все эксперты на CPU | +1 слой на GPU | + точечный тензор |
|---|---|---|---|
1K | 5,1 | 5,6 | 6,2 |
4K | 5,4 | 6,1 | 6,1 |
16K | 4,6 | 5,1 | 5,5 |
24,5K | 4,3 | 5,0 | 5,4 |
Итог — +20-26% к генерации от простейшей раскладки до финальной, бесплатно, просто перераспределением слоёв между GPU и CPU. VRAM в финале — 7666 из 8192 МиБ, то есть упёрлись в карту почти впритык: следующая матрица экспертов (ещё ~425-450 МиБ) уже не влезает без урезания контекста.
Любопытная деталь: лог llama.cpp сам советовал добавить --load-mode none при использовании --override-tensor (типа “так быстрее”). Проверил — это оказалось явной регрессией: prefill упал с 82-99 до 10-29 ток/с. Без mmap операционная система не держит страницы CPU-резидентных весов в файловом кэше, и они перечитываются с диска на каждый токен. Совет из лога, судя по всему, относится к другому сценарию — не доверяйте слепо.
Результаты: Strata — и тут начинается интересное
Та же модель (вариант unsloth UD-IQ4_XS), движок Strata с адаптивным кэшем экспертов и MTP-спекуляцией:
Запрос | llama.cpp, ген. ток/с | Strata, ген. ток/с | Strata, prefill ток/с |
|---|---|---|---|
1K | 6,2 | 15,3 | 237,6 |
4K | 6,1 | 15,3 | 224,9 |
16K | 5,5 | 15,2 | 236,5 |
24,5K | 5,4 | 15,1 | 237,6 |
Эта первая таблица сравнивает разные файлы модели (Uncensored-вариант на llama.cpp, официальный unsloth-квант на Strata) — честно предупреждаю сразу, чтобы не выглядело как жульничество. Дальше разобрался, насколько чистая эта разница на самом деле.
Пересобрал llama.cpp и прогнал на том же файле — и тут выяснилось интересное
Чтобы сравнить движки по-честному, нужен был llama-server, который грузит ровно тот же GGUF, что и Strata. Апстрим llama.cpp эту архитектуру (Qwen3.8 Flash Next) не понимает — но в исходниках Strata есть собственный патченный форк llama.cpp именно под такие модели. Собрал из него llama-server под CUDA 12.6 (ушло на пересборку минут 15; по пути всплыла мелочь — в этом чекауте не хватало каталога веб-морды чата, слепил пустую заглушку, раз нужен был только API) и прогнал на тех же файлах, что грузила Strata.
Тактика с Uncensored-кванта не перенеслась. На нём точечный перенос матрицы blk.46.ffn_down_exps весил ~450 МБ и укладывался в свободные 424-613 МиБ VRAM. На официальном unsloth UD-IQ4_XS та же матрица весит 891 МБ — почти вдвое больше: Unsloth Dynamic Quant держит часть тензоров в повышенной точности ради качества, жертвуя компактностью. Свободных 348 МиБ на такое уже не хватает.
Хуже: даже без точечного переноса, один -ncmoe 47 на этом файле оказался на самой грани VRAM и деградировал с ростом запроса — без перегрева (53°C), без видимого троттлинга, но стабильно медленнее на каждом следующем прогоне:
Запрос | prefill, ток/с | генерация, ток/с |
|---|---|---|
1K | 64,0 | 4,9 |
4K | 79,1 | 4,6 |
16K | 18,1 | 1,6 |
24,5K | 13,0 | 1,5 |
На 24,5К один повтор занимал около 33 минут (на Uncensored-кванте такой же запрос обрабатывался за секунды) — бенчмарк на этом размере пришлось остановить после 2 повторов вместо 3, дальше деградация уже ничего нового не показывала.
Вот теперь сравнение действительно чистое — один и тот же файл модели на обоих движках:
Запрос | llama.cpp ген., ток/с | Strata ген., ток/с | ускорение | llama.cpp prefill | Strata prefill | ускорение |
|---|---|---|---|---|---|---|
1K | 4,9 | 15,3 | ×3,1 | 64,0 | 237,6 | ×3,7 |
4K | 4,6 | 15,3 | ×3,3 | 79,1 | 224,9 | ×2,8 |
16K | 1,6 | 15,2 | ×9,5 | 18,1 | 236,5 | ×13,1 |
24,5K | 1,5 | 15,1 | ×10,1 | 13,0 | 237,6 | ×18,3 |
Разрыв оказался даже больше, чем в первой таблице — но смысл у него другой. Это не “Strata как движок в 3-18 раз быстрее llama.cpp”. Это конкретная находка: выбор кванта решает, останется ли у 8-гигабайтной карты запас VRAM для ручной подгонки llama.cpp вообще. На Uncensored-кванте запас был, и тюнинг дал честные 5,4-6,2 ток/с стабильно на всех размерах. На официальном unsloth-кванте запаса не осталось — и без переподбора конфигурации под конкретный файл llama.cpp на этой карте просто расползается на длинном контексте.
Strata в этом смысле выигрывает не только спекулятивным декодированием (о нём — ниже), а тем, что --expert-cache auto сам считает, сколько экспертов влезет в оставшуюся VRAM, под любой файл, без пересчёта тензоров руками под каждый новый квант. Для человека, который не хочет гонять gguf.GGUFReader каждый раз, когда меняет модель, — это не просто цифры в таблице, а реальная экономия вечера.
Часть разрыва по генерации всё ещё приходится на спекулятивное декодирование: accept rate у MTP-драфтера Strata гулял от 0% до 100% по разным прогонам (медиана 50-65%), llama.cpp в этом тесте спекуляцию не использовал вообще. На малопредсказуемом тексте разрыв по генерации будет меньше, чем в таблице.
Тюнинг на бесплатных флагах
Полез в strata --help в поисках того, что можно включить без риска регрессии — и нашёл два флага чисто про расстановку потоков и памяти, не трогающих арифметику:
--host-core sibling— по умолчанию поток, спинящийся в ожидании GPU-прерываний, занимает отдельное физическое ядро. На HT-процессоре его можно пересадить на второй логический поток той же пары — и отдать освободившееся физическое ядро CPU-пулу экспертов. На 22-ядерном Xeon это 22 воркера вместо 21.--expert-cache-per-layer— по умолчанию слоты VRAM-кэша экспертов раздаются из общего счётчика в порядке появления, из-за чего часть слоёв получает все слоты, часть — почти ничего. Флаг даёт каждому слою свой персональный бюджет.
Результат — стабильный прирост без каких-либо компромиссов:
Запрос | генерация: база → тюнинг | prefill: база → тюнинг |
|---|---|---|
1K | 14,0 → 15,3 (+9%) | 151,4 → 237,6 (+57%) |
4K | 14,1 → 15,3 (+9%) | 214,2 → 224,9 (+5%) |
16K | 14,3 → 15,2 (+6%) | 225,8 → 236,5 (+5%) |
24,5K | 13,9 → 15,1 (+9%) | 228,0 → 237,6 (+4%) |
На коротких запросах эффект на prefill особенно заметный (+57% на 1К) — похоже, там выше доля времени, которое съедает “холодный” проход через CPU-пул, и лишнее ядро там решает больше.
NVMe вместо HDD — не опция, а необходимость
Отдельно стоит сказать про хранение файлов модели. Изначально 95 ГБ лежали на старом 4ТБ HDD — холодная загрузка модели зависла больше чем на 10 минут, пришлось перезагружать машину, не дождавшись. Перенос тех же файлов на системный NVMe — и загрузка укладывается в ~150 секунд (из которых 55 ГБ весов экспертов читаются в RAM со скоростью ~1,2 ГБ/с). Для MoE-моделей с офлоадом в 90+ ГБ это не “было бы неплохо”, а разница между “работает” и “не дождался”.
Что в итоге
Бюджетная связка видеокарта-за-8к + списанный серверный Xeon с кучей медленной DDR3 — рабочий вариант для MoE-моделей таких размеров, если понимать принцип: платите не за то, чтобы весь 125-миллиардный монстр влез в VRAM, а за то, чтобы было, где держать офлоаженных экспертов (много RAM) и кому их считать (много ядер). GPU в этой схеме — ускоритель горячей части, а не обязательное условие “влезет/не влезет”.
Понятно, что 14-15 ток/с — это не скорость топового железа, но для локальной модели на 125B параметров на столе за 43 тысячи рублей — более чем рабочий результат.
Полные данные, сырые JSON/CSV замеров и скрипты, которыми всё это измерялось — в репозитории: github.com/DmNep/llm-bench-1070

