Skip to content

Instantly share code, notes, and snippets.

@zboralski
Last active April 18, 2026 12:11
Show Gist options
  • Select an option

  • Save zboralski/4b091ecd9c5edf04c93cac8aeb8d1465 to your computer and use it in GitHub Desktop.

Select an option

Save zboralski/4b091ecd9c5edf04c93cac8aeb8d1465 to your computer and use it in GitHub Desktop.
AGX3 GPU Execution Model: Predicated Vector Machine with Masked Stores (Apple M3-M5, A18-A19 Pro)

AGX3 Execution Model: Predicated Vector Machine with Masked Stores

We present empirical and ISA-level evidence that Apple AGX3 (G15–G18, M3–M5) exhibits a predication-first execution model for user-level control flow. Across 10.1M instructions from 78 kernels, select instructions outnumber branches 6.1:1, and no instances of the divergence-class branch instruction (b.9a) appear in user-level conditionals. Lane-mask probing, atomic counters, and threadgroup memory tests confirm zero architectural side effects from masked stores across device, atomic, and shared memory domains. A complete G15–G18 opcode decode table (262 entries, spanning M3–M5 opcode variants observed across AGX3 revisions) is provided as a primary source, extending prior G13 documentation through the M5 generation.

Summary

The Apple AGX3 GPU (G15/G16/G17/G18 — M3 through M5, A18 through A19 Pro) does not implement NVIDIA-style SIMT warp divergence for user-level conditional control flow. Instead, it uses full predication with per-lane masked stores: both sides of every conditional are computed by all lanes, a select instruction picks the correct result per lane, and memory stores are suppressed for inactive lanes.

This is a fundamental architectural difference from NVIDIA (which uses a convergence barrier and per-lane program counters) and is closer to ARM SVE's predicated execution model.

How it works

The predication pattern

Every user-level if-else in Metal source:

if (condition) {
    out[id] = f(x);
} else {
    out[id] = g(x);
}

compiles to this ISA pattern (no branches):

compute predicate from condition
compute f(x) → reg_a           // ALL lanes
compute g(x) → reg_b           // ALL lanes
select reg_a, reg_b, predicate → reg_result
masked_store reg_result → out[id]   // only where predicate matches

Both f(x) and g(x) execute for every lane. The select instruction picks the correct value. Memory stores only commit for the lane where the predicate matches.

What the compiler generates

The AGC (Apple GPU Compiler) emits these predication instructions:

Instruction Tag Corpus count Role
select_shr low_alu_0000 587,304 Primary conditional select
usel mid_alu_34 100,829 Unsigned conditional select
icmp.sel low_alu_0027 83,423 Integer compare + select
iabs_sel low_alu_002d 72,358 Integer abs/select
fminmax.sel low_alu_001f 8,532 Float min/max select
FMINMAX_AUX variants mid_alu_*f 44,765 Extended select family
Total select 897,211 8.9% of all instructions

What the compiler does NOT generate

For user-level if-else, the compiler never emits branch_9a (the SIMT divergence instruction). Six shader variants were tested with increasing body complexity (simple ALU, multiple stores, loop-in-body, dependent memory loads). None generated branch_9a.

branch_9a appears only in:

  • Compiler-generated loop iteration control (fixed-trip-count loops with dependent FMA + memory, e.g. matrix multiply)
  • Transcendental function range reduction (sin, cos, exp polynomial scaffolding)

In both cases, all lanes always take the same path — the divergence infrastructure exists structurally but never activates for lane-level divergence.

Evidence

Test 1: Lane mask probe (VERIFIED)

A kernel writes to sideA in the then-body and sideB in the else-body. Both buffers initialized to 0. After GPU dispatch with alternating predicates (even lanes → then, odd → else):

  • sideA[odd] = 0 for all 32 odd lanes (zero leakage)
  • sideB[even] = 0 for all 32 even lanes (zero leakage)
  • Output values match expected computation exactly

Four test patterns (even/odd, all-true, all-false, first-half) all confirm zero leaked writes.

Test 2: Atomic masking (VERIFIED)

An atomic_fetch_add inside a conditional:

  • Expected: counter = 32 (half of 64 lanes have pred=1)
  • Actual: counter = 32 (exact)

Atomics are indivisible. Exact count proves the atomic instruction never reaches the memory unit for inactive lanes — masking occurs before memory commit.

Test 3: Threadgroup memory masking (VERIFIED)

Conditional write to threadgroup shared memory, barrier, readback:

  • Inactive lanes read back the baseline value (0xDEAD)
  • Active lanes read back the conditional value (lid ^ 0x5A)
  • 64/64 correct, 0 mismatches

Store masking applies uniformly to all memory domains: device buffers, atomics, and threadgroup memory.

Corpus-wide statistics (10.1M instructions)

Metric Value
Total instructions analyzed 10,133,198
Select/predication instructions 897,211 (8.9%)
Branch instructions (all types) 147,336 (1.5%)
branch_9a (diverge) specifically 42,681 (0.42%)
Select : Branch ratio 6.1 : 1
SELECT_SHR alone vs all branches 4.0 : 1

Per-workload breakdown:

Corpus Instructions Select % Branch % Select:Branch
imac_flat (system libs) 6.8M 9.4% 1.4% 6.9:1
macOS 26 2.5M 7.7% 1.6% 4.8:1
system 689K 8.4% 2.0% 4.2:1
xcode_beta (CoreImage) 150K 5.4% 1.3% 4.2:1

Select instructions outnumber branches by 4–7× across all workload types. The pattern is consistent: predication dominates, branches are rare, and branch_9a is a fraction of a percent.

Why this architecture

Hardware simplification

A SIMT divergence model requires runtime hardware for:

  • Per-lane program counter tracking
  • Divergence stack management
  • Serialized execution of divergent paths
  • Reconvergence detection and scheduling

A predicated model requires none of this. The hardware issues the same instruction stream to all lanes. The only per-lane decision is at the store unit: write or don't write.

Predictability

SIMT: branch → unknown distribution → unpredictable execution cost. Worst case (all-divergent) serializes to 2× instruction count.

Predication: always 2× instruction count for the conditional body, but deterministic. No branch prediction, no divergence stalls, no reconvergence penalties.

Compiler ownership

The compiler "resolves" control flow at compile time by expanding both paths. The hardware sees a uniform instruction stream. This shifts complexity from hardware (runtime divergence tracking) to the compiler (dead-code analysis, predicate propagation).

Power/area tradeoff

Extra ALU work (computing both paths) costs power. But ALU transistors are cheap and plentiful on modern GPU die. Control-flow tracking hardware is complex, power-hungry, and doesn't scale. Apple's choice: trade compute redundancy for control simplicity.

Relationship to other architectures

Architecture Conditional model Divergence handling
NVIDIA (Volta+) SIMT with independent thread scheduling Per-thread PC, convergence barriers
AMD RDNA SIMT with wave32/64 Exec mask, branch on exec=0
ARM Mali (Valhall) Clause-based execution Predicated within clause
Intel Xe SIMD with predication Per-channel predication
Apple AGX3 Full predication + masked stores No divergence — compiler expands

AGX3 is closest to Intel Xe and ARM Mali in spirit: predication-first with no user-visible divergence. The key Apple-specific detail is that branch_9a exists for compiler-internal loop management but is never exposed as a divergence primitive.

Implications

  1. No warp divergence penalty: GPU code that would cause divergence stalls on NVIDIA executes at full throughput on AGX3 (at the cost of redundant ALU work).

  2. No need for divergence-aware optimization: NVIDIA best practices like "minimize warp divergence" don't apply. AGX3 handles all conditionals uniformly.

  3. Transcendental functions use internal branching: The branch_9a count (42K in 10.1M instructions) comes from sin/cos/exp/log polynomial scaffolding, not user code. These always branch uniformly.

  4. Store masking is pre-memory-unit: Atomic exactness proves stores are suppressed before reaching the memory unit, not at commit. This means no partial writes, no torn atomics, no cache pollution from dead stores.

Detection methodology

To classify a kernel's execution model from its disassembly:

Predication signals:

  • Presence of select_shr, usel, icmp.sel, iabs_sel, fminmax.sel
  • Duplicated computation blocks (same ALU pattern appearing twice)
  • No branch instructions between predicate computation and result store

Control-flow signals:

  • branch_10 (loop-back to CF_PROLOGUE)
  • branch_9a (compiler-internal divergence)
  • branch_90 (conditional branch)
  • Multiple CF_PROLOGUE/CF_EPILOGUE pairs (clause boundaries)

Classification (tinyelim constraint-based):

Class Description
PREDICATED select instructions present, zero user-visible branches
LINEAR no branches, no selects (straight-line compute)
LOOP_ONLY loop scaffolding (branch_10) but no predication or user divergence
MIXED both predication and structural branching present
CONTROL_HEAVY genuine control-flow dominance with branch_90 (rare)

Automated classifier results (78-kernel corpus)

Class Count %
PREDICATED 31 39.7%
LINEAR 23 29.5%
LOOP_ONLY 9 11.5%
MIXED 9 11.5%
CONTROL_HEAVY 6 7.7%
UNKNOWN 0 0.0%

The classifier applies tinyelim FORBID/THRESHOLD/BALANCE constraints:

  • C_internal_cf_not_user_divergence: branch_9a tagged as internal → does not count toward CONTROL_HEAVY
  • C_select_requires_merge: FORBID PREDICATED if no select instructions exist
  • C_control_requires_structural_split: FORBID CONTROL_HEAVY if no branch_90 (conditional branch) exists — branch_10 alone is loop scaffolding
  • C_mixed_requires_both_signals: FORBID MIXED unless both predication and branch signals present

Key finding: zero kernels in the corpus show user-level SIMT divergence. The 39.7% PREDICATED + 29.5% LINEAR = 69.2% of kernels are pure dataflow with no control-flow branching at all. The 7.7% CONTROL_HEAVY comes from kernels with branch_90 (transcendental range reduction), not user-written conditional logic.


Based on reverse engineering of the Apple AGX3 G15/G17 ISA. Verified on Apple M5 (G17g). Corpus: 10.1M instructions from 14,271 functions across macOS system libraries, CoreImage, RenderBox, and compute shaders. Classifier source: agx/agxemu/predication.go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment