Skip to content

[Fix] dspark: Skip top-p renormalization on compact verify padding rows - #43374

Open
ormandj wants to merge 1 commit into
sgl-project:mainfrom
ormandj:dspark-verify-pad-last-row
Open

ormandj wants to merge 1 commit into
sgl-project:mainfrom
ormandj:dspark-verify-pad-last-row

Conversation

@ormandj

@ormandj ormandj commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Compact verify scatters verified logits into a strided tensor and fills each request's unverified rows with zero logits. The resulting [bs * verify_num_draft_tokens, vocab] logits are passed to acceptance. When a request samples with top-p, the accept path builds the target probabilities with softmax and FlashInfer top_p_renorm_probs over every row, padding included.

Without logit adjustments, a padding row is exactly uniform after softmax. In FlashInfer's radix (AIR) top-p, every element of a uniform row falls into the same histogram bucket on every pass, and in the kernel microbenchmark below these rows make the call 3.5x to 12.5x slower than a copy of a verified row. The renorm also has no effect on such a row: FlashInfer keeps every probability >= the threshold it finds, which for a uniform row keeps every token, then divides by the row sum (1 up to rounding).

Modifications

  • build_speculative_verify_target_probs gets an optional padding_verify_lens. In the dense top-p branch (top-p only, no top-k) on CUDA, padding rows are not renormalized. A Triton kernel copies each request's last verified row into its padding rows in place, so the zero-logit padding rows no longer reach the top-p kernel, and a second pass writes the padding rows' saved uniform softmax value back into the output. Rows of requests without padding are not touched, and no extra [rows, vocab] buffer is allocated.
  • The DSpark accept path passes the compact-verify verify_lens only in compact mode, when the batch has no grammar and verify_logits_adjustments_are_noop holds (no logit bias, penalties or custom logit processor). Host-side lengths disable substitution for full-width batches; device-only layouts (built when the verify token count is rounded up to a captured CUDA-graph size) conservatively enable it. For a full-width device-only layout, the two kernels launch but leave every row untouched. In all other cases, and for all other callers, the code path is unchanged.
  • The strided logits themselves are unchanged.

Accuracy Tests

New CUDA test test/registered/spec/dspark/test_dspark_compact_top_p.py. It uses a compact batch with verify lengths 6/3/1/4, per-request temperatures and top-p values, and verified rows on which top-p keeps two tokens and drops a third:

  • target probs at vocab 32,000 and 129,280: verified rows are bit-identical to the full-renorm path; padding rows match it within rtol 1e-5;
  • accept outputs (correct_len, bonus, cap_trim_lens) are bit-identical to the full-renorm path over 4 seeds, and match the expected values for a rejection on an inner verified row, a rejection on the last verified row, and acceptance through the last verified row into the padding rows;
  • with a device-only full-width layout (lengths 6/6/6/6, vocab 129,280), target probs are bit-identical to the full-renorm path and peak CUDA allocation is within 1 MiB of it;
  • with a logit bias added to the logits, under which only a top-p renorm rejects the drafts on padding rows, accept outputs are bit-identical to the full-renorm path.

python -m pytest test/registered/spec/dspark/test_dspark_compact_top_p.py on one RTX PRO 6000 Blackwell (SM120): 4 passed, 2 subtests passed. test/registered/unit/kernels/test_kernels_namespace.py: 67 passed. On 2026-10-29 this was rebased onto main 974831643f; the new head keeps the full renorm for batches whose logits watermarking forced, the CPU-runnable test/registered/unit/kernels/test_kernels_namespace.py passes on it (apart from one failure main has too), and the SM120 GPU results above are from the previous head and will be re-run.

Speed Tests and Profiling

Kernel microbenchmark (RTX PRO 6000 Blackwell, SM120): FlashInfer top_p_renorm_probs on [rows, 129280] FP32 probabilities, top_p 0.95, half the rows padding, timed in a CUDA graph. "Copy of last row" is the kernel input this change produces.

rows zero-logit padding rows copy of last verified row
6 494 us 142 us
48 2352 us 188 us

This times the top-p kernel only. The two added padding-row kernels, and end-to-end serving with this version, were not measured.

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.

Developed with AI assistance.


CI States

Latest PR Test (Base): ❌ Run #40961599764
Latest PR Test (Extra): ❌ Run #40961599687
Latest PR Test (AMD ROCm 10): ❌ Run #40961599658

@ormandj ormandj mentioned this pull request Oct 9, 2026
17 of 56 tasks
@ormandj ormandj changed the title [DSpark] Skip top-p renorm on compact verify padding rows [Fix] dspark: Skip top-p renormalization on compact verify padding rows Oct 19, 2026
detain added a commit to detain/sglang that referenced this pull request Oct 20, 2026
… correctness)

- core batch [10-15]: sgl-project#43376 long-ctx indexer OOM bound, sgl-project#42368 FlashInfer-0.7 decode dispatch,
  sgl-project#39914 extra-KV 64-token page split + candidate fallback, sgl-project#42714 graph-safe page-split buffers,
  sgl-project#32464 SM120 sparse-prefill test gate, sgl-project#34027 int32->int64 page-split overflow guard.
- Group C [16-23]: sgl-project#42717 TP-rank verify-budget, sgl-project#43225/sgl-project#43374 compact-verify seq_lens + padding renorm,
  sgl-project#42718 packed-verify token->request map, sgl-project#42713 forced-uniform capture metadata, sgl-project#45301 padded-query
  graph lifetime, sgl-project#44141 bounded-replay guard, sgl-project#45185 V4.1-Flash-1017 FP8 QDQ (env-gated, default OFF).
  sgl-project#42710 skipped (superseded upstream: step_logits already fp32).
With compact verify, the strided target logits have zero-logit padding
rows past each request's verify length. Without logit adjustments these
rows are exactly uniform after softmax, the worst case for FlashInfer's
radix top-p, and top-p renorm leaves them unchanged (it keeps every
probability >= its threshold). In the dense top-p path, renorm only the
verified rows (the kernel gets a copy of the request's last verified row
in place of each padding row) and keep the padding rows' softmax.

Only used in compact mode with no grammar and no logit adjustments; the
strided logits and every other path are unchanged.
@ormandj
ormandj force-pushed the dspark-verify-pad-last-row branch from 63db908 to 6e78b53 Compare October 29, 2026 21:54

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant