main
py 1,362 lines 44.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import importlib
5 import inspect
6 import sys
7 from copy import deepcopy
8 from types import ModuleType, SimpleNamespace
9 from typing import Any, cast
10 from unittest.mock import patch
11
12 import pytest
13 import torch
14 from torch import nn
15
16 import vllm.config as vllm_config_module
17 from vllm.config import VllmConfig, set_current_vllm_config
18 from vllm.model_executor.layers.fused_moe import utils as fused_moe_utils
19 from vllm.model_executor.layers.fused_moe.layer import determine_expert_counts
20 from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
21 from vllm.model_executor.layers.quantization.fp8 import Fp8Config
22 from vllm.model_executor.layers.quantization.quark.quark import QuarkConfig
23 from vllm.model_executor.layers.quantization.utils.config_utils import (
24 get_quark_ocp_mx_group_size,
25 is_shared_expert_quant_fse_compatible,
26 )
27 from vllm.model_executor.models.qwen3_next import (
28 _should_replicate_misaligned_shared_expert,
29 )
30 from vllm.model_executor.models.utils import PPMissingLayer
31 from vllm.models.deepseek_v4 import quant_config as deepseek_v4_quant_config
32 from vllm.models.minimax_m3.amd import model as minimax_m3_model
33 from vllm.platforms import current_platform
34 from vllm.transformers_utils.configs.minimax_m3 import MiniMaxM3TextConfig
35 from vllm.transformers_utils.configs.qwen3_5_moe import Qwen3_5MoeTextConfig
36
37 pytestmark = pytest.mark.skipif(
38 current_platform.is_xpu(),
39 reason="ROCm-specific aiter ops are not supported on XPU",
40 )
41
42 _QUARK_FSE_CONFIG: dict[str, Any] = {
43 "global_quant_config": {
44 "input_tensors": {
45 "dtype": "fp4",
46 "is_dynamic": True,
47 "qscheme": "per_group",
48 "ch_axis": -1,
49 "group_size": 32,
50 "block_size": None,
51 "symmetric": None,
52 "round_method": "half_even",
53 "scale_type": "float",
54 "scale_format": "e8m0",
55 "scale_calculation_mode": "even",
56 "mx_element_dtype": None,
57 "observer_cls": "PerBlockMXObserver",
58 "is_scale_quant": False,
59 "enable_buffer_reuse": False,
60 "max_input_numel": 4194304,
61 },
62 "output_tensors": None,
63 "weight": {
64 "dtype": "fp4",
65 "is_dynamic": False,
66 "qscheme": "per_group",
67 "ch_axis": -1,
68 "group_size": 32,
69 "block_size": None,
70 "symmetric": None,
71 "round_method": "half_even",
72 "scale_type": "float",
73 "scale_format": "e8m0",
74 "scale_calculation_mode": "even",
75 "mx_element_dtype": None,
76 "observer_cls": "PerBlockMXObserver",
77 "is_scale_quant": False,
78 "enable_buffer_reuse": False,
79 "max_input_numel": 4194304,
80 },
81 "bias": None,
82 "target_device": None,
83 },
84 "algo_config": None,
85 "softmax_quant_spec": None,
86 "quant_method": "quark",
87 "layer_type_quant_config": {},
88 "layer_quant_config": {},
89 "kv_cache_quant_config": {},
90 "kv_cache_post_rope": False,
91 "quant_mode": "eager_mode",
92 "version": "0.12+9d3d471cdf1",
93 "export": {
94 "kv_cache_group": [],
95 "min_kv_scale": 0.0,
96 "pack_method": "reorder",
97 "weight_format": "real_quantized",
98 "weight_merge_groups": None,
99 },
100 }
101
102
103 def get_deepseek_v4_quark_config(exclude: list[str]) -> dict[str, Any]:
104 """Return the DeepSeek-V4-Pro-MXFP4 FSE quantization layout."""
105 # Mimics https://huggingface.co/amd/DeepSeek-V4-Pro-MXFP4.
106 quantization_config: dict[str, Any] = deepcopy(_QUARK_FSE_CONFIG)
107 quantization_config["exclude"] = exclude
108 mxfp4_config = cast(
109 dict[str, Any], deepcopy(quantization_config["global_quant_config"])
110 )
111 fp8_config = deepcopy(mxfp4_config)
112 fp8_config["input_tensors"].update(
113 {
114 "dtype": "fp8_e4m3",
115 "group_size": 128,
116 "symmetric": True,
117 "scale_type": None,
118 "scale_format": None,
119 "scale_calculation_mode": None,
120 "observer_cls": "PerGroupMinMaxObserver",
121 }
122 )
123 fp8_config["weight"].update(
124 {
125 "dtype": "fp8_e4m3",
126 "qscheme": "per_block",
127 "ch_axis": None,
128 "group_size": None,
129 "block_size": [128, 128],
130 "symmetric": True,
131 "scale_type": "float8_e8m0fnu",
132 "scale_format": None,
133 "scale_calculation_mode": None,
134 "observer_cls": "PerBlock2DMinMaxObserver",
135 }
136 )
137 quantization_config["layer_quant_config"] = {
138 r"re:mtp\.0\.ffn\.experts\.\d+\.w1": mxfp4_config,
139 r"re:mtp\.0\.ffn\.shared_experts\.w1": fp8_config,
140 }
141 return quantization_config
142
143
144 def _stub_quant_config() -> QuantizationConfig:
145 return cast(QuantizationConfig, object())
146
147
148 def get_fse_test_model_config(
149 model_type: str,
150 quantization_config: dict[str, Any],
151 ) -> tuple[object, type[nn.Module]]:
152 config: object
153 if model_type == "minimax_m3":
154 config = MiniMaxM3TextConfig(
155 hidden_size=128,
156 intermediate_size=32,
157 dense_intermediate_size=32,
158 num_hidden_layers=2,
159 num_attention_heads=1,
160 num_key_value_heads=1,
161 head_dim=128,
162 num_local_experts=2,
163 num_experts_per_tok=1,
164 moe_layer_freq=[0, 1],
165 sparse_attention_config=None,
166 rotary_dim=64,
167 quantization_config=quantization_config,
168 )
169 return config, minimax_m3_model.MiniMaxM3Model
170 if model_type == "deepseek_v4":
171 from vllm.models.deepseek_v4.amd.model import DeepseekV4Model
172
173 config = SimpleNamespace(
174 vocab_size=256,
175 hidden_size=128,
176 num_hidden_layers=1,
177 num_attention_heads=1,
178 head_dim=128,
179 max_position_embeddings=128,
180 sliding_window=None,
181 compress_ratios=[1],
182 rope_theta=10000.0,
183 compress_rope_theta=10000.0,
184 rope_parameters={"rope_type": "default"},
185 rms_norm_eps=1e-6,
186 hidden_act="silu",
187 q_lora_rank=0,
188 o_lora_rank=0,
189 o_groups=1,
190 qk_rope_head_dim=64,
191 index_head_dim=64,
192 index_n_heads=1,
193 index_topk=1,
194 n_routed_experts=2,
195 n_shared_experts=1,
196 num_experts_per_tok=1,
197 n_group=1,
198 topk_group=1,
199 moe_intermediate_size=32,
200 norm_topk_prob=False,
201 num_hash_layers=0,
202 swiglu_limit=0.0,
203 hc_eps=1e-6,
204 hc_mult=1,
205 hc_sinkhorn_iters=1,
206 expert_dtype="fp4",
207 num_nextn_predict_layers=1,
208 quantization_config=quantization_config,
209 )
210 return config, DeepseekV4Model
211 if model_type == "qwen3_5":
212 from vllm.model_executor.models.qwen3_5 import Qwen3_5Model
213
214 config = Qwen3_5MoeTextConfig(
215 vocab_size=256,
216 hidden_size=128,
217 num_hidden_layers=1,
218 num_attention_heads=1,
219 num_key_value_heads=1,
220 head_dim=128,
221 linear_key_head_dim=64,
222 linear_value_head_dim=64,
223 linear_num_key_heads=1,
224 linear_num_value_heads=1,
225 moe_intermediate_size=32,
226 shared_expert_intermediate_size=32,
227 num_experts_per_tok=1,
228 num_experts=2,
229 layer_types=["full_attention"],
230 quantization_config=quantization_config,
231 )
232 return config, Qwen3_5Model
233 if model_type == "deepseek_v2":
234 from transformers import DeepseekV2Config
235
236 from vllm.model_executor.models.deepseek_v2 import DeepseekV2Model
237
238 config = DeepseekV2Config(
239 vocab_size=256,
240 hidden_size=128,
241 intermediate_size=32,
242 num_hidden_layers=1,
243 num_attention_heads=1,
244 num_key_value_heads=1,
245 head_dim=128,
246 first_k_dense_replace=0,
247 n_routed_experts=2,
248 n_shared_experts=1,
249 num_experts_per_tok=1,
250 n_group=1,
251 topk_group=1,
252 moe_intermediate_size=32,
253 q_lora_rank=None,
254 kv_lora_rank=0,
255 qk_nope_head_dim=0,
256 qk_rope_head_dim=0,
257 v_head_dim=0,
258 quantization_config=quantization_config,
259 )
260 return config, DeepseekV2Model
261 if model_type == "glm4_moe":
262 from transformers.models.glm4_moe import Glm4MoeConfig
263
264 from vllm.model_executor.models.glm4_moe import Glm4MoeModel
265
266 config = Glm4MoeConfig(
267 vocab_size=256,
268 hidden_size=128,
269 intermediate_size=32,
270 num_hidden_layers=1,
271 num_attention_heads=1,
272 num_key_value_heads=1,
273 head_dim=128,
274 moe_intermediate_size=32,
275 n_routed_experts=2,
276 n_shared_experts=1,
277 num_experts_per_tok=1,
278 first_k_dense_replace=0,
279 quantization_config=quantization_config,
280 )
281 return config, Glm4MoeModel
282 raise ValueError(f"Unsupported FSE test model: {model_type}")
283
284
285 def test_determine_expert_counts_fuse_shared_experts_override(
286 monkeypatch: pytest.MonkeyPatch,
287 ) -> None:
288 common_args = (8, 0, 2)
289 assert determine_expert_counts(*common_args, True)[2] == 2
290 assert determine_expert_counts(*common_args, False)[2] == 0
291
292
293 def test_resolve_layer_fused_shared_expert_skips_compatibility_when_disabled(
294 monkeypatch: pytest.MonkeyPatch,
295 ) -> None:
296 monkeypatch.setattr(
297 fused_moe_utils.rocm_aiter_ops,
298 "is_fusion_moe_shared_experts_enabled",
299 lambda: False,
300 )
301 monkeypatch.setattr(
302 fused_moe_utils,
303 "is_shared_expert_quant_fse_compatible",
304 lambda *_: pytest.fail(
305 "compatibility must not be checked when FSE is disabled"
306 ),
307 )
308
309 assert not fused_moe_utils.resolve_layer_fused_shared_expert(
310 _stub_quant_config(), "model.layers.0.mlp"
311 )
312
313
314 def test_resolve_layer_fused_shared_expert_normalizes_unavailable_aiter(
315 monkeypatch: pytest.MonkeyPatch,
316 ) -> None:
317 monkeypatch.setattr(
318 fused_moe_utils.rocm_aiter_ops,
319 "is_fusion_moe_shared_experts_enabled",
320 lambda: None,
321 )
322
323 assert (
324 fused_moe_utils.resolve_layer_fused_shared_expert(
325 _stub_quant_config(), "model.layers.0.mlp"
326 )
327 is False
328 )
329
330
331 def test_resolve_layer_fused_shared_expert_passes_module_prefixes(
332 monkeypatch: pytest.MonkeyPatch,
333 ) -> None:
334 quant_config = _stub_quant_config()
335 monkeypatch.setattr(
336 fused_moe_utils.rocm_aiter_ops,
337 "is_fusion_moe_shared_experts_enabled",
338 lambda: True,
339 )
340
341 calls: list[tuple[object, str, str]] = []
342
343 def check_compatibility(
344 config: object, expert_prefix: str, shared_expert_prefix: str
345 ) -> tuple[bool, str | None]:
346 calls.append((config, expert_prefix, shared_expert_prefix))
347 return True, None
348
349 monkeypatch.setattr(
350 fused_moe_utils, "is_shared_expert_quant_fse_compatible", check_compatibility
351 )
352
353 assert fused_moe_utils.resolve_layer_fused_shared_expert(
354 quant_config,
355 "model.layers.0.mlp",
356 shared_expert_name="shared_expert",
357 )
358 assert calls == [
359 (
360 quant_config,
361 "model.layers.0.mlp.experts",
362 "model.layers.0.mlp.shared_expert",
363 )
364 ]
365
366
367 def test_resolve_layer_fused_shared_expert_rejects_incompatible_quantization(
368 monkeypatch: pytest.MonkeyPatch,
369 caplog: pytest.LogCaptureFixture,
370 ) -> None:
371 monkeypatch.setattr(
372 fused_moe_utils.rocm_aiter_ops,
373 "is_fusion_moe_shared_experts_enabled",
374 lambda: True,
375 )
376 monkeypatch.setattr(
377 fused_moe_utils,
378 "is_shared_expert_quant_fse_compatible",
379 lambda *_: (False, "shared experts are excluded"),
380 )
381
382 assert not fused_moe_utils.resolve_layer_fused_shared_expert(
383 _stub_quant_config(), "model.layers.0.mlp"
384 )
385 assert "shared experts are excluded" in caplog.text
386
387
388 def test_deepseek_v4_shared_expert_fse_uses_mtp_quantization_config_prefix(
389 monkeypatch: pytest.MonkeyPatch,
390 ) -> None:
391 class DeepseekV4Config:
392 expert_dtype = "fp4"
393
394 def _is_quark_mxfp4_ocp(self, hf_config: object) -> bool:
395 return True
396
397 hf_config = SimpleNamespace(
398 num_hidden_layers=2,
399 quantization_config={
400 "layer_quant_config": {
401 r"re:mtp\.0\.ffn\.shared_experts\.w1": {"weight": {"dtype": "fp4"}}
402 },
403 "global_quant_config": {"weight": {"dtype": "fp8"}},
404 },
405 )
406 monkeypatch.setattr(
407 deepseek_v4_quant_config, "DeepseekV4FP8Config", DeepseekV4Config
408 )
409 monkeypatch.setattr(
410 vllm_config_module,
411 "get_current_vllm_config",
412 lambda: SimpleNamespace(model_config=SimpleNamespace(hf_config=hf_config)),
413 )
414
415 compatible, reason = is_shared_expert_quant_fse_compatible(
416 DeepseekV4Config(),
417 "model.layers.2.ffn.experts",
418 "model.layers.2.ffn.shared_experts",
419 )
420
421 assert compatible
422 assert reason is None
423
424
425 def test_deepseek_v4_heterogeneous_fhmoe_keeps_native_intermediate_width() -> None:
426 from vllm.models.deepseek_v4.amd.model import _prepare_native_fp8_shared_expert
427
428 hidden_size = 7168
429 intermediate_size = 384
430 w13 = torch.empty((2 * intermediate_size, hidden_size), dtype=torch.float8_e4m3fn)
431 w2 = torch.empty((hidden_size, intermediate_size), dtype=torch.float8_e4m3fn)
432 w13_scale_bytes = (torch.arange(6 * 56).reshape(6, 56).remainder(20) + 0x60).to(
433 torch.uint8
434 )
435 w2_scale_bytes = (torch.arange(56 * 3).reshape(56, 3).remainder(30) + 0x50).to(
436 torch.uint8
437 )
438 w13_scale = w13_scale_bytes.view(torch.float8_e8m0fnu)
439 w2_scale = w2_scale_bytes.view(torch.float8_e8m0fnu)
440
441 prepared = _prepare_native_fp8_shared_expert(
442 w13, w2, w13_scale, w2_scale, intermediate_size
443 )
444
445 assert prepared[0].shape == (1, 768, 7168)
446 assert prepared[1].shape == (1, 7168, 384)
447 assert prepared[2].shape == (768, 224)
448 assert prepared[3].shape == (7168, 16)
449 expected_w13_scale = w13_scale_bytes.repeat_interleave(
450 128, dim=0
451 ).repeat_interleave(4, dim=1)
452 expected_w2_scale = w2_scale_bytes.repeat_interleave(128, dim=0).repeat_interleave(
453 4, dim=1
454 )
455 assert torch.equal(prepared[2].view(torch.uint8), expected_w13_scale)
456 assert torch.equal(prepared[3].view(torch.uint8)[:, :12], expected_w2_scale)
457 assert torch.all(prepared[3].view(torch.uint8)[:, 12:] == 0x7F)
458
459
460 @pytest.mark.parametrize(
461 ("num_tokens", "supported_through", "expected"),
462 [
463 (0, 4096, False),
464 (1, 4096, True),
465 (1536, 4096, True),
466 (2048, 4096, True),
467 (2049, 4096, True),
468 (4096, 4096, True),
469 (4097, 4096, False),
470 (1536, 0, False),
471 ],
472 )
473 def test_deepseek_v4_heterogeneous_fhmoe_token_policy(
474 monkeypatch: pytest.MonkeyPatch,
475 num_tokens: int,
476 supported_through: int,
477 expected: bool,
478 ) -> None:
479 from vllm.models.deepseek_v4.amd import model as deepseek_v4_model
480
481 checked_tokens: list[int] = []
482
483 def supports(num_tokens: int) -> bool:
484 checked_tokens.append(num_tokens)
485 return num_tokens <= supported_through
486
487 monkeypatch.setattr(
488 deepseek_v4_model.rocm_aiter_ops,
489 "fused_moe_supports_heterogeneous_shared_expert",
490 supports,
491 )
492
493 assert deepseek_v4_model._use_heterogeneous_fhmoe(num_tokens) is expected
494 assert checked_tokens == ([] if num_tokens == 0 else [num_tokens])
495
496
497 def _supported_fhmoe_signature(
498 shared_w1=None,
499 shared_w2=None,
500 shared_w1_scale=None,
501 shared_w2_scale=None,
502 shared_expert_id=-1,
503 ) -> None:
504 pass
505
506
507 def _incomplete_fhmoe_signature(
508 shared_w1=None,
509 shared_w2=None,
510 shared_w1_scale=None,
511 shared_w2_scale=None,
512 ) -> None:
513 pass
514
515
516 def _install_fake_aiter_fhmoe(
517 monkeypatch: pytest.MonkeyPatch,
518 supports_dsv4_i384_fhmoe: object,
519 fused_moe: object,
520 ) -> None:
521 fake_aiter = ModuleType("aiter")
522 fake_aiter.__path__ = []
523 fake_fhmoe = ModuleType("aiter.fhmoe")
524 if supports_dsv4_i384_fhmoe is not None:
525 fake_fhmoe.__dict__["supports_dsv4_i384_fhmoe"] = supports_dsv4_i384_fhmoe
526 fake_fused_moe = ModuleType("aiter.fused_moe")
527 fake_fused_moe.__dict__["fused_moe"] = fused_moe
528 fake_aiter.__dict__["fhmoe"] = fake_fhmoe
529 fake_aiter.__dict__["fused_moe"] = fake_fused_moe
530 monkeypatch.setitem(sys.modules, "aiter", fake_aiter)
531 monkeypatch.setitem(sys.modules, "aiter.fhmoe", fake_fhmoe)
532 monkeypatch.setitem(sys.modules, "aiter.fused_moe", fake_fused_moe)
533
534
535 def _supports_fhmoe_through_2047(max_tokens: int) -> bool:
536 return max_tokens <= 2047
537
538
539 def _supports_fhmoe_through_2048(max_tokens: int) -> bool:
540 return max_tokens <= 2048
541
542
543 def _supports_fhmoe_through_4096(max_tokens: int) -> bool:
544 return max_tokens <= 4096
545
546
547 def _raises_fhmoe_config_error(max_tokens: int) -> bool:
548 raise OSError
549
550
551 def _returns_truthy_non_bool(max_tokens: int) -> int:
552 return 1
553
554
555 @pytest.mark.parametrize(
556 ("num_tokens", "capability", "fused_moe", "expected"),
557 [
558 (0, _supports_fhmoe_through_2048, _supported_fhmoe_signature, False),
559 (True, _supports_fhmoe_through_2048, _supported_fhmoe_signature, False),
560 (2048, None, _supported_fhmoe_signature, False),
561 (2048, True, _supported_fhmoe_signature, False),
562 (2048, _raises_fhmoe_config_error, _supported_fhmoe_signature, False),
563 (2048, _returns_truthy_non_bool, _supported_fhmoe_signature, False),
564 (2048, _supports_fhmoe_through_2047, _supported_fhmoe_signature, False),
565 (2048, _supports_fhmoe_through_2048, _incomplete_fhmoe_signature, False),
566 (2048, _supports_fhmoe_through_2048, _supported_fhmoe_signature, True),
567 (2049, _supports_fhmoe_through_2048, _supported_fhmoe_signature, False),
568 (4096, _supports_fhmoe_through_4096, _supported_fhmoe_signature, True),
569 (4097, _supports_fhmoe_through_4096, _supported_fhmoe_signature, False),
570 ],
571 )
572 def test_deepseek_v4_heterogeneous_fhmoe_aiter_capability(
573 monkeypatch: pytest.MonkeyPatch,
574 num_tokens: object,
575 capability: object,
576 fused_moe: object,
577 expected: bool,
578 ) -> None:
579 from vllm._aiter_ops import rocm_aiter_ops
580
581 _install_fake_aiter_fhmoe(monkeypatch, capability, fused_moe)
582
583 assert (
584 rocm_aiter_ops._probe_dsv4_i384_fhmoe_capability(cast(int, num_tokens))
585 is expected
586 )
587
588
589 def test_deepseek_v4_heterogeneous_fhmoe_aiter_capability_catches_import_error(
590 monkeypatch: pytest.MonkeyPatch,
591 ) -> None:
592 from vllm._aiter_ops import rocm_aiter_ops
593
594 fake_aiter = ModuleType("aiter")
595 fake_aiter.__path__ = []
596 monkeypatch.setitem(sys.modules, "aiter", fake_aiter)
597 monkeypatch.setitem(sys.modules, "aiter.fhmoe", None)
598
599 assert not rocm_aiter_ops._probe_dsv4_i384_fhmoe_capability(2048)
600
601
602 @pytest.mark.parametrize("error", [TypeError, ValueError])
603 def test_deepseek_v4_heterogeneous_fhmoe_aiter_capability_catches_signature_error(
604 monkeypatch: pytest.MonkeyPatch,
605 error: type[Exception],
606 ) -> None:
607 from vllm._aiter_ops import rocm_aiter_ops
608
609 _install_fake_aiter_fhmoe(
610 monkeypatch, _supports_fhmoe_through_2048, _supported_fhmoe_signature
611 )
612
613 def raise_signature_error(_: object) -> inspect.Signature:
614 raise error
615
616 monkeypatch.setattr(inspect, "signature", raise_signature_error)
617
618 assert not rocm_aiter_ops._probe_dsv4_i384_fhmoe_capability(2048)
619
620
621 @pytest.mark.parametrize(
622 ("setting", "value", "expected"),
623 [
624 ("data_parallel_size", 1, True),
625 ("data_parallel_size", 2, False),
626 ("prefill_context_parallel_size", 2, False),
627 ("topk_method", "greedy", False),
628 ("fhmoe_supported", False, False),
629 ],
630 )
631 def test_deepseek_v4_heterogeneous_fhmoe_compatibility_gates(
632 monkeypatch: pytest.MonkeyPatch,
633 setting: str,
634 value: object,
635 expected: bool,
636 ) -> None:
637 from vllm.models.deepseek_v4.amd import model as deepseek_v4_model
638
639 hf_config = SimpleNamespace(
640 n_routed_experts=384,
641 num_experts_per_tok=6,
642 n_shared_experts=1,
643 hidden_size=7168,
644 moe_intermediate_size=3072,
645 hidden_act="silu",
646 expert_dtype="fp4",
647 topk_method="noaux_tc",
648 )
649 parallel_config = SimpleNamespace(
650 enable_expert_parallel=False,
651 enable_eplb=False,
652 tensor_parallel_size=8,
653 data_parallel_size=1,
654 prefill_context_parallel_size=1,
655 )
656 if setting == "topk_method":
657 hf_config.topk_method = value
658 elif setting != "fhmoe_supported":
659 setattr(parallel_config, setting, value)
660
661 quant_config = SimpleNamespace(
662 get_name=lambda: "deepseek_v4_fp8",
663 moe_quant_algo="",
664 weight_block_size=[128, 128],
665 is_checkpoint_fp8_serialized=True,
666 is_scale_e8m0=True,
667 ignored_layers=None,
668 )
669 vllm_config = SimpleNamespace(
670 model_config=SimpleNamespace(hf_config=hf_config, dtype=torch.bfloat16),
671 quant_config=quant_config,
672 parallel_config=parallel_config,
673 kernel_config=SimpleNamespace(moe_backend="aiter"),
674 offload_config=None,
675 )
676 monkeypatch.setattr(deepseek_v4_model.current_platform, "is_rocm", lambda: True)
677 monkeypatch.setattr(
678 deepseek_v4_model.envs,
679 "VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS",
680 True,
681 )
682 monkeypatch.setattr(deepseek_v4_model, "on_gfx950", lambda: True)
683 monkeypatch.setattr(
684 deepseek_v4_model.rocm_aiter_ops,
685 "is_fusion_moe_shared_experts_enabled",
686 lambda: True,
687 )
688 checked_tokens: list[int] = []
689 fhmoe_supported = setting != "fhmoe_supported" or value is True
690
691 def supports(num_tokens: int) -> bool:
692 checked_tokens.append(num_tokens)
693 return fhmoe_supported
694
695 monkeypatch.setattr(
696 deepseek_v4_model.rocm_aiter_ops,
697 "fused_moe_supports_heterogeneous_shared_expert",
698 supports,
699 )
700
701 assert (
702 deepseek_v4_model._heterogeneous_shared_expert_enabled(
703 cast(VllmConfig, vllm_config)
704 )
705 is expected
706 )
707 assert checked_tokens == [1]
708
709
710 @pytest.mark.parametrize(
711 ("weights_shape", "ids_shape", "message"),
712 [
713 ((2, 7, 1), (2, 7, 1), "equal two-dimensional shapes"),
714 ((2, 7), (3, 7), "equal two-dimensional shapes"),
715 ((2, 6), (2, 6), "exactly 7 columns"),
716 ((2, 8), (2, 8), "exactly 7 columns"),
717 ],
718 )
719 def test_deepseek_v4_heterogeneous_fhmoe_rejects_invalid_routes(
720 weights_shape: tuple[int, ...],
721 ids_shape: tuple[int, ...],
722 message: str,
723 ) -> None:
724 from vllm.models.deepseek_v4.amd.model import _validate_heterogeneous_routes
725
726 with pytest.raises(ValueError, match=message):
727 _validate_heterogeneous_routes(
728 torch.empty(weights_shape),
729 torch.empty(ids_shape, dtype=torch.int64),
730 experts_per_token=6,
731 )
732
733
734 def test_deepseek_v4_heterogeneous_fhmoe_accepts_appended_shared_route() -> None:
735 from vllm.models.deepseek_v4.amd.model import _validate_heterogeneous_routes
736
737 _validate_heterogeneous_routes(
738 torch.empty((2, 7)),
739 torch.empty((2, 7), dtype=torch.int64),
740 experts_per_token=6,
741 )
742
743
744 def test_aiter_fused_moe_validates_shared_expert_arguments() -> None:
745 from vllm._aiter_ops import (
746 _validate_rocm_aiter_fused_moe_shared_expert_args,
747 )
748
749 validate = _validate_rocm_aiter_fused_moe_shared_expert_args
750 shared_tensor = torch.empty(0)
751
752 assert not validate(None, None, None, None, -1)
753 with pytest.raises(ValueError, match="requires shared weights and scales"):
754 validate(None, None, None, None, 0)
755 with pytest.raises(ValueError, match="both shared weights and scales"):
756 validate(shared_tensor, None, None, None, 0)
757 with pytest.raises(ValueError, match="non-negative shared expert ID"):
758 validate(shared_tensor, shared_tensor, shared_tensor, shared_tensor, -1)
759 assert validate(shared_tensor, shared_tensor, shared_tensor, shared_tensor, 0)
760
761
762 def test_is_model_fused_shared_expert_compatible() -> None:
763 class MoE(nn.Module):
764 def __init__(self, enabled: bool) -> None:
765 super().__init__()
766 self.is_fused_shared_expert_enabled = enabled
767
768 class Layer(nn.Module):
769 def __init__(self, enabled: bool) -> None:
770 super().__init__()
771 self.mlp = MoE(enabled)
772
773 enabled_layers = nn.ModuleList([Layer(True)])
774 disabled_layers = nn.ModuleList([Layer(False)])
775 mixed_layers = nn.ModuleList([Layer(True), Layer(False)])
776 empty_layers = nn.ModuleList()
777 pipeline_layers = nn.ModuleList([Layer(True), PPMissingLayer()])
778
779 assert fused_moe_utils.is_model_fused_shared_expert_compatible(
780 enabled_layers, MoE, "mlp"
781 )
782 assert not fused_moe_utils.is_model_fused_shared_expert_compatible(
783 disabled_layers, MoE, "mlp"
784 )
785 assert not fused_moe_utils.is_model_fused_shared_expert_compatible(
786 empty_layers, MoE, "mlp"
787 )
788 assert fused_moe_utils.is_model_fused_shared_expert_compatible(
789 pipeline_layers, MoE, "mlp"
790 )
791 with pytest.raises(NotImplementedError, match="1 enabled and 1 disabled layers"):
792 fused_moe_utils.is_model_fused_shared_expert_compatible(
793 mixed_layers, MoE, "mlp"
794 )
795
796
797 @pytest.mark.parametrize(
798 "model_type",
799 ["minimax_m3", "deepseek_v4", "qwen3_5", "glm4_moe", "deepseek_v2"],
800 )
801 @pytest.mark.parametrize(
802 ("use_fse", "exclude"),
803 [
804 (False, []),
805 (True, []),
806 (True, [r"re:.*\.shared_experts?\..*"]),
807 ],
808 )
809 def test_models_fse_init(
810 model_type: str,
811 use_fse: bool,
812 exclude: list[str],
813 dist_init: None,
814 monkeypatch: pytest.MonkeyPatch,
815 ) -> None:
816 """Model construction resolves FSE consistently with Quark quantization."""
817 quantization_config: dict[str, Any] = (
818 get_deepseek_v4_quark_config(["layers.0.ffn.shared_experts"] if exclude else [])
819 if model_type == "deepseek_v4"
820 else {**_QUARK_FSE_CONFIG, "exclude": exclude}
821 )
822
823 config, model_constructor = get_fse_test_model_config(
824 model_type, quantization_config
825 )
826 vllm_config = VllmConfig()
827 vllm_config.model_config = SimpleNamespace(
828 hf_config=config,
829 hf_text_config=config,
830 dtype=torch.bfloat16,
831 max_model_len=128,
832 is_diffusion=False,
833 head_dtype=None,
834 is_mm_prefix_lm=False,
835 multimodal_config=None,
836 quantization_config=None,
837 runner_type="generate",
838 is_moe=True,
839 logits_processors=None,
840 rswa_window=None,
841 )
842 vllm_config.parallel_config.enable_expert_parallel = False
843 if model_type == "deepseek_v4":
844 from vllm.models.deepseek_v4.quant_config import DeepseekV4FP8Config
845
846 vllm_config.cache_config.cache_dtype = "fp8_ds_mla"
847 vllm_config.quant_config = DeepseekV4FP8Config(
848 is_checkpoint_fp8_serialized=True,
849 weight_block_size=[128, 128],
850 )
851 else:
852 vllm_config.quant_config = QuarkConfig(quantization_config)
853
854 import vllm.envs as envs
855 from vllm._aiter_ops import rocm_aiter_ops
856
857 if model_type == "deepseek_v4":
858 from vllm.models.deepseek_v4.amd import model as deepseek_v4_model
859
860 warning_logger = deepseek_v4_model.logger
861 elif model_type == "minimax_m3":
862 warning_logger = minimax_m3_model.logger
863 else:
864 warning_logger = fused_moe_utils.logger
865
866 with monkeypatch.context() as mp:
867 mp.setenv("VLLM_ROCM_USE_AITER", str(use_fse))
868 mp.setenv("VLLM_ROCM_USE_AITER_MOE", str(use_fse))
869 mp.setenv("VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS", str(use_fse))
870 mp.setattr(
871 "vllm.model_executor.layers.fused_moe.experts."
872 "ocp_mx_emulation_moe.has_quark",
873 lambda: True,
874 )
875 importlib.reload(envs)
876 mp.setattr(envs, "VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS", use_fse)
877 rocm_aiter_ops.refresh_env_variables()
878 aiter_fse_enabled = bool(rocm_aiter_ops.is_fusion_moe_shared_experts_enabled())
879 # These AMD-specific models currently use the raw environment flag.
880 # and do not rely on `rocm_aiter_ops.is_fusion_moe_shared_experts_enabled()`.
881 fse_enabled = use_fse and (
882 aiter_fse_enabled or model_type in {"deepseek_v4", "minimax_m3"}
883 )
884
885 with (
886 patch.object(warning_logger, "warning") as warning,
887 set_current_vllm_config(vllm_config),
888 ):
889 model = model_constructor(vllm_config=vllm_config)
890 mtp = None
891 if model_type == "deepseek_v4" and use_fse and not exclude:
892 from vllm.models.deepseek_v4.amd.mtp import (
893 DeepSeekV4MTP,
894 )
895
896 vllm_config.speculative_config = SimpleNamespace(
897 draft_model_config=SimpleNamespace(hf_config=config),
898 method="mtp",
899 parallel_drafting=False,
900 enable_adaptive_verification=False,
901 use_dspark=lambda: False,
902 )
903 mtp = DeepSeekV4MTP(vllm_config=vllm_config)
904 assert model.is_fused_shared_expert_enabled is (fse_enabled and not exclude)
905
906 # The dummy quant config here uses mixed mxfp4/fp8 for experts/shared_expert
907 # so should just raise a warning.
908 if mtp is not None:
909 assert not mtp.model.layers[
910 "1"
911 ].mtp_block.ffn.is_fused_shared_expert_enabled
912 warning.assert_called_once()
913 assert (
914 "DeepSeek-V4 shared experts at mtp.0.ffn.shared_experts"
915 in (warning.call_args.args[1])
916 )
917 if aiter_fse_enabled and exclude:
918 warning.assert_called_once()
919 assert (
920 "VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS is enabled"
921 in (warning.call_args.args[0])
922 )
923 assert "excludes shared experts" in warning.call_args.args[1]
924
925 importlib.reload(envs)
926 rocm_aiter_ops.refresh_env_variables()
927
928
929 @pytest.mark.parametrize(
930 ("exclude", "expected"),
931 [([], True), ([r"re:.*\.shared_expert\..*"], False)],
932 )
933 def test_quark_shared_expert_fse_compatibility(
934 exclude: list[str], expected: bool
935 ) -> None:
936 compatible, reason = is_shared_expert_quant_fse_compatible(
937 QuarkConfig(
938 {
939 "exclude": exclude,
940 "global_quant_config": {},
941 "layer_quant_config": {},
942 }
943 ),
944 "model.layers.0.mlp.experts",
945 "model.layers.0.mlp.shared_expert",
946 )
947
948 assert compatible is expected
949 if expected:
950 assert reason is None
951 else:
952 assert (
953 reason
954 == "Quark excludes shared experts at model.layers.0.mlp.shared_expert"
955 )
956
957
958 def test_quark_shared_expert_fse_exclude_is_scoped_to_the_layer() -> None:
959 """Excluding one layer's shared experts must not disable FSE elsewhere.
960
961 amd/GLM-5.2-MXFP4 excludes its MTP layer (78) wholesale, including
962 ``model.layers.78.mlp.shared_experts.{gate,up,down}_proj``, while the 75
963 target MoE layers keep MXFP4 shared experts.
964 """
965 quant_config = QuarkConfig(
966 {
967 "exclude": [
968 f"model.layers.78.mlp.shared_experts.{projection_name}"
969 for projection_name in ("gate_proj", "up_proj", "down_proj")
970 ],
971 "global_quant_config": {},
972 "layer_quant_config": {},
973 }
974 )
975 quant_config.packed_modules_mapping = {"gate_up_proj": ["gate_proj", "up_proj"]}
976
977 assert is_shared_expert_quant_fse_compatible(
978 quant_config,
979 "model.layers.3.mlp.experts",
980 "model.layers.3.mlp.shared_experts",
981 ) == (True, None)
982
983 compatible, reason = is_shared_expert_quant_fse_compatible(
984 quant_config,
985 "model.layers.78.mlp.experts",
986 "model.layers.78.mlp.shared_experts",
987 )
988 assert compatible is False
989 assert (
990 reason == "Quark excludes shared experts at model.layers.78.mlp.shared_experts"
991 )
992
993
994 def test_quark_shared_expert_fse_rejects_partially_excluded_packed_projection() -> None:
995 """Packed shared-expert projections require consistent shard exclusions."""
996 shared_expert_prefix = "model.layers.0.mlp.shared_expert"
997 quant_config = QuarkConfig(
998 {
999 "exclude": [f"{shared_expert_prefix}.gate_proj"],
1000 "global_quant_config": {},
1001 "layer_quant_config": {},
1002 }
1003 )
1004 quant_config.packed_modules_mapping = {"gate_up_proj": ["gate_proj", "up_proj"]}
1005
1006 with pytest.raises(ValueError, match="different quantization schemes"):
1007 is_shared_expert_quant_fse_compatible(
1008 quant_config, "model.layers.0.mlp.experts", shared_expert_prefix
1009 )
1010
1011
1012 def test_quark_shared_expert_fse_ignores_sibling_gate_exclusions() -> None:
1013 """Gates beside a shared expert must not disable FSE.
1014
1015 Qwen3.x MoE checkpoints (e.g. amd/Qwen3.5-397B-A17B-MoE-MXFP4) quantize the
1016 shared expert to MXFP4 but leave the tiny routing gates next to it in BF16:
1017 ``model.language_model.layers.N.mlp.shared_expert_gate`` and
1018 ``model.language_model.layers.N.mlp.gate``. Neither is a shared-expert
1019 projection, so fusion must stay enabled. Matching ``shared_expert`` against
1020 raw exclude entries also matches ``shared_expert_gate``, which silently
1021 disables FSE on every such checkpoint.
1022 """
1023 expert_prefix = "model.language_model.layers.0.mlp.experts"
1024 shared_expert_prefix = "model.language_model.layers.0.mlp.shared_expert"
1025 quant_config = QuarkConfig(
1026 {
1027 "exclude": [
1028 "model.language_model.layers.0.mlp.gate",
1029 f"{shared_expert_prefix}_gate",
1030 ],
1031 "global_quant_config": {},
1032 "layer_quant_config": {},
1033 }
1034 )
1035 quant_config.packed_modules_mapping = {"gate_up_proj": ["gate_proj", "up_proj"]}
1036
1037 assert is_shared_expert_quant_fse_compatible(
1038 quant_config, expert_prefix, shared_expert_prefix
1039 ) == (True, None)
1040
1041 # The gate exclusions must not mask a genuinely unquantized shared expert.
1042 quant_config.quant_config["exclude"].append(f"{shared_expert_prefix}.down_proj")
1043 compatible, reason = is_shared_expert_quant_fse_compatible(
1044 quant_config, expert_prefix, shared_expert_prefix
1045 )
1046 assert compatible is False
1047 assert reason == f"Quark excludes shared experts at {shared_expert_prefix}"
1048
1049
1050 def test_quark_ocp_mx_group_size_for_layer() -> None:
1051 quant_config = QuarkConfig({**_QUARK_FSE_CONFIG, "exclude": []})
1052 assert (
1053 get_quark_ocp_mx_group_size(
1054 quant_config,
1055 "model.layers.0.mlp.shared_expert.down_proj",
1056 )
1057 == 32
1058 )
1059
1060 quant_config.quant_config["exclude"] = ["model.layers.0.mlp.shared_expert_gate"]
1061 assert (
1062 get_quark_ocp_mx_group_size(
1063 quant_config,
1064 "model.layers.0.mlp.shared_expert.down_proj",
1065 )
1066 == 32
1067 )
1068
1069 quant_config.quant_config["exclude"] = [
1070 "model.layers.0.mlp.shared_expert.down_proj"
1071 ]
1072 assert (
1073 get_quark_ocp_mx_group_size(
1074 quant_config,
1075 "model.layers.0.mlp.shared_expert.down_proj",
1076 )
1077 is None
1078 )
1079
1080
1081 @pytest.mark.parametrize("tp_size", [1, 2, 4])
1082 def test_quark_ocp_mx_shared_expert_uses_supported_tp_shards(tp_size: int) -> None:
1083 assert not _should_replicate_misaligned_shared_expert(
1084 intermediate_size=640,
1085 tp_size=tp_size,
1086 group_size=32,
1087 enable_expert_parallel=False,
1088 is_sequence_parallel=False,
1089 )
1090
1091
1092 def test_quark_ocp_mx_shared_expert_rejects_plain_tp8() -> None:
1093 with pytest.raises(
1094 ValueError,
1095 match="TP size 8 produces a partition of 80",
1096 ):
1097 _should_replicate_misaligned_shared_expert(
1098 intermediate_size=640,
1099 tp_size=8,
1100 group_size=32,
1101 enable_expert_parallel=False,
1102 is_sequence_parallel=False,
1103 )
1104
1105
1106 def test_quark_ocp_mx_shared_expert_is_replicated_for_tep8() -> None:
1107 assert _should_replicate_misaligned_shared_expert(
1108 intermediate_size=640,
1109 tp_size=8,
1110 group_size=32,
1111 enable_expert_parallel=True,
1112 is_sequence_parallel=False,
1113 )
1114
1115
1116 def test_quark_ocp_mx_shared_expert_is_replicated_when_not_tp_divisible() -> None:
1117 assert _should_replicate_misaligned_shared_expert(
1118 intermediate_size=672,
1119 tp_size=8,
1120 group_size=32,
1121 enable_expert_parallel=True,
1122 is_sequence_parallel=False,
1123 )
1124
1125
1126 def test_quark_shared_expert_fse_requires_matching_layer_quant_configs() -> None:
1127 global_quant_config = {"weight": {"dtype": "fp4"}}
1128 quant_config = QuarkConfig(
1129 {
1130 "exclude": [],
1131 "global_quant_config": global_quant_config,
1132 "layer_quant_config": {
1133 "model.layers.0.mlp.shared_expert.gate_up_proj": {
1134 "weight": {"dtype": "fp8"}
1135 },
1136 "model.layers.0.mlp.shared_expert.down_proj": {
1137 "weight": {"dtype": "fp8"}
1138 },
1139 },
1140 }
1141 )
1142
1143 compatible, reason = is_shared_expert_quant_fse_compatible(
1144 quant_config,
1145 "model.layers.0.mlp.experts",
1146 "model.layers.0.mlp.shared_expert",
1147 )
1148
1149 assert not compatible
1150 assert reason == (
1151 "Quark uses different quantization configurations for routed and "
1152 "shared experts at model.layers.0.mlp.shared_expert"
1153 )
1154
1155
1156 def test_quark_shared_expert_fse_rejects_partial_packed_projection_override() -> None:
1157 fp4_config = {"weight": {"dtype": "fp4"}}
1158 fp8_config = {"weight": {"dtype": "fp8"}}
1159 quant_config = QuarkConfig(
1160 {
1161 "exclude": [],
1162 "global_quant_config": fp4_config,
1163 "layer_quant_config": {
1164 "model.layers.0.mlp.experts": fp8_config,
1165 "model.layers.0.mlp.shared_expert.gate_proj": fp8_config,
1166 "model.layers.0.mlp.shared_expert.down_proj": fp8_config,
1167 },
1168 }
1169 )
1170 quant_config.packed_modules_mapping = {"gate_up_proj": ["gate_proj", "up_proj"]}
1171
1172 compatible, reason = is_shared_expert_quant_fse_compatible(
1173 quant_config,
1174 "model.layers.0.mlp.experts",
1175 "model.layers.0.mlp.shared_expert",
1176 )
1177
1178 assert not compatible
1179 assert reason == (
1180 "Quark uses different quantization configurations for routed and "
1181 "shared experts at model.layers.0.mlp.shared_expert"
1182 )
1183
1184
1185 def test_quark_layer_config_from_name_checks_packed_projections() -> None:
1186 quant_config = QuarkConfig(
1187 {
1188 "global_quant_config": {"weight": {"dtype": "fp4"}},
1189 "layer_quant_config": {
1190 "model.layers.0.mlp.shared_expert.w1": {"weight": {"dtype": "fp4"}},
1191 "model.layers.0.mlp.shared_expert.w3": {"weight": {"dtype": "fp8"}},
1192 },
1193 }
1194 )
1195 quant_config.packed_modules_mapping = {"gate_up_proj": ["w1", "w3"]}
1196
1197 with pytest.raises(ValueError, match="requires all to use the same scheme"):
1198 quant_config.get_layer_quant_config_from_name(
1199 "model.layers.0.mlp.shared_expert.gate_up_proj"
1200 )
1201
1202
1203 def test_quark_packed_layer_config_must_match_global_config() -> None:
1204 quant_config = QuarkConfig(
1205 {
1206 "global_quant_config": {"weight": {"dtype": "fp4"}},
1207 "layer_type_quant_config": {},
1208 "layer_quant_config": {
1209 "model.layers.0.mlp.shared_expert.w1": {"weight": {"dtype": "fp8"}},
1210 },
1211 }
1212 )
1213 quant_config.packed_modules_mapping = {"gate_up_proj": ["w1", "w3"]}
1214
1215 with pytest.raises(ValueError, match="requires all to use the same scheme"):
1216 quant_config._find_matched_config(
1217 "model.layers.0.mlp.shared_expert.gate_up_proj", nn.Module()
1218 )
1219
1220
1221 def test_non_quark_shared_expert_fse_is_incompatible() -> None:
1222 compatible, reason = is_shared_expert_quant_fse_compatible(
1223 _stub_quant_config(),
1224 "model.layers.0.mlp.experts",
1225 "model.layers.0.mlp.shared_experts",
1226 )
1227
1228 assert not compatible
1229 assert reason == (
1230 "shared-expert FSE quantization compatibility is not implemented for object"
1231 )
1232
1233
1234 def _fp8_config(**kwargs: Any) -> Fp8Config:
1235 return Fp8Config(
1236 activation_scheme="dynamic",
1237 weight_block_size=[128, 128],
1238 **kwargs,
1239 )
1240
1241
1242 def test_block_fp8_shared_expert_fse_is_compatible() -> None:
1243 compatible, reason = is_shared_expert_quant_fse_compatible(
1244 _fp8_config(),
1245 "model.layers.0.mlp.experts",
1246 "model.layers.0.mlp.shared_experts",
1247 )
1248
1249 assert compatible
1250 assert reason is None
1251
1252
1253 def test_per_tensor_fp8_shared_expert_fse_is_incompatible() -> None:
1254 """Per-tensor scales are 0-D, so the shared-expert chunker cannot slice them."""
1255 compatible, reason = is_shared_expert_quant_fse_compatible(
1256 Fp8Config(activation_scheme="dynamic"),
1257 "model.layers.0.mlp.experts",
1258 "model.layers.0.mlp.shared_experts",
1259 )
1260
1261 assert not compatible
1262 assert reason == (
1263 "FP8 shared-expert FSE is only implemented for block-quantized checkpoints"
1264 )
1265
1266
1267 @pytest.mark.parametrize("store_dtype", ["mxfp4", "nvfp4"])
1268 def test_store_dtype_fp8_shared_expert_fse_is_incompatible(store_dtype: str) -> None:
1269 """Any routed-expert storage override rules out fusing the shared expert."""
1270 compatible, reason = is_shared_expert_quant_fse_compatible(
1271 _fp8_config(store_dtype=store_dtype),
1272 "model.layers.0.mlp.experts",
1273 "model.layers.0.mlp.shared_experts",
1274 )
1275
1276 assert not compatible
1277 assert reason == (
1278 f"FP8 stores routed experts as {store_dtype}, which is not supported "
1279 "for fused shared experts at model.layers.0.mlp.shared_experts"
1280 )
1281
1282
1283 @pytest.mark.parametrize(
1284 "ignored_layers",
1285 [
1286 ["model.layers.0.mlp.experts"],
1287 ["model.layers.0.mlp.shared_experts.gate_up_proj"],
1288 ["model.layers.0.mlp.shared_experts.down_proj"],
1289 ],
1290 )
1291 def test_fp8_shared_expert_fse_rejects_asymmetric_ignored_layers(
1292 ignored_layers: list[str],
1293 ) -> None:
1294 compatible, reason = is_shared_expert_quant_fse_compatible(
1295 _fp8_config(ignored_layers=ignored_layers),
1296 "model.layers.0.mlp.experts",
1297 "model.layers.0.mlp.shared_experts",
1298 )
1299
1300 assert not compatible
1301 assert reason == (
1302 "FP8 ignores routed and shared experts inconsistently at "
1303 "model.layers.0.mlp.shared_experts"
1304 )
1305
1306
1307 def test_fp8_shared_expert_fse_allows_symmetric_ignored_layers() -> None:
1308 """Both sides unquantized is still a consistent scheme."""
1309 compatible, reason = is_shared_expert_quant_fse_compatible(
1310 _fp8_config(
1311 ignored_layers=[
1312 "model.layers.0.mlp.experts",
1313 "model.layers.0.mlp.shared_experts.gate_up_proj",
1314 "model.layers.0.mlp.shared_experts.down_proj",
1315 ]
1316 ),
1317 "model.layers.0.mlp.experts",
1318 "model.layers.0.mlp.shared_experts",
1319 )
1320
1321 assert compatible
1322 assert reason is None
1323
1324
1325 def test_fp8_shared_expert_fse_expands_packed_projections() -> None:
1326 """Both shards of a fused projection ignored is symmetric within that
1327 projection, so the mismatch reported is against the routed experts."""
1328 quant_config = _fp8_config(
1329 ignored_layers=[
1330 "model.layers.0.mlp.shared_experts.gate_proj",
1331 "model.layers.0.mlp.shared_experts.up_proj",
1332 ]
1333 )
1334 quant_config.packed_modules_mapping = {"gate_up_proj": ["gate_proj", "up_proj"]}
1335
1336 compatible, reason = is_shared_expert_quant_fse_compatible(
1337 quant_config,
1338 "model.layers.0.mlp.experts",
1339 "model.layers.0.mlp.shared_experts",
1340 )
1341
1342 assert not compatible
1343 assert reason == (
1344 "FP8 ignores routed and shared experts inconsistently at "
1345 "model.layers.0.mlp.shared_experts"
1346 )
1347
1348
1349 def test_fp8_shared_expert_fse_propagates_partial_shard_exclusion() -> None:
1350 """Half a fused projection excluded is rejected by `is_layer_skipped`
1351 itself, exactly as it is in `Fp8Config.get_quant_method`."""
1352 quant_config = _fp8_config(
1353 ignored_layers=["model.layers.0.mlp.shared_experts.gate_proj"]
1354 )
1355 quant_config.packed_modules_mapping = {"gate_up_proj": ["gate_proj", "up_proj"]}
1356
1357 with pytest.raises(ValueError, match="some but not all shards"):
1358 is_shared_expert_quant_fse_compatible(
1359 quant_config,
1360 "model.layers.0.mlp.experts",
1361 "model.layers.0.mlp.shared_experts",
1362 )