Skip to content

[NPU] Enable MXFP8 low-latency DeepEP dispatch for Fp8MoEMethod FP4 experts - #40519

Open
iridiumine wants to merge 3 commits into
sgl-project:mainfrom
iridiumine:pr-npu-fp4-mxfp8-dispatch
Open

iridiumine wants to merge 3 commits into
sgl-project:mainfrom
iridiumine:pr-npu-fp4-mxfp8-dispatch

Conversation

@iridiumine

@iridiumine iridiumine commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

On NPU, FP4-expert models routed through Fp8MoEMethod ( is_fp4_expert=True , e.g. MiMo-V2.5-Pro with modelopt_fp4) delegate execution to NPUW4A8MXFP4MoEMethod kernels via the ASCEND MoE runner. However, process_weights_after_loading only runs Fp8MoEMethod 's own body, so the DeepEP dispatcher quant config was never set — dispatch fell back to BF16 and a separate DynamicMxQuant kernel ran before every grouped matmul (~15ms/layer at 16K-token prefill).

The native FP4 path ( NPUW4A8MXFP4FusedMoEMethod , DSV4) already sets the same dispatcher config; the Fp8MoEMethod path was simply missing it.

Modifications

  • Declare the dispatch wire format as a DISPATCHER_QUANT_CONFIG class attribute on NPUW4A8MXFP4MoEMethod ( low_latency=mxfp8 ; normal stays BF16 since A5 MXFP8 normal dispatch is intranode-only). The kernel that consumes the activations now owns the declaration, instead of each caller hand-writing platform-specific if/else patches.
  • In Fp8MoEMethod.process_weights_after_loading , pick the config up from the attached layer.w13_kernel when it declares one; otherwise keep the original checkpoint-dtype config. No _is_npu / is_fp4_expert branching — the attached kernel already encodes both.
  • Reuse the same declaration in NPUW4A8MXFP4FusedMoEMethod , replacing its hand-written copy.
    With this, A5 low-latency dispatch quantizes activations to MXFP8 in-kernel (with E8M0 block scales) and the GMMs consume them directly; the three previously duplicated config dicts collapse into a single kernel-owned declaration.

Accuracy Tests

echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
sysctl -w vm.swappiness=0
sysctl -w kernel.numa_balancing=0
sysctl -w kernel.sched_migration_cost_ns=50000
export SGLANG_SET_CPU_AFFINITY=1

source /usr/local/Ascend/ascend-toolkit/set_env.sh
source /usr/local/Ascend/nnal/atb/set_env.sh

export HCCL_BUFFSIZE=300
export HCCL_OP_EXPANSION_MODE=AIV
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export STREAMS_PER_DEVICE=32
export SGLANG_DISAGGREGATION_BOOTSTRAP_TIMEOUT=600
export SGLANG_ENABLE_SPEC_V2=1
export SGLANG_ENABLE_OVERLAP_PLAN_STREAM=1
export ASCEND_USE_FIA=1
export SGLANG_DEEPEP_NUM_MAX_DISPATCH_TOKENS_PER_RANK=32
export DEEPEP_HCCL_BUFFSIZE=2500
export DEEPEP_NORMAL_LONG_SEQ_ROUND=20
export DEEPEP_NORMAL_LONG_SEQ_PER_ROUND_TOKENS=4096
export HCCL_SOCKET_IFNAME=lo
export GLOO_SOCKET_IFNAME=lo
export HCCL_HOST_SOCKET_PORT_RANGE=auto
export SGLANG_ALLOW_OVERWRITE_LONGER_CONTEXT_LEN=1

sglang serve \
        --model-path $MODEL_PATH \
        --served-model-name $MODEL_PATH \
        --trust-remote-code \
        --attention-backend ascend \
        --device npu \
        --mem-fraction-static 0.92 \
        --tp-size 8 --nnodes 1 --node-rank 0 \
        --chunked-prefill-size 8192 --max-total-tokens 600000 \
        --max-running-requests 32 \
        --moe-a2a-backend deepep --deepep-mode auto \
        --cuda-graph-bs-decode 1 2 4 8 16 \
        --speculative-algorithm DFLASH \
        --speculative-draft-model-path $DFLASH_PATH \
        --speculative-num-draft-tokens 8 \
        --dp-size 2 --enable-dp-attention --enable-dp-lm-head \
 DeprecationWarning: sglang.test.few_shot_gsm8k is deprecated. Use sglang.test.run_eval with eval_name='gsm8k' instead.
  metrics = run_eval(args)
