Repository navigation
[None][perf] DSpark agg perf: fused ancestral drafting + drafter slots for the request lifetime - #27
Merged
zheyuf merged 3 commits intoOct 29, 2026
Conversation
DSpark's ancestral drafting samples every block position with the temperature only, but passed neutral top-k / top-p / min-p tensors, so under advanced_sampling_mode no_topk each of the K positions still ran a top-p renorm (a keep-all pass at top_p = 1). Pass None instead: the FlashInfer chain then runs softmax and sampling only, and the fused op (mode full) receives its disable sentinels as before. Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…position Ancestral DSpark drafting ran a full sampling pipeline for every block position (bias add, softmax, sampling, RNG bookkeeping and a [B, vocab] proposal scatter), where the greedy chain it replaced ran one batched sampling over the B x K rows. Draw position k by Gumbel-max in one Triton kernel instead: it adds the Markov bias, scales by 1/T and takes the argmax of the scaled logits plus Gumbel noise from a Philox stream keyed by the request's (seed, offset) draft slot of position k, so every TP rank and every graph replay draws the same token. It writes the biased logits in float32, whatever the drafter's logits dtype, and per-chunk softmax statistics; a small kernel picks the token and gathers the next Markov embedding (the bias GEMM stays on cuBLAS). After the block, one kernel writes q_k = softmax(logits_k / T) for all positions into the slot-indexed draft_probs from those float32 logits, so the stored proposal is the distribution each token was drawn from. CUDA-graph padding rows draw from the bias alone. Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
…n iteration DFlashSpecMetadata.prepare freed the drafter slot of every request missing from the batch, so each context-only step (ARIA NO_MIX) evicted every generation request and the next decode step paid a slot clear and a parked context resume per request. A slot now stays with its request: prepare keeps the slot map in batch order, and _assign_slot reclaims the least recently batched holder only when no slot is free, parking it as the eviction did. A slot that leaves its request (reclaim or restarted prefill) is reset at once: with free slots now rare, a caller's deferred clear would hand it back out with its old base pages. Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
zheyuf
force-pushed
the
zheyuf/brb-dspark-agg-perf-stacked
branch
from
October 29, 2026 07:57
74be80b to
b5e9eef
Compare
zheyuf
marked this pull request as ready for review
October 29, 2026 08:00
zheyuf
merged commit Oct 29, 2026
e71f8ce
into
brb-nv:user/brb/m3.1-flash-gb300-initial
5 of 6 checks passed
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.
Stacked on #25 and the agg unified PR #26: review only the last 3 commits.
Description
Two per-step costs of DSpark agg serving on this branch, three commits. Commits 1-2 make ancestral drafting cheaper
than the greedy chain it replaced. Commit 3 keeps a request's drafter slot for the request's lifetime, so a context-only
step under
TRTLLM_ARIA_NO_MIXno longer makes the next decode step resume every generation request.1. Temperature-only sampling runs no filter
_temperature_only_paramspassesNonefor top-k / top-p / min-p. Underadvanced_sampling_mode: no_topk, theFlashInfer chain then runs softmax and sampling only, not a keep-all top-p renorm. The fused op (mode
full) receivesits disable sentinels as before. Block verification's emitted-token sampler also uses these parameters.
2. One fused kernel per draft position
With
use_rejection_sampling: true, DSpark drafts each block ancestrally: position k is drawn fromsoftmax((base_k + bias(d_{k-1})) / T), whered_{k-1}is the token drawn before it, and that distribution is storedas the proposal. Until now this ran a full sampling pipeline per position: bias add, softmax, top-p renorm, sampling,
RNG bookkeeping and a
[B, vocab]float32 proposal scatter, K times per step. The greedy chain it replaced ran onebatched sampling over the B x K rows. The sampled distribution does not change.
Gumbel noise (Gumbel-max sampling). The bias GEMM stays on cuBLAS.
(seed, offset)draft slot of position k. It isgraph-safe and the same on every TP rank.
plus per-chunk softmax statistics. A small kernel picks the token and gathers the next Markov embedding.
q_k = softmax(logits_k / T)for all positions into the slot-indexeddraft_probs, from those float32 logits. It recomputeslogits_k / Tbit for bit as the draw used it, so thestored proposal is the distribution each token was drawn from.
3. Drafter slots for the request lifetime
DFlashSpecMetadata.preparefreed the drafter slot of every request missing from the batch.TRTLLM_ARIA_NO_MIX=1every context step is context-only, so each one evicted every generation request. Thenext decode step then paid a slot clear and a parked-context resume per request.
preparekeeps the slot map in batch order;_assign_slotreclaims the least recently batched holder only when no slot is free, and parks it as theeviction did;
Results
GB300, agg TP2 with the config of the c44 regression report (
tot-regression-c44-tp2-s6), exceptindexer_kv_dtype: fp8(the image has no MiniMax MSA package) and a 100 GB/rank host cache (two servers per node).TRTLLM_MXFP8_GEMM_BACKEND=trtllm. Without thepin, the decode-graph MXFP8 backend is re-profiled at each start, which adds about ±0.7 ms of noise per step.
Commits 1-2: fixed-shape decode
Forced AL 3.04; 2048-token
ignore_eosrequests, T=1, top_p 0.95; GPU pairs rotated. S1 = commit 1 only.Decode device step (ms; iteration-log median, mean over servers):
the c8 gap, about 0.9 ms, comes from the branch's other DSpark commits, not from drafting.
0.944 / 0.951 / 0.959 / 0.953.
(9.770 / 40.662 vs 9.794 / 40.772 ms, two servers each).
Drafting alone (B x 7 x 200064 logits, rank-256 Markov head, CUDA graph, us per step):
SA AL gate, K7: 80 SPEED-Bench coding prompts, T=1, top_p 0.95, 4096 tokens, c64. AL comes from the server
counters. Four passes per cell, two per GPU pair:
rejection_sample_method: standardrejection_sample_method: blockThe acceptance rate at every draft position agrees within 0.003. With commits 1-2, block verification costs
+0.28 / +0.32 / +0.88 / +1.50 ms per step (+1.4-2.9%) at c1 / c8 / c44 / c88.
Commit 3: agentic c44
Measured with commit 3 alone on the head. Replay of 531 agentic requests (52 conversations) at c44, forced AL 3.04,
generation steps at batch >= 40. Two sets of runs:
Median generation step (ms), "first step after a context step / other steps (difference)":
The other steps are unchanged.
(3.878-3.971);
End to end: InferenceX AgentX on Balaram's recipes
agentx-harness 1.0.12 replays semianalysisai/cc-traces-weka-062126 in full mode (3600 s, 10 warmup requests per lane) at synthetic AL 4.04 (
TLLM_SPEC_DECODE_FORCE_NUM_ACCEPTED_TOKENS=3.04). Each recipe is used verbatim fromaria/m31f/recipes/. "good" is this branch without its four DSpark commits; it fails the SA AL gate (K7 2.96, 0.74 of golden). P90 tok/s/user is 1/P90(ITL), as InferenceX reports it.Test Coverage
tests/unittest/_torch/speculative/test_dspark_markov_sampling.py(GPU):position 0, and for position 1 given the drawn position 0, with one and three vocab chunks.
are.
tests/unittest/_torch/speculative/hw_agnostic/test_dspark_sampling.py(CPU): temperature-only sampling runs nofilter on the FlashInfer chain and gives the fused op its sentinels. The CPU mock of the old per-position loop is
removed; the GPU test covers it.
tests/unittest/_torch/speculative/hw_agnostic/test_dflash_dummy_slot.py: a context-only step keeps the generationslots; the next decode step writes no context length.
tests/unittest/_torch/speculative/hw_agnostic/test_dflash_slot_resume.py:modeling_v2: require, bfloat16 draft logits) starts, captures itsgraphs, and serves a short SA run at AL 4.10.
hw_agnosticsuite,test_tail_bookkeep.py,test_dflash_forward_context_onehot.py,the GB300 TP4 target's spec tests and the DFlash-touching files): no new failures from any commit.
failure, and every new or changed test passes.
test_gen_init_sizing_is_inert_on_a_context_only_workerpass.PR Checklist
🤖 Generated with Claude Code