Skip to content

perf(qwen4): typical acceptance behind --typical-threshold (default off) - #478

Open
davidtai wants to merge 3 commits into
youssofal:mainfrom
davidtai:perf/qwen38-typical-acceptance
Open

perf(qwen4): typical acceptance behind --typical-threshold (default off)#478
davidtai wants to merge 3 commits into
youssofal:mainfrom
davidtai:perf/qwen38-typical-acceptance

Conversation

@davidtai

@davidtai davidtai commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Typical acceptance: an opt-in tokens-per-cycle lane for Qwen3.8 Flash-Next

Stacked on #391 and #475: this branch is the #475 head plus one commit. Only commit 4c963f88 is this pull request; the lane is off by default.

This lane is NOT distribution-exact. Unlike the exact decode lanes on this
stack, typical acceptance changes the emitted token stream: it trades
distribution-exactness for more accepted tokens per verify cycle. It is OFF
by default
, it is its own opt-in switch, and it is gated on task quality, not
on matching the target distribution. Turn it on only when you want the speed
and accept the trade.

This pull request adds one decode lane, typical acceptance (Medusa-2), behind a
single operator knob that is unset by default. When unset, the exact
speculative-sampling law runs byte-for-byte unchanged. When set to a positive
threshold, the lane accepts more draft tokens per cycle and decode gets faster.

Headline, at the canonical 16,384 / 1,024 cell, three seeds: at threshold 0.09
decode is 107.95 tok/s against 81.78 with the lane off, +32.0%, clearing the
100 tok/s target; tokens per cycle rise from 2.54 to 3.73. Quality holds within
measurement noise (Section 5). A stricter threshold 0.20 gives +19.8%.

Terms:

  • verify cycle: one target forward pass that checks a block of draft tokens.
  • tokens/cycle: committed tokens divided by verify cycles; higher is faster.
  • delta, eps, H: the acceptance floor is min(eps, delta*exp(-H)), where H is the
    entropy of the target row.
  • tok/s: decode tokens per second.

1. Chart

Decode tok/s by threshold, three seeds

Three arms at the 16,384 / 1,024 cell: exact (lane off), threshold 0.09, threshold
0.20. The dashed line is the 100 tok/s target. Threshold 0.09 clears it; threshold
0.20 is a stricter setting between exact and 0.09. The full data is in
docs/perf/pr391-typical-acceptance.md. (Replace 4c963f8829a5f3403bd9ac26650fa1719cd36a7e with the pushed SHA.)


2. How it works, in plain terms

The exact rule verifies each draft token with a rejection coin. It accepts a draft
token x with probability min(1, p(x)/q(x)), where p is the target row and
q is the draft row. On a rejection it resamples that position from the residual
(p - q)+ and ends the block. This reproduces the target distribution exactly.

Typical acceptance changes the accept test. It accepts a draft token when the
token is "typical" under the target's own distribution:

p(x) > min(eps, delta * exp(-H(p)))

It accepts the longest run of typical positions in the block. At the first
position that is not typical, it resamples that one position from the target row
p itself (not from the residual) and ends the block. The per-position test uses
no coin; it is deterministic.

The floor adapts to the target's entropy. A confident target (low H) has a high
floor, so only a strong token is typical. An uncertain target (high H) has a low
floor, so more tokens are typical. This accepts more tokens per cycle than the
exact coin, which is where the speed comes from, and why the emitted stream no
longer matches the target distribution. At temperature 0 the argmax is always
typical, so the lane is a no-op there and engages only at temperature > 0.

Reference: Cai, Li, Geng, Peng, Lee, Chen, Dao, "Medusa: Simple LLM Inference
Acceleration Framework with Multiple Decoding Heads," 2024, arXiv:2401.10774,
Section 2.3.1 "Typical Acceptance" (criterion
p_original(x) > min(epsilon, delta * exp(-H(p_original)))). Medusa adapts the
typicality idea from Hewitt, Manning, Liang, "Truncation Sampling as Language
Model Desmoothing," 2022, arXiv:2210.15191.


3. What to merge

One lane, one knob, default off. The threshold is the operator control; it maps to
delta. GET /health reports the resolved state under typical_acceptance, and
each active request prints a [typical-accept] NOT distribution-exact; ... line.

Optimization Knob Class Default Effect at 16,384 tokens Files
Medusa-2 typical acceptance: accept the longest prefix of draft positions that are typical under the target row p(x) > min(eps, delta*exp(-H)), resample the first atypical position from the target row --typical-threshold / MTPLX_FABLE_TYPICAL_THRESHOLD (maps to delta) NOT distribution-exact; gated on task quality off (unset/0 = exact) +32.0% decode at 0.09 (clears 100 tok/s); +19.8% at 0.20; quality within noise mtplx/sampling.py, mtplx/generation.py, mtplx/server/openai.py; tests/test_typical_acceptance.py, tests/test_typical_acceptance_defaults_cpu.py, tests/test_typical_threshold_cli_health_cpu.py, docs/perf/pr391-typical-acceptance.md

Notes on this table:

  • Default off, never armed by a served pack. The threshold is unset by
    default, so the exact law runs. The lane is not part of the served Flash-Next
    default stack; a served pack does not turn it on. A CPU test asserts the served
    default config leaves it off.
  • --typical-threshold maps to delta; higher = stricter. A higher threshold
    raises the entropy floor, so fewer positions are typical and the arm sits closer
    to the exact rule. Unset or 0 is the exact law. As the threshold grows very
    large, every position is resampled from the target row and each cycle commits
    one exact token.
  • eps is an advanced cap (MTPLX_FABLE_TYPICAL_EPS, default 1.0). With
    eps = 1.0 the cap never binds for any delta <= 1, so it is inert at the operating
    points here. A CPU test pins that the delta=0.09 numerics are identical under the
    old eps=0.3 and the new eps=1.0. Most operators never touch eps.
  • Harness change kept in the PR. scripts/code_eval_gate.py gains an opt-in
    --save-completions sidecar (task_id + raw completion + extracted solution).
    Without the flag the scored report is byte-for-byte the same. This is how the
    HumanEval+ column below was produced offline on the exact same completions, the
    reproducibility the PR-391 quality screen needs on-box.

4. How to enable

The lane is off unless you set the threshold. Any of:

  • Server flag: --typical-threshold 0.09
  • Environment: MTPLX_FABLE_TYPICAL_THRESHOLD=0.09

Set it to 0 (or leave it unset) to keep the exact law. Raise it (for example
0.20) for a stricter, closer-to-exact setting. It engages only at
temperature > 0.


5. Benchmark method and results

Setting Value
Model Youssofal/Qwen3.8-Flash-Next-MTPLX-Optimized-Speed, revision 29ba90f82124961d0d902a9ea9bbb1034972af2f
Engine MTPLX 2.10 on MLX 0.32.2, served from this branch
Prompt / output 16,384 templated tokens / 1,024 tokens
Sampler temperature 1, top-p 0.95, top-k 20
Reasoning / MTP depth xhigh / 3
Seeds 20260829, 20260830, 20260831
Memory cap / thermal 100 GiB / fans max, 40 C gate before each cell

Column sources: HE and HE+ are evalplus 0.3.1 greedy (the tool the PR-391 quality
screen used); MBPP and sampled HE are the repo gate. Each is internally consistent
across arms.

threshold decode tok/s tokens/cycle HE (evalplus) HE+ (evalplus) MBPP (repo) sampled HE (repo)
exact (off) 81.78 2.54 0.9329 0.9146 0.9016 0.9329
0.09 107.95 3.73 0.9329 0.9146 0.9016 0.9268
0.20 97.99 3.31 0.9329 0.9146 0.9016 0.9390

Prefill (~1329 tok/s), TTFT (~12.5 s) and peak memory (< 100 GiB) are flat across
arms: the lane touches only the decode accept decision.

6. Quality

Greedy HumanEval, HumanEval+ and MBPP are byte-identical across all three
arms
. At temperature 0 the lane is a no-op (the argmax is always typical), so
greedy completions match exact. This is verified on the base tests and on the
extended HumanEval+ tests.

Sampled HumanEval moves by at most one or two tasks of 164, and the repo gate and
evalplus disagree on the direction (evalplus sampled HE+: exact 0.8963, 0.09
0.9207, 0.20 0.9268). The sampled effect is therefore within n=164 sampling and
harness noise, neither a gain nor a crater. On this battery, typical acceptance
preserves code-evaluation quality within measurement noise at both thresholds.

That is the trade: more tokens per cycle for a token stream that is not
distribution-exact, with a quality cost that stays inside noise here. The default
is the exact law; the threshold is how an operator opts in.

🤖 Generated with Claude Code

davidtai and others added 3 commits September 3, 2026 09:10
Add the Qwen3.8 Flash-Next serving stack: 20 runtime optimizations, the
server code that arms them for the served model, and the tests. Three
engines ran one battery with identical request bodies per cell, 1,024
output tokens, temperature 1, top-p 0.95, top-k 20 and cold prefill.

  Cell  Metric          upstream 2.10.2     branch   mlx-serve 26.8.11
  16K   decode tok/s              57.65      80.92               65.73
  16K   prefill tok/s             1,191      1,332               1,507
  16K   TTFT s                   13.972     12.495              10.914
  255K  decode tok/s              45.86      65.53               35.15
  255K  prefill tok/s               952      1,189                 437
  255K  TTFT s                  275.476    220.629             597.681

Decode optimizations, in merge order:

  MTPLX_QWEN4_FIXED_M4_VERIFY               compiled fixed-M4 verifier
  MTPLX_QWEN4_M4_STAGE3                     fused MoE combine tail
  MTPLX_QSA_M4_FUSED_KV_GATHER              fused QSA key/value gather
  MTPLX_QWEN4_M4_ROUTED_DOWN_REDUCE         routed-down reduction
  MTPLX_QWEN4_M4_ROUTED_DOWN_RESIDUAL_TAIL  reduction plus residual tail
  MTPLX_QWEN4_M4_ROUTED_GLU                 paired gate and up producer
  MTPLX_BATCH_TARGET_ARRAYS                 batched target distributions
  MTPLX_FRSPEC_DRAFT, MTPLX_FRSPEC_VOCAB    FR-Spec ranked draft head
  MTPLX_QWEN4_COMPILED_MTP_PREPARE          compiled draft preparation
  MTPLX_FABLE_HC_M4                         hyper-connection read kernel
  MTPLX_FABLE_OPDIET                        verify-graph operation diet
  MTPLX_FABLE_BLOCK_VERIFY                  block verification
  MTPLX_FABLE_ROUTE_KERNEL                  single-launch route kernel
  MTPLX_FABLE_DRAFT_K20_PRESCATTER          draft top-20 pre-scatter
  MTPLX_FABLE_GRAPH_BUILD_OVERLAP           verify-graph build overlap
  MTPLX_FABLE_VERIFY_GLUE                   rotary and indexer glue
  MTPLX_FABLE_QSA_SPARSE_DECODE             split-K sparse attention

Prefill optimizations, in merge order:

  MTPLX_FABLE_PLE_PREFILL_LOOKAHEAD         n-gram row lookahead
  MTPLX_FABLE_PLE_FIRST_GATHER_EARLY        early first-chunk gather
  MTPLX_PREFILL_CHUNK_SIZE                  chunk size 4,096
  MTPLX_QSA_PREFILL_COMPILE_ROWS            compiled indexer rows 4,096
  MTPLX_FABLE_PREFILL_QSA_QUERY_TILE        query tile of 2,048 rows
  MTPLX_GDN_BLOCKED_PREFILL                 GDN blocked prefill kernel
  MTPLX_FABLE_PREFILL_MASK_FUSE             causal fused attention
  MTPLX_SESSION_BANK_MAX_BYTES              session bank budget 8 GiB

Serving and install:

  --disable-optimization, MTPLX_FABLE_DISABLE   per-key and per-lane opt-out
  fable_install_receipts                        install verdicts on /health
  --ngram-prewarm                               n-gram table pre-read
  pcg64_tape                                    exact uniform random tape

The server arms all 26 keys above by default for the served Flash-Next
model. Set a key to 0, set MTPLX_FABLE_DISABLE, or pass
--disable-optimization <lane>|all to turn one or all of them off. The
three MTPLX_QWEN4_M4_ROUTED_* keys and the route kernel are one chain on
the route_kernel lane; turn the 4 off together.

Quality: HumanEval pass@1 0.951 (156/164) and HumanEval+ 0.933 (153/164)
with the full set, against 0.933 (153/164) and 0.921 (151/164) with every
key off and the same pair on upstream 2.10.2, greedy over all 164
problems.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NH95J2BGTyDX76P6EyYEGS
…ync PLE, pooled-key rowsel)

Adds two decode lanes on top of the Flash-Next stack, both armed by default
for a served Flash-Next pack with per-lane opt-out, an install verdict line,
and a /health entry (aux_lane_defaults).

- 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; at most
  two pending tickets; a failed submission marks the installation unhealthy.
- qsa_pooled_rowsel: the twelve QSA indexers' pooled-key preparation binds the
  pool kernel metadata once per indexer at construction and shares one inv_freq
  object; the pooled block count derives from the write width.

Measured at the 16,384-prompt / 1,024-output cell, three seeds, one build:
both lanes on 81.75 tok/s decode vs 80.47 with both off (+1.59%), reasoning
and text digests identical on every seed, prefill / peak / TTFT flat. ABBA
retest of each lane alone: cached PLE +1.54% (separates from the 1.5%
window noise), pooled rowsel +1.03% (inside noise). Data appendix and chart in
docs/perf/pr391-aux-lanes.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C5oDbNTShwy1QS9dT8VDQD
Adds an opt-in acceptance law for the native MTP verify on Qwen3.8 Flash-Next.
Unset, the verify keeps the exact rejection-sampling rule and the output stream
is unchanged. With --typical-threshold (env MTPLX_FABLE_TYPICAL_THRESHOLD), a
draft token is accepted when it is typical under the target distribution,
p(x) > min(eps, delta * exp(-H(p))); the longest typical prefix is kept and the
first atypical position is resampled from the target. Higher thresholds are
stricter. This is not distribution-exact; quality is gated by task evals.

Measured at the 16,384-prompt / 1,024-output cell, three seeds, one build:
exact 81.78 tok/s; threshold 0.2 97.99 (+19.8%); threshold 0.09 107.95
(+32.0%). Greedy HumanEval, HumanEval+, and MBPP are byte-identical across the
dial; sampled HumanEval moves within one to two tasks of 164 in either
direction depending on harness. Data appendix and chart in
docs/perf/pr391-typical-acceptance.md. The code-eval gate gains an opt-in
--save-completions sidecar so HumanEval+ can be scored offline.

Typical acceptance follows Cai et al., "Medusa: Simple LLM Inference
Acceleration Framework with Multiple Decoding Heads", 2024, arXiv:2401.10774.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@davidtai
davidtai requested a review from youssofal as a code owner September 7, 2026 06:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant