[FEAT] Speculative-cascade acceptance (opt-in, off by default): token-specific rule holds HumanEval at +29% decode; peak rule does not - #485
Conversation
… 2.11.2 MTPLX 2.11.2 re-landed nine of PR 391's twelve Flash-Next decode keys; this ports three of the unlanded five onto upstream's re-landed structure, wired into the fixed-M4 auto-arm block with the MTPLX_QWEN4_*/MTPLX_QSA_* namespace and a per-key =0 opt-out (the old MTPLX_FABLE_* names kept as aliases). - HC_M4 (MTPLX_QWEN4_HC_M4): the verify-width (2..8 row) hyper-connection read run as one multi-threadgroup GEMV (kernels/qwen4_m4_hyper_read). Reader in runtime_options read once at import; GatedResidual gains the geometry eligibility check, pack validation and the fused read; install validation runs after the M4-stage3 install and reports at /health qwen4_install_reports.hc_m4. Rounding-class. - prefill causal-mask fuse (MTPLX_QWEN4_PREFILL_MASK_FUSE): the dense QSA prefill chunk goes through MLX's fused SDPA instead of a materialized score tensor MLX's head-dim-256 heuristic declines; a per-shape-class capability cache keeps a verify step MLX refuses from disarming a wide chunk. Rounding-class (exact visible set). - QSA prefill query tile (MTPLX_QSA_PREFILL_QUERY_TILE): tiles only the dense QSA attention query rows so a wider prefill chunk keeps the narrow chunk's attention peak and cost. Value companion, default 2048 (inert at the production 2,048 chunk width). Rounding-class (exact visible set). Each is default-on for a served fixed-M4 Flash-Next pack (server auto-arm lane_defaults, gated on the fixed-M4 config predicate) with a per-key kill switch through the existing pop loop, and registered in the boot-time runtime-env validator. All three are rounding-class, so quality-gated on HumanEval. Two of the five remain and are documented in docs/perf/qwen38-391-remainder.md: the QSA sparse split-K decode (a native kernel whose build and parity probe need the GPU) and the graph-build overlap (its prefix/suffix split of the fixed-M4 verify has no substrate on 2.11.2's single-graph verify). CPU tests (venv mlx 0.32.2, no GPU): tests/test_qwen4_hc_m4.py 53 passed, tests/test_qwen4_prefill_mask_fuse.py 40 passed.
The fourth of PR 391's five unlanded lanes: MTPLX_QSA_SPARSE_DECODE, the native split-K sparse-GQA attention for the M=4 fixed verify. It reads the selected KV rows of the fixed QSA cache once per verify cycle instead of materializing a gathered [1,2,4,2052,256] K/V pair per layer, which is where the shipped lane's bytes are. Rounding class: fp32 online softmax over the exact visible set. - kernels/qsa_sparse_decode.py + native_extensions/qsa_sparse_gqa (package mtplx_native_qsa: the split-K Metal kernel, steel headers and a nanobind binding); mtplx/native loads it, runtime_options reads MTPLX_QSA_SPARSE_DECODE (+_TILE 128:32, +_SPLITS 17); the old MTPLX_FABLE_* names are honoured as aliases when the new key is unset. - graphbank.TensorOffsetQSACache validates the lane ONCE at cache install (a real parity probe, outside any mx.compile trace); the twin re-promotion sites and the compiled verify_step carry it, and the verify body asserts the lane is in the traced graph. models/qwen4_exp routes the fixed-capacity verify width to the kernel (QSAIndexer._sparse_decode_route) or declines to stock for a request shape it cannot serve. - Server auto-arm: default ON for the fixed-M4 pack ONLY when the native extension is built; a wheel without mtplx_native_qsa declines to stock with a logged verdict and still serves. An explicit MTPLX_QSA_SPARSE_DECODE=1 reaches the fail-closed install (armed and unbuilt raises). Registered in the boot-time runtime-env validator; kill switch through the existing pop loop. - scripts/bundle_native_runtime_wheel.py signs and packages mtplx_native_qsa alongside mtplx_qsa_kernels (Developer ID, hardened runtime, secure timestamp), with tests. - The mask-fuse refusal test now accepts either MLX build's native wording: the lane logs a version-independent per-class line and never raises under default arming (it falls to the stock dense SDPA). Load-time parity on stock mlx 0.32.2 with the native kernel built: vs the fp32 reference worst rel_l2 3.1e-05 with the top-1 token identical, vs the stock gather path rel_l2 4.6e-03 (rounding class), across the 4093 and 2052 probe cells that stand in for the 16K and 261,120 serving regimes. CPU tests (venv mlx 0.32.2): tests/test_qsa_sparse_decode.py, tests/test_qsa_sparse_decode_wiring.py, tests/test_qsa_sparse_gqa_native.py and tests/test_bundle_native_runtime_wheel.py all green.
Served via `mtplx serve` (cli-resolved Turbo, no lane flags), the QSA split-K decode lane did not engage: qsa_sparse_decode_enabled() read the environment at IMPORT and cached the default (False), but the fixed-M4 auto-arm stamps MTPLX_QSA_SPARSE_DECODE (native-gated) into the environment AFTER runtime_options is imported, so the cache froze the default before the stamp landed -- the lane was absent from /health with neither an "armed:" nor a "declined to stock" line. hc_m4 escaped only because its reader is read on a path where the module was imported after the stamp. Resolve the flag lazily on the FIRST read (which is the graphbank cache install, after the overrides are applied), then cache; the _QSA_SPARSE_DECODE module global stays (tests force it to a bool) and the native-gated default in the server auto-arm is unchanged. The env is frozen once serving starts, so a lazy first read is still a single cached bool on the hot path. Regression tests, the shape that would have caught this: - the reader picks up a stamp applied AFTER import (an import-frozen reader fails it), - the fixed-M4 auto-arm block stamps the lane when the native extension is built, or prints the declined-to-stock verdict and leaves it unstamped when it is not.
Second arming failure (battery, 2026-09-07): served as `mtplx serve` launches it, hc_m4 was OFF for the same reason the QSA decode lane was in commit 3 -- its reader froze the environment at import (default off) while the fixed-M4 auto-arm stamps the lane keys into the environment AFTER runtime_options is imported. The earlier claim that hc_m4 read on a post-stamp path did not hold for the served path. Resolve every remainder-lane flag at USE (the install / route path, which runs after the overrides are applied), never at import: - runtime_options: qwen4_hc_m4_enabled, qsa_sparse_decode_tile and qsa_sparse_decode_splits (qsa_sparse_decode_enabled was fixed in commit 3); each keeps its module global as a test override (None = read env). - models/qwen4_exp: _prefill_mask_fuse_enabled drops @lru_cache (its body already reads os.environ), so a stamp landing after import is seen. - qwen4_prefill_chunk.resolve_query_tile_rows already read at use. The native-gated default and the MTPLX_FABLE_* aliases are unchanged. Upstream's own MTPLX_QWEN4_OPDIET / MTPLX_QWEN4_VERIFY_GLUE readers are left as-is (not part of this remainder set). Test reproducing the served order (tests/test_qwen4_remainder_arming.py): import mtplx.runtime + mtplx.server.openai FIRST, assert all four readers off, run _server_runtime_env_overrides for the fixed-M4 pack, apply it to os.environ, then assert all four arm -- the decode lane armed when the native extension is built, else an explicit declined-to-stock verdict, never silent absence. The hc_m4 read-once test is rewritten to assert read-at-use, and the mask-fuse test drops its now-defunct cache_clear() calls.
Arming audit (battery, 2026-09-07): `mtplx serve` imports generation / runtime / model modules before parse_args stamps the auto-arm env, so any flag whose reader resolves at module import freezes its default before the stamp lands and the auto-arm's setdefault is a silent no-op as launched. A column-0 scan of every module holding a stamped key's reader found exactly four such readers among the ~31 auto-armed keys; every other stamped key reads the environment at use or is consumed from config.json at model load. All four are decode-verify lanes, so the release control (71.17 tok/s at 16K) ran without them -- a plausible slice of the 71->81 gap. Resolve all four at use (read the environment each call; the module global stays a test/force override; the env is frozen once serving starts, so two traces of one graph still read the same value): - MTPLX_QWEN4_DRAFT_K20_PRESCATTER: qwen4_draft_k20_prescatter._ENABLED, and generation.py's cached _QWEN4_DRAFT_K20_PRESCATTER (removed; the one draft consult site calls the reader). - MTPLX_QWEN4_BLOCK_VERIFY: qwen4_block_verify._ENABLED, and generation.py's cached _QWEN4_BLOCK_VERIFY (removed; the accept-loop consult calls the reader). - MTPLX_QWEN4_OPDIET (+ _ITEMS): runtime_options. - MTPLX_QWEN4_VERIFY_GLUE (+ _ITEMS): runtime_options (reset hook kept, now forcing the globals). No default value changed; keys that already read at use are untouched. Upstream's STRICT_CLAIMS and BATCH_PAGED_OFFSETS are also import-frozen but are not auto-armed (operator sets them pre-launch), so they are left as-is. Tests: tests/test_qwen4_remainder_arming.py extended to assert all four arm in the served order (import first, stamp, read) and that the fixed-M4 auto-arm stamps OPDIET / BLOCK_VERIFY / VERIFY_GLUE and their readers then arm. The block-verify and draft-k20 source-inspection tests and the opdiet read-once test are rewritten to assert read-at-use.
The arming audit's lesson: gate on the install verdict, not the env. Three
decode-verify lanes had no per-window observable in
/health qwen4_install_reports -- draft_k20_prescatter, block_verify, opdiet --
so a served window could not confirm they engaged. Add read-only reports (no
behaviour change, no defaults touched):
- draft_k20_prescatter: {armed (read at use), engaged (first-use latch set when
claim_draft_route installs the route), receipt (the last install receipt)}.
- block_verify: {armed, engaged (latched when a block verifier is built for the
accept loop)} plus a one-shot "[mtplx] MTPLX_QWEN4_BLOCK_VERIFY armed:" log.
- opdiet: {armed, items (configured selection), applied (first-use latch of the
items that actually ran at a gated site)}.
Each appears only when ARMED (read at use, gate-able without a request), so an
unarmed lane stays absent (== off) like the other lanes; the engaged/applied
latch rides inside the armed report.
CPU test: tests/test_qwen4_remainder_arming.py asserts the three reports are
absent when off / =0 and present with armed True under a served-order stamp.
… 391 remainder port Rebase of PR youssofal#475 (cached async PLE + pooled-key rowsel) onto the PR 391 remainder port head (perf/qwen38-391-remainder-main = upstream main 2.11.2 + the four remainder lanes: HC_M4, prefill mask fuse, QSA query tile, and the QSA split-K sparse-GQA decode extension. The two lanes here add to the same fixed-M4 lane_defaults / _QWEN4_PORT_KEYS block, and their native loader (ple_cpu_rows) and wheel-signer entry union with the QSA sparse-decode extension's (mtplx_native_qsa), all cleanly additive). PR 391 is closed and its Fable delivery stack (mtplx/full_stack_env.py, the turbo-full-stack profile, mtplx/native's QSA sparse-GQA loader) was never merged, but the maintainer independently re-landed the Flash-Next stack under the MTPLX_QWEN4_*/MTPLX_QSA_* namespace, so both lanes' base contracts are present upstream and both apply: the fixed-M4 compiled-verify auxiliary plane (mtplx/qwen4_fixed_verify.py) and the QSA indexer pooled-key kernel (mtplx/kernels/qsa_indexer_prepare._pool_keys_kernel). The work here is re-siting the arming and native loading off the absent full_stack_env onto upstream's own machinery. Lanes (armed by default for a served fixed-M4 Flash-Next pack, per-lane opt-out): - ple_cached_aux: a native CPU-stream provider stages the fixed 64 M4 n-gram rows and the auxiliary embedding plane is produced with mx.async_eval outside the compiled verifier. The stock owner-side row cache is preserved; declines to stock with a printed reason when the ple_cpu_rows extension is not built. - qsa_pooled_rowsel: the twelve QSA indexers' pooled-key preparation binds the pool kernel metadata once per indexer and shares one inv_freq object. Rebase changes vs the closed-PR commit: - mtplx/qwen4_aux_lanes.py rewritten off full_stack_env: primary keys are MTPLX_QWEN4_PLE_CACHED_AUX / MTPLX_QSA_POOLED_ROWSEL, the PR 391 MTPLX_FABLE_* names kept as aliases (primary wins when both set). - mtplx/server/openai.py: the two keys join the fixed-M4 lane_defaults and _QWEN4_PORT_KEYS (so the existing pop-loop kill-switch honours KEY=0), with an alias pre-step mirroring an operator's MTPLX_FABLE_* export onto the primary. - mtplx/qsa_pooled_rowsel.py: op-diet contract re-pointed from the absent fable_opdiet_enabled to upstream's qwen4_opdiet_enabled (MTPLX_QWEN4_OPDIET). - mtplx/runtime.py: the two installs run after the fixed-M4 verify install, logging instead of the removed _print_install_receipt. - mtplx/profiles.py: the two keys added to MODEL_RUNTIME_ENV_OVERRIDE_KEYS so normalize_runtime_env_overrides accepts the server-stamped values. - mtplx/native/__init__.py: a minimal PLE-only loader (load_ple_cpu_rows_extension / ple_cpu_rows_unavailable_reason); PR 391's qsa_sparse_gqa loader is not reproduced (upstream loads native QSA via kernels/qsa_prefill_direct.py). - scripts/bundle_native_runtime_wheel.py: accepts and Developer-ID signs the new mtplx_native_ple_cpu_rows extension alongside mtplx_qsa_kernels, so a notarized release wheel carries a signed ple_cpu_rows Mach-O. - scripts/fable/setup_over100_venv.sh: builds only ple_cpu_rows. 772f5be's opt-in interleaved n-gram row cache (MTPLX_NGRAM_ROW_FILE, default off) touches the same _SidecarGather rows but at the disk-layout layer; it is orthogonal to this runtime-scheduling lane and does not subsume it. CPU tests (venv mlx 0.32.2, no GPU): 84 passed across the six lane test files plus the wheel-bundler test.
Implements the plug-in speculative-cascade deferral rule from Narasimhan et
al., "Faster Cascades via Speculative Decoding" (arXiv:2405.19261 v2), Section
4.3 Equation (10), with the speculative execution of Algorithm 4, beside the
existing typical-acceptance lane.
At each draft position with target distribution p and draft distribution q,
Equation (10) defers to the target iff
max_v q(v) < max_v p(v) - alpha * D_TV(p, q)
with D_TV(p, q) = sum_v max(0, p(v) - q(v)) over the scored top-k support. Not
deferring means the draft is good enough (the cascade target pi = q, so
Algorithm 4's min(1, pi/q) = 1): accept the draft token with no coin. Deferring
sets pi = p and runs the exact speculative law unchanged (min(1, p(x_t)/q(x_t))
coin + residual norm(max(0, p - q))), so the cascade path is a strict superset
of the exact rule with a draft-accept shortcut. The decision is deterministic;
the coin only appears on the deferred path, so with the lane off the RNG stream
and verify path are byte-identical.
q is the native MTP head's scored rows already in the verify loop as
draft_probs[depth_index] (a SparseDistribution over the head's FR-Spec scored
vocabulary, the same q the exact rule uses), so the rule reads max_q and the
top-k mass for D_TV from that object and adds no draft forward.
The knob is --cascade-threshold / MTPLX_FABLE_CASCADE_THRESHOLD (the deferral
cost alpha), read at use, default OFF (unset); any set value including 0 turns
it on, and higher alpha defers less. It is mutually exclusive with
--typical-threshold and fails loud (SystemExit at serve start, ValueError in
the verify setup) if both are set. A /health cascade_acceptance install report
and a per-request [cascade-accept] verdict line (positions, accepted, resamples,
accept_rate, mean_divergence) mirror the typical lane.
Note per the paper's Lemma 3: alpha * D_TV is subtracted, so a larger
disagreement defers less; a diverging draft that also loses peak confidence
defers, while a confident-but-wrong draft is accepted. That behavior is pinned
explicitly in the tests. CPU tests cover the rule, arming (read at use, served
order, default off), mutual exclusion, and exact-mode-off equivalence.
docs/perf/pr478-cascade-acceptance.md carries the citation and the
Problem/Change/Effect/Exactness/Files/Switch writeup.
Rename the speculative-cascade perf note to docs/perf/qwen38-cascade-acceptance.md
(matching the qwen38-* perf-note naming) and repoint the flag help and /health
note references to it. Add a "Recommended alpha grid for the 16K sweep" section:
the paper varies alpha continuously (Section 6, Figure 2) with no fixed grid, so
the grid is set from Equation (10)'s structure and this model's measured scale
(exact MTP acceptance 0.43-0.47 -> D_TV ~= 1 - accept ~= 0.55; target-row entropy
0.83-1.81 nats -> target peak ~0.35-0.65). Grid {0.0, 0.5, 1.0, 2.0} from maximal
deferral to near all-accept; recommended HumanEval operating point alpha = 0.5.
Add the provenance bridge to docs/perf/qwen38-cascade-acceptance.md: arm G and the pending HumanEval cell were measured on served code 2eac2fee; this branch re-parents the cascade mode onto the youssofal#475 served base 27d5ff6 (the same base youssofal#478 uses), so youssofal#478 (typical) and this PR (cascade) are alternative-mode peers. Per-function sha256 proof shows the cascade rule (total_variation, _peak_probability, cascade_defer_decision, the batched/lazy verify branches, the readers, the verdict, the /health payload, the --cascade-threshold arg) is byte-identical to 2eac2fee, so the measurements transfer; the three forced glue deltas (lazy elif->if, exact-block re-indent + target_p_for_cache hoist, the mutual-exclusion reading the typical env name directly) are non-behavioral.
Add the cascade context-ladder decode chart (cascade_decode_by_context.svg): exact pairing and cascade alpha 0.0/0.5/1.0/2.0 across 1K-128K, fastest of seeds with min-max variance bands; 16,384 merged from the arm-G sweep; 261,120 absent (every arm OOMs on the youssofal#475 base without youssofal#482). Update the charts manifest.
… path The re-parent that dropped the typical lane deleted the [cascade-accept] verdict emission from generate_mtpk together with the adjacent [typical-accept] block, between _attach_runtime_diagnostics and the return. The rule still engaged on the served path (cascade_* counters and VerifyStats cascade fields moved, /health reported enabled), but the verdict line never printed, so the engagement gate that parses threshold=/positions= off it saw nothing at every alpha. The block had survived only in generate_mtpa, which is not the served loop. Restore the emission in generate_mtpk, guarded by if _cascade_active, byte-identical to the pre-re-parent block. The acceptance rule is untouched (per-function sha256 of cascade_defer_decision, total_variation, _peak_probability and the generation.py cascade-accept branch all match d8efc3f). Add a served-order CPU test that arms the lane via the env reader at use, drives a temperature>0 mocked verify loop through generate_mtpk, and asserts one verdict line with threshold == alpha and positions > 0; it fails on the unfixed tree.
…te_mtpa generate_mtpa is an upstream function (present on base 27d5ff6) with zero callers anywhere in the tree. The cascade feature commit d8efc3f accidentally added a [cascade-accept] verdict block to it while moving the block around during the re-parent; it references cascade counters that generate_mtpa never defines, so it is both dead and broken (would NameError if the function were ever called). It never ran -- the served verdict is in generate_mtpk. Remove only that copy, restoring generate_mtpa byte-identical to its upstream base form. The acceptance rule is untouched (per-function sha256 unchanged); the served generate_mtpk verdict stays.
… precisely The reference carried a wrong author list and an unverified venue. The paper's authors are Narasimhan, Jitkrittum, Rawat, Kim, Gupta, Menon and Kumar; cite it as arXiv:2405.19261 v2 (2024) and drop the ICLR 2025 claim, which is not verifiable from the paper itself. Name the implemented rule as the paper does: it is r-hat_OPT, Equation (10), the plug-in ESTIMATOR of the optimal speculative-cascade deferral rule (Lemma 4, Equation (9)), which replaces that rule's ground-truth expected 0-1 losses with one minus each model's max probability. It is neither the optimal rule nor an oracle; the oracle needs expectations under the ground-truth distribution. The Diff rule (Equation (5)) is the sequential-cascade oracle and is not implemented here. Lemma 3 is why the deferral cost carries the alpha times D_TV term. Equation, Section, Algorithm and Lemma numbers are otherwise unchanged. Comment and docstring text only; no functional change. mtplx/sampling.py is deliberately NOT touched: its cascade_defer_decision docstring carries the same stale citation, but that function's full source is published as sha256 3ba053b7097fa91e and asserted byte-identical to the measured 2eac2fee, so editing it would invalidate a provenance line already live on the pull request.
cascade_decode_vs_alpha, cascade_accept_vs_alpha and cascade_decode_by_context now include the two added grid points (six cascade alphas: 0.0, 0.25, 0.5, 0.75, 1.0, 2.0). Fastest-of-seeds with min-max bands, same generators. Charts only; no code or measurement change.
cascade_decode_vs_alpha and cascade_accept_vs_alpha now plot both deferral rules: OPT (Equation 10, alpha 0/0.25/0.5/0.75/1/2) and TokenV3 (Equation 15, alpha 0.25/0.5/0.75/0.9/0.95), with distinct markers and dash, the same exact-law and typical-0.09 reference lines, and the legend outside the axes. Captions name both rules and state that equal alphas are NOT comparable across them, since TokenV3's alpha is a fraction of the target peak probability. Charts and manifest only; nothing under mtplx/.
…ade-rule r_OPT (arXiv:2405.19261 v2, Eq. 10) decides between q and p by comparing only their peaks, so a drafted token x_t~q that does not maximise q can be accepted because q is more peaked than p even when the token is poor (Sec. 4.4). This is the cause of the HumanEval loss at every alpha. Implement the token-specific rules that judge the drafted token: tokenv1 (Eq. 13): defer v iff q(v) < max_v' p(v') - alpha tokenv2 (Eq. 14): defer v iff p(v) < max_v' p(v') - alpha tokenv3 (Eq. 15): defer v iff p(v) < max_v' p(v') * (1 - alpha) On a deferred token the exact coin/residual runs with the token-specific target pi_Token (Eq. 11) instead of p -- Algorithm 6 (Appendix D) is GenSpecSample(q, p, pi_Token). A token in Top_alpha has pi(v)=q(v)+p(v)*eta, so the coin accepts it with probability 1 (accept, no coin); a deferred token has pi(v)=p(v)*eta. Rule selector: env MTPLX_FABLE_CASCADE_RULE / flag --cascade-rule, default opt for backward compatibility, same alpha knob, same mutual exclusion with typical. /health reports the rule; the [cascade-accept] verdict line names it. r_OPT's executed code is unchanged: the token-specific branches are a new elif above the OPT branch at both verify sites, so opt (or unset) is byte-for-byte as before. Also correct the stale citation in cascade_defer_decision's docstring (Mreddy/ICLR 2025 -> Narasimhan, Jitkrittum, Rawat, Kim, Gupta, Menon, Kumar, arXiv:2405.19261 v2 (2024)). Since that touches the sha256-hashed function body, docs/perf restates the proof as an AST code-hash (docstring stripped) showing the OPT rule code identical to 2eac2fee/d8efc3f5 while the docstring text changed. Tests: TokenV3 defers a confidently-wrong draft that OPT accepts, Top_alpha accepted, pi_TokenV3 matches Eq. 11 numerically, TokenV1 rule, served-order arming naming the rule, rule-selector read-at-use + default opt + fail-loud. The block-verify structural guard now counts six exact-coin sites (two shipped exact, two OPT defer, two token-specific defer), one coin per depth.
… guidance Add a measured-results section to docs/perf/qwen38-cascade-acceptance.md: the OPT HumanEval strict grid by alpha (0.9024/0.8537/0.7805/0.7073; alpha 1.0 dnf, 2.0 not run), the TokenV3 decode tok/s grid by alpha (84.09/88.70/95.30/96.52/107.10), the TokenV3 alpha 0.95 quality point (0.9695 strict / 1.000 completed / 3.05% trunc at 107.10 tok/s), the exact and typical-0.09 baselines, and the equal-speed comparison at ~105 tok/s. Add the Sec. 4.4 mechanism paragraph (why the peak rule admits poor tokens as alpha rises and the token-specific rule does not). Extend the Switch section with the --cascade-rule selector (opt default for backward compatibility; tokenv1/2/3), noting TokenV3's alpha is a fraction of the target peak so its useful range is >= 0.9, and update the /health and verdict-line docs for the rule_name/rule fields and the rule= verdict field. A placeholder line marks the pending TokenV3 alpha 0.75 quality point. Docs only.
Measured 16K HumanEval cell for TokenV3 at alpha 0.75: strict 0.9695 (159/164), completed-task 0.9876, truncation 1.83% (mean 2,652 tokens), cascade acceptance 0.820, at 95.30 tok/s (+15.1% vs exact 82.85), wall 1 h 19 m. Replaces the placeholder, and adds the closing numbers line (TokenV3 0.9695 at both alpha 0.75 and 0.95; OPT 0.9024 -> 0.7073). Docs only.
cascade_decode_by_context now carries eight arms: the exact pairing, the OPT rule at alpha 0.0/0.25/0.5/0.75/1.0/2.0, and the TokenV3 rule at alpha 0.95 across 1K-128K (fastest of three seeds, min-max bands). 261,120 is absent because every arm exceeds the memory knob on the youssofal#475 base without youssofal#482. Chart and manifest only; nothing under mtplx/.
David's ruling (2026-09-09): TokenV3 is the only cascade rule with decent accuracy (HumanEval 0.9695 strict at alpha 0.95, equal to exact, while OPT loses at every alpha: 0.9024 at 0.0 down to 0.7073 at 0.75), so it becomes the default rule. Flip the default of MTPLX_FABLE_CASCADE_RULE / --cascade-rule from opt to tokenv3 everywhere the default is defined: the reader _cascade_accept_rule() (unset -> tokenv3), the /health cascade_acceptance.rule_name default, and the --cascade-rule help text. The argparse default stays the None sentinel so an unset flag does not override a shell-set env; the effective default resolves in the reader. opt, tokenv1 and tokenv2 stay selectable (env or flag). The mode itself is unchanged and still OFF by default: with the alpha knob unset the exact speculative law runs, so this changes only which rule engages once --cascade-threshold is set. The OPT rule body is untouched: the AST code hash (docstring stripped) of cascade_defer_decision is byte-identical to 2eac2fee/d8efc3f5. Tests: the default-selector test flips to tokenv3, a new test asserts env opt still selects OPT (backward compatibility), and the exact-off and block-verify guards still pass. Docs switch section updated with the new default, why, and how to select opt.
Two charts comparing this pull request's operating points against youssofal#475 and youssofal#478, both re-derived from the receipt json rather than from any table: cascade_vs_475_478_16k.svg decode at 16,384 tokens for release 2.11.2 (exact), youssofal#475 (exact), youssofal#478 (typical 0.09), OPT alpha 0.25, and TokenV3 alpha 0.75 and 0.95, each bar annotated with its own HumanEval strict pass@1 cascade_vs_475_478_ladder.svg decode against context size, 1,024 to 131,072, for the same four arms that have a full ladder Bar height and line point are the fastest seed, every band is min-max, and acceptance mode is in each label: a tok/s figure is not readable without it. The alphas of the two rules are different quantities and the captions say so. No arm carries a 261,120 point, because on the youssofal#475 base without youssofal#482 every arm on this pack exceeds the memory knob.
David: accept rate is never defined for speculative cascade. The served verdict's accept_rate = cascade_accepted / cascade_positions is a KEPT-DRAFT rate over cascade-decided positions (no-defer accepts plus coin-accepted deferred tokens), not the paper's deferral rate r. Add a cascade_deferred counter, incremented on every deferral (the _defer / OPT defer branch, before the coin) at all four verify sites (OPT and TokenV3, batched and lazy). Expose defer_rate = cascade_deferred / cascade_positions in VerifyStats (cascade_deferred, cascade_defer_rate), the [cascade-accept] verdict line (deferred=, defer_rate=), and the /health cascade_acceptance payload (documented rates block). accept_rate is unchanged, now documented as the kept-draft rate; accept_rate + resample_rate == 1 over decided positions, while defer_rate is independent. Rule bodies untouched: the docstring-stripped AST hashes of cascade_defer_decision, total_variation and _peak_probability are byte-identical to 2eac2fee/d8efc3f5. Tests: defer/accept/resample consistency (accepted + resamples == positions; resamples <= deferred <= positions; defer_rate == deferred/positions; deferred>0 for a divergent draft under TokenV3), served-order emission of defer_rate, and exact-off leaves the cascade counters zero. docs/perf gains a Definitions block giving the three quantities, their formulas, and which artifact each comes from (verdict line / VerifyStats vs the receipt's mtp_accept_rate).
The TokenV3 series in cascade_decode_by_context.svg had a hole exactly at 16,384. The by-context renderer merged the 16K rung from battery475/g only, but the two rules were swept separately: the arm-T (TokenV3) 16,384 windows live in battery475/t, and the context ladder appended only the non-16K rungs for those arms. With no row to merge, the point was silently dropped. render_cascade_ladder.py now stages t/manifest.tsv alongside g/, reads past that manifest's leading comment line, and admits rc=65 rows, which are the arm-T gate-regex false negative on valid measured windows. The rc gate itself stays: ladder-cascade carries an rc=1 duplicate row whose receipt_dir points at the 65,536 runroot under a 131,072 ctx, and dropping the gate entirely would let that mis-aliased row through the ctx cross-check. The TokenV3 line now carries six points, 1K through 128K, with 16,384 at 107.10 tok/s (96.70-107.10, n=3), the same fastest-of-seeds and min-max rule as every other point. No other series or value changes.
cascade_accept_vs_alpha.svg plotted the kept-draft rate on a left y axis and tokens per cycle on a right y axis, and its legend named the series '(left)' and '(right)' without saying what those axes were; the right axis itself was clipped out of the figure. It is now two stacked panels on one alpha axis: top = cascade kept-draft rate (Definitions (c), cascade_accepted / cascade_positions), bottom = tokens per verify cycle, one line per rule (OPT Eq 10, TokenV3 Eq 15). Same data, same sidecar.
cascade_accept_vs_alpha.svg is replaced by cascade_kept_draft_vs_alpha.svg (kept-draft rate, Definitions (c)) and cascade_tokens_per_cycle_vs_alpha.svg (tokens per verify cycle), one quantity per chart, one line per rule. Same data, same sidecar.
… as the reference David's rulings applied to the youssofal#485 chart set: RULING 1 ("youssofal#475 is NOT exact"): no chart, legend, reference line, caption or manifest labels youssofal#475 or release 2.11.2 as "exact". The acceptance-off state is now named "acceptance mode off" / "cascade off"; the youssofal#475 arm is "youssofal#475 (base)". * cascade_decode_vs_alpha: reference lines relabeled "cascade off, youssofal#475 base (82.80)" and (see below) "typical 0.2 (99.16)". * cascade_decode_by_context: arm-C-caspair legend "exact (no cascade)" -> "cascade off (youssofal#475 base)". * cascade_vs_475_478_16k / _ladder: "youssofal#475 (base)", "acceptance mode off" / "cascade off"; the exact-acceptance disclaimer now names the ordinary speculative-decoding acceptance law, not any arm. RULING 2 (typical 0.2, not 0.09, is the youssofal#478 reference): every youssofal#485-vs-youssofal#478 comparison now references youssofal#478 at typical threshold 0.2 (pooled 16,384 window, 99.16 tok/s fastest of n=9, HumanEval strict pass@1 0.9695), replacing typical 0.09. Applied to cascade_decode_vs_alpha (dotted reference line), the 16K bars and the context ladder. The §2.4 ABAB (typical 0.09 vs TokenV3 0.95) is a separate measurement and is unchanged. Charts re-rendered from receipts (fastest-of-seeds, min-max band); manifest bytes/sha256 refreshed. Docs only; no receipts touched.
… as the reference David's rulings applied to the youssofal#485 chart set: RULING 1 ("youssofal#475 is NOT exact"): no chart, legend, reference line, caption or manifest labels youssofal#475 or release 2.11.2 as "exact". The acceptance-off state is now named "acceptance mode off" / "cascade off"; the youssofal#475 arm is "youssofal#475 (base)". * cascade_decode_vs_alpha: reference lines relabeled "cascade off, youssofal#475 base (82.80)" and (see below) "typical 0.2 (99.16)". * cascade_decode_by_context: arm-C-caspair legend "exact (no cascade)" -> "cascade off (youssofal#475 base)". * cascade_vs_475_478_16k / _ladder: "youssofal#475 (base)", "acceptance mode off" / "cascade off"; the exact-acceptance disclaimer now names the ordinary speculative-decoding acceptance law, not any arm. RULING 2 (typical 0.2, not 0.09, is the youssofal#478 reference): every youssofal#485-vs-youssofal#478 comparison now references youssofal#478 at typical threshold 0.2 (pooled 16,384 window, 99.16 tok/s fastest of n=9, HumanEval strict pass@1 0.9695), replacing typical 0.09. Applied to cascade_decode_vs_alpha (dotted reference line), the 16K bars and the context ladder. The §2.4 ABAB (typical 0.09 vs TokenV3 0.95) is a separate measurement and is unchanged. Charts re-rendered from receipts (fastest-of-seeds, min-max band); manifest bytes/sha256 refreshed. Docs only; no receipts touched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…al off"
David's RULING 1 ("youssofal#475 is NOT exact"): the typical sweep's acceptance-off arm
must not be labeled "exact". It is the ordinary acceptance law with typical
acceptance turned off, so it is now named "typical off".
* acc_vs_length, acc_vs_speed, acc_vs_length_mbpp, acc_vs_speed_mbpp: series
label "exact (off)" -> "typical off"; the MBPP single-series title/caption
"exact arm" -> "typical-off arm".
* tok_s_vs_passk: the base point "exact (off)" -> "typical off".
* pr391-charts/pr391-typical-acceptance-decode: legend "exact (lane off)" ->
"typical off"; footnote "+/-1 task of exact (153/164)" -> "of typical off
(153/164)".
* manifest.json alt/caption "exact arm" -> "typical-off arm"; bytes refreshed.
RULING 2 does not change this PR's charts: the typical sweep shows every
threshold (off / 0.09 / 0.2 / 0.4) as its own series, not a youssofal#485-vs-youssofal#478
comparison, so there is no single youssofal#478 reference to switch here.
Charts re-rendered from the sweep receipts. Docs only.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
8c467b4 to
d41a68e
Compare
…al off"
David's RULING 1 ("youssofal#475 is NOT exact"): the typical sweep's acceptance-off arm
must not be labeled "exact". It is the ordinary acceptance law with typical
acceptance turned off, so it is now named "typical off".
* acc_vs_length, acc_vs_speed, acc_vs_length_mbpp, acc_vs_speed_mbpp: series
label "exact (off)" -> "typical off"; the MBPP single-series title/caption
"exact arm" -> "typical-off arm".
* tok_s_vs_passk: the base point "exact (off)" -> "typical off".
* pr391-charts/pr391-typical-acceptance-decode: legend "exact (lane off)" ->
"typical off"; footnote "+/-1 task of exact (153/164)" -> "of typical off
(153/164)".
* manifest.json alt/caption "exact arm" -> "typical-off arm"; bytes refreshed.
RULING 2 does not change this PR's charts: the typical sweep shows every
threshold (off / 0.09 / 0.2 / 0.4) as its own series, not a youssofal#485-vs-youssofal#478
comparison, so there is no single youssofal#478 reference to switch here.
Charts re-rendered from the sweep receipts. Docs only.
…senses of "exact" Against pr-bodies/GLOSSARY.md, the same pass the pull request bodies had. The arm table gave an acceptance mode for D and E only, so A and C read as though they had none. Both run with typical off, and their rows now say so. "Exact" appears once here, in the optimization-class sense: an exact optimization is byte-for-byte the stock path, against a rounding-class one that differs only by floating-point rounding. Nothing in the file bound that, so it was indistinguishable from the exact acceptance LAW that youssofal#478 and youssofal#485 use the same word for. The paragraph now binds it and states that it never labels an arm, since arms A and C are rounding-class builds overall. The prose mixed short context labels with exact counts; prose now gives the counts and the short form is left to table row labels.
cascade_humaneval_by_alpha.svg: strict and completed-task pass@1 as grouped bars for cascade off (youssofal#475 base), the OPT rule at alpha 0.0/0.25/0.5/0.75 and the TokenV3 rule at 0.75/0.95, with the cascade-off strict level as a reference line, so the quality cost of the OPT rule is visible next to TokenV3. Data: evalsweep478/armG_summary.json and the youssofal#478 sweep's typical-off cell; nothing re-measured.
…ating-point chart cascade_humaneval_by_alpha.svg gains a youssofal#478 typical 0.2 bar pair (strict 0.9695, completed-task 1.0000, the youssofal#478 sweep's own cell on the same base, seed and sampler) beside cascade off, so the quality-neutral reference the speed comparisons use is on the quality chart too. Both sweep cells are now read from evalsweep478/results.csv instead of being pasted constants.
|
Bottom line: this PR gave us about 25% more speed on short prompts and about 40% more on long ones, on a different Flash-Next model than the one you tested. It still works after merging it onto 2.11.3. The catch is the same one you list: the output is no longer exactly the model's own distribution. What we ran
Results (tokens per second, higher is better)
What "exact output" means Flash-Next has a small draft head that guesses the next 3 tokens. The big model checks the guesses in one pass, so right guesses come almost free. That is where the speed comes from.
In practice the difference is small (your HumanEval numbers are inside noise, and we saw nothing odd in our runs), but it is a real change in what the model emits, which is why it belongs behind an opt-in flag. Rule of thumb: exact for evals or anything you will compare against, cascade for everyday chat and coding when speed matters more. What it means for this PR
|
Speculative-cascade acceptance: a second opt-in speed optimization for Qwen3.8 Flash-Next
This pull request adds one optional decode optimization, speculative-cascade acceptance, behind a single operator knob,
--cascade-threshold(the deferral cost alpha), that is unset by default. Its base is commit27d5ff6b, the #475 optimizations on upstream MTPLX 2.11.2 and the same base as #478's typical acceptance, so cascade is a sibling of #478, a second selectable acceptance mode on the shared exact base, not a change stacked on top of typical. When the knob is unset, the exact speculative-sampling law runs unchanged, token for token, and the output is byte-identical to the base. When it is set, the verify step keeps a drafted token whenever the draft distribution is good enough against the target, and defers to the exact law otherwise, so decode commits more tokens per cycle.Headline figures at 16,384 tokens of context, 3 seeds, cold prefill:
opt,tokenv1andtokenv2stay selectable via--cascade-rule.Definitions
Three different fractions in this pull request could each be called an acceptance rate, and they are not interchangeable. Every table column and every sentence below names one of these three terms. No table column and no sentence in this document says "accept rate" on its own. Where the raw field names
mtp_accept_rateandaccept_rateappear, they are named as fields and are bound to (a) and (c) respectively by the definitions below.(a) MTP draft acceptance. Accepted draft tokens divided by drafted tokens.
Source: the benchmark receipt's
mtp_accept_ratefield, written by the harness for every window. This is the same quantity #475 and #478 report, on the same population (the throughput windows), so it is the only one of the three that is comparable across the three pull requests. It is what every speed table in Section 2 shows, under the column heading MTP draft acceptance. With the mode off it is the exact law's own figure, about 0.493 on this model at 16,384 tokens.(b) Cascade deferral rate r. Positions at which the rule deferred to the target, divided by positions the rule decided.
This is the paper's own quantity: r is the deferral indicator of Narasimhan et al., and their Lemma 3 gives the rejection probability as r times D_TV, which is why the deferral cost carries the
alpha * D_TVterm. No run in this pull request reports r. The[cascade-accept]verdict line counts positions, accepted and resamples, and a resample can only happen at a deferred position whose coin failed, so the counters bound r from below but do not determine it: a deferred position whose coin succeeded is counted as an accept and is indistinguishable from a position that was never deferred. Adefer_ratefield is being added to the verdict line so r can be reported directly. Until a run carries that field, r is not available and this document reports (c) instead. Nothing in this document is a measurement of r.(c) Cascade kept-draft rate. Draft tokens kept, which is the no-defer keeps plus the deferred positions whose exact coin accepted, divided by positions the rule decided.
Source: the
accept_ratefield of the served request's[cascade-accept]verdict line, wherecascade_acceptedcounts both keep paths andcascade_positionscounts every position the rule decided. The two counters satisfyaccepted + resamples = positionsat every position, so the printedaccept_rateis exactly the fraction above. This is the quantity the HumanEval quality table reports, under the column heading Kept-draft rate (HumanEval cell), and the quantity the acceptance chart in Section 2.1 plots. It is a within-cascade engagement figure on one HumanEval run, measured on a different population from (a). The two are never set against each other as if they were the same measurement; where both are named in one sentence it is to keep them apart.Other terms:
sum_v max(0, p(v) - q(v))over the scored top-k.--cascade-threshold), the deferral cost. Higher alpha means fewer deferrals, so faster and lossier. The two rules read alpha differently and equal alphas are not comparable between them; see Section 1.1. Strategy and references
Typical acceptance (Cai et al., #478) and speculative-cascade acceptance (Narasimhan et al., this pull request) are two selectable lossy acceptance modes on the same exact base, the #475 optimizations. An operator picks at most one, and with neither set the exact speculative-sampling law runs. This pull request is the cascade mode.
The exact speculative rule keeps a draft token by a rejection coin
min(1, p(x)/q(x))that reproduces the target distribution. The cascade rule first asks whether to defer at all. When it does not defer, the cascade target is pi = q, so Algorithm 4's per-position accept probabilitymin(1, pi/q)is 1 and the draft token is kept with no coin. That probability is a per-position quantity and is none of the three rates defined above. When it defers, pi = p, which is exactly the lossless speculative-decoding law: themin(1, p/q)coin plus the residualnorm(max(0, p - q)). The cascade path is therefore a strict superset of the exact rule with a keep shortcut in front of it, and with the knob unset it is byte-identical to the exact rule. More draft tokens survive per verify cycle, so decode is faster and the kept stream is no longer distribution-exact.qon this pack is the native MTP head's scored rows, the FR-Spec frequency-ranked subset, the sameqthe exact rule already uses, so the rule adds no draft forward.1.1 The OPT rule (Equation (10))
This is the paper's r-hat_OPT (Section 4.3, Equation (10)), executed with Algorithm 4. It is the plug-in ESTIMATOR of the optimal speculative-cascade deferral rule (Lemma 4, Equation (9)): it replaces the ground-truth expected 0-1 losses that rule needs with one minus each model's max probability. It is therefore not the optimal rule itself and not an oracle, since the oracle requires expectations under the ground-truth distribution. The separate Diff rule (Equation (5),
max q < max p - alpha, with no total-variation term) is the SEQUENTIAL-cascade oracle, which this pull request does not implement.On the sign of the divergence term: the cost
alpha * D_TVis subtracted, so more disagreement lowers the bar and defers less, not more. The rule pays the expensive verification only when the target clearly leads. A diverging draft that has also lost peak confidence defers; a draft that stays confident on a wrong token is kept. This is the paper's rule as written.The paper names the resulting failure itself, in its Section 4.4: comparing max probabilities can keep a drafted token that does not maximize q, when q is merely more peaked than p. That failure is what the OPT quality curve in Section 3 measures.
1.2 The TokenV3 rule (Equation (15))
TokenV3 is the paper's answer to that failure: a token-specific rule (Section 4.4, Equation (15), target pi_Token of Equation (11), executed with Algorithm 6) that judges the drafted token v itself rather than comparing the two peaks. It defers when the target's probability on the drafted token is low relative to the target's own peak.
TokenV3's alpha is a fraction of the target's peak probability, while OPT's alpha scales a total-variation margin. The two alphas are different quantities. Equal alphas are not comparable between the two rules; compare the rules at equal decode speed instead, which is what Sections 2.4 and 3 do.
1.3 Selector and defaults
Both rules ship behind one selector,
--cascade-rule(envMTPLX_FABLE_CASCADE_RULE, valuesopt|tokenv1|tokenv2|tokenv3), on the same alpha knob. The selector defaults totokenv3.opt,tokenv1andtokenv2remain selectable. The default is the rule, not the mode: the mode stays off until alpha is set, and with alpha unset the exact speculative law runs whatever the selector says.Reference: Narasimhan, Jitkrittum, Rawat, Kim, Gupta, Menon, and Kumar, "Faster Cascades via Speculative Decoding," arXiv:2405.19261 v2 (2024), Section 4.3 Equation (10) (r-hat_OPT), Lemma 4 Equation (9), Equation (5), Algorithm 4, Lemma 3, and Section 4.4 Equations (12) to (15) with Algorithm 6.
2. Speed results
The base arm (optimization off) and the cascade arms ran on one machine, each arm receiving the same request body per cell. Section 4 gives the settings. Prefill, TTFT and peak memory are flat across alpha, because the optimization touches only the decode keep decision.
Every throughput cell is the fastest of its seeds with the slowest-to-fastest range. Every acceptance column in this section is (a) MTP draft acceptance, from the receipt's
mtp_accept_rate, so it is comparable down the column and across #475 and #478.2.1 OPT rule, decode at 16,384 tokens (higher is better)
Decode tok/s by cascade alpha: the fastest seed with its slow-to-fast band. The two reference lines are cascade off on the #475 base at 82.80 tok/s and #478 typical acceptance at 0.2 on 99.16 tok/s, both from the pooled 16,384-token windows in #478.
Two charts: cascade kept-draft rate (c) by alpha, and tokens per verify cycle by alpha, the two quantities the dial trades against decode rate. Both are read from the run's
[cascade-accept]verdict lines, not from the receipts, which do not carry them. The kept-draft rate plotted here is a different quantity from the MTP draft acceptance column in the table below.The alpha grid is set from the paper's continuous alpha sweep (Section 6, Figure 2) and this model's measured scale. The exact law's MTP draft acceptance of about 0.493 gives D_TV of about 0.51, so
alpha * D_TVspans about 0 to 1.0 across the grid, from maximal deferral at alpha 0 to near all-keep at alpha 2.2.2 OPT rule, prefill, TTFT and peak memory at 16,384
2.3 TokenV3 rule, decode at 16,384 tokens
The token-specific rule (
--cascade-rule tokenv3) at 16,384 tokens, 3 seeds, fastest-of with the min-max band.vs exactis against the pooled exact law at 82.80 tok/s. This is a separate rule from Section 2.1 and the two tables are not comparable cell for cell at equal alpha, because the two alphas mean different things (Section 1.2).The TokenV3 HumanEval cell runs at alpha 0.95 (107.1 tok/s), the TokenV3 setting closest in speed to OPT alpha 0.25 (104.8 tok/s), so Section 3 compares the two rules at equal decode speed.
2.4 Paired ABAB: typical acceptance 0.09 against TokenV3 alpha 0.95
An interleaved A/B/A/B run of the two lossy modes at their comparable speeds, reported both as each arm's fastest-of and as the paired per-seed delta (B minus A, matched by round and seed) with a 95% interval. The paired delta is the comparison of record; a difference of two fastest-of values is not.
In a three-round interleaved run at 16,384 tokens the two quality-neutral modes decode at the same speed within noise: typical acceptance at 0.09 reaches 104.46 tok/s and TokenV3 at alpha 0.95 reaches 107.11 tok/s, and the paired mean difference is -1.62 tok/s with a 95% confidence interval of -4.30 to +1.05.
2.5 Context sweep
The context sweep runs the exact arm and each cascade arm across 1,024 to 261,120 tokens, with the same fastest-of rule and min-max band as the 16K cell. The 261,120 row overflows on this base: cascade sits on the #475 base (
27d5ff6b) without the #482 verify-KV fix, so the first decode step exceeds the memory knob. #482 fixes it. See #475 Section 1.5.The 16,384 row of every context table is the Section 2.1 and Section 2.3 window for that arm. The exact column is
arm-C-caspair, this run's own paired exact windows, so at 16,384 it reads 82.85 rather than the pooled 82.80 in Section 2.1; the 0.05 tok/s between them is inside either band. The 131,072 exact cell is two seeds: the third hit a server crash unrelated to memory and was dropped.Decode tok/s by context, one line per arm, each point the fastest seed with a min-max variance bar. The 261,120 cell is absent, over the knob on this base.
Decode tok/s by context
At 64K the alpha 0.75 fastest seed (147.00 (110.63-147.00) n=3) lands above alpha 1.0. Cascade keeps are stochastic at temperature 1, so decode varies seed to seed and this band overlaps alpha 1.0's. The point inversion at this one context is seed noise, not a reversal of the alpha ordering.
Prefill tok/s by context
TTFT s by context
Wall s by context
Peak memory GB by context
2.6 Comparison with #475 and #478
Both charts put this pull request's operating points beside the two that precede it on the same base: #475 (base, acceptance mode off) and #478 (typical acceptance at 0.2). Every point is the fastest seed with a min-max band, and each arm's acceptance mode is in its label, because a decode figure is not readable without it.
Decode tok/s at 16,384 tokens for release 2.11.2, #475, #478 and four operating points from this pull request, each bar annotated with the same arm's HumanEval strict pass@1.
TokenV3 at alpha 0.95 reaches 107.10 tok/s against 99.16 for #478 typical acceptance at 0.2 and 82.84 for the #475 base with acceptance mode off, at the same strict pass@1 the release build scores. The paired ABAB comparison in Section 2.4 is the controlled version of the TokenV3-against-typical figure and puts the two at the same speed within noise; the bars above are the unpaired sweep windows. OPT at alpha 0.25 lands at 104.78 tok/s, within noise of typical acceptance, but at strict pass@1 0.8537.
Decode tok/s by context size, 1,024 to 131,072 tokens, for #475 (base) with cascade off, #478 at typical 0.2, this pull request's OPT alpha 0.25 and its TokenV3 alpha 0.95. No arm has a 261,120 point: on the #475 base without #482 every arm on this pack exceeds the memory knob.
3. Quality results
HumanEval at the cell's own sampler (temperature 1, top-p 0.95, top-k 20,
xhigh), one seed 20260829, re-scored offline. Each cell reports strict pass@1, completed-task pass@1 and the truncation rate.The acceptance column here is (c) the kept-draft rate, measured on this HumanEval run itself from its
[cascade-accept]verdict lines. It is a within-cascade engagement figure for the cell in its own row. It is not the MTP draft acceptance of the speed tables in Section 2, which is a different quantity on a different population, and the two are never compared.HumanEval pass@1 per operating point: strict (truncated tasks count as failures) and completed-task (truncated tasks excluded). The two references on the left are cascade off on the #475 base (0.9634 strict) and #478 typical acceptance at 0.2 (0.9695 strict, 1.0000 completed-task), both from the #478 sweep on the same base, seed and sampler. The OPT rule loses accuracy at every alpha, from 0.9024 at alpha 0.0 down to 0.7073 at 0.75; the TokenV3 rule holds 0.9695 at both 0.75 and 0.95, level with typical 0.2. OPT alpha 1.0 did not finish and has no bar.
Quality cells were run at alpha 0.0, 0.25, 0.5, 0.75 for OPT (and at 1.0, which did not finish inside the window) and 0.75, 0.95 for TokenV3; other alphas have speed windows only.
The off (exact) row is the #478 sweep's own exact cell, served from that tree with the typical rule off: the same exact speculative law on the same #475 base, one seed 20260829, re-scored the same way. It is the comparison point for the alpha rows, not a cascade-tree measurement. Cascade is off there, so it has no kept-draft rate.
3.1 The two rules at equal decode speed
TokenV3 at alpha 0.95 decodes at 107.10 tok/s with strict pass@1 0.9695 (159 of 164), the same as the release build and one task above the exact law on this base. OPT at alpha 0.25 decodes at 104.78 tok/s with strict pass@1 0.8537. At matched speed the two rules separate by 19 tasks of 164, which is the failure the paper's Section 4.4 token-specific rules were designed to fix.
On the 16,384-token speed windows those two arms differ in (a) MTP draft acceptance as well: TokenV3 alpha 0.95 reads 0.722 and OPT alpha 0.25 reads 0.838, both from the Section 2.1 and Section 2.3 tables. TokenV3 therefore reaches the same speed while keeping fewer drafts, which means it defers on different tokens rather than on fewer of them.
3.2 The OPT alpha curve
Strict pass@1 across the OPT sweep is 0.9634 for the exact law, 0.9024 at alpha 0.0, 0.8537 at 0.25, 0.7805 at 0.5 and 0.7073 at 0.75. Alpha 1.0 did not finish 164 tasks in three hours.
At alpha 0.0, the mildest setting, strict pass@1 is 0.9024 against 0.9634 for the exact law on the same base, ten tasks of 164; completed-task pass@1 is 0.9427 against 0.9814 and truncation 4.3% against 1.8%. At alpha 0.5, strict pass@1 is 0.7805 (128 of 164), completed-task 0.8533, truncation 8.5% and mean output 5,633 tokens, while the kept-draft rate on the cell rises from 0.871 at alpha 0.0 to 0.953 at 0.5. Quality falls as the cell keeps more drafts.
Generation length grows with alpha and is itself a result. Mean output is 2,734 tokens for the exact law, 3,419 at alpha 0.0 and 5,633 at alpha 0.5. At alpha 1.0 mean output reached 7,207 tokens with a kept-draft rate of 0.9999, and 153 of 164 tasks finished inside the three-hour window.
3.3 The TokenV3 alpha curve
TokenV3 holds strict pass@1 at 0.9695 at both measured alphas: 0.75 at 95.30 tok/s and 0.95 at 107.10 tok/s. It is the only rule measured here that holds HumanEval at the release build's own score.
4. Benchmark method
Youssofal/Qwen3.8-Flash-Next-MTPLX-Optimized-Speed, revision29ba90f82124961d0d902a9ea9bbb1034972af2f27d5ff6b, optimization off); base + cascade acceptance at alpha 0.0, 0.25, 0.5, 0.75, 1.0, 2.0xhigh5. Switch and observability
One optimization, one dial, off by default, mutually exclusive with typical acceptance.
The knob is
--cascade-threshold(envMTPLX_FABLE_CASCADE_THRESHOLD). Unset is off and byte-identical to the exact law. Any set value turns the mode on, and higher alpha is faster and lossier. The rule selector is--cascade-rule(envMTPLX_FABLE_CASCADE_RULE, valuesopt|tokenv1|tokenv2|tokenv3), which picks the deferral rule on that same alpha knob; it defaults totokenv3, the paper's Section 4.4 token-specific rule, andopt(Equation (10)),tokenv1andtokenv2remain selectable.GET /healthreports the resolved state undercascade_acceptance. Each active request prints one[cascade-accept] NOT distribution-exactline carryingrule,threshold,alpha,positions,accepted,resamples,accept_rate,mean_divergence,tokens_per_cycle,accepted_by_depth,generatedandverify_calls. Theaccept_ratefield is (c) the kept-draft rate as defined above, andaccepted + resamplesequalspositionsat every position. The line does not yet carry (b) the cascade deferral rate r; adefer_ratefield is being added for it, and until then no run reports r.The mode ships off by default. When an operator enables it, the rule defaults to TokenV3, the only measured rule that holds HumanEval, and alpha is the operator's choice. The measured TokenV3 points are alpha 0.75 (95.30 tok/s) and alpha 0.95 (107.10 tok/s), both at strict pass@1 0.9695.
6. Provenance and file map
The
[cascade-accept]verdict lives in the servedgenerate_mtpkloop (generation.py:8028; server entryopenai.py:24502). A re-parent had moved it into the uncalledgenerate_mtpa(generation.py:13858), so between the re-parent andef0e83e8no served request could emit it;351c889cremoves the dead copy fromgenerate_mtpa, leaving that function byte-identical to the base. The cascade rule itself runs solely ingenerate_mtpk(cascade_defer_decision,generation.py:12383/12497). The 16K OPT sweep ran on the pre-re-parent code and the HumanEval cells on the fixed code, both with the verdict present; the context ladder ran on the un-fixed code with engagement verified through/healthand through (a) MTP draft acceptance rising monotonically with alpha across the ladder receipts, from 0.494 on the exact arm to 0.706 at alpha 0.0, 0.816 at 0.25, 0.910 at 0.5, 0.945 at 0.75 and 1.000 at 1.0 and 2.0 (mean over the ladder's 15 records per arm).Measured on served code
2eac2fee. The OPT rule on the branch of record (perf/qwen38-cascade-acceptance, base27d5ff6b) is byte-identical to it by AST code hash, with the proof indocs/perf/qwen38-cascade-acceptance.md. The TokenV3 numbers were measured on served code3bfdb685, whosemtplx/content equalsa2531bde, the commit on this branch that adds the rule selector.ef0e83e8and351c889cdiffer only insidegenerate_mtpa, a function with no callers on any tree, so every HumanEval cell served an identical decode path and rule regardless of which blob it imported; the per-cell blob hashes in the sweep report are recorded for the record, not relied on.Full writeup:
docs/perf/qwen38-cascade-acceptance.md.