Transformers documentation
Parallélisme d'experts
Parallélisme d'experts
Le parallélisme d’experts est une stratégie de parallélisation destinée aux modèles mixture-of-experts (MoE). La couche feedforward de chaque expert réside sur un accélérateur matériel différent. Un routeur achemine les jetons vers les experts appropriés et collecte les résultats. Cette approche permet de faire évoluer les modèles vers des nombres de paramètres bien plus élevés sans augmenter le coût de calcul, car chaque jeton n’active que quelques experts.
DistributedConfig
Activez le parallélisme d'experts avec la classe DistributedConfig et l'argument ep_size. La plupart des modèles MoE utilisent par défaut le token dispatch, ce qui permet de définir ep_size indépendamment de tp_size. Certains, comme Llama 4 et Gemma 4, utilisent encore par défaut le masquage et l'all-reduce ("ep_router" et "moe_tp_experts" dans model.ep_plan). Le masquage est également disponible sur tout modèle avec une surcharge de ep_plan, et nécessite ep_size=tp_size afin que chaque rang d'un groupe d'experts reçoive les mêmes jetons.
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,
)Chaque modèle MoE définit deux plans dans sa configuration : base_model_tp_plan pour les modules denses et base_model_ep_plan pour les experts. Ils sont exposés sur le modèle chargé sous la forme de model.tp_plan et model.ep_plan. Avec tp_size > 1 et ep_size > 1, les deux s’appliquent : le plan de parallélisme de tenseurs découpe l’attention et les MLP denses, tandis que le plan de parallélisme d’experts découpe les experts. Les règles EP prennent le pas sur les règles TP pour les mêmes modules, de sorte que les poids des experts ne sont découpés qu’une fois, par le plan EP. Dans le plan EP, le style GroupedGemmParallel répartit les poids des experts le long de la dimension des experts afin que chaque rang ne charge que ses experts locaux, et ep_router masque les experts situés sur d’autres rangs avant qu’un all-reduce ne combine les sorties des experts.
tp_plan n’est appliqué que lorsque tp_size > 1, et ep_plan uniquement lorsque ep_size > 1. Si TP est activé et EP désactivé, le plan TP complet s’applique, y compris les règles d’experts.
La règle de propagation avant d’expert dans ep_plan sélectionne la manière dont les jetons atteignent les experts :
| règle | mécanisme | layout |
|---|---|---|
"moe_tp_experts" avec "ep_router" sur le routeur | masquage et all-reduce : chaque rang exécute ses experts locaux sur le lot complet, le routeur masque les autres, et un all-reduce combine les sorties | ep_size=tp_size |
"ep_dispatch_experts" | distribution des jetons : chaque rang conserve ses propres jetons et n'échange que les paires (jeton, expert) routés via deux collectifs all-to-all | ep_size est un multiple de tp_size qui divise fsdp_size * tp_size |
enable_expert_parallel=Truesansep_sizeest obsolète et émet unFutureWarning. Il ne sharde que les experts, commeep_size=tp_size, tp_plan={}.
Lancez votre script d'inférence avec torchrun. Le nombre de processus doit être égal à tp_size * fsdp_size * pp_size, et ep_size doit diviser exactement le nombre d'experts.
torchrun --nproc-per-node 8 your_script.py
Écraser les plans
Passez tp_plan={...} ou ep_plan={...} à DistributedConfig pour remplacer des règles individuelles des plans prédéfinis. Les règles non spécifiées sont conservées, et les plans fusionnés sont stockés sur le modèle. Chaque clé doit correspondre à un module, à un paramètre ou à une entrée existante du plan ; sinon, le chargement lève une ValueError avant tout fractionnement. Utilisez le chemin complet tel qu’il est vu depuis le modèle chargé, donc model.layers.* pour un LM causal et layers.* pour son modèle de base.
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"},
)Fournir un plan n’infère pas les tailles de parallélisme : définissez explicitement tp_size et ep_size.
La plupart des modèles MoE utilisent par défaut "ep_dispatch_experts". Pour utiliser le masquage et l'all-reduce à la place, définissez ep_size=tp_size et écrasez à la fois les règles de propagation avant du routeur et des experts (le nom du module du routeur dépend du modèle, par exemple mlp.router sur 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",
},
)À l’inverse, pour un modèle dont le plan utilise le masquage, écrasez la règle de propagation avant des experts avec "ep_dispatch_experts" afin d’utiliser la distribution de jetons. La règle du routeur est alors ignorée, car la distribution a besoin des identifiants globaux des experts pour trouver le propriétaire de chaque expert.
Affectation des jetons
Avec la distribution de jetons, chaque rang entraîne son propre segment du lot. À chaque couche MoE, un routage des jetons est effectué par le rang, chaque paire (jeton, expert) est envoyée au rang propriétaire de l’expert concerné via un all-to-all, les experts locaux sont exécutés sur les données reçues, les résultats sont récupérés par un second all-to-all puis combinés avec les poids de routage. Seules les activations routées et les sorties des experts transitent, et aucun rang ne calcule les experts pour des jetons qui ne lui appartiennent pas.
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)Avec tp_size=1, ep_size doit diviser fsdp_size et le nombre d'experts, et l'attention n'est pas limitée par num_key_value_heads. Pour le reste du modèle :
- Les paramètres situés en dehors des experts sont fractionnés avec FSDP2 sur
fsdp, ce qui réduit leurs gradients. - Les experts sont répartis sur
epet, lorsqueefsdp_size = fsdp_size * tp_size // ep_sizeest supérieur à un, ils sont également répartis via FSDP surefsdp. Ils sont toujours enveloppés par FSDP, de sorte quefsdp_mixed_precisionetfsdp_cpu_offloads’appliquent également à eux et que save_pretrained() les regroupe comme n’importe quel autre paramètre. - Un groupe d’expert parallèle détient
ep_size / tp_sizelots, de sorte que le gradient d’un expert est une somme sur ce nombre de lots. La réductionefsdpdivise parfsdp_sizeau lieu de la taille de son groupe, ce qui donne la même moyenne par lot que celle utilisée par FSDP2 pour les modules denses. - Chaque expert local traite également une ligne de zéros par couche. Un rang dont les experts n’ont reçu aucun jeton participe toujours à l’opération inverse all-to-all et à la réduction des gradients des experts.
Comment les tailles se combinent
tp_size * fsdp_sizeest le nombre de processus.ep_sizen’en ajoute aucun : il regroupe les mêmes rangs uniquement pour les poids des experts.ep_sizedécoupe la liste des experts enep_sizeblocs. Chaque rang calculenum_experts / ep_sizeexperts, etep_sizerangs consécutifs détiennent un ensemble complet. Cet ensemble de rangs constitue le groupe dans lequel s’exécute l’opération all-to-all.efsdp_size = fsdp_size * tp_size / ep_sizecorrespond au nombre de copies complètes de l’ensemble d’experts. Les rangs occupant la même position dans les différentes copies découpent ces experts pour la mémoire et moyennent leurs gradients, de la même manière que FSDP le fait pour les modules denses.- Le lot détenu par un rang ne dépend que de
fsdp. Les rangs consécutifs forment un groupe TP et reçoivent le même lot ; lesfsdp_sizegroupes reçoivent des lots différents.
Huit processus, tp_size=2, fsdp_size=4, huit experts :
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
Deux nombres découlent de l'image :
- Au sein d'un groupe EP, chaque jeton existe
tp_sizefois, une fois par rang de la paire qui détient son lot. Cela ne dépend pas deep_size. - Un groupe EP détient
ep_size / tp_sizelots différents. C'est le nombre sur lequel la gradient d'un expert est additionné.
Avec le parallélisme de tenseurs
Définissez tp_size > 1 pour sharder les modules denses avec le plan TP tandis que les experts utilisent le dispatch. Sur huit processus :
distributed_config = DistributedConfig(
tp_size=2,
fsdp_size=4,
ep_size=4,
)Chaque paire de rangs TP reçoit le même lot, car le parallélisme de tenseurs réplique les activations au sein de la paire. Si les deux rangs transmettaient tous leurs jetons, le rang propriétaire d'un expert recevrait chaque jeton deux fois, le calculerait deux fois, et le gradient de ses poids serait doublé. Les experts sont entiers sur un seul rang, de sorte que le doublon ne peut pas être réparti par les poids, et le propriétaire est généralement un autre rang, ce qui empêche de résoudre le problème par la propriété, contrairement au masquage. La paire divise donc les lignes : chaque rang TP transmet une fraction disjointe de 1 / tp_size des jetons, les résultats reviennent au rang qui les a envoyés, et une réduction globale sur la paire des moitiés complétées par des zéros restaure la sortie répliquée attendue par la couche suivante. La division est effectuée par tp_size, et non par ep_size, car seuls les rangs qui détiennent un lot peuvent le transmettre. Les groupes d'experts s'étendent sur quatre rangs et chaque expert est fragmenté par FSDP sur efsdp_size = 2 rangs, tandis que le groupe FSDP du tronc s'étend sur quatre rangs. Les contraintes TP habituelles du modèle, telles que la divisibilité des têtes d'attention, s'appliquent toujours aux modules denses. Les tranches de jetons peuvent être inégales ou vides, y compris lors du décodage mono-jeton.
La distribution de jetons ne peut pas encore être combinée avec le parallélisme de pipeline ; utilisez pp_size=1 (non testé pour l'instant)
Ces configurations utilisent chacune huit GPU :
| Configuration | Résultat |
|---|---|
DistributedConfig(tp_size=4, fsdp_size=2, ep_size=4) | Dispatch avec des groupes de TP de quatre, chacun découpant son lot en quatre (plan par défaut) ; sauf si vous spécifiez un ep_plan pour utiliser l'EP masqué legacy |
DistributedConfig(tp_size=1, fsdp_size=8, ep_size=4) | Dispatch avec un lot indépendant sur chaque rang, sans troncature, et des experts répartis via FSDP sur des paires de rangs. |
DistributedConfig(tp_size=2, fsdp_size=4, ep_size=4) | Dispatch avec une paire TP par lot, chaque paire découpant son lot en deux ; deux lots par groupe d'experts. |
DistributedConfig(tp_size=8, ep_size=8) | Dispatch avec chaque rang partageant un même lot, découpé en huit, ou masquage et all-reduce pour un plan masqué. |
Le Trainer ne prend pas encore en charge la distribution des jetons : le comptage des lots et des jetons suppose la disposition all-reduce, où les rangs d’un groupe TP partagent un lot et où
fsdp_sizefragments de parallélisme de données existent. La prise en charge de la distribution par le Trainer sera apportée dans une mise à jour ultérieure.
Combinaison avec FSDP2
Le parallélisme de tenseurs et d’experts répartit les poids sur tp, mais l’état de l’optimiseur et les modules sans règle restent répliqués sur chaque rang du groupe, ce qui limite la taille des modèles que vous pouvez entraîner. Ajoutez FSDP2 sur une seconde dimension de maillage avec fsdp_size. Grâce au masquage et à l’all-reduce, conservez ep_size=tp_size et passez `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)Chargez le modèle comme d’habitude, puis entraînez-le avec Trainer. Il calcule la norme du gradient sur les deux maillages et attribue à chaque maillage son propre groupe de paramètres d’optimiseur. save_model() regroupe les poids fragmentés en un point de contrôle standard. Cela nécessite accelerate>=1.12 afin que Trainer puisse refléter tp_size et fsdp_size dans ~Accelerate.ParallelismConfig.
Le tableau ci-dessous compare l’entraînement EP seul avec l’entraînement 2D EP+FSDP2 sur 8 GPU H100. La charge de travail est le fine-tuning complet de Qwen3-30B-A3B en bf16 avec une longueur de séquence de 2048. Plus il y a de shards FSDP, plus la mémoire de pointe est réduite, et les tokens/s baissent légèrement car FSDP2 effectue des all-gathers et des reduce-scatters des experts à travers fsdp.
| configuration | tokens/s/GPU | mémoire de pointe/GPU |
|---|---|---|
tp_size=8 | 3485 | 38,6 Go |
tp_size=4, fsdp_size=2 | 2900 | 34,2 Go |
tp_size=2, fsdp_size=4 | 2830 | 32,3 Go |
La reprise à partir d'un point de contrôle n'est pas encore prise en charge pour les modèles fragmentés au chargement, de sorte que le Trainer n'accepte que
save_only_model=Trueousave_strategy="no"pour ces modèles.
Référence de l'API
classe 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 )
Paramètres
- tp_size (
int, facultatif) — Nombre d’appareils pour le parallélisme de tenseurs. SiNoneet quetp_planest défini, la valeur par défaut estWORLD_SIZE // (other_parallel_size). SiNoneet qu’aucuntp_plann’est défini, la valeur par défaut est 1. - tp_plan (
dict[str, str]ou"auto", facultatif) — Plan de fractionnement du parallélisme de tenseurs. Passez"auto", ou laissezNonelorsquetp_sizeest défini, pour utiliser lebase_model_tp_planprédéfini du modèle. Passez un dictionnaire pour remplacer des règles individuelles de ce plan ; les règles non spécifiées sont conservées. Un dictionnaire vide n’applique aucune règle de parallélisme de tenseurs. - enable_sequence_parallel (
bool, facultatif, valeur par défautFalse) — Réservé au parallélisme de séquence. Pas encore implémenté. - enable_expert_parallel (
bool, facultatif, valeur par défautFalse) — Obsolète, supprimé dans la v5.21. Sansep_size, identique àep_size=tp_size, tp_plan={}: le plan de parallélisme d'experts seul surtp_sizerangs. Unep_sizeexplicite a la priorité. Ce drapeau ne modifie pastp_sizenifsdp_size. - fsdp_size (
int, facultatif) — Nombre d’appareils pour FSDP (parallélisme de données). SiNoneet quetp_sizeest défini, la valeur par défaut est 1. - fsdp_plan (
dict[str, str], facultatif) — Plan de sharding FSDP2. Laissez la valeur àNonepour utiliser lebase_model_fsdp_planprédéfini du modèle et le_fsdp_planau niveau de la classe. Transmettez un dictionnaire pour remplacer des règles individuelles ; les règles non spécifiées sont conservées. Appliqué uniquement lorsquefsdp_size > 1. - fsdp_cpu_offload (
bool, facultatif, valeur par défautFalse) — Indique s'il faut activer le déchargement sur CPU pour FSDP2. - fsdp_mixed_precision (
bool, facultatif, valeur par défautFalse) — Indique s'il faut activer la précision mixte pour FSDP2. - pp_size (
int, facultatif) — Nombre d’appareils pour le parallélisme par pipeline. SiNoneet qu’un autre mode de parallélisme est défini, la valeur par défaut est 1. - ep_size (
int, facultatif) — Nombre d’appareils possédant des fragments d’experts distincts. La valeur par défaut est 1. Définissez-le explicitement pour activer l’EP. Il doit être un multiple detp_sizeet diviserfsdp_size * tp_size. Les plans d’experts all-reduce exigentep_size=tp_size; la distribution de jetons ("ep_dispatch_experts") autorise égalementep_size > tp_size. - ep_plan (
dict[str, str], facultatif) — Plan de fractionnement parallèle des experts. Laissez la valeur àNonepour utiliser lebase_model_ep_planprédéfini du modèle. Transmettez un dictionnaire pour remplacer des règles individuelles de ce plan ; les règles non spécifiées sont conservées. Appliqué uniquement lorsqueep_size > 1, et ses règles prennent le pas sur les règles detp_planpour les mêmes modules. Une règle"ep_dispatch_experts"sélectionne la distribution de jetons all-to-all au lieu du masquage du routeur et de l'all-reduce.
Configuration pour l'inférence et l'entraînement distribués natifs avec parallélisme par tenseurs, par pipeline ou FSDP2.