Skip to content

compile: four more Espresso workarounds from fuzz round 2; matmul saturation characterized - #116

Merged
sbryngelson merged 2 commits into
mainfrom
fix/fuzz-triage-batch1
Jul 23, 2026
Merged

compile: four more Espresso workarounds from fuzz round 2; matmul saturation characterized#116
sbryngelson merged 2 commits into
mainfrom
fix/fuzz-triage-batch1

Conversation

@sbryngelson

Copy link
Copy Markdown
Owner

Triage of all 7 findings from the second fuzz batch. Verdict: four real Espresso bugs fixed with probed workarounds, two oracle-model corrections, and one genuinely open corruption class, now tracked with replayable reproducers.

Fixed (emitter/lowering workarounds, each probed to its boundary)

  • round corrupts |x| ≥ 1024 (round(1024)=1025, round(1025)=1026, round(−2047)=−2048 — it adds 0.5 where fp16 can't represent the tie). Since every fp16 value ≥1024 is already an integer, the emitter lowers select(|x| < 1024, round(x), x) — exact over the full range. This also heals the rmax→round→muls dropped-tail chain.
  • muls(0) after a reduce crashes ANECCompile — emitted as sub(x, x).
  • Empty programs crash Espresso (unordered_map::at: key not found): a graph whose output is an input lowers to an empty MIL body. compile() now wraps such graphs in an exact scalar mul(1.0) at the graph level (three emitter-level guard attempts documented why: output-port naming breaks on rename, Espresso strips no-op reshapes, and late-registered blob weights don't resolve).
  • Bonus from round 1's test writing, already merged context: ANE round is half-away-from-zero.

Characterized, not worked around (measured, in the new issue)

  • Matmul saturates at ~32752 = fp16_max/2, for every K — 32,400 exact at K=4, 33,280 → inf at K=64. K≤32 accumulation is wide (my earlier fp16-accumulation reading was wrong — corrected in comments). The fuzzer's accumulation screen bounds sit below the cliff.
  • Integer reduce sums lose bit-exactness crossing 2048 (2050-for-2048 finding); also modeled by the screen.

Open

Two transpose-fed matmul findings return inf with mathematically bounded sums (≤17,321 under any ordering — no correct path reaches saturation). Replayable specs + characterization in the issue; that's the next thread.

Verification (on-device, M5)

11 regression tests in test_espresso_workarounds.py (all pass); 187 tests across ONNX/fuzz/workaround suites; 6/8 finding specs replay as PASSING post-fix; fresh 400-graph batches on two seeds surface only the open matmul class; off-device selection, ruff, pyright clean.

…uration characterized

Emitter/lowering workarounds, each probed to its exact boundary:

- round: the ANE kernel corrupts |x| >= 1024 (round(1024)=1025,
  round(1025)=1026, round(-2047)=-2048 - it adds 0.5 where fp16 cannot
  represent the tie). Every fp16 value >= 1024 is already integral, so
  the emitter lowers select(|x| < 1024, round(x), x): exact on the full
  range. This also heals the reduce->round->muls chain, whose tail was
  dropped by the same scale-slot fusion family as #112.
- muls(k=0): mul-by-zero after a reduce crashes ANECCompile; emitted as
  sub(x, x) - identical zeros for every finite x, compiles everywhere.
- empty programs: a graph whose output IS an input lowers to an empty
  MIL body, which crashes Espresso with 'unordered_map::at: key not
  found'. compile() now wraps such graphs in an exact scalar mul(1.0)
  at the GRAPH level (emitter-level guards broke output-port naming,
  and Espresso strips no-op reshapes back to the empty body).

Characterized and modeled in the fuzzer's oracle rather than worked
around (see the new issue): matmul results saturate to inf above
~32752 = fp16_max/2 for every K (K<=32 accumulation is wide - the
earlier fp16-accum hypothesis was wrong); integer reduce sums lose
exactness crossing 2048. The fuzzer's accumulation screen bounds sit
below both cliffs. Two transpose-fed matmul findings return inf even
below the threshold and remain open.

11 regression tests in test_espresso_workarounds.py; 187 tests across
the ONNX/fuzz/workaround suites; 6 of 8 round-2 finding specs replay as
PASSING (the two open matmul cases are tracked in the issue); fresh
400-graph batches on two seeds produce only the open matmul class.
All round-2 measurements came from one machine (M5 / H17s); ANE
generations differ at the datapath level (see the guide's datapath
chapter), so:

- Every workaround comment now states the measured family, and why the
  workaround FORM is safe everywhere regardless (select-round, stable
  softplus, sub(x,x), and the mul(1.0) guard are semantically exact on
  a correct kernel too - the only cross-family question is whether they
  compile, which is now tested: cross_compile_check over all of them
  for H13-H16s, added to test_espresso_workarounds).
- cross_compile_check gets the same empty-program guard as compile()
  (the cross-family matrix exposed that it bypassed it).
- The fuzzer's oracle docstring says its cliffs (matmul saturation at
  ~32752, integer reduce exactness at 2048) are H17s measurements, and
  that a community run diverging on another chip is per-family DATA,
  not noise.
@sbryngelson
sbryngelson merged commit 5979658 into main Jul 23, 2026
13 checks passed
@sbryngelson
sbryngelson deleted the fix/fuzz-triage-batch1 branch July 23, 2026 01:11
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