Skip to content

[Perf] DFLASH: route draft bf16 GEMMs through tuned dispatch on GB300 - #45876

Merged
Fridge003 merged 3 commits into
sgl-project:mainfrom
wookjeHan:wj/dflash-draft-tuned-bf16-gemm
Oct 31, 2026
Merged

Fridge003 merged 3 commits into
sgl-project:mainfrom
wookjeHan:wj/dflash-draft-tuned-bf16-gemm

Conversation

@wookjeHan

@wookjeHan wookjeHan commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

At low concurrency the DFLASH draft model runs its bf16 linears at small M: one block of num_draft_tokens tokens per request.
Two of its paths bypass SGLang's tuned bf16 GEMM dispatch and always use cuBLAS:

  • DFlashGroupedConv.kernel_projection is a plain nn.Linear;
  • the candidate head in _project_candidate_logits calls torch.matmul directly.

Most of the draft's other GEMM shapes have no GB300 tuned entry and fall back to cuBLAS, which is well below the memory bandwidth
limit at these M sizes. The exception is TP4 qkv at M = 8 and 16, which already used the split-K tactics of the "GB300 TP16" block.

Modifications

  • models/dflash.py
    • kernel_projection becomes ReplicatedLinear, so it goes through UnquantizedLinearMethod.apply and the tuned dispatch.
    • The candidate head calls bf16_gemm_dispatch when the backend is cutedsl/gemv and the weight is bf16 on CUDA; otherwise it
      keeps torch.matmul.
  • layers/quantization/unquant.py: GB300 split-K / warp split-K entries at M <= 32, TP4 and TP8.
  • kernels/ops/gemm/cutedsl_bf16_gemm.py: TGV M ranges for three shapes at M = 17..32.

Each entry was kept only if it beats the current kernel in all three timings of the micro-benchmark below.

The tables are keyed by (M, N, K) only, so the entries are not draft-specific:

  • The qkv / gate_up / down shapes (hidden 8192, intermediate 28672, 64 q / 8 kv heads) are also those of Llama-3-70B-class dense
    models at TP4 and TP8. With the default auto backend on SM10x those models switch kernels at M <= 32 as well. Only GB300 was
    measured; GB200 was not.
  • For TP4 qkv at M = 8 and 16, the new warp split-K entries replace the existing GB300 TP16 split-K tactics (warp split-K takes
    precedence). They are faster in all three timings; see the micro-benchmark.
  • precompile_splitk_tactics() (only Qwen4-Exp calls it) compiles every table entry, so it now builds 19 more tactics at startup.

Accuracy Tests

Model GSM8K (x1, 65K max tokens) AIME26 pass@1 (30 x 16, 131K) AIME26 pass@16
Muse Spark Open (NVFP4, TP4, DFLASH) + this PR 97.50% 95.83% ± 3.33% (std over 16 runs; SEM 0.83%) 100%

Speed Tests and Profiling

Micro-benchmark (GB300, CUDA graphs, cold L2; us per call, after-PDL timing)

GEMM (N, K) M main this PR
kernel_projection (2048, 8192) 8 9.66 (cuBLAS) 5.95 (-38%)
TP4 gate_up (14336, 8192) 16 42.28 (cuBLAS) 35.59 (-16%)
TP4 qkv (2560, 8192) 8 8.70 (split-K) 7.41 (warp split-K, -15%)
TP4 qkv (2560, 8192) 16 9.04 (split-K) 8.12 (warp split-K, -10%)
TP4 candidate head (50512, 8192) 7 136.21 (cuBLAS) 113.74 (-16%)
TP8 qkv (1280, 8192) 8 9.82 (cuBLAS) 4.82 (-51%)
TP8 candidate head (25256, 8192) 7 67.35 (cuBLAS) 60.64 (-10%)

End-to-end (Muse Spark Open NVFP4, GB300, DFLASH 8 drafts, FlashInfer autotune off on both sides)

Mean decode cycle (target verify + draft step) from nsys, main -> this PR; FlashInfer autotune off on both sides.

c=1 c=2 c=4
TP4 9354.9 -> 9252.9 us (-1.09%) 10946.2 -> 10862.4 us (-0.77%) 13219.6 -> 13048.4 us (-1.30%*)
TP8 7573.1 -> 7465.7 us (-1.42%) 8495.7 -> 8390.4 us (-1.24%) 9820.0 -> 9769.0 us (-0.52%)

