Skip to content

Potential fix for code scanning alert no. 1: Workflow does not contain permissions - #2

Merged
gstoner merged 1 commit into
mainfrom
alert-autofix-1
Sep 11, 2025
Merged

gstoner merged 1 commit into
mainfrom
alert-autofix-1

Conversation

@gstoner

@gstoner gstoner commented Sep 11, 2025

Copy link
Copy Markdown
Owner

Potential fix for https://github.com/gstoner/tessera/security/code-scanning/1

To fix the problem, we should add a permissions key to the workflow YAML, with the minimum set required for this analysis job. "Minimal" in this case means giving read-only access to repository contents, as this is necessary for actions/checkout and subsequent analysis. There is no evidence this workflow needs to write to anything or needs higher access. The permissions key can be added at the workflow root (recommended, applies to all jobs) right below the name or on fields. No changes to the rest of the workflow are required.

Suggested fixes powered by Copilot Autofix. Review carefully before merging.

…n permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@gstoner
gstoner marked this pull request as ready for review September 11, 2025 20:47
@gstoner
gstoner merged commit 456362e into main Sep 11, 2025
4 of 7 checks passed
gstoner added a commit that referenced this pull request May 18, 2026
Summary — all four issues fixed
P1 #1 — Kernel widening + sweep envelope (FIXED)
The MSL kernel had a per-thread float y_local[256] register buffer that hard-capped D at 256. With fixed gradient the T-step recurrence is exactly closed-form y_T = y0 − T·η·grad, so I rewrote the kernel to stream through D once accumulating squared-norm energy, then a second pass for the winner. No register-vector spill; D is now unbounded; K is still bounded at 256 by the threadgroup-size budget for the argmin reduction. Both Python and runtime guards updated.

P1 #2 — Proof of native dispatch (FIXED)
Added tessera.ebm.ebt_tiny_dispatched_on_gpu() / ebt_tiny_last_route() probes. The workload now returns a 5-tuple including dispatched_on_gpu, and run_workload_apple_gpu:

If dispatched_on_gpu=False (silent fallback): row is degraded to backend="python_ref", mode="reference_chain_fallback", ok=False. Caller cannot mistake it for a native win.
If True: row is backend="apple_gpu", mode="fused_chain", includes dispatched_on_gpu=True field.
Sweep summary gains status per shape (native_dispatched vs degraded_fallback) and degraded_count in the envelope. speedup only computed when the native attempt actually fired.

P2 #3 — Bridge breadth (FIXED, with honest narrowing)
Migrated 10 fast paths through jit_bridge.dispatch_via_manifest — every op the workloads actually use:

GA: reverse, grade_involution, conjugate, hodge_star, exp_mv, log_mv, geometric_product, wedge, left_contraction, rotor_sandwich, norm, inner (12)
EBM: inner_step, ebt_tiny (2)
Docs now explicitly say 14 of 26 fast paths route through the bridge — the rest still call _apple_gpu_dispatch.bind_symbol directly and are correctness-equivalent but invisible to the trace. Migrating the long tail is an open follow-up; the breadth is no longer overstated.

P2 #4 — Stale docs (FIXED)
benchmark_ga_ebm.py:26 header now lists the 8 native EBM kernels + ebt_tiny correctly (was "EBM ops do not yet ship Apple GPU MSL kernels").
README.md workload section: workload now described as fused single-dispatch ebm.ebt_tiny, with the new sweep numbers (~55× peak, not the old 116×).
docs/status/ga_ebm_milestone.md: non-claim #1 rewritten — was "first_native_win_shape is None" and "ebm_refinement now wins"; now coherently describes the streaming closed-form rewrite + the proof bit + headline numbers will drift.
docs/audit/apple_ga_ebm_native_execution_gap.md:232: first_native_win_shape=None replaced with the current numbers + dispatch proof description; "only ebm_inner_step routes through public API dispatch" replaced with the 14-of-26 breakdown.
Roadmap top status line refreshed.
Sweep run on M-series with fresh sample artifact: every sweep point dispatches on GPU (degraded_count=0); first native win at B=16,K=32,D=128,T=8 (~1.1×); peak ~55× at B=64,K=128,D=1024,T=256. Final: 3607 / 1 skipped / 0 failed.
gstoner added a commit that referenced this pull request May 18, 2026
Summary
Task 1 — Compiler-integrated GA vertical slice ✅
New module python/tessera/compiler/clifford_jit.py:

@clifford_jit(target="apple_gpu") decorator
First call traces the function under jit_bridge.jit_context("apple_gpu") with tracing on
Captures the bridge route trace as the canonical op plan
Verifies every op has an apple_gpu=fused manifest entry (raises CliffordJitError at compile-time otherwise)
Builds a frozen CliffordCompiledArtifact carrying the plan + plan hash + Apple target metadata
Subsequent calls execute under a fresh jit_context so the per-call route trace can be checked against the plan via plan_matches_routes()
Demo: point_cloud_rotor_invariant(rotor, points) = ga.norm(ga.rotor_sandwich(rotor, points)) → plan (clifford_rotor_sandwich, clifford_norm), plan_hash 9bb1cd4d73ae1bc5. Benchmark emits a namespace="vertical_slice" row with the full compiled_artifact JSON embedded.

11 / 11 tests in tests/unit/test_clifford_jit.py — trace + plan capture, manifest verification, hash determinism, per-call route trace match, JSON round-trip, error semantics, frozen-dataclass.

Task 2 — Fused GA + EBM workload ✅
rotor_conditioned_ebt — ga.exp_mv → ga.rotor_sandwich → ebm.ebt_tiny through public APIs. Routes all three ops through the bridge with a dispatched_on_gpu proof bit. Native ~21× speedup vs the equivalent numpy chain (~0.8 ms vs ~16 ms on M-series). Tests verify all 3 symbols appear in row["symbols"] and the proof bit fires.

Task 3 (partial) — Metal buffer pool ✅
New MetalDeviceContext::buffer_pool — 19-bucket size-class pool (16B → 4MB) with metal_buffer_acquire / metal_buffer_release / metal_buffer_acquire_with_bytes helpers. Wired into dispatch_clifford_unary_8x8_f32_msl + dispatch_ebm_ebt_tiny_refinement_argmin_f32_msl (the two heaviest dispatchers in the workloads). Remaining ~25 dispatchers still call newBufferWithBytes directly — migration is mechanical and explicitly listed as the #1 next target.

Task 4 — On-device RNG + energy lowering (next sprint)
Sized as #2 + #3 in the Next Targets list — these are a sprint of their own (Philox-in-MSL kernel + AST → MSL visitor for restricted energy_fn shapes) so I scoped them out of this turn and documented the path.

Task 5 — Docs honest + pruned ✅
docs/audit/apple_ga_ebm_native_execution_gap.md — front-matter changed to status: Historical (superseded by docs/status/ga_ebm_milestone.md); a banner block at the top redirects readers to the canonical milestone page. The roadmap remains the canonical sprint sequence.

docs/status/ga_ebm_milestone.md gains rows for Compiler vertical slice, Fused GA + EBM workload, and Metal buffer pool in the TL;DR table; the "Next targets" section reshuffled with finish-the-buffer-pool-sweep at #1, Philox-in-MSL at #2, energy lowering at #3, broaden @clifford_jit at #4.

Sample artifact regenerated
benchmarks/apple_gpu/sample_ga_ebm_report.json — now includes the vertical_slice row (with embedded compiled_artifact JSON), the rotor_conditioned_ebt workload row, and the updated pool-accelerated workload timings.

Sweep: 3623 passed, 1 skipped, 0 failures.
gstoner added a commit that referenced this pull request May 19, 2026
Tight diff. Summary:

Findings — both addressed
P2 #1: tsrIsInitialized now declared in the public header
Fix — src/runtime/include/tessera/tessera_runtime.h: added the extern "C" TsrStatus tsrIsInitialized(int* out) declaration alongside tsrInit / tsrShutdown, with a doc comment that points to the implementation file for the lifecycle rationale. C/C++ callers no longer need ad-hoc extern decls.

Lock — tests/unit/test_runtime_header_abi.py (2 tests):

test_every_implementation_symbol_has_a_header_declaration — regex-scans tessera_runtime.cpp for every tsr* definition and asserts each appears as a declaration in the public header surface (the union of tessera_runtime.h + the sibling tsr_*.h headers it includes). This is a general-purpose ABI drift gate that catches any future tsr* symbol shipping without a header decl, not just the one the audit flagged.
test_tsrIsInitialized_is_declared — spot-check for the specific symbol.
The general test also caught tsrClearLastError, tsrEnableProfiling, tsrGetVersion, tsrSuggestTile, tsrTimestampNowNs, tsrValidateLaunch initially — they were declared, just in sibling headers (tsr_status.h, tsr_shape.h, tsr_version.h), so I taught the test to honor the public surface as the union. That's the right model — the umbrella header #includes them all, so a caller using #include <tessera/tessera_runtime.h> sees the full ABI.

P2 #2: JitFn.compile_report() now emits fallback_reason
Fix — python/tessera/compiler/jit.py::compile_report: added fallback_reason=self.last_fallback_reason to the CompileReport(...) construction. The schema field already existed on CompileReport (compile_report.py:79) and .as_dict() already knew how to serialize it; the JIT path just wasn't passing it through.

Lock — tests/unit/test_jit_native_required_cpu.py::test_compile_report_carries_last_fallback_reason: end-to-end test that

compiles a tiny @tessera.jit function,
runs it (clean → compile_report().fallback_reason is None),
stamps last_fallback_reason = FallbackReason.CAPABILITY_NOT_READY (mimicking what _native_cpu_fast_call does on the ABI-failure path),
asserts the next compile_report() carries the reason verbatim and its serialized form (as_dict()["fallback_reason"] == "capability_not_ready").
Numbers
Python sweep: 4273 → 4276 passing, 0 failures
Focused tests: 97 passed, 4 skipped (was 82 → +15 over the two batches of regression suites)
C++ rebuild: tessera_runtime clean
Audit gates: support_table --check clean, claim_lint clean
Code path the user will see now
@tessera.jit
def f(x): ...

# Native CPU launch fails internally → caught, recorded, fallback runs
f(some_input)

report = f.compile_report()
report.fallback_reason          # FallbackReason.CAPABILITY_NOT_READY
report.as_dict()["fallback_reason"]  # "capability_not_ready"
…and for callers who'd rather see the failure surface than the silent fallback:

@tessera.jit(native_required=True)
def f(x): ...

f(some_input)   # TesseraNativeRequiredError on ABI failure
Both behaviors are now locked by regression tests.
gstoner added a commit that referenced this pull request May 23, 2026
… Triage of all 7 findings

While that runs, let me give you the triage summary so you can react.

Triage of all 7 findings
#	Finding	My verdict	Action taken
1	mypy ratchet 7 errors	STALE / FALSE	None — mypy python/tessera/ reports 0 errors across 185 files; test_static_analysis_baseline.py 3/3 passes. The reviewer was looking at an older state
2	MockRankGroup repeated-collective race	REAL	✅ Fixed — added per-rank generation counters; (kind, gen) tuple keys make every invocation unique; deposit and withdraw can no longer race
3	bind_symbol ctypes signature mutation race	REAL	✅ Fixed — replaced getattr(handle, symbol) + .argtypes mutation with ctypes.CFUNCTYPE(restype, *argtypes)((symbol, handle)) so each signature gets a genuinely independent prototype; the whole bind now runs under _lock with a double-check after acquire
4	Graph IR cache no LRU/lock	REAL but explicitly intentional	Deferred — graph_ir_cache.py:18 documents the no-LRU design; turning it into a bounded LRU is a feature decision, not a bug fix
5	Monolith files	REAL but architectural	Deferred — apple_gpu_runtime.mm 6,988 LOC, vjp.py 4,262, etc. Splitting requires its own sprint
6	Queue verifier no-op stubs + shape_inference XFAIL	REAL	Deferred — confirmed QueueVerifiers.cpp is 3 trivial return success(); stubs and tests/tessera-ir/phase6/shape_inference.mlir is XFAIL: *. Next verifier-quality pass should cover them
7	trust_treedef=True default contradicts docstring	REAL	✅ Fixed — flipped default to sentinel None and emit DeprecationWarning on implicit use; explicit True/False stays silent. Sets the trajectory to flip the implicit default to False in a future release without breaking current callers today
Why I didn't fix #4, #5, #6 in this session
#4 is documented intent (process-local cache for hot loops). Adding LRU + locks would change observable behavior; needs a design decision, not a drive-by fix.
#5 is real but every file is structurally coherent — slicing them apart without a target architecture for each (Apple runtime by kernel family, vjp.py by op family, etc.) risks creating worse seams than the current single-file layout.
#6 queue verifier stubs and shape_inference XFAIL are pre-existing surface area, not new debt. Worth a focused sprint along with the verifier-stub audit the reviewer flagged ("187 return success(); occurrences across src/").
Once the full unit sweep finishes I'll confirm green.

Ran
Full sweep result
5254 passed, 0 failures — same count as before the fixes, no regression. The 3 actionable findings (#2 MockRankGroup race, #3 bind_symbol mutation, #7 trust_treedef default) are landed and verified.

Final verification
mypy: 0 errors / 185 source files
Full unit sweep: 5,254 passed, 7 skipped, 0 failures
Static-analysis baseline: 3/3 passing
Apple GPU MLA E2E proof: 6/6 passing
MLIR verifier sprint guards: 64/64 passing
Apple GPU buffer pool + JIT no-leak: 14/14 passing
gstoner added a commit that referenced this pull request May 30, 2026
…and buffer/token) (#47)

* apple_gpu R2: encoded elementwise ops complete the command-buffer block surface

The R2 encode session (AppleGPUEncodeSession) covered bmm / rowop
(rmsnorm/softmax) / gumbel — enough for the MLA decode chain but not a full
transformer/MLP block. Add encoded flat elementwise ops so a single command
buffer can express residual adds, SwiGLU, ReLU heads, and additive
attention masks:

- apple_gpu_runtime.mm: tessera_apple_gpu_{unary,binary}_dev_f32_enc encode
  mpsg_{unary,binary}_node into the session command buffer (cached graphs;
  unary op 0 relu / 4 silu, binary op 0 add / 2 mul).
- stub: non-Darwin reference parity for both.
- runtime.py: loader gate requires the 2 new symbols (forces rebuild of stale
  prebuilts); enc-api ctypes config; session methods relu/silu/add/mul/silu_mul
  (silu_mul composes silu+mul to match ops.silu_mul = silu(a)*b).

Validated on Metal: relu/add exact, silu_mul ~7e-9; a full pre-norm residual
block (rmsnorm -> value proj -> residual -> rmsnorm -> SwiGLU -> residual)
encodes into ONE command buffer and matches a float64 numpy reference
(tests/unit/test_apple_gpu_encode_elementwise.py, 3 tests). No regression
across resident/encode/mpsgraph suites (61 pass).

This is the enabling infra for a GPU-resident Gumiho draft+verify step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* examples/advanced/gumiho: GPU-resident serial draft (one command buffer/token)

Builds on the new R2 elementwise encode ops to keep the serial head's
autoregressive draft GPU-resident. gumiho/resident.py:

- ResidentSerialDraft uploads the serial weights once (resident DeviceTensors)
  and encodes a whole serial step into ONE Metal command buffer via
  AppleGPUEncodeSession (fc_in bmm -> [rmsnorm, value-attn bmm/bmm, residual
  add, rmsnorm, SwiGLU via bmm/bmm/silu_mul/bmm, residual add] x2 -> rmsnorm ->
  LM bmm). Only the sampled token id + carry hidden read back per step.
- The T=1 self-attention reduces to a value projection (v @ Wo), so it uses the
  value slice of Wqkv and is numerically identical to the host SerialHead —
  validate_resident_draft checks it token-for-token (logit err ~4e-7 on Metal).
- Degrades to the host SerialHead off Metal.

Measured: 2 command buffers vs ~46 per-op host dispatches = 23x fewer GPU syncs
for a 2-token serial draft. demo.py gains --mode resident.

The tree-verify phase stays host-orchestrated (FTA top-k + prefix trie are
data-dependent control flow); this targets the serial draft, a pure resident
dense chain. +1 test in test_example_gumiho.py; ruff + lint_docs clean; 57
adjacent pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 2, 2026
#1 — MTL4 fp16 matmul routing (the headline) ✅ honest flip
Measured fp16 MTL4 matmul2d vs MPS end-to-end on this Mac (macOS 26.5, Metal 4 live). Finding: MPS's fp16 GEMM is well-tuned and wins on square shapes (0.6–0.8×) and most decode shapes — except the M==1 GEMV decode step, where MTL4 wins a rock-solid 3.2–3.4× (3 trials, tight variance; MPS has a slow fp16 M==1 path).

Added _mtl4_route_matmul2d_f16 size-gated to M==1 by default (TESSERA_APPLE_GPU_MTL4_F16: auto=M==1 only / all=force / 0=off). A blanket flip would regress square matmuls, so I deliberately didn't take it — the honest win is the narrow one.
Re-runnable benchmark benchmarks/apple_gpu/benchmark_mtl4_matmul_routing.py + 6 gate/numerics tests + P7 doc entry with the measured table.
#2 — TPP embedded-MLIR driver ✅ subprocess killed
New libtessera_tpp_capi shared lib (TPPCApi.cpp) runs the tpp-space-time pipeline in-process via PassManager; tpp.solve(mlir) loads it via ctypes (mirrors the Apple-runtime pattern) — no tessera-opt subprocess, no PATH dependency. Built clean, verified in-process, python_driver_wired now reports True. 10 tests.

#3 — Optimizer batching rules ✅
Proved vmap composes with all functional optimizers (elementwise: vmap==whole-batch; lamb/muon: vmap==per-row loop) and flipped optimizer/functional_optimizer_step batching_rule partial→complete. 9 tests.

#4 — Lit counts + G4 spot-check ✅
CLAUDE.md refreshed to the live 61 PASS / 19 UNSUPPORTED / 18 XFAIL / 0 FAIL across 98. Confirmed G4's matrix-as-dispatcher invariant holds — launch() resolves the executor via _exec_row_for_metadata with an explicit "no new branch in launch()" contract (the prior codex P1 was closed by G6/G6.1).

Plus caught + fixed two registry-drift dashboards from #3 (s_series_status, regenerated) and added s_series_status to the #12 pre-commit drift gate so that class can't recur. ruff clean, mypy ratchet 0.
gstoner added a commit that referenced this pull request Jun 3, 2026
… where the three items landed.

All green — drift gate passes, 71 guard tests pass, everything consistent. Here's where the three items landed.

✅ #4 — Coverage prose stops copying stale counts (complete)
COVERAGE_AUDIT.md, MASTER_AUDIT.md, and BACKEND_AUDIT.md now link the generated dashboards instead of copying numbers (the entry-point had already drifted — it claimed "218 C ABI symbols" vs the live 226+). Added a banner stating the rule. Drift gate owns every count.

✅ #3 — Manifold Langevin ops off planned (complete, honestly scoped)
Found a real name mismatch (_EBM_PRIMITIVES listed ebm_sphere_langevin but the registry/runtime use ..._step). Fixed it and added manifest entries:

ebm_sphere_langevin_step → partial, fused (dedicated MSL kernel)
ebm_bivector_langevin_step → partial, fused (reuses the affine ebm_langevin_step kernel on grade-2 coeffs)
ebm_sphere/bivector_langevin_sample → partial, reference (real numpy chain wrappers)
4 of 5 flipped honestly (the 5th, rng_langevin_sample, is a different namespace). Backed by 2 guard tests + 38 existing geo-sampling tests. The universal complete gate stays intact (nvidia/rocm still planned); their distributed-mesh axis stays correctly Phase-G-gated.

🟡 #2 — Long-tail axes (batching done; transpose/sharding remain)
Batching closed 27 → 4 by promoting the textbook-batchable categories (collective, recurrent, state_space, linalg decomposition+solver, sparse, segment_reduce) with documented justification — leaving only the genuinely mesh-aware ones (moe / moe_transport / kv-cache state) honestly partial. Hit and fixed a real duplicate-key shadowing bug in the category table along the way. Locked by test_batching_rule_closure.py.

Transpose (40) and sharding (30) remain — I stopped here rather than rush them, because they need careful per-category judgment, not flag-flipping:

transpose: closes mostly via not_applicable for non-linear families (optimizers, ebm, recurrent, linalg-decomp, moe) + complete for the genuinely-linear ones (spmm, segment_reduce, avg_pool).
sharding: largely Phase-G-mesh-pending (distributed attention/spectral/moe) — the axis where staying partial is most honest; only trivially-replicated/not_applicable ones close cleanly.
Verification: drift gate green; 130 EBM-benchmark + 71 guard/manifest/roadmap tests pass; dashboards (s_series_status, apple_target_map, standalone_primitive_coverage) regenerated and consistent; lint clean.
gstoner added a commit that referenced this pull request Jun 3, 2026
#4 — Coverage prose stops copying stale counts
Audit docs link the drift-gated dashboards instead of copying numbers.

✅ #3 — Manifold Langevin ops off planned
4 ops flipped planned → partial with real Apple GPU kernels (fixed a real _EBM_PRIMITIVES name mismatch along the way). Distributed-mesh axis correctly stays Phase-G-gated.

✅ #2 — Long-tail axes closed to their honest limit
batching_rule: 27 → 4 — textbook-batchable families complete; mesh-aware ones (moe/transport/kv-cache) stay partial.
transpose_rule: 40 → 0 — closed on the linear-vs-nonlinear principle: linear maps (sparse spmm/sddmm/bsmm, moe_transport gather/scatter adjoints, segment_reduce, tri_solve, avg_pool) → complete; nonlinear families (optimizers, recurrent cells, linalg decomposition, ebm energy/sampling, moe routing, max/min/adaptive pool) → not_applicable (their backward is the registered VJP, not a linear transpose). avg_pool handled per-name since pooling is a mixed category.
sharding_rule: 30 — left honestly partial. Every open entry is genuinely mesh-aware (distributed FFT transpose, seq/head-partitioned attention, all-to-all MoE, block-cyclic linalg, sequence-parallel SSM). There's no trivially-replicated subset to close — this is the same Phase-G-mesh gate as the universal backend_kernel axis, not closeable without real multi-GPU hardware.
Verification: transpose 40→0, 432 complete; 5 drift gates green; 284 coverage/manifest/roadmap/memory/reasoning tests pass + the new test_batching_rule_closure.py transpose guards (zero-open + linear-complete + nonlinear-N/A, both directions locked).
gstoner added a commit that referenced this pull request Jun 3, 2026
Apple is now an executable proof lane with value-producing Target IR, while artifact ops stay for dashboards. All 7 sprint items (AV0–AV6) landed and verified.

AV0 — cleanup
safeEraseLowered confirmed transactional (loops all results → poison → erase; no partial-rewrite path).
SVD verifier full_matrices=true branch added (U square M×M, V square N×N) — completes R4.
AV1 — value-producing ODS ops
Added to the tessera_apple dialect, alongside the unchanged artifact ops:
cpu.call, gpu.kernel_call, gpu.package_call — Variadic<AnyType> operands + results, attrs {op_kind, symbol, abi, status, dtype, framework, argument_layout}, assembly operands attr-dict : functional-type(...). Lit roundtrip fixture (incl. multi-result + argument_layout).

AV2/AV3 — value-mode lowering by pipeline intent
TileToApple got a valueMode flag. Artifact mode (default) = metadata ops + ub.poison husk. Value mode = replaceOp with value ops (true SSA hand-off, multi-result direct). Linalg converted: CPU all 6 → cpu.call; GPU cholesky/tri_solve → kernel_call (executable); other GPU linalg → named diagnostic + pass failure.

AV4 — -full = value-only + guard
tessera-lower-to-apple_{cpu,gpu}-full now pass valueMode=true. Verified end-to-end: %0 = tessera_apple.cpu.call %a, %b {...} : (...) -> ...; return %0 — multi-result svd %0:3 ... return %0#0,#1,#2, no ub.poison/tensor.empty/tile.*. Guard test (test_apple_value_target_ir.py) + reframed apple_linalg_review_fixes.mlir (husk now correctly an artifact-mode property on bare tile input).

AV5 — front door + dispatcher
driver.classify_apple_target_ir() → value_target_ir vs target_ir_artifact; extract_apple_value_calls() reads the dispatch tuple (op_kind/symbol/status) the runtime consumes; apple_value_call_is_executable() gates native execution on status=="executable" — the seam-closure contract (execute the symbol named in the IR).

AV6 — backend-neutral contract doc
docs/spec/VALUE_TARGET_IR_CONTRACT.md documents the value-op shape (value operands/results + backend attrs), artifact-vs-value pipeline intent, and the exact pattern NVIDIA/ROCm inherit — explicitly not converting their execution this sprint, only confirming Apple's shape is backend-neutral.

Verification
tessera-opt rebuilt clean (UB dialect registered earlier; 3 new value ops tblgen'd into TesseraApple).
lit phase2/3/8: 49 passed / 7 XFAIL / 0 unexpected.
73 Python tests pass (value-IR guard incl. classifier/extractor, linalg family, seam-closure, drift gate, pipeline registry, package-author, opt-build).
5/5 generated-doc drift gates ok.
gstoner added a commit that referenced this pull request Jun 6, 2026
…ph kernels)

#2 bf16 in the whole-graph lane: author_graph gains an io_bf16 boundary flag
(bf16 placeholders -> cast f32 -> f32 body -> cast bf16). The mlpkg reflection
path (MTLTensorDataTypeFromMPSDataType) hard-asserts on bf16 bindings today, so
GraphFn.run_mlpkg(elem="bf16") authors an f32 package and converts at the Python
boundary (bf16 in/out, f32 internal compute = ABI f32-accumulate). io_bf16 C
path retained for when bf16 bindings become reflectable.

#3 native bf16 MPSGraph kernels: tessera_apple_gpu_{mpsgraph_unary,
mpsgraph_binary,rmsnorm_gpu,layer_norm}_bf16 — native via the dtype-parameterized
mpsg_run_* helpers (bf16 supported per mpsgraph_bf16_supported()), with an
upcast->f32-extern->round host fallback; stub parity. The back-half's
rmsnorm/layer_norm/silu+unary/elementwise bf16 paths now run native bf16 instead
of host-upcast. _SENTINEL_SYMBOL bumped; runtime ABI docs regenerated.

