Skip to content

honeykrisp: M1 compute speedups and the deferred CDM barrier on G13G (stacked on #3, #4, #5) - #6

Open
joshuaswarren wants to merge 67 commits into
omacom:mainfrom
joshuaswarren:honeykrisp-omarchy-v3
Open

joshuaswarren wants to merge 67 commits into
omacom:mainfrom
joshuaswarren:honeykrisp-omarchy-v3

Conversation

@joshuaswarren

@joshuaswarren joshuaswarren commented Oct 1, 2026 •

Copy link
Copy Markdown

Current checkpoint: NOT READY TO MERGE (30 October retained snapshot)

The corrected frozen differential has 938 of 1,367 complete chunk pairs, covering 932,240 of 1,346,778 cases per arm. There are zero recorded regressions, zero invocation errors and 1,942 status differences in the completed rows. Both arms cover the same completed pairs.

The score remains BLOCK / INCOMPLETE. The three errors record the unfinished run and incomplete coverage in each arm. There are 429 chunk pairs left. These counts are provisional evidence, not qualification or a final pass.

Source bindings remain control 59c25f62c9d9b3077160b180ee294612df3d4de7 and candidate 413636ccd83578a8ec9020532d16552a5a8a3551. Both arms contain the older #8, and only the candidate adds this PR. Each pair uses the same boot. The CTS binary SHA256 is 550d04d3bc2b2164c3a74ae5f386f287a97bc2e3c2a311756838c994e9b50744. The checkpoint JSON SHA256 is a396e23f274bd47dd4094c7ce967089ac0b87cd7c51fc85aaee018a38d2db299.

This is a differential of those frozen arms, not qualification of a future stack with the corrected #8. The planned confirmation on the corrected, restacked tips is fixed at 86 chunk pairs and 83,899 cases, limited to placed and mapped memory. It follows device qualification of #8. That confirmation has not run or passed. It is not a whole-run repeat.

Landing order and the current review and device holds remain unchanged.

Earlier dated status and evidence

NOT READY TO MERGE

Head: 9323171c12c5aa6055f1031ea26c16c247191987.

Qualification correction — 29 October

I withdraw the earlier 366-chunk / 361,849-case, no-Pass-to-Fail claim as qualification of this PR. Its control included #8, #9 and #10, while the candidate was PR #6 alone. That comparison changed more than #6 and does not isolate this change. CPU compiler tests and source review are not device qualification.

The corrected comparison keeps the same prerequisite stack in both arms:

The controlled run is incomplete. Its selected scope is 1,367 chunk pairs / 1,346,778 cases, not the full CTS. Completion counts are not a pass: every pair's gate JSON, driver identity and same-boot binding still need to be scored. No final corrected pass count or no-regression verdict is claimed here.

Valid provisional evidence is the matched prerequisite-stack check, not a result for #6, and the source-only review at this head, not GPU qualification. The old whole-suite results for 518212284e do not qualify the current head. The three shader-object timing regressions remain unresolved, not waived.

Landing order remains #10, #8, #9, then #6. No merge is requested until the controlled device gates and remaining coverage/hold decisions are complete.

Full correction: #6 (comment)

Corrected-run provisional snapshot

The retained gate, last written at 2026-10-29T15:35:02.470819+00:00 and read at 2026-10-29T20:08:08.547446+00:00, has 669 of 1,367 chunk pairs complete: 665,231 of 1,346,778 selected cases have results in each arm. It records 1,912 status differences, zero regressions and zero invocation errors in those completed rows. The overall gate is BLOCK / INCOMPLETE, solely reporting unfinished coverage; this is not a completed no-regression result. The latest individual 1,000-case chunk is ALLOW, which does not authorize the whole run. There is no final result yet.

The source ancestry was checked: the control contains the exact #10, #8 and #9 heads, and the candidate contains both that control and #6 at 9323171c12c5aa6055f1031ea26c16c247191987. The CTS ELF SHA-256 is 550d04d3bc2b2164c3a74ae5f386f287a97bc2e3c2a311756838c994e9b50744; its recorded source commit has not been independently verified.

Historical source and test notes (not current-head qualification)

Status: NOT READY TO MERGE (source head f144c7949b7d, native and package builds passed 48 of 48 targeted cases, wide suite pending; wide CTS build 69bde153405c; diagnostic tail build 594460915d6a)

The wide Vulkan CTS found three problems in the previous head of this PR (8b217d2f67cc). All three came from the dependency-tracked compute barrier. Those three fixes were measured on build 69bde153405c (see the table). The completed wide CTS on that build also found 55 regressions. The later barrier-word fix 5c8d6b910379 was rerun on the 48 known cases: 48 of 48 Fail, with 0 new GPU timeout lines. Both later attempts, the barrier-word change and a query-copy batch-closure change, are reverted in this branch. The current source head f144c7949b7d keeps a compute batch open across a barrier only when it is the tail stream. A native build of it (library SHA-256 prefix cfa40cace1eb8dfb, with a src/ tree identical to 594460915d6a) passed the 48 known correctness cases, 48 of 48, on the 16-inch M1 Max. The stored control and a control rebuilt from main also passed 48 of 48, and the GPU timeout delta was 0. A package build of the same head (library SHA-256 prefix 4874d533a942e052, byte-identical to the staged package library) passed the same 48, 48 of 48. A package build of main as the control also passed 48 of 48, and the GPU timeout delta was 0. That is the first result on the bytes that would be installed. The wide suite on the package pair is queued and has no results yet. A diagnostic build of a related change, 594460915d6a (69bde153405c plus one hunk in hk_cmd_buffer.c), passed the 48 known correctness cases, 48 of 48, on the 16-inch M1 Max. A control rebuilt from main also passed 48 of 48, and the GPU timeout delta was 0. That build is not the head, and no wide suite has run on it. The seven timing cases have been rerun, with the sequences below; none is waived.

More checks ran on all three machines. The 8 MLX suites, recovery after a refusal and sparse_resources are done on the 13-inch M1 and the 16-inch M1 Max; the 8 MLX suites are done on the M2 Max. The wide CTS on the 13-inch M1 is complete: all 20 of 20 parts finished, 0 wedge-stopped, 3,284 of 3,284 chunks complete in all arms, 3,253,584 cases with a result in both arms, 1,958 status differences, 0 GPU timeout lines this boot. The wide CTS on the 16-inch M1 Max is complete: all 20 of 20 parts finished, 3,284 of 3,284 chunks complete in both arms, 3,253,584 cases compared and 2,037 status differences. Of those, 1,951 are NotSupported to Pass, 31 Fail to Pass, 53 Pass to Fail and 2 QualityWarning to Fail. Those 55 regressions were recorded on the wide CTS build. On the later combined build, the 42 multiview cases passed the targeted run and the six renderpasses cases still failed. The seven timing cases are not waived. This PR stays NOT READY.

  1. Image copies, 16-inch M1 Max. 12,944 api.copy_and_blit image-copy tests passed on the base branch and failed on the previous head. The groups are core.image_to_image 10,088, copy_commands2.image_to_image 1,420, dedicated_allocation.image_to_image 1,420 and buffer_to_image 16. A bisect over the 54 commits the previous head had, with exact-sha builds, names the tracked-barrier commit as the first bad one. An image copy launches one dispatch per region, and the app declares no barrier between them. The fix is inside that commit.
  2. Sparse-image atomics, 16-inch M1 Max. Inside a 141-case window of the image group, three image.atomic_operations ... sparse_img tests failed and the GPU timed out. The first bad commit is the same one. That commit emits a reduced barrier word on G13X. A device that has created a sparse image or buffer now keeps the full word. The fix is inside that commit.
  3. Robustness, both M1 chips. robustness.index_access.draw_indexed_pipeline_robustness_2_vert_tess_frag failed on the M1 Max. On the 13-inch M1, the tessellation draw_indexed_indirect_count cases hung the GPU in a 100-case chunk (Job timed out on the DRM scheduler). Of eight settings tried on the failing case with the build 02765a81b1a5 (the previous head's fixes without this last one), only those that include HK_PERFTEST=alwayscdmbarrier cured it. A command buffer that records rendering, a copy, or a precomp that feeds a draw now flushes every launch. That is one new commit near the top of the series.

This block changes to READY TO MERGE only when every number below names the final sha and the regressions are resolved. The current source head is f144c7949b7d960f26677b671341d5a123057ddf; its native and package builds passed the 48 targeted cases, but nothing else below, including the wide suite, was measured on it. The wide CTS builds in the table are 69bde153405cf02d52b0c9810307a0c7d0085d52 (tree d4272dcddc2842ca775258da7fae307960262e65), not the current source head. Two earlier targeted builds were reverted: 5c8d6b9103798916692efa0b80c675b507d2b5d3 (48 of 48 Fail) and a5a7723d7c3b (42 Pass, 6 Fail). A diagnostic build, 594460915d6ac9da1249a5075e10c6c316063900, passed 48 of 48 targeted cases. It is not the head. These results do not qualify the source head.

What this PR is

One pull request with everything: 61 commits on top of main, 62 files, +4950/-199. It replaces #3, #4 and #5 (see "Merge" below).

  • Cooperative matrices on the M1 family, in hardware (8x8x8 fp32 matrix multiply-add), with shapes, software fallback and bf16.
  • Compute speedups on M1: paired fragment loads, constant address folding, no register-pressure scheduler for compute, load hoisting.
  • A deferred or dependency-tracked compute barrier, chosen per chip. Command buffers that record rendering or copies flush every launch.
  • Correctly rounded fp32 division, faithful log, exact sin/cos reduction, and a hardware reciprocal for the edge cases.
  • Memory: the device heap defaults to 90% of system memory, with a free-memory reserve so a large allocation is refused instead of exhausting the machine; the address window is used in full; the buffer-object cache is given back before the reserve refuses.
  • Image texels are made available after a write and visible before a read (spirv:).

Results (each line names the sha it was measured on)

What Chips Head measured Result
Compiles from the exact source archive 16-inch M1 Max 69bde153405c yes, 65 seconds (setup, compile and install); the driver reports git-69bde15340 (Mesa 26.3.0-devel)
The 24 sampled image-copy tests that failed on the previous head 16-inch M1 Max 69bde153405c 24 of 24 pass
image chunks 0 and 3 (2,000 cases) and 12 api chunks that held the earlier copy failures (12,000 cases), against the base branch 16-inch M1 Max 69bde153405c 0 status differences, 0 GPU timeouts
The 141-case image window with the sparse-image atomics, 3 runs 16-inch M1 Max 69bde153405c 0 failures and 0 GPU timeouts in all 3 runs (on an earlier build of this PR, 83be9b16b3d8: 3 failures in each of 3 runs and GPU timeouts)
100 robustness cases that hung the 13-inch M1 16-inch M1 Max 69bde153405c same results as the base branch (46 Pass, 54 NotSupported); the previous head had 1 Fail
The same 100 robustness cases, 3 runs, and 3 runs of the base branch 13-inch M1 69bde153405c all 6 runs have identical per-case results (46 Pass, 54 NotSupported); 0 GPU timeouts in the boot (on two earlier builds the chunk hung the GPU in both runs: the previous head 8b217d2f67cc, and a build of the previous head's fixes without the last one, 02765a81b1a5; the kernel logs show Job timed out on the DRM scheduler)
Wide Vulkan CTS, this head against the base branch (whole suite, 3,253,584 cases listed) 13-inch M1 69bde153405c complete: 3,253,584 cases with a result in both arms (3,284 of 3,284 chunks complete in all arms; all 20 of 20 tickets finished; 0 wedge-stopped; 0 GPU timeout lines this boot). 1,958 status differences, of which 7 are timing tests in shader_object.performance that flip on the base branch as well and are excluded from the intended count (see the next row). The remaining 1,951 are all NotSupported on the base and Pass here: 1,911 cooperative matrix tests in compute, 39 glsl bfloat16 tests and the properties query in api. That is the intended change. No case changed in any other direction. 0 GPU timeouts.
Wide Vulkan CTS, measured build against pre-PR main 16-inch M1 Max 69bde153405c complete: all 20 of 20 parts finished; 3,284 of 3,284 chunks complete in both arms; 3,253,584 cases with a result in both arms; 2,037 status differences against pre-PR main 7cda7850edd1. 1,951 are NotSupported to Pass: 1,911 cooperative matrix tests in compute, 39 glsl bfloat16 tests and the properties query in api. The other 86 are 31 Fail to Pass, 53 Pass to Fail and 2 QualityWarning to Fail. The 30 memory_model image-payload cases were Fail in 90 of 90 base runs and Pass in 90 of 90 measured-build runs, with 0 within-arm variation and 0 GPU timeouts; the cause is not bisected. The 42 multiview query-copy and 6 renderpasses suballocation cases were Pass in 144 of 144 base reruns and Fail in 144 of 144 measured-build reruns, with 0 within-arm variation. The other 8 differences are in shader_object.performance: 5 Pass to Fail, 2 QualityWarning to Fail and 1 Fail to Pass. These 55 regressions were measured on this wide CTS build. Later targeted results are separate rows below; no timing waiver. The installed system library on that machine was built from the same source commit as the measured build, but the bytes differ (13,792,048 against 15,869,440 bytes), so this evidence is bound to the measured library, not to the installed package. It is not a separate stock control. Before part 1, the boot already had 8 GPU timeout lines from another job. Parts 1 to 9 each read before 8 and after 8: 0 own GPU timeouts.
The 42 multiview query-copy and 6 renderpasses cases, targeted rerun (FIX5C8D) 16-inch M1 Max 5c8d6b910379 48 of 48 Fail; GPU timeout count after minus before: 0. The barrier-word fix did not resolve these cases. This is not a pass result for the later source head.
The same 48 correctness cases, targeted rerun (FIXA5A7) 16-inch M1 Max a5a7723d7c3b (includes 5c8d6b910379) 42 multiview query-copy cases Pass; 6 dynamic-rendering suballocation low_resolution_z cases Fail. The library SHA-256 prefix is c63179b3da8e2a95. Script journal counter: before 8, delta 0. Guard counter: before 0, after 0, delta 0. These are different counters. The combined changes are not a full fix or qualification.
The same 48 correctness cases, diagnostic tail build 16-inch M1 Max 594460915d6a (69bde153405c plus one hunk in hk_cmd_buffer.c) 48 of 48 Pass (42 multiview, 6 renderpasses). A control rebuilt from main 7cda7850edd1 also passed 48 of 48, and the GPU timeout delta was 0. This is a diagnostic build, not the PR head. No wide suite, speed measurement or other case was run on it.
The same 48 correctness cases, exact PR head, native build 16-inch M1 Max f144c7949b7d (library prefix cfa40cace1eb8dfb, native build, not the package bytes) 48 of 48 Pass (42 multiview, 6 renderpasses). The stored control and a control rebuilt from main 7cda7850edd1 also passed 48 of 48, and the GPU timeout delta was 0. Not shown: any other case, speed, the M2 Max, the 13-inch M1, or the packaged library.
The same 48 correctness cases, exact PR head, package build 16-inch M1 Max f144c7949b7d (package library prefix 4874d533a942e052) 48 of 48 Pass. A control built the same way from main 7cda7850edd1 (library prefix 30cf82ae3074421a) and the stored control also passed 48 of 48, and the GPU timeout delta was 0. This is the library that would be installed. Not shown: any other case, speed, the M2 Max, or the 13-inch M1.
Seven shader_object.performance cases, five runs per arm (PERF7, finished) 16-inch M1 Max 69bde153405c against 7cda7850edd1 indexed_dynamic and indirect_dynamic: both arms Fail x5. indexed_static and indirect_static: both arms Pass x5. binary_shaders: base Fail Pass Fail Fail Fail; head Fail x5. indirect_count: base Fail x5; head Fail QualityWarning Pass Fail Fail. draw_static: base QualityWarning QualityWarning Fail QualityWarning Fail; head QualityWarning Fail Fail Fail Fail. No blanket exclusion or timing waiver.
7 shader_object.performance timing cases that flipped between pre-PR main and the head in the wide CTS, rerun with 5 runs per arm and separate deqp processes (ticket at 20:50Z) 13-inch M1 69bde153405c these tests compare dynamic vs static pipeline draw times. draw_dynamic_pipeline and draw_indexed_dynamic_pipeline Fail in all 10 runs of BOTH builds; draw_indexed_indirect_static_pipeline and draw_static_pipeline Pass in all 10 runs of both. draw_indexed_indirect_count_dynamic_pipeline (main Fail Fail Pass Pass Pass, head Pass Fail Pass Pass Fail), draw_indexed_indirect_dynamic_pipeline (main Fail Fail Fail Pass Fail, head QualityWarning Pass Fail Pass Pass) and draw_indirect_count_dynamic_pipeline (main Pass Fail Fail Fail QualityWarning, head Fail Pass Fail Fail Pass) flip between runs INSIDE the base build too. These are timing tests; they flip on the base branch as well, so they are excluded from the difference count. 0 GPU timeouts.
8 MLX test suites, this head against the base branch 13-inch M1, M2 Max 69bde153405c no failures in 7 suites in either build on both chips. fast_ops has the same 8 failed assertions of about 1.38 million in both builds, so they are not new. The cooperative matrix suite runs 82.9 million assertions here against 21.1 million on the base branch, all passing.
8 MLX test suites, same 16-inch M1 Max 69bde153405c no failures in 7 suites in either build. fast_ops has the same 8 failed assertions in both builds, so they are not new. The cooperative matrix suite runs 82.9 million assertions here against 21.1 million on the base branch, all passing. (An earlier try found no test binaries on this machine; they were copied from the 13-inch M1 with the same checksums.)
Recovery after a refused allocation (3 runs per build) 13-inch M1 69bde153405c this head: 3 of 3 runs recovered at once after the typed out-of-device-memory error (a 1.5 to 1.7 GiB allocation and a matrix multiply, 0.56 s). The base branch: 0 of 3, and 0 of 1 for the system driver; the retry failed at once and again after the cache was cleared, and memory was not released within 8 seconds. No kernel out-of-memory line, and the compositor process was the same before and after.
Recovery after a refused allocation, same 16-inch M1 Max 69bde153405c this head: 3 of 3 runs recovered at once after the typed out-of-device-memory error (a 1.8 GiB allocation and a matrix multiply, about 0.66 s). The system driver recovered in its one run. The base branch: all 3 runs were killed by the memory cap (exit 137) while still allocating, with no typed error. Compositor process unchanged. The earlier try, with other work using memory, was not clean and is not counted.
sparse_resources CTS (7,263 cases), this head against the base branch 13-inch M1 69bde153405c 0 status differences in 7,263 cases (26 buffer cases fail in both builds, the same ones)
sparse_resources CTS, same 16-inch M1 Max 69bde153405c 0 status differences in 7,263 cases (the same 26 buffer cases fail in both builds)
24 image-copy cases, the 141-case sparse-image atomic window and the 100 robustness cases, run with the Arch package built from this tree (installed driver, lib 40282811c6e1, reports git-69bde15340) next to the exact-sha build above and the base branch 16-inch M1 Max, 13-inch M1, M2 Max 69bde153405c package, exact-sha build and base branch give identical per-case results in every probe on all three machines: copy 24 Pass, atomic window 115 NotSupported and 26 Pass, robustness 54 NotSupported and 46 Pass. 0 GPU timeouts.
gemma-4-26B-A4B (MoE, 4-bit, MLX) decode, the Arch package built from this tree (installed driver, lib 40282811c6e1) against the earlier v3 driver, three runs per driver interleaved in one block, same wheel; every ratio below is the median of the three package runs over the median of the three v3 runs 16-inch M1 Max 69bde153405c decode 11.99, 11.89 and 11.89 against 11.68, 11.69 and 11.70: ratio 1.017. Prompt processing ratio 1.001. Same output digest. The v3 runs agree within 0.2% inside the block.
Same test, 26B, Qwen3.6-35B-A3B and Qwen3-Coder-30B-A3B, kernel with compute preemption on M2 Max 69bde153405c 26B: decode 12.22, 12.20 and 12.08 against 10.90 three times: ratio 1.119, prompt processing 1.010. 35B: decode 14.55, 14.54 and 14.55 against 13.09, 13.09 and 13.07: ratio 1.112, prompt processing 1.007. Coder 30B: decode 12.92, 12.94 and 12.92 against 11.92, 11.91 and 11.88: ratio 1.085, prompt processing 1.003. Same digests. The v3 runs agree within 0.3% inside each block.
gemma-4-E4B (dense) decode, the package against the earlier v3 driver, three runs per driver interleaved in one block, same wheel 13-inch M1 69bde153405c decode 9.02, 8.98 and 8.97 against 8.86, 8.85 and 8.87: ratio 1.014. Prompt processing ratio 0.996. The v3 runs agree within 0.2% inside the block. A background neural-engine job ran during most rows; the first v3 run, taken without it, sits in the range of the other two. An earlier four-driver block on the same machine had a v3 control that moved by 18% between rounds, so I dropped it; none of its rounds showed a difference above 2.6%.
llama.cpp gemma-3-12B (7.3 GB) and Mellum2-12B (8.2 GB) on a 16 GB machine 13-inch M1 8b217d2f67cc with HK_PERFTEST=alwayscdmbarrier both run: gemma-3-12B 31.46 tokens/s prompt, 5.18 generation; Mellum2-12B 114.30 and 20.28. An earlier build on the v3 branch (6543eeb7df7) fails both with an out-of-device-memory error. The same earlier build with the system-memory limit set by hand (HK_SYSMEM=11000000000) runs gemma-3-12B at 31.62 and 5.35. The raw error text of the two failures was overwritten by later runs; the empty score rows remain. Measured again on 69bde153405c: queued.
Ornith-1.5-9B (OptiQ, 7.1 GB) with MLX, same wheel 13-inch M1 8b217d2f67cc with HK_PERFTEST=alwayscdmbarrier prompt processing 30.97 tokens/s against 5.70 on the earlier build (5.4 times faster); generation 4.28 against 4.35. Both runs were on different boots; the boot parameters are in the receipts. Measured again on 69bde153405c: queued.
Partial wide CTS on the previous head: memory, sparse_resources, and 46 of 151 chunks of binding_model 16-inch M1 Max 8b217d2f67cc identical to the base branch; api showed the image-copy failures above
Recovery after a refusal, the 8 MLX suites and sparse_resources on an earlier tree 16-inch M1 Max, 13-inch M1 4c1ca73cddb (tree f51deca39fbf) passed (three of four checks; the wide CTS on that tree was never completed); built from a working tree with patches applied, not from an exact archive. They do not count for this head until rerun.
313 targeted cases, exact PR head against main, control and candidate on the same boot (the 48 multiview and renderpasses cases, the 24 sampled image-copy cases, the 141-case sparse-image atomic window and 100 robustness cases) 16-inch M1 Max, 13-inch M1 f144c7949b7d against 7cda7850edd1, package builds 0 status differences in 313 cases on each chip: 144 Pass and 169 NotSupported in both arms, and the result files are byte-identical between the arms and between the chips. The 48 correctness cases are Pass in both arms. The gate comparison printed 0 regressions and 0 invocation errors, and the boot had 0 GPU timeout lines. This shows no regression at the head on these cases. It is not a fix result, because main also passes the 48 cases, and 169 of the 313 rows are NotSupported in both arms and so test nothing. It does not cover the 7 shader_object.performance cases, the binding_model rows, the full suite, the M2 or any speed.

Not run: the CTS on the M2 (a separate problem, below). Speeds are ratios inside one block, because the absolute tokens per second on the M1 chips drift by 10 to 20 percent over hours. The memory rows above were not shown to prevent out-of-memory kills: a run without those commits finished without a kill. Anything not listed in this table was not run.

Every row above ran on kernel 7.1.12-2-12.6. The M2 Max rows added the compute-preemption debug word, as stated above. On the 13-inch M1 only the Ornith run on the earlier driver added that word. The other memory rows on that machine ran on a stock boot. Release kernel .15 results (kill-mid-dispatch arm on the M2 Max and the 13-inch M1) are kernel results. They are reported in omacom/linux-aurora #25 and are not part of this PR.

What still blocks READY

  1. Wide Vulkan CTS on the 16-inch M1 Max is complete on build 69bde153405c. That build had 53 Pass to Fail and 2 QualityWarning to Fail regressions. All 48 targeted correctness cases failed on build 5c8d6b910379, with 0 new GPU timeout lines. The reverted combined build at a5a7723d7c3b gave 42 Pass and 6 Fail. A diagnostic tail build, 594460915d6a, passed all 48. The source head f144c7949b7d has native and package builds that passed the 48 targeted cases. No wide suite, speed measurement, M2 Max or 13-inch M1 result exists for it. PERF7 is finished on 69bde153405c; the recorded sequences above are not a blanket exclusion. A passing wide suite on the exact final head, run from the packaged library bytes, is still required.
  2. Review of this final body by the reviewer.
  3. llama.cpp gemma-3-12B and Mellum2-12B on the 13-inch M1, and Ornith-1.5-9B on the 13-inch M1, measured again on 69bde153405c: queued.

Merge

Merge this pull request with a merge commit, not a squash, so the 61 commits and their authorship stay. It will not mark #3, #4 or #5 as merged, because their heads are not ancestors of this one. This PR holds the commits of all three. 12 of #3's 16, 19 of #4's 25 and 21 of #5's 28 are identical patches here. Later commits in this PR edited the rest. For example, the precise division work was redone. Lines that exist only in #3 to #5 are earlier versions of code that later commits here rewrite. I compared final trees but did not review every such line. After merging, close #3, #4 and #5 with a comment that their content is in this PR. Merging #3 to #5 first would not help, because their heads are not ancestors of one another.

Commits by layer

Layer (by patch identity at this head) Commits What
#3 12 Hardware cooperative matrix, shapes, math (division, log, sin/cos), VK_KHR_cooperative_matrix on by default, bounded syncobj poll, FP_MATH_CTRL for the builder generator, regression tests
#4 7 Software cooperative matrix outside full subgroups, address and index fixes, shader cache key, ubfe fusion, one precise division fix
#5 2 Cooperative matrices and the tracked barrier limited to G13
new here 35 Barrier defaults per chip and the deferred barrier on M1, paired loads, address folding, no compute scheduler, M1 codegen defaults, G14X cooperative matrix and tracked barrier, division and reciprocal work with pinned tests, sub-format address fold, copies without a full barrier, large constant arrays, bf16 matrix shapes, image texel visibility, load hoisting, 90% heap, memory reserve, full address window, BO cache give-back, a full flush for command buffers with rendering or copies, a neutral lock file name in five test scripts

Four of the #3 commits no longer count as identical. The tests commit had comment edits. The first commit dropped an experimental env-gated code block and its lab-notebook reference. The tracked-barrier commit carries the fixes above. The division work was redone.

Defaults per chip at this head

Chip Cooperative matrix Paired fragment loads Address fold, no compute scheduler, load hoisting Compute barrier
M1 (G13G) on on on deferred to the end of each batch
M1 Pro, Max, Ultra (G13X) on on on dependency-tracked
M2 (G14G) off (AGX_SIMDMAT=1 opts in) off off before every launch
M2 Pro, Max, Ultra (G14X, generation 14) on on (follows cooperative matrix) off dependency-tracked
Later G14X parts (generation 15 and up) on on (follows cooperative matrix) off before every launch (HK_PERFTEST=trackcdmbarrier opts in)

Paired fragment loads have no chip check of their own: they run wherever cooperative matrices run, so they are on for G14X as well. AGX_HWMAT_VEC2_OFF=1 turns them off.

Where the barrier is dependency-tracked, some command buffers flush after every launch. These are the buffers that record rendering, an image copy or blit, or a precomp that feeds or follows a draw. A command buffer that only dispatches compute keeps the tracking. A device that has created a sparse image or buffer uses the full barrier word.

On every chip: the device heap is 90% of system memory, and an allocation of 1 MiB or more is refused when free memory would drop below the larger of 1 GiB and 2% of RAM.

Knobs

AGX_SIMDMAT=0|1 cooperative matrix. AGX_HWMAT_VEC2_OFF=1, AGX_LOCAL_FOLD_OFF=1, AGX_SCHED_COMPUTE=1 switch the M1 codegen defaults off or restore the scheduler. AGX_HOIST_LOADS=0|1 load hoisting. HK_CDM_DEFER_FLUSH=0|1 deferred barrier. HK_PERFTEST=trackcdmbarrier or alwayscdmbarrier. HK_SUBMIT_POLL_US bounded poll before blocking waits (default off). HK_LARGE_CONSTANTS=0|1. HK_SYSMEM=<bytes> overrides the system memory size the heap and the reserve are computed from (the 11,000,000,000 row above used it). The heap share is the driconf option heap_memory_percent (this PR changes its default from 0.50 to 0.90). The compiler switches are part of the shader cache key. They are AGX_SIMDMAT, AGX_HWMAT_VEC2_OFF, AGX_LOCAL_FOLD_OFF, AGX_SCHED_COMPUTE and AGX_HOIST_LOADS.

Try it on your Mac

git fetch https://github.com/joshuaswarren/mesa-1.git honeykrisp-omarchy-v3 && git checkout 795ae42a71c8889dd3b629d03a3aeb657501f20f
meson setup build --buildtype=release -Db_ndebug=true -Dvulkan-drivers=asahi -Dgallium-drivers= -Dplatforms=x11,wayland -Dvulkan-layers= -Dopengl=false -Dgles1=disabled -Dgles2=disabled -Degl=disabled -Dgbm=disabled -Dglx=disabled -Dllvm=enabled -Dshared-llvm=enabled -Dvalgrind=disabled -Dbuild-tests=false
meson compile -C build
VK_DRIVER_FILES=$PWD/build/src/asahi/vulkan/asahi_devenv_icd.aarch64.json vulkaninfo --summary

The last line should print driverName = Honeykrisp and Mesa 26.3.0-devel (git-795ae42a71). To run the CTS against it, put the same VK_DRIVER_FILES in front of deqp-vk.

One thing about the M2

On the M2 Max, a single long compute dispatch can return partly wrong values with no error while a window animates on the same GPU. It must run about 150 ms or longer: 87 ms was correct and 152 ms was wrong. The kernel log then shows "GPU timeout" and recovery lines from the GPU driver. It also happens with the stock distribution driver (Mesa 26.2.4) and with every build of this PR we tried, so this PR is not its cause. The M1 chips do not show it. It is a kernel matter. With compute preemption switched on through a kernel debug flag (bit 48 of the GPU driver's debug flags), the same test matched its reference in 60 of 60 animated runs. Those were 5 harness runs on 3 boots, with no GPU timeout. Without the flag, the same test showed wrong results and GPU timeouts on the stock kernel (39 timeout events in one run, 120 in another). The kernel change is not part of this PR.

…add32

Add the opcodes and packer (byte-exact against applegpu), a NIR intrinsic
bridge, the agx_nir_lower_simdmat lowering, the validator widths, and a gated
advertisement of an 8x8x8 fp32 cooperative matrix shape.
Everything is behind AGX_SIMDMAT; default Honeykrisp behavior is unchanged.
VK_KHR_cooperative_matrix on Honeykrisp only advertised 8x8x8 fp32 even
though agx_nir_lower_simdmat.c already tiles a 16x16x16 coopmat into a
2x2x2 grid of simd_matrix_fmadd ops, and the G13 matrix unit takes fp16
operands. Now advertised (subgroup scope, N in {8, 16}, NxNxN):

  fp32 A/B, fp32 C/D      simd_matrix_fmadd32 with 32-bit pairs
  fp16 A/B, fp32 C/D      simd_matrix_fmadd32 with 16-bit A/B pairs
  fp16 A/B, fp16 C/D      simd_matrix_fmadd16

The mixed form is the one Apple's own Metal mul_mm kernel emits
(simd_matrix_fmadd32 r15_r16, r49l_r49h, r46l_r46h, r15_r16); the generic
ALU packer already encodes the per-source size bits, so the lowering
passes A/B at their own width instead of up-converting to fp32, and the
nir intrinsic gains a 16-bit dest form that selects fmadd16.

Bug found while verifying: with fp16 A and fp32 D the register allocator's
early-kill let D (a 32-bit pair) land on top of A (a 16-bit pair inside
that pair). The hardware then produced zeros for that fmadd: 16x16x16
fp16/fp32 failed in every input/layout variant, always sub-block (1,1),
whose last fmadd was the only one with D overlapping A. Apple's compiler
only ever emits D == C, so the RA now keeps A and B live until D is
placed (D may still reuse C).

Verification on Apple M1 (G13G B1), the cmshape test program, one coopMatMulAdd
per run, exact host reference, integer-valued inputs bit-exact and random
floats within tolerance, identity/ones/randint/randf x row/col-major:
  stock Mesa 26.1.7:        FAIL, extension not exposed
  5bb2b28 (before):        8x8x8 fp32 8/8 PASS; 16x16x16 not advertised
  this commit, no RA guard: 40/48 (16x16x16 fp16/fp32: 8/8 FAIL)
  this commit:              48/48 PASS, plus 48/48 randint over 4 seeds
MLX Release tests (primitive 98, reduce_ops 34, runtime 40,
matmul_family 14 cases) pass identically on the stock ICD and on this
driver with AGX_SIMDMAT=1.
Bug: (word >> s) & mask with a non-constant s returns zero on Honeykrisp
whenever s has any bit set above bit 4 (s >= 32). MLX hit this as
the "dynamic shift-then-mask byte extraction" miscompile family: bool
scatter dropping writes across word lanes, select picking the wrong
operand under broadcast conditions, reduce_general's load_truthy reading
zero, masked_scatter's scan undercounting (s_scan[255] = 32, not 128).

Root cause: agx_nir_fuse_algebraic_late rewrites
   iand(ushr(a, b), (1 << bits) - 1)  ->  ubitfield_extract(a, b, bits)
NIR's ushr uses only the low 5 bits of its count, and by this point the
main nir_opt_algebraic has already relied on that to strip explicit masks
(ushr(a, iand(b, 31)) -> ushr(a, b); ushr(a, ishl(iand(b, 3), 3)) ->
ushr(a, ishl(b, 3))). ubitfield_extract has no such wrap: an offset >= 32
is undefined in NIR and bfeil returns 0 for it on the hardware.
lower_sm5_shift, which masks the count of every surviving shift, runs
after the fusion and never sees the ushr it consumed.

Fix: fuse directly only when the count is a constant below 32; otherwise
fuse with an explicit iand(b, 31) on the offset. Same instruction count as
the unfused shift+and, and the mask CSEs with one the shader already had.

Verification on Apple M1 (G13G B1), standalone Vulkan compute reproducer
(20 variants, 1024 lanes each, host-compared):
  stock Mesa 26.1.7:  9 of 16 base variants FAIL (byte_dyn,
    byte_dyn_const, byte_dyn_gid, scan_dyn, scan_dyn_const, scan_dyn_rand,
    select_bcast, select_dyn, shift_dyn_mask), plus all 4 boundary
    variants FAIL (count exactly 32: 1016/1024 wrong; 63: 516/1024;
    uniform 40: 1020/1024; divergent 8/40: 509/1024, only the 40 lanes).
  this commit:        20 of 20 PASS, 0 mismatches.
MLX suites, stock vs this commit (AGX_SIMDMAT=1), identical:
  primitive 98/98 (2699373 assertions), reduce_ops 34/34 (7152),
  runtime 40/40 (6307), indexing_ops 53/53 (4093), select_layout 11/11
  (1908), scatter_determinism 21/21 (127).
…duction

Three fp32 rounding defects on Honeykrisp (Apple M1 G13G, Mesa 26.1.7)
that MLX patches around in its shaders:

  a) GLSL "/" was lowered by nir_opt_algebraic (lower_fdiv) to
     a * frcp(b), and frcp was only Newton-refined when the instruction
     was `precise`. Even with an exact reciprocal a * RN(1/b) is not
     RN(a/b): 80/25 and 10/25 come out one ulp low, 6.55859375/0.030016447
     one ulp high. Measured on 1008 pairs: 299 mismatches (298 at 1 ulp,
     1 at 2 ulp), 1/b: 204 mismatches; 1M random pairs: 305792 mismatches.
     fma() itself is IEEE (0/1000 mismatches), the MLX note
     blaming fma contraction was a misdiagnosis.
  b) log(x) was flog2(x) * ln2 on the hardware log2, which is only
     absolutely accurate: log(3) = 1.0986121892929077 instead of
     1.0986123085021973, 2792/4107 mismatches on [0.5, 64] with errors up
     to 357 ulp near 1 (log(1.0000001) = 1.65e-7 vs 1.0e-7); log2 had
     2905/4107 mismatches.
  c) sin/cos reduced the argument as fract(x / 2pi) in fp32, so the
     reduced angle lost ulp(x) of precision: sin(3.0) 14 ulp off,
     sin(1e5) 1.06e-3 absolute error, sin(1e8) = 0.0, sin(-1e-3) 571 ulp
     off, sin(pi_f32) = 0, and the hardware sin_pt_2 core itself is 2 to
     3 ulp off (cos(1e-3) 3 ulp).

The lowering moves to a new the AGX compiler pass file agx_nir_lower_math.c,
built only from the exact fp32 fma and marked no-fast-math so
nir_opt_algebraic cannot reassociate it:

  - lower_fdiv is dropped from agx_nir_options; fdiv32 becomes Markstein
    division (y = refined rcp, q = a y, r = fma(-b, q, a), q + r y) with
    the NaN/zero guard keeping q for b = 0, overflow and signed zero.
    frcp32 is always refined. fdiv16 keeps a * frcp(b).
  - fmul(flog2(x), ln2) as emitted by nir_flog, and any remaining flog2,
    become a double-float evaluation of log(m) = 2s + 2s^3 P(s^2) with
    s = (m-1)/(m+1) kept as a pair, m folded into [sqrt(1/2), sqrt(2)),
    e ln2 split hi/lo and Fast2Sum accumulation (about 40 bits before the
    final rounding). Zero/subnormal/negative/inf/NaN are handled by bit
    tests, not the hardware log2.
  - fsin/fcos: Cody-Waite in radians with pi/2 in three pieces, the k C2
    product and its subtraction as TwoProduct/TwoSum so the reduced
    argument is a pair accurate to an ulp, for |x| < 2^22; an integer
    Payne-Hanek (24-bit mantissa times a 128-bit window of 2/pi, 64
    fraction bits kept) for |x| >= 2^22 up to FLT_MAX. The quadrant then
    selects a Taylor polynomial in quadrant units (sin degree 13, cos
    degree 14, leading terms as pairs, low part of the argument fed to
    the derivative). The hardware sin_pt_1/sin_pt_2 core (nir_op_fsin_agx)
    is no longer used by the lowering. fp16 sin/cos go through the fp32
    path.

Verification (standalone Vulkan compute reproducer):

  - division: 1008-pair table incl. 80/25, 10/25, 1000 random: 0
    mismatches (stock 299); 1/b 0 (stock 204); 1M random pairs 0
    mismatches for both a/b and 1/b; b = 0, inf, NaN, overflow, flushed
    subnormal and -0/5 = -0 match host IEEE.
  - log: 4107 points on [0.5, 64] plus pinned exact cases: 0 mismatches
    (stock 2792), log(3) = 1.0986123085021973; log2 0 (stock 2905);
    1M random on [1e-37, 1e38]: log 3 at 1 ulp, log2 14 at 1 ulp,
    rest exact.
  - sin/cos at 1e5, 5e5, 1e6, 5e6, 1e8: all 0 ulp from the double
    reference except cos(5e6) at 1 ulp (stock: 2.8e5 to 1.06e9 ulp);
    1310 mixed points max 1 ulp; 1M random over [1e-3, 1e9] and [-10, 10]:
    sin 42346 and cos 41184 at 1 ulp, rest exact, max 1 ulp (stock
    >= 8 ulp for 627689 / 512816 of them).
  - MLX suites on the same Release tree, stock vs fixed:
    primitive 98/98, eq_math 7/7, fast_ops 30/30, runtime 40/40,
    reduce_ops 34/34 both; complex_ops 30/33 both with the same three
    pre-existing cases (failed assertions 31 -> 29). The upstream
    ops_tests log(3) pin through libmlx: FAIL on stock, PASS on fixed,
    as do 80/25, 10/25 and sin/cos up to the 1e5 limit mlx enforces.
  - timing: 16M-element elementwise dispatch is memory bound at ~2.45 ms
    on both drivers (sin 2.474 -> 2.463 ms median, within spread). A
    32-deep in-register chain shows the ALU cost: sin 6.64 -> 39.6 ms,
    log 2.57 -> 34.8 ms, div 2.84 -> 7.24 ms, mul unchanged.
…it late

Symptom: any shader converting fp32 to a 64-bit integer aborts in the
backend with "Unhandled ALU op fdiv" (agx_compile.c agx_emit_alu); the
MLX suite the copy-offset tests crashes on "dtype converting
copies cover the integer family and bool". Fragment shaders with
perspective-correct varyings hit the same path.

Cause: "agx: correctly rounded fdiv/frcp, faithful log/log2, exact sin/cos reduction" dropped lower_fdiv from the compiler options so
nir_opt_algebraic no longer splits fdiv into fmul/frcp, and only ran
agx_nir_lower_fdiv in agx_preprocess_nir. nir_lower_int64 (f2u64/f2i64:
fdiv by 2^32, plus the frem that lower_fmod turns into a second fdiv) and
agx_nir_lower_interpolation run after preprocessing, so their fdiv reached
the backend, which has no fdiv.

Fix: agx_nir_lower_fdiv_late runs in agx_optimize_nir after the int64
cleanup loop and lowers only fdiv, leaving frcp alone so the reciprocals
built by the first pass and by the log lowering are not refined twice.

Verification: hk/precise-math driver alone reproduced the abort on the 13-inch M1;
with this commit the copy-offset tests passes 25/25 .
…0 disables)

