Skip to content

[DSV4] Chunk the indexer MQA logits by query rows under a free-memory budget - #39095

Merged
kpham-sgl merged 11 commits into
sgl-project:mainfrom
shikicloud:dsv4-indexer-chunk
Sep 19, 2026
Merged

kpham-sgl merged 11 commits into
sgl-project:mainfrom
shikicloud:dsv4-indexer-chunk

Conversation

@shikicloud

@shikicloud shikicloud commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

The DeepSeek-V4 C4 indexer scores every query row of a prefill chunk against every compressed (c4) position of the context into one fp32 logits matrix, [query_rows, align256(max_compressed_seq_len)], allocated inside DeepGEMM (fp8_paged_mqa_logits on the paged path, fp8_mqa_logits on the nonpaged path) once per layer. Nothing sized by --mem-fraction-static accounts for it, and it grows linearly with context:

shape logits
4096 rows x 372K ctx (issue repro, H100 TP4) 1.42 GiB
4096 rows x 800K ctx (last chunk of the same request) 3.05 GiB
8192 rows x 1M ctx (default chunked-prefill, nonpaged path) ~8 GiB

So a long prefill deterministically OOMs mid-request on every TP rank once the KV pool has consumed the headroom. The DSA (V3.2) indexer already solves the same problem in _get_topk_ragged by chunking the query rows under a free-memory budget; the DSV4 indexer had no equivalent on either path, while SM120 already chunked the paged path (fixed 4096 rows, DeepGEMM metadata kernel cap) and ROCm FP4 chunked into a pooled buffer.

Modifications

  • dsa/utils.py: DSA budget logic (_get_mqa_logits_budget_bytes, _should_chunk_mqa_logits) extracted into shared helpers; dsa_indexer.py delegates, behavior unchanged. Knob stays SGLANG_DSA_MQA_LOGITS_FREE_MEM_FRACTION; no new env var.
  • dsv4/metadata.py: PagedIndexerMetadata.__post_init__ decides once per forward: one mem_get_info, rows_per_chunk = min(SM120 cap, rows the budget allows), then a DeepGEMM schedule and a top-k v2 plan per chunk. The SM120 list mechanism becomes the generic one. No chunking under prefill CUDA graph, stream capture, BCG/piecewise, or below 8M elements.
  • dsv4/indexer.py: the SM120 list loop steps by rows_per_chunk and uses per-chunk top-k plans (slices previously reused the full-batch plan on CUDA); the nonpaged path gathers K once and chunks fp8_mqa_logits by rows.

Accuracy Tests

  • CPU unit tests: 21 cases pass (test_dsv4_indexer_chunk.py, test_dsv4_nonpaged_indexer.py, existing test_dsa_mqa_logits_chunking.py).
  • GB200 kernel check at the issue shape (4096 x 93000 c4, cluster-path rows included): chunked vs unchunked fp8_paged_mqa_logits / fp8_mqa_logits logits bit-identical; per-chunk top-k v2 plans select the same pages as the full plan.
  • E2E, DeepSeek-V4-Flash-0731 TP4 GB200: forced chunking (fraction 0.0005) vs default, prompt logprob deviation inside the run-to-run noise floor of two default launches (mean |dlogprob| ~0.08, top-1 agreement ~0.9), on both the paged (cps 4096) and nonpaged (cps 16384) paths; chunk logs 24/16 vs 0.
  • Issue shape: 692K-token prompt, --mem-fraction-static 0.95 (3.7 GB free), prefill completes in 49 s with no OOM (late steps chunk to 411 rows x 173K cols).

Speed Tests and Profiling

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 #35405295077
Latest PR Test (Extra): ✅ Run #35405873102
Latest PR Test (AMD ROCm 10): ❌ Run #35405295029

