Skip to content

Commit ec31d57

Browse files
committed
aarch64 reproduces the effects oracle now, so its allowance goes
The four ubuntu-24.04-arm cells on PR #86 fail verify_effects, and they fail by AGREEING with x86_64. aarch64's accepted baseline 3ec1c31f no longer describes what the architecture produces; what it produces is 544d29ca, which is cpython_stdout_sha256 itself, with "0 of 882 numeric fields differ" against the committed effects_component_stdout.txt. WHY, AND IT IS THE SAME CAUSE THAT WAS WRITTEN DOWN The 2026-09-03 acceptance said the deviation was "confined to the two delay-line interpolators, consistent with fused multiply-add rounding" -- six blocks, multitap 1/0/2, 1/2/1, 2/0/5, 2/2/1, 2/2/2 and pitchshift 1/-5/1. AArch64 fuses by baseline where x86-64 does not without -mfma. 71b917e gave every src/shared/ file that computes in float the fp-contract=off pragma, so aarch64 stopped fusing and exactly those six blocks closed. The cause was inferred in 2026-09-02 and is measured now. WHAT MOVED, AND WHAT DID NOT No stored digest moved. cpython_stdout_sha256 and circuitpython_stdout_sha256 are untouched, effects_component_stdout.txt is untouched, and x86_64, macOS and Windows stay green on the same bytes they were green on before. The only hash line this deletes is the ARM allowance, so the two-goldens rule does not come into it: nothing was re-captured. The allowance is REMOVED rather than set to the reference hash. With no entry in cpython_stdout_sha256_by_arch, aarch64 is compared against cpython_stdout_sha256 -- the gate on that architecture is now the oracle agreement itself, which is strictly stronger than a baseline of its own. SO A FUTURE ARM FAILURE STILL READS CORRECTLY Removing the entry alone would have made a later aarch64 mismatch print "has no accepted baseline yet ... not expected to match", which would be false: we know it matched. cpython_stdout_sha256_reproduced_by records the agreement with its date and the eight CI cells behind it (runs 35269077774 and 35269087728, Python 3.11-3.14 each), and verify_effects.py grows the branch that says an architecture which agreed to the byte and stopped has had something change under it, and should not be handed an allowance before that is explained. Proven by planting the fault: one int16 in the committed reference moved by 1, its hash re-derived so the file stays self-consistent, and the gate run with platform.machine() forced to aarch64. It fails, names the moved field, and prints the new branch. Restored after. TWO NOTES THAT THE FILES HAD OUTRUN docs/correctness-standard.md said the contraction difference "is a board-only one", and verify_effects.py's docstring said the gate hashes sums and not PCM and that strengthening it was not a unilateral edit. Both were true when written and are not now: aarch64 is a desktop architecture that fuses, and e304ae0 closed audioif#15 by putting an FNV-1a checksum on every probe line. docs/building-wheels.md carries the resolution of the ARM section it opened.
1 parent 71b917e commit ec31d57

4 files changed

Lines changed: 88 additions & 49 deletions

File tree

docs/building-wheels.md

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -79,31 +79,36 @@ exactly the class an emulator reproduces only by accident. An untested
7979
wheel is a claim; a wheel tested on emulated hardware is a weaker claim
8080
than it looks.
8181

