Files
wehub-resource-sync 7ce4c8e27e
pre-commit / pre-run-check (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:55:37 +08:00

442 lines
18 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from collections.abc import Callable
from typing import Any
import torch
import vllm.envs as envs
from vllm._aiter_ops import rocm_aiter_ops
from vllm.config import ParallelConfig, get_current_vllm_config
from vllm.distributed import (
get_dp_group,
get_pcp_group,
get_tensor_model_parallel_world_size,
)
from vllm.distributed.eplb.eplb_state import EplbLayerState
from vllm.logger import init_logger
from vllm.model_executor.layers.fused_moe.activation import MoEActivation
from vllm.model_executor.layers.fused_moe.config import (
FusedMoEConfig,
FusedMoEParallelConfig,
)
from vllm.model_executor.layers.fused_moe.expert_map_manager import (
ExpertMapManager,
)
from vllm.model_executor.layers.fused_moe.routed_experts import RoutedExperts
from vllm.model_executor.layers.fused_moe.router.fused_moe_router import (
FusedMoERouter,
)
from vllm.model_executor.layers.fused_moe.router.router_factory import (
create_fused_moe_router,
)
from vllm.model_executor.layers.fused_moe.runner.moe_runner import (
MoERunner,
)
from vllm.model_executor.layers.quantization.base_config import (
QuantizationConfig,
)
logger = init_logger(__name__)
def make_parallel_config(
tp_size: int | None,
dp_size: int | None,
pcp_size: int | None,
is_sequence_parallel: bool,
parallel_config: ParallelConfig,
) -> FusedMoEParallelConfig:
tp_size_ = (
tp_size if tp_size is not None else get_tensor_model_parallel_world_size()
)
dp_size_ = dp_size if dp_size is not None else get_dp_group().world_size
pcp_size_ = pcp_size if pcp_size is not None else get_pcp_group().world_size
sp_size = tp_size_ if is_sequence_parallel else 1
moe_parallel_config = FusedMoEParallelConfig.make(
tp_size_=tp_size_,
pcp_size_=pcp_size_,
dp_size_=dp_size_,
sp_size_=sp_size,
vllm_parallel_config=parallel_config,
)
assert moe_parallel_config.is_sequence_parallel == is_sequence_parallel
logger.debug("FusedMoEParallelConfig = %s", str(moe_parallel_config))
return moe_parallel_config
def determine_expert_counts(
num_experts: int,
num_redundant_experts: int,
n_shared_experts: int | None,
is_act_and_mul: bool,
) -> tuple[int, int, int]:
global_num_experts = num_experts + num_redundant_experts
logical_num_experts = num_experts
# Shared-expert fusion: append the shared expert(s) as routed-expert slots
# so they run in the same grouped GEMM. Gated by
# VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS: either the native aiter fused-MoE
# path (env + master switch, via is_fusion_moe_shared_experts_enabled) or the
# backend-neutral router-append path (env alone, independent of the master
# switch; e.g. the MM3 triton/flydsl mxfp8 MoE). Gated activations only.
fuse_shared_enabled = (
rocm_aiter_ops.is_fusion_moe_shared_experts_enabled()
or envs.VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS
) and is_act_and_mul
num_fused_shared_experts = (
n_shared_experts if n_shared_experts is not None and fuse_shared_enabled else 0
)
return global_num_experts, logical_num_experts, num_fused_shared_experts
# TODO: rename this
def FusedMoE(
num_experts: int, # Global number of experts
top_k: int,
hidden_size: int,
intermediate_size: int,
intermediate_pad: int | None = None,
params_dtype: torch.dtype | None = None,
renormalize: bool = True,
use_grouped_topk: bool = False,
num_expert_group: int | None = None,
topk_group: int | None = None,
quant_config: QuantizationConfig | None = None,
tp_size: int | None = None,
dp_size: int | None = None,
pcp_size: int | None = None,
prefix: str = "",
custom_routing_function: Callable | None = None,
router: FusedMoERouter | None = None,
scoring_func: str = "softmax",
routed_scaling_factor: float = 1.0,
swiglu_limit: float | None = None,
swiglu_alpha: float | None = None,
swiglu_beta: float | None = None,
e_score_correction_bias: torch.Tensor | None = None,
apply_router_weight_on_input: bool = False,
activation: str = "silu",
enable_eplb: bool = False,
num_redundant_experts: int = 0,
has_bias: bool = False,
is_sequence_parallel: bool = False,
reduce_results: bool = True,
ckpt_names: tuple[str, str, str] = ("gate_proj", "down_proj", "up_proj"),
n_shared_experts: int | None = None,
router_logits_dtype: torch.dtype | None = None,
gate: torch.nn.Module | None = None,
shared_experts: torch.nn.Module | None = None,
shared_expert_gate: torch.nn.Module | None = None,
routed_input_transform: torch.nn.Module | None = None,
routed_output_transform: torch.nn.Module | None = None,
apply_routed_scale_to_output: bool = False,
zero_expert_type: str | None = None,
hash_indices_table: torch.Tensor | None = None,
runner_cls: type[MoERunner] | None = None,
runner_args: dict[str, Any] | None = None,
routed_experts_cls: type[RoutedExperts] | None = None,
routed_experts_args: dict[str, Any] | None = None,
) -> MoERunner:
"""Factory function for creating MoE execution pipeline.
Creates and configures a complete MoE execution pipeline including:
- Router (for token-to-expert assignment)
- RoutedExperts (containing expert weight parameters)
- MoERunner (orchestrates the complete forward pass)
The experts contain both MergedColumnParallel weights (gate_up_proj/w13)
and RowParallelLinear weights (down_proj/w2).
Note: Mixtral uses w1, w2, and w3 for gate, up, and down_proj. We
copy that naming convention here and handle any remapping in the
load_weights function in each model implementation.
Args:
num_experts: Number of experts in the model (global count)
top_k: Number of experts selected for each token
hidden_size: Input hidden state size of the transformer
intermediate_size: Intermediate size of the experts
params_dtype: Data type for the parameters
renormalize: Whether to renormalize the logits in the router
use_grouped_topk: Whether to use grouped top-k routing
num_expert_group: Number of expert groups for grouped top-k
topk_group: Top-k value per group for grouped top-k
quant_config: Quantization configuration
tp_size: Tensor parallelism size (None = use global default)
dp_size: Data parallelism size (None = use global default)
pcp_size: Pipeline context parallelism size (None = use global default)
prefix: Layer name prefix for weight loading
custom_routing_function: Custom routing function override
router: Pre-configured router instance (None = create default)
scoring_func: Scoring function for routing ("softmax" or others)
routed_scaling_factor: Scaling factor applied to topk_weights or output
swiglu_limit: SwiGLU activation limit
e_score_correction_bias: Expert score correction bias tensor
apply_router_weight_on_input: Whether to apply router weights on input
activation: Activation function name ("silu", "gelu", etc.)
enable_eplb: Whether to enable expert parallelism load balancer
num_redundant_experts: Number of redundant experts for EPLB
has_bias: Whether expert layers have bias terms
is_sequence_parallel: Whether sequence parallelism is enabled
reduce_results: Whether to all-reduce the final output. Setting this
to False (to fuse the all-reduce downstream) is only honored on
the late-AR path.
ckpt_names: Checkpoint parameter name tuple (gate_proj, down_proj,
up_proj) used for weight loading
n_shared_experts: Number of shared experts to fuse into the routed
grouped GEMM (ROCm; requires aiter FSE or the router-append path)
router_logits_dtype: Data type for router logits buffers
gate: Pre-configured gate module
shared_experts: Pre-configured shared experts module
shared_expert_gate: Pre-configured shared expert gate module
routed_input_transform: Input transformation module
routed_output_transform: Output transformation module
apply_routed_scale_to_output: Whether to apply routed_scaling_factor to
output instead of topk_weights
zero_expert_type: Type of zero expert handling
hash_indices_table: Hash table for expert indices
runner_cls: Custom MoERunner class (None = use default MoERunner)
runner_args: Additional arguments for runner constructor
routed_experts_cls: Custom RoutedExperts class (None = use default)
routed_experts_args: Additional arguments for routed_experts constructor
Returns:
MoERunner: Configured MoE execution pipeline ready for forward passes
"""
vllm_config = get_current_vllm_config()
layer_name = prefix
moe_activation = MoEActivation.from_str(activation)
is_act_and_mul = moe_activation.is_gated
moe_parallel_config = make_parallel_config(
tp_size=tp_size,
dp_size=dp_size,
pcp_size=pcp_size,
is_sequence_parallel=is_sequence_parallel,
parallel_config=vllm_config.parallel_config,
)
# Resolve the deferred all-reduce request against the parallel config.
skip_final_all_reduce = (
not reduce_results
and not moe_parallel_config.use_all2all_kernels
and not moe_parallel_config.is_sequence_parallel
and zero_expert_type is None
)
global_num_experts, logical_num_experts, num_fused_shared_experts = (
determine_expert_counts(
num_experts,
num_redundant_experts,
n_shared_experts,
is_act_and_mul,
)
)
# Initialize EPLB manager (or None?)
eplb_state: EplbLayerState | None = None
if enable_eplb:
use_ep = moe_parallel_config.use_ep
ep_size = moe_parallel_config.ep_size
if use_ep and global_num_experts % ep_size != 0:
raise ValueError(
f"EPLB currently only supports even distribution of "
f"experts across ranks. Got {global_num_experts} experts "
f"and {ep_size} EP ranks."
)
eplb_state = EplbLayerState()
else:
assert num_redundant_experts == 0, (
"Redundant experts are only supported with EPLB."
)
max_num_batched_tokens = vllm_config.scheduler_config.max_num_batched_tokens
# Create ExpertMapManager to handle expert mapping and placement for EP.
# See ExpertMapManager for a detailed description of what it does and when
# it is required.
expert_map_manager = ExpertMapManager(
max_num_batched_tokens=max_num_batched_tokens,
top_k=top_k,
global_num_experts=global_num_experts,
num_redundant_experts=num_redundant_experts,
num_expert_group=num_expert_group,
moe_parallel_config=moe_parallel_config,
placement_strategy=vllm_config.parallel_config.expert_placement_strategy,
enable_eplb=eplb_state is not None,
num_fused_shared_experts=num_fused_shared_experts,
rocm_aiter_enabled=rocm_aiter_ops.is_fused_moe_enabled() and is_act_and_mul,
)
# TODO(bnell): we should not have to create a router if the kernel is
# monolithic.
if router is None:
router = create_fused_moe_router(
top_k=top_k,
global_num_experts=global_num_experts,
eplb_state=eplb_state,
renormalize=renormalize,
use_grouped_topk=use_grouped_topk,
num_expert_group=num_expert_group,
topk_group=topk_group,
custom_routing_function=custom_routing_function,
scoring_func=scoring_func,
# When apply_routed_scale_to_output is True, we set the scaling factor
# to 1.0 so it ends up being a nop. Applying the scale will be handled
# by the runner in this case.
# The member variable must be set in the same way as the router since
# some quantization methods can access it.
routed_scaling_factor=routed_scaling_factor
if not apply_routed_scale_to_output
else 1.0,
e_score_correction_bias=e_score_correction_bias,
num_fused_shared_experts=num_fused_shared_experts,
# Fused shared-expert slot weight. With apply_routed_scale_to_output
# the runner scales the combined output by routed_scaling_factor, so
# the shared slot weight must be 1/routed_scaling_factor for its net
# contribution to be 1.0 (matching the un-scaled separate-MLP add).
shared_expert_weight=(
(1.0 / routed_scaling_factor)
if (
apply_routed_scale_to_output
and num_fused_shared_experts > 0
and routed_scaling_factor
)
else 1.0
),
zero_expert_type=zero_expert_type,
num_logical_experts=logical_num_experts,
hash_indices_table=hash_indices_table,
)
if params_dtype is None:
params_dtype = torch.get_default_dtype()
# FIXME (varun): We should have a better way of inferring the activation
# datatype. This works for now as the tensor datatype entering the MoE
# operation is typically unquantized (i.e. float16/bfloat16).
if vllm_config.model_config is not None:
moe_in_dtype = vllm_config.model_config.dtype
else:
# TODO (bnell): This is a hack to get test_mixtral_moe to work
# since model_config is not set in the pytest test.
moe_in_dtype = params_dtype
moe_config = FusedMoEConfig(
num_experts=global_num_experts,
experts_per_token=top_k,
hidden_dim=hidden_size,
intermediate_size=intermediate_size,
intermediate_pad=intermediate_pad,
num_local_experts=expert_map_manager.local_num_experts,
num_logical_experts=logical_num_experts,
moe_parallel_config=moe_parallel_config,
in_dtype=moe_in_dtype,
moe_backend=vllm_config.kernel_config.moe_backend,
router_logits_dtype=router_logits_dtype,
max_num_tokens=max_num_batched_tokens,
has_bias=has_bias,
is_lora_enabled=vllm_config.lora_config is not None,
activation=moe_activation,
device=vllm_config.device_config.device,
routing_method=router.routing_method_type, # Not ideal
swiglu_limit=swiglu_limit,
swiglu_alpha=swiglu_alpha,
swiglu_beta=swiglu_beta,
max_capture_size=vllm_config.compilation_config.max_cudagraph_capture_size,
skip_final_all_reduce=skip_final_all_reduce,
)
logger.debug("FusedMoEConfig = %s", moe_config)
# Create RoutedExperts instance BEFORE create_weights()
# This will hold all expert weight parameters
if routed_experts_cls is None:
routed_experts_cls = RoutedExperts
assert params_dtype is not None
routed_experts = routed_experts_cls(
layer_name,
params_dtype,
moe_config,
quant_config,
expert_map_manager=expert_map_manager,
ckpt_gate_proj_name=ckpt_names[0],
ckpt_down_proj_name=ckpt_names[1],
ckpt_up_proj_name=ckpt_names[2],
# Extra params that are needed by quant_methods, pass along for now
# Prefer getting these from other sources, e.g. moe_config or
# router object
renormalize=renormalize,
use_grouped_topk=use_grouped_topk,
num_expert_group=num_expert_group,
topk_group=topk_group,
custom_routing_function=custom_routing_function,
scoring_func=scoring_func,
routed_scaling_factor=routed_scaling_factor
if not apply_routed_scale_to_output
else 1.0,
swiglu_limit=swiglu_limit,
swiglu_alpha=swiglu_alpha,
swiglu_beta=swiglu_beta,
# TODO get from router? needs to be truncated?
e_score_correction_bias=e_score_correction_bias,
apply_router_weight_on_input=apply_router_weight_on_input,
**routed_experts_args if routed_experts_args is not None else {},
)
if runner_cls is None:
runner_cls = MoERunner
runner = runner_cls(
layer_name=layer_name,
moe_config=moe_config,
router=router,
routed_experts=routed_experts,
enable_dbo=vllm_config.parallel_config.enable_dbo,
gate=gate,
shared_expert_gate=shared_expert_gate,
shared_experts=shared_experts,
routed_input_transform=routed_input_transform,
routed_output_transform=routed_output_transform,
# When apply_routed_scale_to_output is True, we allow
# the scaling factor to be passed to the runner, otherwise
# we pass 1.0 so it ends up being a nop.
routed_scaling_factor=routed_scaling_factor
if apply_routed_scale_to_output
else 1.0,
**runner_args if runner_args is not None else {},
)
return runner
def fused_moe_make_expert_params_mapping(
model: torch.nn.Module,
ckpt_gate_proj_name: str,
ckpt_down_proj_name: str,
ckpt_up_proj_name: str,
num_experts: int,
num_redundant_experts: int = 0,
routed_experts_prefix: str = "routed_experts",
) -> list[tuple[str, str, int, str]]:
"""Delegate to EPLB manager."""
return RoutedExperts.make_expert_params_mapping(
model,
ckpt_gate_proj_name,
ckpt_down_proj_name,
ckpt_up_proj_name,
num_experts,
num_redundant_experts,
routed_experts_prefix,
)