100%|█████████████████████████████████████████████████████████████████████████████████| 200/200 [00:37<00:00,  5.28it/s]
Accuracy: 0.970
Invalid: 0.010
Latency: 38.289 s
Output throughput: 604.323 token/s
metrics={'accuracy': np.float64(0.97), 'invalid': np.float64(0.01), 'latency': 38.289156210026704, 'output_throughput': 604.3225364663596}
metrics['accuracy']=np.float64(0.97)

Speed Tests and Profiling

MiMo-V2.5-Pro-FP4 (DFlash), single-node A5, DeepEP low-latency, prefill with 16K-token random inputs.

Per-layer kernel profile (avg ms, before → after):

Kernel Before After
DynamicMxQuant (post-dispatch) 15.14 eliminated
MoeLowLatencyDispatchV2 33.16 30.63
GroupedMatmulSwigluQuantV2 30.37 27.60
Layer total 238.6 222.7 (-6.7%)

End-to-end (bench_serving, 128 requests, rate=1.0, saturated queueing):

Metric Before After
Mean TTFT (ms) 11633 9095 (-21.8%)
P99 TTFT (ms) 18856 16405

Checklist

Review and Merge Process

  1. Ping Merge Oncalls to start the process. See the PR Merge Process.
  2. Get approvals from CODEOWNERS and other reviewers.
  3. Trigger CI tests with comments or contact authorized users to do so.
    • Common commands include /tag-and-rerun-ci, /tag-run-ci-label, /rerun-failed-ci
  4. After green CI and required approvals, ask Merge Oncalls or people with Write permission to merge the PR.

CI States

Latest PR Test (Base): ❌ Run #37717189731
Latest PR Test (Extra): ❌ Run #37717189408
Latest PR Test (AMD ROCm 10): ❌ Run #37717189609

Comment thread python/sglang/srt/layers/quantization/fp8.py Outdated
Fp8MoEMethod with is_fp4_expert on NPU delegates execution to
NPUW4A8MXFP4MoEMethod kernels but never configured the DeepEP
dispatcher quant dtype, leaving dispatch in BF16 and forcing a
separate DynamicMxQuant before each GMM.

Declare the required dispatch wire format as a DISPATCHER_QUANT_CONFIG
class attribute on NPUW4A8MXFP4MoEMethod (low_latency=mxfp8, normal
stays BF16 since A5 MXFP8 normal dispatch is intranode-only), have
Fp8MoEMethod pick it up from the attached w13_kernel instead of
platform if/else patches, and reuse the same declaration in
NPUW4A8MXFP4FusedMoEMethod, replacing its hand-written copy.
@iridiumine
iridiumine force-pushed the pr-npu-fp4-mxfp8-dispatch branch from 3d5920d to 12b643b Compare September 23, 2026 08:41
@sglang-npu-bot

Copy link
Copy Markdown
Collaborator

/tag-and-rerun-ci

@github-actions github-actions Bot added the run-ci CI: run the baseline test suite on this PR label Sep 23, 2026
@iridiumine

Copy link
Copy Markdown
Contributor Author

/rerun-failed-ci

3 similar comments
@iridiumine

Copy link
Copy Markdown
Contributor Author

/rerun-failed-ci

@iridiumine

Copy link
Copy Markdown
Contributor Author

/rerun-failed-ci

@iridiumine

Copy link
Copy Markdown
Contributor Author

/rerun-failed-ci

@iridiumine

Copy link
Copy Markdown
Contributor Author

/rerun-failed-ci

Comment on lines 172 to 175
if hasattr(layer, "dispatcher"):
layer.dispatcher.set_quant_config(
{
"normal_dispatcher_output_dtype": "bf16",
"low_latency_dispatcher_output_dtype": "mxfp8",
}
dict(self.w13_kernel.DISPATCHER_QUANT_CONFIG)
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! My PR include #26408 an alternative solution that makes it possible to automatically determine the type of output dispatcher (fixing a long‑broken strategy), and it is implemented for both normal and low‑latency modes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d also prefer a single interface for interacting with the dispatcher type, for example the change introduced in PR #39589 has now broken automatic detection of the dispatcher output dtype in the TP (tensor parallelism) case.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-ci CI: run the baseline test suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants