[perf] Sample next tokens from temperature-scaled logits without materializing full-vocab probs - #36103
Open
positive666 wants to merge 1 commit into
Open
[perf] Sample next tokens from temperature-scaled logits without materializing full-vocab probs#36103positive666 wants to merge 1 commit into
positive666 wants to merge 1 commit into
Conversation
…rializing full-vocab probs On the default CUDA (flashinfer) sampling path, non-greedy decoding runs an explicit full-vocab softmax plus an in-place copy-back every step, then samples from the materialized probs (via torch.multinomial for the filter-free case). The fused flashinfer samplers can consume temperature-scaled logits directly: - top-k/top-p: top_k_top_p_sampling_from_logits (joint order) draws RNG-for-RNG identical tokens to softmax + top_k_top_p_sampling_from_probs, saving the softmax + copy pass (~190us/step at bs=128, vocab 152k, L40S). - filter-free (temperature-only): sampling_from_logits replaces softmax + torch.multinomial, whose cost explodes with batch size (988us -> 61us at bs=128; 168us -> 61us at bs<=32). The fast path only engages when nothing downstream needs the probs tensor: probs-derived logprobs, RL sampling-mask capture, per-request seeds, deterministic mode, and min_p all keep the existing pipeline. Escape hatch: SGLANG_DISABLE_SAMPLING_FROM_LOGITS=1. Measured on qwen3-0.6b (L40S, conc 128, random 256/256): temperature-only median TPOT 20.7ms -> 20.1ms (~3%), output throughput +3.5-6%; top-k/top-p e2e within restart noise (kernel-level saving confirmed by microbench at top_k=50 and top_k=ALL). Greedy outputs are byte-identical with the flag on/off; logprob and seeded requests fall back and work unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
positive666
requested review from
BBuf,
Edwardf0t1,
Fridge003,
HaiShaw,
Ying1123,
ch-wan,
ispobock and
merrymercy
as code owners
August 23, 2026 18:13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
On the default CUDA (flashinfer) sampling path, every non-greedy decode step runs an explicit full-vocab softmax plus an in-place copy-back (
logits[:] = torch.softmax(logits, dim=-1)), then samples from the materialized probs. The filter-free (temperature-only) case additionally usestorch.multinomial, whose cost explodes with batch size.The fused flashinfer samplers can consume temperature-scaled logits directly, and the repo already ships an alignment test (
python/sglang/kernels/aot/tests/test_sampling.py) showingtop_k_top_p_sampling_from_logitsdraws RNG-for-RNG identical tokens tosoftmax+top_k_top_p_sampling_from_probs(bothjointandtop_k_firstorders). This is the output-side sibling of the input-logprob fast path from #31958.Modifications
Sampler._should_sample_from_scaled_logits: the fast path engages only when nothing downstream needs the probs tensor — it is skipped for probs-derived logprobs (return_logprob), RL sampling-mask capture, per-requestsampling_seed, deterministic inference mode,min_p, and non-flashinfer backends. All those paths are byte-for-byte unchanged.Sampler._sample_from_scaled_logits:top_k_top_p_sampling_from_logits(..., filter_apply_order="joint")for the top-k/top-p case,sampling_from_logitsfor the filter-free case.environ.py:SGLANG_DISABLE_SAMPLING_FROM_LOGITS(EnvBool(False)) escape hatch, placed next to its input-side siblingSGLANG_ENABLE_FAST_INPUT_LOGPROBS.test/registered/unit/sampling/test_sampler_from_logits.py(gate truth table, bitwise parity vs the probs path under a shared RNG state, distribution parity vstorch.multinomial).Note: #35966 restructures the same region of
Sampler.forwardfor sampling-mask capture. The two changes are logically independent (the mask path stays on the probs pipeline and is excluded by this gate); whichever lands second needs a trivial rebase.Accuracy Tests
softmax + top_k_top_p_sampling_from_probs(bs=64, vocab 32k).return_logproband seeded requests fall back to the existing pipeline and return unchanged results.Speed Tests and Profiling
Kernel-level (L40S, vocab=151936, fp32 logits; old = softmax + copy-back + sample-from-probs, per step):
E2E (qwen3-0.6b, 1x L40S,
bench_servingrandom 256/256, concurrency 128, ABAB design across 4 server restarts):The temperature-only saving is a fixed per-step cost (vocab- and batch-dependent, model-size independent), so the relative gain is largest for small/fast models and large running batches.
Checklist
CI States
Latest PR Test (Base): ❌ Run #32657339125
Latest PR Test (Extra): ❌ Run #32657339077
Latest PR Test (AMD ROCm 7.2): ❌ Run #32657339116