Skip to content

[AMD][gfx95] Fill the chunked-prefill compute budget exactly - #32888

Merged
HaiShaw merged 6 commits into
sgl-project:mainfrom
Jacob0226:jacob/gfx95-exact-chunk-fill
Sep 14, 2026
Merged

HaiShaw merged 6 commits into
sgl-project:mainfrom
Jacob0226:jacob/gfx95-exact-chunk-fill

Conversation

@Jacob0226

@Jacob0226 Jacob0226 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

A prefill batch never reaches --chunked-prefill-size: _update_prefill_budget charges the page-ceiled extend length against rem_chunk_tokens and rem_input_tokens, and add_one_req floors the last truncation back to a page. On gfx95 that shortfall is expensive — the aiter MLA absorb bmm picks its EVEN_MN specialization from a compile-time constexpr on M % BLOCK_SIZE_M, so a misaligned M runs a different compiled binary, not the same one with a partial tile.

before after
MLA absorb bmm 311.2 us @ M=16368 155.8 us @ M=16384
instructions 3819 1562
trace token annotation toks=16350 toks=16384

compute_charge bills the compute budgets in raw tokens; the KV budgets stay page-ceiled. Below, the ledger spends 16384 while the forward runs 16328 (page_size=64, 8100-token prompts) — only whole admissions leak.

req admitted runs charged budget left
1 whole 8100 8128 8256
2 whole 8100 8128 128
3 chunked 128 128 0
Accu. tokens 16328 16384

Scope

gfx95 (MI355X) chunked prefill, gated on SGLANG_EXACT_CHUNK_FILL and is_gfx95_supported(). CUDA and other AMD parts keep upstream behaviour on every path, and the dLLM path is excluded. ROCm/aiter#4453 deepens the kernel win but does not gate this PR.

schedule_policy.py is common code, so the CUDA no-op is measured rather than asserted. Replaying 48 admission scenarios (page_size 1/8/64 × chunk 2048/16384 × four prompt mixes, 114 admissions) against upstream/main with the gate off reproduces every budget counter byte-for-byte; forcing it on moves 73 lines, and page_size=1 is identical either way.

Test plan

