Skip to content

Commit 5068efe

Browse files
physicsrobclaude
andcommitted
Drift test: tolerate both GEMM reduction orders on the staircase rows
The staircase rows measure fp32 reduction order, not the op: lane contributions reach ~6.4e5 and cancel to ~4.0, so the surviving error is ULP-quantized at the partial-sum magnitude and lands one to two binades apart between sequential (CPU BLAS) and blocked (A100 cuBLAS) orders. A targeted factor-of-8 ratio guard on piecewise_linear's staircase_* distributions covers both orders while still catching a genuine decomposition regression; the global 40% tolerance is unchanged for every other row. Verified: passes locally and on an A100 (previously the suite's only failure). Findings entry updated; restructuring the lane decomposition remains the open principled fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent fd79928 commit 5068efe

2 files changed

Lines changed: 47 additions & 6 deletions

File tree

docs/numerical_noise_findings.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,18 @@ a property of the summation order — which differs between local CPU BLAS
3434
and A100 cuBLAS — and the op's real defect is catastrophic cancellation in
3535
its lane decomposition.
3636

37-
**Status: decision pending (spans_plan.md decision 2).** Either pin the
38-
probe's device and reduction so the committed number means something, or
39-
restructure the lane decomposition so contributions don't blow up. Do
40-
**not** re-run `make measure-noise` on whichever machine is to hand — that
41-
re-pins one device's reduction order and papers over the defect again.
37+
**Status: decided 2026-07-22 (Rob) — the drift gate tolerates both
38+
reduction orders.** `test_numerical_noise_drift.py` now applies a targeted
39+
factor-of-8 ratio guard to the `staircase_*` rows of `piecewise_linear`
40+
(both machines) instead of the global 40% tolerance: the observed
41+
CPU-sequential vs A100-blocked swing is 2–4x and ULP-quantized, and a
42+
genuine decomposition regression moves these numbers by orders of
43+
magnitude, so the wider guard still catches it. The committed numbers
44+
remain the sequential-order row. Restructuring the lane decomposition so
45+
contributions don't blow up remains open as the principled fix. The
46+
warning still stands: do **not** re-run `make measure-noise` on whichever
47+
machine is to hand to "fix" a staircase diff — that just re-pins the other
48+
reduction order.
4249

4350
### MLP-side cancel on the swish machine: worst residue 1.5e-8, gate passes (2026-07-07)
4451

tests/docs/test_numerical_noise_drift.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,21 @@
6060
# exact on every kernel. Absolute moves above 5e-4 are genuine drift; tight
6161
# per-op budgets live in the ops' unit tests, not here.
6262
_ATOL = 5e-4
63+
# The staircase rows measure fp32 GEMM reduction order, not the op: the
64+
# decomposition sums lanes whose contributions reach ~6.4e5 and cancel to
65+
# ~4.0, so the surviving error is a small multiple of the fp32 ulp at the
66+
# partial-sum magnitude — quantized in powers of two, and one to two binades
67+
# apart between sequential (local CPU BLAS) and blocked (A100 cuBLAS)
68+
# reduction orders (largest observed swing: p99_abs_error 4x, 0.03125 ->
69+
# 0.125). A factor-of-8 ratio guard covers both orders with one binade of
70+
# headroom while still catching a genuine decomposition regression, which
71+
# moves these numbers by orders of magnitude. See
72+
# docs/numerical_noise_findings.md § staircase.
73+
_STAIRCASE_RATIO = 8.0
74+
75+
76+
def _is_staircase(op_key: tuple, dist_name: str) -> bool:
77+
return op_key[1] == "piecewise_linear" and dist_name.startswith("staircase_")
6378

6479

6580
def _close_enough(a: float, b: float) -> bool:
@@ -72,6 +87,21 @@ def _close_enough(a: float, b: float) -> bool:
7287
return abs(a - b) <= _ATOL + _RTOL * max(abs(a), abs(b))
7388

7489

90+
def _ratio_close(a: float, b: float, factor: float) -> bool:
91+
"""True when the two magnitudes are within ``factor`` of each other —
92+
the machine-dependent-row guard (see _STAIRCASE_RATIO)."""
93+
if a is None and b is None:
94+
return True
95+
if a is None or b is None:
96+
return False
97+
if a == b:
98+
return True
99+
lo, hi = sorted((abs(a), abs(b)))
100+
if lo == 0.0:
101+
return hi <= _ATOL
102+
return hi / lo <= factor
103+
104+
75105
def _compare_ops(committed: dict, regenerated: dict) -> List[str]:
76106
"""Compare two stripped JSON dicts and return a list of failure messages.
77107
@@ -116,7 +146,11 @@ def _compare_ops(committed: dict, regenerated: dict) -> List[str]:
116146

117147
for key in _ERROR_METRIC_KEYS:
118148
cv, rv = cd[key], rd[key]
119-
if not _close_enough(cv, rv):
149+
if _is_staircase(op_key, dist_name):
150+
ok = _ratio_close(cv, rv, _STAIRCASE_RATIO)
151+
else:
152+
ok = _close_enough(cv, rv)
153+
if not ok:
120154
failures.append(f"{op_name}/{dist_name}: {key} " f"{cv} -> {rv}")
121155

122156
return failures

0 commit comments

Comments
 (0)