Repository navigation
Conversation
… and qwen4_exp families
…a renorm in Sampler.probs
…alings it does not apply
…replacing model_config
…rlo tests already cover
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
Adds DFlash 2 as a second speculative decoding method next to MTP (#844). A separate draft checkpoint reads the outputs of a few target layers and drafts up to
block_size- 1 tokens in one forward (7 for the released drafts). Theqwen3_5_moeandqwen4_expfamilies (HF and GGUF) accept any DFlash 2 draft that fits the target; tested with the three released drafts on their own targets:incoai/Qwen3.8-27B-DFlash2,incoai/Qwen3.6-35B-A3B-DFlash2andincoai/Qwen3.8-Flash-Next-DFlash2.Claims
Validation
H100 80GB, driver 580.95.05, Intel Xeon Platinum 8480+ (26 vCPUs), CUDA 13.0, torch 2.14.1+cu130; this branch at 0f48b9d, main at 9cacd08. The last commit, a033e53, only drops redundant tests; the trimmed test files pass on it (62 passed, 4 skipped). FreeToken's CI does not run the test suite, so everything below ran locally.
Accuracy
fa(the backend speculative decoding switches to)test_rejection.py, Monte Carlo over 6M rowstest_aime.pywith DFlash on Flash-Next;test_cache_rebuild.pywith DFlash on 35B-A3BSpeed
One request, AIME25 problem 1, recommended sampling (
temperature=1.0, top_p=0.95, top_k=20), natural end. Each cell is the median of 5 runs of output tokens /generate()wall time, with the median tokens committed per verify step in parentheses.Qwen/Qwen3.8-27B(bf16)Qwen/Qwen3.6-35B-A3B(bf16)nvidia/Qwen3.8-Flash-Next-NVFP4On the GGUF, DFlash k=3 is faster: 179.2 (3.23), 1.73x off.
All settings, main vs this branch, and memory
Each run is
freetoken.llm.LLM(model, dtype=torch.bfloat16, max_running_req=4)with the chat template, one prompt,SamplingParams(max_tokens=16384, temperature=1.0, top_p=0.95, top_k=20), andspeculative_algorithm/speculative_num_steps/speculative_draft_model_pathper row. The GGUF isISTA-DASLab/Qwen3.8-Flash-Next-GSQ-RCO-GGUF(IQ3_XXS). Off and MTP ran on main 9cacd08 and on this branch; each pair produced the same token count in every run. KV tokens are what the engine sized at startup with default memory settings, with MoE cache slots in parentheses (35B-A3B and the GGUF cache every expert, Flash-Next NVFP4 22483 of 24576 when off).The runs that miss the answer all hit the 16384-token limit. Flash-Next NVFP4's KV is at its 8192-token reserve floor with DFlash, so its draft takes expert cache slots instead.
The draft stays resident: 1924M / 526M / 603M parameters (27B / 35B-A3B / Flash-Next drafts), plus its K/V ring and a window of 2112 x hidden bf16 on each GDN slot (25 slots here: 516 / 206 / 258 MiB).
Reproduce
Tests added or changed
tests/kernels/test_dflash.pytests/models/dflash/test_draft.pytests/spec/test_dflash_window.pytests/spec/test_rejection.pytests/spec/test_verify_contract.pyqwen3_5andqwen4_exp, with and without graphsFREETOKEN_QWEN36_MODEL(tokenizer)tests/kvcache/test_linear_state_pool_alloc.py,tests/models/qwen4_exp/test_ple.pytrack_srctests/e2e/test_cache_rebuild.py,tests/e2e/test_aime.py--moe-strategy hybrid);FREETOKEN_TEST_SPEC_*knobsDetails
The released DFlash 2 drafts use sliding-window attention (window 2048).
fc+ norm) and write their K/V into the ring. One draft forward over[last token, mask x 7]reads it; the target LM head gives 16 candidates per slot, and a selector picks one path (sampled from its distribution q, or argmax when greedy).dflash_windowslot state on the target's GDN state slot. They are snapshotted and restored with the GDN state, and on a prefix hit the ring is rebuilt from the last 2047 of them.min(1, p/q), on a reject resample frommax(0, p - q), so the output follows the target distribution. All-greedy batches keep the prefix match of feat(spec): verify draft tokens on the target side #836.FeatureSink.layer_outputgains aresidualargument and is called after each layer the draft reads (qwen3_5_moe,qwen4_exp);Proposer.proposegets the pending tokens and returns aSpecProposal(tokens, cand, q); a proposer can declare slot states (EngineConfig.spec_slot_states);Sampler.probsandFLAMetadata.track_srcare new.config.json: no draft path; a draft that is notDFlash2DraftModel(DFlash v1 included), or is quantized, causal, not all sliding-window, or uses a rope, partial or non-NeoX rotary, or output scaling this forward does not apply; selector sizes orhead_dimthat are not powers of two, aconv_group_sizethat does not divide the hidden size, or empty, repeated or negativetarget_layer_ids; k outside 1..block_size- 1 (the default isblock_size- 1); a draft whose layers, hidden size, vocabulary or mask token do not fit the target; a target without GDN layers unless--cache-type naive.Known gaps
--moe-strategy hybrid/cpu.memory_ratioheadroom: the tapped features of the rows the draft keeps (at most 2112 per request; 256 MiB for an 8192-row chunk of four 2048-token prompts on 35B-A3B), and a prefix-hit restore that projects every restoring request's 2047 rows in one call, so it grows withmax_running_req.--tp 1and runs without overlap scheduling, as in feat(spec): verify draft tokens on the target side #836.