*Includes about ±1% MoE routing variation between runs; the GEMMs this PR changes account for -52.6 us/cycle (-0.40%).

No change at c >= 5 (draft GEMM M > 32, no new entries).

Decode throughput scales with 1 / cycle, so the gain is about +1.1% (TP4) and +1.4% (TP8) at c=1.

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 #40499863108
Latest PR Test (Extra): ❌ Run #40499863678
Latest PR Test (AMD ROCm 10): ❌ Run #40499862483

@nvpohanh nvpohanh left a comment

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.

[by Claude Code] This review is done by agents. Please feel free to disagree if they do not make sense.

[by Claude Code] Severity: perf | Confidence: Medium

Issue

The accuracy table cannot show whether the new GEMM entries are correct. In the Muse Spark Open run, these shapes run only in the DFLASH draft, and target verification corrects draft errors. A wrong tactic would leave GSM8K and AIME26 unchanged and only lower the acceptance length.

Why it matters

The end-to-end table measures time per decode cycle, so "throughput scales with 1 / cycle" assumes the acceptance length did not change. The same (M, N, K) keys also run in the target forward of Llama-3-70B-class BF16 models at TP4/TP8, where a wrong result changes outputs.

Fix

Add the mean acceptance length for main and this PR from the same GB300 TP4/TP8 runs.

@nvpohanh

Copy link
Copy Markdown
Collaborator

/rerun-test test/registered/e2e/models/test_muse_spark_12_b300_pd.py test/registered/cp/test_glm53_flash_cp.py test/registered/e2e/models/test_muse_spark_unified_memory.py test/registered/e2e/models/test_glm53_flash_b200.py test/registered/unit/spec/test_dflash_logits.py test/registered/e2e/models/test_nvidia_nemotron_3_nano.py test/registered/core/test_basic_sanity_dflash.py test/registered/spec/dflash/test_dflash.py test/registered/spec/test_spec_mixed_chunk.py test/registered/e2e/speculative/test_dflash_domino.py test/registered/spec/test_gemma4_dflash_31b_extra.py test/registered/spec/dflash/test_muse_glimmer_dflash_assistant_gsm8k.py test/registered/unit/spec/test_dflash_domino.py test/registered/kernels/ops/speculative/test_dflash_domino.py test/registered/unit/spec/test_dflash_extra_buffer_lazy.py test/registered/unit/spec/test_dflash_overlap_hostsync.py test/registered/unit/spec/test_oot_dflash_hooks.py test/registered/spec/dspark/test_dspark_stacked_ctx_kv_parity.py test/registered/unit/layers/test_sharded_loader_parallel_groups.py test/registered/unit/mem_cache/test_prefix_valid_fp8_pool.py test/registered/unit/model_executor/runner_utils/test_graph_pool_borrow.py test/registered/unit/model_executor/model_runner_components/test_spec_aux_hidden_state.py test/registered/unit/model_executor/test_spec_aux_hidden_state_resolver.py test/registered/unit/layer_boundary/test_glm5_next_dflash_capture.py test/registered/unit/mem_cache/test_kv_cache_configurator_draft_gate.py test/registered/unit/mem_cache/test_kv_index_translator_draft_disposition.py test/registered/unit/mem_cache/test_fused_draft_placement.py test/registered/unit/model_executor/runner/test_prefill_shared_read_done.py test/registered/unit/spec/test_weight_update_runners.py test/registered/unit/spec/test_block_verification_args.py test/registered/kernels/ops/speculative/test_block_verification.py test/registered/unit/spec/test_decode_bookkeeping_ownership.py test/registered/unit/server_args/test_unified_memory_spec_gate.py test/registered/scheduler/test_min_free_slots_delayer.py test/registered/unit/npu/graph_runner/test_npu_graph_runner.py test/registered/unit/layers/muse_spark/test_muse_spark_runner_dispatch.py test/registered/kernels/ops/attention/test_muse_spark_qk_norm_rope_store.py test/registered/kernels/ops/communication/test_muse_spark_moe_finalize_all_reduce.py test/registered/kernels/ops/layernorm/test_muse_spark_norm_ops.py test/registered/kernels/ops/layernorm/test_muse_spark_rmsnorm_nvfp4_quant.py test/registered/kernels/ops/moe/test_muse_spark_routing.py test/registered/kernels/ops/moe/test_moe_wna16_marlin.py test/registered/unit/function_call/test_function_call_parser.py test/registered/unit/parser/test_reasoning_parser.py test/registered/unit/entrypoints/openai/test_serving_responses.py test/registered/unit/entrypoints/openai/test_serving_responses_stream.py test/registered/unit/multimodal/test_processor_async_call_sites.py test/registered/attention/unittests/dense/test_fa3.py test/registered/attention/unittests/dense/test_fa4.py test/registered/attention/unittests/dense/test_flashinfer.py test/registered/attention/unittests/dense/test_triton.py test/registered/attention/unittests/gdn/test_flashinfer.py test/registered/attention/unittests/gdn/test_triton.py test/registered/attention/unittests/kda/test_triton.py test/registered/attention/unittests/lightning/test_triton.py test/registered/attention/unittests/mamba/test_mamba2.py test/registered/attention/unittests/mla/test_triton.py test/registered/attention/unittests/swa/test_flashinfer.py test/registered/attention/unittests/swa/test_triton.py

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