Replace the five getenv("AGX_SIMDMAT") checks (agx_preprocess_nir lowering,
the extension table, the feature bit, the supported stages, and the
properties query) with agx_simdmat_enabled(), which reads the variable through
debug_get_bool_option with a default of true. Setting AGX_SIMDMAT=0 restores
the previous behaviour.

The 25/25 MLX battery and every reproducer ran with the gate on
(AGX_SIMDMAT=1), so the default now matches the verified configuration and a
system-installed driver exposes coopmat to unmodified wheels.
@joshuaswarren

joshuaswarren commented Oct 6, 2026 •

Copy link
Copy Markdown
Author

fdiv: default-path 2.5 ULP contract pinned, with identity + performance receipts (M1 G13G)

Spec: the Vulkan SPIR-V environment allows 2.5 ULP for OpFDiv - correctly rounded is not required (SPIR-V environment appendix). 963e7da's exact division is stricter than anything a Vulkan/OpenCL consumer of this driver can request: rusticl does not advertise CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (git grep CORRECTLY_ROUNDED over rusticl: empty), and SPIR-V cannot demand exact division - FPFastMathMode only relaxes. nir_fp_exact remains the in-tree ask mechanism (GL precise, libclc builtins).

Measured on jwm1 (M1 G13G, Coreglass ledger, 3-round crossed driver-ab.sh, only the Vulkan ICD varies; medians, min-max in the log):

build 4B prefill/decode tok/s 2B prefill/decode fp32 division vs correctly-rounded-FTZ oracle
eb0cdf9 (pre-43b) 172.2 / 17.1 400.6 / 43.3 750 of 262,144 random pairs over 2.5 ULP - frcp flushes for d ≥ 2¹²⁶ → ±0 instead of ±normal, inf/max-finite → NaN; 2⁻¹²⁶ boundary class: 1 ULP
116ff37 (this PR's head before push) 166.0 / 16.6 388.2 / 42.9 0 ULP
8d26739 → 29266f6 (v1→v2, pushed to this branch) 168.4 / 16.8 392.6 / 42.9 0 ULP (division + reciprocal, regression + boundary sets)
92aa3f6 (new head, fresh lib sha256 28c82e70ed33…) 169.2 / 16.7 399.7 / 42.9 max 0 ULP, 0 over-bound, 0 special mismatches over 328,192 pairs (tests/ulp_bound.py)

The last row is the paired clean run on the same box after w71's window (e763 measured in the same run: 171.5 / 17.1 and 404.8 / 43.3); across three additional independent runs the candidate sits at 168.4-170.9 vs e763 171.0-173.5 4B prefill, i.e. the exact-division default costs ~1.3-2.2% 4B prefill against a baseline that is itself outside the spec budget.

A cheaper default was tried and rejected before pinning the test: a guarded Markstein form (common 2⁻³ operand scale for d ≥ 2¹²⁶, float selects for specials) measured 8,376 over-bound results on the same 328,192 pairs - the compute pipeline preserves denormals, so any float-path special handling needs the integer flush machinery back, and nothing is left to save against the ~44-op normalized form. The normalized correctly rounded division therefore stays the default for every fdiv/frcp; the exactness is not gratuitous, it is what makes Dj's M2 Pro 2⁻¹²⁶ boundary class (Basecamp 10277024959) correct, and the ~2% 4B prefill delta vs e763 is the price of a spec-legal default (e763's shape is not legal).

What 92aa3f6 adds: tests/ulp_bound.py - pins ≤ 2.5 ULP over the 262,144-pair precise_math regression input plus a 66,048-pair 2⁻¹²⁶-straddle boundary set, with bitwise specials; precise_math.py keeps the exact-rounding contract for the nir_fp_exact path. Follow-up note: if a cheap plain-fdiv default ever lands behind nir_fp_exact, precise_math.comp should grow a precise-qualified division/reciprocal column pair so the exact path keeps GPU coverage.

Library identity: fresh meson builds are byte-deterministic (two independent 29266f6 builds identical), and every number above comes from a recorded sha256; the v2 candidate lib measured earlier differs from a fresh 29266f6 build by 38 bytes - build-id + stale baked git-string only, zero code bytes.

@joshuaswarren

joshuaswarren commented Oct 6, 2026 •

Copy link
Copy Markdown
Author

Decode regression from the fdiv fix: found and recovered, results bit-identical (head ed28f1d)

Bisect. I bisected e763159..01de043 one commit at a time on an M1 Max (G13C, jw16), with MLX held fixed (bf62cfb). Each commit got its own ICD, built natively with the PKGBUILD flags. The runs alternated in 5 rounds, measuring 2B d64 decode. 43b2756 costs -6.63 tok/s and was negative in 5 of 5 rounds. 443153a recovers +2.1 tok/s. Every other commit, including the 934b barrier (+0.04), stays within noise.

Mechanism. After 43b2756, every fp32 frcp and fdiv lowers to the normalized correctly rounded division: about 44 ops, or about 28 for a reciprocal after folding. e763159 used about 5-8. AGX_MESA_DEBUG=shaderdb on the decode pipelines shows 31 of 37 pipelines growing; for example, one goes 100 → 413 → 265 instructions (e763 → 43b2 → 01de). Occupancy does not change. Decode takes many reciprocals (sigmoid, softmax normalizers).

a9a668a: exact reciprocal without the division machinery. For x in [2^-126, 2^126), the normalized reciprocal is the frcp refinement on x, scaled by a power of two. So 1/x becomes frcp plus 2 fma. The other exponents have fixed results, chosen by selects. fdiv(1, x) takes the same path. General division does not change.

  • tests/div_window.py (new) compares every fp32 reciprocal (2^32) and 2^32 hashed quotients with 01de043 bitwise, and with the correctly rounded FTZ oracle. G13C: 0 mismatches. On G13G (jwm1), the same sweep of the in-window frcp scaling also found 0 mismatches. precise_math.py and ulp_bound.py pass unchanged (max 0 ulp).
  • I tried a branch around the slow path instead (bit-identical, dont_flatten). It was slower than 01de (2B 93.8, 9B 11.6), so it was dropped. A streamlined 38-op general division brought no measurable gain, so it was dropped too.

ed28f1d: round log() once unless the multiply is exact. 155e743 made every default log() less accurate. Over all 2,130,706,432 positive normal fp32 inputs, against fp64:

log() max ulp mean ulp not correctly rounded
before 155e743 (c787e19) 0.5004 0.250 3,448
155e743 / 01de043 1.2388 0.343 536,324,864
ed28f1d 0.5004 0.250 3,448

log2 stays at 0.5025 max. The soft-log rewrite comes back for non-exact multiplies only. A precise log2(x) * ln2 keeps the rounding the source asks for, so Dj's literal-vs-buffer case still matches bit for bit (precise_math.py: literal_vs_buffer 0, precise_multiply 0). 2B decode KLD against an upstream-MLX CPU fp32 reference, over 64 teacher-forced steps: c787 1.156268e-3, 155e 1.156298e-3, ed28 1.156268e-3.

d64 decode tok/s, jw16, medians of 5 alternating rounds (e763159 / 01de043 / head):

model e763 01de head
Qwen3.8-2B 4-bit 105.15 100.11-101.20 103.74
Qwen3-4B 4-bit 66.64 63.82-64.12 64.84
9B qwen3_5 4-bit (3 rounds) 14.23 14.08 14.17 (reciprocal only)

Greedy token digests do not change on these prompts. The remaining 4B gap (about 2.7%) is general division a/d, which stays on the exact path. e763159 is faster there only because it breaks the 2.5 ulp bound on 750 pairs.

Not yet verified: G14 (M2). jw14m2-linux was unreachable all day on three routes. Please run tests/div_window.py ICD compute_runner REF_ICD there before merge, because the reciprocal path relies on the hardware frcp scaling.

@joshuaswarren

joshuaswarren commented Oct 7, 2026 •

Copy link
Copy Markdown
Author

f24b680: 7-op exact reciprocal (decode profile follow-up)

Where the remaining decode gap sits, on jw16 (M1 Max, G13C), measured with d64 decode and 5 alternating rounds:

  • I built an ICD that gives general a/d the bare e763159 Markstein form. It is diagnostic only and not correct at the exponent edges. It moves 2B from 103.33 to 103.53 tok/s (e763 104.46) and 4B from 64.78 to 65.23 (e763 65.81). So exact general division explains about 0.2 of the 2B gap and about 0.45 of the 4B gap. The rest of the 2B gap was the 16-op reciprocal.
  • The host thread is busy for 47-68% of each decode token's wall time on both drivers (per-thread /proc CPU over 64 tokens), so the remaining gap is GPU kernel time, not driver CPU time.

This commit lets the hardware rcp produce the edge cases: a Newton step, the hardware value where the step is NaN, then the canonical NaN. That is 7 ops instead of 16.

  • tests/div_window.py against ed28f1d and against the correctly rounded FTZ oracle, over all 2^32 reciprocals and 2^32 hashed quotients: 0 mismatches. precise_math.py and ulp_bound.py pass (max 0 ulp).
  • d64 tok/s medians (ed28 / f24b / e763): 2B 103.19 / 104.35 / 104.75, ahead in 5 of 5 paired rounds (median +0.76); 4B 64.80 / 64.87 / 66.68. Greedy digests do not change.

The 2B gap to e763159 is now about 0.4 tok/s. The 4B gap (about 2.7%) is general division.

@joshuaswarren

joshuaswarren commented Oct 7, 2026 •

Copy link
Copy Markdown
Author

Two commits joined this layer after the description was written:

Commit Change
4d31d407776 honeykrisp: fold sub-format lea offsets into the access index on single-cluster G13 Cooperative-matrix loads reach agx_nir_lower_address as ulea(base, byte_offset, 0). They fell back to a materialized 64-bit address with index 0; now they index by the offset scaled down to elements. New agx_device_key bit fold_subformat_address, on for single-cluster, single-die G13 only.
0d0abdbee6c agx: test the sub-format address fold LowerAddress.FoldScalesByteOffsetToElements and LowerAddress.NoFoldMaterializesTheSum: the index is the byte offset shifted down by the format shift (signed for ilea), the base is kept; without the key bit the sum is materialized.

Measured on the gated build against the same tree without it, three rounds per cell, with bit-identical output on both chips (GEMM output hashes and 21 MLX matmul cases in f16, bf16 and f32; on the M1 Max also the Qwen3.8-2B 4-bit prefill logits and 32 greedy tokens):

Chip Fold Result
M1 (G13G) on dense f16 a @ b 4096^3: 1.669-1.695 -> 1.857-1.892 TFLOP/s (+11.5 %); MLX 4096^2 f16 1.686 -> 1.941 (+15.1 %), f32 1.124 -> 1.240 (+10.3 %), bf16 unchanged; shader-db of the GEMM kernel 375 -> 353 instructions, 111 -> 95 GPRs, 896 -> 1024 threads
M1 Max (G13X) off shader-db identical to the base; every cell within run-to-run spread

Applied to every chip, the same fold lost 13-17 % on dense f16 GEMM on the M1 Max, which is why it is keyed per device. Receipt: https://github.com/joshuaswarren/omarchy-mlx/tree/main/receipts/2026-10-07-coopmatload-lowering-per-device

Build and test on the M1 Max (aarch64, the driver's meson suite): both LowerAddress tests pass. Three LowerMathTest pins (DivDefaultFastSequencePinned, DivFtzModeUnchanged, FrcpDefaultSequencePinned) fail on this head. They pin the op counts from before 2e7cd6b2751, 9fad121d12e and 7df67055d04 changed the division and reciprocal lowering; a follow-up updates them.

@joshuaswarren

Copy link
Copy Markdown
Author

G14 (M2 Max, Apple M2 Max (G14C B1)) results for the division and log() changes, driver built from a4648d3 (same driver code as the current head 64edca0):

  • tests/precise_math.py: 0 failures in every category, 262,144 pairs.
  • tests/ulp_bound.py: division and reciprocal max error 0.0 ulp, 0 results over 2.5 ulp, 0 special-value mismatches, 328,192 pairs.
  • div_window.py: 2^32 quotients + 2^32 reciprocals, 0 bitwise mismatches against the correctly rounded (FTZ) oracle and 0 against the pre-fix build (01de043).
  • log_sweep.py over all 2,130,706,432 positive normal fp32 inputs: log max 0.5004 ulp with 3,448 results above 0.5 ulp; log2 max 0.5025.

These are the same results as on G13C (M1 Max) and G13G (M1), so the reciprocal and log() changes are now checked on all three GPU generations.

@joshuaswarren

Copy link
Copy Markdown
Author

Pushed bbbfa36 (hk: launch buffer copies and fills without an internal full barrier) on top of 64edca0.

Why: a per-commit bisect of Qwen3-4B 4-bit decode on G13C (M1 Max, 64 tokens, 5 paired rounds) found two commits that slow it down. 43b2756 (division) costs -3.09 tok/s, and later division commits on this PR recover most of that. 934b7c3 costs -1.22 tok/s (0/5 rounds positive) and changes no shader. Since 934b7c3, every AGX_BARRIER_ALL precomp launch flushes the CDM caches before it and again before the next launch. vkCmdCopyBuffer2, vkCmdUpdateBuffer and vkCmdFillBuffer passed AGX_BARRIER_ALL, although the application already orders transfers with vkCmdPipelineBarrier2, which sets cdm_barrier_pending. These four launches now use AGX_BARRIER_NONE. Internal draw-time launches keep the full barrier.

Results on G13C, 64edca0 vs bbbfa36, 5 paired rounds:

model CDM flushes per 64-token decode tok/s (median) paired gain rounds positive
Qwen3-4B 4-bit 55,694 -> 46,520 (60,066 dispatches) 65.20 -> 66.02 +0.96 4/5
Qwen3.5-2B 4-bit 37,376 -> 35,744 (39,470 dispatches) 104.39 -> 104.97 +0.65 5/5

An earlier run of the same change (identical code, different comment) gave 4B +1.61 (5/5) and 2B +0.41 (9/10 over 10 rounds). Greedy output digests are unchanged for the 2B, 4B and 9B models. No shader changes. G13G uses the deferred CDM flush path by default; a greedy-digest check there is next.

@joshuaswarren

Copy link
Copy Markdown
Author

Pushed two commits on top of bbbfa36:

  • ae546fc nir/opt_large_constants: allow a re-store of the same constants from another block
  • fc8f604 hk: compute-shader large constant arrays as constant data

Why: Honeykrisp had nir_opt_large_constants commented out, so nir_lower_vars_to_scratch gave every invocation its own scratch copy of any indirectly indexed constant array of 256 B or more. llama.cpp's IQ1_S/IQ2 grid tables hit this with 4-8 KiB of scratch per invocation. Compute shaders now read such arrays from a per-shader constant buffer through the root table, and the load offset is clamped into the variable. HK_LARGE_CONSTANTS=0 restores the old lowering and is part of the compiler-flags cache key.

The NIR change is needed because glslc -O emits the grid as a function variable with the constant array as initializer and also stores the same array into it in a later block, so the pass saw constant stores in two blocks and skipped the variable. A store in another block is now accepted only when it leaves the collected bytes unchanged. Two new NIR unit tests cover the same and the different re-store; nir_tests 6230/6230.

Results on G13C (M1 Max), driver at this commit:

check default (new) HK_LARGE_CONSTANTS=0
llama-bench Llama-3.1-8B IQ2_M, decode tg32 12.78 tok/s (3 earlier rounds 12.74-12.89) 0.94-0.95 tok/s
same, prompt pp512 149.6 tok/s 123.5-124.2 tok/s
iq1_s mat-vec (MUL_MAT) 2849 instrs, 95 GPRs, 1024 threads, 0 scratch 6141 instrs, 255 GPRs, 384 threads, 4096 B scratch
iq2_s mat-vec 1816 instrs, 0 scratch 6967 instrs, 8192 B scratch
test-backend-ops MUL_MAT / MUL_MAT_ID 1684/1684, 939/939 1684/1684, 939/939

IQ mat-vec kernels are 5-24x faster (MUL_MAT_ID iq2_xs 2290 -> 102 us). With HK_LARGE_CONSTANTS=0, the llama.cpp IQ shader disassembly is identical to 64edca0. tests/large_constants.py gives 0 mismatches of 131,072 for both settings, and 0 scratch by default, on G13C, G13G (M1) and G14C (M2 Max). MLX decode shaders for Qwen 2B, 4B and 9B are identical with and without the change on all three chips, and greedy outputs are unchanged.

@joshuaswarren

joshuaswarren commented Oct 7, 2026 •

Copy link
Copy Markdown
Author

Pushed 135d870 (hk: expose VK_KHR_shader_bfloat16 with bf16 cooperative matrices) on top of b648043.

Why: Honeykrisp advertised fp32 and fp16 cooperative-matrix A/B only, so bf16 GEMMs had to stage every tile through shared memory and widen bf16 with ALU ops; MLX's bf16 matmul ran 1458 instructions per shader against 337 for the direct f16 route. This adds VK_KHR_shader_bfloat16 (shaderBFloat16Type, and shaderBFloat16CooperativeMatrix with cooperative matrices; no dot product) and bf16 A/B with fp32 C at 8x8x8 and 16x16x16. bf16 fragments stay 16-bit through load/store/extract/insert; MulAdd widens them exactly to fp32 and runs the fp32 A/B simd_matrix_fmadd32 form. Conversions and element-wise ops on bf16 matrices go through fp32 in both the hardware and software cooperative-matrix lowerings. bf2f is a shift; f2bf rounds to nearest even in integer ALU with NaN quieted (Mesa's common lowering truncates).

Correctness:

  • Vulkan CTS 9335a4e4e455 (2026-10-02), every dEQP-VK.glsl.bfloat16.* and dEQP-VK.compute.pipeline.cooperative_matrix.*bfloat16* case on G13C: 121 Pass, 0 Fail, 1650 NotSupported (NV_cooperative_matrix2, EXT_cooperative_matrix_maintenance1, NV cooperativeMatrix, shaderSplitBarrier, shaderFloat8, shaderBFloat16DotProduct, unadvertised type combinations). Passing groups include matrixmuladd bfloat16_float32 (row/column major, array, min/max, push, stride0), convert_base, multicomponent, composite, constant, length, and the glsl bf16 compute/vertex/fragment tests.
  • The 16,169 non-bf16 dEQP-VK.compute.pipeline.cooperative_matrix cases: 520 Pass / 15,649 NotSupported / 0 Fail both before (b648043) and after.
  • f32 <-> bf16 conversion on the GPU over 4,587,584 inputs (every bf16 pattern, ties and ties +-1 ulp, specials, 2^22 random): 0 mismatches against round-to-nearest-even and exact widening.
  • the driver's meson test suite with assertions on: 5/5.

Speed, bf16 GEMM with direct bf16 cooperative-matrix loads vs the current staged kernel, output bits identical on every element:

a@b 4096^3 a@b.T 4096^3 a.T@b 4096^3
G13G (M1) 0.595 -> 1.722 TFLOP/s 0.722 -> 1.579 0.507 -> 1.623
G13C (M1 Max) 2.40 -> 5.65 2.90 -> 5.88 2.05 -> 3.47

Existing fp16/fp32 shaders compile to the same operations; MLX decode greedy outputs are unchanged.

@joshuaswarren

Copy link
Copy Markdown
Author

Pushed a6cbc830bbf to honeykrisp-omarchy-v3 (fast-forward from 6543eeb7df7): the dependency-tracked CDM barrier is now the default on G14X of generation 14 (M2 Pro/Max/Ultra class). Multi-cluster generation 15 and later parts also map to AGX_CHIP_G14X; they keep the per-launch barrier. HK_PERFTEST=alwayscdmbarrier still forces the per-launch barrier everywhere, trackcdmbarrier opts other chips in.

Tested on an M2 Max (G14C) only. Tracked vs per-launch barrier, same driver:

  • 2048-token greedy decode, 2 rounds: digests equal on Qwen3.5-9B, Qwen3-4B and Qwen3.5-2B. Decode tok/s 9B 24.2 -> 26.2, 4B 39.1 -> 41.3, 2B 49.6 -> 55.1.
  • Qwen3.5-9B, 5 paired rounds, 256 tokens: +8.4%, 5 of 5 rounds positive, digests equal.
  • CTS, 20,683 cases (bf16 + precision + cooperative_matrix list and the compute.pipeline cases): identical statuses in both modes (11,984 Pass, 8,699 NotSupported, 0 Fail).
  • precise_math and ulp_bound: 0 failures in both modes. div_window over all 2^32 pairs with tracking: 0 mismatches.
  • The built default ICD (not only trackcdmbarrier on v3): Qwen3-4B, 3 rounds, tok/s medians v3 default 59.50, v3 + trackcdmbarrier 62.76, new default 62.77, new default + alwayscdmbarrier 59.52; all 12 runs digest b241c0d8.

The gen-15 negative branch is not run (no hardware); it is a plain && on params.gpu_generation.

