apple: implement binary opcodes 9-22 in the portable stub; fail closed on unknown ones - #592
Merged
Merged
Conversation
…d on unknown ones
`apple_gpu_runtime_stub.cpp` — compiled on every NON-Darwin host so the C symbol
exists — implemented opcodes 0-8 and ended its switch with:
default: out[i] = x; break;
So `mod`(9), `floor_div`(10), the six comparisons(11-16), and the
logical/bitwise ops(17-22) returned the LEFT operand instead of computing:
14 of the 23 opcodes in `_APPLE_GPU_BINARY_OPCODES`, on CI and on the Strix Halo
box.
It was not a visible envelope miss. `_load_apple_gpu_runtime` compiles this stub
off Darwin precisely "so the symbol still exists with a portable reference
path", and because the symbol then exists,
`_apple_gpu_dispatch_mpsgraph_binary` takes the kernel branch instead of its
numpy fallback — so a wrong value came back as if it had been computed, with no
diagnostic. Observed on CI as `tessera.floor_div` with `{"scalar": 2.0}`
returning `[1,2,4]` where the truth is `[0,1,2]`. Decision #21: a lowering the
backend cannot carry must diagnose, never silently fall through.
Two changes:
* Opcodes 9-22 implemented to match `mpsg_binary_node` in
apple_gpu_runtime.mm and the declared host reference
`runtime._apple_gpu_binary_numpy`, which were already in agreement. Floor-mod
and floor-division follow numpy (sign of the divisor), NOT C `fmod`/
truncation; comparisons and logical ops produce f32 0/1 masks; bitwise ops go
through int32, whose truncation toward zero matches both `.astype(np.int32)`
and MPSGraph's `castTensor:toType:MPSDataTypeInt32`.
* An unknown opcode is now rejected BEFORE the output is touched, reported
through the stub's last-error channel, and the buffer poisoned with NaN. The
channel was three fixed no-op parity symbols; it now carries real
thread_local state mirroring the .mm's `g_last_gpu_error_kind` pattern, with
a new kind 3 = "not implemented by the portable stub" (1 = timeout and
2 = command-buffer error are Metal-only). `_apple_gpu_run_checked` reads that
channel and routes to the host fallback, so an unimplemented opcode now
produces the correct answer via numpy rather than a plausible wrong one.
The guard is a separate predicate rather than a `default:` arm so that adding a
case to the switch without adding it to the guard fails closed.
Testing this is awkward: a Mac loads the real Metal symbol and never executes
the stub, so the previous coverage could not have caught it.
`tests/unit/test_apple_gpu_binary_opcodes.py` layers four kinds of evidence:
* the dispatcher against the numpy reference for every opcode, on both lanes;
* structural drift gates over the stub source, which run on ANY host — a newly
declared opcode missing from the stub, or a `default:` arm that returns an
operand, fails there even on a machine that cannot execute the stub;
* the stub's opcode switch and error channel extracted VERBATIM (sliced from
the file, never retyped, so the tested text cannot drift from the shipped
text), compiled with the host C++ compiler and RUN against the numpy
reference for all 23 opcodes plus silu_mul and an unknown opcode. This is the
only coverage that executes the stub's real semantics. It skips when no C++
compiler is present;
* a kernel that reports failure through the last-error channel must route to
the host reference rather than have its buffer believed.
Inputs include a negative numerator and a zero, which separate floor-mod from
C `fmod`, floor-division from truncation, and the logical ops from the bitwise
ones. On the pre-fix stub the structural gates fail and the compiled lane
cannot find its anchors.
Verified on the Mac (M1 Max): the extracted stub compiles and all 23 opcodes
match the numpy reference exactly, silu_mul matches, and opcode 23 reports
kind 3 with NaN output. Full unit sweep: 14065 passed, 48 failed — failure set
identical to the pre-existing baseline (all 48 the missing Apple runtime
dylib), zero new. mypy clean over 467 files; ruff clean; generated-doc drift
gate in sync. The in-context Linux compile of the whole TU is proved by CI's
build job, not by this host.
Cross-backend assessment (AGENTS.md), sync key
`APPLE-STUB-BINARY-OPCODES-2026-08-19`:
* apple — parity validated off-device; Metal was always correct, so no
exact-device row, .metallib, or dylib evidence changes or is re-claimed.
* nvidia — not applicable; the symbol is referenced only by Apple files.
* rocm — not applicable; the gfx1151 binary lane is separate code and binds
both operands positionally, raising rather than falling through.
* x86 — not applicable today, one follow-up recorded: the AVX-512 binary
kernel carries the SAME shape (`default: return a;` and `default: y = a;`),
currently unreachable because it implements kinds 0-7 and `_X86_BINARY_OPS`
maps exactly onto that range. It becomes live the moment an opcode is added
on one side only — which is exactly how this defect arose. Fixing it needs
AVX-512 evidence from the Zen 5 box.
No device evidence is produced or claimed for nvidia, rocm, or x86.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ary-opcodes # Conflicts: # docs/audit/backend/apple/todo.md # docs/audit/backend/nvidia/todo.md # docs/audit/backend/rocm/todo.md # docs/audit/backend/x86/todo.md # docs/audit/generated/test_coverage.md
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.
The defect
apple_gpu_runtime_stub.cpp— compiled on every non-Darwin host so the C symbol exists — implemented opcodes 0–8 and ended its switch with:_APPLE_GPU_BINARY_OPCODESruns to 22. So 14 of 23 opcodes returned operand A instead of computing, on CI and on the Strix Halo box:This was not a visible envelope miss.
_load_apple_gpu_runtimecompiles the stub off Darwin precisely "so the symbol still exists with a portable reference path" — and because the symbol then exists,_apple_gpu_dispatch_mpsgraph_binarytakes the kernel branch instead of its numpy fallback. A wrong value came back as if computed, with no diagnostic. Decision #21: a lowering the backend cannot carry must diagnose, never silently fall through.Observed directly on CI (run 32278268687):
tessera.floor_divwith{"scalar": 2.0}returned[1,2,4]where the truth is[0,1,2].Pre-existing on
main. Surfaced by #589, which was the first thing to exercise those opcodes on Linux.The fix
Opcodes 9–22 implemented, matching
mpsg_binary_nodeinapple_gpu_runtime.mmand the declared host referenceruntime._apple_gpu_binary_numpy— which were already in agreement with each other. The subtleties that matter:modandfloor_divfollow numpy (sign of the divisor), not Cfmod/truncation.astype(np.int32)and MPSGraph'scastTensor:toType:MPSDataTypeInt32Unknown opcodes now fail closed. Rejected before the output is touched, reported through the stub's last-error channel, and the buffer poisoned with NaN. That channel was three fixed no-op parity symbols — which is what left the stub unable to say it hadn't computed anything. It now carries real
thread_localstate mirroring the.mm'sg_last_gpu_error_kindpattern, with a new kind 3 = "not implemented by the portable stub" (1 = timeout and 2 = command-buffer error are Metal-only and unreachable here)._apple_gpu_run_checkedreads that channel and routes to the host fallback, so an unimplemented opcode now yields the correct answer via numpy rather than a plausible wrong one.The guard is a separate predicate rather than a
default:arm, so adding a case to the switch without adding it to the guard fails closed.Testing something this host cannot execute
A Mac loads the real Metal symbol and never reaches the stub — which is exactly why the existing coverage couldn't catch this.
tests/unit/test_apple_gpu_binary_opcodes.pylayers four kinds of evidence:default:arm that returns an operandThe compiled layer slices the opcode switch and error channel verbatim out of the real file rather than retyping them, so the tested text cannot drift from the shipped text. It compiles them with the host compiler and runs all 23 opcodes plus
silu_muland an unknown opcode against the numpy reference. It skips when no C++ compiler is present.Inputs include a negative numerator and a zero — chosen to separate the implementations, not merely exercise them: they distinguish floor-mod from C
fmod, floor-division from truncation, and the logical ops from the bitwise ones.On the pre-fix stub the structural gates fail and the compiled lane cannot find its anchors.
Verification
Mac (M1 Max). Extracted stub compiles; all 23 opcodes match the numpy reference exactly,
silu_mulmatches, and opcode 23 reports kind 3 with NaN output.tests/unit/test_apple_gpu_binary_opcodes.py— 74 passedLimit of this evidence: the in-context Linux compile of the whole TU is proved by CI's build job, not by this host —
-U__APPLE__breaks libc++ on macOS, so the TU cannot be compiled here in its non-Darwin configuration. The extracted-region compile is the closest local proxy.Cross-backend assessment (AGENTS.md)
Sync key
APPLE-STUB-BINARY-OPCODES-2026-08-19, recorded in all four plans:mpsg_binary_nodeimplements the full table — so no exact-device row,.metallib, or dylib evidence changes or is re-claimed. What changes is that the portable path now agrees with Metal instead of silently disagreeing on 14 of 23 opcodes.tessera_apple_gpu_mpsgraph_binary_f32is referenced only by Apple files; no NVIDIA path reaches it._execute_rocm_compiled_binaryover a compiler-generated hsaco) and binds both operands positionally, raising rather than falling through. Checked for the same defect shape — it has no silent-default arm.x86 follow-up (verified, not assumed)
The AVX-512 binary kernel carries the same shape:
avx512_binary_f32.cppendsscalar_binarywithdefault: return a;and the vector loop withdefault: y = a; break;.It is latent, not live: the kernel implements kinds 0–7 (
kSub/kDiv/kMax/kMin/kAdd/kMul/kMod/kFloorDiv) and_X86_BINARY_OPSmaps exactly onto that range, so no reachable call hits the default today. It becomes live the moment an opcode is added on one side only — which is precisely how the Apple defect arose. Fixing it needs AVX-512 evidence from the Zen 5 box, so it belongs in an x86 change, not here.No device evidence is produced or claimed for nvidia, rocm, or x86.
🤖 Generated with Claude Code