Repository navigation
[Quantization] Enable per-layer fused shared expert selection - #61457
Conversation
Signed-off-by: Felix Marty <Felix.Marty@amd.com>
Signed-off-by: Felix Marty <Felix.Marty@amd.com>
Signed-off-by: Felix Marty <Felix.Marty@amd.com>
Signed-off-by: Felix Marty <Felix.Marty@amd.com>
Signed-off-by: Felix Marty <Felix.Marty@amd.com>
Signed-off-by: Felix Marty <Felix.Marty@amd.com>
Signed-off-by: Felix Marty <Felix.Marty@amd.com>
Signed-off-by: Felix Marty <Felix.Marty@amd.com>
|
✅ Triggered Buildkite CI #97265 for commit |
|
This pull request has merge conflicts that must be resolved before it can be |
Conflict: `tests/model_executor/layers/test_fused_shared_expert.py` also gained DeepSeek-V4.1 coverage from vllm-project#63099. Keep this branch's rewritten per-layer assertions (DSv4 mixed-precision skip reason, MTP layer-set expectations) and adopt the reworded `DeepSeek-V4/V4.1 ...` skip reason; vllm-project#63099's two new `test_deepseek_v41_*` tests merge in unchanged.
|
/ci cancel |
|
✅ Requested cancellation of 1 CI build for |
|
/ci run |
|
✅ Triggered Buildkite CI #97276 for commit |
…erride scan
The override scan reads warmup owners' instance dicts via
`hasattr(value, "__dict__")` + `vars(value).get("kernel")`. Proxy objects
synthesise every attribute through `__getattr__`: `llvmlite.binding.ffi.lib`
resolves `__dict__` to a `_lib_fn_wrapper`, so `hasattr` is True while `vars()`
returns a non-dict and `.get` raises
AttributeError: _lib_fn_wrapper object has no attribute get
That breaks `tests/model_executor/test_triton_dispatcher.py` (5 tests) whenever
llvmlite is imported, as it is in the ROCm and NVIDIA CI images.
Only use a real dict as the instance namespace, and treat `vars()` raising
TypeError (slots-only objects) as "no instance attributes".
Signed-off-by: Felix Marty <Felix.Marty@amd.com>
|
Note: updating if hasattr(value, "__dict__"):
wrapper = lookup(vars(value).get("kernel"))was failing with |
|
/ci run |
|
✅ Triggered Buildkite CI #97316 for commit |
|
This pull request has merge conflicts that must be resolved before it can be |
Signed-off-by: Felix Marty <Felix.Marty@amd.com> # Conflicts: # vllm/models/minimax_m3/amd/model.py # vllm/triton_utils/dispatcher.py
…on path vllm-project#63637 uses envs.VLLM_BATCH_INVARIANT in the MiniMax M3 AMD model, while this branch removed the import when FSE stopped reading the env flag directly. Signed-off-by: Felix Marty <Felix.Marty@amd.com>
|
Merged Conflicts:
Breakage git didn't flag, fixed in 01c144f: this branch removed Other main commits: the other four that touch PR files (#63155, #63999, #63996, #62318) don't interact with FSE. No new code in main calls the APIs the PR removed or renamed. Tests rerun (MI350, after rebuilding the C++ extensions)python -m pytest tests/model_executor/layers/test_fused_shared_expert.py
# 168 passed, 3 skipped, 14 warnings in 114.20s (0:01:54)
python -m pytest tests/quantization/test_online_shared_expert_fusion.py
# 18 passed, 14 warnings in 42.01s
python -m pytest tests/model_executor/test_triton_dispatcher.py
# 12 passed, 1 skipped, 25 warnings in 4.46s
python -m pytest tests/models/test_deepseek_v4_aiter_mega_moe.py
# 16 passed, 14 warnings in 10.03s
python -m pytest tests/models/test_deepseek_v4_vl_rocm.py
# 14 passed, 14 warnings in 21.21s
python -m pytest tests/kernels/moe/test_fp8_tp_loading.py
# 24 passed, 6 skipped, 14 warnings in 6.05s
python -m pytest tests/model_executor/model_loader/test_reload.py
# partial: 77 passed, 0 failed (up to 77%); the DeepSeek-V3 cases did not complete
# because the HF download of inference-optimization/DeepSeek-V3-debug-multiply stalled |
|
/ci run |
|
✅ Triggered Buildkite CI #97980 for commit ℹ️ This PR is 3 commits behind upstream |
Resolve conflicts with vllm-project#62407 (_in_own_storage) and vllm-project#61457 (kernel_parameters import); both sides kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Aaron Hao <aaronhao@inferact.ai>
Breaking: AITER FSE with MiniMax M3 and DeepSeek V4 now requires
VLLM_ROCM_USE_AITER=1, like all other models. Updating the recipe https://recipes.vllm.ai/MiniMaxAI/MiniMax-M3 for that. AITER will be enabled by default with: #62092Disclosure
AI assistance was used. The changes were reviewed and tested manually.
Overview
Allow compatible layers to use AITER fused shared experts (FSE) while other layers retain higher-precision shared experts.
Current vLLM rejects the entire model in case FSE can't be enabled for all layers, and various places assume all-or-nothing selection.
Motivation: certain layers are less sensitive to MXFP4 shared expert quantization than others, and fusion can be enabled for them.
Claims
resolve_layer_fused_shared_expertAPI for models migrated to layer-scoped checkpoint loading.nixl_epall2all backend, which assume uniform expert geometry across layers.disable_under_ep) instead of model-type lookups.vllm servelogs the MoE layers that use FSE, those that keep a separate shared expert, and one line per distinct skip reason (deduplicated, not one per layer).Validation
Unit tests
Run on MI350:
main:105 passed, 14 warnings in 62.09s (0:01:02)this PR:166 passed, 3 skipped, 14 warnings in 76.12s (0:01:16)Test changes in
test_fused_shared_expert.py:test_models_fse_initnow covers 13 architectures (including MiniMax M3, 2-layer DeepSeek V4 and Qwen4 Exp), both MXFP4-only and mixed MXFP4/MXFP8 shared experts, MTP loading, and the EPLB / expert-parallelism /nixl_epmixed-FSE rejections.test_models_fse_initalso runs off ROCm: where AITER is unavailable nothing fuses, so it asserts the unfused outcome (no appended shared-expert slot, weights kept in the separate shared expert, no skip-reason warning) instead of skipping.ModuleListvs absoluteModuleDictindex spaces, the expert-parallelism andnixl_eprejection cases, and the DeepSeek V4 mapper fallback (mixed / all-excluded,.scalenames).test_models_fse_initnow usessingle_rank_parallel_stateinstead ofdist_init(~60% faster for that test), with shared fake-model, config, and MTP builders to avoid duplication.End-to-end
Offline quantization
End-to-end mixed-precision checkpoint, using the Quark
quantize_quark.pyexample script to quantizedeepseek-ai/DeepSeek-V2-Liteto MXFP4 while keeping the shared experts of layers 5, 6 and 7 in BF16 (published asfxmarty-amd/DeepSeek-V2-Lite-mixed-shared-expert-mxfp4-testing):python quantize_quark.py \ --model_dir <DeepSeek-V2-Lite> \ --quant_scheme mxfp4 \ --exclude_layers "lm_head" "*self_attn*" "*mlp.gate" \ "*layers.5.mlp.shared_experts*" \ "*layers.6.mlp.shared_experts*" \ "*layers.7.mlp.shared_experts*" \ --model_export hf_format --data_type bfloat16 \ --num_calib_data 8 --seq_len 128 --skip_evaluation \ --output_dir <mixed-mxfp4-checkpoint>(
--exclude_layersreplaces the template defaults, hence the repeateddeepseek_v2entries; DeepSeek-V2-Lite's legacy remote code needs a small transformers-5 shim foris_torch_fx_availableand_tied_weights_keysto run the example script.)VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS=1 vllm serve fxmarty-amd/DeepSeek-V2-Lite-mixed-shared-expert-mxfp4-testingon MI350 then reports the resolved per-layer policy:WARNING ... VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS is enabled but cannot be enabled - skipping shared-expert fusion: Quark excludes shared experts at <shared_expert>.INFO ... AITER fused shared experts: enabled on MoE layer(s) [1, 2, 3, 4, 8, ..., 26]; disabled on MoE layer(s) [5, 6, 7].Online shared experts quantization
amd/Qwen3.5-35B-A3B-MXFP4keeps the shared experts in BF16 while the routed experts are MXFP4. Targeting a few layers' shared experts tomxfp4online should fuse only those layers. This is the exact server command that produced the results below (mixed FSE, layers 10 to 20):HIP_VISIBLE_DEVICES=2,3 \ VLLM_ROCM_USE_AITER=1 VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS=1 \ vllm serve amd/Qwen3.5-35B-A3B-MXFP4 \ --served-model-name amd/Qwen3.5-35B-A3B-MXFP4 \ --port 8101 \ --gpu-memory-utilization 0.5 \ --tensor-parallel-size 2 \ --quantization-config '{"targets":{"re:.*layers\\.(1[0-9]|20)\\.mlp\\.shared_expert\\..*":"mxfp4"}}'Captured on MI350 with
HIP_VISIBLE_DEVICES=2,3and TP=2 (both TP workers report the same policy):(Worker_TP1 pid=4846) INFO 10-21 19:31:58 [base_loader.py:151] Quantizing 22 layers of types: mlp.shared_expert.down_proj: 11 (from targets: re:.*layers\.(1[0-9]|20)\.mlp\.shared_expert\..*, mxfp4); mlp.shared_expert.gate_up_proj: 11 (from targets: re:.*layers\.(1[0-9]|20)\.mlp\.shared_expert\..*, mxfp4)(Worker_TP1 pid=4846) INFO 10-21 19:31:58 [utils.py:563] AITER fused shared experts: enabled on MoE layer(s) [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; disabled on MoE layer(s) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39].(Worker_TP0 pid=4834) WARNING 10-21 19:31:58 [utils.py:156] VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS is enabled but cannot be enabled - skipping shared-expert fusion: Quark excludes shared experts at <shared_expert>.(Worker_TP0 pid=4834) INFO 10-21 19:31:58 [utils.py:563] AITER fused shared experts: enabled on MoE layer(s) [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; disabled on MoE layer(s) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39].This confirms that the online
mxfp4targets make only the targeted layers FSE-compatible; every other layer keeps its BF16 shared expert, and the single skip reason is reported once instead of per layer.Accuracy: all versus some shared experts quantized
gsm8k_platinum(1209 samples, 5 repeats) on MI350, and evaluated with a patchedlm-evaluation-harness(https://github.com/fxmarty-amd/lm-evaluation-harness/tree/lm-eval-fixes at30563cd).mxfp4targets on shared experts.*layers\.(1[0-9]|20)\.mlp\.shared_expert\..*.*\.mlp\.shared_expert\..*The all-layers case logs no skip reason at all, as expected:
INFO ... AITER fused shared experts: enabled on MoE layer(s) [0, 1, ..., 39]; disabled on MoE layer(s) none.Quantizing all shared experts is detrimental to generation length (~9% longer completions: median 3327 → 3767, p90 5820 → 6112), while the mixed per-layer policy preserves it (median 3327 → 3385, p90 5820 → 5789).
Details
Left to other PRs
vllm/vllm/models/deepseek_v4/amd/model.py
Lines 212 to 240 in 12b005e
vllm/vllm/models/deepseek_v4/amd/model.py
Lines 1077 to 1092 in 12b005e
fused_shared_expert_layers. They build MoE blocks through the per-layer resolver but still load as if unfused; construction now raisesNotImplementedErrorif any MTP/V4.1 layer resolves fused, until this follow-up lands.Limitations
nixl_ep, and Mori remains unsupported with FSE requested.Compatibility changes
main, migrated models accept mixed fusion eligibility with EPLB and expert parallelism off, retaining incompatible shared experts separately at their configured precision.maybe_fuse_shared_experts()requires afused_shared_expert_layerscollection instead of the optionalenabledboolean/environment fallback; migrated model-wide FSE booleans become layer sets;FusedMoEConfig.aiter_fmoe_shared_expert_enabledbecomesis_fused_shared_expert_requested.is_model_fused_shared_expert_compatible()is removed;get_fused_shared_expert_layers()collects the fused layer indices and rejects mixed FSE with EPLB, expert parallelism, ornixl_ep.resolve_layer_fused_shared_expert()takes an explicitdisable_under_epflag (set by MiniMax M3 and DeepSeek V4) instead of branching on model type. DeepSeek V4 performs its owndata_parallel_size > 1fail-fast inDeepseekV4MoE.__init__. MiniMax M3 now enables FSE through the resolver (AITER-based) instead of the rawVLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTSflag.is_fused_shared_expert_enabled = Falsescaffolding; only the removedis_model_fused_shared_expert_compatible()call is replaced with that constant, so the broader dead-FSE cleanup can be split into a separate PR.Related work