Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f2b01f7
Load an MoE expert-parallel: shard the experts at load time
shengliangxu Oct 30, 2026
184675c
Fail every rank when one rank's expert-parallel load fails
shengliangxu Oct 30, 2026
40ab9af
Refuse an EP plan whose experts no grouped_gemm entry shards
shengliangxu Oct 30, 2026
b67fa11
Refuse an EP plan that matches no experts or styles some otherwise
shengliangxu Oct 30, 2026
5546c2a
Stand in a list for the unplaced keys in the no-module plan test
shengliangxu Oct 30, 2026
22cf2be
Count the routed experts in the config that holds the expert plan
shengliangxu Oct 30, 2026
07105a2
Refuse an expert-parallel load in which some ranks have no GPU of the…
shengliangxu Oct 30, 2026
9a50ecc
Import the expert-parallel load's helpers at module scope
shengliangxu Oct 30, 2026
baf6dea
Say what the load's failure guarantee rests on
shengliangxu Oct 30, 2026
b795ccc
Refuse an MXFP4 checkpoint in the expert-parallel load
shengliangxu Oct 30, 2026
1f30540
Name the transformers releases the load's failure guarantee was check…
shengliangxu Oct 30, 2026
cb5495d
Decide the expert-parallel load's GPU check from one gather
shengliangxu Oct 30, 2026
02552e6
Keep the no-TP-linear warning off a model loaded expert-parallel
shengliangxu Oct 30, 2026
45eaaf6
Derive the load's kept plan styles from its one style swap
shengliangxu Oct 30, 2026
0d9bed6
Build the EP loader tests' stand-in MoE from the shared test model
shengliangxu Oct 30, 2026
636d08f
Check that one visible GPU per process passes the EP load's GPU check
shengliangxu Oct 30, 2026
2c45e20
Refuse an expert-parallel load on a rank not yet bound to its GPU
shengliangxu Oct 30, 2026
9475053
Pin the CUDA state the EP load's GPU check reads in its two-rank tests
shengliangxu Oct 30, 2026
974c72c
Say the plan checks refuse any other expert-weight style, not drop it
shengliangxu Oct 30, 2026
82c2c91
Refuse ranks the meshes would put on no GPU or on one GPU together
shengliangxu Oct 30, 2026
71cf53d
Pin the single-process EP loader tests' CUDA to none
shengliangxu Oct 30, 2026
160f89b
Keep load_and_prepare_ep's docstring to the caller contract
shengliangxu Oct 30, 2026
930bb0f
Move the EP loader tests to the test files of the modules they cover
shengliangxu Oct 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions modelopt/torch/quantization/plugins/huggingface.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from modelopt.torch.kernels.quantization.gemm import IS_AVAILABLE as IS_TRITON_AVAILABLE
from modelopt.torch.opt.dynamic import DynamicModule
from modelopt.torch.utils import warn_rank_0
from modelopt.torch.utils.distributed import ParallelState
from modelopt.torch.utils.distributed import ParallelState, recorded_distribution_plan

from ..algorithms import AutoQuantizeGradientSearcher
from ..conversion import register
Expand Down Expand Up @@ -596,7 +596,9 @@ def convert_hf_parallel_linears_on_the_fly(model):
elif HFRowParallelLinear.is_compatible(module, tp_mesh):
HFRowParallelLinear.convert(module)
converted = True
if tp_mesh is not None and not converted:
# An expert-parallel load keeps its experts' mesh there too, and shards no Linear by design.
plan = recorded_distribution_plan(model)
if tp_mesh is not None and not converted and not (plan is not None and plan.is_expert_parallel):
warnings.warn(
"Found a transformers tensor-parallel mesh but no TP-sharded linear layer; they will be"
" quantized as unsharded layers."
Expand Down
40 changes: 31 additions & 9 deletions modelopt/torch/utils/moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,25 @@

"""HuggingFace-coupled MoE expert-parallel (EP) helpers.

Finding a model's routed experts, choosing the EP degree they are sharded over, and the DP=EP
all-to-all expert transport (``torch_a2a``).
Finding a model's routed experts, choosing the EP degree they are sharded over, the DP=EP
all-to-all expert transport (``torch_a2a``). The expert-parallel model loader is
:func:`~modelopt.torch.utils.plugins.model_load_utils.load_and_prepare_ep`.

The all-to-all transport is plain ``torch.distributed.all_to_all_single`` (NCCL) wrapped around
transformers' quantizable ``_grouped_linear``. ``register_torch_a2a()`` registers it into
transformers' ``ExpertsInterface`` lazily -- never at import time, so importing this module has no
transformers-``ExpertsInterface`` side effect.
transformers' quantizable ``_grouped_linear``. ``register_torch_a2a()`` registers
it into transformers' ``ExpertsInterface`` (lazily, from ``load_and_prepare_ep``'s a2a path -- never at
import time, so importing this module has no transformers-``ExpertsInterface`` side effect).
"""

import warnings
from typing import TYPE_CHECKING

import torch
import torch.distributed as dist
import torch.nn as nn
from packaging.version import parse
from torch.distributed.nn.functional import all_to_all_single as autograd_all_to_all_single

if TYPE_CHECKING:
from torch import nn
from .distributed import distribution_plan


def require_transformers(min_version: str, feature: str) -> None:
Expand Down Expand Up @@ -406,7 +406,9 @@ def register_torch_a2a():
These two styles are functionally transformers' ``megamoe_router`` / ``megamoe_experts`` (added
in 5.10.4 for DeepGEMM Mega-MoE), reimplemented here so the transport works below 5.10.4 too.
The base classes (``RouterParallel``, ``TensorParallelLayer``) are stock wherever the EP infra
exists (transformers >= 5.6); ``grouped_gemm`` (expert weight sharding) is stock too.
exists (transformers >= 5.6); ``grouped_gemm`` (expert weight sharding) is stock too. A model
builds with ``torch_a2a`` only from 5.7, the floor ``load_and_prepare_ep`` enforces: 5.6 accepts
only the experts implementations it ships.
"""
global _TORCH_A2A_REGISTERED
if _TORCH_A2A_REGISTERED:
Expand Down Expand Up @@ -473,3 +475,23 @@ def shard_tensor(self, param, tensor_idx=None, device=None, dtype=None):
ALL_PARALLEL_STYLES.register("torch_a2a_router", TorchA2ARouter())
ALL_PARALLEL_STYLES.register("torch_a2a_experts", TorchA2AExperts())
_TORCH_A2A_REGISTERED = True


def expert_parallel_mesh(model: nn.Module):
"""The mesh ``model``'s routed experts are sharded over, or ``None`` if its plan shards none.

:func:`~modelopt.torch.utils.plugins.model_load_utils.load_and_prepare_ep` loads the model over
its expert-parallel submesh, and keeps that submesh on the model as ``_device_mesh``.
Calibration takes the mesh to re-install the all-to-all forwards
(:func:`~modelopt.torch.utils.distributed.distributed_forward_loop`).
"""
if not distribution_plan(model).is_expert_parallel:
return None
mesh = getattr(model, "_device_mesh", None)
if mesh is None:
raise RuntimeError(
f"{type(model).__name__} is planned expert-parallel but carries no device mesh. Pass "
"the model load_and_prepare_ep returned, not a submodule of it: the loader keeps the "
"mesh it shards the experts over on that model alone."
)
return mesh
Loading
Loading