🚀 8-gpu-b300 (2 tests): ✅ View workflow run

cd test/ && python3 registered/e2e/models/test_muse_spark_12_b300_pd.py
cd test/ && python3 registered/e2e/models/test_muse_spark_unified_memory.py

🚀 4-gpu-b200 (15 tests): ✅ View workflow run

cd test/ && python3 registered/e2e/models/test_glm53_flash_b200.py
cd test/ && python3 registered/e2e/models/test_nvidia_nemotron_3_nano.py
cd test/ && python3 registered/kernels/ops/communication/test_muse_spark_moe_finalize_all_reduce.py
cd test/ && python3 registered/kernels/ops/layernorm/test_muse_spark_rmsnorm_nvfp4_quant.py
cd test/ && python3 registered/attention/unittests/dense/test_fa4.py
cd test/ && python3 registered/attention/unittests/dense/test_flashinfer.py
cd test/ && python3 registered/attention/unittests/dense/test_triton.py
cd test/ && python3 registered/attention/unittests/gdn/test_flashinfer.py
cd test/ && python3 registered/attention/unittests/gdn/test_triton.py
cd test/ && python3 registered/attention/unittests/kda/test_triton.py
cd test/ && python3 registered/attention/unittests/lightning/test_triton.py
cd test/ && python3 registered/attention/unittests/mamba/test_mamba2.py
cd test/ && python3 registered/attention/unittests/mla/test_triton.py
cd test/ && python3 registered/attention/unittests/swa/test_flashinfer.py
cd test/ && python3 registered/attention/unittests/swa/test_triton.py

🚀 ubuntu-latest (22 tests): ✅ View workflow run

cd test/ && python3 registered/unit/spec/test_dflash_logits.py
cd test/ && python3 registered/unit/spec/test_dflash_domino.py
cd test/ && python3 registered/unit/spec/test_oot_dflash_hooks.py
cd test/ && python3 registered/unit/model_executor/model_runner_components/test_spec_aux_hidden_state.py
cd test/ && python3 registered/unit/model_executor/test_spec_aux_hidden_state_resolver.py
cd test/ && python3 registered/unit/layer_boundary/test_glm5_next_dflash_capture.py
cd test/ && python3 registered/unit/mem_cache/test_kv_cache_configurator_draft_gate.py
cd test/ && python3 registered/unit/mem_cache/test_kv_index_translator_draft_disposition.py
cd test/ && python3 registered/unit/mem_cache/test_fused_draft_placement.py
cd test/ && python3 registered/unit/model_executor/runner/test_prefill_shared_read_done.py
cd test/ && python3 registered/unit/spec/test_weight_update_runners.py
cd test/ && python3 registered/unit/spec/test_block_verification_args.py
cd test/ && python3 registered/unit/spec/test_decode_bookkeeping_ownership.py
cd test/ && python3 registered/unit/server_args/test_unified_memory_spec_gate.py
cd test/ && python3 registered/scheduler/test_min_free_slots_delayer.py
cd test/ && python3 registered/unit/npu/graph_runner/test_npu_graph_runner.py
cd test/ && python3 registered/unit/layers/muse_spark/test_muse_spark_runner_dispatch.py
cd test/ && python3 registered/unit/function_call/test_function_call_parser.py
cd test/ && python3 registered/unit/parser/test_reasoning_parser.py
cd test/ && python3 registered/unit/entrypoints/openai/test_serving_responses.py
cd test/ && python3 registered/unit/entrypoints/openai/test_serving_responses_stream.py
cd test/ && python3 registered/unit/multimodal/test_processor_async_call_sites.py

🚀 1-gpu-5090 (6 tests): ✅ View workflow run

cd test/ && python3 registered/core/test_basic_sanity_dflash.py
cd test/ && python3 registered/spec/dflash/test_dflash.py
cd test/ && python3 registered/unit/spec/test_dflash_extra_buffer_lazy.py
cd test/ && python3 registered/unit/spec/test_dflash_overlap_hostsync.py
cd test/ && python3 registered/spec/dspark/test_dspark_stacked_ctx_kv_parity.py
cd test/ && python3 registered/unit/model_executor/runner_utils/test_graph_pool_borrow.py

🚀 1-gpu-h100 (22 tests): ✅ View workflow run

cd test/ && python3 registered/spec/test_spec_mixed_chunk.py
cd test/ && python3 registered/spec/dflash/test_muse_glimmer_dflash_assistant_gsm8k.py
cd test/ && python3 registered/kernels/ops/speculative/test_dflash_domino.py
cd test/ && python3 registered/unit/layers/test_sharded_loader_parallel_groups.py
cd test/ && python3 registered/unit/mem_cache/test_prefix_valid_fp8_pool.py
cd test/ && python3 registered/kernels/ops/speculative/test_block_verification.py
cd test/ && python3 registered/kernels/ops/attention/test_muse_spark_qk_norm_rope_store.py
cd test/ && python3 registered/kernels/ops/layernorm/test_muse_spark_norm_ops.py
cd test/ && python3 registered/kernels/ops/moe/test_muse_spark_routing.py
cd test/ && python3 registered/kernels/ops/moe/test_moe_wna16_marlin.py
cd test/ && python3 registered/attention/unittests/dense/test_fa3.py
cd test/ && python3 registered/attention/unittests/dense/test_fa4.py
cd test/ && python3 registered/attention/unittests/dense/test_flashinfer.py
cd test/ && python3 registered/attention/unittests/dense/test_triton.py
cd test/ && python3 registered/attention/unittests/gdn/test_flashinfer.py
cd test/ && python3 registered/attention/unittests/gdn/test_triton.py
cd test/ && python3 registered/attention/unittests/kda/test_triton.py
cd test/ && python3 registered/attention/unittests/lightning/test_triton.py
cd test/ && python3 registered/attention/unittests/mamba/test_mamba2.py
cd test/ && python3 registered/attention/unittests/mla/test_triton.py
cd test/ && python3 registered/attention/unittests/swa/test_flashinfer.py
cd test/ && python3 registered/attention/unittests/swa/test_triton.py

🚀 2-gpu-h100 (2 tests): ✅ View workflow run

cd test/ && python3 registered/e2e/speculative/test_dflash_domino.py
cd test/ && python3 registered/spec/test_gemma4_dflash_31b_extra.py

⛔ test/registered/cp/test_glm53_flash_cp.py: File not found: test/registered/cp/test_glm53_flash_cp.py

@Fridge003

Copy link
Copy Markdown
Collaborator

@claude review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline finding, I also checked two things in dflash.py and found no problems. The ReplicatedLinear swap keeps the kernel_projection.weight parameter name and is built without a quant_config, so it stays unquantized. weight[:num_org] slices leading rows of a contiguous weight, so the view passed to bf16_gemm_dispatch is still contiguous.

Extended reasoning...

Findings were reported inline. This note only records the weight-loading and layout checks in dflash.py that were looked at and turned up no problems.

@Fridge003
Fridge003 merged commit b20fdd2 into sgl-project:main Oct 31, 2026
110 of 126 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

jit-kernel quant LLM Quantization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants