Skip to content

metal: gate the M3-class fusion whitelists on the pre-M5 predicate - #770

Open
kk1987 wants to merge 1 commit into
antirez:mainfrom
kk1987:metal-m4-fusion-whitelist
Open

metal: gate the M3-class fusion whitelists on the pre-M5 predicate#770
kk1987 wants to merge 1 commit into
antirez:mainfrom
kk1987:metal-m4-fusion-whitelist

Conversation

@kk1987

@kk1987 kk1987 commented Aug 10, 2026

Copy link
Copy Markdown

Fixes #769.

The exact-fusion campaign's newest decode ports gate on ds4_gpu_ported_m5_decode_feature_enabled(), whose pre-M5 predicate already admits M1–M4. The older fusion sites still used literal device_name_contains("M3") || ("M5") whitelists, so pre-M5 devices other than M3 silently skipped fifteen eligible fusions, and ds4_test failed its 29 forced-fusion exactness assertions there because the entry points refuse to run at all.

This switches those fifteen sites to the same predicates the ported gate uses: is_pre_m5 || is_m5 where M5 was admitted, plain is_pre_m5 for the M3-only sites (those screened negative on M5, so M5 stays out). Net −15 lines. Left untouched: the Metal 4 tensor API sites and the M5 private-scratch heuristic (pre-M5 hardware lacks them), and the router SIMD sites (already on the predicate). ds4.c needs nothing.

Verified on M4 Max 128 GiB, DS4F 0731 IQ2XXS resident:

  • make test goes from 29 failures to fully green, including the bit-exact fused-vs-reference comparisons for both previously failing kernels.
  • Per-fusion A/B with metal_decode_schedule_bench / metal_prefill_variant_bench, 3 samples per fusion, cool-down rounds, medians; every run bit-exact over the full vocabulary with identical token selection:
fusion side median
zero-prefix prefill mask cache prefill +4.5%
inplace/affine RoPE pair decode +3.8%
gathered KV stage decode +1.8%
persistent zero attention mask decode +0.8%
router weights batch prefill +0.8%
remaining nine both neutral (±0.15%)
  • Full ds4-bench curves (ABBA, this branch vs main, 32 ctx points): median generation +1.55%, prefill +0.7%.

On M1 Ultra 128 GB, an equivalent generalization measured roughly 23.5 → 25 tok/s generation on an empty context (thanks @lmr — see comments). M2 has no tester, but every site keeps its DS4_METAL_DISABLE_* rollback and the forced-fusion test assertions now cover any device the predicate admits.

A benchmarking note: on a MacBook the numbers drift heavily once thermal throttling kicks in — enough that two prefill deltas came out with the wrong sign on back-to-back hot runs. The numbers above are cool-down multi-round medians.

🤖 Generated with Claude Code

@lmr

lmr commented Aug 12, 2026

Copy link
Copy Markdown

