Repository navigation
Conversation
This branch has not been deployed
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.
Refs #828
Second PR of the speculative decoding plan in #828: the target side of a verify step, driven by the scheduler, on top of the multi-row seams from #833. No draft method is registered yet (the MTP PR adds the first one), so nothing changes unless a proposer is plugged in; the tests plug in a stub. The full stack is in the draft PoC #832.
Summary
With
--speculative-algorithmset, every decode step becomes a verify step: each running request gets k draft tokens from aProposer, the target scores the k+1 rows in one forward, and the scheduler commits the accepted drafts plus one target token.New flags:
--speculative-algorithm,--speculative-num-steps(k),--speculative-draft-model-path.Design
engine/spec_protocol.py.spec/runner.py(SpecRunner) is the target side and the order of a step;spec/proposer.pyis the draft-side interface. Method code goes underspec/<method>/in later PRs.spec/rejection.py). Each row samples with its request's temperature, top-k, top-p and greedy flag. Presence and frequency penalties are applied per row, with the drafts before a row counted as already generated.LinearVerifyScratchowned by the state pool and handed to the GDN layers throughFLAMetadata.verify_scratch. The drain replays the accepted rows into the live state (gdn_state_replay, a Triton kernel whose result matches stepwise decode bitwise) and restores the conv window, before a finished request gives its slot back./v1/cache/status(geometry.unit_bytes.kv_per_token). The verify scratch is counted with the GDN state pool.Rejected at startup: TP > 1; targets with sliding-window or DSV4 window-tier KV; decode attention backends that cannot capture multi-row graphs; proposers that sample their drafts. With
--attention-backend X,fi, where X captures multi-row graphs on device, the backend becomesXand the change is logged.Tests
tests/spec/test_verify_contract.py: a tiny hybrid (GDN + attention) model with dummy weights, served end to end through the scheduler with a stub proposer whose drafts come from a plain greedy run and are wrong at a different place each step (so every accept count occurs). The speculative run must reproduce the greedy run token for token, except at a near tie, which the test checks is one. fa and triton, with and without CUDA graphs.tests/spec/test_gdn_verify.py: a GDN verify gives the same outputs as stepwise decode, leaves the live state untouched, and after the commit matches decode after n tokens for every n; the stash and replay kernels match decode bitwise, also for a launch with more layer x row x head programs than one CUDA grid axis holds.tests/spec/test_rejection.py: greedy prefix match; each verify row samples with its own request's parameters (top-k, greedy); each verify row counts the drafts before it as committed for penalties.tests/scheduler/test_spec_drain.py: what a verify drain commits, which slots it gives back, and that the target state is committed before a finished request frees its GDN slot.tests/kvcache/test_cache_unit_bytes.py:kv_bytes_per_tokencounts the draft KV.Test environment
Unit tests
FREETOKEN_QWEN36_MODELgives the contract test its tokenizer. 1686 passed, 202 skipped, 1 failed. The failure istests/models/test_quant_config.py::test_scheme_for_agrees_with_the_stored_tensors[apodex/Apodex-1.1-mini-NVFP4[index-only]](a local checkpoint withquant_method: modelopt_mixed); it fails the same way on main.End-to-end with speculative decoding off
No draft method is registered here, so serving without the new flags must match main. AIME25 problem 1 through
freetoken.llm.LLM(model_path, dtype=torch.bfloat16, max_running_req=4)with the chat template, greedy (temperature=0,max_tokens=16384, natural end), compared token by token with the same run on main 9221fd7 (the commit before #833 merged):Qwen/Qwen3.6-35B-A3B(GDN hybrid, MoE)Qwen/Qwen3-30B-A3B(full attention, MoE)Smoke test on this branch, passed:
Known gaps
Proposer.slot_states(per-request draft state on the GDN slots) is declared but not yet wired into the pool or the budget; no proposer uses it yet.