Repository navigation
[Speculative Decoding] Add native UNO serving support - #37667
Conversation
Port the frozen integration/uno-v0.5.18 implementation at 95a406e95 and adapt it to K2's resolved config bags, page-aligned allocation, CUDA graph capture, and LoRA batch metadata.
| if spec_tokens is None: | ||
| raise RuntimeError("UNO requires speculative_num_draft_tokens") | ||
| # UNO retains an additional clean-root position beside Q/F draft slots. | ||
| return spec_tokens + 1 |
There was a problem hiding this comment.
This makes the UNO reserve 2 * (V + 1), but with page_size == 1 get_req_to_token_extra_context_len() still gives each row only V + 4 extra slots. Near the context limit, UnoDraftInput.prepare_for_decode() can hit its max(next_lengths) > row_width error (for example with V=8). Could we size the UNO row headroom from get_alloc_reserve_per_decode() too, as the page-size > 1 path already does?
There was a problem hiding this comment.
I restored reserve-sized headroom for UNO at page size 1 while leaving other speculative algorithms unchanged. For V=8, the row now provides 18 slots, matching 2 * (V + 1). I also added a CPU regression test for this invariant.
This is the same page-size-1 row-sizing issue tracked in #33579; the general DFLASH/DSPARK fix is pending in #33581.
| probs = torch.softmax(scaled, dim=-1) | ||
| cdf = torch.cumsum(probs, dim=-1) | ||
| probs = probs.masked_fill((cdf - probs) > top_ps, 0.0) | ||
| return probs / probs.sum(dim=-1, keepdim=True).clamp_min(1e-12) |
There was a problem hiding this comment.
This renormalizes over top-k before applying top-p. The regular SGLang sampler uses joint filtering (filter_apply_order="joint"), where top-p is computed from the full softmax distribution. The dense UNO path is top-k-first as well, so with both top_k and top_p (including the 50/0.95 benchmark setup), UNO's target distribution differs from normal AR despite the lossless claim. Could we align both UNO target paths with the regular sampler and add a small distribution/support parity test?
There was a problem hiding this comment.
I wanted to note that both dflash and eagle3 do this:
Since applying top-k first avoids materializing full-vocabulary softmax probabilities, I intentionally followed the existing speculative-decoding convention. Changing UNO alone would change the computational cost relative to competing specdecs, affecting apples-to-apples comparisons.
Would you prefer that UNO retain the existing convention in this PR, or align with AR while dflash and eagle remain different?
| result.num_correct_drafts_per_req_cpu = [ | ||
| length - num_non_draft for length in accept_lens | ||
| ] | ||
| result.num_correct_drafts = sum(result.num_correct_drafts_per_req_cpu) |
There was a problem hiding this comment.
num_correct_drafts = accept_len - 2 is right for UNO proposal acceptance, but the downstream token counters still add only batch_size + num_correct_drafts (update_spec_metrics, report_decode_stats, and _record_step_counters). That counts accept_len - 1, so every UNO verify under-reports one generated token per request. Could we carry the non-draft-token count through those counters too?
There was a problem hiding this comment.
I centralized the generated-token count as num_correct_drafts + batch_size * num_non_draft_tokens_per_req.
It is now used consistently for aggregate acceptance length, logged throughput, realtime/MFU metrics, and scheduler decode moments. Correct drafts are tracked separately so UNO's second non-draft token is not incorrectly included in the accept-rate numerator. Existing speculative algorithms retain their default of one non-draft token per request.
I added CPU coverage for this.
| est_time=480, | ||
| stage="base-b", | ||
| runner_config="1-gpu-large", | ||
| disabled="The UNO draft LoRA checkpoint is not published yet.", |
There was a problem hiding this comment.
Since this is the only adapter-backed linear/tree CUDA-graph test, can we enable it before the PR leaves draft? Right now the checkpoint is not public and the assertions only cover non-empty output/TPF, so public CI cannot catch a lossless-correctness regression. A fixed greedy UNO-vs-AR prefix check for both modes would cover that.
There was a problem hiding this comment.
The AR-vs-UNO parity test was pushed right after your comment.
The CUDA test now verifies that the first 32 greedily generated tokens from both linear and tree UNO exactly match AR.
The same test exercises CUDA graphs in both modes and reports linear TPF 2.526 and tree TPF 3.048. The test remains disabled in CI until we publish the adapter; we will enable it before the PR leaves draft.
# Conflicts: # test/registered/unit/managers/test_batch_result_processor_spec_grammar.py
|
@akhauriyash @drproduck hi, Uno is a great feature. How does it perform on large-sized models? Do you have experimental data? Will you continue to optimize uno on the large-sized and high TP models in the future? |
Motivation
Add native SGLang serving support for UNO, a diffusion-augmented LLM. UNO uses two model pathways:
diffusion pathwaycombines the autoregressive (AR) weights with diffusion weights implemented as LoRA adapters.AR pathwayuses only the base AR weights.The diffusion pathway drafts a block of tokens in parallel, and the AR pathway verifies those proposals against the base model's autoregressive distribution.
UNO is lossless like speculative decoding; however, it does not employ a separate draft model.
Each decoding cycle performs two target-model forward passes:
The integration supports two UNO sampling modes. The diffusion pathway constructs marginal distributions for a block of
Btokens:Linear sampler:Samples each token independently from its marginal distribution. This mode is designed to maximize system throughput and usesBas its primary hyperparameter.Tree sampler:ConsidersVcandidate sequences by selecting the topKcandidates from each marginal distribution. Its hyperparameters are(B, K, V).Modifications
UNOas a native speculative-decoding algorithm with--uno-lora-path.constraints at startup.
wrappers, and stacked A/B weight buffers. Add an optimized single-adapter
cuBLAS execution backend that overlaps LoRA-A computation with the base
GEMMs.
base model alone for target verification.
tree-verification path.
use EAGLE's dense sampling path.
SGLang's existing FlashAttention and state-merge path as fallback.
scheduler bookkeeping for its
B + 1output layout.B-wide LoRA draftand base-only verification passes.
B-wide LoRA draft pass andV-node EAGLE cascade-attention verification pass.correctness test for the fused suffix merge, and an end-to-end CUDA-graph
smoke test using a trained UNO adapter.
Changes to shared SGLang infrastructure
UNO is implemented primarily in UNO-specific modules. The following extensions
touch shared runtime interfaces but are opt-in or preserve existing defaults:
explicit token-row routing, and skipping inactive internal adapters. Only
the UNO backend enables these capabilities.
for eligible UNO tree verification. All other cases retain the existing
cascade-attention path.
target sampling; calls without it retain the existing sampling behavior.
GenerationBatchResultgains optional output-stride and non-draft-token-countfields. Scheduler bookkeeping uses them to slice UNO's
B + 1output rowsand count accepted proposals; existing algorithms retain their previous
defaults.
additional KV position required by its two-pass decode.
CUDA-graph runner only when
--speculative-algorithm UNOis active.Validation
H200 unit and CUDA smoke validation
The CPU regression tests and the registered H200 CUDA smoke test passed. The
smoke test captured and replayed both linear and tree CUDA-graph paths using
three fixed GSM8K prompts with 128 generated tokens per prompt.
TPF is useful output tokens divided by both target-model forwards per UNO
cycle:
tokens / (2 * spec_verify_ct). Tree TPF exceeded linear TPF.H200 math evaluation
The runs used
Qwen/Qwen3-8Bin BF16 on one NVIDIA H200 with FA3, a 40,960-token context, and the published UNO Qwen3-8B adapter for UNO modes. All modes used temperature 1, top-k 50, top-p 0.95, random seed 42, and up to 32,768 output tokens. GSM8K and MATH500 used one sample per problem; AIME25 used 10 samples per problem (300 completions). Each dataset was run separately so it has an independent throughput measurement.For UNO modes, the three numbers are
B/K/V: draft width, top-k candidates kept per position,and verification width. Thus,
Linear 8/1/8uses an8-token draft width, one candidate per position, and an 8-token verification
width.
tok/s/requestis aggregate throughput divided by the number ofconcurrent requests.
Reproduction
The table was generated with
benchmark/uno/run_math_eval.py, whichinstantiates
sglang.Enginein-process and excludes engine startup from thetimed interval. Exact commands for reproducing every row are provided in
benchmark/uno/README.md.The equivalent server launch commands for the two UNO configurations are:
Test coverage
Added regression coverage for:
representation.
attention features and layouts.
merge kernel.
acceptance.
Current test files:
test/registered/unit/spec/test_uno_tree_config.pytest/registered/unit/lora/test_uno_inactive_lora_batch.pytest/registered/unit/spec/test_uno_tree_sparse_sampling.pytest/registered/unit/spec/test_suffix_attention_merge_dispatch.pytest/registered/kernels/ops/attention/test_suffix_attention_merge.pytest/registered/spec/uno/test_uno.pyChecklist
paths.
CI States
Latest PR Test (Base): ⏳ Run #33738458871
Latest PR Test (Extra): ⏳ Run #33738458816
Latest PR Test (AMD ROCm 7.2): ⏳ Run #33738458864