Expert parallelism and MoE serving
Code version: vLLM @ 3e44c0d · 2026-10-22
In one sentence
vLLM runs each MoE layer as router, dispatch, local experts and combine. Expert parallelism spreads the experts over all DP × TP ranks.
The idea#
A Mixture-of-Experts layer has many expert MLPs, and a router sends each token to its top-k experts (Mixture of Experts). With expert parallelism, each GPU holds only some experts, and tokens travel to the GPUs of their experts and back (Expert parallelism). This page shows how vLLM builds such a layer, how it chooses the communication and the kernels, and which flags change it.
vLLM splits the work of one MoE layer into four parts. The router picks the experts. The dispatch moves the tokens. The experts compute. The combine brings the weighted results back. A modular MoE kernel keeps dispatch and combine in one object and the experts in another, so any communication method can work with any expert kernel. The communication method is the all2all backend.
Without --enable-expert-parallel, an MoE model still runs on several GPUs, but each expert is split with tensor parallelism over all DP × TP ranks. With the flag, each rank owns whole experts. A large MoE deployment usually runs data parallel attention plus expert parallel MoE: -dp 8 -ep.
An everyday comparison. Think of a hospital with specialists on several floors. At the front desk (the router), each patient gets a list of two specialists. Porters (dispatch) take the patients to the right floors, the specialists see them (experts), and the porters bring the patients back with the reports (combine). The comparison stops here. In vLLM, every patient on every floor moves in the same time slot, so a floor with no patients still joins each slot.
The picture#
Follow the token ids. Rank 0's token t0 chose experts 1 and 5. So both ranks compute part of it, and the combine sums the two parts on rank 0. Panel 3 is the code view: the runner owns the layer; the kernel is "prepare/finalize + experts".
In the code#
Step 1: FusedMoEFactory builds the layer#
A model builds an MoE layer with FusedMoEFactory. It is a function, not a class, and it returns an MoERunner (vllm/model_executor/layers/fused_moe/layer.py:92-153). It builds the parts in this order:
- The MoE parallel config from the current TP, DP and PCP sizes (
vllm/model_executor/layers/fused_moe/layer.py:238-244). - The expert counts: global = model experts + redundant experts (
vllm/model_executor/layers/fused_moe/layer.py:76-89). - An EPLB layer state, only with EPLB on. Then the experts must divide evenly over the EP ranks (
vllm/model_executor/layers/fused_moe/layer.py:269-284). - An
ExpertMapManagerthat knows which experts are local (vllm/model_executor/layers/fused_moe/layer.py:291-302). - The router, unless the model passes its own (
vllm/model_executor/layers/fused_moe/layer.py:306-346). - A
FusedMoEConfig, theRoutedExpertsobject that holds the expert weights, and theMoERunner(vllm/model_executor/layers/fused_moe/layer.py:361-470).
The runner calls the layer through a registered custom op: torch.ops.vllm.moe_forward, or moe_forward_shared with shared experts. So torch.compile sees one opaque node (vllm/model_executor/layers/fused_moe/runner/moe_runner.py:309-327, vllm/model_executor/layers/fused_moe/runner/moe_runner.py:198-214).
Step 2: does this layer use expert parallelism?#
use_ep = (
dp_size_ * pcp_size_ * tp_size_ > 1
and vllm_parallel_config.enable_expert_parallel
)
dp_size = dp_size_
dp_rank = get_dp_group().rank_in_group if dp_size > 1 else 0
pcp_size = pcp_size_
pcp_rank = get_pcp_group().rank_in_group if pcp_size > 1 else 0
tp_size, tp_rank = FusedMoEParallelConfig.flatten_tp_across_dp_and_pcp(
tp_size_, dp_size_, dp_rank, pcp_size_, pcp_rank
)
if not use_ep:
return FusedMoEParallelConfig(
tp_size=tp_size,
tp_rank=tp_rank,
pcp_size=pcp_size,
pcp_rank=pcp_rank,
dp_size=dp_size,
dp_rank=dp_rank,
ep_size=1,
ep_rank=0,
sp_size=sp_size_,
use_ep=False,
all2all_backend=vllm_parallel_config.all2all_backend,
enable_eplb=vllm_parallel_config.enable_eplb,
)
# DP + EP / TP + EP / DP + TP + EP
assert use_ep
# In EP, each device owns a set of experts fully. There is no tensor
# parallel update tp_size, tp_rank, ep_size and ep_rank to reflect that.
ep_size = tp_size
ep_rank = tp_rank
return FusedMoEParallelConfig(
tp_size=1,
tp_rank=0,
pcp_size=pcp_size,
pcp_rank=pcp_rank,
dp_size=dp_size,
dp_rank=dp_rank,
ep_size=ep_size,
ep_rank=ep_rank,
sp_size=sp_size_,
use_ep=True,
all2all_backend=vllm_parallel_config.all2all_backend,
enable_eplb=vllm_parallel_config.enable_eplb,- Lines 1198-1201. EP is used only when there is more than one rank and
--enable-expert-parallelis set. The flag is off by default (vllm/config/parallel.py:190-191). - Lines 1207-1209. The TP size of the MoE layer is "flattened" over DP and PCP: DP × PCP × TP ranks (
vllm/model_executor/layers/fused_moe/config.py:1105-1114). - Lines 1211-1226. Without EP, the experts are split with TP over all those ranks, and
ep_sizeis 1. - Lines 1230-1231. With EP, the flattened TP size becomes the EP size, and the MoE TP size becomes 1: each rank owns whole experts.
The docstring lists six cases. One is "TP = 2, DP(PCP) = 2 and EP = True … the experts are split between the 4 devices" (vllm/model_executor/layers/fused_moe/config.py:1187-1195). The process group behind it is the EP group, built for every MoE model (Process groups: how vLLM slices its ranks).
Step 3: which experts live on which rank#
--expert-placement-strategy is linear by default: with 4 experts and 2 ranks, rank 0 gets [0, 1] and rank 1 gets [2, 3]. round_robin gives [0, 2] and [1, 3] (vllm/config/parallel.py:210-219). The expert_map is a tensor of shape [global_num_experts] with the local index of each local expert and −1 for the others. It exists only when ep_size > 1 (vllm/model_executor/layers/fused_moe/expert_map_manager.py:152-180).
Step 4: the modular kernel: prepare/finalize plus experts#
The design comment in modular_kernel.py names the four parts: "[Router] → [Quantize-Dispatch] → [Permute-Experts-Unpermute] → [Combine]" (vllm/model_executor/layers/fused_moe/modular_kernel.py:51-84). Two abstract classes carry them:
FusedMoEPrepareAndFinalize: quantize and dispatch the inputs, then combine the outputs. Dispatch and combine sit in one class, because they may use collectives that must match (vllm/model_executor/layers/fused_moe/modular_kernel.py:186-193).FusedMoEExperts: the expert GEMMs, with checks such asis_supported_configfor the device, quantization, activation and parallel config (vllm/model_executor/layers/fused_moe/modular_kernel.py:487-526,vllm/model_executor/layers/fused_moe/modular_kernel.py:558-614).
FusedMoEKernel joins one of each; both must be "modular" or both "monolithic". A monolithic pair works on the router logits instead of top-k ids (vllm/model_executor/layers/fused_moe/modular_kernel.py:1678-1710).
Step 5: selecting the experts implementation#
Each quantization type has an "oracle" that picks the experts class (vllm/model_executor/layers/fused_moe/oracle/base.py:1-20). For BF16 weights on CUDA, the priority list is FlashInfer TRT-LLM, FlashInfer CUTLASS, Triton, batched Triton. On Hopper (compute capability 9.x) both FlashInfer entries move to the back, because they are slower there. With DP > 1 FlashInfer CUTLASS moves back too:
if current_platform.is_rocm():
_AVAILABLE_BACKENDS = [
UnquantizedMoeBackend.AITER,
UnquantizedMoeBackend.TRITON,
UnquantizedMoeBackend.BATCHED_TRITON,
]
elif current_platform.is_cuda():
_AVAILABLE_BACKENDS = [
UnquantizedMoeBackend.FLASHINFER_TRTLLM,
UnquantizedMoeBackend.FLASHINFER_CUTLASS,
UnquantizedMoeBackend.TRITON,
UnquantizedMoeBackend.BATCHED_TRITON,
]
# On Hopper (SM90), the FlashInfer unquantized MoE kernels are slower
# than Triton, so prefer Triton by default.
if current_platform.is_device_capability_family(90):
_move_to_back(_AVAILABLE_BACKENDS, UnquantizedMoeBackend.FLASHINFER_TRTLLM)
_move_to_back(_AVAILABLE_BACKENDS, UnquantizedMoeBackend.FLASHINFER_CUTLASS)
# HACK: Qwen3.5 has crash with FLASHINFER_CUTLASS BF16 if DEP.
# Updating the oracle querying logic is out of the scope of this
# PR. Need to fix the kernel or update structure in follow up.
if moe_config.moe_parallel_config.dp_size > 1:
_move_to_back(_AVAILABLE_BACKENDS, UnquantizedMoeBackend.FLASHINFER_CUTLASS)The oracle takes the first backend whose kernel class accepts the config, and logs "Using <backend> Unquantized MoE backend out of potential backends: [...]" (vllm/model_executor/layers/fused_moe/oracle/unquantized.py:263-268, vllm/model_executor/layers/fused_moe/oracle/unquantized.py:359-372). --moe-backend forces a choice; its default is auto (vllm/config/kernel.py:302-306). Then the oracle builds the prepare/finalize object and logs its class name too (vllm/model_executor/layers/fused_moe/oracle/unquantized.py:518-530). Kernel internals are out of scope for this site.
Step 6: selecting dispatch and combine: --all2all-backend#
--all2all-backend defaults to allgather_reducescatter (vllm/config/parallel.py:220-234). The removed names naive and pplx fall back to it with a warning (vllm/config/parallel.py:537-543). maybe_make_prepare_finalize maps the choice to a class:
def maybe_make_prepare_finalize(
moe: FusedMoEConfig,
quant_config: FusedMoEQuantConfig | None,
routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
allow_new_interface: bool = False,
use_monolithic: bool = False,
all2all_manager: Any | None = None,
input_dtype: torch.dtype | None = None,
) -> FusedMoEPrepareAndFinalize | None:
if moe.moe_parallel_config.use_passthrough_all2all:
# The experts dispatch and combine themselves, in every EP topology.
return PassThroughPrepareAndFinalize()
if not moe.moe_parallel_config.use_all2all_kernels:
if not allow_new_interface:
return None
# Opt-in XPU batched path: reorganize tokens into E x T x K locally
# (no all-to-all) so BatchedTritonExperts (moe_mmk TD) can run.
if current_platform.is_xpu() and moe.moe_backend == "batched_triton":
return BatchedPrepareAndFinalize(
max_num_tokens=moe.max_num_tokens,
num_local_experts=moe.num_local_experts,
num_dispatchers=1,
rank=moe.moe_parallel_config.ep_rank,
)
# For DP/TP case, fall back to naive P/F.
if moe.moe_parallel_config.dp_size > 1:
logger.info_once(
"Detected DP deployment with no --enable-expert-parallel. "
"Falling back to AllGather+ReduceScatter dispatch/combine."
)
if all2all_manager is None:
all2all_manager = get_ep_all2all_manager()
return make_moe_prepare_and_finalize_naive_dp_ep(
is_sequence_parallel=moe.moe_parallel_config.is_sequence_parallel,
num_dispatchers=all2all_manager.world_size,
use_monolithic=use_monolithic,
)
else:
return make_moe_prepare_and_finalize_no_dp_ep(use_monolithic)
if all2all_manager is None:
all2all_manager = get_ep_all2all_manager()
prepare_finalize: FusedMoEPrepareAndFinalize | None = None
if moe.use_deepep_ht_kernels:
assert moe.dp_size == all2all_manager.dp_world_size
- Lines 191-193.
passthrough: the MoE backend dispatches and combines by itself. vLLM binds it automatically for such backends; the CLI does not offer it (vllm/engine/arg_utils.py:1148-1151,vllm/config/kernel.py:194-229). - Lines 195-223. No all2all kernels (no EP, or one rank). With DP > 1 the experts are TP-split over all DP ranks, so each rank still needs every rank's tokens. vLLM logs "Detected DP deployment with no --enable-expert-parallel" and uses the all-gather/reduce-scatter object. Without DP, a local object needs no communication.
- Lines 225-230 and on. With EP, the code takes the all2all manager of the EP group's device communicator. Then it builds one class per backend: DeepEP high-throughput, low-latency and v2, MoonEP, MoRI, FlashInfer NVLink, all-gather/reduce-scatter, NIXL-EP (lines 232-420).
The managers themselves are made by the EP group's CudaCommunicator when use_all2all is true (vllm/distributed/device_communicators/cuda_communicator.py:202-283). The default manager is AgRsAll2AllManager: dispatch is an all-gather, combine a reduce-scatter, over the DP group or, with sequence-parallel MoE, the EP group (vllm/distributed/device_communicators/all2all.py:67-173).
Step 7: what DP + EP looks like for a large MoE#
vllm serve <large MoE model> -dp 8 --enable-expert-parallel # one node, 8 GPUs, TP = 1
- Eight engine cores, each a
DPEngineCoreProcwith one worker (vllm/v1/engine/core.py:1534-1537). Each rank runs attention only for its own requests, with its own KV cache. - The MoE layers use EP size 8: each GPU owns 1/8 of the experts. The EP group spans all 8 ranks (Process groups: how vLLM slices its ranks).
- All ranks step together in waves; an idle rank runs dummy batches, and the workers agree on the padded batch size each step (Data parallel serving: several engine cores behind one endpoint).
- With TP > 1, DP > 1 and EP, the MoE input is sequence parallel for most backends. So TP ranks do not compute the same tokens twice (
vllm/config/parallel.py:759-785). This applies only to models that pass the setting to their MoE layer, for example Qwen3-MoE (vllm/model_executor/models/qwen3_moe.py:152,vllm/model_executor/layers/fused_moe/layer.py:59).
Deep dive#
The sections below give the full detail. Open them one at a time, or use Open all deep dives at the top of the page.
Deep diveThe all2all backends at a glance
--all2all-backend | Manager (EP communicator) | Prepare/finalize | Notes from the code |
|---|---|---|---|
allgather_reducescatter (default) | AgRsAll2AllManager | naive DP/EP (all-gather + reduce-scatter) | works with any EP + DP setup; every rank receives every token |
deepep_high_throughput | DeepEPHTAll2AllManager | DeepEPHTPrepareAndFinalize | doc string: "deepep high-throughput kernels" |
deepep_low_latency | DeepEPLLAll2AllManager | DeepEPLLPrepareAndFinalize | batched expert format; FP8 dispatch for block-FP8 models |
deepep_v2 | DeepEPV2All2AllManager | DeepEPV2PrepareAndFinalize | not in the doc list of the field; in the type (vllm/config/parallel.py:45-60) |
mori_high_throughput, mori_low_latency | MoriAll2AllManager | MoriPrepareAndFinalize | MoRI EP for multi-node |
moonep | MoonEPAll2AllManager | MoonEPPrepareAndFinalize | "balanced EP with dynamic redundant experts (NVLink)" |
nixl_ep | NixlEPAll2AllManager | NIXL-EP | batched expert format |
flashinfer_nvlink_one_sided, flashinfer_nvlink_two_sided | FlashInfer managers | FlashInfer NVLink | flashinfer_all2allv is a deprecated alias of the two-sided one |
passthrough | PassThroughAll2AllManager | PassThroughPrepareAndFinalize | bound automatically for MoE backends that communicate themselves |
Sources: managers vllm/distributed/device_communicators/cuda_communicator.py:202-283; prepare/finalize vllm/model_executor/layers/fused_moe/all2all_utils.py:182-420; descriptions vllm/config/parallel.py:220-234. A backend needs its library to be installed (DeepEP, NIXL, MoRI, FlashInfer); vLLM does not ship them.
Deep diveWorked example: expert counts and memory with EPLB
The EPLB module docstring gives the example: DeepSeek-R1 has 256 logical experts per MoE layer. With 32 redundant experts there are 288 physical experts, and with 32 EP ranks each GPU holds 288 / 32 = 9 (vllm/distributed/eplb/eplb_state.py:3-25).
The same model with -dp 8 -ep (EP size 8):
- No EPLB: 256 / 8 = 32 experts per GPU.
num_redundant_expertsmust stay 0, or the config raises (vllm/config/parallel.py:585-592). --enable-eplb --eplb-config '{"num_redundant_experts": 8}': global experts = 256 + 8 = 264 (vllm/model_executor/layers/fused_moe/layer.py:82). 264 / 8 = 33 per GPU; the division must be exact, elseValueError(vllm/model_executor/layers/fused_moe/layer.py:272-279).num_redundant_expertscounts the extra experts of the whole model, not per rank (derived from line 82). The docs table says "Additional global experts per EP rank beyond equal distribution", which reads differently (docs/serving/expert_parallel_deployment.md:156).
Deep diveEPLB: when and how the experts move
EPLBConfig fields and defaults (vllm/config/parallel.py:63-119):
| Field | Default | Meaning |
|---|---|---|
window_size | 1000 | steps of expert load that are kept |
step_interval | 3000 | steps between two rearrangements |
num_redundant_experts | 0 | extra physical copies of popular experts |
log_balancedness, log_balancedness_interval | False, 1 | log average/max load per rank (costs a sync) |
use_async | True | move weights in the background |
policy | default | the rebalancing algorithm (adapted from DeepSeek EPLB, vllm/distributed/eplb/policy/default.py:3-11) |
communicator | None: XCCL on XPU, else NIXL if present, else gloo | how expert weights travel |
Validation: EPLB needs --enable-expert-parallel, a CUDA, ROCm or XPU platform, and TP × PCP × DP > 1 (vllm/config/parallel.py:563-583). Async EPLB refuses the torch_nccl and pynccl communicators (vllm/config/parallel.py:121-130).
How one cycle runs (Model Runner V2):
- The router kernels record how many tokens each physical expert got in this step (
expert_load_pass). - After each
sample_tokens(and each dummy run, marked as dummy), a decorator callsEplbState.step()(vllm/v1/worker/gpu/eplb_utils.py:46-63,vllm/v1/worker/gpu/model_runner.py:2113-2117). step()copies the step's load into a sliding window ofwindow_sizeslots. It records only in the lastwindow_sizesteps before a rearrangement (or for the balancedness log), and never for dummy steps (vllm/distributed/eplb/eplb_state.py:647-660,vllm/distributed/eplb/eplb_state.py:697-717). It counts every step, dummy or not, so all ranks stay in sync (lines 662-666).- When the counter reaches
step_interval,rearrange()runs: it sums the window across the EP ranks, runs the policy, and moves expert weights between GPUs (vllm/distributed/eplb/eplb_state.py:681-693,vllm/distributed/eplb/eplb_state.py:758-800).
The weight moves use the separate EPLB process group, so they do not deadlock with the MoE collectives of the forward pass (vllm/distributed/parallel_state.py:2347-2350).
Deep diveDBO: dual batch overlap
DBO overlaps the MoE all-to-all with compute. The model runner splits a batch into two microbatches and runs each in its own CPU thread. It switches between them at yield points in the modular kernel. While one microbatch waits for dispatch or combine, the other computes (docs/design/dbo.md:5-11). The design doc shows the schedule (lines 15-28).
- Flags.
--enable-dbo(off),--dbo-decode-token-threshold(32) and--dbo-prefill-token-threshold(512): a batch smaller than the threshold runs as one batch (vllm/config/parallel.py:258-271). - Backends. On CUDA, microbatching asserts
deepep_low_latency,deepep_high_throughputornixl_ep; ROCm also allowsallgather_reducescatter(vllm/config/vllm.py:2159-2181). The design doc names only DeepEP (docs/design/dbo.md:37). - Model runner. Model Runner V2 lists DBO as unsupported unless you set
VLLM_USE_V2_MODEL_RUNNERexplicitly. So vLLM uses the fallback runner (vllm/config/vllm.py:3181-3194, The model runner (Model Runner V2)). With the variable set, V2 still refuses DBO with LoRA, speculative decoding, PP, context parallelism, multimodal, hybrid models and PIECEWISE CUDA graphs (lines 3196-3218). - Lockstep. All DP ranks must agree to microbatch; the per-step DP sync carries an "allow microbatching" vote (
vllm/v1/worker/gpu/dp_utils.py:69,vllm/v1/worker/gpu/dp_utils.py:96-112).
Deep diveElastic EP in one paragraph
--enable-elastic-ep lets a running MoE deployment change its DP (and so EP) size. The config requires EPLB, no PP and no hybrid load balancing; async EPLB then needs NIXL (vllm/config/parallel.py:975-997). The DP, EP and EPLB groups become stateless groups on a TCP store, so they can be rebuilt (vllm/distributed/parallel_state.py:2297-2305). Only one API server runs: a larger count is capped to 1 with a warning (vllm/entrypoints/cli/serve.py:138-145). You scale with POST /scale_elastic_ep and a body {"new_data_parallel_size": N, "drain_timeout": 120} (vllm/entrypoints/serve/elastic_ep/api_router.py:28-66). In internal load-balancing mode, the scale-up and scale-down code needs --data-parallel-backend ray (vllm/v1/engine/core_client.py:1920-1922).
The route is registered for every server of a generative model, also without --enable-elastic-ep (vllm/entrypoints/launchers/api_server/routers.py:55-66). Its path is not under the prefixes that --api-key checks (vllm/entrypoints/serve/middleware/authenticate.py:11). Without elastic EP, a call gives these results (traced in the code):
- Missing or invalid body values: HTTP 400 (
vllm/entrypoints/serve/elastic_ep/api_router.py:44-61). - The current DP size:
AsyncLLMlogs "Data parallel size is already N, skipping scale" and changes nothing. The route answers 200 (vllm/v1/engine/async_llm.py:1301-1308,vllm/entrypoints/serve/elastic_ep/api_router.py:65-70). - Another size, one engine core:
AsyncMPClientkeeps the base method, which raisesNotImplementedError. The route logs "Scale failed" and answers 500 (vllm/v1/engine/core_client.py:274-275,vllm/entrypoints/serve/elastic_ep/api_router.py:79-81). - Another size, DP > 1, external load balancing:
NotImplementedError"Elastic EP scaling requires enable_elastic_ep=True.", HTTP 500 (vllm/v1/engine/core_client.py:1659-1664,vllm/v1/engine/core_client.py:1674-1683). - Another size, DP > 1, internal load balancing, default
mpDP backend: the assertion "Only ray DP backend supports scaling elastic EP" fails, HTTP 500 (vllm/v1/engine/core_client.py:1913-1922). - The same with the Ray DP backend: a larger size gives HTTP 400, because without elastic EP the maximum DP size is the start size (
vllm/config/parallel.py:1037-1042,vllm/v1/engine/core_client.py:1924-1929). A smaller size gives HTTP 400 when the experts do not fit on fewer engines, which is always true with no redundant experts (lines 1930-1945).
In these cases the error comes before set_scaling_elastic_ep(True), so the server does not hold back new requests (vllm/v1/engine/async_llm.py:1310, vllm/v1/engine/async_llm.py:1329). The code in this path does not check --enable-elastic-ep itself (vllm/v1/engine/core_client.py:1913-1952). One case remains: the Ray DP backend and a smaller size that passes the expert check. Then the client sends reconfiguration calls to the engines. This site does not trace that case further.
What can go wrong#
- EP without DP or TP does nothing. With one rank,
use_epis false even with the flag (vllm/model_executor/layers/fused_moe/config.py:1198-1201). - EPLB flags without EP.
--enable-eplbwithout--enable-expert-parallelraises "enable_expert_parallel must be True to use EPLB." (vllm/config/parallel.py:569-570). - Uneven experts. With EPLB, global experts must divide by the EP size (
vllm/model_executor/layers/fused_moe/layer.py:272-279). Choosenum_redundant_expertsto fit. - Missing library. DeepEP, NIXL-EP, MoRI or FlashInfer backends need their packages; an unknown backend name raises "Unknown all2all backend" (
vllm/distributed/device_communicators/cuda_communicator.py:277-278). - The default backend moves every token to every rank.
allgather_reducescatteris simple but sends DP × the tokens of each rank in each layer (vllm/distributed/device_communicators/all2all.py:128-161). At scale, the DeepEP-style backends send only the needed tokens. - Unexpected fallback runner.
--enable-dbomoves the model to the fallback model runner unlessVLLM_USE_V2_MODEL_RUNNERis set (DBO deep dive).
Why it matters. The largest open models are MoE models. Their throughput depends on how tokens move between GPUs, so the all2all backend, the expert kernel and EPLB are among the most important serving choices.
Check yourself#
- 1What doeseasy
FusedMoEFactoryreturn?Show answer
An
MoERunner, which holds the router and theRoutedExperts(vllm/model_executor/layers/fused_moe/layer.py:447-470). - 2What is the defaulteasy
--all2all-backend, and how does it dispatch?Show answer
allgather_reducescatter(vllm/config/parallel.py:220). Dispatch is an all-gather of all tokens; combine is a reduce-scatter (vllm/distributed/device_communicators/all2all.py:67-173). - 3Withmedium
-tp 2 -dp 2 --enable-expert-parallel, what are the MoE TP size and EP size of each layer?Show answer
The flattened TP size is DP × PCP × TP = 4. With EP,
ep_size= 4 and the MoE TP size = 1 (vllm/model_executor/layers/fused_moe/config.py:1105-1114,vllm/model_executor/layers/fused_moe/config.py:1227-1244). - 4On an H100, which unquantized MoE backend does the oracle try first, and why?hard
Show answer
Triton. The CUDA list starts with the two FlashInfer backends. On compute capability 9.x both move to the back, because they are slower than Triton there (
vllm/model_executor/layers/fused_moe/oracle/unquantized.py:68-80). - 5EPLB is on with the defaults. A server runs 2,000 steps per minute. How often are the experts rearranged, and how much history is used?hard
Show answer
Every 3,000 steps, so about every 90 seconds, using the load of the last 1,000 recorded steps (
vllm/config/parallel.py:67-75,vllm/distributed/eplb/eplb_state.py:681-693). Dummy steps advance the counter but record no load (vllm/distributed/eplb/eplb_state.py:587-590,vllm/distributed/eplb/eplb_state.py:662-666).