[DeepSeek-V4] Add mhc_fused_post_pre kernel - #25976
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces kernel fusion for the multi-head latent attention (mHC) post-mapping and pre-norm GEMM stages in DeepSeek-V4 models. It adds a new TileLang JIT kernel optimized for small token batches and a unified wrapper, mhc_fused_post_pre, which supports both within-layer and cross-layer fusion. The review feedback highlights several code quality improvements, including the removal of unused variables, consolidation of redundant local imports, and the clarification of magic numbers in the model implementation.
a9f629e to
a7c2a80
Compare
3cb8300 to
a9f9e28
Compare
a9f9e28 to
2c52dfd
Compare
846c8b6 to
2abba97
Compare
| monkeypatch.setattr(mhc, "is_dsa_prefill_cp_round_robin_split", lambda: False) | ||
| torch.manual_seed(0) | ||
| device = torch.device("cuda") | ||
| hc_mult = 2 |
There was a problem hiding this comment.
Could we change this to hc_mult=4 to match 4's actual config?
| from sglang.srt.layers.mhc import mhc_fused_post_pre, mhc_post, mhc_pre | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("num_tokens", [0, 1, 8, 17, 32]) |
There was a problem hiding this comment.
Can we add a batch size of 64 to test the correctness of the original (unfused large-batch) path?
| hc_mult, | ||
| hidden_size, | ||
| hc_mult3, | ||
| tile_n=tile_n, |
There was a problem hiding this comment.
It looks like mhc_fused_post_pre_fma_tilelang doesn’t have the parameter tile_n?
There was a problem hiding this comment.
You’re right! tile_n should be passed to tile_mix_outputs, I missed that during the refactor.
Thanks for catching it, fixed now.
5beed4e to
a85f425
Compare
| torch.manual_seed(0) | ||
| device = torch.device("cuda") | ||
| hc_mult = 4 | ||
| hidden_size = 4096 |
There was a problem hiding this comment.
can we parameterize hidden_size and add 7168 (v4pro)?
| tilelang.PassConfigKey.TL_PTXAS_REGISTER_USAGE_LEVEL: 10, | ||
| }, | ||
| ) | ||
| def mhc_fused_post_pre_fma_tilelang( |
There was a problem hiding this comment.
Can we add warmup for the fused kernel in prewarm_mhc_token_counts?
|
/tag-and-rerun-ci |
6c8bb20 to
053d52a
Compare
053d52a to
48c47c5
Compare
Co-authored-by: Qichao Li <liqichao@baidu.com>
Co-authored-by: Qichao Li <liqichao@baidu.com>
The one-shot MHC prenorm prewarm (sgl-project#27986) triggers lazily inside the first forward that carries tokens, per rank, uncoordinated. On wide-EP disagg prefill (GB200 1p1d dep8) that is fatal: idle EP peers still launch deep_gemm.fp8_fp4_mega_moe every layer and wait for the compiling rank inside its NVLink barrier, which device-traps after 180 s ("DeepGEMM NVLink barrier timeout ... signal=7, target=8") and poisons the context (CUDA error 719 on later launches). Measured on GB200: the 23-bucket burst is 159-166 s on a cold JIT cache, so any >15 s trigger stagger between ranks blows the barrier budget. Fix: trigger the same prewarm from the tail of load_weights on every rank — before the memory pool, CUDA-graph capture, and any forward — then barrier on the TP group so no rank proceeds while a peer still compiles, and empty_cache so the transient prewarm buffers don't skew KV-pool sizing. The in-forward trigger stays as a fallback for entry points that bypass load_weights. Also drop the orphaned prewarm_mhc_token_counts/_buckets pair (added by sgl-project#25810, removed by sgl-project#26238, resurrected without a caller by the sgl-project#25976 merge).
The one-shot MHC prenorm prewarm (sgl-project#27986) triggers lazily inside the first forward that carries tokens, per rank, uncoordinated. On wide-EP disagg prefill (GB200 1p1d dep8) that is fatal: idle EP peers still launch deep_gemm.fp8_fp4_mega_moe every layer and wait for the compiling rank inside its NVLink barrier, which device-traps after 180 s ("DeepGEMM NVLink barrier timeout ... signal=7, target=8") and poisons the context (CUDA error 719 on later launches). Measured on GB200: the 23-bucket burst is 159-166 s on a cold JIT cache, so any >15 s trigger stagger between ranks blows the barrier budget. Fix: trigger the same prewarm from the tail of load_weights on every rank — before the memory pool, CUDA-graph capture, and any forward — then barrier on the TP group so no rank proceeds while a peer still compiles, and empty_cache so the transient prewarm buffers don't skew KV-pool sizing. The in-forward trigger stays as a fallback for entry points that bypass load_weights. Also drop the orphaned prewarm_mhc_token_counts/_buckets pair (added by sgl-project#25810, removed by sgl-project#26238, resurrected without a caller by the sgl-project#25976 merge).
The one-shot MHC prenorm prewarm (sgl-project#27986) triggers lazily inside the first forward that carries tokens, per rank, uncoordinated. On wide-EP disagg prefill (GB200 1p1d dep8) that is fatal: idle EP peers still launch deep_gemm.fp8_fp4_mega_moe every layer and wait for the compiling rank inside its NVLink barrier, which device-traps after 180 s ("DeepGEMM NVLink barrier timeout ... signal=7, target=8") and poisons the context (CUDA error 719 on later launches). Measured on GB200: the 23-bucket burst is 159-166 s on a cold JIT cache, so any >15 s trigger stagger between ranks blows the barrier budget. Fix: drive prewarm_mhc_pre() from the tail of load_weights on every rank — before the memory pool, CUDA-graph capture, and any forward — then barrier on the TP group so no rank proceeds while a peer still compiles, and empty_cache so the transient prewarm buffers don't skew KV-pool sizing. With the trigger out of the forward path, the in-forward gate and the mhc_pre/_mhc_pre_impl split (which existed only so the prewarm replay would not re-enter mhc_pre) lose their reason to exist: remove them and make mhc_pre the single implementation again. Also drop the orphaned prewarm_mhc_token_counts/_buckets pair (added by sgl-project#25810, removed by sgl-project#26238, resurrected without a caller by the sgl-project#25976 merge).
The one-shot MHC prenorm prewarm (sgl-project#27986) triggers lazily inside the first forward that carries tokens, per rank, uncoordinated. On wide-EP disagg prefill (GB200 1p1d dep8) that is fatal: idle EP peers still launch deep_gemm.fp8_fp4_mega_moe every layer and wait for the compiling rank inside its NVLink barrier, which device-traps after 180 s ("DeepGEMM NVLink barrier timeout ... signal=7, target=8") and poisons the context (CUDA error 719 on later launches). Measured on GB200: the 23-bucket burst is 159-166 s on a cold JIT cache, so any >15 s trigger stagger between ranks blows the barrier budget. Fix: drive prewarm_mhc_pre() from the tail of load_weights on every rank — before the memory pool, CUDA-graph capture, and any forward — then barrier on the TP group so no rank proceeds while a peer still compiles, and empty_cache so the transient prewarm buffers don't skew KV-pool sizing. With the trigger out of the forward path, the in-forward gate and the mhc_pre/_mhc_pre_impl split (which existed only so the prewarm replay would not re-enter mhc_pre) lose their reason to exist: remove them and make mhc_pre the single implementation again. Also drop the orphaned prewarm_mhc_token_counts/_buckets pair (added by sgl-project#25810, removed by sgl-project#26238, resurrected without a caller by the sgl-project#25976 merge).
The one-shot MHC prenorm prewarm (sgl-project#27986) triggers lazily inside the first forward that carries tokens, per rank, uncoordinated. On wide-EP disagg prefill (GB200 1p1d dep8) that is fatal: idle EP peers still launch deep_gemm.fp8_fp4_mega_moe every layer and wait for the compiling rank inside its NVLink barrier, which device-traps after 180 s ("DeepGEMM NVLink barrier timeout ... signal=7, target=8") and poisons the context (CUDA error 719 on later launches). Measured on GB200: the 23-bucket burst is 159-166 s on a cold JIT cache, so any >15 s trigger stagger between ranks blows the barrier budget. Fix: drive prewarm_mhc_pre() from the tail of load_weights on every rank — before the memory pool, CUDA-graph capture, and any forward — then barrier on the TP group so no rank proceeds while a peer still compiles, and empty_cache so the transient prewarm buffers don't skew KV-pool sizing. With the trigger out of the forward path, the in-forward gate and the mhc_pre/_mhc_pre_impl split (which existed only so the prewarm replay would not re-enter mhc_pre) lose their reason to exist: remove them and make mhc_pre the single implementation again. Also drop the orphaned prewarm_mhc_token_counts/_buckets pair (added by sgl-project#25810, removed by sgl-project#26238, resurrected without a caller by the sgl-project#25976 merge).
Co-authored-by: Qichao Li <liqichao@baidu.com>
Co-authored-by: Qichao Li <liqichao@baidu.com>
[DeepSeek-V4] Add fused mHC post/pre kernel
Motivation
This PR optimizes the DeepSeek-V4 mHC path by fusing the boundary between one mHC post step and the next mHC pre step.
The approach is adapted from TRTLLM. The main idea is to avoid launching separate kernels for the latency-sensitive decode path:
The fused path keeps the existing numerically sensitive
mhc_pre_big_fuse(_with_norm)finalization stage, but removes the separate post/pre scheduling boundary.For small token batches, a TileLang scalar-FMA kernel computes the following work in one launch:
For larger token batches, the implementation keeps the existing high-throughput TileLang
mhc_post+ DeepGEMM decomposition.The optimization is guarded by a new env var:
It intentionally depends on the existing TileLang mHC switches:
because the fused implementation directly reuses the TileLang mHC tensor layouts and the
mhc_pre_big_fuse(_with_norm)finalization kernels.Performance Test
total_throughput increased by 3.35%
Performance summary
Accuracy / Correctness Tests
GSM8K score: 0.975
Modifications
python/sglang/srt/environ.pyAdd:
python/sglang/srt/layers/mhc.pymhc_fused_post_pre_fma_tilelang, a small-token fused TileLang kernel.mhc_fused_post_pre, which dispatches:mhc_post+ DeepGEMM path.mhc_pre_big_fuse(_with_norm)finalization stage.python/sglang/srt/models/deepseek_v4.py__init__.hc_post+ next attentionhc_pre;hc_post+ FFNhc_pre.hc_postand close it at model tail.python/sglang/srt/models/deepseek_v4_nextn.pytests/kernels/test_mhc_kernels.pymhc_fused_post_prevs the unfused reference sequence.Checklist
SGLANG_OPT_FUSE_MHC_POST_PRE.bench_servingperformance improvement on DeepSeek-V4-Flash.CI States
Latest PR Test (Base): ❌ Run #26549213709
Latest PR Test (Extra): ❌ Run #26549213622