Signed-off-by: Shiki Wu <shikiw@nvidia.com>
shikicloud and others added 4 commits September 13, 2026 23:28
Signed-off-by: Shiki Wu <shikiw@nvidia.com>
Signed-off-by: Shiki Wu <shikiw@nvidia.com>
Signed-off-by: Shiki Wu <shikiw@nvidia.com>
Comment thread python/sglang/srt/layers/attention/dsa/utils.py Outdated
Comment thread python/sglang/srt/layers/attention/dsa/dsa_indexer.py Outdated
Comment thread test/registered/unit/layers/test_dsv4_indexer_chunk.py Outdated
Signed-off-by: Shiki Wu <shikiw@nvidia.com>
Signed-off-by: Shiki Wu <shikiw@nvidia.com>
@Fridge003 Fridge003 added run-ci CI: run the baseline test suite on this PR run-ci-extra CI: also run the extra suite (requires run-ci) labels Sep 17, 2026
@Fridge003 Fridge003 removed the run-ci-extra CI: also run the extra suite (requires run-ci) label Sep 18, 2026
@Fridge003

Copy link
Copy Markdown
Collaborator

Fridge003 and others added 3 commits September 18, 2026 00:26
# Conflicts:
#	python/sglang/srt/layers/attention/dsv4/metadata.py
#	test/registered/unit/layers/test_dsv4_nonpaged_indexer.py
@kpham-sgl
kpham-sgl merged commit 7b67a96 into sgl-project:main Sep 19, 2026
208 of 223 checks passed
SouthWest7 added a commit to SouthWest7/sglang that referenced this pull request Sep 20, 2026
The kpool indexer's ragged extend path builds one dense fp32 logits tensor
whose two dimensions are both summed over the whole batch, while each q row
may only read its own request's column window. Peak memory is therefore
quadratic in the request count and each row's useful area is ~1/B of the
allocation, so long-context traffic with prefix-cache hits kills every TP
rank inside deep_gemm.fp8_mqa_logits.

The kpool path carried a dead, drifted copy of the budget predicate and never
called it. Delete it, take the shared policy from attention/mqa_logits_utils.py
(sgl-project#39095) instead, and split the kpool computation per request and then per
q-row chunk under that budget. The single-call fast path is preserved
unchanged when the dense tensor fits.

Fixes sgl-project#37712
SouthWest7 added a commit to SouthWest7/sglang that referenced this pull request Sep 20, 2026
The kpool indexer's ragged extend path builds one dense fp32 logits tensor
whose two dimensions are both summed over the whole batch, while each q row
may only read its own request's column window. Peak memory is therefore
quadratic in the request count and each row's useful area is ~1/B of the
allocation, so long-context traffic with prefix-cache hits kills every TP
rank inside deep_gemm.fp8_mqa_logits.

The kpool path carried a dead, drifted copy of the budget predicate and never
called it. Delete it, take the shared policy from attention/mqa_logits_utils.py
(sgl-project#39095) instead, and split the kpool computation per request and then per
q-row chunk under that budget. The single-call fast path is preserved
unchanged when the dense tensor fits.

Fixes sgl-project#37712
SouthWest7 added a commit to SouthWest7/sglang that referenced this pull request Sep 20, 2026
The kpool indexer's ragged extend path builds one dense fp32 logits tensor
whose two dimensions are both summed over the whole batch, while each q row
may only read its own request's column window. Peak memory is therefore
quadratic in the request count and each row's useful area is ~1/B of the
allocation, so long-context traffic with prefix-cache hits kills every TP
rank inside deep_gemm.fp8_mqa_logits.

The kpool path carried a dead, drifted copy of the budget predicate and never
called it. Delete it, take the shared policy from attention/mqa_logits_utils.py
(sgl-project#39095) instead, and split the kpool computation per request and then per
q-row chunk under that budget. The single-call fast path is preserved
unchanged when the dense tensor fits.

Fixes sgl-project#37712
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bypass-fastfail high priority run-ci CI: run the baseline test suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants