Repository navigation
Conversation
`row < m ? x[i] : 0` over a read-only buffer stays a branch: peephole select keeps indirect loads under their if, because the if may be what keeps the address valid, and soft faults do not change that. omarchy-mlx's FMA prefill matmul (qmm_fma.comp) guards every load this way, so its inner loop is a chain of one-load ifs with a phi at each join. In a short compute-shader arm made only of ALU, preamble reads and read-only global loads, point each load at the zero page when its arm is not taken. The zero page is always mapped and the select formed from the if discards the value, so the load may run either way, on chips with or without soft faults. The base and the unscaled 32-bit index are selected rather than the 64-bit sum, so agx_nir_lower_address still folds base + index into the load. Loads the driver already marks speculatable (descriptor reads) keep their address; read-only, reorderable load_global becomes load_global_constant, the form peephole select accepts; and flattening is requested for that if only. The pass runs before soft-fault speculation marks every load. Other indirect loads keep their branches. ASAHI_NO_GUARDED_LOAD_SELECT=1 turns it off for A/B runs. GuardedLoadSelect in agx_tests covers the rewrite (zero-page base and unscaled index selected, load_global_constant marked speculatable, the if flattened) and the cases left alone: an arm with a store, a writable or volatile load, dont_flatten, a fragment shader, and a descriptor read that is already speculatable. Compile-only, through drm-shim on an M4 Mac mini host (no GPU submits), assertions and NIR validation on, robustBufferAccess off: - qmm_fma.comp, G17P: 3687 -> 3523 instructions, 429 -> 249 blocks, 542 -> 350 moves; G15S: 3690 -> 3620, 429 -> 249, 545 -> 447. - All 106 omarchy-mlx compute shaders that build with a storage-buffer-only layout compile with and without the pass on G15S, G17P and G13G, with no new failure. 44 change; static instructions in total -1.3% (G15S), -1.9% (G17P), -0.9% (G13G), blocks -10%. Some kernels grow, by up to 7.5% (fast_norm_vjp_dx 828 -> 890 on G15S, int8_matmul 2046 -> 2103 on G13G). Not run on a GPU with this tree. On an M4 (G16G) with the GravityLinux Apple9 backend, the same idea took that kernel from 174 to 778 GFLOP/s and f16 prefill 4.5-4.8x, but there the branches cost far more (5300 of 6412 instructions were copies), so expect a smaller effect here. Signed-off-by: Sean Dolbec <sdolbec1@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
techmore
marked this pull request as ready for review
November 2, 2026 02:58
[Medium impact] The PR appears safe to merge; both previous findings are addressed and no new blocking issue was found. SummaryThe PR lets short compute-shader branches issue read-only loads safely before selecting their results.
Reviews (2) · Last reviewed commit: "agx: keep guarded loads' alignment true ..." · Reviewed by Greptile |
A redirected load kept its align_mul/align_offset, but when its arm is not taken it reads AGX_ZERO_PAGE_ADDRESS, which is 0 modulo any align_mul. For a load with align_mul 16 and align_offset 4 that made the alignment facts false, and the load/store vectorizer that runs after the pass may rely on them. Read zero page + align_offset instead. The zero page is 4 GiB aligned, so that address satisfies the load's facts, and an arm load is at most 16 bytes, so it stays inside the 64 KiB page. If it would not fit, weaken the facts to the largest power of two dividing align_offset, which both addresses satisfy. GuardedLoadSelect gains three tests: - FallbackKeepsAlignmentFacts: a 16/4 load keeps 16/4 and falls back to zero page + 4 (fails without this change). - BothArmsBecomeOneSelect: loads in both arms, run with nir_opt_peephole_select as agx_optimize_nir configures it. The if is gone, the result is bcsel(cond, then load, else load), and each load reads its own address when its arm is taken and the zero page otherwise; the else arm's guard is the reversed select. - PeepholeSelectAloneKeepsBranch: the same shader keeps its branch without this pass. Signed-off-by: Sean Dolbec <sdolbec1@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
|
Aurora downstream draft: aurora-silicon#193 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
row < m ? x[i] : 0over a read-only buffer stays a branch on every chip. Peephole select keeps indirect loads under their if, because the if may be what keeps the address valid, and soft faults do not change that. omarchy-mlx's FMA prefill matmul (qmm_fma.comp) guards every load this way, so its inner loop is a chain of one-load ifs with a phi at each join.A new NIR pass,
agx_nir_speculate_guarded_loads, runs inagx_optimize_nirbefore soft-fault speculation, for compute shaders only. It looks for an if whose arms are single blocks of at most 64 instructions, made of ALU,load_preambleand read-only global loads:AGX_ZERO_PAGE_ADDRESSis always mapped. The base and the unscaled 32-bit index are selected instead of the 64-bit sum, soagx_nir_lower_addressstill folds base + index into the load.load_globalbecomesload_global_constant, the form peephole select accepts.nir_selection_control_flatten, so the peephole select that follows turns it into selects.indirect_load_okglobally. That flag stays off because of the existing XXX about CTS flakes.ASAHI_NO_GUARDED_LOAD_SELECT=1turns the pass off for A/B runs.Why
This change is behind our largest prefill gain on M4. All numbers here are from our receipts, measured on an M4 Mac mini (G16G) with GravityLinux's Apple9 backend on a kernel without soft faults, not on this tree:
qmm_fma4-bit matmul went from 174 to 778 GFLOP/s.That backend's register allocator paid far more for the joins than this tree's does: 5300 of 6412 instructions were copies. So expect a smaller effect here. The static numbers below are what we can show for this tree without hardware.
Evidence (compile-only, this tree)
drm-shim on an M4 Mac mini host (aarch64 Arch Linux), earlier round, same base. No GPU submits,
-Db_ndebug=false(assertions and NIR validation on), shader cache off, robustBufferAccess off. ProfilesASAHI_SHIM_CHIP=g15s / g17p / g13g. Instruction counts come fromAGX_MESA_DEBUG=shaders, with the pass on vs.ASAHI_NO_GUARDED_LOAD_SELECT=1:should've been loweredassertion with and without the pass on these chips.Tests run
GuardedLoadSelectinagx_tests(test/test-guarded-load-select.cpp, 7 tests).load_global_constantwithACCESS_CAN_SPECULATE, the if is flattened, and a second run finds nothing left to do.dont_flatten, a fragment shader.agx_testson this branch: 509 tests, 508 passed, 1 skipped (base: 502, 501 passed).meson test.agx_testsbuilt (496 passed + 1 skipped in release; 502 + 1 skipped with assertions). The assertion build caught one bug before that version:nir_intrinsic_accessonload_preamble. It is fixed.Remaining test plan
mini-run/run-drm-shim.sh):meson test(all suites),drm-smoke.py, andpipelines.pywith the pass on and withASAHI_NO_GUARDED_LOAD_SELECT=1.ASAHI_NO_GUARDED_LOAD_SELECT=1on M3 (no soft faults) and A18 Pro (soft faults):qmm_fmaGFLOP/s and prefill tok/s.dEQP-VK.compute.*anddEQP-VK.robustness.*with robustBufferAccess off and on.Risks and limitations
agx_compile.calready assumes that when it replaces addresses withAGX_ZERO_PAGE_ADDRESS.agx_optimize_nir). The G13G numbers are mixed: 15 shaders shrink, 28 grow, total -0.9%. Gating it to native AGX3 is a one-line change if preferred.load_globalasload_global_constant. Both lower to the same AGX load, and the reorder flag was already set.🤖 Generated with Claude Code