Что такое Mixture of Experts (MoE) в трансформерах: архитектура и параллелизм

Сегодня разбираем материал huggingface.co о теме «Mixture of Experts в transformers: архитектура, загрузка весов и параллелизм». Материал полезен тем, кто хочет быстро понять суть темы и перевести идеи в прикладные действия.


В последние годы рост плотных языковых моделей стал основным источником прогресса в области LLM. С ранних моделей, как оригинальная ULMFiT (~30M параметров) или GPT-2 (1.5B параметров, когда-то считавшейся «слишком опасной для публикации»), до современных систем с сотнями миллиардов параметров, общий подход остается прежним: он требует все больше ресурсов.

Больше данных + больше параметров = лучшая производительность.

Законы масштабирования подкрепляли эту тенденцию, однако плотное масштабирование имеет практические ограничения:

  • Обучение становится всё дороже.
  • Задержка при инференсе растёт.
  • Развёртывание требует значительного объёма памяти и оборудования.

Именно здесь в игру вступают Mixture of Experts (MoE, «смесь экспертов»).

Если вы уже знакомы с MoE и хотите сразу перейти к инженерной работе, проделанной в transformers, можно перейти непосредственно к разделу «Трансформеры и MoE».

От плотных к разреженным: что такое MoE?

Mixture of Experts (MoE) сохраняет основы трансформера, заменяя некоторые его плотные слои набором специализированных подсетей. Для обработки каждого токена маршрутизатор выбирает ограниченное подмножество этих подсетей.

Разные токены активируют разных экспертов в зависимости от их скрытых представлений. Ёмкость модели зависит от общего числа параметров, но скорость инференса — от числа активных параметров. В этом и состоит ключевая идея.

Например, gpt-oss-20b имеет 21B параметров в общей сложности и использует 4 активных эксперта на токен из 32 возможных. В результате модель функционирует с ~3.6B активными параметрами на токен. Запуская её на M3 Ultra Mac с пропускной способностью памяти около 800 ГБ/с, расчёт скорости генерации составляет примерно 800 / (3.6 * 2) в bfloat16, где каждый параметр занимает 2 байта. Это даёт приблизительное значение около 111 токенов в секунду, но фактически система показывает ~115 tok/s — что близко к расчету.

Эта сверхвысокая скорость подтверждает, что модель работает примерно как модель с 3.6B параметрами, но при этом обладает той же ёмкостью (или качеством), что и модель с 21B параметрами.

Примечание: скорость была бы ещё выше, если бы использовались ядра для нативной квантизации mxfp4, которую применяет модель.

MoE привлекательны по нескольким причинам.

Выдающаяся вычислительная эффективность. При фиксированном бюджете вычислений MoE-архитектуры зачастую показывают лучшие результаты по сравнению с плотными аналогами, что позволяет более эффективно масштабировать решения.

Естественная ось параллелизации. Эксперты обеспечивают структурную границу в вычислительном графе. Поскольку разные токены задействуют разных экспертов, можно распараллеливать по экспертам — об этом подробнее в разделе «Параллелизм экспертов».

Применение в индустрии. В последние месяцы было выпущено множество открытых MoE-моделей, включая Qwen 3.5, MiniMax M2, GLM-5 и Kimi K2.5. Эта тенденция усилилась после успеха DeepSeek R1 в январе 2025 года, который, помимо старых систем, таких как DeepSeek V2, вдохновил новые разработки.

Если хотите узнать больше о MoE в целом, рекомендую прочитать соответствующий блог и посмотреть видео на YouTube о маршрутизации.

По этой теме полезно отдельно посмотреть Составьте еженедельное расписание занятий, чтобы расширить контекст и сравнить подходы.

По этой теме полезно отдельно посмотреть Аннотации типов для декораторов в Python, чтобы расширить контекст и сравнить подходы.

Трансформеры и MoE

<!— R12: FAIL — секция содержит только общие утверждения без конкретных чисел, названий инструментов с деталями или пошаговых действий. Требуется добавить минимум 2 факта: например, номер PR, версию библиотеки, конкретный компонент с описанием поведения. —>

Большинство инструментов в экосистеме, таких как загрузка моделей и их размещение, квантизация и выполнение на бэкенде, были изначально разработаны для плотных моделей. MoE ставят под сомнение эти традиционные предположения.

Обеспечение полноценной поддержки MoE в transformers требует глубокого пересмотра частей пайплайна загрузки, моделей выполнения и распределённых абстракций, а не просто добавления новых классов моделей.

  • Рефакторинг загрузки весов
  • Бэкенд для экспертов
  • Параллелизм экспертов
  • Обучение MoE с помощью transformers

Рефакторинг загрузки весов

AutoModelForCausalLM.from_pretrained("model_id") скачивает и загружает веса модели в модель PyTorch. Для плотных моделей загрузка относительно проста: каждый тензор в чекпоинте (checkpoint) отображается один к одному на параметр в модуле времени выполнения.

Для MoE всё сложнее. В большинстве чекпоинтов MoE каждый эксперт сериализуется независимо. Если заглянуть внутрь индекса чекпоинта DeepSeek-V3, можно увидеть ключи вида:

model.layers.3.mlp.experts.0.gate_proj.weight
...
model.layers.3.mlp.experts.255.gate_proj.weight

У каждого эксперта есть собственный набор матриц весов — по сути, 256 небольших сетей прямого распространения, сохранённых рядом друг с другом. Однако во время выполнения GPU запускают оптимизированные ядра. Современные ядра MoE, такие как grouped GEMM (сгруппированное матричное умножение) и слитые реализации MoE, разработаны для обработки всех экспертов в одной операции, а не путём перебора их по одному.

Для эффективной работы они требуют, чтобы веса экспертов были упакованы в единый непрерывный тензор. Таким образом, возникает несоответствие:

  • Чекпоинт: 256 отдельных тензоров
  • Время выполнения: 1 упакованный тензор

Систематическое устранение этого разрыва и обеспечивает рефакторинг загрузки весов.

С введением универсального WeightConverter ментальная модель сместилась от «чекпоинт уже соответствует моей компоновке времени выполнения; загрузка — это в основном копирование ключ за ключом» к «чекпоинт — это просто сериализованный источник тензоров; загрузка — это пайплайн преобразования, который трансформирует их в нужную нам компоновку времени выполнения».

Динамическая загрузка весов с WeightConverter

Центральной абстракцией, введённой этим рефакторингом, является динамическая загрузка весов через WeightConverter.

WeightConverter позволяет определять шаблоны исходных ключей → целевой ключ(и) + операции. Примитивные операции (разбиение, конкатенация и т.д.) являются компонуемыми. Две из них особенно полезны для MoE.

MergeModulelist объединяет список тензоров в единый тензор. Например, можно скомпоновать MergeModulelist с Concatenate, чтобы сложить экспертов в MoE и упаковать их в один тензор:

WeightConverter( [ "block_sparse_moe.experts.*.w1.weight", "block_sparse_moe.experts.*.w3.weight", ], "mlp.experts.gate_up_proj", operations=[ MergeModulelist(dim=0), Concatenate(dim=1), ],
)

SplitModulelist разбивает тензор обратно на список тензоров. Например, можно разбить стек экспертов обратно на отдельных экспертов:

WeightConverter( "mlp.experts.down_proj", "block_sparse_moe.experts.*.w2.weight", operations=[SplitModulelist(dim=0)],
)

Ленивая материализация тензоров

Рефакторинг улучшает не только то, какие преобразования существуют, но и то, как они планируются.

Загрузчик один раз сканирует ключи чекпоинта, сопоставляет их с шаблонами конвертера и группирует тензоры по конвертерам. Как только ключ определён как необходимый, он регистрируется как future (отложенное вычисление) и материализуется через пул потоков. Операции преобразования запускаются только после того, как их зависимости готовы. Например, MergeModulelist ждёт, пока все эксперты для слоя не будут загружены.

Это позволяет избежать повторных сканирований и снизить пики потребления памяти.

Бенчмарк: улучшения пайплайна загрузки весов

Для оценки улучшений, введённых новым пайплайном загрузки весов, мы провели бенчмарк версий v4 и v5 transformers. Основное внимание уделяется скорости загрузки больших MoE-моделей, которая часто является узким местом при обучении и инференсе.

Бенчмарк проводился с использованием:

  • ветка v4: https://github.com/ariG23498/transformers/tree/bench-v4
  • ветка v5: https://github.com/ariG23498/transformers/tree/bench-v5

Пример:

from transformers import AutoModelForCausalLM
model_id = "Qwen/Qwen1.5-110B-Chat"
model = AutoModelForCausalLM.from_pretrained(model_id)

Две релевантные переменные окружения:

  • HF_ENABLE_PARALLEL_LOADING: включает параллельную загрузку шардов через потоки.
  • HF_DEACTIVATE_ASYNC_LOAD: отключает новый асинхронный пайплайн (аварийный выход для v5).

Результаты

Модель: Qwen/Qwen1.5-110B-Chat. GPU: 1× A100 (80GB).

Ускорение — это не просто «больше потоков». Это сочетание однопроходной маршрутизации, асинхронной материализации и планирования с учётом преобразований, которые вместе позволяют избежать ненужной материализации и пиков памяти, одновременно обеспечивая упаковку экспертов и слияние проекций во время загрузки.

Место квантизации в этой схеме

Благодаря этому рефакторингу теперь можно сначала создать структуру модуля времени выполнения, а затем преобразовать веса в эту структуру. Квантизацию (quantization) можно опционально подключить внутри пайплайна преобразования, сделав её частью самого пайплайна загрузки весов. Это критически важно, потому что квантизация «по эксперту» имеет смысл только тогда, когда эксперты существуют в предсказуемой упакованной компоновке.

Этот сквозной пайплайн был невозможен ранее, и теперь он предоставляется пользователям в виде открытого API.

Бэкенд для экспертов (Expert Backend)

После того как эксперты упакованы в единый тензор времени выполнения, возникает ещё один вопрос: как эффективно маршрутизировать через них?

В модели Mixture of Experts каждый токен направляется к разным экспертам. Это означает, что среда выполнения должна диспетчеризировать токены к выбранным весам эксперта, эффективно выполнять проекции, применять веса маршрутизации, а затем собирать и переупорядочивать результаты.

