| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- # SPDX-License-Identifier: Apache-2.0
- # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
- import os
- from functools import partial
- import torch
- from vllm import _custom_ops as ops
- from vllm import envs as envs
- from vllm._aiter_ops import rocm_aiter_ops
- from vllm.distributed.eplb.eplb_state import EplbLayerState
- from vllm.forward_context import get_forward_context, is_forward_context_available
- from vllm.model_executor.custom_op import CustomOp
- from vllm.model_executor.layers.fused_moe.config import (
- RoutingMethodType,
- get_routing_method_type,
- )
- from vllm.model_executor.layers.fused_moe.experts.rocm_aiter_moe import (
- rocm_aiter_grouped_topk,
- )
- from vllm.model_executor.layers.fused_moe.router.base_router import BaseRouter
- from vllm.model_executor.layers.fused_moe.router.fused_topk_bias_router import (
- fused_topk_bias,
- )
- from vllm.model_executor.layers.fused_moe.router.fused_topk_router import fused_topk
- from vllm.model_executor.utils import maybe_disable_graph_partition
- from vllm.platforms import current_platform
- def fused_grouped_topk(
- hidden_states: torch.Tensor,
- gating_output: torch.Tensor,
- topk: int,
- renormalize: bool,
- e_score_correction_bias: torch.Tensor,
- num_expert_group: int = 0,
- topk_group: int = 0,
- scoring_func: str = "softmax",
- routed_scaling_factor: float = 1.0,
- ) -> tuple[torch.Tensor, torch.Tensor]:
- assert hidden_states.size(0) == gating_output.size(0), "Number of tokens mismatch"
- if scoring_func == "sigmoid":
- # Fully fused kernel path for sigmoid
- topk_values, topk_indices = ops.grouped_topk(
- gating_output, # raw logits
- num_expert_group,
- topk_group,
- topk,
- renormalize,
- routed_scaling_factor,
- e_score_correction_bias,
- 1, # scoring_func=1 for sigmoid
- )
- elif scoring_func == "softmax":
- # Apply softmax in Python, then use fused kernel
- # TODO: Add support for softmax in kernel
- scores = torch.softmax(gating_output, dim=-1)
- topk_values, topk_indices = ops.grouped_topk(
- scores, # pre-computed scores
- num_expert_group,
- topk_group,
- topk,
- renormalize,
- routed_scaling_factor,
- e_score_correction_bias,
- 0, # scoring_func=0 (no activation, scores already computed)
- )
- else:
- raise ValueError(f"Unsupported scoring function: {scoring_func}")
- # Fused kernel outputs float32 values and int32 indices directly
- return topk_values, topk_indices
- # This is used by the Deepseek-V2 and Deepseek-V3 model
- @torch.compile(
- dynamic=True,
- backend=current_platform.simple_compile_backend,
- options=maybe_disable_graph_partition(current_platform.simple_compile_backend),
- )
- def grouped_topk(
- hidden_states: torch.Tensor,
- gating_output: torch.Tensor,
- topk: int,
- renormalize: bool,
- num_expert_group: int = 0,
- topk_group: int = 0,
- scoring_func: str = "softmax",
- routed_scaling_factor: float = 1.0,
- e_score_correction_bias: torch.Tensor | None = None,
- ) -> tuple[torch.Tensor, torch.Tensor]:
- if (
- envs.VLLM_USE_FUSED_MOE_GROUPED_TOPK
- and (current_platform.is_cuda() or current_platform.is_xpu())
- and num_expert_group <= 32
- and topk <= 32
- and e_score_correction_bias is not None
- ):
- return fused_grouped_topk(
- hidden_states=hidden_states,
- gating_output=gating_output,
- topk=topk,
- renormalize=renormalize,
- e_score_correction_bias=e_score_correction_bias,
- num_expert_group=num_expert_group,
- topk_group=topk_group,
- scoring_func=scoring_func,
- routed_scaling_factor=routed_scaling_factor,
- )
- assert hidden_states.size(0) == gating_output.size(0), "Number of tokens mismatch"
- if scoring_func == "softmax":
- scores = torch.softmax(gating_output, dim=-1)
- elif scoring_func == "sigmoid":
- scores = gating_output.sigmoid()
- else:
- raise ValueError(f"Unsupported scoring function: {scoring_func}")
- num_token = scores.size(0)
- if e_score_correction_bias is not None:
- # Store original scores before applying correction bias. We use biased
- # scores for expert selection but original scores for routing weights
- original_scores = scores
- scores = scores + e_score_correction_bias.unsqueeze(0)
- group_scores = (
- scores.view(num_token, num_expert_group, -1).topk(2, dim=-1)[0].sum(dim=-1)
- )
- else:
- group_scores = (
- scores.view(num_token, num_expert_group, -1).max(dim=-1).values
- ) # [n, n_group]
- # For batch invariance, use sorted=True to ensure deterministic expert selection
- use_sorted = envs.VLLM_BATCH_INVARIANT
- group_idx = torch.topk(group_scores, k=topk_group, dim=-1, sorted=use_sorted)[
- 1
- ] # [n, top_k_group]
- group_mask = torch.zeros_like(group_scores) # [n, n_group]
- group_mask.scatter_(1, group_idx, 1) # [n, n_group]
- score_mask = (
- group_mask.unsqueeze(-1)
- .expand(num_token, num_expert_group, scores.size(-1) // num_expert_group)
- .reshape(num_token, -1)
- ) # [n, e]
- tmp_scores = scores.masked_fill(~score_mask.bool(), float("-inf")) # [n, e]
- if e_score_correction_bias is not None:
- topk_ids = torch.topk(tmp_scores, k=topk, dim=-1, sorted=use_sorted)[1]
- # Use original unbiased scores for the routing weights
- topk_weights = original_scores.gather(1, topk_ids)
- else:
- topk_weights, topk_ids = torch.topk(
- tmp_scores, k=topk, dim=-1, sorted=use_sorted
- )
- if renormalize:
- topk_weights = topk_weights / topk_weights.sum(dim=-1, keepdim=True)
- if routed_scaling_factor != 1.0:
- topk_weights = topk_weights * routed_scaling_factor
- return topk_weights.to(torch.float32), topk_ids.to(torch.int32)
- # --8<-- [start:grouped_topk]
- @CustomOp.register("grouped_topk")
- class GroupedTopk(CustomOp):
- """GroupedTopk used by the Deepseek-V2 and Deepseek-V3 model."""
- # --8<-- [end:grouped_topk]
- def __init__(
- self,
- topk: int,
- renormalize: bool,
- num_expert_group: int = 0,
- topk_group: int = 0,
- scoring_func: str = "softmax",
- routed_scaling_factor: float = 1.0,
- num_fused_shared_experts: int = 0,
- ) -> None:
- super().__init__()
- self.native_impl = grouped_topk
- self.topk = topk
- self.renormalize = renormalize
- self.num_expert_group = num_expert_group
- self.topk_group = topk_group
- self.scoring_func = scoring_func
- self.routed_scaling_factor = routed_scaling_factor
- self.num_fused_shared_experts = num_fused_shared_experts
- def forward_native(
- self,
- hidden_states: torch.Tensor,
- gating_output: torch.Tensor,
- e_score_correction_bias: torch.Tensor | None = None,
- ) -> tuple[torch.Tensor, torch.Tensor]:
- return self.native_impl(
- hidden_states,
- gating_output,
- self.topk,
- self.renormalize,
- self.num_expert_group,
- self.topk_group,
- self.scoring_func,
- self.routed_scaling_factor,
- e_score_correction_bias,
- )
- def forward_cuda(
- self,
- hidden_states: torch.Tensor,
- gating_output: torch.Tensor,
- e_score_correction_bias: torch.Tensor | None = None,
- ) -> tuple[torch.Tensor, torch.Tensor]:
- return self.forward_native(
- hidden_states, gating_output, e_score_correction_bias
- )
- def forward_hip(
- self,
- hidden_states: torch.Tensor,
- gating_output: torch.Tensor,
- e_score_correction_bias: torch.Tensor | None = None,
- ) -> tuple[torch.Tensor, torch.Tensor]:
- if rocm_aiter_ops.is_fused_moe_enabled():
- if not rocm_aiter_ops.is_fusion_moe_shared_experts_enabled():
- assert self.num_fused_shared_experts == 0
- return rocm_aiter_grouped_topk(
- hidden_states,
- gating_output,
- self.topk,
- self.renormalize,
- self.num_expert_group,
- self.topk_group,
- self.scoring_func,
- self.routed_scaling_factor,
- e_score_correction_bias,
- self.num_fused_shared_experts,
- )
- else:
- return self.forward_native(
- hidden_states, gating_output, e_score_correction_bias
- )
- class GroupedTopKRouter(BaseRouter):
- """Router using grouped top-k routing (e.g., DeepSeekV2/V3)."""
- def __init__(
- self,
- top_k: int,
- global_num_experts: int,
- num_expert_group: int,
- topk_group: int,
- renormalize: bool = True,
- scoring_func: str = "softmax",
- routed_scaling_factor: float = 1.0,
- e_score_correction_bias: torch.Tensor | None = None,
- num_fused_shared_experts: int = 0,
- eplb_state: EplbLayerState | None = None,
- skip_padding: bool = False,
- ):
- super().__init__(
- top_k=top_k,
- global_num_experts=global_num_experts,
- eplb_state=eplb_state,
- )
- self.num_expert_group = num_expert_group
- self.topk_group = topk_group
- self.renormalize = renormalize
- self.scoring_func = scoring_func
- self.routed_scaling_factor = routed_scaling_factor
- self.e_score_correction_bias = e_score_correction_bias
- self.num_fused_shared_experts = num_fused_shared_experts
- # Draft truncation (draft_trunc) marks dead verify tokens as padding.
- self.skip_padding = skip_padding or bool(os.environ.get("VLLM_DRAFT_TRUNC_CONTROL"))
- @property
- def routing_method_type(self) -> RoutingMethodType:
- return get_routing_method_type(
- scoring_func=self.scoring_func,
- top_k=self.top_k,
- renormalize=self.renormalize,
- num_expert_group=self.num_expert_group,
- has_e_score_bias=self.e_score_correction_bias is not None,
- )
- def _compute_routing(
- self,
- hidden_states: torch.Tensor,
- router_logits: torch.Tensor,
- indices_type: torch.dtype | None,
- *,
- input_ids: torch.Tensor | None = None,
- ) -> tuple[torch.Tensor, torch.Tensor]:
- """Compute routing using grouped top-k."""
- def valid_grouping() -> bool:
- # Check if num_experts is greater than num_expert_group
- # and is divisible by num_expert_group
- num_experts = router_logits.shape[-1]
- if num_experts <= self.num_expert_group:
- return False
- return num_experts % self.num_expert_group == 0
- if not valid_grouping():
- if self.e_score_correction_bias is not None:
- topk_weights, topk_ids = fused_topk_bias(
- hidden_states=hidden_states,
- gating_output=router_logits,
- scoring_func=self.scoring_func,
- e_score_correction_bias=self.e_score_correction_bias.data,
- topk=self.top_k,
- renormalize=self.renormalize,
- )
- if self.routed_scaling_factor != 1.0:
- topk_weights *= self.routed_scaling_factor
- else:
- topk_weights, topk_ids, token_expert_indices = fused_topk(
- hidden_states=hidden_states,
- gating_output=router_logits,
- topk=self.top_k,
- renormalize=self.renormalize,
- indices_type=indices_type,
- )
- return topk_weights, topk_ids
- # Select grouped_topk implementation
- if rocm_aiter_ops.is_fused_moe_enabled():
- if not rocm_aiter_ops.is_fusion_moe_shared_experts_enabled():
- assert self.num_fused_shared_experts == 0
- grouped_topk_impl = partial(
- rocm_aiter_grouped_topk,
- num_fused_shared_experts=self.num_fused_shared_experts,
- )
- else:
- grouped_topk_impl = grouped_topk
- topk_weights, topk_ids = grouped_topk_impl(
- hidden_states=hidden_states,
- gating_output=router_logits,
- topk=self.top_k,
- renormalize=self.renormalize,
- num_expert_group=self.num_expert_group,
- topk_group=self.topk_group,
- scoring_func=self.scoring_func,
- routed_scaling_factor=self.routed_scaling_factor,
- e_score_correction_bias=self.e_score_correction_bias,
- )
- if (
- self.skip_padding
- and envs.VLLM_MOE_SKIP_PADDING
- and is_forward_context_available()
- ):
- is_padding = get_forward_context().is_padding
- # A sequence-parallel shard routes a slice of the batch, which the
- # first rows of is_padding do not describe.
- if is_padding is not None and is_padding.shape[0] == topk_ids.shape[0]:
- is_padding = is_padding[: topk_ids.shape[0]].unsqueeze(1)
- topk_weights = topk_weights.masked_fill(is_padding, 0)
- topk_ids = topk_ids.masked_fill(is_padding, -1)
- return topk_weights, topk_ids
|