main
py 220 lines 8.2 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """MoonEP experts: grouped GEMM over MoonEP's expert-grouped activations.
4
5 MoonEP dispatch delivers tokens already contiguous per weight row
6 (``[NvS, H]`` in ``cu_seqlens[E+B]`` segment order), so the expert compute
7 is three grouped GEMMs over those segments with no permute/unpermute:
8
9 gate = grouped_mm(x, w_gate[E+B]) # [NvS, I]
10 up = grouped_mm(x, w_up[E+B]) # [NvS, I]
11 act = silu(gate) * up * route_weight # route weights applied here
12 out = grouped_mm(act, w_down[E+B]) # [NvS, H]
13
14 Rows ``[E, E+B)`` are the redundant-expert prefetch slots that
15 ``MoonEPPrepareAndFinalize`` fills before ``apply`` runs. Empty segments
16 (including unused prefetch slots) are skipped by the grouped GEMM.
17
18 Weight layout: MoonEP's ``prefetch_weight`` requires each projection to be
19 its own contiguous ``[E+B, ., .]`` tensor, so gate and up are separate
20 tensors rather than vLLM's interleaved ``[E, 2I, H]`` ``w13``. The modular
21 kernel passes ``w1``/``w2`` from the layer (``w1`` is the gate tensor); the
22 full layout is picked up from the layer in ``process_weights_after_loading``
23 and shared with ``MoonEPPrepareAndFinalize`` via ``post_init_setup``.
24 """
25
26 import torch
27
28 import vllm.model_executor.layers.fused_moe.modular_kernel as mk
29 from vllm.model_executor.layers.fused_moe.activation import MoEActivation
30 from vllm.model_executor.layers.fused_moe.config import (
31 FusedMoEConfig,
32 FusedMoEParallelConfig,
33 FusedMoEQuantConfig,
34 )
35 from vllm.model_executor.layers.fused_moe.topk_weight_and_reduce import (
36 TopKWeightAndReduceNoOP,
37 )
38 from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey
39 from vllm.platforms import current_platform
40
41
42 def moonep_grouped_gemm(
43 a: torch.Tensor,
44 w: torch.Tensor,
45 cu_seqlens: torch.Tensor,
46 ) -> torch.Tensor:
47 """``out[seg_g] = a[seg_g] @ w[g].T`` for each ``cu_seqlens`` segment.
48
49 Args:
50 a: ``[NvS, K]`` activations in segment order.
51 w: ``[G, N, K]`` weights (vLLM ``[E, N, K]`` convention).
52 cu_seqlens: ``[G]`` int32 cumulative segment end offsets.
53
54 Returns:
55 ``[NvS, N]``; rows past ``cu_seqlens[-1]`` are zero-filled.
56
57 """
58 return torch._grouped_mm(a, w.transpose(1, 2), offs=cu_seqlens)
59
60
61 class MoonEPExperts(mk.FusedMoEExpertsModular):
62 """Grouped-GEMM experts over MoonEP's ``[NvS, H]`` layout (BF16)."""
63
64 def __init__(
65 self,
66 moe_config: FusedMoEConfig,
67 quant_config: FusedMoEQuantConfig,
68 max_num_tokens: int | None = None,
69 num_dispatchers: int | None = None,
70 ):
71 super().__init__(moe_config, quant_config, max_num_tokens, num_dispatchers)
72 assert quant_config.quant_dtype is None, (
73 "MoonEPExperts supports unquantized BF16 only"
74 )
75 if (
76 quant_config.gemm1_alpha is not None
77 or quant_config.gemm1_beta is not None
78 or quant_config.gemm1_clamp_limit is not None
79 ):
80 raise NotImplementedError(
81 "MoonEPExperts implements plain silu(gate) * up only; SwiGLU "
82 "variants with alpha/beta/clamp parameters are not supported"
83 )
84 self._weight_layout = None
85
86 def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
87 # The [E+B] gate/up/down layout built by
88 # convert_to_unquantized_kernel_format; MoonEPPrepareAndFinalize
89 # reads it from here (post_init_setup) for weight prefetch.
90 layout = getattr(layer, "_moonep_weight_layout", None)
91 assert layout is not None, (
92 "MoonEPExperts requires the layer to carry _moonep_weight_layout "
93 "(set by convert_to_unquantized_kernel_format)"
94 )
95 self._weight_layout = layout
96
97 @property
98 def weight_layout(self):
99 return self._weight_layout
100
101 @staticmethod
102 def activation_format() -> mk.FusedMoEActivationFormat:
103 return mk.FusedMoEActivationFormat.Standard
104
105 @staticmethod
106 def _supports_current_device() -> bool:
107 return current_platform.is_cuda()
108
109 @staticmethod
110 def _supports_no_act_and_mul() -> bool:
111 return False
112
113 @staticmethod
114 def _supports_activation(activation: MoEActivation) -> bool:
115 return activation == MoEActivation.SILU
116
117 @staticmethod
118 def _supports_parallel_config(moe_parallel_config: FusedMoEParallelConfig) -> bool:
119 # EPLB rearranges the named expert parameters as local_num_experts
120 # rows, which does not understand the replicated [E+B] layout.
121 return (
122 moe_parallel_config.use_moonep_kernels
123 and not moe_parallel_config.enable_eplb
124 )
125
126 @staticmethod
127 def _supports_quant_scheme(
128 weight_key: QuantKey | None,
129 activation_key: QuantKey | None,
130 ) -> bool:
131 return weight_key is None and activation_key is None
132
133 def supports_chunking(self) -> bool:
134 # NvS is static and segments must stay whole.
135 return False
136
137 def supports_expert_map(self) -> bool:
138 # MoonEP addresses global expert rows directly.
139 return False
140
141 def finalize_weight_and_reduce_impl(self) -> mk.TopKWeightAndReduce:
142 return TopKWeightAndReduceNoOP()
143
144 def moe_problem_size(
145 self,
146 a1: torch.Tensor,
147 w1: torch.Tensor,
148 w2: torch.Tensor,
149 topk_ids: torch.Tensor,
150 ) -> tuple[int, int, int, int, int]:
151 # a1 is [NvS, H] in segment order, not token-major, so the base
152 # implementation's topk_ids/a1 row-count check does not apply.
153 assert a1.dim() == 2 and w1.dim() == 3 and w2.dim() == 3
154 num_rows, intermediate, hidden = w1.shape # [E+B, I, H]
155 assert a1.size(1) == hidden
156 return num_rows, a1.size(0), intermediate, hidden, topk_ids.size(1)
157
158 def workspace_shapes(
159 self,
160 M: int,
161 N: int,
162 K: int,
163 topk: int,
164 global_num_experts: int,
165 local_num_experts: int,
166 expert_tokens_meta: mk.ExpertTokensMetadata | None,
167 activation: MoEActivation,
168 ) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...]]:
169 # M is NvS. torch._grouped_mm allocates its own outputs, so no
170 # workspaces are needed.
171 return ((0,), (0,), (M, K))
172
173 def apply(
174 self,
175 output: torch.Tensor,
176 hidden_states: torch.Tensor,
177 w1: torch.Tensor,
178 w2: torch.Tensor,
179 topk_weights: torch.Tensor,
180 topk_ids: torch.Tensor,
181 activation: MoEActivation,
182 global_num_experts: int,
183 expert_map: torch.Tensor | None,
184 a1q_scale: torch.Tensor | None,
185 a2_scale: torch.Tensor | None,
186 workspace13: torch.Tensor,
187 workspace2: torch.Tensor,
188 expert_tokens_meta: mk.ExpertTokensMetadata | None,
189 apply_router_weight_on_input: bool,
190 ) -> None:
191 # expert_map is ignored: MoonEP dispatches on global expert ids and
192 # the weights are addressed by global [E+B] row, on every rank.
193 assert activation == MoEActivation.SILU
194 assert self._weight_layout is not None, (
195 "process_weights_after_loading() not called"
196 )
197 assert expert_tokens_meta is not None
198 # MoonEPPrepareAndFinalize returns route weights in NvS order as the
199 # dispatched topk_weights, and per-row segment lengths as
200 # expert_tokens_meta.
201 route_weights_nvs = topk_weights
202 assert route_weights_nvs.dim() == 1
203 cu_seqlens = torch.cumsum(
204 expert_tokens_meta.expert_num_tokens, dim=0, dtype=torch.int32
205 )
206
207 assert cu_seqlens.numel() == w1.size(0)
208
209 gate = moonep_grouped_gemm(hidden_states, w1, cu_seqlens)
210 up = moonep_grouped_gemm(
211 hidden_states, self._weight_layout.full_up_weight, cu_seqlens
212 )
213 act = torch.nn.functional.silu(gate)
214 act.mul_(up)
215 if not apply_router_weight_on_input:
216 # Input-weighted routing (top-1, e.g. Llama 4) was already
217 # applied to the activations by prepare(), before dispatch.
218 act.mul_(route_weights_nvs.to(act.dtype).unsqueeze(-1))
219 # Padding rows past the last segment come out zero-filled.
220 output.copy_(moonep_grouped_gemm(act, w2, cu_seqlens))