perf(qwen35): retain speculation during paged prefill - #686
Draft
Graffioh wants to merge 1 commit into
Draft
Conversation
Graffioh
force-pushed
the
codex/qwen-mixed-prefill-spec
branch
from
August 31, 2026 13:32
f6aed43 to
312beb1
Compare
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.
Status
This remains a draft. The mixed-row lowering is implemented, and an initial staggered A/B/A exposed a deterministic direct-row numerical error. Commit
312beb19c5b680111a0a21e2af9aa7dae49531eecontains the causal fix and a boundary regression, but the corrected commit has not yet completed GPU qualification because the device was handed back to another user.cfa76afe30ade3e53456da86bd957159a121f4bc312beb19c5b680111a0a21e2af9aa7dae49531eeqwen-mixed-prefill-specmixed-prefill-spec-lanes-0001(draft)Do not merge this PR until the corrected GPU contract, exact-output A/B/A, steady-state controls, and matched profiles pass.
Mechanism
Qwen now lowers one target step as:
select_chain_lanes()no longer disables every eligible chain lane merely because the samecommon::SeqEngine::StepPlancontains prefill work. The common scheduler and plan types are unchanged.The Qwen-private builder reuses the existing packed-prefill and fixed-chain machinery. It compacts LM-head rows as completed prompt tails, ordinary direct outputs, and tree verification rows, so long prompt interiors do not reach the LM head. Its replay key contains the ordered prefill topology, direct-row shape, logits shape, tree bucket, and padded KV span. Capacity accounts for prefill, optional AR, and tree recurrence segments.
The attention call is split only when a tree graph has a direct prefix:
Q/K/V projections and KV writes remain shared across the batch. Pure-tree and ordinary packed paths are unchanged. This preserves ordinary direct-row partitioning while still verifying the tree in the same target graph.
State and commit invariants
ggml_backend_cuda_tree_commit_transaction()remains the only tree promotion path and promotes only the accepted prefix across KV, features, convolution state, and Gated DeltaNet state.StepResultcontract. The scheduler retires the cohort; slot admission reinitializes recurrent state. Tree promotion still preflights all domains before mutation.No universal row planner, common scheduler policy, or merged durable/scratch commit path is introduced.
Correctness failure found during qualification
The first candidate retained speculation and produced a large live-decoder speed signal, but one arriving request changed output from its first generated token. Baseline-before and baseline-after were byte-identical, the compact prompt-tail row mapping was correct, and the top-logit gap was large enough to rule out a sampling tie.
The root cause was paged-attention partitioning at the 1,024-token boundary. With 16-token blocks, the ordinary direct call used 64 blocks and one partition. Adding a width-8 tree suffix made the combined launch size 1,032 tokens, or 65 blocks and two partitions. Direct rows were then rounded through the half-precision partial buffer even though their own causal span had not changed. Recurrent state was byte-identical through the preceding layers and first diverged immediately after that full-attention layer.
The current commit separates direct and tree attention calls inside the mixed graph. The new model contract pins this boundary with two established decoders and two ragged prefills, compares the completing prompt-tail token, checks the exact compact row map and W8 tree topology, and requires every durable convolution and SSM slab to remain byte-identical to speculation-disabled execution.
Current verification
Completed on the current commit without using the GPU after it was released:
git diff --check: passtest_generate,test_model_smoke, anddflash_server: passtest_generate --seq-engine-mixed-spec-contractrebuilt after the final sampler-correctness cleanupAn earlier short Qwen3.8/DFlash2 contract passed against the split production code, but it predates the final 1,024-token boundary fixture and is not the qualification result. The corrected boundary contract and corrected staggered workload have not run.
Pushing the draft automatically queued upstream self-hosted CI. Two CUDA GPU checks finished before this was noticed; the two ROCm checks were still pending. An attempt to cancel the workflow returned GitHub
403because the fork identity lacks repository Actions administration. Treat any automatic CI result as incidental, not as the corrected qualification bracket.Model and machine provenance
The frozen qualification pair is:
Qwen3.8-27B-UD-IQ4_XS.gguf, sourceunsloth/Qwen3.8-27B-GGUF@4ca720…, 14,252,845,984 bytes, SHA-25640fac4050e940397dbf13087afd50f4734a11805bf9d65ef8ddd7483470e6199Qwen3.8-27B-DFlash2Q8_0, sourceincoai/Qwen3.8-27B-DFlash2@dedf8df68adfb1afeaf7b7480c0a0243108177b4, 2,045,471,776 bytes, SHA-256bb727abc583498aa4deea8b3cd0c34c2d96553954cbff25b5f7bdd469f0f1306[5, 19, 33, 47, 61]gfx1201), ROCm 7.2, Q8 KVThe initial host did not satisfy the frozen clock/cooling topology preflight, so its timings are diagnostic only in addition to failing the output gate.
Quarantined initial A/B/A
Frozen workload: two established HumanEval-raw decoders generating 512 tokens each; after exactly 2.000 seconds, one medium 1,150-word and one long 3,400-word prompt arrived and generated 64 tokens each. Temperature was 0, seed 1, EOS ignored, five slots, 8,192-token context, 40,960 KV tokens, W8, Q8 KV, and graph/prefix caches disabled.
A/A2 drift was 0.02% for live goodput and 0.09% for full-round time. The candidate used speculation in all eight mixed rounds and accepted 68 of 147 verified draft tokens. However, its output-set hash differed (
cd4b5b…versus identical A/A2ded4dec…). These numbers are quarantined and are not a performance result.Relation to Luce Forge PR #67
PR #67 reports synchronized, steady fixed-concurrency waves: ten HumanEval waves per concurrency with 256 generated tokens per request. Its output-window rates are 101.9, 189.5, 219.5, 268.3, and 298.4 tok/s at C1 through C5.
The 10.711 tok/s value above is established-decoder goodput only inside an overlapping prefill window. It is neither total server throughput nor comparable to PR #67. The corrected commit therefore has no valid improvement result against that baseline yet. Structurally, decode-only execution does not enter this mixed path, but matched C1/C2/C5 controls still have to prove no steady regression.
Qualification handoff
Run no broader HumanEval sweep than the frozen ten-case subset. In order:
test_generate --seq-engine-mixed-spec-contract <target> <draft> 4. This is the smallest old-exclusion, prompt-tail, W8, 1,024-token partition-boundary, and durable-state regression.The preregistered promotion gate is at least 1% median established-decoder goodput improvement during prefill, greater than two times measured A/A2 drift, with unchanged outputs/transactions and no material steady decode regression.
Remaining limitations