Skip to content

metal: bypass DSpark speculation once a request proves unprofitable - #965

Open
rinaldofesta wants to merge 1 commit into
antirez:mainfrom
rinaldofesta:perf/dspark-metal-lifetime-bypass
Open

metal: bypass DSpark speculation once a request proves unprofitable#965
rinaldofesta wants to merge 1 commit into
antirez:mainfrom
rinaldofesta:perf/dspark-metal-lifetime-bypass

Conversation

@rinaldofesta

Copy link
Copy Markdown
Contributor

On Metal, --dspark is slower than plain decode on text the proposer cannot predict. The README measures 36-44 t/s against 46.5 plain on prose, and I get 35.9 against 40.6 after 8k tokens of Italian prose. The scheduler already pauses and re-probes on a bad window, but it never gives up, so the loss repeats for the whole request.

Change, 19 added and 3 edited lines in ds4.c: track accepted drafts and scheduler cycles for the lifetime of the request. Once two probe windows have run and the lifetime average is below 0.5 accepted drafts per cycle, disable speculation for the rest of the request, the same bypass the gfx1151 fast path already takes. Counters, not timings, so a greedy request follows the same schedule on every run. No new flags. The bypass is sticky for the request, like the gfx1151 one: a response that opens with unpredictable prose and later turns into code keeps plain decode. Reasoning text sits well above the floor, so the agent's usual think-then-edit turns are not affected.

Why 0.5 and not higher: the ratio the rule sees at its first decision point does not rank text the way full-run profit does. On this machine, reasoning with thinking reads 1.25 at the first decision and gains 6.5% from speculation; short English prose reads 1.75 at the first decision, then decays to 0.88 over the run, and loses 8%. A floor that would catch English prose would also catch reasoning. 0.5 catches only the regime where every verified row costs more than it saves, with a 2.5x margin below reasoning. English prose stays a mild loss, out of scope here.

Measured, MacBook Pro M5 Max 128 GB, Metal, DeepSeek V4 Flash 0731 IQ2_XXS/Q2_K, greedy:

Text Ratio at first decision Bypass Before After Plain
Italian prose, 8192-token prefill, 64 gen 0.25 yes, at 2/8 35.9 t/s 38.5 t/s 40.6 t/s
Reasoning with thinking, 384 tokens 1.25 no 47.9 47.95 45.0
Python class, 192 tokens never unprofitable no 54.7 54.6 44.8
English prose, 192 tokens 1.75 no 41.6 41.6 45.0

The reasoning prompt produced byte-identical output across four runs with the bypass on and off. The pre-existing --dspark caveat about batched floating-point order is unchanged; --dspark-strict still gives one-token-decode equivalence.

Validation: make dspark-verify-depth OK with worst_argmax_gap=0.000; make dspark-acceptance OK, c_add accepts 21 drafts with no bypass and 4 of 5 cases match the plain baseline byte for byte, the fifth (hello) being the pre-existing allowed divergence; ./ds4_test --chat-multimodal-text-only, ./ds4_test --server, ./ds4_agent_test OK; Metal build clean. No ds4_test case reaches this branch, since the only DSpark unit test runs with the scheduler disabled; coverage is the fixture plus the measurements above. Not run: CUDA, ROCm, GLM. The rule is gated on the Metal backend, so other backends are untouched.

A first version of this used a second rule that bypassed after one all-miss window plus two environment knobs. The all-miss rule fired on reasoning and cost 3.1 t/s there, so it is gone, and so are the knobs.

Implemented with the help of a coding agent (Claude Code and Codex) and reviewed locally by the author.

On Metal every verified DSpark row re-streams its routed experts, so
speculation on text the proposer cannot predict runs slower than plain
decode. The README measures 36-44 t/s against 46.5 t/s plain on prose.
The scheduler already pauses and re-probes, but it never gives up, so
the loss repeats for the whole request.

Track accepted drafts and scheduler cycles for the lifetime of the
request. Once two probe windows have run and the lifetime average falls
below 0.5 accepted drafts per cycle, disable speculation for the rest of
the request, the same bypass the gfx1151 fast path already takes. The
rule uses counters, not timings, so a greedy request follows the same
schedule on every run.

Measured on a MacBook Pro M5 Max 128 GB, Metal, DeepSeek V4 Flash 0731
IQ2_XXS/Q2_K, greedy, ratios read at the first bypass decision:
8192 tokens of Italian prose 0.25 (bypass fires, decode 35.9 -> 39.2 t/s
against 40.7 plain), reasoning with thinking 1.25 (no bypass, 47.9 t/s
kept), short English prose 1.75 at the first decision (no bypass, stays
a mild loss at 41.6 vs 45.0 plain, out of scope here), Python code never
reaches the decision (54.7 t/s kept). Output stays byte-identical across
scheduler variants on the reasoning prompt; the existing batched
floating-point divergence of --dspark is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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