Baserocm/sgl-dev:v0.5.15.post1-rocm720-mi35x-20260714 plus six AMD PRs (#30519, #30575, #30715, #30808, #31323, #31324). This PR — that base plus this PR and ROCm/aiter#4453; nothing else moves.

Kernel level, same config:

before after
dense GEMM total 163.5 ms 136.6 ms
EXTEND wall 695.4 ms 671.6 ms

End to end, GLM-5.2-MXFP4, MI355X TP4, in8192 / out1024:

Concurrency Mean TTFT base this PR Δ Median TPOT base this PR Δ
4 512.25 484.58 −5.4% 13.04 13.00 −0.3%
8 727.92 681.18 −6.4% 16.41 16.23 −1.1%
16 999.91 930.41 −7.0% 21.91 21.61 −1.4%
32 1558.20 1442.90 −7.4% 31.00 30.37 −2.0%
64 2742.82 2481.36 −9.5% 49.83 48.39 −2.9%

Mean TTFT improves monotonically with concurrency: the saving is per prefill forward, so a deeper queue waits behind more of them.

Prefill kernel attribution, one forward at in8192 / concurrency 64:

Category baseline (ms) this PR (ms) Δ
dense/linear GEMM 173.15 107.18 −65.97
sparse-MLA attn 177.39 179.49 +2.10
all-reduce/comm 134.31 134.79 +0.48
MoE moe1 + moe2 + routing 143.81 144.59 +0.78
DSA indexer + topk 24.91 25.15 +0.24
rmsnorm/quant/act 26.09 25.83 −0.26
Σ kernel 709.34 646.71 −62.62

Only dense/linear GEMM improves, where the mechanism predicts; everything else shifts within noise.

Accuracy: GSM8K 0.933 → 0.927, single scoring pass each — within what one pass resolves.


CI States

Latest PR Test (Base): ✅ Run #34549992078
Latest PR Test (Extra): ✅ Run #34793772179
Latest PR Test (AMD ROCm 10): ❌ Run #34549992056

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

A prefill batch never reaches `chunked_prefill_size`. Two independent page
roundings leak tokens out of it: `_update_prefill_budget` charges the
page-ceiled extend length against `rem_chunk_tokens` and `rem_input_tokens`,
and `add_one_req` floors the last request's truncation back down to a page.
Every request admitted whole leaks up to page_size-1 tokens, so with
page_size=64 a 16384-token chunk runs a few dozen tokens short.

That shortfall is not free on gfx95. The MLA absorb bmm is an aiter Triton
kernel whose `EVEN_MN` heuristic is a compile-time constexpr keyed on
`M % BLOCK_SIZE_M == 0`, so a misaligned M compiles to a different
specialization -- 3819 instructions against 1562 for identical MFMA work,
because the masked store degenerates into 64 separate 16-bit stores and
`% M` expands to a full division sequence. Measured on that kernel:
311.2 us at M=16368 vs 155.8 us at M=16384.

Add `compute_charge` to `_update_prefill_budget` so the compute budgets
(counted in forward-pass tokens) move independently of the KV budgets
(counted in pages, and left page-ceiled). Both compute budgets have to switch
together: they are typically configured to the same value, so leaving either
one ceiled makes it hit zero first and stop admission while the other still
has the rounding slack unspent. The last request of a batch then takes the
remainder without flooring it to a page.

Neither floor is a correctness requirement: `alloc_extend` handles a partial
leading/trailing page explicitly, DSA builds its page tables per token, and
`add_chunked_req` already commits non-page-aligned continuation chunks. The
page granularity only bought conservative KV accounting, which is unchanged.
An SWA-capped chunk stays capped -- the exact-fill path takes
`chunk_tokens_limit`, not `rem_chunk_tokens`.

Gated on gfx95 and on SGLANG_EXACT_CHUNK_FILL, so CUDA and other AMD parts
keep upstream behaviour. The dLLM admission path is excluded. Side effect on
the gated path: the `#new-token` metric now reports the tokens the forward
actually runs, which is the TODO(lsyin) next to `log_input_tokens`.

Measured on GLM-5.2-MXFP4, MI355X TP4, in8192/out1024:
dense GEMM 163.5 -> 136.6 ms, of which 25.8 ms is the two absorb bmm calls.
Tensile/hipBLASLt GEMMs are nearly indifferent to the alignment (1%); they
have no all-or-nothing constexpr branch, they just run one partial tile.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Jacob0226
Jacob0226 force-pushed the jacob/gfx95-exact-chunk-fill branch from ef9d38f to 3f3ebf3 Compare August 20, 2026 07:52
@Jacob0226
Jacob0226 marked this pull request as ready for review August 20, 2026 07:53
@Jacob0226

Jacob0226 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author
image

Except for base-c-test-8-gpu-b300, which is still in the queue, all other PRs Test Base have passed the CI tests.

@Jacob0226

Copy link
Copy Markdown
Contributor Author

@amd-bot ci-status

2 similar comments
@Jacob0226

Copy link
Copy Markdown
Contributor Author

@amd-bot ci-status

@kkHuang-amd

Copy link
Copy Markdown
Collaborator

@amd-bot ci-status

@HaiShaw HaiShaw 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.

@Jacob0226 please extend this to cover gfx942 next, gfx1250 later.

@HaiShaw

HaiShaw commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

@merrymercy @hnyls2002 please have a review.

@Jacob0226

Copy link
Copy Markdown
Contributor Author

@Jacob0226 please extend this to cover gfx942 next, gfx1250 later.

Will cover gfx942 in a follow-up PR, gfx1250 after that.

…hunk-fill

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	python/sglang/srt/managers/schedule_policy.py
@Jacob0226

Copy link
Copy Markdown
Contributor Author

/rerun-failed-ci

@Jacob0226

Copy link
Copy Markdown
Contributor Author

@amd-bot ci-status

@bingxche

Copy link
Copy Markdown
Collaborator

@Jacob0226

CI Status for PR #32888

Merge verdict: No PR-caused failures found. This PR's new exact-chunk-fill logic is gated behind is_gfx95_supported() (MI35x only); off gfx95 the schedule_policy.py refactor is behavior-preserving (compute_charge defaults to the page-ceiled length → identical to upstream), so the NVIDIA/NPU/MLX/MI300 reds cannot come from it. The gfx95 path is exercised and green (stage-b-test-1-gpu-small-amd-mi35x and other MI35x jobs passed). Every executed failure is a pre-existing/infra/flaky issue unrelated to the diff. Two cautions below: the NVIDIA base pipeline fast-fail-cancelled its downstream stage, and the changed code's only active hardware (gfx95) had 3 of its jobs red for unrelated reasons.

Note

The changed code path (PrefillAdder.exact_chunk_fill, default-on for gfx95) was exercised and passed on stage-b-test-1-gpu-small-amd-mi35x, stage-c-dsv4-pro-fp4-amd-mi35x, and 2/3 stage-c-test-large-8-gpu-amd-mi35x shards. On all non-gfx95 backends the flag resolves to False, so behavior is unchanged. Green here is meaningful for this PR.

Caution

NVIDIA "PR Test Base" is incomplete. Run 34426366171 was cancelled by fast-fail after base-b failures; base-c-test-4-gpu-b200 (4) was cancelled and the base-c stage did not fully run — those downstream jobs are not tested. Author should re-run the NVIDIA base pipeline to completion (or use bypass-fastfail sparingly) before merge.

Changed files: python/sglang/srt/environ.py (+4/-0), python/sglang/srt/managers/schedule_policy.py (+91/-9)

Executed CI failure attribution: AMD: 10 failures (0 related) · Others: 6 failures (0 related) · Fast-fail/gate cascade jobs collapsed into their root causes.

AMD Executed Failures

Job Test File Test Function Error Related? Why
stage-c-large-8gpu-mi35x (0) test/registered/amd/perf/mi35x/test_qwen35_fp8_ar_fusion_mi35x.py test_qwen35_fp8_ar_fusion_accuracy_and_perf vattn_asm: hipModuleLaunchKernel failed: 709 during graph capture → server -9 🟢 Attention-kernel graph capture, not scheduling; sister mi35x shards 1&2 green
stage-c-dsv4-flash-fp4-fp8-mi35x test/registered/amd/test_deepseek_v4_flash_fp8.py, test/registered/hicache/test_hicache_storage_umbp_backend.py setUpClass SGLANG_OPT_FP8_WO_A_GEMM ... expected float8_e4m3fn checkpoint dtype 🟢 fp8 weight-quant config at model load; unrelated to prefill budget
stage-b-mi35x-disaggregation test/registered/amd/disaggregation/test_nixl_transfer_engine_e2e.py, .../test_disaggregation_pp.py setUpClass nixlBackendError: NIXL_ERR_BACKEND; UCX Destination is unreachable 🟢 RDMA/UCX networking infra
stage-c-large-8gpu (mi300, 0) test/registered/amd/test_moriep_small.py setUpClass AttributeError: 'Tensor' object has no attribute 'format_ue8m0' 🟢 Env/library (aiter) mismatch; MI300 ≠ gfx95, path off
stage-c-large-8gpu (mi300, 1) test/registered/amd/test_deepseek_v3_mtp.py, .../test_deepseek_v3_basic.py setUpClass same format_ue8m0 → server -9 🟢 Same env/lib cluster
stage-c-large-8gpu (mi300, 2) test/registered/amd/test_deepseek_v32_basic.py setUpClass same format_ue8m0 🟢 Same env/lib cluster
stage-c-large-8gpu (mi300, 3) test/registered/amd/test_kimi_k2_instruct.py setUpClass same format_ue8m0 🟢 Same env/lib cluster
stage-b-1gpu-small (mi300, 11) test/registered/attention/test_deterministic.py assert result == 1 retry exceeded max 🟢 Deterministic-sampling flake
stage-b-1gpu-small (mi300, 13) test/registered/lora/test_multi_lora_backend.py ROUGE-L 0.977 below tolerance 1.0 🟢 LoRA accuracy tolerance; not scheduling
stage-a-1gpu-small (mi300) test/.../mem_cache/test_umbp_store.py, .../attention/test_wave_attention_kernels.py MagicMock > int TypeError; cos_sim < 0.99 🟢 Mock-test bug + attention numeric

Other Executed Failures

Job Test File Test Function Error Related? Why
base-b-1gpu-small (9) test/registered/hicache/test_hicache_storage.py test_mmlu TIMEOUT 1200s, acc 0.0, SIGQUIT (child crash) 🟢 HiCache storage backend; not scheduling
base-b-2gpu-large (3) test/registered/hicache/test_hicache_storage_mooncake_backend.py Failed to stop HiCache storage threads; read timeout 🟢 HiCache storage teardown
base-b-2gpu-large (4) test/registered/disaggregation/test_disaggregation_decode_offload.py TIMEOUT 1200s; ConnectionRefused 127.0.0.1:23100 🟢 Disagg server never came up
base-c-test-perf-16-npu-a3 test/registered/npu/performance/.../test_npu_qwen3_5_397b_..._50ms.py perf 98.69 > 51.0 ms + custom container implementation failed 🟢 NPU perf threshold + runner infra
multimodal-gen-test-1-npu-a3 (0) N/A N/A Executing the custom container implementation failed 🟢 Self-hosted NPU runner infra
stage-a-unit-test-mlx test/registered/unit/hardware_backend/mlx/test_scheduler_mixin.py test_loop_exits_after_shutdown_req mock raise in event_loop_overlap_mlx 🟢 MLX overlap-loop test in mlx/scheduler_mixin.py, not schedule_policy.py; arm64 ≠ gfx95

Details / what to do before merge

  • Re-run the NVIDIA base pipeline to completion — run 34426366171 fast-fail-cancelled the base-c stage, so those jobs carry no signal. Confirm they pass (or are red for the same unrelated HiCache/disagg reasons) before merge.
  • Confirm the AMD/NVIDIA reds are pre-existing on main. The clusters — format_ue8m0 AttributeError (aiter/env), HiCache storage timeouts, NPU/nixl runner infra, fp8-checkpoint config — are independent of this diff. A quick check against a recent main scheduled run will confirm none are introduced here.
  • gfx95 coverage is adequate: stage-b-test-1-gpu-small-amd-mi35x passed, exercising the default-on exact_chunk_fill path. No additional local run is strictly required, though re-running the 3 failed MI35x jobs would remove the unrelated-red noise from the merge picture.

Generated by amd-bot using Claude Code CLI

…hunk-fill

Upstream independently fixed the TODO(lsyin) next to log_input_tokens by
introducing raw_extend_input_len, so the metric now reports the tokens the
forward actually runs on every platform. Take upstream's version and drop
this branch's gated-path-only equivalent; on every exact-fill call site
raw_extend_input_len equals compute_charge, so the gfx95 path is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	python/sglang/srt/managers/schedule_policy.py
@HaiShaw

HaiShaw commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

@merrymercy @hnyls2002 @xiezhq-hermann Please have a review.

@Jacob0226

Copy link
Copy Markdown
Contributor Author

/rerun-failed-ci

@HaiShaw

HaiShaw commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

@hnyls2002 please review, no functional change made to non-gfx/hip.

@Jacob0226

Copy link
Copy Markdown
Contributor Author
image CUDA Base CI all green

@Jacob0226

Copy link
Copy Markdown
Contributor Author

/rerun-failed-ci

@Jacob0226

Copy link
Copy Markdown
Contributor Author
image CUDA Extra CI all green

@HaiShaw
HaiShaw merged commit 5200508 into sgl-project:main Sep 14, 2026
482 of 561 checks passed
mqhc2020 pushed a commit to mqhc2020/sglang that referenced this pull request Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants