Почему MoE-модель тормозит? Чек-лист из трех шагов. artificial intelligence.. artificial intelligence. linux.. artificial intelligence. linux. mixture of experts.. artificial intelligence. linux. mixture of experts. moe.. artificial intelligence. linux. mixture of experts. moe. Open source.. artificial intelligence. linux. mixture of experts. moe. Open source. искусственный интеллект.. artificial intelligence. linux. mixture of experts. moe. Open source. искусственный интеллект. траблшутинг.
Автор перевода и адаптации статьи - Данил Г., Python-разработчик

Автор перевода и адаптации статьи – Данил Г., Python-разработчик

Для кого эта статья

Вы запускаете большую MoE‑модель (например, Qwen, DeepSeek, Mixtral) на локальной машине через llama.cpp / llama-server, но скорость генерации токенов в разы ниже ожидаемой. Статья — для Linux-пользователей с гибридными процессорами Intel 12-го поколения и новее и видеокартами NVIDIA (CUDA). Конфигурация: RTX 4070 (12 ГБ), i5-12600K, 32 ГБ DDR5-6000.

Статья написана по мотивам публикации “Local LLM Inference Optimization: The Complete Guide” и является частью цикла статьей по траблшутингу и оптимизации работы языковой модели на локальных машинах.

Глоссарий

Термин

Определение

MoE (Mixture of Experts)

Архитектура, заменяющая плотные слои в нейросети набором «экспертов» (FFN). Маршрутизатор направляет каждый токен только к нескольким лучшим экспертам. Подробнее

E-core (Efficient-core)

Энергоэффективные ядра в гибридных процессорах Intel (Alder Lake и новее). Имеют упрощенную архитектуру, низкую тактовую частоту, предназначены для фоновых задач. Подробнее

P-core (Performance-core)

Высокопроизводительные ядра Intel для тяжелых вычислений.

XMP (Extreme Memory Profile)

Технология Intel для разгона оперативной памяти. Подробнее

FFN (Feed-Forward Network)

feed-forward подсеть внутри блока трансформера, обычно состоящая из нескольких линейных преобразований и функции активации

KV-кэш

Кэш Key/Value для уже обработанных токенов; его размер растёт примерно линейно с длиной контекста. При включённом GPU KV-offload, который в llama.cpp используется по умолчанию, он занимает память GPU; KV можно оставить в RAM через --no-kv-offload

CUDA OOM (Out Of Memory)

Ошибка нехватки GPU-памяти, которая может возникнуть как при загрузке модели, так и позже при выделении KV-кэша и других runtime-буферов.

llama-fit-params

Утилита llama.cpp, которая рассчитывает параметры размещения, с которыми прогнозируемое потребление памяти укладывается в доступный бюджет.

taskset

Команда Linux для привязки процессов к конкретным ядрам CPU.

Короткий ответ

С высокой долей вероятности проблема в одном из следующих мест:

  1. Скорость оперативной памяти (RAM) — не включен XMP/EXPO.

  2. Неоптимальное размещение слоев модели между GPU и CPU.

  3. Использование E‑ядер на гибридном процессоре Intel (только Linux).

Ниже — пошаговый алгоритм диагностики. Все команды и цифры — из опыта автора оригинальной статьи (https://carteakey.dev/blog/local-inference/local-llm-optimization/) на стенде с RTX 4070 (12 ГБ), i5-12600K, DDR5-6000, Linux и CUDA. Объём RAM на стенде менялся между отдельными экспериментами, поэтому приведённые результаты не следует воспринимать как измерения одной неизменной конфигурации.

Шаг 1. Проверьте скорость оперативной памяти

Иерархия памяти: скорость генерации токенов ограничена тем, как быстро исполняющая среда может прокачивать активные веса через иерархию памяти.

Примерные цифры пропускной способности:

  • RTX 4070 — 504 ГБ/с теоретической пропускной способности VRAM.

  • Dual‑channel DDR5-6000 — 96 ГБ/с теоретического пика, реальная пропускная способность RAM ниже.

Почему это критично для MoE:

MoE-модели часто подгружают веса экспертов из системной оперативной памяти (RAM), что делает критически важной пропускную способность памяти. На стенде автора исходного исследования включение XMP восстановило скорость генерации MoE примерно с одной трети от нормального уровня.

Что делать

Запустите в терминале:

sudo dmidecode -t memory | grep -E "Speed|Configured"

Ожидаемый результат: Сравните Configured Memory Speed с ожидаемой скоростью установленного комплекта памяти. Если память поддерживает XMP, а профиль не активирован, его можно включить в BIOS при поддержке со стороны материнской платы и процессора. После включения проверьте стабильность системы.

Если после проверки памяти скорость все еще низкая — переходим к шагу 2.

Шаг 2. Размещение слоев (основная оптимизация для MoE)

Для гибридных конфигураций MoE размещение слоев — именно здесь кроется большая часть производительности. Цель: держать как можно больше блоков на GPU (особенно ранние слои и внимание), выгружая веса экспертов в RAM.

Важно: параметры ручного размещения ниже не обязательно взаимоисключающие. Например, --n-gpu-layers и --override-tensor могут использоваться вместе: первый задаёт общее размещение слоёв, а второй переопределяет размещение отдельных групп тензоров.

При этом есть разные режимы настройки:

  • Ручной (пункты 2.1–2.3) — вы сами задаёте параметры размещения и при необходимости комбинируете их.

  • Автоматический (пункт 2.4) — --fit подбирает не заданные пользователем параметры, чтобы модель укладывалась в доступную память.

  • Расчёт параметров заранее (пункт 2.5) — llama-fit-params использует тот же механизм подбора, но вместо запуска сервера выводит рассчитанный набор параметров, например комбинацию -ngl и -ot, которую затем можно сохранить в скрипте запуска.

Если вы явно задаёте параметры, влияющие на размещение модели, например --n-gpu-layers, --tensor-split или --override-tensor, автоматический fitter не переопределяет это размещение. Поэтому для автоматического подбора не следует одновременно фиксировать эти параметры вручную.

2.1. Ручной способ: –n-gpu-layers (базовый)

Сколько блоков трансформера загружать на GPU.

# 37 на GPU, остальные на CPU
llama-server -m model.gguf --n-gpu-layers all
llama-server -m model.gguf --n-gpu-layers 37   

Начните с --n-gpu-layers all (все слои). Уменьшайте, если возникает ошибка CUDA OOM.

2.2. Ручной способ: –n-cpu-moe (грубое управление)

Целое число: оставить указанное количество слоев MoE с весами экспертов на CPU.

--n-cpu-moe 31   # первые 31 слой экспертов MoE на CPU

Предостережение: учитывайте ограничение по RAM. Если размер модели ~60 ГБ, и вы пытаетесь разместить всех экспертов на CPU, система попытается загрузить все эти ~60 ГБ в оперативную память. На компьютере с 64 ГБ RAM это приведет к аварийному завершению работы системы. Всегда заранее используйте llama-fit-params (пункт 2.5), чтобы подобрать безопасные значения.

2.3. Ручной способ: –override-tensor (точное управление)

--override-tensor позволяет вручную задавать, где будут размещаться отдельные группы тензоров модели — например, на CPU или GPU. Это дает более точный контроль над использованием VRAM, чем перенос целых слоев.

Будьте внимательны с shared experts:

В некоторых MoE-моделях, например Qwen3.5 MoE, кроме маршрутизируемых экспертов есть отдельный общий эксперт (shared expert). В llama.cpp их основные FFN-тензоры имеют разные имена:

  • Маршрутизируемые эксперты: ffn_*_exps

  • Общий эксперт (всегда активен, 1 на слой): ffn_*_shexp

Кроме того, в некоторых моделях gate и up объединены в тензор ffn_gate_up_exps.

Поэтому шаблон, который переносит на CPU только _exps, не затронет _shexp. Shared expert останется на GPU и продолжит занимать VRAM.

Например, чтобы перенести на CPU основные FFN-веса как маршрутизируемых, так и shared experts:

--override-tensor '.*.ffn_(up|down|gate|gate_up)_(exps|shexp)..*=CPU'

Здесь:

  • (up|down|gate|gate_up) охватывает основные варианты имен FFN-тензоров;

  • (exps|shexp) охватывает маршрутизируемых и shared experts.

Если нужно оставить экспертов первых пяти блоков (0–4) на GPU, а начиная с блока 5 перенести их на CPU:

--override-tensor 'blk.([5-9]|[1-9][0-9]+).ffn_(up|down|gate|gate_up)_(exps|shexp)..*=CPU'

Имена тензоров зависят от архитектуры модели и версии llama.cpp, поэтому перед использованием сложных правил стоит проверить структуру конкретного GGUF. Не воспринимайте приведенный шаблон как универсальный для любой MoE-модели.

--override-tensor дает максимальную гибкость при распределении весов между CPU и GPU, но требует внимательной проверки того, какие именно тензоры попали под регулярное выражение.

2.4. Автоматический подбор размещения: –fit

В актуальных версиях llama.cpp автоматический fitter включён по умолчанию: --fit on. Он подбирает незаданные пользователем параметры размещения так, чтобы модель укладывалась в доступную память устройства.

По умолчанию llama.cpp оставляет запас в 1024 MiB на каждом устройстве (--fit-target 1024). Параметр --fit-ctx задаёт минимальный размер контекста, до которого fitter может уменьшить автоматически выбираемый context при нехватке памяти. Значение по умолчанию — 4096 токенов.

Если сервер должен работать с контекстом, например, 65 536 токенов, задайте его явно:

llama-server 
  -m model.gguf 
  --ctx-size 65536  
  --fit-target 1024

В этом случае fitter будет подбирать остальные незаданные параметры размещения, не уменьшая явно заданный размер контекста.

Если же размер контекста оставлен автоматическим, но вы не хотите, чтобы fitter уменьшал его ниже 65 536 токенов, используется:

llama-server 
  -m model.gguf 
  --fit-ctx 65536 
  --fit-target 1024

--fit-target задаёт запас памяти, который fitter старается оставить свободным. Значение 1024 MB является текущим значением по умолчанию и разумной отправной точкой.

Расчёт fitter’а следует воспринимать как оценку, а не гарантию отсутствия OOM. После подбора параметров проверьте сервер на целевой длине контекста и типичной рабочей нагрузке.

На конкретной конфигурации запас можно уменьшить, например до 512 MB, чтобы освободить больше VRAM под модель:

--fit-target 512

Но такое значение стоит рассматривать как более агрессивную настройку и проверять на длинном контексте и реальной нагрузке: слишком маленький запас повышает риск OOM при дальнейшем росте потребления памяти.

Примечание: исходное исследование и приведённые в статье измерения относятся к конфигурации с NVIDIA GPU и CUDA.

2.5. Расчёт параметров заранее: llama-fit-params

Если автоматическое размещение вас устраивает, его можно рассчитать отдельно и сохранить полученные параметры в скрипте запуска:

# -fitt: запас памяти в MiB
# -fitc: минимальный context, который fitter может выбрать
llama-fit-params 
  -m /path/to/model.gguf 
  -fitt 1024 
  -fitc 65536              

# Вывод примерно такой:
# -c 65536 -ngl 49 -ot "blk.8.ffn_...=CPU,..."

После проверки стабильности запас можно уменьшать экспериментально, например до 512 MB, если это позволяет разместить больше весов на GPU.

Этот вывод — то, что вы закрепляете для статического размещения.

Сравнение способов размещения

Критерий

--fit (автоматический, включён по умолчанию)

Жестко заданные -ngl + -ot (фиксированный)

Запуск

Задержка на зондирование (обычно 1–5 сек, для очень больших моделей — до 10+ сек)

Мгновенный

Размещение

Заново при каждой загрузке

Фиксированное

Усилия по настройке

Не требуются

Одноразовые (выполнить llama-fit-params)

Лучше всего подходит для

Экспериментов

Продакшена

Воспроизводимость

Хорошая (если VRAM свободна)

Детерминированная

Рекомендация: для стабильного домашнего сервера получите размещение один раз через llama-fit-params и зафиксируйте его в скрипте запуска. Автоматический fitter удобно использовать при тестировании новых моделей или после изменений в оборудовании. В актуальных версиях llama.cpp он включён по умолчанию.

Шаг 3. Закрепление на P‑ядрах (только для Intel 12-го поколения и новее, только Linux)

Если вы на Windows — этот шаг пропустите (но учитывайте, что на Windows производительность в среднем на 15–20% ниже по данным бенчмарков автора). Если вы используете Linux и гибридный процессор Intel, имеет смысл отдельно сравнить обычный запуск с запуском только на P-ядрах. На стенде автора с i5-12600K исключение E-ядер из набора inference-потоков повысило скорость генерации на 20–30% в тех профилях, где CPU был узким местом. На другой конфигурации эффект может отличаться.

Самый надежный способ удержать инференс от использования E‑ядер:

taskset -c 0-11 llama-server ...   # закрепить все потоки процесса на P‑ядрах

Как определить диапазон P‑ядер:

  • Проверьте через lscpu -e или lstopo.

  • На Intel 12600K ядра 0–11 (6 P‑ядер × 2 потока) — P‑ядра; 12–15 — E‑ядра.

E‑ядра снижают скорость генерации (токенов/с) на 20–30%, даже если вы не замечаете этого в бенчмарках.

Итоговый чек-лист (для быстрой проверки)

Действие

Команда / проверка

Ожидаемый результат

1

Проверить скорость RAM

sudo dmidecode -t memory | grep -E "Speed|Configured"

Память работает с ожидаемой скоростью

2

Включить XMP/EXPO в BIOS

Зайти в BIOS при загрузке

XMP-профиль применён, система стабильна

3

Проверить автоматическое размещение

llama-server -m model.gguf --ctx-size 65536 --fit-target 1024

Сервер успешно загрузился; проверить потребление памяти на целевом контексте и рабочей нагрузке.

4

(Опционально) зафиксировать параметры

llama-fit-params -m model.gguf -fitt 1024 -fitc 65536 → подставить вывод в скрипт запуска

Получены стабильные флаги -ngl и -ot

5

Закрепить на P‑ядрах (только Linux, Intel)

taskset -c 0-11 llama-server … (подставить свой диапазон)

Все потоки инференса работают на P‑ядрах

Что дальше?

Если после этих трех шагов генерация токенов все еще не устраивает — значит, проблема глубже, и мы разберем ее в следующих статьях цикла:

  • Квантование KV-кэша — как освободить VRAM под слои модели.

  • Контекст и размер батча — влияние на пропускную способность.

  • Диагностический чек-лист — XMP, CPU-гувернор, PCIe, температура, термальное троттлинг.

  • Спекулятивное декодирование (MTP) — как выжать в 2 раза больше токенов.

А пока — полный 52-страничный справочник с детальными бенчмарками, скриптами и сценариями отказов можно скачать по ссылке. Там все эти разделы разобраны до мельчайших флагов.

Автор: MaDeLa

Источник