82-
**What the ARM lane found on its first run — and it is worth knowing
83-
whatever happens to ARM wheels.** aarch64 does *not* reproduce audioif's
84-
effects output byte for byte. Three of the four parity gates
85-
(`verify_acceptance`, `verify_streaming`, `verify_biquad`) are identical;
86-
`verify_effects` is not. Measured: **6 of 744 numeric fields differ,
87-
largest absolute deviation 1 in every case**, confined to `multitap` and
88-
`pitchshift` — the two effects doing delay-line interpolation, which is
89-
where a compiler's fused multiply-add changes the rounding. Those fields
90-
are `sum(data)` over a 512-byte block, so a delta of 1 is one byte off by
91-
one: **1 LSB of int16, about −90 dBFS.**
92-
93-
The rule this settles (Brad, 2026-09-02): **bit-identical audio is
94-
required within one CPU architecture, not across them.** So no tolerance
95-
was introduced anywhere. The gate stays exact everywhere and each
96-
architecture is held to its own recorded baseline, accepted deliberately
97-
with its evidence written beside it in
98-
`tests/parity/golden/effects_component.json`. That is both a faithful
99-
reading of the rule and a stricter regression detector than a threshold.
100-
101-
One caveat travels with that measurement, filed as
102-
[#15](https://github.com/PyDevices/audioif/issues/15): the gate hashes
103-
per-block **sums**, not PCM, so drifts that cancel within a block are
104-
invisible to it. The 1-LSB figure is therefore a lower bound on agreement,
105-
not a proof of it — true of every architecture the gate has ever passed,
106-
x86_64 included.
82+
**What the ARM lane found on its first run, and what closed it.** aarch64
83+
reproduces audioif's effects output byte for byte, since 2026-09-17. It did
84+
not at first, and the gap is the part worth keeping. Three of the four
85+
parity gates (`verify_acceptance`, `verify_streaming`, `verify_biquad`)
86+
were identical from the start; `verify_effects` was not — **6 of 744
87+
numeric fields differed, largest absolute deviation 1 in every case**,
88+
confined to `multitap` and `pitchshift`, the two effects doing delay-line
89+
interpolation. That is where a compiler's fused multiply-add changes the
90+
rounding, and AArch64 fuses by baseline where x86-64 does not without
91+
`-mfma`. So the six blocks were accepted as an aarch64 baseline of its own,
92+
with the evidence written beside it.
93+
94+
[#79](https://github.com/PyDevices/audioif/issues/79) forbade contraction
95+
in every `src/shared/` file that computes in float, and the six blocks
96+
closed. Eight `ubuntu-24.04-arm` cells — Python 3.11 through 3.14 in two CI
97+
runs — now hash identically to the x86_64 reference, **0 of 882 numeric
98+
fields differing**, checksums included rather than sums alone. aarch64's
99+
baseline is therefore *removed* from
100+
`tests/parity/golden/effects_component.json` rather than updated: it is
101+
held to the oracle hash itself now, the same one x86_64 is held to, and
102+
`cpython_stdout_sha256_reproduced_by` records that the agreement was
103+
measured so a future mismatch reads as a regression.
104+
105+
The rule this settled (Brad, 2026-09-02) stands whatever any one
106+
architecture does: **bit-identical audio is required within one CPU
107+
architecture, not across them.** No tolerance was introduced anywhere. The
108+
gate stays exact everywhere, and an architecture that genuinely differs is
109+
held to its own recorded baseline, accepted deliberately with its evidence
110+
written beside it. That is both a faithful reading of the rule and a
111+
stricter regression detector than a threshold.
107112

108113
**Release-time gap, precisely — this one IS open.** The organization
109114
desktop matrix (`reusable-build-native-and-wasm-wheels.yml`, at

docs/correctness-standard.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,16 @@ nothing, why it is not a build flag, and what it costs. **Add the include to any
181181
new shared file that does float arithmetic**, or that file alone keeps the
182182
behaviour the rest of the kernel has given up.
183183

184-
This gate cannot see contraction on its own, and should not be trusted to.
185-
x86-64 does not fuse without `-mfma`, so all three desktop interpreters agree
186-
whether or not the pragma is there; the difference is a board-only one, and
187-
proving it needs board digests. See audioif#79 and audioif#55.
184+
This gate cannot see contraction *on an x86-64 host*, and should not be
185+
trusted to. x86-64 does not fuse without `-mfma`, so all three desktop
186+
interpreters there agree whether or not the pragma is present, and that is the
187+
host almost every run happens on. aarch64 is not blind to it: AArch64 fuses by
188+
baseline, and CI's ARM lane had carried an accepted baseline of its own since
189+
2026-09-02 for six `multitap` and `pitchshift` blocks one byte from x86_64.
190+
Adding the pragma closed exactly those six, which is where the mechanism stops
191+
being inferred and starts being measured
192+
([docs/building-wheels.md](building-wheels.md)). Proving the board half still
193+
needs board digests. See audioif#79 and audioif#55.
188194

189195
## The one thing this page does not cover
190196

tests/parity/golden/effects_component.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
],
99
"cpython_stdout_sha256": "544d29ca31973613163977b2f1ef7b6e2ab04bc4881cc25f3df3acafcdfd95a3",
1010
"audiospeed_oracle": "CircuitPython 10.2.1 source plus MicroPython executable regression",
11-
"cpython_stdout_sha256_by_arch_note": "Removed 2026-09-02: the recorded aarch64 baseline described the probe's pre-checksum output format and cannot be regenerated off an aarch64 machine. An aarch64 run will now fail against the oracle hash and print the report a human needs in order to accept it, which is the documented path for a new architecture.",
12-
"cpython_stdout_sha256_by_arch": {
11+
"cpython_stdout_sha256_by_arch_note": "Empty since 2026-09-17, and that is the result rather than the absence of one. aarch64 held the only entry this file has ever carried: six blocks of multitap and pitchshift one byte from x86_64, accepted 2026-09-02 and re-accepted 2026-09-03 under the byte-checksum probe format. audioif#79 gave every src/shared/ file that computes in float the shared/audioif_fp_contract.h pragma, aarch64 stopped fusing, and the six blocks closed -- so the allowance was removed rather than re-recorded, and aarch64 is held to cpython_stdout_sha256 like the reference architecture is. See cpython_stdout_sha256_reproduced_by. An architecture that genuinely differs still earns an entry here: the hash, the date, and the evidence a human judged on, per Brad's 2026-09-02 rule that bit-identity is required within one architecture and not across.",
12+
"cpython_stdout_sha256_by_arch": {},
13+
"cpython_stdout_sha256_reproduced_by": {
1314
"aarch64": {
14-
"sha256": "3ec1c31fb45476b0d8d2a1c8df2f5b2746ee9834a2bb31fa54d8109dd322d9e1",
15-
"accepted": "2026-09-03",
16-
"evidence": "Re-accepted under the byte-checksum probe format (e304ae0) from the CI report of run 33787147404 (ubuntu-24.04-arm, all five Python cells identical). 12 of 882 numeric fields differ from x86_64: the same six blocks as the 2026-09-02 acceptance -- multitap 1/0/2, 1/2/1, 2/0/5, 2/2/1, 2/2/2 and pitchshift 1/-5/1 -- each showing a byte-sum delta of exactly 1 and a different FNV byte checksum. Stated plainly: the checksums differing means the bytes differ in those six blocks; the sum deltas bound nothing about sample magnitude, and no dBFS figure is inferred. Confined to the two delay-line interpolators, consistent with fused multiply-add rounding; phaser, chorus, freeverb and speed identical, and the 16 decoded phaser samples identical. Acceptance rests on Brad's rule (bit-identity within an architecture, not across) and on the divergence being confined to those six blocks."
15+
"since": "2026-09-17",
16+
"evidence": "Reproduces cpython_stdout_sha256 (544d29ca31973613163977b2f1ef7b6e2ab04bc4881cc25f3df3acafcdfd95a3) exactly. Eight ubuntu-24.04-arm cells -- Python 3.11, 3.12, 3.13 and 3.14 in each of CI runs 35269077774 and 35269087728, on PR #86 at 71b917e -- all report the same digest and '0 of 882 numeric fields differ' against the committed effects_component_stdout.txt. Every field, not a sum or a sample: the whole probe stdout hashes the same on both architectures. The cause of the old divergence was compiler contraction and nothing else, which is now settled rather than inferred -- the accepted 2026-09-03 baseline said the deviation was 'confined to the two delay-line interpolators, consistent with fused multiply-add rounding', and forbidding contraction closed exactly it. The gate remains exact here: with no entry in cpython_stdout_sha256_by_arch, aarch64 is compared against cpython_stdout_sha256 itself."
1717
}
1818
}
1919
}

tests/parity/verify_effects.py

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,21 @@
88
99
**Bit-identical audio is required within one CPU architecture, not across
1010
them** (Brad, 2026-09-02). The oracle hash in `effects_component.json` was
11-
captured on x86_64; aarch64 does not reproduce it, and is not expected to.
12-
Floating-point contraction (a compiler fusing `a*b+c` into one fused
13-
multiply-add, which rounds once instead of twice) and differing libm
14-
implementations both legitimately move the last bit, and both differ
15-
between the two architectures.
11+
captured on x86_64. Floating-point contraction (a compiler fusing `a*b+c`
12+
into one fused multiply-add, which rounds once instead of twice) and
13+
differing libm implementations both legitimately move the last bit, and
14+
both can differ between architectures.
15+
16+
aarch64 was the live example of that and is not one any more. It carried an
17+
accepted baseline of its own because six blocks of `multitap` and
18+
`pitchshift` -- the two delay-line interpolators -- came out one byte from
19+
x86_64. AArch64 fuses by baseline where x86-64 does not without `-mfma`,
20+
and that was the whole of it: since audioif#79 put
21+
`shared/audioif_fp_contract.h` in every shared file that computes in float,
22+
aarch64 reproduces the x86_64 hash exactly. So the allowance is gone rather
23+
than moved, and `cpython_stdout_sha256_reproduced_by` records that the
24+
agreement was measured -- a later aarch64 mismatch is a regression against
25+
a known-matching architecture, not the drift it would have been before.
1626
1727
"Different" must not be allowed to become a synonym for "wrong", so an
1828
architecture is not simply excused -- it is held to **its own exact hash**,
@@ -24,14 +34,15 @@
2434
act with its evidence written down beside the hash; drifting through a
2535
tolerance is not.
2636
27-
A tolerance would also have been measuring the wrong thing. **What this
28-
gate hashes is the probe's stdout, and that stdout carries per-block
29-
`sum(data)` values, not PCM** -- so two samples drifting in opposite
30-
directions inside one block leave the sum, and therefore the hash,
31-
unchanged. The gate is a sum-level check wearing a PCM-level name. That
32-
predates this change and is tracked separately; strengthening it means
33-
changing the probe's output format, which invalidates the committed
34-
CircuitPython oracle capture and so is not a unilateral edit.
37+
A tolerance would also have been measuring the wrong thing. What this gate
38+
hashes is the probe's stdout, and every line of that stdout carries an
39+
FNV-1a checksum over the block's bytes as well as `sum(data)` (audioif#15,
40+
`e304ae0`). The checksum is what makes the hash a fingerprint of the PCM:
41+
`sum(data)` sums *unsigned bytes* and is invariant under a permutation of a
42+
block, or under any set of byte deltas that cancel. Read a deviation report
43+
with that split in mind -- a sum delta bounds nothing about sample
44+
magnitude, and two lines differing only in checksum still means their bytes
45+
differ.
3546
"""
3647

3748
import hashlib
@@ -58,10 +69,14 @@
5869
machine = platform.machine()
5970
reference_expected = fixture["cpython_stdout_sha256"]
6071
accepted = fixture.get("cpython_stdout_sha256_by_arch", {}).get(machine)
72+
reproduced = fixture.get(
73+
"cpython_stdout_sha256_reproduced_by", {}).get(machine)
6174
# The reference architecture is gated on the oracle hash. Another
6275
# architecture is gated on its own accepted baseline if one has been
6376
# recorded, and otherwise on the oracle hash -- which it will fail,
64-
# printing the report a human needs in order to accept it.
77+
# printing the report a human needs in order to accept it, unless it is one
78+
# of the architectures measured to reproduce that hash, where matching is
79+
# the expectation and failing is a regression.
6580
expected = accepted["sha256"] if accepted else reference_expected
6681

6782
environment = os.environ.copy()
@@ -187,6 +202,16 @@ def deviations(ref_text, got_text):
187202
"regression on this architecture, not cross-architecture drift -- "
188203
"bit-identity IS required within one architecture."
189204
)
205+
elif reproduced:
206+
report.append(
207+
f" {machine} was measured reproducing the x86_64 oracle hash "
208+
f"exactly ({reproduced['since']}) and no longer does. It has no "
209+
"allowance of its own to fall back on, and it should not be given "
210+
"one before the deviation is explained: an architecture that "
211+
"agreed to the byte and stopped has had something change under it. "
212+
"The evidence for the agreement, to read against what you are "
213+
f"seeing now:\n {reproduced['evidence']}"
214+
)
190215
else:
191216
report.append(
192217
f" {machine} has no accepted baseline yet, so it was compared "
@@ -199,7 +224,10 @@ def deviations(ref_text, got_text):
199224
"whether they are last-bit floating-point divergence or a defect, "
200225
"then add an entry to cpython_stdout_sha256_by_arch in "
201226
f"{fixture_path.name} with the hash, the date, and the evidence you "
202-
"judged on. Note the limits of what you are reading: the sum "
227+
"judged on -- or, if it turns out to match the reference hash after "
228+
"all, record it in cpython_stdout_sha256_reproduced_by instead, "
229+
"which grants no allowance and is what aarch64 has. Note the limits "
230+
"of what you are reading: the sum "
203231
"fields are per-block sum(data) over unsigned bytes and bound nothing "
204232
"about sample magnitude (a +256/-1 pair passes them); the checksum "
205233
"fields differing means the bytes differ. Do not infer a dBFS figure "

0 commit comments

Comments
 (0)