Transformers documentation

Экспертная параллельность

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Экспертная параллельность

Экспертная параллельность — это стратегия параллелизма для моделей 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-allep_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=8348538,6 ГБ
tp_size=4, fsdp_size=2290034,2 ГБ
tp_size=2, fsdp_size=4283032,3 ГБ

Возобновление обучения из контрольной точки пока не поддерживается для моделей, разбитых на части при загрузке, поэтому Trainer принимает для них только save_only_model=True или save_strategy="no".

Справочник API

класс transformers.DistributedConfig

< >

( 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.

Update on GitHub