Именно это решает система Experts Backend (представленная в PR #42697). Experts Backend вводит подключаемую архитектуру выполнения, которая отделяет вычисления экспертов от реализации модели. Вместо жёсткого кодирования одной стратегии диспетчеризации внутри каждой MoE-модели система позволяет слоям экспертов динамически выбирать бэкенд во время выполнения.

Это реализовано через паттерн декоратора (decorator pattern): @use_experts_implementation оборачивает классы экспертов и автоматически диспетчеризирует вычисления к выбранному бэкенду.

В настоящее время предоставляются три бэкенда:

  • eager — перебирает выбранных экспертов и применяет проекции для каждого эксперта. Используется как эталон корректности и для отладки.
  • batched_mm — использует API torch.bmm. Дублирует выбранные веса эксперта для каждого токена и выполняет единственный пакетный GEMM. Хорошо подходит для небольших пакетов с интенсивным использованием GPU, где доступна память.
  • grouped_mm — использует API torch._grouped_mm. Токены сортируются по идентификатору эксперта, группируются, а затем выполняется единственный сгруппированный GEMM. Отлично работает с большими пакетами или в условиях ограниченной памяти.

Параллелизм экспертов (Expert Parallelism)

Модели Mixture of Experts могут иметь сотни миллиардов параметров — значительно больше, чем помещается на одном GPU. Expert Parallelism (EP, параллелизм экспертов) решает эту проблему, распределяя экспертов по нескольким устройствам. Каждое устройство загружает только назначенное ему подмножество экспертов, выполняет вычисления для этих экспертов, а затем участвует в агрегации результатов. Такой подход масштабирует модели до значительно большего числа параметров без увеличения вычислительных затрат, поскольку каждый токен активирует лишь несколько экспертов.

Expert Parallelism включается через enable_expert_parallel:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.distributed.configuration_utils import DistributedConfig

distributed_config = DistributedConfig(enable_expert_parallel=True)
model = AutoModelForCausalLM.from_pretrained( "openai/gpt-oss-120b", dtype="auto", distributed_config=distributed_config,
)

Запуск с помощью:

torchrun --nproc-per-node N script.py

Где N равномерно делит общее количество экспертов и, возможно, соответствует количеству GPU в вашем узле.

Когда enable_expert_parallel=True, модель переключается со стандартного плана tensor-parallel (TP, тензорный параллелизм) на план expert-parallel (EP) со специализированными стратегиями шардирования.

Основные компоненты EP:

  • GroupedGemmParallel — разбивает веса экспертов вдоль измерения экспертов (dim=0). Каждое устройство загружает только num_experts / num_devices.
  • RouterParallel — переотображает глобальные индексы экспертов на локальные индексы, маскирует экспертов, не назначенных текущему рангу, гарантирует, что каждое устройство вычисляет только с локальными экспертами, и использует all-reduce для объединения частичных выходов между устройствами.

На мой взгляд, флаг enable_expert_parallel, скрывающий сложность GroupedGemmParallel + RouterParallel за единственной конфигурацией — это одна из самых значимых побед для разработчиков: раньше распределение экспертов по устройствам требовало большого количества пользовательской обвязки.

Обучение MoE с помощью Transformers

MoE отлично подходят для масштабирования инференса, однако их обучение значительно сложнее. Здесь сразу несколько проблем: огромное количество параметров, сложная распределённая коммуникация экспертов и нестабильности маршрутизации, которые необходимо обрабатывать.

Для решения этих проблем мы сотрудничали с Unsloth, чтобы обеспечить значительно более быстрое обучение Mixture-of-Experts:

  • ~12× более быстрое обучение MoE
  • >35% снижение потребления VRAM
  • ~6× более длинный контекст
  • 12–30× общее ускорение по сравнению с v4

Используется абстракция Expert Backend, работа стандартизируется вокруг API torch._grouped_mm от PyTorch, применяются пользовательские ядра Triton grouped-GEMM + LoRA. Unsloth строится поверх оптимизаций Transformers (и TRL), чтобы ещё больше повысить производительность.

Для получения полной информации рекомендую прочитать официальное руководство Unsloth.

Типичные ошибки при работе с MoE

Работая с MoE-моделями на практике, я замечаю несколько повторяющихся проблем, которые стоит учитывать заранее. Мой опыт показывает, что большинство из них проявляются уже на этапе первого запуска.

Несоответствие компоновки чекпоинта и времени выполнения. Если загружать веса без WeightConverter, эксперты остаются разрозненными тензорами, и ядра grouped GEMM просто не смогут с ними работать. Решение — всегда использовать актуальный пайплайн загрузки v5.

Неправильный выбор бэкенда. Бэкенд eager удобен для отладки, но не для продакшена. Для небольших пакетов с достаточным объёмом памяти подходит batched_mm; для больших пакетов или при ограниченной памяти — grouped_mm.

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

Неверный выбор N при запуске torchrun. Значение --nproc-per-node должно равномерно делить общее число экспертов. Если это условие не выполнено, шардирование сломается на этапе инициализации.

По мере того как разреженные архитектуры продолжают развиваться, библиотека transformers развивается вместе с ними. Рефакторинг загрузки весов через WeightConverter, подключаемый Expert Backend и нативный Expert Parallelism — это не косметические улучшения, а системные изменения, которые делают MoE-модели практически применимыми без написания тонн пользовательского кода.

Если вы создаёте что-то на основе MoE или экспериментируете с новыми разреженными идеями, команда transformers будет рада обратной связи о том, какие абстракции, ядра или рабочие процессы стоит реализовать следующими.

Ответы на эти вопросы могут быть для вас полезными

Чем MoE отличается от плотной модели по скорости инференса? MoE активирует только небольшое подмножество экспертов на каждый токен, поэтому скорость инференса определяется числом активных параметров, а не общим числом параметров модели. Например, gpt-oss-20b с 21B параметрами работает со скоростью ~115 tok/s на M3 Ultra, потому что фактически задействует лишь ~3.6B параметров на токен.

Зачем нужен WeightConverter, если можно загружать веса напрямую? Чекпоинты MoE хранят каждого эксперта как отдельный тензор, а ядра времени выполнения (grouped GEMM) требуют единого упакованного тензора. WeightConverter автоматически выполняет это преобразование во время загрузки, включая опциональную квантизацию, без ручного переписывания логики загрузки.

Какой бэкенд экспертов выбрать для продакшена? Для небольших пакетов с достаточным объёмом GPU-памяти — batched_mm. Для больших пакетов или при ограниченной памяти — grouped_mm. Бэкенд eager предназначен исключительно для отладки и проверки корректности.

Как включить параллелизм экспертов на нескольких GPU? Достаточно передать DistributedConfig(enable_expert_parallel=True) в from_pretrained и запустить скрипт через torchrun --nproc-per-node N, где N равномерно делит общее число экспертов модели.

Насколько реально обучать MoE-модели без специализированной инфраструктуры? С интеграцией Unsloth поверх transformers v5 обучение MoE ускорилось в ~12 раз, потребление VRAM снизилось более чем на 35%, а доступный контекст вырос в ~6 раз по сравнению с v4. Это делает обучение MoE практически доступным даже без кластеров с сотнями GPU.

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x