[Model][MoE] DeepSeek-V4: add opt-in FlashInfer moe_ep expert backend (#49636)

Signed-off-by: Md Saidul Hoque Anik <mhoqueanik@nvidia.com> Signed-off-by: Anerudhan Gopal <agopal@nvidia.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Anerudhan Gopal <agopal@nvidia.com>

Md Saidul Hoque Anik committed Aug 24, 2026 at 20:35 UTC 8fe9317f2e401aff6e13044098ac7f59e95dce97
5 files changed +1083 -12
tests/models/test_deepseek_v4_fi_moe_ep.py new
+259
@@ -0,0 +1,259 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""Unit tests for the flashinfer moe_ep backend plumbing.
4 +
5 +Everything here runs without a GPU or a flashinfer install: the flashinfer
6 +modules the helpers import lazily are replaced with capture fakes.
7 +"""
8 +
9 +import sys
10 +from dataclasses import dataclass, field
11 +from types import ModuleType, SimpleNamespace
12 +from typing import Any
13 +
14 +import pytest
15 +import torch
16 +
17 +from vllm.config.kernel import (
18 + FLASHINFER_MOE_EP_BACKENDS,
19 + MEGA_MOE_BACKENDS,
20 + validate_flashinfer_moe_ep_model,
21 +)
22 +from vllm.utils.flashinfer_moe_ep import (
23 + _E2M1_LUT,
24 + FI_MOE_EP_BACKEND_SPECS,
25 + _dequant_fp4_ue8m0_gran32,
26 + build_fi_mega_config,
27 + fi_moe_ep_backend_spec,
28 + make_fi_moe_ep_bootstrap,
29 + megakernel_runtime_requirements,
30 +)
31 +
32 +
33 +@dataclass
34 +class _FakeBootstrapConfig:
35 + world_size: int
36 + rank: int
37 + process_group: Any = None
38 + auto_bootstrap: bool = True
39 + device: int | None = field(default=None, kw_only=True)
40 +
41 +
42 +@dataclass
43 +class _FakeDeepGemmMegaMoeConfig:
44 + intermediate_size: int
45 + top_k: int
46 + activation_clamp: float | None
47 + fast_math: bool
48 +
49 +
50 +@dataclass
51 +class _FakeNvfp4CutedslMegaMoeConfig:
52 + intermediate_size: int
53 + top_k: int
54 + activation_clamp: float | None
55 + fast_math: bool
56 +
57 +
58 +@dataclass
59 +class _FakeMegaConfig:
60 + megakernel: Any
61 + preprocess_weights: bool
62 + quantize_input: bool
63 +
64 +
65 +@pytest.fixture
66 +def fake_flashinfer(monkeypatch):
67 + """Install a minimal fake flashinfer.moe_ep for the lazy imports."""
68 + moe_ep = ModuleType("flashinfer.moe_ep")
69 + core = ModuleType("flashinfer.moe_ep.core")
70 + runtime = ModuleType("flashinfer.moe_ep.core.runtime")
71 + flashinfer = ModuleType("flashinfer")
72 + fake_attrs: dict[ModuleType, dict[str, Any]] = {
73 + moe_ep: {
74 + "BootstrapConfig": _FakeBootstrapConfig,
75 + "DeepGemmMegaMoeConfig": _FakeDeepGemmMegaMoeConfig,
76 + "Nvfp4CutedslMegaMoeConfig": _FakeNvfp4CutedslMegaMoeConfig,
77 + "MegaConfig": _FakeMegaConfig,
78 + "core": core,
79 + },
80 + runtime: {"TORCH_DIST": "torch_dist", "NVSHMEM": "nvshmem"},
81 + flashinfer: {"moe_ep": moe_ep},
82 + core: {"runtime": runtime},
83 + }
84 + for mod, attrs in fake_attrs.items():
85 + for attr, value in attrs.items():
86 + setattr(mod, attr, value)
87 +
88 + for name, mod in {
89 + "flashinfer": flashinfer,
90 + "flashinfer.moe_ep": moe_ep,
91 + "flashinfer.moe_ep.core": core,
92 + "flashinfer.moe_ep.core.runtime": runtime,
93 + }.items():
94 + monkeypatch.setitem(sys.modules, name, mod)
95 + return moe_ep
96 +
97 +
98 +def test_fi_backend_strings_are_registered_mega_moe_backends():
99 + assert set(FI_MOE_EP_BACKEND_SPECS) == FLASHINFER_MOE_EP_BACKENDS
100 + assert FLASHINFER_MOE_EP_BACKENDS < MEGA_MOE_BACKENDS
101 +
102 +
103 +@pytest.mark.parametrize("moe_backend", sorted(FLASHINFER_MOE_EP_BACKENDS))
104 +def test_fi_moe_ep_backend_rejected_for_non_dsv4(moe_backend):
105 + """An FI moe_ep backend with a non-DSv4 model must fail at config time
106 + instead of silently falling through to the generic FusedMoE path."""
107 + with pytest.raises(ValueError, match="only supported for DeepSeek-V4"):
108 + validate_flashinfer_moe_ep_model(moe_backend, ["MixtralForCausalLM"])
109 +
110 +
111 +@pytest.mark.parametrize("moe_backend", sorted(FLASHINFER_MOE_EP_BACKENDS))
112 +def test_fi_moe_ep_backend_accepted_for_dsv4(moe_backend):
113 + validate_flashinfer_moe_ep_model(moe_backend, ["DeepseekV4ForCausalLM"])
114 +
115 +
116 +@pytest.mark.parametrize(
117 + "architectures",
118 + [["KimiK3ForConditionalGeneration"], ["MixtralForCausalLM"]],
119 +)
120 +def test_native_deep_gemm_mega_moe_not_arch_gated(architectures):
121 + """vLLM's own deep_gemm mega path is not DSv4-only (Kimi K3 uses it);
122 + models validate their own constraints at construction time."""
123 + validate_flashinfer_moe_ep_model("deep_gemm_mega_moe", architectures)
124 +
125 +
126 +def test_non_fi_backend_ignores_architectures():
127 + validate_flashinfer_moe_ep_model("auto", ["MixtralForCausalLM"])
128 +
129 +
130 +@pytest.mark.parametrize("moe_backend", sorted(MEGA_MOE_BACKENDS))
131 +def test_all_mega_backends_get_sequence_parallel_moe(moe_backend):
132 + """Every mega backend must qualify for sequence-parallel MoE at
133 + TP>1/EP: the predicate once matched only the native backend string,
134 + which silently ran the fi backends full-batch with an all-reduce on
135 + every rank — 0.42-0.65x native e2e at TP8."""
136 + from vllm.models.deepseek_v4.nvidia.model import _use_sequence_parallel
137 +
138 + vllm_config = SimpleNamespace(
139 + parallel_config=SimpleNamespace(
140 + pipeline_parallel_size=1,
141 + enable_expert_parallel=True,
142 + tensor_parallel_size=8,
143 + data_parallel_size=1,
144 + ),
145 + kernel_config=SimpleNamespace(moe_backend=moe_backend),
146 + )
147 + assert _use_sequence_parallel(vllm_config)
148 +
149 +
150 +def test_fi_moe_ep_backend_spec_kernel_and_nvshmem_contract():
151 + dg = fi_moe_ep_backend_spec("flashinfer_moe_ep_mega_deep_gemm")
152 + assert dg.megakernel == "deep_gemm_mega"
153 + assert not dg.needs_nvshmem
154 +
155 + cd = fi_moe_ep_backend_spec("flashinfer_moe_ep_mega_cutedsl")
156 + assert cd.megakernel == "nvfp4_cutedsl"
157 + assert cd.needs_nvshmem
158 +
159 + with pytest.raises(ValueError, match="not a flashinfer moe_ep backend"):
160 + fi_moe_ep_backend_spec("deep_gemm_mega_moe")
161 +
162 +
163 +def test_megakernel_runtime_requirements(fake_flashinfer):
164 + dg = megakernel_runtime_requirements(
165 + fi_moe_ep_backend_spec("flashinfer_moe_ep_mega_deep_gemm")
166 + )
167 + assert dg == frozenset({"torch_dist"})
168 +
169 + cd = megakernel_runtime_requirements(
170 + fi_moe_ep_backend_spec("flashinfer_moe_ep_mega_cutedsl")
171 + )
172 + assert cd == frozenset({"torch_dist", "nvshmem"})
173 +
174 +
175 +def test_bootstrap_pins_the_device_vllm_bound(fake_flashinfer, monkeypatch):
176 + """The runtime must not rederive the device from LOCAL_RANK/rank: under a
177 + remapped CUDA_VISIBLE_DEVICES that ordinal points at the wrong GPU
178 + (CUDA_ERROR_ILLEGAL_ADDRESS in the weight transforms). vLLM passes the
179 + device it already bound via BootstrapConfig.device."""
180 + import vllm.utils.flashinfer_moe_ep as mod
181 +
182 + pg = object()
183 + monkeypatch.setattr(
184 + mod,
185 + "get_ep_group",
186 + lambda: SimpleNamespace(world_size=4, rank_in_group=2, device_group=pg),
187 + )
188 + monkeypatch.setattr(torch.accelerator, "current_device_index", lambda: 3)
189 +
190 + bootstrap = make_fi_moe_ep_bootstrap()
191 +
192 + assert bootstrap.world_size == 4
193 + assert bootstrap.rank == 2
194 + assert bootstrap.process_group is pg
195 + assert bootstrap.auto_bootstrap is False
196 + assert bootstrap.device == 3
197 +
198 +
199 +def test_build_fi_mega_config_selects_kernel_config(fake_flashinfer):
200 + dg = build_fi_mega_config(
201 + intermediate_size=2048,
202 + top_k=8,
203 + activation_clamp=7.0,
204 + megakernel="deep_gemm_mega",
205 + )
206 + assert isinstance(dg.megakernel, _FakeDeepGemmMegaMoeConfig)
207 + assert dg.megakernel.intermediate_size == 2048
208 + assert dg.megakernel.top_k == 8
209 + assert dg.megakernel.activation_clamp == 7.0
210 + assert dg.preprocess_weights and dg.quantize_input
211 +
212 + cd = build_fi_mega_config(
213 + intermediate_size=2048,
214 + top_k=8,
215 + activation_clamp=None,
216 + megakernel="nvfp4_cutedsl",
217 + )
218 + assert isinstance(cd.megakernel, _FakeNvfp4CutedslMegaMoeConfig)
219 +
220 + with pytest.raises(ValueError, match="Unsupported fi_moe_ep megakernel"):
221 + build_fi_mega_config(
222 + intermediate_size=2048,
223 + top_k=8,
224 + activation_clamp=None,
225 + megakernel="deep_gemm",
226 + )
227 +
228 +
229 +def test_ckpt_uses_nvfp4_experts_reads_moe_quant_algo():
230 + from vllm.models.deepseek_v4.nvidia.fi_moe import ckpt_uses_nvfp4_experts
231 +
232 + nvfp4 = SimpleNamespace(quant_config=SimpleNamespace(moe_quant_algo="NVFP4"))
233 + assert ckpt_uses_nvfp4_experts(nvfp4)
234 +
235 + mxfp4 = SimpleNamespace(quant_config=SimpleNamespace(moe_quant_algo=None))
236 + assert not ckpt_uses_nvfp4_experts(mxfp4)
237 +
238 + no_algo = SimpleNamespace(quant_config=SimpleNamespace())
239 + assert not ckpt_uses_nvfp4_experts(no_algo)
240 +
241 +
242 +def test_dequant_fp4_ue8m0_gran32_decodes_lut_and_scales():
243 + """One 32-element scale group per row: low nibble is the even element,
244 + high nibble the odd one, ue8m0 scale applies to the whole group."""
245 + packed = torch.arange(32, dtype=torch.uint8).reshape(2, 16)
246 + sf = torch.tensor([[127], [128]], dtype=torch.uint8) # 2**0, 2**1
247 +
248 + out = _dequant_fp4_ue8m0_gran32(packed, sf)
249 +
250 + assert out.shape == (2, 32)
251 + assert out.dtype == torch.bfloat16
252 + expected = torch.empty(2, 32)
253 + for row in range(2):
254 + for col in range(16):
255 + byte = int(packed[row, col])
256 + expected[row, 2 * col] = _E2M1_LUT[byte & 0x0F]
257 + expected[row, 2 * col + 1] = _E2M1_LUT[byte >> 4]
258 + expected[row] *= 2.0**row
259 + assert torch.equal(out, expected.to(torch.bfloat16))
vllm/config/kernel.py
+56 -1
@@ -1,7 +1,7 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import contextlib
4 -from collections.abc import Callable
4 +from collections.abc import Callable, Iterable
5 from dataclasses import asdict, fields
6 from typing import TYPE_CHECKING, Any, Literal
7
@@ -131,6 +131,8 @@ MoEBackend = Literal[
131 "flashinfer_cutedsl",
132 "flashinfer_b12x",
133 "b12x",
134 + "flashinfer_moe_ep_mega_deep_gemm",
135 + "flashinfer_moe_ep_mega_cutedsl",
136 "marlin",
137 "humming",
138 "triton_unfused",
@@ -140,6 +142,46 @@ MoEBackend = Literal[
142 "emulation",
143 ]
144
145 +# Backends that run the mega-MoE model path through the flashinfer moe_ep
146 +# runtime. Only architectures in FLASHINFER_MOE_EP_ARCHITECTURES wire up
147 +# these experts.
148 +FLASHINFER_MOE_EP_BACKENDS = frozenset(
149 + {
150 + "flashinfer_moe_ep_mega_deep_gemm",
151 + "flashinfer_moe_ep_mega_cutedsl",
152 + }
153 +)
154 +
155 +# Backends that run a mega-MoE model path (fused expert module plus
156 +# prepare_megamoe routing): vLLM's native deep_gemm path, which any model
157 +# with a mega-MoE module may use (DeepSeek-V4, Kimi K3), plus the flashinfer
158 +# moe_ep variants.
159 +MEGA_MOE_BACKENDS = frozenset({"deep_gemm_mega_moe"}) | FLASHINFER_MOE_EP_BACKENDS
160 +
161 +# Architectures whose model code wires up the flashinfer moe_ep experts. MTP
162 +# and DSpark draft variants inherit the setting from these target models.
163 +FLASHINFER_MOE_EP_ARCHITECTURES = frozenset(
164 + {
165 + "DeepseekV4ForCausalLM",
166 + "DeepSeekV4MTPModel",
167 + }
168 +)
169 +
170 +
171 +def validate_flashinfer_moe_ep_model(
172 + moe_backend: str, architectures: Iterable[str]
173 +) -> None:
174 + """Reject flashinfer moe_ep backends for models that lack the FI path."""
175 + if moe_backend not in FLASHINFER_MOE_EP_BACKENDS:
176 + return
177 + if not any(arch in FLASHINFER_MOE_EP_ARCHITECTURES for arch in architectures):
178 + raise ValueError(
179 + f"moe_backend={moe_backend!r} is only supported for DeepSeek-V4 "
180 + f"models ({sorted(FLASHINFER_MOE_EP_ARCHITECTURES)}), but the "
181 + f"model is {list(architectures)}."
182 + )
183 +
184 +
185 LinearBackend = Literal[
186 "auto",
187 "cutlass",
@@ -206,6 +248,14 @@ class KernelConfig:
248 - "flashinfer_b12x": Use FlashInfer CuteDSL fused MoE for SM12x
249 (RTX Pro 6000 / DGX Spark)
250 - "b12x": Use b12x FP4 MoE kernels on SM12x
251 + - "flashinfer_moe_ep_mega_deep_gemm": Use the FlashInfer moe_ep
252 + expert-parallel mega-MoE with the DeepGEMM megakernel, which consumes an
253 + MXFP4 checkpoint verbatim (Blackwell, requires expert parallel;
254 + DeepSeek-V4 only)
255 + - "flashinfer_moe_ep_mega_cutedsl": Same, with the CuteDSL megakernel
256 + (additionally requires NVSHMEM). The checkpoint selects the weight path:
257 + an NVFP4 checkpoint is consumed prequantized, MXFP4 weights are
258 + requantized at load
259 - "marlin": Use Marlin kernels (weight-only quantization)
260 - "humming": Use Humming Mixed Precision kernels
261 - "triton_unfused": Use Triton unfused MoE kernels
@@ -288,6 +338,11 @@ class KernelConfig:
338 """Set platform-specific defaults for the kernel config."""
339 from vllm.platforms import current_platform
340
341 + if vllm_config.model_config is not None:
342 + validate_flashinfer_moe_ep_model(
343 + self.moe_backend, vllm_config.model_config.architectures
344 + )
345 +
346 platform_op_priority = current_platform.get_default_ir_op_priority(vllm_config)
347 logger.debug(
348 "Setting platform-specific IR op priority defaults: %s, user-defined: %s",
vllm/models/deepseek_v4/nvidia/fi_moe.py new
+397
@@ -0,0 +1,397 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""FlashInfer ``moe_ep`` expert module for DeepSeek V4."""
4 +
5 +from __future__ import annotations
6 +
7 +from typing import TYPE_CHECKING, Any
8 +
9 +import torch
10 +import torch.nn as nn
11 +
12 +from vllm.model_executor.utils import set_weight_attrs
13 +from vllm.models.deepseek_v4.nvidia.model import DeepseekV4MegaMoEExperts
14 +from vllm.utils.flashinfer_moe_ep import (
15 + build_fi_mega_layer,
16 + ensure_fi_moe_ep_runtime,
17 + fi_moe_ep_backend_spec,
18 + make_fi_moe_ep_bootstrap,
19 + mega_moe_weight_pack_from_params,
20 +)
21 +
22 +if TYPE_CHECKING:
23 + from flashinfer.moe_ep import MoEEpMegaLayer
24 +
25 + from vllm.config import VllmConfig
26 + from vllm.models.deepseek_v4.nvidia.model import DeepseekV4MLP
27 +
28 +_MOE_SKIP_PADDING: bool | None = None
29 +
30 +
31 +def ckpt_uses_nvfp4_experts(vllm_config: VllmConfig) -> bool:
32 + """True when the loaded checkpoint quantizes experts with modelopt NVFP4
33 + (e2m1 + fp8-e4m3 per-16 block scales + per-tensor weight_scale_2), i.e.
34 + the recipe the nvfp4_cutedsl prequantized-weights path consumes verbatim.
35 +
36 + DeepSeek-V4 NVFP4-expert checkpoints declare ``moe_quant_algo: NVFP4`` in
37 + their ``quantization_config``, which DeepseekV4FP8Config surfaces as
38 + ``moe_quant_algo``.
39 + """
40 + return getattr(vllm_config.quant_config, "moe_quant_algo", "") == "NVFP4"
41 +
42 +
43 +def resolve_mega_moe_is_padding(num_tokens: int) -> torch.Tensor | None:
44 + from vllm.forward_context import get_forward_context, is_forward_context_available
45 +
46 + global _MOE_SKIP_PADDING
47 + if _MOE_SKIP_PADDING is None:
48 + import vllm.envs as envs
49 +
50 + _MOE_SKIP_PADDING = bool(envs.VLLM_MOE_SKIP_PADDING)
51 + if not _MOE_SKIP_PADDING or not is_forward_context_available():
52 + return None
53 + is_padding = get_forward_context().is_padding
54 + if is_padding is None:
55 + return None
56 + return is_padding[:num_tokens]
57 +
58 +
59 +def apply_mega_moe_routing_preprocess(
60 + topk_ids: torch.Tensor,
61 + *,
62 + is_padding: torch.Tensor | None = None,
63 +) -> torch.Tensor:
64 + """Padding-only routing preprocess (EPLB hooks go here later)."""
65 + if is_padding is not None:
66 + topk_ids = torch.where(is_padding.unsqueeze(1), -1, topk_ids)
67 + return topk_ids
68 +
69 +
70 +def nvfp4_prequant_pack_and_alphas(
71 + w13_weight: torch.Tensor,
72 + w13_weight_scale: torch.Tensor,
73 + w13_weight_scale_2: torch.Tensor, # (E_local, 2) fp32: [:,0]=gate(w1), [:,1]=up(w3)
74 + w2_weight: torch.Tensor,
75 + w2_weight_scale: torch.Tensor,
76 + w2_weight_scale_2: torch.Tensor, # (E_local,) fp32
77 + *,
78 + intermediate_size: int,
79 +):
80 + """NVFP4 checkpoint params -> (MoEWeightPack, fc1_alpha, fc2_alpha).
81 +
82 + Activation quantization is fully dynamic, so the checkpoint's static
83 + ``input_scale`` drops out and each GEMM's epilogue alpha reduces to the
84 + weight's per-tensor ``weight_scale_2``.
85 +
86 + fc1_alpha is one scalar per expert, but gate (w1) and up (w3) carry their
87 + own ``weight_scale_2``. When they differ, the ratio is folded into the up
88 + half's e4m3 block scales — only if the fold round-trips exactly, since a
89 + lossy rescale would silently change the model.
90 + """
91 + from flashinfer.moe_ep import MoEWeightPack
92 +
93 + inter = intermediate_size
94 + gate_s2 = w13_weight_scale_2[:, 0].float()
95 + up_s2 = w13_weight_scale_2[:, 1].float()
96 + if (gate_s2 <= 0).any() or (up_s2 <= 0).any() or (w2_weight_scale_2 <= 0).any():
97 + raise ValueError(
98 + "nvfp4 prequant: non-positive weight_scale_2 loaded — checkpoint "
99 + "scale tensors missing or loader routed them wrong."
100 + )
101 +
102 + w13_scale = w13_weight_scale
103 + if not torch.equal(gate_s2, up_s2):
104 + ratio = up_s2 / gate_s2 # (E_local,)
105 + up_sf = w13_scale[:, inter:, :].float()
106 + folded = up_sf * ratio[:, None, None]
107 + folded_e4m3 = folded.to(torch.float8_e4m3fn)
108 + if not torch.equal(folded_e4m3.float(), folded):
109 + raise ValueError(
110 + "NVFP4 MoE cannot merge the gate and up weight_scale_2 values "
111 + "because their ratio is not exactly representable in "
112 + "float8_e4m3fn. Use a checkpoint quantized with a shared "
113 + "gate/up weight_scale_2."
114 + )
115 + w13_scale = w13_scale.clone()
116 + w13_scale[:, inter:, :] = folded_e4m3
117 +
118 + fc1_alpha = gate_s2.clone().contiguous()
119 + fc2_alpha = w2_weight_scale_2.float().clone().contiguous()
120 + pack = MoEWeightPack(
121 + w13=w13_weight,
122 + w2=w2_weight,
123 + w13_scale=w13_scale,
124 + w2_scale=w2_weight_scale,
125 + )
126 + return pack, fc1_alpha, fc2_alpha
127 +
128 +
129 +class DeepseekV4MegaMoEExpertsFI(DeepseekV4MegaMoEExperts):
130 + """Same weight layout/loader as the native mega experts, FI compute path."""
131 +
132 + def __init__(
133 + self,
134 + vllm_config: VllmConfig,
135 + *,
136 + activation_clamp: float | None = None,
137 + **kwargs: Any,
138 + ) -> None:
139 + super().__init__(vllm_config, **kwargs)
140 + self._vllm_config = vllm_config
141 + self._activation_clamp = activation_clamp
142 + self._mega_layer: MoEEpMegaLayer | None = None
143 + self._fast_ctx: tuple[Any, Any, Any, int, bool] | None = None
144 + self._epilogue_alphas: tuple[torch.Tensor, torch.Tensor] | None = None
145 + self._nvfp4_prequant = ckpt_uses_nvfp4_experts(vllm_config)
146 + if self._nvfp4_prequant:
147 + megakernel = fi_moe_ep_backend_spec(
148 + vllm_config.kernel_config.moe_backend
149 + ).megakernel
150 + if megakernel != "nvfp4_cutedsl":
151 + raise ValueError(
152 + "NVFP4-quantized expert checkpoint requires "
153 + "moe_backend=flashinfer_moe_ep_mega_cutedsl, got a "
154 + f"backend using megakernel {megakernel!r} "
155 + "(deep_gemm consumes the MXFP4 checkpoint instead)."
156 + )
157 + self._realloc_nvfp4_params()
158 +
159 + def _realloc_nvfp4_params(self) -> None:
160 + """Swap the mx-recipe scale params for the NVFP4 checkpoint's:
161 + fp8-e4m3 per-16 block scales plus the per-tensor second-level
162 + scales (weight_scale_2, input_scale) modelopt exports."""
163 + n_e = self.num_local_experts
164 + inter = self.intermediate_size
165 + hidden = self.hidden_size
166 + attrs = {"weight_loader": self.weight_loader}
167 +
168 + def _param(shape: tuple, dtype: torch.dtype) -> nn.Parameter:
169 + p = nn.Parameter(torch.zeros(*shape, dtype=dtype), requires_grad=False)
170 + set_weight_attrs(p, attrs)
171 + return p
172 +
173 + self.w13_weight_scale = _param(
174 + (n_e, 2 * inter, hidden // 16), torch.float8_e4m3fn
175 + )
176 + self.w13_weight_scale.quant_method = "block"
177 + self.w2_weight_scale = _param((n_e, hidden, inter // 16), torch.float8_e4m3fn)
178 + self.w2_weight_scale.quant_method = "block"
179 + # (E, 2): column 0 = gate (w1), column 1 = up (w3).
180 + self.w13_weight_scale_2 = _param((n_e, 2), torch.float32)
181 + self.w2_weight_scale_2 = _param((n_e,), torch.float32)
182 + # Loaded for completeness; unused — activation quant is dynamic
183 + # (see nvfp4_prequant_pack_and_alphas).
184 + self.w13_input_scale = _param((n_e, 2), torch.float32)
185 + self.w2_input_scale = _param((n_e,), torch.float32)
186 +
187 + def weight_loader(
188 + self,
189 + param: nn.Parameter,
190 + loaded_weight: torch.Tensor,
191 + weight_name: str,
192 + shard_id: str,
193 + expert_id: int,
194 + return_success: bool = False,
195 + ) -> bool | None:
196 + # NVFP4 checkpoint second-level scalars route here; everything
197 + # else (packed weights, block scales) matches the base layout.
198 + if "weight_scale_2" in weight_name or "input_scale" in weight_name:
199 + local_expert_ids = self._map_global_expert_id(expert_id)
200 + if not local_expert_ids:
201 + return False if return_success else None
202 + value = loaded_weight.reshape(()).to(torch.float32)
203 + for local_expert_id in local_expert_ids:
204 + if shard_id in ("w1", "w3"):
205 + if "w13_" not in weight_name:
206 + return False if return_success else None
207 + param.data[local_expert_id, 0 if shard_id == "w1" else 1] = value
208 + elif shard_id == "w2":
209 + if "w2_" not in weight_name:
210 + return False if return_success else None
211 + param.data[local_expert_id] = value
212 + else:
213 + raise ValueError(f"Unsupported expert shard id: {shard_id}")
214 + return True if return_success else None
215 + return super().weight_loader(
216 + param,
217 + loaded_weight,
218 + weight_name,
219 + shard_id,
220 + expert_id,
221 + return_success,
222 + )
223 +
224 + def finalize_weights(self, shared_experts: DeepseekV4MLP | None = None) -> None:
225 + # The FlashInfer megakernel has no shared-expert fusion; the caller's
226 + # serial shared MLP path handles shared_experts.
227 + if self._mega_layer is not None:
228 + return
229 + if self.w13_weight is None:
230 + return
231 +
232 + self._check_runtime_supported()
233 + ensure_fi_moe_ep_runtime(self._vllm_config)
234 +
235 + if self._nvfp4_prequant:
236 + # NVFP4 checkpoint: hand the packed weights + both scale
237 + # planes straight to the backend (no dequant->requant);
238 + # per-expert globals become fc1/fc2 epilogue alphas staged
239 + # at every forward via MoEEpTensors.
240 + weights, fc1_alpha, fc2_alpha = nvfp4_prequant_pack_and_alphas(
241 + self.w13_weight.data,
242 + self.w13_weight_scale.data,
243 + self.w13_weight_scale_2.data,
244 + self.w2_weight.data,
245 + self.w2_weight_scale.data,
246 + self.w2_weight_scale_2.data,
247 + intermediate_size=self.intermediate_size,
248 + )
249 + self._epilogue_alphas = (fc1_alpha, fc2_alpha)
250 + else:
251 + weights = mega_moe_weight_pack_from_params(
252 + self.w13_weight,
253 + self.w13_weight_scale,
254 + self.w2_weight,
255 + self.w2_weight_scale,
256 + megakernel=fi_moe_ep_backend_spec(
257 + self._vllm_config.kernel_config.moe_backend
258 + ).megakernel,
259 + )
260 + self._mega_layer = build_fi_mega_layer(
261 + make_fi_moe_ep_bootstrap(),
262 + vllm_config=self._vllm_config,
263 + num_experts=self.num_experts,
264 + max_tokens_per_rank=self.max_num_tokens,
265 + hidden_size=self.hidden_size,
266 + intermediate_size=self.intermediate_size,
267 + top_k=self.top_k,
268 + activation_clamp=self._activation_clamp,
269 + weights=weights,
270 + )
271 + del weights
272 + # Allocate (or attach to) the pooled workspace before first
273 + # forward so warmup/capture never hits the lazy path.
274 + self._mega_layer._ensure_workspace()
275 + self.w13_weight = None
276 + self.w13_weight_scale = None
277 + self.w2_weight = None
278 + self.w2_weight_scale = None
279 + if self._nvfp4_prequant:
280 + self.w13_weight_scale_2 = None
281 + self.w2_weight_scale_2 = None
282 + self.w13_input_scale = None
283 + self.w2_input_scale = None
284 +
285 + # EPLB is rejected for these backends in validate_fi_moe_ep_config, so
286 + # nothing constructs an EplbState and none of these run. They are kept
287 + # as explicit errors rather than deleted because inheriting the native
288 + # implementations would be worse: set_eplb_state would record a map
289 + # this forward path never consults, silently splitting routing from
290 + # weights, and get_expert_weights reads transformed-weight attributes
291 + # the flashinfer path releases after preprocess.
292 + _EPLB_UNSUPPORTED = (
293 + "EPLB is not supported with the flashinfer moe_ep backends; "
294 + "validate_fi_moe_ep_config should have rejected this "
295 + "configuration at startup."
296 + )
297 +
298 + def set_eplb_state(
299 + self,
300 + moe_layer_idx: int,
301 + expert_load_view: torch.Tensor,
302 + logical_to_physical_map: torch.Tensor,
303 + logical_replica_count: torch.Tensor,
304 + ) -> None:
305 + raise NotImplementedError(self._EPLB_UNSUPPORTED)
306 +
307 + def get_expert_weights(self) -> list[torch.Tensor]:
308 + raise NotImplementedError(self._EPLB_UNSUPPORTED)
309 +
310 + def update_expert_map(self) -> None:
311 + raise NotImplementedError(self._EPLB_UNSUPPORTED)
312 +
313 + def forward(
314 + self,
315 + hidden_states: torch.Tensor,
316 + topk_weights: torch.Tensor,
317 + topk_ids: torch.Tensor,
318 + *,
319 + activation_clamp: float | None,
320 + fast_math: bool = True,
321 + ) -> torch.Tensor:
322 + # fast_math is a native deep_gemm knob; the FI kernels have no
323 + # equivalent toggle, so it is accepted for signature parity only.
324 + if hidden_states.shape[0] > self.max_num_tokens:
325 + raise ValueError(
326 + f"DeepSeek V4 MegaMoE got {hidden_states.shape[0]} tokens, "
327 + f"but the symmetric buffer was sized for {self.max_num_tokens}."
328 + )
329 +
330 + from flashinfer.moe_ep import MoEEpTensors
331 +
332 + num_tokens = hidden_states.shape[0]
333 + is_padding = resolve_mega_moe_is_padding(num_tokens)
334 + topk_ids = apply_mega_moe_routing_preprocess(
335 + topk_ids,
336 + is_padding=is_padding,
337 + )
338 +
339 + # Fast path: after the first successful full forward the layer is
340 + # immutable, so skip MoEEpMegaLayer.forward()'s per-call validation
341 + # and go straight to the kernel backend's stage_inputs + compute.
342 + alphas = self._epilogue_alphas
343 + fc1_alpha = alphas[0] if alphas is not None else None
344 + fc2_alpha = alphas[1] if alphas is not None else None
345 +
346 + fast = self._fast_ctx
347 + if fast is not None:
348 + kernel, workspace, transformed, hidden_size, zero_copy = fast
349 + t = MoEEpTensors(
350 + hidden_states=hidden_states,
351 + topk_ids=topk_ids,
352 + topk_weights=topk_weights,
353 + fc1_alpha=fc1_alpha,
354 + fc2_alpha=fc2_alpha,
355 + )
356 + kernel.stage_inputs(t, workspace, quantize_input=True)
357 + if zero_copy:
358 + # Zero-copy (cutedsl backends): consume the workspace [:n]
359 + # view directly — valid under stream ordering until the next
360 + # MoE layer's launch on the shared workspace.
361 + return kernel.compute(workspace, transformed, output=None)
362 + # deep_gemm_mega's compute() requires a real output tensor.
363 + out = torch.empty(
364 + num_tokens,
365 + hidden_size,
366 + dtype=torch.bfloat16,
367 + device=hidden_states.device,
368 + )
369 + return kernel.compute(workspace, transformed, output=out)
370 +
371 + ensure_fi_moe_ep_runtime(self._vllm_config)
372 + self.finalize_weights()
373 + assert self._mega_layer is not None
374 +
375 + y = self._mega_layer.forward(
376 + MoEEpTensors(
377 + hidden_states=hidden_states,
378 + topk_ids=topk_ids,
379 + topk_weights=topk_weights,
380 + fc1_alpha=fc1_alpha,
381 + fc2_alpha=fc2_alpha,
382 + )
383 + )
384 + layer = self._mega_layer
385 + if hidden_states.dtype == torch.bfloat16:
386 + self._fast_ctx = (
387 + layer._kernel,
388 + layer._ensure_workspace(),
389 + layer._transformed,
390 + layer._fleet_params.token_hidden_size,
391 + # zero-copy output views are a cutedsl-backend contract
392 + layer._kernel.kernel_name() != "deep_gemm_mega",
393 + )
394 + return y
395 +
396 +
397 +DeepseekV4MegaMoEExpertsFI.weight_loader.supports_moe_loading = True # type: ignore[attr-defined]
vllm/models/deepseek_v4/nvidia/model.py
+32 -11
@@ -11,6 +11,7 @@ import torch.nn as nn
11
12 import vllm.envs as envs
13 from vllm.config import VllmConfig
14 +from vllm.config.kernel import MEGA_MOE_BACKENDS
15 from vllm.distributed import (
16 get_ep_group,
17 get_pp_group,
@@ -83,6 +84,10 @@ from vllm.models.deepseek_v4.nvidia.flashmla import DeepseekV4FlashMLAAttention
84 from vllm.models.deepseek_v4.nvidia.ops.prepare_megamoe import prepare_megamoe_inputs
85 from vllm.platforms import current_platform
86 from vllm.sequence import IntermediateTensors
87 +from vllm.utils.flashinfer_moe_ep import (
88 + is_fi_moe_ep_backend,
89 + validate_fi_moe_ep_config,
90 +)
91 from vllm.utils.math_utils import cdiv
92 from vllm.v1.attention.backends.registry import AttentionBackendEnum
93 from vllm.v1.worker.ubatching import dbo_current_ubatch_id
@@ -752,9 +757,10 @@ class DeepseekV4MoE(nn.Module):
757 quant_config = vllm_config.quant_config
758 self.prefix = prefix
759 self.use_sequence_parallel = use_sequence_parallel
755 - self.use_mega_moe = (
756 - vllm_config.kernel_config.moe_backend == "deep_gemm_mega_moe"
757 - )
760 + moe_backend = vllm_config.kernel_config.moe_backend
761 + validate_fi_moe_ep_config(vllm_config)
762 + self.use_mega_moe = moe_backend in MEGA_MOE_BACKENDS
763 + self.use_fi_mega_moe = is_fi_moe_ep_backend(moe_backend)
764 if self.use_mega_moe and not vllm_config.parallel_config.enable_expert_parallel:
765 raise NotImplementedError(
766 "DeepSeek V4 MegaMoE currently requires expert parallel. "
@@ -779,7 +785,7 @@ class DeepseekV4MoE(nn.Module):
785 raise NotImplementedError(
786 "DeepSeek V4 MegaMoE only supports fp4 experts; got expert_dtype="
787 f"{config.expert_dtype!r}. Drop --kernel-config moe_backend="
782 - "deep_gemm_mega_moe for this checkpoint."
788 + f"{moe_backend} for this checkpoint."
789 )
790
791 self.gate = GateLinear(
@@ -867,15 +873,29 @@ class DeepseekV4MoE(nn.Module):
873 # Native DeepGEMM fusion requires each EP rank to own the complete
874 # shared MLP. Sequence parallel replicates those weights while sharding
875 # tokens. TP=1 is also naturally replicated. With PP+TP the shared MLP
870 - # remains tensor-sharded, so retain the serial path.
876 + # remains tensor-sharded, so retain the serial path. The FlashInfer
877 + # megakernel has no shared-expert fusion, so it keeps the serial path.
878 fuse_shared_experts = bool(
879 self.shared_experts is not None
880 and not envs.VLLM_DISABLE_DSV4_MEGAMOE_SHARED_EXPERT_FUSION
881 and (self.use_sequence_parallel or self.tp_size == 1)
882 + and not self.use_fi_mega_moe
883 )
884
877 - self.experts = DeepseekV4MegaMoEExperts(
878 - vllm_config,
885 + activation_clamp = (
886 + float(self.swiglu_limit) if self.swiglu_limit is not None else None
887 + )
888 + if self.use_fi_mega_moe:
889 + # Deferred: fi_moe subclasses DeepseekV4MegaMoEExperts, so a
890 + # module-level import here would be circular.
891 + from vllm.models.deepseek_v4.nvidia.fi_moe import (
892 + DeepseekV4MegaMoEExpertsFI,
893 + )
894 +
895 + experts_cls: type[DeepseekV4MegaMoEExperts] = DeepseekV4MegaMoEExpertsFI
896 + else:
897 + experts_cls = DeepseekV4MegaMoEExperts
898 + expert_kwargs: dict[str, typing.Any] = dict(
899 num_experts=self.n_physical_experts,
900 num_local_experts=self.n_local_physical_experts,
901 experts_start_idx=self.physical_expert_start,
@@ -886,6 +906,9 @@ class DeepseekV4MoE(nn.Module):
906 num_shared_experts=(self.n_shared_experts if fuse_shared_experts else 0),
907 prefix=f"{prefix}.experts",
908 )
909 + if self.use_fi_mega_moe:
910 + expert_kwargs["activation_clamp"] = activation_clamp
911 + self.experts = experts_cls(vllm_config, **expert_kwargs)
912
913 def _init_fused_moe_experts(
914 self,
@@ -1031,7 +1054,7 @@ def _select_dsv4_attn_cls(vllm_config: VllmConfig) -> type[DeepseekV4Attention]:
1054
1055 def _use_sequence_parallel(vllm_config: VllmConfig) -> bool:
1056 parallel_config = vllm_config.parallel_config
1034 - use_mega_moe = vllm_config.kernel_config.moe_backend == "deep_gemm_mega_moe"
1057 + use_mega_moe = vllm_config.kernel_config.moe_backend in MEGA_MOE_BACKENDS
1058 return (
1059 parallel_config.pipeline_parallel_size == 1
1060 and parallel_config.enable_expert_parallel
@@ -1226,9 +1249,7 @@ class DeepseekV4Model(nn.Module, EagleModelMixin):
1249 self.config = config
1250 self.quant_config = quant_config
1251 self.parallel_config = vllm_config.parallel_config
1229 - self.use_mega_moe = (
1230 - vllm_config.kernel_config.moe_backend == "deep_gemm_mega_moe"
1231 - )
1252 + self.use_mega_moe = vllm_config.kernel_config.moe_backend in MEGA_MOE_BACKENDS
1253 self.use_sequence_parallel = _use_sequence_parallel(vllm_config)
1254 if self.use_mega_moe and not vllm_config.parallel_config.enable_expert_parallel:
1255 raise NotImplementedError(
vllm/utils/flashinfer_moe_ep.py new
+339
@@ -0,0 +1,339 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""FlashInfer ``moe_ep`` helpers for DeepSeek V4 vLLM integration."""
4 +
5 +from __future__ import annotations
6 +
7 +from dataclasses import dataclass
8 +from typing import TYPE_CHECKING, Any
9 +
10 +import torch
11 +import torch.nn as nn
12 +
13 +from vllm.config.kernel import FLASHINFER_MOE_EP_BACKENDS
14 +from vllm.distributed import get_ep_group
15 +from vllm.platforms import current_platform
16 +
17 +if TYPE_CHECKING:
18 + from flashinfer.moe_ep import BootstrapConfig, MoEEpMegaLayer
19 +
20 + from vllm.config import VllmConfig
21 +
22 +
23 +# Every mega kernel is Blackwell-only, so the arch is a property of the family
24 +# rather than of a particular backend: validate it against the live device
25 +# instead of encoding it in the backend name. An explicit allowlist so new
26 +# archs (SM110/SM120, Rubin) fail loudly until flashinfer supports them.
27 +FI_MOE_EP_SUPPORTED_CAPABILITIES = frozenset({(10, 0), (10, 3)})
28 +
29 +
30 +@dataclass(frozen=True)
31 +class FiMoeEpBackendSpec:
32 + """Static properties of one ``flashinfer_moe_ep_*`` backend string.
33 +
34 + The backend names the kernel *family*; the arch comes from the device and
35 + the weight handling from the checkpoint, so this only has to carry which
36 + megakernel to build and whether it needs NVSHMEM in the runtime set.
37 + """
38 +
39 + megakernel: str
40 + needs_nvshmem: bool
41 +
42 +
43 +FI_MOE_EP_BACKEND_SPECS: dict[str, FiMoeEpBackendSpec] = {
44 + # Consumes an MXFP4 checkpoint verbatim (e2m1 weights + E8M0 per-32
45 + # scales) -- the same recipe the native deep_gemm mega path uses.
46 + "flashinfer_moe_ep_mega_deep_gemm": FiMoeEpBackendSpec(
47 + megakernel="deep_gemm_mega",
48 + needs_nvshmem=False,
49 + ),
50 + # The checkpoint picks the weight path, not the kernel: an NVFP4
51 + # checkpoint is consumed prequantized (no round trip), while MXFP4 weights
52 + # are dequantized to bf16 and requantized. See
53 + # models/deepseek_v4/nvidia/fi_moe.py:ckpt_uses_nvfp4_experts.
54 + "flashinfer_moe_ep_mega_cutedsl": FiMoeEpBackendSpec(
55 + megakernel="nvfp4_cutedsl",
56 + needs_nvshmem=True,
57 + ),
58 +}
59 +
60 +assert set(FI_MOE_EP_BACKEND_SPECS) == FLASHINFER_MOE_EP_BACKENDS
61 +
62 +_FI_RUNTIME_HANDLE: Any = None
63 +
64 +
65 +def is_fi_moe_ep_backend(moe_backend: str) -> bool:
66 + return moe_backend in FI_MOE_EP_BACKEND_SPECS
67 +
68 +
69 +def fi_moe_ep_backend_spec(moe_backend: str) -> FiMoeEpBackendSpec:
70 + try:
71 + return FI_MOE_EP_BACKEND_SPECS[moe_backend]
72 + except KeyError:
73 + raise ValueError(
74 + f"{moe_backend!r} is not a flashinfer moe_ep backend; expected "
75 + f"one of {sorted(FI_MOE_EP_BACKEND_SPECS)}"
76 + ) from None
77 +
78 +
79 +def validate_fi_moe_ep_config(vllm_config: VllmConfig) -> None:
80 + """Config-time checks for the mega-MoE backends, native and flashinfer."""
81 + moe_backend = vllm_config.kernel_config.moe_backend
82 + if not is_fi_moe_ep_backend(moe_backend):
83 + return
84 +
85 + # flashinfer validates the arch too, but not until the layer constructor
86 + # runs during weight load; check here so the error names the flag the user
87 + # actually typed.
88 + capability = current_platform.get_device_capability()
89 + if capability is not None:
90 + cc = (capability.major, capability.minor)
91 + if cc not in FI_MOE_EP_SUPPORTED_CAPABILITIES:
92 + supported = ", ".join(
93 + f"{m}.{n}" for m, n in sorted(FI_MOE_EP_SUPPORTED_CAPABILITIES)
94 + )
95 + raise ValueError(
96 + f"moe_backend={moe_backend!r} is only supported on compute "
97 + f"capability {supported} (SM100/SM103), but this device is "
98 + f"{cc[0]}.{cc[1]}."
99 + )
100 +
101 + if vllm_config.parallel_config.enable_eplb:
102 + raise NotImplementedError(
103 + f"EPLB is not supported with moe_backend={moe_backend!r}: the "
104 + "flashinfer moe_ep experts neither apply the logical-to-physical "
105 + "expert map nor report per-expert load, so rebalancing would move "
106 + "weights without moving routing. Use "
107 + "moe_backend=deep_gemm_mega_moe to run the mega path with EPLB."
108 + )
109 +
110 +
111 +def make_fi_moe_ep_bootstrap() -> BootstrapConfig:
112 + from flashinfer.moe_ep import BootstrapConfig
113 +
114 + ep = get_ep_group()
115 + return BootstrapConfig(
116 + world_size=ep.world_size,
117 + rank=ep.rank_in_group,
118 + process_group=ep.device_group,
119 + auto_bootstrap=False,
120 + # vLLM already bound this worker's (possibly remapped) device;
121 + # without this the runtime would rebind to cuda:LOCAL_RANK|rank and
122 + # launch weight transforms against another device's pointers.
123 + device=torch.accelerator.current_device_index(),
124 + )
125 +
126 +
127 +def megakernel_runtime_requirements(spec: FiMoeEpBackendSpec) -> frozenset[str]:
128 + from flashinfer.moe_ep.core.runtime import NVSHMEM, TORCH_DIST
129 +
130 + if spec.needs_nvshmem:
131 + return frozenset({TORCH_DIST, NVSHMEM})
132 + return frozenset({TORCH_DIST})
133 +
134 +
135 +def ensure_fi_moe_ep_runtime(vllm_config: VllmConfig) -> None:
136 + """Acquire the process-wide flashinfer moe_ep runtime once per worker."""
137 + global _FI_RUNTIME_HANDLE
138 + if _FI_RUNTIME_HANDLE is not None:
139 + return
140 +
141 + from flashinfer.moe_ep import bootstrap_moe_ep_runtime
142 +
143 + bootstrap = make_fi_moe_ep_bootstrap()
144 + spec = fi_moe_ep_backend_spec(vllm_config.kernel_config.moe_backend)
145 + _FI_RUNTIME_HANDLE = bootstrap_moe_ep_runtime(
146 + bootstrap,
147 + megakernel_runtime_requirements(spec),
148 + )
149 +
150 +
151 +def finalize_fi_moe_ep_runtime() -> None:
152 + """Release the process-wide flashinfer moe_ep runtime."""
153 + global _FI_RUNTIME_HANDLE
154 + if _FI_RUNTIME_HANDLE is None:
155 + return
156 +
157 + from flashinfer.moe_ep import finalize_moe_ep_runtime
158 +
159 + finalize_moe_ep_runtime(_FI_RUNTIME_HANDLE)
160 + _FI_RUNTIME_HANDLE = None
161 +
162 +
163 +_E2M1_LUT = (
164 + 0.0,
165 + 0.5,
166 + 1.0,
167 + 1.5,
168 + 2.0,
169 + 3.0,
170 + 4.0,
171 + 6.0,
172 + -0.0,
173 + -0.5,
174 + -1.0,
175 + -1.5,
176 + -2.0,
177 + -3.0,
178 + -4.0,
179 + -6.0,
180 +)
181 +
182 +
183 +def _dequant_fp4_ue8m0_gran32(
184 + packed: torch.Tensor, sf_ue8m0: torch.Tensor
185 +) -> torch.Tensor:
186 + """[rows, K//2] packed e2m1 + [rows, K//32] ue8m0-uint8 scales -> bf16 [rows, K]."""
187 + raw = packed.view(torch.uint8)
188 + lut = torch.tensor(_E2M1_LUT, dtype=torch.float32, device=raw.device)
189 + vals = torch.empty(
190 + raw.shape[0], raw.shape[1] * 2, dtype=torch.float32, device=raw.device
191 + )
192 + vals[:, ::2] = lut[(raw & 0x0F).to(torch.int64)]
193 + vals[:, 1::2] = lut[(raw >> 4).to(torch.int64)]
194 + sf = (sf_ue8m0.to(torch.int32) << 23).view(torch.float32)
195 + return (vals * sf.repeat_interleave(32, dim=-1)).to(torch.bfloat16)
196 +
197 +
198 +def _dequant_expert_weights_to_bf16(
199 + weight: torch.Tensor, scale: torch.Tensor
200 +) -> torch.Tensor:
201 + """[E, N, K//2] fp4 + [E, N, K//32] ue8m0 -> [E, N, K] bf16 (expert loop)."""
202 + num_experts, n, k_half = weight.shape
203 + out = torch.empty(
204 + num_experts, n, k_half * 2, dtype=torch.bfloat16, device=weight.device
205 + )
206 + for e in range(num_experts):
207 + out[e] = _dequant_fp4_ue8m0_gran32(weight[e], scale[e])
208 + return out
209 +
210 +
211 +def mega_moe_weight_pack_from_params(
212 + w13_weight: nn.Parameter,
213 + w13_weight_scale: nn.Parameter,
214 + w2_weight: nn.Parameter,
215 + w2_weight_scale: nn.Parameter,
216 + *,
217 + megakernel: str = "deep_gemm_mega",
218 +):
219 + from flashinfer.moe_ep import MoEWeightPack
220 +
221 + if megakernel == "deep_gemm_mega":
222 + # Same fp4-e2m1 + ue8m0-per-32 recipe as the native path: pass verbatim,
223 + # flashinfer runs the identical deep_gemm transform.
224 + return MoEWeightPack(
225 + w13=w13_weight.data,
226 + w2=w2_weight.data,
227 + w13_scale=w13_weight_scale.data,
228 + w2_scale=w2_weight_scale.data,
229 + )
230 + # The cutedsl kernel quantizes with its own recipe (nvfp4
231 + # e2m1+e4m3-per-16): dequantize the checkpoint fp4 to bf16 and let the
232 + # backend preprocess requantize. Double quantization: outputs are close to
233 + # but not bit-identical with the native path.
234 + return MoEWeightPack(
235 + w13=_dequant_expert_weights_to_bf16(w13_weight.data, w13_weight_scale.data),
236 + w2=_dequant_expert_weights_to_bf16(w2_weight.data, w2_weight_scale.data),
237 + )
238 +
239 +
240 +def build_fi_mega_config(
241 + *,
242 + intermediate_size: int,
243 + top_k: int,
244 + activation_clamp: float | None,
245 + megakernel: str,
246 +):
247 + from flashinfer.moe_ep import (
248 + DeepGemmMegaMoeConfig,
249 + MegaConfig,
250 + Nvfp4CutedslMegaMoeConfig,
251 + )
252 +
253 + # fast_math selects approximate exp/rcp in DeepGEMM's fused SwiGLU
254 + # epilogue; the cutedsl kernels accept it for API parity only.
255 + if megakernel == "deep_gemm_mega":
256 + mk = DeepGemmMegaMoeConfig(
257 + intermediate_size=intermediate_size,
258 + top_k=top_k,
259 + activation_clamp=activation_clamp,
260 + fast_math=True,
261 + )
262 + elif megakernel == "nvfp4_cutedsl":
263 + mk = Nvfp4CutedslMegaMoeConfig(
264 + intermediate_size=intermediate_size,
265 + top_k=top_k,
266 + activation_clamp=activation_clamp,
267 + fast_math=True,
268 + )
269 + else:
270 + raise ValueError(f"Unsupported fi_moe_ep megakernel {megakernel!r}")
271 +
272 + return MegaConfig(
273 + megakernel=mk,
274 + preprocess_weights=True,
275 + quantize_input=True,
276 + )
277 +
278 +
279 +# All MoE layers share one symmetric workspace, like the native path's
280 +# class-level DeepseekV4MegaMoEExperts._symm_buffer_cache. Without this the
281 +# fi path allocates one symm buffer PER LAYER (43x memory + cold working
282 +# sets); the workspace is stateless across forwards (kernel tail-cleans) and
283 +# layers execute sequentially on one stream, so sharing is safe.
284 +def build_fi_mega_layer(
285 + bootstrap: BootstrapConfig,
286 + *,
287 + vllm_config: VllmConfig,
288 + num_experts: int,
289 + max_tokens_per_rank: int,
290 + hidden_size: int,
291 + intermediate_size: int,
292 + top_k: int,
293 + activation_clamp: float | None,
294 + weights,
295 +) -> MoEEpMegaLayer:
296 + from flashinfer.moe_ep import FleetParams, MoEEpLayer
297 +
298 + megakernel = fi_moe_ep_backend_spec(
299 + vllm_config.kernel_config.moe_backend
300 + ).megakernel
301 + mega_config = build_fi_mega_config(
302 + intermediate_size=intermediate_size,
303 + top_k=top_k,
304 + activation_clamp=activation_clamp,
305 + megakernel=megakernel,
306 + )
307 + layer = MoEEpLayer(
308 + bootstrap=bootstrap,
309 + fleet_params=FleetParams(
310 + num_experts=num_experts,
311 + max_tokens_per_rank=max_tokens_per_rank,
312 + token_hidden_size=hidden_size,
313 + ),
314 + weights=weights,
315 + backend=mega_config,
316 + )
317 + from flashinfer.moe_ep import MoEEpMegaLayer
318 +
319 + if not isinstance(layer, MoEEpMegaLayer):
320 + raise TypeError(
321 + f"fi_moe_ep expected MoEEpMegaLayer, got {type(layer).__name__}"
322 + )
323 + return layer
324 +
325 +
326 +__all__ = [
327 + "FI_MOE_EP_BACKEND_SPECS",
328 + "FiMoeEpBackendSpec",
329 + "build_fi_mega_config",
330 + "build_fi_mega_layer",
331 + "ensure_fi_moe_ep_runtime",
332 + "fi_moe_ep_backend_spec",
333 + "finalize_fi_moe_ep_runtime",
334 + "is_fi_moe_ep_backend",
335 + "make_fi_moe_ep_bootstrap",
336 + "mega_moe_weight_pack_from_params",
337 + "megakernel_runtime_requirements",
338 + "validate_fi_moe_ep_config",
339 +]