bf16 is now uniform across the GPU back-half AND both GraphFn engines (run +
run_mlpkg). 256/256 production-lane + 1166 apple_gpu tests green; mypy clean on
host and --platform linux.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 7, 2026
Strategic reframe before building test infra: the compiler is NOT stub-riddled.
Reading the existing audit dashboards shows the software-actionable gap surface is
small and specific; most incompleteness is hardware-gated (expected) or thin-test
(better closed by generative differential testing than hand-written tests).

- scripts/stub_surface_report.py reads the checked-in dashboards (verifier_coverage,
  op_target_conformance, test_coverage) and emits a single ranked rollup that
  SEPARATES software-actionable gaps from hardware-gated rows. Rerunnable, derives
  every number from the CSVs (no hand-copied counts that drift).
  -> docs/audit/generated/stub_surface.md.
- Findings: ~9 trivial-stub verifiers (Arch* NAS + KVCacheCreate/RingCreate);
  ~9 software conformance cells (conv2d/kv_cache_read -> cpu stop at numerical;
  conv2d/flash_attn/kv_cache_read -> metalium/nvidia/rocm stop at codegen); the
  rest of the conformance failures are hardware-gated (honest); ~123 needs-direct-
  test ops are the differential-generator target.
- MASTER_AUDIT: a "Compiler-Completeness & Testing Program" section linking the
  report + the #1/#2/#4 workstream, and the key reframe (stubs are an
  oracle/conformance problem, not a fuzz problem; fuzz layers on the oracle).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 7, 2026
Synthesizes random well-typed programs over the executable lane
(tessera.ops elementwise/matmul/norm subset + tessera.control.fori_loop/cond)
and diffs two evaluators of the same function:
  * oracle    — eager fn(*arrays): tessera.ops numpy reference + eager control
  * candidate — run_traced(...): the real trace -> GraphFn / execute_traced
                fused run_graph_* Apple GPU (Metal) path

A mismatch is a miscompile in the production lane — the bug class hand-written
per-op tests (item #1's ~123 needs_direct_test ops) miss. 51 numerical cases
green on Apple GPU across straight-line, fused run_graph_loop, and fused
run_graph_cond; runtime-free trace/op-name + vocab-subset properties run
everywhere. Deterministic stdlib random over fixed seeds; square NxN tensors
so every op composes; unstable generated programs (non-finite/overflow eager
output) are skipped so generator noise can't masquerade as a miscompile.

Closes #2 + #4 of the compiler-testing program; MASTER_AUDIT updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 7, 2026
…lane

Adds test_differential_generator_hypothesis.py — a property-based sibling to the
stdlib differential generator. Same contract (eager numpy oracle vs the real
trace -> GraphFn / execute_traced Apple GPU path over the executable lane) but
driven by hypothesis @given strategies instead of fixed seeds, so a Metal
miscompile auto-shrinks to the minimal failing program (verified: an injected
fault on one op reduces to [(op,(0,))]). Guarded by importorskip('hypothesis')
so CI without it still passes on the stdlib harness.

Factors the shared program grammar + numpy oracle into _diff_lane.py (a non-test
helper, not collected) so the two harnesses can't drift; the stdlib test now
imports from it. 40 Metal examples/property green across straight-line, fused
run_graph_loop, and fused run_graph_cond.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 8, 2026
… + apple_gpu routing

Closes GA Gap #1 (no taped autodiff) + Gap #2 (GA lane disjoint from
tessera.ops) by projecting the tessera.ga.* Multivector lane onto the
canonical tessera.ops surface as flat 8-coefficient Cl(3,0) wrappers.

Keystone (python/tessera/_clifford_ops.py): 10 flat-coefficient wrappers
(geometric_product / wedge / left_contraction / inner / reverse /
grade_involution / conjugate / grade_projection / norm / norm_squared) over
the GA lane, which already GPU-dispatches Cl(3,0) f32 to the cl30 MSL kernels.
Registered into the tessera.ops namespace + autodiff tape chokepoint.

Autodiff (autodiff/vjp.py + jvp.py): closed-form VJPs + JVPs for all 10 ops,
validated to ~1e-5 vs finite-difference:
  - geometric_product adjoint via the reverse involution
  - wedge / left_contraction via basis-probe transpose of the linearized map
  - reverse / grade_involution / conjugate / grade_projection self-adjoint
  - norm / norm_squared via Euclidean metric probe

apple_gpu routing: op_catalog OpSpecs (tessera.clifford_*) so the AST graph
builder emits IR; _APPLE_GPU_CLIFFORD_OPS envelope + _apple_gpu_dispatch_clifford
in runtime.py (mirrored in driver.py) so @jit(target="apple_gpu") clifford
calls report execution_mode="metal_runtime". The dispatcher defers to the GA
shim (single source of GA truth).

primitive_coverage: the 9 GA4-owned clifford names keep their authoritative
_planned() rows (references + halo sharding + Clifford .td alignment) — the
OP_SPECS auto-import skips them so the richer rows win via setdefault.
clifford_norm_squared (not in the GA4 / .td set) imports as a reduction row.

Tests: test_clifford_ops_autodiff.py (24 — surface parity, tape backward,
JVP, batched), test_apple_gpu_clifford_lane.py (7 — envelope + metal_runtime).
PYTHON_API_SPEC.md rows added; generated dashboards regenerated; GA4 coverage
+ Clifford-dialect-wiring contracts preserved (17/17). mypy host+linux + ruff
clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 8, 2026
The clifford_* (10) + ebm_* (4) flat-array shims onto tessera.ops close GA/EBM
Gap #1 (tape autodiff with closed-form VJP/JVP) and Gap #2 (@jit(apple_gpu)
metal_runtime routing to the cl30/EBM MSL kernels), with the authoritative
geometric_algebra/ebm coverage rows preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 8, 2026
The canonical rotor-invariant norm(rotor_sandwich(R, x)) previously dispatched
two GA ops with an intermediate multivector round-trip through global memory.
This fuses it into a single kernel.

Kernel — tessera_apple_gpu_clifford_rotor_sandwich_norm_cl30_f32 in
apple_gpu_runtime.mm: reuses the Cl(3,0) double-geometric-product expansion,
keeps the 8-vector sandwich result in registers, and writes only the scalar
norm per batch element (one dispatch, no intermediate spill). C++ reference
fallback for non-Metal hosts.

Surface — ga.rotor_sandwich_norm(R, x) is the direct entry (numpy fallback =
norm(rotor_sandwich(...))); routes Cl(3,0) f32 through the bridge manifest.

Fusion pass — _fuse_rotor_sandwich_norm rewrites the rotor_sandwich→norm
adjacency into one clifford_rotor_sandwich_norm op, but ONLY when the
intermediate sandwich is consumed exactly once and isn't the program return.
Applied by the @clifford_jit decorator (and the lazy compile path); the
structural lower_function_to_ir stays a faithful unfused 1:1 AST→IR projection.
So @clifford_jit(norm(rotor_sandwich(...))) now compiles to a single-op plan +
single dispatch.

Wiring — _CLIFFORD_APPLE_GPU_FUSED + _GA_ATTR_TO_OP_NAME + a new
_CLIFFORD_FUSION_OPS set (kept OUT of the 17 _CLIFFORD_PRIMITIVES so the
"seventeen primitives" audits/counts stay exact); clifford_manifest_for now
reports fusion ops (apple_gpu fused fp32 + CPU reference). _SENTINEL_SYMBOL
bumped.

Tests: test_clifford_rotor_sandwich_norm_fusion.py (8 — numerics, fusion pass,
non-fusable cases, manifest, @clifford_jit single-op plan + single route).
Updated the canonical-chain plan/route assertions across test_clifford_jit,
test_compile_report{,_auto_emission}, test_benchmark_{ga_ebm,row},
test_compiler_audit, test_ga_backend_manifest to reflect the fused plan.
mypy host+linux + ruff clean; generated docs in sync.

Closes GA/EBM close-out gaps #1/#2/#5/#6; remaining: Apple-CPU GA/EBM native
kernels (#3), Cl(1,3) kernels (#4, gated with a diagnostic), exp/log GA autodiff.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 13, 2026
)

#1 — Draft KV cache. block_diffusion_attention gains return_ctx_kv to expose
this step's projected+roped context KV; DraftKVCache accumulates it per layer.
dflash_decoder_layer_cached / dflash_draft_forward_cached thread the cache so
the draft attends to the full accumulated context instead of recomputing it.
Verified: cached(accumulated) == non-cached(full context) to 1e-3, and the
cache accumulates/advances correctly across steps.

#2 — Non-greedy sampling + distribution-preserving acceptance. make_sampler
(temperature / top-k / top-p, rng-reproducible), sampler_probs (matching
truncated distribution), and dflash_speculative_verify (Leviathan rule: accept
d_i w.p. min(1, p_t/p_d); on reject draw from normalize(relu(p_t-p_d)); bonus
from the target's next-position distribution). Verified: greedy == argmax,
top-k restricts support, draft==target accepts all, and the speculative-sampling
theorem — the emitted token's marginal equals the target distribution (Monte
Carlo, 40k draws, max abs err < 0.02).

tests/unit/test_dflash_cached_sampling.py (7). ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 13, 2026
…odel (#4)

#4 — tessera.dflash_reference.ReferenceDecoderLM: a small numpy causal decoder
(pre-norm MHA + SwiGLU, rope, tied/untied LM head) with a multi-layer hidden tap
(the DFlash conditioning signal) and a stateless forward() that is the greedy-AR
ground truth. random_decoder_lm builds one with small random weights.

#3 — stateful KV cache + rollback: step(tokens) does causal cached decoding and
appends roped-K/V per layer; rollback(n) drops the over-speculated tail. Verified
that incremental step() (in 3 chunks) reproduces the stateless full-sequence
forward to 1e-3, and that rollback restores exact cache state.

dflash_generate_cached ties it together: cached draft (#1) + stateful target with
rollback (#3) + greedy or rejection sampling (#2). Verified the whole efficient
loop reproduces greedy AR exactly, is block-size independent, and sampling is
reproducible + in-vocab. tests/unit/test_dflash_reference_target.py (5).
ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 13, 2026
)

#1 — Draft KV cache. block_diffusion_attention gains return_ctx_kv to expose
this step's projected+roped context KV; DraftKVCache accumulates it per layer.
dflash_decoder_layer_cached / dflash_draft_forward_cached thread the cache so
the draft attends to the full accumulated context instead of recomputing it.
Verified: cached(accumulated) == non-cached(full context) to 1e-3, and the
cache accumulates/advances correctly across steps.

#2 — Non-greedy sampling + distribution-preserving acceptance. make_sampler
(temperature / top-k / top-p, rng-reproducible), sampler_probs (matching
truncated distribution), and dflash_speculative_verify (Leviathan rule: accept
d_i w.p. min(1, p_t/p_d); on reject draw from normalize(relu(p_t-p_d)); bonus
from the target's next-position distribution). Verified: greedy == argmax,
top-k restricts support, draft==target accepts all, and the speculative-sampling
theorem — the emitted token's marginal equals the target distribution (Monte
Carlo, 40k draws, max abs err < 0.02).

tests/unit/test_dflash_cached_sampling.py (7). ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 13, 2026
…odel (#4)

#4 — tessera.dflash_reference.ReferenceDecoderLM: a small numpy causal decoder
(pre-norm MHA + SwiGLU, rope, tied/untied LM head) with a multi-layer hidden tap
(the DFlash conditioning signal) and a stateless forward() that is the greedy-AR
ground truth. random_decoder_lm builds one with small random weights.

#3 — stateful KV cache + rollback: step(tokens) does causal cached decoding and
appends roped-K/V per layer; rollback(n) drops the over-speculated tail. Verified
that incremental step() (in 3 chunks) reproduces the stateless full-sequence
forward to 1e-3, and that rollback restores exact cache state.

dflash_generate_cached ties it together: cached draft (#1) + stateful target with
rollback (#3) + greedy or rejection sampling (#2). Verified the whole efficient
loop reproduces greedy AR exactly, is block-size independent, and sampling is
reproducible + in-vocab. tests/unit/test_dflash_reference_target.py (5).
ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 13, 2026
* feat(dflash): cached drafting (#1) + sampling & rejection acceptance (#2)

#1 — Draft KV cache. block_diffusion_attention gains return_ctx_kv to expose
this step's projected+roped context KV; DraftKVCache accumulates it per layer.
dflash_decoder_layer_cached / dflash_draft_forward_cached thread the cache so
the draft attends to the full accumulated context instead of recomputing it.
Verified: cached(accumulated) == non-cached(full context) to 1e-3, and the
cache accumulates/advances correctly across steps.

#2 — Non-greedy sampling + distribution-preserving acceptance. make_sampler
(temperature / top-k / top-p, rng-reproducible), sampler_probs (matching
truncated distribution), and dflash_speculative_verify (Leviathan rule: accept
d_i w.p. min(1, p_t/p_d); on reject draw from normalize(relu(p_t-p_d)); bonus
from the target's next-position distribution). Verified: greedy == argmax,
top-k restricts support, draft==target accepts all, and the speculative-sampling
theorem — the emitted token's marginal equals the target distribution (Monte
Carlo, 40k draws, max abs err < 0.02).

tests/unit/test_dflash_cached_sampling.py (7). ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(dflash): stateful target with rollback (#3) + reference target model (#4)

#4 — tessera.dflash_reference.ReferenceDecoderLM: a small numpy causal decoder
(pre-norm MHA + SwiGLU, rope, tied/untied LM head) with a multi-layer hidden tap
(the DFlash conditioning signal) and a stateless forward() that is the greedy-AR
ground truth. random_decoder_lm builds one with small random weights.

#3 — stateful KV cache + rollback: step(tokens) does causal cached decoding and
appends roped-K/V per layer; rollback(n) drops the over-speculated tail. Verified
that incremental step() (in 3 chunks) reproduces the stateless full-sequence
forward to 1e-3, and that rollback restores exact cache state.

dflash_generate_cached ties it together: cached draft (#1) + stateful target with
rollback (#3) + greedy or rejection sampling (#2). Verified the whole efficient
loop reproduces greedy AR exactly, is block-size independent, and sampling is
reproducible + in-vocab. tests/unit/test_dflash_reference_target.py (5).
ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(dflash): GPU draft attention (#5) + training loss (#9a) + checkpoint I/O (#7)

#5 — attention_fn threaded through dflash_decoder_layer / dflash_draft_forward
(+ cached variants) so the whole draft forward runs its attention on the Apple
GPU metal_runtime lane via apple_gpu_attention_fn. Verified the whole draft
(2 layers) matches the numpy reference on Metal (rtol/atol 1e-3). The matmul-
heavy projections/MLP/LM-head stay host-side (GPU gather/embedding is the
remaining blocker for a single fully-jitted artifact).

#9a — position-weighted block training loss: dflash_position_weights
(wₖ = exp(-k/γ), normalized), dflash_block_loss (mean/sum/none) and the explicit
gradient dflash_block_loss_grad. Verified the gradient vs finite differences
(<1e-7), that a grad step lowers the loss, and reduction consistency.

#7 — checkpoint I/O (tessera.dflash_io): a dependency-free safetensors
reader/writer + HF state-dict <-> DFlashWeights mapping (transposing the
nn.Linear (out,in) weights to the x@W (in,out) convention; embedding/LM head
supplied from the target). load_dflash_weights reads a z-lab/*-DFlash draft;
verified safetensors round-trip, the (out,in) transpose, and that round-tripped
weights produce identical draft logits.

tests: test_dflash_train_io.py (7) + #5 GPU draft case. ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(dflash): nn.Module (#6) + rotating cache (#9b) + tokenizer/scheduler (#9c/#9d)

#6 — DFlashDraft(nn.Module): holds every draft tensor as a Parameter (so it
participates in parameters()/state_dict/.to(dtype)), forwards through the
functional draft (cached or not), from_weights()/to_weights() round-trip.
Verified module forward == functional (<1e-5), 5 + 11*N params registered,
weight round-trip.

#9b — RotatingDraftKVCache: bounds the draft context cache to the last max_size
tokens (the draft analogue of MLX RotatingKVCache for sliding layers). Verified
it caps per-layer length and, when unbounded, is identical to DraftKVCache.

#9c/#9d — tessera.dflash_serve: dflash_generate_text (string-in/out via any
encode/decode tokenizer) and DFlashScheduler (holds draft + stateful target,
serves generation requests, greedy == AR). Verified scheduler greedy == AR and
generate_text round-trips through a tokenizer.

tests/unit/test_dflash_module_serve.py (5). ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(dflash): GQA repeat note (#8) + MASTER_AUDIT integration 1–9 landed

#8 — annotate block_diffusion_attention's GQA: repeat is numerically exact; the
native flash_attn_gqa kernel doesn't support DFlash's concat-context+proposal KV
with an additive bias, so the reference materializes the repeat (no code change —
correctness is unaffected).

MASTER_AUDIT records DFlash integration items 1–9 as landed, with the two
remaining gates flagged as external (real-checkpoint numerical parity needs a
network download; a single fully-jitted GPU draft artifact needs GPU gather).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: record DFlash + attn_bias in README, add docs/dflash.md

- README status table: new "Speculative decoding — attn_bias substrate + DFlash
  block-diffusion draft" row (honest status: Python reference + attention core on
  Apple GPU metal_runtime; greedy spec-decode == greedy AR proven vs the MLX
  reference; real-checkpoint parity + fully-jitted GPU draft are external gates).
- README: refresh stale Apple C ABI counts to the generated truth (256→264
  symbols, 109→112 kernel families).
- New docs/dflash.md: user-facing overview — the attn_bias substrate, the module
  map (dflash / dflash_reference / dflash_io / dflash_serve), a quick start,
  what's proven (per-test), and the external gates. Linked from the README doc
  index. docs lint passes; all links resolve.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(api): fold DFlash + attn_bias public API into PYTHON_API_SPEC + CANONICAL_API

PYTHON_API_SPEC.md:
- flash_attn signature + parameter table gain attn_bias (additive (B,Sq,Sk) mask,
  Apple GPU flash_attn_bias_* / metal_runtime, causal+bias, broadcast fallback,
  positional-bias VJP).
- Module hierarchy lists tessera.dflash / dflash_reference / dflash_io / dflash_serve.
- New §18 "Speculative Decoding (DFlash)" documents the full public surface across
  the four modules + nn.functional.block_diffusion_attention / mask_token_block;
  TOC + Appendix A symbol index updated.

CANONICAL_API.md:
- flash_attn ops row gains attn_bias; functional table gains block_diffusion_attention
  + mask_token_block; new "tessera.dflash — Speculative Decoding (DFlash)" section
  with canonical names (one per concept) + a quick-start.

Verified: check_spec_sync + docs lint pass; every documented symbol exists and
every module __all__ symbol is documented (zero drift, confirmed programmatically).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(api): close tessera.nn / tessera.ops doc-coverage gaps (full sweep)

A programmatic sweep of the public surface vs both API docs found the surface
~99% documented with concentrated gaps; this closes them to zero.

tessera.ops (312 ops): added the 2 missing — bmm (batched matmul + broadcast,
Apple GPU metal_runtime) and fake_quantize (QAT STE) — to both ops tables.

tessera.nn (77 public attrs): added the 10 missing functional layers
(linear_general, lora_linear, spectral_norm, conv_transpose, avg/max/min/adaptive
pool, gru_cell, simple_rnn_cell, bidirectional_scan) to CANONICAL's functional
table, and the 10 missing Module classes (LinearGeneral, Einsum, LoRALinear,
ConvTranspose1d/ConvTranspose, SpectralNorm, GRUCell/SimpleRNNCell,
NativeSparseAttention, MixtureOfRecursions) to the stateful class table, with
accurate constructor/forward signatures.

Verified programmatically: tessera.ops, tessera.nn, and nn.functional.__all__
now have ZERO undocumented public symbols. check_spec_sync + docs lint pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@gstoner
gstoner deleted the alert-autofix-1 branch June 14, 2026 22:29
gstoner pushed a commit that referenced this pull request Jun 24, 2026
Item #2: close the Decision #19 gap. The Graph tessera.matmul -> Tile -> Target-IR
lowering (_lower_rocm_op on tile.mma) now EMITS the executable
tessera_rocm.wmma_gemm directive (m=n=k=16 WMMA tile + dtype) alongside the
abstract tessera_rocm.mfma marker. So a @jit(target="rocm") matmul's target_ir
contains the directive the generate-wmma-gemm-kernel pass consumes — the
directive is produced by the IR stack, not only synthesized by the runtime.

- target_ir.py: tile.mma -> [mfma (abstract marker, kept for the hardware-free
  contract + lit), wmma_gemm (concrete RDNA executable directive), async_copy,
  wait]. dtype threaded from the tile op (f16 default).
- Test (GPU-free, CI-runnable): test_rocm_matmul_front_end_glue.py — the
  directive appears in target_ir with the right attrs AND the extracted directive
  feeds the generate pass into a gpu.func + WMMA op (directive consumed).
- The abstract mfma marker stays (target_ir_contract / lit assertions unchanged).
- The runtime lane still synthesizes a clean directive at launch for the
  per-shape mt/nt perf choice; the canonical lowering now owns directive
  production. Docs updated (op .td + ROCM_AUDIT).

drift in sync, ruff + mypy clean, target_ir + rocm/wmma regression green
(pre-existing test_apple_value_target_ir failures are Apple-backend-off, unrelated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Jun 24, 2026
…lue + int/FA perf ladders (#90)

* feat(rocm): compiler-generated flash_attn (FA-2 forward) — second compiled op

Brings flash_attn into the compiler-generated lane (was hand-written HIPRTC
only). The Stage L machinery (directive -> generated WMMA kernel -> in-process
hsaco) now covers a second op.

- New tessera_rocm.flash_attn directive (head_dim, dtype) + the
  generate-wmma-flash-attn-kernel pass: a faithful MLIR re-emission of the
  hardware_verified hand-written FA-2 forward kernel — one wave per (16-query
  tile, b*h), LDS-staged Q (gpu.func workgroup attributions), S = scale*Q@K^T on
  WMMA over head-dim chunks, causal/ragged mask, online softmax (running
  max/sum, rescale), O += P@V on WMMA. Scores are staged in LDS so the QK^T
  accumulator layout is reread in the P@V A-fragment layout (the layout bridge).
  head_dim (mult of 16) is compile-time; Sq/Sk/scale/causal are runtime args.
- tessera-opt: registered the math ConvertToLLVM external model (so
  convert-gpu-to-rocdl lowers the softmax math.exp -> llvm exp) + the math
  dialect; gpu.barrier + workgroup LDS already lower. The flash_attn pipeline is
  the same in-process chain as the GEMM lane (no mlir-opt).
- Test: the compiler-generated FA-2 forward executes on gfx1151 matching a numpy
  attention reference (maxerr < 2e-2) across head_dim 16/64, causal/non-causal,
  and ragged Sq/Sk. test_rocm_flash_attn_compiled.py.

Honest scope: forward only; the runtime.launch() executor-table lane (a
flash_attn op-metadata contract + executor + matrix row) is the remaining glue,
same additive step matmul took at L4 — not yet wired, so no execution-matrix row
claimed. backward + perf ladder remain (audit item 10).

drift in sync, ruff clean, rocm/wmma/flash_attn regression green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(rocm): front-end glue — IR stack emits the wmma_gemm directive

Item #2: close the Decision #19 gap. The Graph tessera.matmul -> Tile -> Target-IR
lowering (_lower_rocm_op on tile.mma) now EMITS the executable
tessera_rocm.wmma_gemm directive (m=n=k=16 WMMA tile + dtype) alongside the
abstract tessera_rocm.mfma marker. So a @jit(target="rocm") matmul's target_ir
contains the directive the generate-wmma-gemm-kernel pass consumes — the
directive is produced by the IR stack, not only synthesized by the runtime.

- target_ir.py: tile.mma -> [mfma (abstract marker, kept for the hardware-free
  contract + lit), wmma_gemm (concrete RDNA executable directive), async_copy,
  wait]. dtype threaded from the tile op (f16 default).
- Test (GPU-free, CI-runnable): test_rocm_matmul_front_end_glue.py — the
  directive appears in target_ir with the right attrs AND the extracted directive
  feeds the generate pass into a gpu.func + WMMA op (directive consumed).
- The abstract mfma marker stays (target_ir_contract / lit assertions unchanged).
- The runtime lane still synthesizes a clean directive at launch for the
  per-shape mt/nt perf choice; the canonical lowering now owns directive
  production. Docs updated (op .td + ROCM_AUDIT).

drift in sync, ruff + mypy clean, target_ir + rocm/wmma regression green
(pre-existing test_apple_value_target_ir failures are Apple-backend-off, unrelated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(rocm): int8/int4 compiled-GEMM dtype sweep — measured (packed int4 deferred)

Item #3. Measured the compiler-generated int paths rather than assuming.

benchmark_rocm_compiled_gemm_dtype.py (new): kernel-only dtype sweep of the
compiled WMMA GEMM (f16/bf16/int8/int4, best macro-tile), honest-gated, JSON
schema. On gfx1151 at 2048^3:
  f16 ~23.2 TFLOP/s, bf16 ~23.1, int8 ~21.0 TOP/s, int4 ~23.8 TOP/s  (within ~10%)

Finding: RDNA 3.5 WMMA runs iu8/iu4 at the SAME matrix-op rate as f16 (no
low-precision FLOP-rate multiplier), so the compiled int paths are already
compute-competitive and the int4 in-kernel nibble-pack is amortized.

Consequence (measured, not assumed — Decision #25): packed-memory int4 (2
int4/byte) would buy memory footprint (1/2) + bandwidth, NOT compute on this
arch. Its large sub-byte-strided-B layout is therefore deliberately DEFERRED —
unjustified by a compute speedup that doesn't exist on RDNA 3.5. Documented in
ROCM_AUDIT with the numbers.

drift in sync, ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(rocm): compiler-generated flash_attn forward perf ladder (measured)

Item #4 (perf-ladder half). Moves the compiled FA-2 forward from "rung-0
correctness-only, no perf data" to a measured ladder.

benchmark_rocm_flash_attn_compiled.py (new): kernel-only hipEvent ladder of the
compiler-generated FA forward across (head_dim, seqlen), honest-gated, JSON
schema. On gfx1151: ~4.0 TFLOP/s at head_dim 64, ~2.4 at 128 (FA-2 fwd FLOPs =
4*B*H*Sq*Sk*D). Modest by design — the kernel is correctness-first (one wave per
query tile, LDS round-trips, online-softmax barriers, no KV pipelining / double
buffering / multi-wave query tiles); the ladder quantifies the headroom.

Audit item 10 updated: forward + forward-ladder done; flash_attn BACKWARD is the
largest remaining attention piece (no hand-written oracle — a new kernel
validated vs a numpy attention-backward reference; a focused standalone effort
comparable to the forward), plus the runtime.launch() executor-table lane.

drift in sync, ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(rocm): compiler-generated flash_attn BACKWARD (3 WMMA kernels)

Expand a single tessera_rocm.flash_attn_bwd directive into the textbook
FA-2 backward as three fragment-materialized RDNA WMMA kernels (no stored
attention matrix; S/P recomputed per tile):
  _pre   scalar logsumexp L + D=rowsum(O*dO)
  _dkdv  per key-tile: dP=dO@V^T, dS=P*(dP-D), dV+=P^T@dO, dK+=scale*dS^T@Q
  _dq    per query-tile: dQ+=scale*dS@K
All use the same C[m,n]=sum_k A[m,k]B[n,k] WMMA primitive + Stage J->I
lowering as the forward; P/dS staged in LDS and reread transposed (the
layout bridge). Executes on gfx1151 vs a numpy attention-backward reference
(itself checked against finite differences): rel-err ~2-4e-4 (f16 storage,
f32 accumulate) across head_dim 16/64, causal+non-causal, ragged.

flash_attn (fwd+bwd) is now the third compiler-generated op on ROCm after
matmul. Backward perf ladder measured: ~1.1-1.3 TFLOP/s @ D=64 (correctness-
first; scalar logsumexp pre-pass + 5 matmuls dominate — WMMA logsumexp /
causal tile-skip / pipelining are the next rung).

- ROCM_FlashAttnBwdOp ODS + GenerateWMMAFlashAttnBwdKernel pass (registered)
- tests/unit/test_rocm_flash_attn_bwd_compiled.py (on-device, skip-clean)
- benchmarks/rocm/benchmark_rocm_flash_attn_bwd_compiled.py (ladder)
- ROCM_AUDIT item 10 updated

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: gstoner <angstroms01@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
gstoner added a commit that referenced this pull request Aug 18, 2026
…kend sync

Both #584 review findings accepted and implemented:

P2 — chain law now anchors its finite difference on the CANONICAL forward
(resolved via linear._resolve_forward), never the JVP's own primal: a JVP
that self-consistently implements the wrong function (exp(2x) with tangent
2·dx·exp(2x)) previously agreed with an FD of its own primal on every
probe. Added a primal-consistency gate (JVP primal must equal the canonical
forward at the base point) and a fail-closed not_applicable status when no
canonical forward resolves — the law refuses rather than degrading to
self-consistency. Both scenarios are pinned as executable tests.

The hardened anchor immediately caught finding #2 of the rmsnorm class:
jvp_clamp spelled its kwargs min_val/max_val while the forward and
vjp_clamp use min/max, so canonical kwargs fell into `**_` and the JVP
silently computed the UNCLAMPED identity for primal and tangent (and the
matched-degenerate pair passed the adjoint law). Fixed to min/max; pinned
by test_clamp_jvp_honors_canonical_kwargs. A registry-wide scan for the
class ("one side declares a kwonly name the sibling swallows via `**_`")
found 20 more (op, side) instances — fft/stft norm handling, clip, pow
exponent, the quantize family — pinned exactly as OPEN findings in
_KNOWN_SWALLOWED_KWARGS: additions fail the gate, and fixes must remove
their entry to record the triage outcome. Triage lands in the next
AD-LAW slice, not here.

P1 (AGENTS.md L81-85) — cross-backend sync recorded in all four backend
plans under key AD-LAW-1-SHARED-ORACLE-2026-08-18: Apple / ROCm / x86
parity validated (explicit-eps norm bindings; VJP-side defaults unmoved;
no device evidence produced or claimed), NVIDIA not-applicable today with
parity by construction for future family migrations.

Sweep after hardening: 54 adjoint + 52 chain pass, 0 failures, all chain
rows canonically anchored. 16 law tests + 135 adjacent green; mypy 466
files clean; ruff clean; generated docs 26 in sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant