Skip to content

Experiment: qualify shared-prefix tree verification and conservative admission #728

Open

Description

@linson007

This issue tracks experimental draft PR #727 and the conservative tree admission experiment. Keep tree verification default off. The earlier default-on question below is historical, not the current proposal.

Experimental tree verification — default off

Related issue #728. Keep this PR in draft; do not enable the default. This updates the existing tree PR instead of duplicating it. The earlier two measured improvements remain separately tracked in #756 and #757.

Reference: Muse Spark Open Ultrafast: Pushing beyond 1,000 Tokens per second, specifically shared-prefix speculative tree verification. The article’s CUDA/GB300 measurements are not a Metal/M5 baseline.

The opt-in native path verifies sixteen unique shared-prefix candidate nodes from the existing trained eight-row draft, then retains the selected target path and its GDN/attention state. Eligibility remains a single dense 5120-hidden target (tested Qwen3.8-27B 4-bit), fp32 GDN state and Int8 KV, without penalties, images, constraints or thinking. Other requests use the chain path. This is research code, not a generally qualified model/runtime optimization.

The latest change preserves all fixes in 35e5bf21a07f9012a0a575b9d5d1ab95cdbce19f (desktop options, current server fixtures, argument/sampling tests and architecture cleanup). It changes the initial retained-token average from four to the maximum eight rows. The 6.5 threshold and 0.125 EMA update weight are unchanged: observe chain acceptance before paying for wider work. It also adds a reproducible same-Engine benchmark and frozen twelve-prompt token cohort.

Current branch validation

  • Production/native builds, make check-native-cpu, build identity verification and architecture validation pass. Engine size is 30,104 lines against the unchanged 30,105 budget. No budget or exception was raised. Python lint/format and patch whitespace checks pass.
  • The committed benchmark on e5c3c323f96ba5bdbad7940e427a100e55fdfd8f passes all twelve cold greedy prompts × four samples under Metal shader validation: complete output arrays and cache metadata match, with 90 observed wide cycles and zero wide cycles in chain arms. The subsequent runner-only change permits output directories outside the checkout; it does not alter instrumentation or model execution.
  • This is not a new sampled-distribution, restored/branching conversation, active mixed-tree batch, long-context, 35B/MoE, or HTTP serving qualification. Default remains off.

Current source timing

Measured source e1f91e4ffb58bf561562aa2eba3f6e789e11c2ef, with the committed benchmark. Forty samples per prompt, first eight excluded from timing only; eight alternating balanced blocks remain. All twelve full output/cache groups match; 920 observed wide cycles. Geometric mean of per-prompt whole-request tokens/sec changes: +4.23%.

Prompt Chain request tok/s Tree request tok/s Change Chain TTFT ms Tree TTFT ms
python_lru 65.81 67.44 +2.48% 134.541 135.573
sql_latest 67.51 70.06 +3.77% 142.602 143.145
typescript_heap 50.62 60.75 +20.01% 152.390 155.579
chinese_explanation 38.52 45.76 +18.80% 167.291 168.889
technical_memory 43.38 46.96 +8.25% 170.483 173.453
json_calculation 88.49 88.44 -0.05% 173.352 174.892
count_secondary 82.90 82.97 +0.08% 170.549 171.081
plain_json_squares 82.78 82.77 -0.01% 172.858 173.417
plain_json_fibonacci 88.74 88.71 -0.03% 171.726 171.643
plain_csv_inventory 83.35 83.35 -0.01% 173.858 173.522
plain_yaml_services 82.59 82.68 +0.11% 171.626 170.779
plain_sql_schema 67.94 67.98 +0.05% 168.007 168.538

Aggregate metric verdicts: {'pass': 36}. Individual block verdicts: {'pass': 282, 'fail': 4, 'inconclusive': 2}. All blocks are retained, including failures/inconclusive results; no TTFT benefit is claimed. Raw JSON SHA-256: 57ea29b6459f1c947804c71b071e5636b247e88e30409e6f1fc29a7b705cdc74.

The original six screen prompts give +8.60%; the six structured prompts give +0.03%. These are repeat measurements of the frozen cohort, not a new independent holdout.

Retained non-pass block Metric Block (zero-based) Verdict Change in time Bound Spread
typescript_heap ttft_ms 2 fail +2.70% 2.00% 0.56%
typescript_heap ttft_ms 3 inconclusive +1.71% 13.03% 6.51%
typescript_heap ttft_ms 5 inconclusive -2.05% 18.15% 9.08%
technical_memory ttft_ms 3 fail +2.07% 2.02% 1.01%
technical_memory ttft_ms 4 fail +2.25% 2.00% 0.62%
count_secondary ttft_ms 5 fail +2.16% 2.00% 0.60%

Earlier controlled experiments (different source revision)

Measured tree source a8795e3d960b389a80f659ce34f62fff6c3918eb, based on attention-layout commit c4038a1a520bc490b5ecec7e6440c8252b3fd9ba (PR #756). Both arms shared that port’s setup/cache writer-retention policy. Only tree execution toggled. These results do not describe an unmodified-main comparison, an HTTP benchmark, additive gains with #757, or the exact current PR head.

The initial four-row prior lost 3.89% on counting (96.63 → 92.87 tokens/sec): both arms accepted 55 proposals in nine cycles, but the tree evaluated 102 nodes instead of 62. This failure motivated freezing the conservative eight-row prior and cohort before the next measurements. With the new rule, counting B1–B4 stayed on the chain and was within 0.29%.

Twelve cold greedy prompts capped at 96 tokens, 40 samples each (8 excluded from timing only, then 8 alternating ABBA/BAAB blocks): geometric mean request tokens/sec +4.42% overall, +9.04% for six screen prompts, +0.0035% for six structured holdout prompts. The holdout shows neutral fallback, not an independent positive gain. All aggregate wall/TTFT/GPU comparisons passed; individual metric blocks were 277 pass / 10 inconclusive / 1 fail. The failed SQL TTFT block was +2.67% against a 2.16% bound and is retained. An independent SQL repeat passed all eight TTFT blocks (132.747 → 132.420 ms, −0.25%). No TTFT gain is claimed.

Earlier prompt Chain request tok/s Tree request tok/s Change Chain TTFT ms Tree TTFT ms
Python LRU 57.34 58.96 +2.83% 180.062 184.704
SQL latest 60.44 62.78 +3.88% 181.762 183.864
TypeScript heap 47.30 57.18 +20.87% 175.111 179.237
Chinese explanation 37.57 44.93 +19.58% 173.022 176.048
Technical memory 42.95 46.78 +8.92% 173.012 174.960
JSON calculation 88.22 88.16 −0.07% 174.140 174.093

Earlier shader evidence at a8795e3d: 580 exact raw target-argmax checks / 59 wide cycles, six complete arrays matching the untouched reference. Pathwise GDN oracles passed 108 cases per mode (pending 0–8, two layers, flat/tiled head order, three trees; all 10,616,832 hidden values plus state/tape/guards exact). Attention oracles passed 54 cases per mode (histories 0–8192, two split choices, three trees, all 16 nodes/24 heads/D256; 5,308,416 BF16 outputs and mapped FP32 partials/statistics exact; 2,112 FP64 samples pass). Those operator implementations are unchanged here, but these are earlier runs, not freshly repeated current-head oracle certificates.

The borrowed attention fixture initially omitted residency declarations through its raw-pointer KV-table writer, causing both reference and candidate to return zeros under validation. Switching the fixture to the typed writeTable(pages, table) registered the addressed buffers. No kernel, tolerance or test case was relaxed; the original failure is retained locally. The old prototype’s 30,128-line engine architecture failure is also historical; the preserved later branch fixes now pass at 30,104.

Reproduce and interpret

M5 Pro (20 GPU cores, 48 GB), macOS 26.6, Apple clang 21 / Metal 4.0, macOS deployment target 26.4. Public target revision 10c35caafbb80f7dc6a7a432cdd11af10a6d4818.

python3 dev/benchmarks/experiments/tree_verify/run.py --model /path/to/Qwen3.8-27B-4bit --shader-validation --output build/experiments/tree-verify/shader
python3 dev/benchmarks/experiments/tree_verify/run.py --model /path/to/Qwen3.8-27B-4bit --output build/experiments/tree-verify/normal

The benchmark links generated instrumentation ahead of the runtime library, forces all draft rows, disables ANE sharing and keeps default int8 activations. Both arms keep the same tree-capable setup/cache writer policy; the per-request execution gate is the only treatment. Every sample checks complete output arrays and cache metadata. Whole-request tokens/sec includes prefill; it is not decode-only throughput. The cohort is a repeat of the original prompts, not a new holdout.

The unchanged repository ABBA rule declares spread >5% inconclusive, otherwise permits regression up to max(2%, twice spread). A pass means non-regression, not a demonstrated gain. Raw output, per-request progress, every metric block, source/binary/model identities and hashes remain in ignored build/experiments/tree-verify/; the runner reproduces them. Model weights are not included.

Companion experiments (also drafts): larger attention fusion #759 / #761; GPU ready-queue scheduling #760 / #762. Their regressions are retained; neither is integrated into the production runtime. Full-layer megakernels and a full GPU VM remain unimplemented; testing these components does not exhaust every idea in the article.

Hosted CI status

Current Actions run is not green. The production job fails during xcodebuild -downloadComponent MetalToolchain, before project compilation: Apple’s Metal toolchain asset catalog could not be fetched (exit 70). Local validation above passed independently. Other hosted jobs may still be running; no hosted all-checks-pass claim is made.

Historical issue and default question (superseded by experimental/default-off status)

Opened PR #727: an optional wide tree verifier for the dense 27B target, --verify-tree on|off, default off. This issue records the results and asks whether you would want the default flipped.

What it does. One 16-row verify step checks the draft's chain plus up to four likely alternative leaves from the draft's top-16 selector. The target's own selection at each node picks the child to follow, so every committed token is the target's selection at its path node. For sampled requests (Splash's default traffic) each node draws from the nucleus-filtered distribution with its own uniform (new uniform_stride; 0 keeps existing paths unchanged), so the output distribution equals the chain verify's. An EMA guard (SPLASH_WIDE_EMA, default 6.5 retained rows) falls back to the plain chain cycle when the tree can't help.

Results (M5 Pro 20-core, 48 GiB, Qwen3.8-27B 4-bit):

Check Result
Greedy exactness on current main (31 cold prompts, raw F32 argmax of every committed token) 7,581 / 7,581, 0 ties
Sampled exactness (temp 1, top-p .95, top-k 20; 1,500 seeds/arm; chi-square per position and pair) min p 0.34 (chain-vs-chain control 0.09). Measured on the pre-port base; sampling code unchanged by the port, not re-run on main
Paired same-process speed, 31 prompts x 2 seeds, default sampling +6.5% pooled (+5.6% geomean); code +7.8%, prose +10.8%, structured output -1.7% pooled (-0.2% geomean); tokens/cycle 5.01 -> 5.37
Through splash serve (thinking on, 6 prompts, off/on alternating) +5% and +7% in two clean pairs; tokens/cycle +8.5%
Tests test-engine-cpu, test-engine-metal, architecture check pass; decode scratch with option off identical to main (4,060,032 B)

Caveats. Gain is modest and measured on one machine; absolute tok/s drifts +-8-20% between runs so only paired numbers are used. Server A/B is only two clean pairs (one earlier attempt was invalid and discarded). Lifecycle runs (shader validation, branching conversations) show rank-0/rank-1 near-tie differences from the chain build, comparable to the chain's own run-to-run variation. Structured output is slightly slower (~1-2%).

Questions for maintainers

  1. Is the correctness bar (every committed token equals the target selection; distribution check for sampling) acceptable, or do you require text identical to the chain build?
  2. If it holds up on your hardware, would you want the default flipped to on (one line), or kept opt-in?
  3. Any other hardware or models you would want benchmarked before merging?

Certificate probe, scripts and raw numbers are available on request.

Activity

  1. changed the title [-]Tree verify (--verify-tree, default off): results and default question (PR #727)[/-] [+]Experiment: qualify shared-prefix tree verification and conservative admission[/+] on Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions