feat(pflash): Qwen3.5-0.8B hybrid drafter with block-15 LongAttnComp scorer - #724
Draft
Graffioh wants to merge 16 commits into
Draft
feat(pflash): Qwen3.5-0.8B hybrid drafter with block-15 LongAttnComp scorer#724Graffioh wants to merge 16 commits into
Graffioh wants to merge 16 commits into
Conversation
Snapshot of the uncommitted patches in the pflash-longattncomp-quality-probe worktree (patch sha256 ceef0542…) that trials 0023-0074 executed against: LongAttnComp head loading for the Qwen3-0.6B drafter, strict selection plumbing in the HTTP server and drafter IPC, and their tests. Carried verbatim so the Qwen3.5 work below builds on the same runtime baseline. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2xwWvXuXfzi7ZionCHKdk
…head Strict LongAttnComp selection with a Qwen3.5-0.8B drafter now runs only the first fifteen blocks (twelve GatedDeltaNet, three full attention) and scores every context token against the query window with block 15's own NoPE Q/K projections: softmax over the keys before the query, mean over heads and query tokens, chunk mass fill to the token budget. This is the runtime counterpart of the Python retention screen (trial 0075) and mirrors the Qwen3-0.6B block-13 head path; the legacy all-layer running-max scorer remains for legacy selection or PFLASH_QWEN35_LEGACY_SCORER=1. PFLASH_LONGATTNCOMP_HEAD_GGUF is honoured for this architecture with its own schema (qwen3_5_0_8b_nope_qk_mass_v1, feature tap before block 15, query-only [1024,2048] and [1024,512] F32 tensors, drafter sha256 pin); a trained head replaces the two projections and the loader fails closed on any mismatch. Runtime fixes needed to execute at all: the drafter load plan skips the lm_head because tied-embedding Qwen3.5-0.8B exports omit output.weight; the key projection is chunked so no score tensor puts the sequence length into a HIP grid y/z dimension (the 65,535 limit aborted at 87K tokens); the causal mask builder memsets visible rows instead of writing per element; and the KV_TQ3 environment guard is one RAII helper shared by both scorers. Verified against the Python head on a 31K QA case and an 87K CodeDebug case (Pearson 0.999 chunk-mass agreement, 14/16 top-chunk overlap; the gold function chunk is retained). Unit test covers the head/query mean reduction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2xwWvXuXfzi7ZionCHKdk
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2xwWvXuXfzi7ZionCHKdk
pflash_query (top level or under extra_body) replaces the last-user-message tail as the text the compressor scores the context against; it must occur in the latest user content so the existing window mapping applies. With the same untrained Qwen3.5 head at real-use budgets, the tail heuristic keeps 24/36 QA and 5/8 CodeDebug gold spans while the explicit question keeps 32/36 and 7/8 (trial 0077); the retrieval-instruction prefix makes no difference. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2xwWvXuXfzi7ZionCHKdk
The strict-mode window search required the query suffix to end exactly at the content boundary, which is right for the message-tail heuristic but rejected every explicit question that sits before trailing instructions. Explicit queries now search unanchored inside [content_begin, content_end) for their latest occurrence, still trimming leading tokens to absorb BPE boundary drift. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2xwWvXuXfzi7ZionCHKdk
A question ending in whitespace tokenizes to a trailing token on its own that merges with the prompt's newline in context; the unanchored window search only trimmed the query's leading tokens, so the mapping failed and the request was refused. Allow dropping up to two trailing query tokens (untrimmed match still preferred) and take the expected query ids from the matched range. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2xwWvXuXfzi7ZionCHKdk
The per-session acceptance-rate controller already applies to the Qwen3.5 drafter path (the resolved ratio sizes the strict selector's budget, feedback runs after every speculative turn). A new session started from a fixed 10%, ignoring --prefill-keep-ratio / --prefill-curve; it now keeps the configured ratio until its first feedback and adapts from there, clamped to the same 2.5-20% range. Documented in the Qwen3.5 drafter section. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2xwWvXuXfzi7ZionCHKdk
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.
Evaluation of 10 September 2026 (full prefill vs Qwen3.5 PFlash)
Qwen3.6-27B target, real-use ratio budget (16K → ~3K, 120K → ~16K), 256-token chunks for QA and 1,024 for code, explicit question as scorer query, 8K output cap.
QA never loses a case to compression. CodeDebug losses (full 29/32, heads 23/32) come from the gold function not being selected (4 cases) or from a distractor definition cut by a 1,024-token chunk boundary, after which the target picks the truncated function (7 of 8 wrong-with-evidence answers). Offline, definition-aligned segment boundaries cut those truncations from 9 cases to 1 at the same budget; that runtime change is the next step on this branch. The original all-layer scorer on the Qwen3.5 backbone drops the evidence on nearly every case; the block-15 head is what makes the backbone viable.
Summary
PFlash can now use Qwen3.5-0.8B as its drafter with the LongAttnComp attention-mass scorer, the same selection mechanism the Qwen3-0.6B block-13 head uses. Under strict selection (
PFLASH_LONGATTNCOMP_MODE=budget_onlyetc.) a Qwen3.5 drafter runs only its first 15 blocks (12 GatedDeltaNet + 3 full attention), then scores every context token against the query window with block 15's own NoPE Q/K projections: softmax over the keys before the query, mean over heads and query tokens, chunk-mass fill to the token budget. Qwen3.5's 262K native context covers the complete 76-97K-token CodeDebug inputs that the Qwen3-0.6B scorer cannot score inside its 32K window, and it shares the Qwen3.6 target tokenizer.Runtime counterpart of the Python retention screen (trial 0075: untrained Qwen3.5 hybrid head 36/36 QA support retained vs 35/36 for both Qwen3 heads, 7/8 CodeDebug gold functions vs blocked for Qwen3).
Stacked on
codex/pflash-longattncomp-core(merge base c927a59). The first commit carries, verbatim, the uncommitted research patches from thepflash-longattncomp-quality-probeworktree that trials 0023-0074 executed against (Qwen3 head loader, strict selection plumbing); every arm below runs on that state plus this branch.What changed
server/src/qwen3/qwen3_drafter.cppqwen35_longattncomp_score_and_compress: 15-block prefix, block-15 NoPE Q/K scoring, keys projected in 8K chunks (any tensor with the sequence length in a HIP grid y/z dimension aborts above 65,535 tokens; this failed at 87K before), one softmax over[S, query, heads], CPU mean reduction, then the sharedselect_longattncomp_chunkswith direct chunk mass. Routed when strict selection is active;PFLASH_QWEN35_LEGACY_SCORER=1keeps the old all-layer running-max scorer.PFLASH_LONGATTNCOMP_HEAD_GGUFfor this architecture: schemaqwen3_5_0_8b_nope_qk_mass_v1, feature tappost_block14_residual_before_block15, query-only[1024,2048]and[1024,512]F32 tensors, drafter sha256 pin; fails closed on any mismatch, like the Qwen3 loader.output.weight; the old Qwen3.5 drafter branch could not load the 0.8B GGUF at all).DFLASH27B_KV_TQ3guard is one RAII helper shared by both Qwen3.5 scorers.server/src/qwen35/gguf_target_loader.cpp: missingoutput.weightis only fatal when the load plan needs the lm_head.server/src/qwen3/qwen3_drafter_model.h:longattncomp_mean_token_masshelper (unit-tested intest_pflash_selection.cpp).server/README.md: drafter documentation.Not in this PR: block-sparse FlashPrefill for head dimension 256 (Qwen3.5 attention runs dense
ggml_flash_attn_ext), a trained Qwen3.5 head (the exporter lives in the training package), and drafter forward-pass optimisation (per-ubatch graph rebuilds; see timings).Explicit scorer query (added after the comparison)
pflash_query(top level orextra_body) tells the compressor which text to score the context against, replacing the last-128-token message-tail heuristic; it must occur inside the latest user content and is matched unanchored with leading-token drift tolerance. Trial 0077 measured why this matters with the same untrained head at real-use budgets: the message tail keeps complete support on 24/36 QA and 5/8 CodeDebug gold functions, the explicit question keeps 32/36 and 7/8, and the retrieval-instruction prefix makes no difference. Runtime check on the ten flipping cases: the query window shrank from 128 tokens to the 11-37-token question and CodeDebug explicit choices went from 1/3 to 2/3 correct. The benches in luce_box PR #107 now send the recorded question per case.Follow-ups on this branch (10 September 2026)
adaptive_keep_ratio.h) already applies to the Qwen3.5 drafter, since the resolved ratio sizes the strict selector's budget and the feedback runs after every speculative turn. A new session started from a fixed 10% and ignored--prefill-keep-ratio/--prefill-curve; it now keeps the configured ratio until its first feedback and adapts from there within the same 2.5-20% range. README documents the behaviour and the caveat that acceptance is a proxy, not a retention measurement.test_server_unit, HIP gfx1201 build).Verification
test_server_unit), including the reduction, query-field and unanchored-query tests.Qwen3.5-0.8B-BF16.gguf(converted from the pinned HF revision2fc06364…; norms already stored as 1+w): 31K-token QA case forward 4.1 s, chunk masses vs the Python head Pearson 0.999, top-16 chunk overlap 14/16; 87K-token CodeDebug case forward 21.5 s, gold function chunk retained, top-16 overlap 14/16.PFLASH_LONGATTNCOMP_HEAD_GGUFreproduces the head-less selection exactly (chunk-score difference 1.4e-4, identical chunks).Quality and time comparison (trial 0076)
Target Qwen3.6-27B-Q4_K_M, hip:0, q4_0 KV,
--max-ctx 137408, temperature 0, seed 20260908, thinking off, QAmax_tokens256, CodeDebug 2,048. Cases: reviewed development QA from the 0074 suite and the 8 complete CodeDebug cases (76-91K tokens). Arms: full prefill; Qwen3.5-0.8B hybrid (this PR, untrained head); Qwen3-0.6B PFlash with its native tail scorer under the same strict selection (the current runtime drafter). The Qwen3 trained-head arm was skipped to save time. Official benchmark scoring plus per-answer error-factor flags (luce_box PR #107). W&B run with per-answer tables: https://wandb.ai/logberto-na/lucebox/runs/ay4u28tiRatio policy (
--prefill-curve 16384:0.1875 120000:0.1365, i.e. 16K→3K and 120K→16K; chunk 256 for QA, 1,024 for code). Accuracy is the official benchmark metric; "flagged" counts answers produced under an external error factor (request error or output cap), whose scores still count:16K-budget policy (campaign protocol, constant 16,384-token budget, chunk 1,024; 30 reviewed QA cases): full prefill 27/30, Qwen3.5 hybrid 27/30 with the same per-case verdicts and no flags, median TTFT 44.0 s vs 29.6 s (1.1x median speedup, 0.78x on 17K prompts and 1.5x on 29K, because this budget barely compresses these prompts while a fixed 8-9 s compression cost is paid). Qwen3 arms were not run at this policy.
What the numbers say:
final prompt exceeds target-token ceiling (5272 > 5253): the Qwen3 drafter tokenizer differs from the Qwen3.6 target tokenizer, so re-tokenized compressed text can overshoot by a few tokens and the server fails closed. Qwen3.5 shares the target tokenizer and cannot hit this.🤖 Generated with Claude Code
https://claude.ai/code/session_01F2xwWvXuXfzi7ZionCHKdk