Transformers documentation
エキスパート並列
エキスパート並列
Expert parallelism is a parallelism strategy for mixture-of-experts (MoE) models. Each expert’s feedforward layer lives on a different hardware accelerator. A router dispatches tokens to the appropriate experts and gathers the results. This approach scales models to far larger parameter counts without increasing computation cost because each token activates only a few experts.
DistributedConfig
DistributedConfig クラスと ep_size 引数を使用して、エキスパート並列化を有効にします。MoE モデルは "ep_dispatch_experts" でエキスパートをシャーディングします。その実装は、トークンがエキスパートにどのように到達するかを選択します。トークンディスパッチにより、ep_size と tp_size を異ならせることができます。一部の EP 実装では ep_size=tp_size が必要であり、例えば "all_reduce" や "deepgemm_megamoe" では、エキスパートグループのすべてのランクが同じトークンを保持します。
Llama 4にはEPプランがありません。そのエキスパートは、ルーターのスコアで既にスケーリング済みのすべてのトークンを処理するため、ディスパッチするものがありません。代わりに、テンソル並列性(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 モデルは、その設定で2つのプランを定義します: 稠密モジュール用の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_dispatch_experts"ルールは、トークンがエキスパートにどのように到達するかを選択します。
tp_plan は tp_size > 1 の場合のみ適用され、ep_plan は ep_size > 1 の場合のみ適用されます。TP が有効で EP が無効の場合、エキスパートのルールを含む完全な TP プランが適用されます。
"ep_dispatch_experts" は、ランクごとにエキスパート全体をシャード化します(ep_size は tp_size の倍数であり、fsdp_size * tp_size を割り切ります)。アクセスには ep_implementation で指定された実装を使用します。実装の選択を参照してください。
実装の選択
"ep_dispatch_experts" は、エキスパートを一度だけシャード化し、DistributedConfig の ep_implementation で選択される EPImplementationInterface に登録された実装から、トークンがエキスパートに到達する方法を決定します。
ep_implementation | 利点 | デメリット |
|---|---|---|
"all_to_all"、トークンディスパッチを参照 | ep_size > tp_size を含む、すべてのレイアウトで動作します。ルーティングされたトークンのみが移動します。 | レイヤーごとに2回のall-to-all集約と1回のホスト同期が発生するため、CUDAグラフでのキャプチャは行われません。 |
"all_reduce" | トークンの移動もホスト同期も発生しないため、デコードは CUDA グラフでキャプチャされます。 | ep_size=tp_sizeが必要です。すべてのランクがバッチ全体でエキスパートを実行するため、コストはバッチサイズに比例して増加します。 |
"deepgemm_megamoe" | DeepGEMM Mega MoE は、dispatch、experts、combine を 1 つのカーネルに融合します。EP グループの各レイヤーは、その対称メモリバッファを共有します。 | ep_size=tp_sizeが必要です。SM100以上では、FP8アクティベーションとFP4エキスパートを使用します。推論専用です。 |
デフォルトの"auto" | 推論時に、エキスパートグループのランクが1つのバッチを共有している場合は"all_reduce"を実行し、それ以外の場合は"all_to_all"を実行します。 | 必ずしも最速の選択肢とは限りません: ワークロードはカスタム実装によって恩恵を受ける場合があります。実装の追加を参照してください。 |
ep_implementationは、DistributedConfigで設定された並列サイズの中から選択するだけです。エキスパートグループのランクが異なるバッチを保持する場合、例えばtp_size=1かつfsdp_size=ep_sizeの場合、適用可能な実装は"all_to_all"のみです。
実装の追加
実装はEPImplementationをサブクラス化し、ep_implementationの名前で登録されます。各expertsモジュールごとに一度だけ構築されるため、通信バッファなどの状態を保持できます。このランクが保持するバッチと、グローバルなexpert IDを引数として呼び出すと、各トークンに対する重み付きexpert出力が返されます。このランクのexpertは、experts_forwardによって、行、ローカルexpert ID、およびそれらに付与された重みを用いて実行されます。
from transformers.distributed.expert_parallel import EPImplementation, EPImplementationInterface
class MyEP(EPImplementation):
def __call__(self, hidden_states, top_k_index, top_k_weights):
tokens, expert_ids, weights = ... # bring this rank's experts the rows routed to them
expert_output = self.experts_forward(tokens, expert_ids, weights)
return ... # the weighted expert output for each token of the batch this rank holds
EPImplementationInterface.register("my_ep", MyEP)
distributed_config = DistributedConfig(tp_size=1, fsdp_size=8, ep_size=8, ep_implementation="my_ep")勾配は、トークンを保持するすべてのランク(ルーティング重みを含む)に到達する必要があります。そのため、autograd が微分しない集合通信を
torch.autograd.Functionでラップしてください。
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 が発生します。パスは、読み込まれたモデルから見た完全なパスを使用してください。例えば、causal LM には model.layers.*、そのベースモデルには 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 を明示的に設定してください。
ep_size=tp_sizeの場合、デフォルトの"auto"実装は推論時に"all_reduce"を実行し、学習時にトークンをディスパッチします。学習時にもマスクとall-reduceを行うには、ep_implementation="all_reduce"を設定してください。
distributed_config = DistributedConfig(tp_size=4, ep_size=4, ep_implementation="all_reduce")トークンディスパッチ
Token dispatch は "all_to_all" の実装です。各ランクはバッチの自分の担当部分で学習を行います。各 MoE レイヤーにおいて、ランクは自分のトークンをルーティングし、all-to-all によって各 (token, expert) ペアをその expert を所有するランクに送信します。次に、受信したデータに対してローカルの expert を実行し、2 回目の all-to-all で結果を受け取り、ルーティング重みと結合します。移動するのはルーティングされた活性化値と expert の出力のみであり、どのランクも自分が所有していないトークンの expert を計算することはありません。
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によって制約されません。モデルの残りの部分については:
- エキスパート以外のパラメータは、
fsdp上でFSDP2によってシャード化され、それらの勾配が削減されます。 - エキスパートは
epにシャード化され、efsdp_size = fsdp_size * tp_size // ep_sizeが1より大きい場合は、さらにefsdpにFSDPシャード化されます。常にFSDPでラップされるため、fsdp_mixed_precisionおよびfsdp_cpu_offloadも適用され、save_pretrained()は他のパラメータと同様にこれらを収集します。 - Expert parallel グループは
ep_size / tp_size個のバッチを保持するため、ある expert の勾配は、その数のバッチに対する和になります。efsdpの reduction は、そのグループサイズではなくfsdp_sizeで割るため、FSDP2 が dense モジュールに対して採用するバッチごとの平均値と同じになります。 - すべてのローカルエキスパートは、レイヤーごとにゼロパッド行を1行処理します。エキスパートがトークンを1つも受け取らなかったランクであっても、reverse all-to-allおよびエキスパートの勾配リダクションに参加します。
サイズの組み合わせ方
tp_size * fsdp_sizeはプロセス数を表します。ep_sizeはプロセス数を増やしません。エキスパートの重みのために同じランクを再グループ化するだけです。ep_sizeは、エキスパートのリストをep_size個のブロックに分割します。各ランクはnum_experts / ep_size個のエキスパートを計算し、ep_size個の連続するランクが1つの完全なセットを保持します。このランクの集合が、all-to-allが実行されるグループです。efsdp_size = fsdp_size * tp_size / ep_sizeは、エキスパートセットの完全なコピーが何個存在するかを示します。異なるコピー内で同じ位置にあるランクは、メモリ削減のためにそれらのエキスパートをシャード化し、勾配を平均化します。これは FSDP がデンスモジュールに対して行う処理と同様です。- あるランクが保持するバッチは、
fsdpのみによって決まります。連続するランクがTPグループを形成し、同じバッチを取得します。fsdp_size個のグループはそれぞれ異なるバッチを取得します。
8つのプロセス、tp_size=2, fsdp_size=4、8つのエキスパート:
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
この図から2つの数値が導き出せます:
- EPグループ内では、各トークンは、そのバッチを保持するペアのランクごとに1回ずつ、合計
tp_size回存在します。これはep_sizeには依存しません。 - EPグループは
ep_size / tp_size個の異なるバッチを保持します。これは、あるエキスパートの勾配が合計される対象のバッチ数です。
テンソル並列性を使用する場合
tp_size > 1 を設定すると、TP プランに従って dense モジュールをシャーディングし、エキスパートは dispatch を使用します。8 プロセスの場合:
distributed_config = DistributedConfig(
tp_size=2,
fsdp_size=4,
ep_size=4,
)TP ランクの各ペアは同じバッチを受信します。これは、テンソル並列性がペア内のアクティベーションを複製するためです。両方のランクがすべてのトークンをディスパッチした場合、あるエキスパートを所有するランクはすべてのトークンを2回受信し、2回計算することになり、その重み勾配が2倍になってしまいます。エキスパートは1つのランクに完全に存在するため、重複分を重みで分割することはできません。また、所有権は通常別のランクにあるため、マスキングのように所有権で解決することもできません。したがって、ペアは行を分割します。各 TP ランクはトークンの互いに素な 1 / tp_size をディスパッチし、結果は送信元のランクに返され、ゼロパディングされた半分に対するペア間の all-reduce により、次のレイヤーが期待する複製された出力が復元されます。分割は tp_size によるものであり、ep_size ではありません。これは、バッチを保持しているランクのみが送信できるためです。エキスパートグループは4つのランクにまたがり、各エキスパートは efsdp_size = 2 のランク間で FSDP によりシャード化されます。一方、トランクの FSDP グループは4つのランクにまたがります。モデルの通常の TP 制約(アテンションヘッドの割り切れることなど)は、デンスモジュールにも引き続き適用されます。トークンスライスは不均一または空になる場合があり、単一トークンデコーディング中も同様です。
Token dispatch はまだパイプライン並列化と組み合わせられません。 pp_size=1 を使用してください(未テスト)
これらの設定はそれぞれ 8 つの GPU を使用します:
| 設定 | 結果 |
|---|---|
DistributedConfig(tp_size=4, fsdp_size=2, ep_size=4) | 4つのTPグループのそれぞれが1つのバッチを共有します。学習ではトークンをディスパッチし、各ランクがバッチの4分の1を担当します。推論では"all_reduce"を使用します。ep_implementation="all_reduce"を設定すると、全体でこれを使用します。 |
DistributedConfig(tp_size=1, fsdp_size=8, ep_size=4) | 各ランクで独立したバッチをディスパッチし、スライシングを行わず、エキスパートをランクのペア間でFSDPシャードします。 |
DistributedConfig(tp_size=2, fsdp_size=4, ep_size=4) | Dispatch はバッチごとに TP ペアを割り当て、各ペアが自分のバッチを2分割します。エキスパートグループごとに2つのバッチです。 |
DistributedConfig(tp_size=8, ep_size=8) | すべてのランクが1つのバッチを共有します。学習ではトークンをディスパッチし、各ランクがバッチの8分の1を担当します。推論では"all_reduce"を使用します。ep_implementation="all_reduce"を設定すると、全体でこれを使用します。 |
Trainerは、トークンディスパッチをまだ考慮していません。バッチおよびトークンのカウントは、TPグループのランクがバッチを共有し、
fsdp_size個のデータ並列シャードが存在する、オールリデュースレイアウトを前提としています。Trainerによるディスパッチへの対応は、後続のリリースで提供される予定です。
FSDP2との組み合わせ
Tensor parallelism と expert parallelism は、それぞれ独自のメッシュ tp と ep にわたって重みをシャードしますが、オプティマイザの状態とルールのないモジュールは、グループのすべてのランクにレプリケートされ続けます。これにより、学習できるモデルのサイズが制限されます。別のメッシュ次元で FSDP2 を fsdp_size を用いて追加してください。マスキングと all-reduce を使用する場合、ep_size=tp_size を維持し、ep_implementation="all_reduce" を渡してください。
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_implementation="all_reduce", # "auto" would dispatch tokens in training, which the Trainer does not support yet
)
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に反映できるようにします。
下表は、8xH100 GPU 環境における EP のみの学習と 2D EP+FSDP2 の比較を示しています。ワークロードは、シーケンス長 2048 で bf16 による Qwen3-30B-A3B のフルファインチューニングです。FSDP シャード数を増やすとピークメモリは削減されますが、fsdp 間でエキスパートを all-gather および reduce-scatter するため、tokens/s はやや低下します。
| 設定 | tokens/s/GPU | ピークメモリ/GPU |
|---|---|---|
tp_size=8 | 3485 | 38.6 GB |
tp_size=4, fsdp_size=2 | 2900 | 34.2 GB |
tp_size=2, fsdp_size=4 | 2830 | 32.3 GB |
ロード時にシャード化されたモデルでは、チェックポイントからの再開はまだサポートされていないため、Trainerはそれらに対して
save_only_model=Trueまたはsave_strategy="no"のみを受け入れます。
API リファレンス
class 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 = Nonedp_replicate_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 = Noneep_implementation: str = 'auto' )
パラメータ
- tp_size (
int, 省略可) — テンソル並列化に使用するデバイス数。Noneでtp_planが設定されている場合、デフォルト値はWORLD_SIZE // (other_parallel_size)になります。Noneでtp_planが設定されていない場合、デフォルト値は 1 になります。 - tp_plan (
dict[str, str]または"auto"、省略可) — テンソル並列シャーディング計画。"auto"を渡すか、tp_sizeが設定されている場合はNoneのままにすると、モデルで事前定義されたbase_model_tp_planが使用されます。辞書を渡すことで、その計画の個別ルールを上書きできます。指定されていないルールは保持されます。空の辞書を渡すと、テンソル並列ルールは適用されません。 - enable_sequence_parallel (
bool、任意、デフォルトはFalse) — シーケンス並列処理用に予約されています。まだ実装されていません。 - enable_expert_parallel (
bool、任意、デフォルトはFalse) — 非推奨です。v5.21 で削除されました。ep_sizeを指定しない場合、ep_size=tp_size, tp_plan={}と同じ動作となり、tp_size個のランク全体でエキスパート並列プランのみが適用されます。明示的にep_sizeが指定されている場合は、そちらが優先されます。このフラグはtp_sizeやfsdp_sizeを変更しません。 - fsdp_size (
int, 任意) — FSDP(データ並列)に使用するデバイス数。Noneでtp_sizeが設定されている場合、デフォルト値は1です。 - dp_replicate_size (
int, 任意) — FSDP リプリカの数 (HSDP) です。パラメータはfsdp_size個のデバイス間でシャード化され、dp_replicate_size回リプリカされます。勾配はリプリカ内で reduce-scatter され、その後リプリカ間で all-reduce されます。デフォルト値は 1 です。マルチノード学習での使用を推奨します。通常の FSDP では、各ステップでパラメータの all-gather と勾配の reduce-scatter がすべてのデバイス間で実行されるため、このトラフィックが遅いノード間リンクを通過します。dp_replicate_sizeをノード数に、fsdp_size * tp_sizeをノードあたりのデバイス数に設定すると、シャード化のトラフィックは高速なノード内リンクに留まり、勾配の all-reduce だけがノード間を跨ぐようになります。ノード内でパラメータがシャード化されるため、デバイスあたりのメモリ使用量が増えるというコストがあります。 - fsdp_plan (
dict[str, str]、省略可) — FSDP2 のシャーディング計画です。Noneのままにすると、モデルで事前定義されているbase_model_fsdp_planとクラスレベルの_fsdp_planが使用されます。辞書を渡すことで個々のルールを上書きできます。指定されていないルールは保持されます。dp_replicate_size * fsdp_size > 1の場合のみ適用されます。 - fsdp_cpu_offload (
bool、任意、デフォルトはFalse) — FSDP2 における CPU オフロードを有効にするかどうか。 - fsdp_mixed_precision (
bool、省略可、デフォルトはFalse) — FSDP2 で混合精度を有効にするかどうか。 - pp_size (
int, 任意) — パイプライン並列化に使用するデバイス数。Noneで他の並列モードが設定されている場合、デフォルト値は 1 です。 - ep_size (
int, 省略可) — 異なるエキスパートのシャードを所有するデバイスの数です。デフォルト値は 1 です。EP を有効にするには、明示的に設定してください。tp_sizeの倍数であり、かつfsdp_size * tp_sizeで割り切れる必要があります。ep_size > tp_sizeの場合、"all_to_all"のようなトークンを交換するep_implementationを持つ"ep_dispatch_experts"ルールが必要です。 - ep_plan (
dict[str, str]、省略可) — エキスパート並列シャーディング計画です。Noneのままにすると、モデルで事前定義されているbase_model_ep_planが使用されます。辞書を渡すことで、その計画の個々のルールを上書きできます。指定されていないルールは保持されます。この設定はep_size > 1の場合のみ適用され、同じモジュールに対してはtp_planのルールよりも優先されます。"ep_dispatch_experts"ルールにより、ep_implementationがトークンをエキスパートに届ける方法を決定できます。 - ep_implementation (
str, 省略可, デフォルトは"auto") — 「"ep_dispatch_experts"」ルールがトークンをエキスパートに届ける方法です。EPImplementationに登録されたEPImplementationInterface:"all_to_all"は、ルーティングされたトークンをオールツーオール集約通信で交換します。"all_reduce"は、EP グループが共有するバッチに対して各ランクのエキスパートを実行し、出力を合計します。ep_size == tp_sizeであることが必要です。"deepgemm_megamoe"は、そのバッチを DeepGEMM Mega MoE に引き渡します。このカーネルはトークンの交換を自身で行います。experts_implementation="deepgemm_megamoe"を指定して読み込むことで、これが選択されます。"auto"は、EP グループが 1 つのバッチを共有している場合、推論時に"all_reduce"を実行し、それ以外の場合は"all_to_all"を実行します。
テンソル並列、パイプライン並列、またはFSDP2並列によるネイティブ分散推論および学習の設定。