@joshuaswarren joshuaswarren changed the title asahi: M1 compute speedups and the deferred CDM barrier on G13G (stacked on #3, #4, #5) honeykrisp: M1 compute speedups and the deferred CDM barrier on G13G (stacked on #3, #4, #5) Oct 8, 2026
joshuaswarren added a commit to joshuaswarren/mesa-1 that referenced this pull request Oct 8, 2026
DivDefaultFastSequencePinned and FrcpDefaultSequencePinned counted the
ALU ops of the e763159 division and reciprocal. Every exact lowering
since 43b2756 emits other ops, so agx_tests has failed these three
pins on the PR omacom#6 head (found by w7Q, the driver's meson suite: 61/64).
The bits of division and reciprocal are pinned on hardware by
tests/precise_math.py, ulp_bound.py and div_window.py. What this file
can pin is the float-control contract: DenormFlushToZero emits exactly
the default lowering. Compare the two op histograms for fdiv and frcp
instead of fixed counts.
@joshuaswarren
joshuaswarren force-pushed the honeykrisp-omarchy-v3 branch from a6cbc83 to 66cb84f Compare October 8, 2026 13:52
@joshuaswarren

Copy link
Copy Markdown
Author

Pushed 6ba7b69821d (spirv: make image texels available after the write and visible before the read) to honeykrisp-omarchy-v3, fast-forward from 66cb84fd431.

It fixes 30 deterministic CTS failures on G13C (M1 Max): dEQP-VK.memory_model.message_passing.ext.u32.coherent.*.{device,queuefamily}.payload_local.image.guard_local.*.comp. OpImageWrite ... MakeTexelAvailable had its availability barrier placed before the store, so nir_lower_memory_model left the store non-coherent. The failing and the passing variants differ only by the coherent flag on the image write in the final ISA; four cache-flush variants did not help. The change swaps the placement for image reads and writes (write made available after, read made visible before); atomics are unchanged.

Results, same driver with and without the change (kernel 12.6 prerelease):

  • G13C: the 30 cases pass 3 of 3 runs (they failed 30 of 30 in 6 of 6 runs before); memory_model + subgroups.basic, 18,600 cases: 2,283 Pass, 16,317 NotSupported, 0 Fail, 30 Fail to Pass and 0 Pass to other; the 20,683-case bf16 / precision / cooperative_matrix / compute list: 0 status changes.
  • G13G and G14C: 30 cases pass 3 of 3, memory list 0 status changes (both already passed without the change), 20,683-case list 0 status changes.
  • Decode (G13C, Qwen3.5-4bit): all 74 shaders of a 16-token run are identical with and without the change; 256-token digests equal, +0.2% paired (noise).
  • Two unit tests added to avail_vis.cpp (opimageread_vis, opimagewrite_avail): they assert the order, fail on the parent commit and pass with the change; 27 of 27 spirv_tests pass.

Not shown: other drivers (the NIR is identical for them, I ran none), and why G14C passes the 30 cases without the change.

@joshuaswarren

Copy link
Copy Markdown
Author

Pushed 02c03c9ce65 to honeykrisp-omarchy-v3 (fast-forward from 6ba7b69821d): agx: hoist compute device loads over independent ALU and barriers, on by default for G13.

What it does. A compute-only pass (agx_hoist_loads) moves device loads, uniform loads and workgroup-memory loads up over independent ALU work and barriers. A load crosses a uniform store only when the two address windows (16 halfwords at the store index) are provably disjoint; a non-immediate index counts as a conflict. 10 unit tests (test-hoist-loads.cpp); agx_tests 73/73.

Gate. On by default for gpu_generation 13. Generation 14 is opt-in with AGX_HOIST_LOADS=1. AGX_HOIST_LOADS=0 turns it off everywhere. The setting is bit 37 of hk_physical_device_compiler_flags(), so it is part of the pipeline cache UUID and the disk cache key: the off switch never gets a shader cached with the pass on. One helper (hk_hoist_loads_enabled) serves both the flags and hk_compile_nir. All on/off arms below ran with MESA_SHADER_CACHE_DISABLE set.

Gain (build hoist05, the final pass logic; the pushed commit differs only in the cache-key bit and the gate helper).

Chip Workload Result
G13C (M1 Max) 256-token decode, 5 interleaved rounds 4B 4-bit +5.2% (5/5 positive), Qwen3.5 4-bit +6.2% (5/5)
G13C 2048-token decode Qwen3.5 4-bit +4.6%, digests equal
G13G (M1) 2048-token decode Qwen3.5 4-bit +2.0% (2/2), 4B +1.0% (2/2), digests equal

Correctness, default vs AGX_HOIST_LOADS=0, same ICD, identical results in both arms.

Check G13C G13G G14C (opt-in arm)
450-cell dense matmul sweep, bit identity 450/450 450/450 450/450
batch-row sweep clean clean clean
8 MLX test suites identical identical 7/8 identical; the 8th (matmul_family) fails in both arms on a stale test binary, and 33/33 pass with the current binary under both arms
registers / spills (16-token decode run) no register gained, no spills (max 189) none (max 177) none (max 189)
CTS slice, 12,000 cases (synchronization, synchronization2, subgroups, compute shader_object) identical statuses identical statuses not yet run (queued)
ROPE / CPY / SOFT_MAX, 6 interleaved passes none slower than -3% one of 60 ROPE cases at -5.6% queued

Earlier builds of the same rule (before the pure-operation and uniform_store changes): memory_model and subgroups.basic (18,600 cases) and a 20,683-case list with identical statuses on all three chips; precise_math and ulp_bound 0 failures; llama.cpp Qwen3-1.7B / Qwen3-8B decode on G13C +7.9% / +1.7%.

The same gates are being rerun on the pushed tree (hoist07 ICD, G13C first); I will post the deltas if anything differs.

@joshuaswarren

Copy link
Copy Markdown
Author

Pushed 9ba2bbe75ce to honeykrisp-omarchy-v3 (fast-forward from 02c03c9ce65): two commits that ship together.

  • c8a08070f12 hk: default the device heap to 90% of system memory. The heap was 50% of MemTotal: 7.55 GiB on a 16 GB M1, so a model of about 8.2 GB could not load with 13 GB free. heap_memory_percent in ~/.drirc still overrides it per application.
  • 9ba2bbe75ce hk: refuse device memory that would leave too little system memory. heapBudget is advisory and vkAllocateMemory only checked the static heap size. GPU memory cannot be evicted or swapped. hk_AllocateMemory now reads MemAvailable and returns VK_ERROR_OUT_OF_DEVICE_MEMORY when an allocation of 1 MiB or more would leave less than max(1 GiB, 2% of RAM). Allocations under 1 MiB bypass the check, so the compositor's small allocations still succeed below the reserve. Imported memory is not checked. If /proc/meminfo is unreadable only the heap limit applies.

Why the reserve. Without it, two processes that each fill the old 50% heap (7.55 GiB each on the 16 GB M1) took MemAvailable to 0 kB, Unevictable to 14.4 of 15.1 GiB, and the kernel OOM killer killed quickshell and the Hyprland unit (journal: heap-alloc2 invoked oom-killer ... global_oom, task=quickshell). Swap stayed unused: GPU memory is unevictable, so zram does not help.

With the reserve (G13G, 16 GB M1, desktop running; test processes in cgroup scopes with no swap and oom_score_adj 1000; 256 MiB host-visible chunks filled with random bytes).

Setting One process Two processes at once
0.85 (heap 12.84 GiB) refused at 11.00 GiB, MemAvailable 1.25 GiB both refused at 5.50 GiB, 1.24 GiB
0.90 (heap 13.60 GiB) refused at 11.25 GiB, 1.05 GiB both refused at 5.50 GiB, 1.24 GiB

Same pair with 32 MiB chunks, 0.90, ICD with the final reserve code: one process refused at 10.72 GiB (343 calls), minimum MemAvailable 1.03 GiB; two at once both refused at 5.34 and 5.31 GiB, minimum 1.01 GiB. All processes exited 0, no oom-kill line in journalctl -k, same Hyprland pid. A 0.5 s sampler saw MemAvailable at 0.84 GiB once, 0.16 GiB below the reserve. [Inference] two concurrent allocators can each overshoot by about one allocation, plus desktop use between the check and the page population. Nothing came near 0 kB.

On G13C (64 GB M1 Max, cgroup caps): one process holds 46.00 GiB (heap 46.78 GiB at 75%); a second process capped at MemAvailable minus 0.5 GiB was refused at MemAvailable 1.35 GiB; two more at once were both refused at 27.75 GiB with MemAvailable 1.49 GiB; 0 oom-kill lines, Hyprland unchanged.

heapBudget follows free memory (heap at 0.75): 8.86 GiB idle, 6.15 GiB with 4 GiB held by another process, 3.38 GiB with 8 GiB held.

With the reserve in place the percent is no longer what bounds a process on a quiet 16 GB host (about 11.0 to 11.25 GiB of GPU memory with the desktop holding about 3 GiB): the reserve is. A dedicated inference host can raise heap_memory_percent; one process is also bounded by the chip's GPU address window, which is still being measured (on the M2 Max a single process stopped at 63.75 GiB with VK_ERROR_OUT_OF_DEVICE_MEMORY while 25.7 GiB was free).

The per-call cost of the added /proc/meminfo read is queued as a paired run and will follow in a reply.

@joshuaswarren

Copy link
Copy Markdown
Author

Per-call cost of the reserve's /proc/meminfo read, as asked in review. G13G (16 GB M1), 32 MiB allocations (128 per run, 4 GiB), 5 alternating rounds, test process in a cgroup scope, only the vkAllocateMemory calls timed:

ICD per call (mean of 5 runs) range
without the reserve (hoist05 build) 1934.6 us 1899.8 to 2025.1 us
with the reserve (heapres02 build) 1929.4 us 1912.3 to 1980.0 us

No measurable difference: the 32 MiB page population at bind time dominates (about 1.9 ms per call), and one /proc/meminfo read is well below the run-to-run spread (the first run of each arm is the slow one). Smaller allocations would see the read more, but they bypass the check below 1 MiB, and between 1 and 32 MiB the page population still scales with size.

@joshuaswarren

Copy link
Copy Markdown
Author

Pushed 2cbafec59b9 to honeykrisp-omarchy-v3 (fast-forward from 9ba2bbe75ce): agx: use the whole user address window for the heap.

Problem. One process could not map more than about 64 GiB of device memory, whatever heap_memory_percent said. On a 94 GiB M2 Max with the heap at 0.95, vkAllocateMemory returned VK_ERROR_OUT_OF_DEVICE_MEMORY at 63.75 to 63.94 GiB with 25 GiB of RAM free, and the driver printed Failed to allocate BO VMA.

Cause. The kernel reports a 512 GiB GPU address window on all three chips (vm_end 0x7fffff8000, kernel minimum 0.5 GiB; read with the read-only GET_PARAMS ioctl on G13C, G13G and G14C). agx_open_device reserves 32 GiB at the top for the kernel and starts the user heap at 72 GiB. It then rounded the window to powers of two and halved it twice (once for the read-only sparse shadow, again when the start equals the size), leaving [128 GiB, +64 GiB). No kernel change is needed.

Change. agx_sparse_layout() picks the power of two for the sparse read-only offset that leaves the largest heap with its shadow still below the kernel window. With the numbers above: heap from 72 GiB, offset 256 GiB, heap 152 GiB (2.4x). The highest address in use becomes 408 GiB; the descriptor address fields in cmdbuf.xml (36-bit shr(4), 36-bit shr(3) = 39 bits = 512 GiB) still fit. Four unit tests cover the layout rule; libasahi_tests 9/9 on G13C.

Hardware proof (ICD vaw01 against the old layout heapres02).

Chip Check Result
G14C (M2 Max, 94 GiB) one process, heap 0.95 (89.5 GiB), 256 MiB chunks, cgroup cap, no swap, desktop up allocated 87.75 GiB, then VK_ERROR_OUT_OF_DEVICE_MEMORY at MemAvailable 1.73 GiB (the free-memory reserve), no Failed to allocate BO VMA, 0 oom-kill lines. The old layout stopped at 63.94 GiB.
G13G (M1, 16 GB) sparse_resources CTS (buffer, image_sparse_binding, image_sparse_residency.2d, shader_intrinsics, mipmap_sparse_residency) 7,263 cases with a status in both layouts, 0 differences (the 26 buffer fails are identical in both)
G13G api.buffer, memory.allocation.basic, buffer_device_address 3,179 cases, 0 differences
G13G one process at 0.95 (heap 14.35 GiB); two processes at 0.90 with 256 and 32 MiB chunks stop at the reserve (12.50 GiB one process; 6.12 to 6.25 GiB each for two), MemAvailable 0.97 to 1.03 GiB, 0 oom-kill lines
G13C (M1 Max) the same sparse_resources CTS 7,263 cases, 0 differences
G13C MLX test suites with vaw01 and heapres02 matmul_family 34/34 cases, 82,942,953 assertions pass in both; fast_ops has the same 8 failed assertions in both layouts (sdpa vjp and fused gdn vjp tolerances, not related to addresses)

Not done. The G14C CTS (sparse_resources) and MLX suites with vaw01 are queued behind other work on the M2 and have not run, so G14C is covered by the 87.75 GiB allocation only. Six of the eight MLX suites were not in the test build used on G13C.

@joshuaswarren

Copy link
Copy Markdown
Author

Open gate on 2cbafec59b9 (the address-window commit): do not pin or package this head yet (omarchy-pkgs #894 stays on 02c03c9ce65e).

On a 16 GB M1 (G13G) I ran one MLX process (omarchy backend, 256 MiB bf16 arrays) until the free-memory reserve refused an allocation, then freed everything and called clear_cache, then asked for 1 GiB again.

ICD refusal recovery (1 GiB allocation + matmul)
vaw01 (this head: reserve + address-window change) VK_ERROR_OUT_OF_DEVICE_MEMORY after 11.75 GiB, in 0.000 to 0.003 s failed: same error, in both of two runs
stock system Mesa 26.2.4 (no reserve, 50% heap) same error after 7.50 GiB, in 0.014 s ok

The refusal itself is as designed (a typed error, immediate, desktop untouched). The recovery failure has not been attributed. Two causes are possible and I have not separated them: MLX's allocator keeps freed buffers pinned for one more generation, so the memory is still held when the reserve is checked (the stock arm also showed MemAvailable at 4.19 GiB after "recovery"), or the new layout or the reserve keeps memory that the old path returned. A paired run on the same wheel with vaw01, the earlier heapres02 build (reserve, old layout) and stock, with a drain-and-retry step, is queued on the same host. If vaw01 is the cause I will fix or revert the commit on this branch.

Receipt: ~/mesa-work/foundation/mlx-reserve-jwm1-linux-20261010T045905 on that host.

@joshuaswarren

Copy link
Copy Markdown
Author

Recovery gate: fixed in 4c1ca73cddb (now the head of honeykrisp-omarchy-v3, fast-forward from 2cbafec59b9).

Cause: freed device memory is parked in the driver BO cache and counts as used in MemAvailable. The cache is trimmed only on a later BO free or when agx_bo_create fails. The reserve check runs before agx_bo_create, so after an application freed gigabytes the check saw almost no free memory and refused while the memory sat in the cache. This pin is not new: a driver-only probe (allocate 10 GiB, free, poll MemAvailable for 8 s) shows no release on all three builds, including stock Mesa 26.2.4 (10 of 10 runs). The VA-window commit is not involved.

Fix: when the check would refuse, call agx_bo_cache_evict_all() and read MemAvailable again once.

Evidence, 16 GB M1 (G13G), MLX with 256 MiB arrays, cgroup-capped, refusal at 11.75 GiB:

  • Immediate recovery after the typed VK_ERROR_OUT_OF_DEVICE_MEMORY (1.6 GiB allocation plus a 2048x2048 matmul): new head 3 of 3 (0.58-0.59 s, MemAvailable 10.8 GiB). 2cbafec59b9 0 of 3.
  • Two processes, 32 MiB chunks: both refused at 5.97 GiB with MemAvailable 1.00 GiB, no oom-kill line, desktop up.
  • CTS, new head against 2cbafec59b9, same status for every case: sparse_resources 7,263 cases on G13G and on G14C; api.buffer 3,076 and memory.allocation.basic 102 on G13G. 26 sparse_resources failures and an abort in buffer_device_address.capture_replay_stress are the same in both arms.

Still open before a package pin: a recipe build at this head, and the G14C MLX suites on it. omarchy-pkgs #894 stays on 02c03c9ce65e until then.

@joshuaswarren

Copy link
Copy Markdown
Author

Dependency order correction. Nothing here says this PR is ready.

The full Vulkan conformance run of this PR against its base cannot qualify it as planned. The first chunk of the run failed on two machines (a 16-inch M1 Max and a 13-inch M1) with the same error from the comparison gate: invalid baseline: dEQP-VK.memory.map_placed.unmap_reserve.size_1048576 Crash->Crash.

  • Current main crashes on that case. The fix is in honeykrisp: map placed memory at the allocation size #8 (placed memory mapping at the allocation size).
  • The head of this PR, f144c7949b7d, crashes on the same case, because it does not contain the honeykrisp: map placed memory at the allocation size #8 commits.
  • The gate treats a runtime error in the control arm as an invalid baseline, not as a pass, and it does not allow an exception. So the comparison of this PR against bare main is withdrawn. It was not run past the first chunk, and no result of it exists.

What this means for order. #8, #9 and #10 should merge before this PR, and #11 after it. Each of those three branches is small and independent of this PR. A merge of f144c7949b7d with the five commits of #8, #9 and #10 has no textual conflict (7 files are changed by both sides and merge cleanly; checked file by file).

What runs now. A control build (main plus #8, #9 and #10, 4ff9fc5f15e8) is compared with a candidate build (this PR plus #8, #9, #10 and the one commit of #11, cb7830f581a1) on the same two machines, same boot for each chunk pair, over the whole Vulkan suite (3,253,584 cases). The control build ran the whole suite once on the M1 Max with no crash. The candidate minus control difference is this PR together with the #11 commit, so a difference cannot be assigned to one of them without a further bisect. The 55 cases that failed in the earlier wide run of an older head are re-checked by name in that run. No result exists yet.

This PR stays NOT READY TO MERGE. The two M1 machines have not run the whole suite on a build of this PR yet, and the status block above lists what was measured on which build.

@joshuaswarren

Copy link
Copy Markdown
Author

Status, 25 Oct: this PR has been idle since 15 Oct, and that is on me.

Where it stands. The source head f144c7949b7d passes the 48 cases that failed on the earlier head, on a native build and on a package build (16-inch M1 Max). The whole-suite table in the description was measured on the earlier build 69bde153405c, which had 55 regressions; the later commits address them, but I have NOT run the whole suite on f144c7949b7d.

Why it stalled. The base branch crashes in dEQP-VK.memory.map_placed.unmap_reserve.size_1048576 when it runs late in a whole-suite pass. The fix is #8 (three commits), which has had no review since I asked for a merge contact. My account has pull access only, so I cannot merge it.

Plan:

  1. Put the three honeykrisp: map placed memory at the allocation size #8 commits under this branch, so the suite baseline stops crashing. Main has not moved since 8 Sep, so there is nothing else to rebase onto.
  2. Rebuild the exact head from source on both machines.
  3. Run the whole Vulkan CTS, this head against main, on the 13-inch M1 and the 16-inch M1 Max (about 10 GPU hours each), plus the seven shader_object.performance cases.
  4. Fix any regression it finds and rerun the affected groups.
  5. Post the table with the exact head here and mark the PR ready.

Results: I expect the first full numbers by 27 Oct and will post them here either way. If someone on the team can review #8 in the meantime, that is the one change that unblocks the baseline.

ulp_bound.py divided the error by np.spacing of the float64 reference.
np.spacing is negative for a negative reference, so the distance came out
negative and any error on a negative reference passed the > 2.5 check; for
a positive reference the float64 spacing made every inexact result fail
instead. Use the absolute float32 spacing of the reference.

Add a --self-test mode that needs no GPU: it checks the distance for 0, 1
and 3 ULP errors in both directions on positive and negative references,
that a 3 ULP error on a negative reference exceeds 2.5, and that the old
expression gives a negative distance for it.

MEASURED: the self test passes; the old expression gives -1610612736 for a
3 ULP error at -1.5, which passes the old check.
NOT measured: the GPU run of ulp_bound.py with the corrected distance; the
numbers from the old script are not to be relied on.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
@joshuaswarren

Copy link
Copy Markdown
Author

Update, 25 Oct: the head of this branch is now 8fe35ace2bd126b8850e7896d30cca3b86c2b3a3. The one new commit changes only tests/ulp_bound.py; no driver source changed since f144c7949b7d.

A reviewer found that the script's distance check let errors on negative references through. It divided by the float64 spacing, which is negative there. The commit uses the absolute float32 spacing and adds a --self-test mode that needs no GPU.

MEASURED: the self test passes. NOT measured: any GPU run of the corrected script yet. The division numbers from the old script are not to be relied on until that run is done. The whole-suite results in the description were measured on earlier builds, not on this head.

Measure positive binary32 ULPs, reject non-finite results for finite references, and add seven CPU controls. Restore unchanged public shader/runner fixtures from Joshua Warren source f144c79; provenance is recorded beside the tests. No driver code changes.

Generated-by: LLM
@greptile-apps

greptile-apps Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[High impact] No reviewed code finding remains blocking, but the PR's stated GPU test gate is still pending.

Summary

This PR adds cooperative matrices, compute speedups, math fixes, and memory changes for Honeykrisp.

Since the previous review:

  • Apple GPUs can run cooperative matrix shaders on hardware or through a software fallback.
  • M1 compute shaders use shorter load, address, and scheduling paths.
  • Compute batches defer or track barriers according to the chip and workload.
  • The driver reserves free memory and gives large requests more address space.
  • The compiler lowers fp32 division, logarithms, and trig functions with explicit rounding paths.
  • Matching constant-table stores can stay in shared constant data.
  • SPIR-V image reads and writes place texel visibility barriers on the right side of the access.
  • Vulkan sync-object waits can poll briefly before blocking.

No new actionable issue was established. This review does not replace the exact-head GPU testing still required by the PR description.

Reviews (2) · Last reviewed commit: "agx: keep the sign of shared-memory cons..." · Reviewed by Greptile

Comment thread src/asahi/compiler/agx_compile.c
Comment thread src/asahi/compiler/agx_nir_lower_address.c
Comment thread src/asahi/vulkan/hk_shader.c
Comment thread src/vulkan/runtime/vk_drm_syncobj.c Outdated
Comment thread tests/cmshape2.c
Comment thread tests/ulp_bound.py Outdated
… allocated

hk_upload_shader created the preamble buffer and the shader constants buffer
without checking agx_bo_create, which returns NULL after its allocation
retries fail. Large constant arrays take the constants path by default now, so
loading such a shader under memory pressure dereferenced NULL. Return
VK_ERROR_OUT_OF_DEVICE_MEMORY from hk_upload_shader and pass it back through
both callers (compile and deserialize). The shader's destroy path already
tolerates a NULL buffer.

NOT measured: an allocation failure on hardware; the change is a null check.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
With HK_SUBMIT_POLL_US set, the bounded poll ran for its whole budget even when
the caller's deadline was zero or already past, so vkGetFenceStatus and short
waits could spin for the full budget. Start the poll only while the caller's
deadline is in the future and cap the poll deadline at it. The option is off by
default.

NOT measured: the wait latency of short waits with the option on.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
…x case

ulp_bound.py locked /tmp/m1-gpu.lock while the other harnesses lock /tmp/gpu.lock, so
they did not exclude each other; use /tmp/gpu.lock. cmshape2.c created uint8_t
storage buffers for the u8 element type without enabling storageBuffer8BitAccess; enable
it when the element type is u8.

NOT measured: the u8 case on hardware.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
…ment before the sub-format fold

Two address folds could change the address they were meant to keep.

agx_peel_const read constant addends as unsigned. A 16-bit add of 0xfff0 is
x - 16, but it was summed as +65520, so the shared-offset fold gave the access
the index x + 65520, and the hardware index add does not wrap. Read the
constants signed.

The sub-format fold in agx_nir_lower_address scales a byte offset down to an
element index. That equals the byte offset only when the offset is a multiple of
the element size; an access that is aligned as a whole does not say so (base P + 2
with offset 2 for a 32-bit access is aligned, and the fold drops the 2). Fold
only when the offset is provably a multiple (constants, shifts, multiplies, adds,
masks, widenings); otherwise keep the 64-bit sum.

Tests: SharedFoldKeepsNegativeAddends, SubformatFoldNeedsProvablyAlignedOffset and
SubformatFoldKeepsProvablyAlignedOffset in test-lower-math.cpp. agx_nir_fold_shared_offsets
is exported for them.

MEASURED: not yet run. NOT measured: the performance of the cooperative matrix
sub-format fold with the new guard (it was +15 % on the single-cluster M1 and -13
to -17 % on the M1 Max; a pattern the guard does not recognize loses the fold).

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
@joshuaswarren

Copy link
Copy Markdown
Author

GREEN at 9323171

Delta 5ce4451..9323171 (4 commits, 8 files) read in full.

  • hk_shader.c: hk_upload_shader now returns VkResult; the code buffer and the constants buffer each return VK_ERROR_OUT_OF_DEVICE_MEMORY when agx_bo_create returns NULL before the memcpy, and the only two callers (hk_compile_nir, hk_deserialize_shader) return the result.
  • vk_drm_syncobj.c: one clock read; the poll is armed only while now < the caller's deadline and its end is capped at that deadline, so a zero or expired deadline skips polling.
  • agx_compile.c: agx_peel_const reads constants signed. Its only caller is the shared-memory fold, which works on 16-bit addresses, so 0xfff0 now peels as -16 (the fold assumes the 16-bit sum does not wrap, as before, now with the right sign). agx_nir_fold_shared_offsets is exported through agx_nir.h for the test.
  • agx_nir_lower_address.c: the sub-format fold now requires scalar_multiple_of(offset, format_shift - shift, depth 4). I checked each case: constants by mask, ishl (a >= k true, otherwise recurse with k - a; a variable shift keeps the multiple), imul (either factor), iadd/isub (both), iand (mask with zero low bits, or either operand), and the widenings/mov; everything else is false, so the fold is skipped and the 64-bit sum kept. The required multiple (format_shift - shift) is in offset units, which is what the scaled-down index needs.
  • tests: the two new alignment tests and the negative-addend test assert the right things (aligned: one ushr; unprovable: none; shared fold totals {-16, 0}); ulp_bound.py now uses /tmp/gpu.lock like run_matrix2.sh, div_window.py and precise_math.py; cmshape2.c enables storageBuffer8BitAccess only for the 1-byte case.

Not checked: I did not build this head or run agx_tests (the author reports 76 of 76, with the two new tests failing on the old behaviour), no GPU run, and the speed effect of the stricter fold (the author lists it as not measured).

@joshuaswarren

Copy link
Copy Markdown
Author

GREEN at 9323171

Delta against 5ce4451: four commits, eight files (gh compare). I read all of it.

  • hk_shader.c: hk_upload_shader now returns VK_ERROR_OUT_OF_DEVICE_MEMORY when either buffer allocation fails (preamble/code and constants), and both callers (hk_compile_nir, hk_deserialize_shader) pass it up. The callers destroy the shader on failure, and hk_shader_destroy unreferences both buffers, which is safe with a NULL buffer (agx_bo_unreference returns early on NULL).
  • vk_drm_syncobj.c: the bounded poll starts only when the caller's deadline is in the future and is capped at it, so a zero or expired deadline skips the poll. I did not check the upper clamp on the poll budget, so I did not rule out overflow in now + budget.
  • agx_compile.c: agx_peel_const reads constants signed (nir_scalar_as_int), so a 16-bit 0xfff0 addend is -16, not +65520. agx_nir_fold_shared_offsets is exported for the test.
  • agx_nir_lower_address.c: the sub-format fold now needs scalar_multiple_of(offset, format_shift - shift, 4). I checked every case for soundness (it may only say yes when the low k bits are provably zero): a constant with the low bits clear; ishl by a constant (at least k, or the value is a multiple of 2^(k-a)) and by an unknown amount (a multiple stays one); imul when either factor is; iadd and isub when both are; iand with a mask whose low k bits are clear, or when either source is a multiple; widenings and mov pass through. Anything else returns false, which keeps the 64-bit sum.
  • Tests: SharedFoldKeepsNegativeAddends, SubformatFoldNeedsProvablyAlignedOffset (an offset loaded from memory must not be shifted) and SubformatFoldKeepsProvablyAlignedOffset (an ishl by 2 is folded) do what their names say. The harness changes (one GPU lock path, 8-bit storage feature for the u8 case) are as described.
  • The four commit messages contain no host names, paths or lane names.

Not checked: a build of this head, the test run (I did not compile Mesa), any GPU run, and the speed effect of the stricter fold.

@joshuaswarren

Copy link
Copy Markdown
Author

The branch is at 9323171. The six review threads are resolved. The latest code review found no new actionable issue. But the GPU test gate stays open. The results in the description belong to older builds. They do not qualify this head. I will tie any readiness claim to the source and library that were actually tested.

joshuaswarren added a commit to joshuaswarren/mesa-1 that referenced this pull request Oct 25, 2026
DivDefaultFastSequencePinned and FrcpDefaultSequencePinned counted the
ALU ops of the "hk: enable cooperative matrices by default on G14X" division and reciprocal. Every exact lowering
since "agx: flush the exact quotient in precise fp32 division, not the correction step" emits other ops, so agx_tests has failed these three
pins on the PR omacom#6 head (found by w7Q, the driver's meson suite: 61/64).
The bits of division and reciprocal are pinned on hardware by
tests/precise_math.py, ulp_bound.py and div_window.py. What this file
can pin is the float-control contract: DenormFlushToZero emits exactly
the default lowering. Compare the two op histograms for fdiv and frcp
instead of fixed counts.
@joshuaswarren

Copy link
Copy Markdown
Author

Partial whole-suite Vulkan CTS table for the stack, not final. Both runs are still going.

Same commits, same boot, paired chunks. Control is main 7cda7850ed plus #8, #9 and #10. Candidate is that plus the #6 head 518212284e. This table is for 518212284e. It is not for the current #6 head, which has later review fixes. A separate run for the current head is queued.

16-inch M1 Max 13-inch M1
Paired chunks done 2708 of 3284 2143 of 3284
Tests compared 2,692,008 2,136,647
Pass, control to candidate 890,296 to 892,281 766,165 to 768,076
Fail, control to candidate 777 to 742 741 to 744
NotSupported, control to candidate 1,800,930 to 1,798,979 1,369,733 to 1,367,821
Quality warning, control to candidate 5 to 6 8 to 6
Tests whose result changed 1,986 1,916

What changed:

  • NotSupported to Pass: 1,951 on the M1 Max and 1,912 on the M1. These are the cooperative matrix and replicated composite tests the stack turns on.
  • Fail to Pass: 34 on the M1 Max. I have not grouped them yet.
  • Pass to Fail: none on the M1 Max. Two on the M1, shader_object.performance.dispatch and shader_object.performance.dispatch_indirect.
  • Quality warning to Fail: one on the M1, shader_object.performance.draw_indexed_static_pipeline. On the M1 Max draw_static_pipeline went from Fail to Quality warning.

All of those changes are timing tests in the performance group. I have not repeated them, so I do not know whether they are noise.

NOT measured yet: the remaining chunks (about 576 on the M1 Max, about 1,141 on the M1), and any result at the current #6 head. Remaining groups include shader_object, transform_feedback, fragment_shading_rate, pipeline and renderpasses. No GPU timeouts so far.

@joshuaswarren

Copy link
Copy Markdown
Author

Update on the CTS runs. It is still partial. It has two parts, for two heads.

Part 1: the targeted set at the current #6 head 9323171c12. Both runs are complete: 366 of 366 chunks, 361,849 tests. The groups are compute, synchronization, synchronization2, memory_model, ssbo, ubo, reconvergence and subgroups. A seeded sample of 49 other chunks is included. Control is main plus #8, #9 and #10.

16-inch M1 Max 13-inch M1
NotSupported to Pass 1,911 1,911
Fail to Pass 30 0
Pass to Fail 0 0
Any other change 0 0

NOT measured at this head: everything outside those groups. The full runs below are for 518212284e.

Part 2: the whole-suite runs of 518212284e (before the review fixes), paired chunks done.

16-inch M1 Max 13-inch M1
Chunks done 3,047 of 3,284 2,304 of 3,284
Tests compared 3,019,115 2,296,110
NotSupported to Pass 1,951 1,951
Fail to Pass 34 0
Pass to Fail 0 2
Other changes 1 2

The only changes that are not gains are in shader_object.performance. That is 4 tests on the 13-inch and 1 on the 16-inch. These tests time draws and dispatches. On the 13-inch the comparison gate blocks on them, so the 13-inch run is paused at that group. I have queued a three times rerun of the two Pass to Fail cases. I will report it before anything else.

A decode benchmark A/B of the head 9323171c12 against f144c7949b showed no cost: the 4B model ran 55.3 tokens per second on both on the M1 Max, and the 2B model 92.0 against 92.1. That is a speed check, not a correctness result.

NOT measured yet: the last 237 chunks on the 16-inch, the last 980 on the 13-inch (240 of them in shader_object), and a whole-suite run at the current head.

@joshuaswarren

Copy link
Copy Markdown
Author

Final whole-suite result on the 16-inch M1 Max, for 518212284e (the #6 stack before the review fixes), not for the current head.

Same boot, both arms per chunk. Control is main 7cda7850ed plus #8, #9 and #10. Candidate is that plus the #6 head of that sha. All 3,284 chunks and 3,253,584 tests are complete in both arms.

Status Control Candidate
Pass 1,026,515 1,028,500
Fail 779 744
NotSupported 2,226,285 2,224,334
Quality warning 5 6

1,986 tests changed:

  • 1,951 NotSupported to Pass (cooperative matrix and replicated composite tests).
  • 34 Fail to Pass.
  • 1 Fail to Quality warning: shader_object.performance.draw_static_pipeline, a timing test.
  • 0 Pass to Fail. 0 other changes.

The comparison gate says ALLOW: regressions 0, invocation errors 0, errors 0, coverage 3,253,584 of 3,253,584. No GPU timeouts.

NOT measured: the 13-inch M1 run is still going. A whole-suite run at the current #6 head has not been made; the targeted set above covers its changed areas only.

joshuaswarren added a commit to joshuaswarren/mesa-1 that referenced this pull request Oct 25, 2026
DivDefaultFastSequencePinned and FrcpDefaultSequencePinned counted the
ALU ops of the "hk: enable cooperative matrices by default on G14X" division and reciprocal. Every exact lowering
since "agx: flush the exact quotient in precise fp32 division, not the correction step" emits other ops, so agx_tests has failed these three
pins on the PR omacom#6 head (found by w7Q, the driver's meson suite: 61/64).
The bits of division and reciprocal are pinned on hardware by
tests/precise_math.py, ulp_bound.py and div_window.py. What this file
can pin is the float-control contract: DenormFlushToZero emits exactly
the default lowering. Compare the two op histograms for fdiv and frcp
instead of fixed counts.
@joshuaswarren

Copy link
Copy Markdown
Author

Final whole-suite result on the 13-inch M1, for 518212284e (the #6 stack before the review fixes), not for the current head.

Same boot, both arms per chunk. Control is main 7cda7850ed plus #8, #9 and #10. Candidate is that plus the #6 head of that sha. All 3,284 chunks and 3,253,584 tests are complete in both arms. No GPU timeouts.

Status Control Candidate
Pass 1,026,545 1,028,495
Fail 746 749
NotSupported 2,226,285 2,224,334
Quality warning 8 6

1,955 tests changed: 1,951 NotSupported to Pass, and 4 tests in shader_object.performance. Those 4 are two Pass to Fail (dispatch, dispatch_indirect), one Quality warning to Fail (draw_indexed_static_pipeline) and one Quality warning to Pass (draw_static_pipeline). There are no changes in any other group.

The comparison gate says BLOCK for the whole run: regressions 3, errors 0, coverage complete. All three regressions are in shader_object.performance. These tests time shader object draws and dispatches against pipelines, and the harness is correctness only. So I excluded the 30 shader_object.performance cases of chunk 3 from that chunk's gate compare, and ran the rest of shader_object with that rule. This is an exclusion as timing tests, not a waiver. Their raw results are in the data. The full list is the 30 cases named dEQP-VK.shader_object.performance.*. Over those 30, the control has 21 Fail, 6 Pass and 3 Quality warning. The candidate has 24 Fail, 5 Pass and 1 Quality warning.

Rerun of the two Pass to Fail cases on the 13-inch, same boot, 12 runs per arm. dispatch: control 10 of 12 Pass, candidate 12 of 12 Pass. dispatch_indirect: control 8 of 12 Pass, candidate 11 of 12 Pass. Every failure was the message "Shader object dispatch was more than 5% slower than compute pipeline dispatch". The control fails as often as the candidate, so these look like timing noise. This is not a measurement of speed. draw_indexed_static_pipeline was not rerun.

NOT measured: a whole-suite run at the current #6 head. The targeted set above covers its changed areas only.

@joshuaswarren

Copy link
Copy Markdown
Author

Status, no change to head 9323171c (still MERGEABLE, no conflicts): this PR is waiting on two things. (1) Landing order: #10, #8 and #9 are all still open and must merge first, then #6. (2) Whole-suite CTS on this head: the completed whole-suite runs are for older 518212284e; current-head coverage (361,849 targeted cases per M1 chip, no Pass to Fail) is not a whole-suite pass, and the three M1 timing failures are not waived. Not superseded; staying open. Not ready to merge.

@joshuaswarren

joshuaswarren commented Oct 29, 2026 •

Copy link
Copy Markdown
Author

Correction for head 9323171c12c5aa6055f1031ea26c16c247191987: not ready to merge.

I withdraw the earlier 366-chunk / 361,849-case, no-Pass-to-Fail claim as qualification of this PR. Its control included #8, #9 and #10, while the candidate was PR #6 alone. That comparison changed more than #6 and does not isolate this change. CPU compiler tests and source review are not device qualification.

The corrected comparison keeps the same prerequisite stack in both arms:

The controlled run is incomplete. Its selected scope is 1,367 chunk pairs / 1,346,778 cases, not the full CTS. Completion counts are not a pass: every pair's gate JSON, driver identity and same-boot binding still need to be scored. No final corrected pass count or no-regression verdict is claimed here.

Valid provisional evidence is the matched prerequisite-stack check, not a result for #6, and the source-only review at this head, not GPU qualification. The old whole-suite results for 518212284e do not qualify the current head. The three shader-object timing regressions remain unresolved, not waived.

Landing order remains #10, #8, #9, then #6. No merge is requested until the controlled device gates and remaining coverage/hold decisions are complete.

Corrected-run provisional snapshot

The retained gate, last written at 2026-10-29T15:35:02.470819+00:00 and read at 2026-10-29T20:08:08.547446+00:00, has 669 of 1,367 chunk pairs complete: 665,231 of 1,346,778 selected cases have results in each arm. It records 1,912 status differences, zero regressions and zero invocation errors in those completed rows. The overall gate is BLOCK / INCOMPLETE, solely reporting unfinished coverage; this is not a completed no-regression result. The latest individual 1,000-case chunk is ALLOW, which does not authorize the whole run. There is no final result yet.

The source ancestry was checked: the control contains the exact #10, #8 and #9 heads, and the candidate contains both that control and #6 at 9323171c12c5aa6055f1031ea26c16c247191987. The CTS ELF SHA-256 is 550d04d3bc2b2164c3a74ae5f386f287a97bc2e3c2a311756838c994e9b50744; its recorded source commit has not been independently verified.

joshuaswarren added a commit to joshuaswarren/mesa-1 that referenced this pull request Oct 29, 2026
DivDefaultFastSequencePinned and FrcpDefaultSequencePinned counted the
ALU ops of the "hk: enable cooperative matrices by default on G14X" division and reciprocal. Every exact lowering
since "agx: flush the exact quotient in precise fp32 division, not the correction step" emits other ops, so agx_tests has failed these three
pins on the PR omacom#6 head (found by w7Q, the driver's meson suite: 61/64).
The bits of division and reciprocal are pinned on hardware by
tests/precise_math.py, ulp_bound.py and div_window.py. What this file
can pin is the float-control contract: DenormFlushToZero emits exactly
the default lowering. Compare the two op histograms for fdiv and frcp
instead of fixed counts.
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.

2 participants