Just as a comment, I made a more generalized version of your change to admit any pre-M5 hardware to these M3-class fusion whitelists and it seems to work well on my M1 Ultra 128GB. I would have to get the precise benchmark numbers to validate it, but I am getting around 25 tops of generation on an empty context, which is better than I had before (23.5 on an empty context, consistent with what another M1 Ultra user reported in #706).

@lmr

lmr commented Aug 12, 2026

Copy link
Copy Markdown

So I can test a more generalized version of your PR in case you want to make it (or leave it like this since we don't have an M2 user to verify it on that hardware too).

The exact-fusion campaign's newest decode ports gate on
ds4_gpu_ported_m5_decode_feature_enabled(), whose pre-M5 predicate
admits M1-M4. The older fusion sites still used literal M3/M5
device-name whitelists, so pre-M5 devices other than M3 silently
skipped fifteen eligible fusions and ds4_test failed its 29
forced-fusion exactness assertions there (pair compressor store,
gathered KV staging) because the entry points refuse to run at all.

Switch the fifteen sites to the same predicates the ported gate
uses: is_pre_m5 || is_m5 where M5 was admitted, plain is_pre_m5 for
the M3-only sites (those screened negative on M5, so M5 stays out).
Metal 4 tensor API sites and the M5 private scratch heuristic are
unchanged (pre-M5 hardware lacks them); the router SIMD sites
already use the predicate; ds4.c needs nothing.

Verified on M4 Max 128 GiB with the DS4F 0731 IQ2XXS GGUF: ds4_test
goes from 29 failures to fully green, per-fusion A/B medians reach
+3.8% decode (RoPE pair) and +4.5% prefill (zero-prefix mask cache)
with every run bit-exact, and the full ds4-bench curve improves
+1.55% median generation over 2K-64K. An equivalent generalization
measured ~23.5 -> 25 tok/s empty-context generation on M1 Ultra
128 GB (reported by lmr on the PR).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H58vqtiLFWWWAbcB7oHAp8
@kk1987
kk1987 force-pushed the metal-m4-fusion-whitelist branch from 7ed2b8f to dfea3a7 Compare August 13, 2026 06:29
@kk1987 kk1987 changed the title metal: admit M4 to the M3-class fusion whitelists metal: gate the M3-class fusion whitelists on the pre-M5 predicate Aug 13, 2026
@kk1987

kk1987 commented Aug 13, 2026

Copy link
Copy Markdown
Author

Nice, thanks for testing! That M1 Ultra bump matches what I'd hope for — the RoPE pair fusions were the biggest single win on M4 too.

I've switched the PR to the pre-M5 predicate: is_pre_m5 || M5-name for the sites that admitted M3/M5, plain is_pre_m5 for the M3-only ones (those are the fusions that screened negative on M5, so M5 stays out). Nobody can verify M2, but every site keeps its DS4_METAL_DISABLE_* rollback and ds4_test's forced-fusion assertions now cover any device the whitelist admits, so an M2 user would find out quickly and can opt out per fusion.

If you want precise M1 numbers, the harness I used: ./speed-bench/metal_decode_schedule_bench -m model.gguf --candidate-env DS4_METAL_DISABLE_INPLACE_ROPE_PAIR --include-selection (same for the other DISABLE names) — it aborts unless both variants are bit-identical. One caveat from my runs: on my MacBook the numbers drift heavily once thermal throttling kicks in, enough that two prefill deltas came out with the wrong sign on back-to-back hot runs. Letting the machine cool down before each run and taking the median of 3 runs was what it took to get numbers that were stable run-to-run.

@chilang

chilang commented Aug 18, 2026

Copy link
Copy Markdown

Independent replication on a second M4 Max (128 GiB, DS4F 0731 IQ2XXS resident). I measured these before finding this PR, so the numbers are uncoordinated.

Applies clean to current main 84cc882, 169 commits after it was opened. On that tree: make clean, make cpu clean, ./ds4_test --metal-kernels OK, full ./ds4_test exit 0 (ds4 tests: ok). Reverting it reproduces the 29 assertion failures from #769.

Per-fusion A/B with metal_decode_schedule_bench --include-selection, control = fused:

candidate-env control t/s candidate t/s delta tokens
DISABLE_INPLACE_ROPE_PAIR 35.0378 33.8086 +3.64% 1024
DISABLE_AFFINE_ROPE_PAIR 28.7764 27.6956 +3.90% 1024
DISABLE_GATHERED_KV_STAGE 21.8532 21.5448 +1.43% 1024
DISABLE_GATHERED_KV_STAGE 32.7534 32.3385 +1.28% 512
DISABLE_COMPRESSOR_PAIR_PROJ 25.2436 25.2255 +0.07% 1024
DISABLE_COMPRESSOR_PAIR_PROJ 33.8912 33.9329 -0.12% 512
DISABLE_PERSISTENT_ZERO_ATTN_MASK 19.9442 19.8918 +0.26% 1024

Every run bit-exact: exact_rows=1041 exact_floats=134580480 exact_selected_ids=1040 vocab=129280.

Two notes on reading those rows:

  • INPLACE and AFFINE both gate decode_attn_rope_fuse_available and kv_rope_fp8_fuse_available, so those two rows measure the same group twice rather than summing. The agreement between them is the useful part.
  • My +0.26% on the persistent zero mask is heat-soak, not a disagreement with your +0.8%. It was the third of three back-to-back runs, with absolute decode falling 35.0 -> 28.8 -> 19.9 t/s across them. Same drift you documented.

I had first patched this by adding device_name_contains("M4") alongside the M3 sites. The predicate version here is better: it covers M1/M2 too and removes lines instead of adding them.

ryan5rdx added a commit to ryan5rdx/ds4 that referenced this pull request Aug 24, 2026
…irez#770

Cherry-picks the hunks of antirez#770 (kk1987) that this branch had not
already covered. That PR fixes the same defect found here independently -
literal ds4_gpu_device_name_contains("M3")/("M5") whitelists where the
surrounding port used ds4_gpu_device_is_pre_m5_apple_silicon() - and
reports the same 29 forced-fusion exactness assertions failing, which is
the count this branch saw go 29 -> 23 -> 0 across ba132ba and bb576ed.

Of its fifteen sites, eight were already converted here (01a56db x4,
ba132ba x2, bb576ed, 36d2758). Every one of the remaining seven is a
prefill or batch path except shared kvpad, which is deliberately left out
below. Six are taken here:

  2012   zero-prefix prefill mask cache      +4.5% prefill on M4 Max
  20623  HC rms-scale-project, n_rows > 8
  21869  compressor score + APE add
  22677  concat f32 dim1 / ratio-4 pack
  22723  ratio-4 direct pool
  32096  router select batch weights, n_tokens > 1   +0.8% prefill

This branch's decode audit had already classified all six as prefill-only
and skipped them for that reason, so no decode change is expected; they are
here for the prefill side, which nobody had swept.

Taken as upstream wrote them, replacing the name test outright rather than
adding an or-arm as the earlier commits here did, so the tree converges
with antirez#770 if it merges. The eight sites already converted are left in their
existing form: they are measured and working, and rewriting them for
cosmetic convergence would risk a regression for no gain.

Not applied: 27563, shared kvpad. It removes no dispatch, and ba132ba
records the reason for leaving it M3-only - shared_pad stays 0 so the fused
stage writes both the K and V pad copies, matching the standalone pad
kernel. Upstream measured it in its neutral bucket, so there is nothing to
gain against a layout hazard nobody has tested on Apple8.

ds4_test --metal-kernels stays OK. Prefill effect unmeasured on this
hardware; every site keeps its own DS4_METAL_DISABLE_* switch.
ryan5rdx added a commit to ryan5rdx/ds4 that referenced this pull request Sep 9, 2026
…irez#770

Cherry-picks the hunks of antirez#770 (kk1987) that this branch had not
already covered. That PR fixes the same defect found here independently -
literal ds4_gpu_device_name_contains("M3")/("M5") whitelists where the
surrounding port used ds4_gpu_device_is_pre_m5_apple_silicon() - and
reports the same 29 forced-fusion exactness assertions failing, which is
the count this branch saw go 29 -> 23 -> 0 across ba132ba and bb576ed.

Of its fifteen sites, eight were already converted here (01a56db x4,
ba132ba x2, bb576ed, 36d2758). Every one of the remaining seven is a
prefill or batch path except shared kvpad, which is deliberately left out
below. Six are taken here:

  2012   zero-prefix prefill mask cache      +4.5% prefill on M4 Max
  20623  HC rms-scale-project, n_rows > 8
  21869  compressor score + APE add
  22677  concat f32 dim1 / ratio-4 pack
  22723  ratio-4 direct pool
  32096  router select batch weights, n_tokens > 1   +0.8% prefill

This branch's decode audit had already classified all six as prefill-only
and skipped them for that reason, so no decode change is expected; they are
here for the prefill side, which nobody had swept.

Taken as upstream wrote them, replacing the name test outright rather than
adding an or-arm as the earlier commits here did, so the tree converges
with antirez#770 if it merges. The eight sites already converted are left in their
existing form: they are measured and working, and rewriting them for
cosmetic convergence would risk a regression for no gain.

Not applied: 27563, shared kvpad. It removes no dispatch, and ba132ba
records the reason for leaving it M3-only - shared_pad stays 0 so the fused
stage writes both the K and V pad copies, matching the standalone pad
kernel. Upstream measured it in its neutral bucket, so there is nothing to
gain against a layout hazard nobody has tested on Apple8.

ds4_test --metal-kernels stays OK. Prefill effect unmeasured on this
hardware; every site keeps its own DS4_METAL_DISABLE_* switch.
ryan5rdx added a commit to ryan5rdx/ds4 that referenced this pull request Sep 9, 2026
…irez#770

Cherry-picks the hunks of antirez#770 (kk1987) that this branch had not
already covered. That PR fixes the same defect found here independently -
literal ds4_gpu_device_name_contains("M3")/("M5") whitelists where the
surrounding port used ds4_gpu_device_is_pre_m5_apple_silicon() - and
reports the same 29 forced-fusion exactness assertions failing, which is
the count this branch saw go 29 -> 23 -> 0 across ba132ba and bb576ed.

Of its fifteen sites, eight were already converted here (01a56db x4,
ba132ba x2, bb576ed, 36d2758). Every one of the remaining seven is a
prefill or batch path except shared kvpad, which is deliberately left out
below. Six are taken here:

  2012   zero-prefix prefill mask cache      +4.5% prefill on M4 Max
  20623  HC rms-scale-project, n_rows > 8
  21869  compressor score + APE add
  22677  concat f32 dim1 / ratio-4 pack
  22723  ratio-4 direct pool
  32096  router select batch weights, n_tokens > 1   +0.8% prefill

This branch's decode audit had already classified all six as prefill-only
and skipped them for that reason, so no decode change is expected; they are
here for the prefill side, which nobody had swept.

Taken as upstream wrote them, replacing the name test outright rather than
adding an or-arm as the earlier commits here did, so the tree converges
with antirez#770 if it merges. The eight sites already converted are left in their
existing form: they are measured and working, and rewriting them for
cosmetic convergence would risk a regression for no gain.

Not applied: 27563, shared kvpad. It removes no dispatch, and ba132ba
records the reason for leaving it M3-only - shared_pad stays 0 so the fused
stage writes both the K and V pad copies, matching the standalone pad
kernel. Upstream measured it in its neutral bucket, so there is nothing to
gain against a layout hazard nobody has tested on Apple8.

ds4_test --metal-kernels stays OK. Prefill effect unmeasured on this
hardware; every site keeps its own DS4_METAL_DISABLE_* switch.
ryan5rdx added a commit to ryan5rdx/ds4 that referenced this pull request Sep 9, 2026
…irez#770

Cherry-picks the hunks of antirez#770 (kk1987) that this branch had not
already covered. That PR fixes the same defect found here independently -
literal ds4_gpu_device_name_contains("M3")/("M5") whitelists where the
surrounding port used ds4_gpu_device_is_pre_m5_apple_silicon() - and
reports the same 29 forced-fusion exactness assertions failing, which is
the count this branch saw go 29 -> 23 -> 0 across ba132ba and bb576ed.

Of its fifteen sites, eight were already converted here (01a56db x4,
ba132ba x2, bb576ed, 36d2758). Every one of the remaining seven is a
prefill or batch path except shared kvpad, which is deliberately left out
below. Six are taken here:

  2012   zero-prefix prefill mask cache      +4.5% prefill on M4 Max
  20623  HC rms-scale-project, n_rows > 8
  21869  compressor score + APE add
  22677  concat f32 dim1 / ratio-4 pack
  22723  ratio-4 direct pool
  32096  router select batch weights, n_tokens > 1   +0.8% prefill

This branch's decode audit had already classified all six as prefill-only
and skipped them for that reason, so no decode change is expected; they are
here for the prefill side, which nobody had swept.

Taken as upstream wrote them, replacing the name test outright rather than
adding an or-arm as the earlier commits here did, so the tree converges
with antirez#770 if it merges. The eight sites already converted are left in their
existing form: they are measured and working, and rewriting them for
cosmetic convergence would risk a regression for no gain.

Not applied: 27563, shared kvpad. It removes no dispatch, and ba132ba
records the reason for leaving it M3-only - shared_pad stays 0 so the fused
stage writes both the K and V pad copies, matching the standalone pad
kernel. Upstream measured it in its neutral bucket, so there is nothing to
gain against a layout hazard nobody has tested on Apple8.

ds4_test --metal-kernels stays OK. Prefill effect unmeasured on this
hardware; every site keeps its own DS4_METAL_DISABLE_* switch.
ryan5rdx added a commit to ryan5rdx/ds4 that referenced this pull request Sep 9, 2026
…irez#770

Cherry-picks the hunks of antirez#770 (kk1987) that this branch had not
already covered. That PR fixes the same defect found here independently -
literal ds4_gpu_device_name_contains("M3")/("M5") whitelists where the
surrounding port used ds4_gpu_device_is_pre_m5_apple_silicon() - and
reports the same 29 forced-fusion exactness assertions failing, which is
the count this branch saw go 29 -> 23 -> 0 across ba132ba and bb576ed.

Of its fifteen sites, eight were already converted here (01a56db x4,
ba132ba x2, bb576ed, 36d2758). Every one of the remaining seven is a
prefill or batch path except shared kvpad, which is deliberately left out
below. Six are taken here:

  2012   zero-prefix prefill mask cache      +4.5% prefill on M4 Max
  20623  HC rms-scale-project, n_rows > 8
  21869  compressor score + APE add
  22677  concat f32 dim1 / ratio-4 pack
  22723  ratio-4 direct pool
  32096  router select batch weights, n_tokens > 1   +0.8% prefill

This branch's decode audit had already classified all six as prefill-only
and skipped them for that reason, so no decode change is expected; they are
here for the prefill side, which nobody had swept.

Taken as upstream wrote them, replacing the name test outright rather than
adding an or-arm as the earlier commits here did, so the tree converges
with antirez#770 if it merges. The eight sites already converted are left in their
existing form: they are measured and working, and rewriting them for
cosmetic convergence would risk a regression for no gain.

Not applied: 27563, shared kvpad. It removes no dispatch, and ba132ba
records the reason for leaving it M3-only - shared_pad stays 0 so the fused
stage writes both the K and V pad copies, matching the standalone pad
kernel. Upstream measured it in its neutral bucket, so there is nothing to
gain against a layout hazard nobody has tested on Apple8.

ds4_test --metal-kernels stays OK. Prefill effect unmeasured on this
hardware; every site keeps its own DS4_METAL_DISABLE_* switch.
ryan5rdx added a commit to ryan5rdx/ds4 that referenced this pull request Sep 9, 2026
…irez#770

Cherry-picks the hunks of antirez#770 (kk1987) that this branch had not
already covered. That PR fixes the same defect found here independently -
literal ds4_gpu_device_name_contains("M3")/("M5") whitelists where the
surrounding port used ds4_gpu_device_is_pre_m5_apple_silicon() - and
reports the same 29 forced-fusion exactness assertions failing, which is
the count this branch saw go 29 -> 23 -> 0 across ba132ba and bb576ed.

Of its fifteen sites, eight were already converted here (01a56db x4,
ba132ba x2, bb576ed, 36d2758). Every one of the remaining seven is a
prefill or batch path except shared kvpad, which is deliberately left out
below. Six are taken here:

  2012   zero-prefix prefill mask cache      +4.5% prefill on M4 Max
  20623  HC rms-scale-project, n_rows > 8
  21869  compressor score + APE add
  22677  concat f32 dim1 / ratio-4 pack
  22723  ratio-4 direct pool
  32096  router select batch weights, n_tokens > 1   +0.8% prefill

This branch's decode audit had already classified all six as prefill-only
and skipped them for that reason, so no decode change is expected; they are
here for the prefill side, which nobody had swept.

Taken as upstream wrote them, replacing the name test outright rather than
adding an or-arm as the earlier commits here did, so the tree converges
with antirez#770 if it merges. The eight sites already converted are left in their
existing form: they are measured and working, and rewriting them for
cosmetic convergence would risk a regression for no gain.

Not applied: 27563, shared kvpad. It removes no dispatch, and ba132ba
records the reason for leaving it M3-only - shared_pad stays 0 so the fused
stage writes both the K and V pad copies, matching the standalone pad
kernel. Upstream measured it in its neutral bucket, so there is nothing to
gain against a layout hazard nobody has tested on Apple8.

ds4_test --metal-kernels stays OK. Prefill effect unmeasured on this
hardware; every site keeps its own DS4_METAL_DISABLE_* switch.
ryan5rdx added a commit to ryan5rdx/ds4 that referenced this pull request Sep 9, 2026
…irez#770

Cherry-picks the hunks of antirez#770 (kk1987) that this branch had not
already covered. That PR fixes the same defect found here independently -
literal ds4_gpu_device_name_contains("M3")/("M5") whitelists where the
surrounding port used ds4_gpu_device_is_pre_m5_apple_silicon() - and
reports the same 29 forced-fusion exactness assertions failing, which is
the count this branch saw go 29 -> 23 -> 0 across ba132ba and bb576ed.

Of its fifteen sites, eight were already converted here (01a56db x4,
ba132ba x2, bb576ed, 36d2758). Every one of the remaining seven is a
prefill or batch path except shared kvpad, which is deliberately left out
below. Six are taken here:

  2012   zero-prefix prefill mask cache      +4.5% prefill on M4 Max
  20623  HC rms-scale-project, n_rows > 8
  21869  compressor score + APE add
  22677  concat f32 dim1 / ratio-4 pack
  22723  ratio-4 direct pool
  32096  router select batch weights, n_tokens > 1   +0.8% prefill

This branch's decode audit had already classified all six as prefill-only
and skipped them for that reason, so no decode change is expected; they are
here for the prefill side, which nobody had swept.

Taken as upstream wrote them, replacing the name test outright rather than
adding an or-arm as the earlier commits here did, so the tree converges
with antirez#770 if it merges. The eight sites already converted are left in their
existing form: they are measured and working, and rewriting them for
cosmetic convergence would risk a regression for no gain.

Not applied: 27563, shared kvpad. It removes no dispatch, and ba132ba
records the reason for leaving it M3-only - shared_pad stays 0 so the fused
stage writes both the K and V pad copies, matching the standalone pad
kernel. Upstream measured it in its neutral bucket, so there is nothing to
gain against a layout hazard nobody has tested on Apple8.

ds4_test --metal-kernels stays OK. Prefill effect unmeasured on this
hardware; every site keeps its own DS4_METAL_DISABLE_* switch.
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.

ds4_test fails 29 fusion assertions on M4 — fusion whitelists only admit M3/M5 device names

3 participants