Repository navigation
Description
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
e5c3c323f96ba5bdbad7940e427a100e55fdfd8fpasses 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/normalThe 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
- 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?
- If it holds up on your hardware, would you want the default flipped to
on(one line), or kept opt-in? - Any other hardware or models you would want benchmarked before merging?
Certificate probe, scripts and raw numbers are available on request.
Activity
[-]Tree verify (--verify-tree, default off): results and default question (PR #727)[/-][+]Experiment: qualify shared-prefix tree verification and conservative admission[/+]