Transformers documentation
Экспертная параллельность
Экспертная параллельность
Экспертная параллельность — это стратегия параллелизма для моделей mixture-of-experts (MoE). Каждый экспертный слой прямой передачи размещается на отдельном аппаратном ускорителе. Роутер направляет токены к соответствующим экспертам и собирает результаты. Этот подход позволяет масштабировать модели до значительно большего количества параметров без увеличения вычислительных затрат, поскольку каждый токен активирует лишь несколько экспертов.
DistributedConfig
Включите экспертную параллельность с помощью класса DistributedConfig и аргумента ep_size. Большинство MoE-моделей по умолчанию используют token dispatch, поэтому ep_size можно задавать независимо от tp_size. Некоторые модели, такие как Llama 4 и Gemma 4, по-прежнему по умолчанию используют маскирование и all-reduce ("ep_router" и "moe_tp_experts" в model.ep_plan). Маскирование также доступно для любой модели с переопределением ep_plan и требует, чтобы ep_size=tp_size, чтобы каждый ранк в экспертной группе получал одни и те же токены.
import os
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.distributed.configuration_utils import DistributedConfig
distributed_config = DistributedConfig(
tp_size=int(os.environ["WORLD_SIZE"]),
ep_size=int(os.environ["WORLD_SIZE"]),
)
model = AutoModelForCausalLM.from_pretrained(
"openai/gpt-oss-120b",
distributed_config=distributed_config,
)Каждая MoE-модель определяет два плана в своём конфиге: base_model_tp_plan для плотных модулей и base_model_ep_plan для экспертов. Они доступны в загруженной модели как model.tp_plan и model.ep_plan. При tp_size > 1 и ep_size > 1 применяются оба: план тензорного параллелизма разбивает внимание и плотные MLP, а план экспертного параллелизма разбивает экспертов. Правила EP имеют приоритет над правилами TP для одних и тех же модулей, поэтому веса экспертов разбиваются один раз, согласно плану EP. В плане EP стиль GroupedGemmParallel разбивает веса экспертов вдоль измерения экспертов, так что каждый ранг загружает только своих локальных экспертов, а ep_router маскирует экспертов, находящихся на других рангах, до того, как all-reduce объединит выходы экспертов.
tp_plan применяется только при tp_size > 1, а ep_plan — только при ep_size > 1. Если TP включён, а EP отключён, применяется полный план TP, включая правила для экспертов.
Правило экспертного прямого прохода в ep_plan определяет, как токены поступают к экспертам:
| правило | механизм | layout |
|---|---|---|
"moe_tp_experts" с "ep_router" на роутере | маскирование и all-reduce: каждый ранг выполняет свои локальные эксперты на всей выборке, роутер маскирует остальные, а all-reduce объединяет выходные данные | ep_size=tp_size |
"ep_dispatch_experts" | token dispatch: каждый ранг хранит собственные токены и обменивает только маршрутизированные пары (токен, эксперт) с помощью двух коллективных операций all-to-all | ep_size — кратное tp_size число, делящее fsdp_size * tp_size |
Использование
enable_expert_parallel=Trueбезep_sizeустарело и вызываетFutureWarning. Это разделяет только экспертов, как в случаеep_size=tp_size, tp_plan={}.
Запустите скрипт инференса с помощью torchrun. Количество процессов должно быть равно tp_size * fsdp_size * pp_size, а ep_size должно делить количество экспертов нацело.
torchrun --nproc-per-node 8 your_script.py
Переопределение планов
Передайте tp_plan={...} или ep_plan={...} в DistributedConfig, чтобы переопределить отдельные правила предопределённых планов. Нерасписанные правила сохраняются, а объединённые планы записываются в модель. Каждый ключ должен соответствовать модулю, параметру или существующей записи плана; в противном случае при загрузке возникает ValueError до начала шардирования. Используйте полный путь, как он виден из загруженной модели, то есть model.layers.* для causal LM и layers.* для её базовой модели.
distributed_config = DistributedConfig(
tp_size=4,
ep_size=4,
tp_plan={"model.layers.*.self_attn.q_proj": "colwise_rep"},
ep_plan={"model.layers.*.mlp.experts.down_proj": "grouped_gemm"},
)Указание плана не определяет размеры параллелизма: явно задайте tp_size и ep_size.
Большинство MoE-моделей по умолчанию используют "ep_dispatch_experts". Чтобы вместо этого применить маскирование и all-reduce, установите ep_size=tp_size и переопределите правила forward для обоих модулей — роутера и экспертов (имя модуля роутера зависит от модели, например, mlp.router в gpt-oss):
distributed_config = DistributedConfig(
tp_size=4,
ep_size=4,
ep_plan={
"model.layers.*.mlp.gate": "ep_router",
"model.layers.*.mlp.experts": "moe_tp_experts",
},
)Напротив, переопределите правило forward для экспертов модели, чей план использует маскирование, с помощью "ep_dispatch_experts", чтобы использовать диспетчеризацию токенов. Правило роутера в этом случае игнорируется, поскольку для диспетчеризации необходимы глобальные идентификаторы экспертов, чтобы определить владельца каждого эксперта.
Диспетчеризация токенов
With token dispatch, each rank trains on its own part of the batch. At every MoE layer, a rank routes its tokens, sends each (token, expert) pair to the rank that owns the expert with an all-to-all, runs its local experts on what it receives, gets the results back with a second all-to-all and combines them with the routing weights. Only the routed activations and expert outputs travel, and no rank computes experts for tokens it does not own.
from transformers import AutoModelForCausalLM
from transformers.distributed import DistributedConfig
distributed_config = DistributedConfig(
tp_size=1,
fsdp_size=8,
ep_size=4,
)
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-30B-A3B", distributed_config=distributed_config)При tp_size=1 ep_size должно делить fsdp_size и количество экспертов, а внимание не ограничивается num_key_value_heads. Для остальной части модели:
- Параметры вне экспертов распределяются с помощью FSDP2 по
fsdp, что уменьшает их градиенты. - Эксперты разбиты на шарды по
epи, когдаefsdp_size = fsdp_size * tp_size // ep_sizeбольше единицы, дополнительно разбиты на шарды FSDP поefsdp. Они всегда обёрнуты в FSDP, поэтомуfsdp_mixed_precisionиfsdp_cpu_offloadприменяются и к ним, а save_pretrained() собирает их так же, как и любой другой параметр. - Группа экспертного параллелизма содержит
ep_size / tp_sizeбатчей, поэтому градиент эксперта представляет собой сумму по такому количеству батчей. Редукцияefsdpделит наfsdp_size, а не на размер своей группы, что даёт тот же средний по батчу результат, который FSDP2 использует для плотных модулей. - Каждый локальный эксперт также обрабатывает одну строку нулевых отступов на каждый слой. Ранг, эксперты которого не получили токены, всё равно участвует в обратном all-to-all и в редукции градиентов экспертов.
Как комбинируются размеры
tp_size * fsdp_size— это количество процессов.ep_sizeне добавляет новых: он лишь перегруппировывает те же ранги, но только для весов экспертов.ep_sizeразбивает список экспертов наep_sizeблоков. Каждый ранг вычисляетnum_experts / ep_sizeэкспертов, аep_sizeпоследовательных рангов хранят один полный набор. Этот набор рангов является группой, в которой выполняется all-to-all.efsdp_size = fsdp_size * tp_size / ep_size— это количество полных копий набора экспертов. Ранги, находящиеся на одной и той же позиции в разных копиях, разделяют этих экспертов по памяти и усредняют их градиенты, как это делает FSDP для плотных модулей.- Партия, которую удерживает ранг, зависит только от
fsdp. Последовательные ранги образуют группу TP и получают одну и ту же партию; группыfsdp_sizeполучают разные партии.
Восемь процессов, tp_size=2, fsdp_size=4, восемь экспертов:
rank 0 1 2 3 4 5 6 7 batch [====B0====] [====B1====] [====B2====] [====B3====] one batch per TP pair tp 0 1 0 1 0 1 0 1 ep_size=2 E0-3 E4-7 E0-3 E4-7 E0-3 E4-7 E0-3 E4-7 group = a TP pair, efsdp_size=4 ep_size=4 E0E1 E2E3 E4E5 E6E7 E0E1 E2E3 E4E5 E6E7 group = two pairs, efsdp_size=2 ep_size=8 E0 E1 E2 E3 E4 E5 E6 E7 group = all ranks, efsdp_size=1
Из рисунка следуют два числа:
- Внутри группы EP каждый токен существует
tp_sizeраз, по одному разу на каждый ранг пары, которая хранит его батч. Это не зависит отep_size. - Группа EP содержит
ep_size / tp_sizeразличных батчей. Это количество, по которому суммируются градиенты эксперта.
С параллелизмом тензоров
Установите tp_size > 1, чтобы разбить плотные модули по плану TP, в то время как эксперты используют dispatch. На восьми процессах:
distributed_config = DistributedConfig(
tp_size=2,
fsdp_size=4,
ep_size=4,
)Каждая пара рангов TP получает один и тот же батч, поскольку тензорное параллелизм дублирует активации внутри пары. Если бы оба ранга передали все свои токены, ранг, владеющий экспертом, получил бы каждый токен дважды, вычислил бы его дважды, и градиент его весов удвоился бы. Эксперты целиком находятся на одном ранге, поэтому дубликат нельзя разделить по весам, а владелец обычно является другим рангом, поэтому проблему нельзя решить через владение, как это делает маскирование. Поэтому пара разделяет строки: каждый ранг TP передает непересекающуюся часть 1 / tp_size токенов, результаты возвращаются на ранг, который их отправил, а all-reduce по паре нулевых дополненных половин восстанавливает дублированный вывод, который ожидает следующий слой. Разделение выполняется по tp_size, а не по ep_size, поскольку только ранги, хранящие батч, могут его отправить. Группы экспертов охватывают четыре ранга, и каждый эксперт шардирован FSDP по efsdp_size = 2 рангам, в то время как FSDP-группа ствольной части охватывает четыре ранга. Обычные ограничения TP модели, такие как делимость заголовков внимания, по-прежнему применяются к плотным модулям. Срезы токенов могут быть неравными или пустыми, в том числе во время декодирования одного токена.
Пока нельзя использовать диспетчеризацию токенов вместе с параллелизмом по конвейеру; используйте pp_size=1 (ещё не протестировано)
Каждая из этих конфигураций использует восемь GPU:
| Конфигурация | Результат |
|---|---|
DistributedConfig(tp_size=4, fsdp_size=2, ep_size=4) | Диспетчеризация с TP-группами по четыре, каждая из которых делит свою партию на четыре части (план по умолчанию); если не указать ep_plan, будет использоваться устаревшая маскированная EP |
DistributedConfig(tp_size=1, fsdp_size=8, ep_size=4) | Диспетчеризация с независимой партией на каждом ранке, без нарезки, и экспертами, распределёнными через FSDP по парам ранков. |
DistributedConfig(tp_size=2, fsdp_size=4, ep_size=4) | Диспетчеризация с парой TP на каждый батч, где каждая пара разбивает свой батч на две части; два батча на группу экспертов. |
DistributedConfig(tp_size=8, ep_size=8) | Диспетчеризация с общим для всех рангов батчем, разбитым на восемь частей, либо маскирование и all-reduce для маскированного плана. |
Класс Trainer пока не учитывает диспетчеризацию токенов: подсчёт батчей и токенов предполагает компоновку all-reduce, в которой ранги группы TP разделяют один батч, а существует
fsdp_sizeшардов с параллелизмом данных. Поддержка диспетчеризации в Trainer будет добавлена в последующих обновлениях.
Комбинирование с FSDP2
Параллелизм тензоров и экспертов распределяет веса по tp, но состояние оптимизатора и модули без правил по-прежнему дублируются на каждом ранге группы, что ограничивает размер модели, которую можно обучать. Добавьте FSDP2 по второму измерению сетки с fsdp_size. С использованием маскирования и all-reduce, поддерживайте ep_size=tp_size и передавайте `ep_plan={“layers..mlp.gate”: “ep_router”, “layers..mlp.experts”: “moe_tp_experts”}“.
from transformers import AutoModelForCausalLM
from transformers.distributed import DistributedConfig
distributed_config = DistributedConfig(
tp_size=4,
ep_size=4, # expert parallel size, must match tp_size with masking and all-reduce
fsdp_size=2, # data parallel shards
ep_plan={
"model.layers.*.mlp.gate": "ep_router",
"model.layers.*.mlp.experts": "moe_tp_experts",
},
)
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-30B-A3B", distributed_config=distributed_config)Загрузите модель как обычно, затем обучите с помощью Trainer. Он вычисляет норму градиента по обеим сеткам и назначает каждой сетке собственную группу параметров оптимизатора. save_model() собирает разбитые веса в обычный чекпоинт. Для этого требуется accelerate>=1.12, чтобы Trainer мог отразить tp_size и fsdp_size в ~Accelerate.ParallelismConfig.
Таблица ниже сравнивает обучение только с EP с обучением 2D EP+FSDP2 на 8xH100 GPU. Нагрузкой является полное дообучение Qwen3-30B-A3B в bf16 при длине последовательности 2048. Увеличение количества шардов FSDP снижает пиковое потребление памяти, а токены/с немного падают, так как FSDP2 выполняет all-gather и reduce-scatter экспертов через fsdp.
| конфигурация | токенов/с/ГП | пиковая память/ГП |
|---|---|---|
tp_size=8 | 3485 | 38,6 ГБ |
tp_size=4, fsdp_size=2 | 2900 | 34,2 ГБ |
tp_size=2, fsdp_size=4 | 2830 | 32,3 ГБ |
Возобновление обучения из контрольной точки пока не поддерживается для моделей, разбитых на части при загрузке, поэтому Trainer принимает для них только
save_only_model=Trueилиsave_strategy="no".
Справочник API
класс transformers.DistributedConfig
< source >( tp_size: int | None = Nonetp_plan: typing.Union[dict[str, str], typing.Literal['auto'], NoneType] = Noneenable_sequence_parallel: bool = Falseenable_expert_parallel: bool = Falsefsdp_size: int | None = Nonefsdp_plan: dict[str, str] | None = Nonefsdp_cpu_offload: bool = Falsefsdp_mixed_precision: bool = Falsepp_size: int | None = Noneep_size: int | None = Noneep_plan: dict[str, str] | None = None )
Параметры
- tp_size (
int, необязательный) — Количество устройств для тензорного параллелизма. ЕслиNoneиtp_planзадан, по умолчанию равноWORLD_SIZE // (other_parallel_size). ЕслиNoneиtp_planне задан, по умолчанию равно 1. - tp_plan (
dict[str, str]или"auto", необязательный) — План шардирования для тензорного параллелизма. Передайте"auto"или оставьтеNone, если заданtp_size, чтобы использовать предопределённый для моделиbase_model_tp_plan. Передайте словарь, чтобы переопределить отдельные правила этого плана; неуказанные правила сохраняются. Пустой словарь не применяет никаких правил тензорного параллелизма. - enable_sequence_parallel (
bool, необязательный, по умолчаниюFalse) — Зарезервировано для последовательного параллелизма. Пока не реализовано. - enable_expert_parallel (
bool, необязательный, по умолчаниюFalse) — Устаревший параметр, удалён в v5.21. Без указанияep_sizeэквивалентенep_size=tp_size, tp_plan={}: используется только план expert-parallel наtp_sizeрангах. Явно заданное значениеep_sizeимеет приоритет. Этот флаг не изменяетtp_sizeилиfsdp_size. - fsdp_size (
int, необязательный) — Количество устройств для FSDP (параллелизм данных). ЕслиNoneиtp_sizeзадано, по умолчанию равно 1. - fsdp_plan (
dict[str, str], необязательный) — План шардинга FSDP2. ОставьтеNone, чтобы использовать предопределённый для моделиbase_model_fsdp_planи классовый_fsdp_plan. Передайте словарь, чтобы переопределить отдельные правила; неуказанные правила сохраняются. Применяется только приfsdp_size > 1. - fsdp_cpu_offload (
bool, необязательный, по умолчаниюFalse) — Включает ли выгрузку на CPU для FSDP2. - fsdp_mixed_precision (
bool, необязательный, по умолчаниюFalse) — Включает ли смешанную точность для FSDP2. - pp_size (
int, необязательный) — Количество устройств для конвейерного параллелизма. ЕслиNoneи задан другой режим параллелизма, по умолчанию равно 1. - ep_size (
int, необязательный) — Количество устройств, владеющих различными фрагментами экспертов. По умолчанию равно 1. Укажите его явно, чтобы включить EP. Должно быть кратнымtp_sizeи делитьfsdp_size * tp_size. Планы all-reduce для экспертов требуютep_size=tp_size; для диспетчеризации токенов ("ep_dispatch_experts") также допустимоep_size > tp_size. - ep_plan (
dict[str, str], необязательный) — План шардирования с параллелизмом экспертов. ОставьтеNone, чтобы использовать предопределённый для моделиbase_model_ep_plan. Передайте словарь, чтобы переопределить отдельные правила этого плана; неуказанные правила сохраняются. Применяется только приep_size > 1, и его правила имеют приоритет над правиламиtp_planдля тех же модулей. Правило"ep_dispatch_experts"выбирает диспетчеризацию токенов all-to-all вместо маскирования роутера и all-reduce.
Конфигурация для нативного распределённого вывода и обучения с использованием параллелизма по тензорам, конвейерной параллели или FSDP2.