File size: 11,497 Bytes
b004b80
9ae1216
b004b80
9ae1216
 
 
 
 
 
 
 
a879f4a
9ae1216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54e1054
 
9ae1216
54e1054
 
 
 
 
 
 
 
 
 
 
 
9ae1216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12e3142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4a8b60f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a879f4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c6693a5
 
 
 
 
 
 
 
 
 
a879f4a
 
 
 
 
 
 
 
 
 
9ae1216
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# Test results β€” updated 2026-07-16

All suites run with `npm test` (chains all ten). Every suite exits 0.
Hardware for GPU numbers: NVIDIA via WebGPU, DP4A int8 dot path, exact-gated
against the verified units at init.

| suite | what it proves | result |
|---|---|---|
| `test_core.js` | float trainer converges, replicas bit-identical | PASS β€” loss 33.71 β†’ 0.000000, replica diff 0.000e+0 |
| `test_verified.js` | training THROUGH the int8 units converges, replicas bit-identical | PASS β€” loss 300.6 β†’ 1.41, replica diff 0.000e+0 |
| `test_ieee.js` | the JS epilogue mirror is IEEE-754 spec-correct, not merely agreeable | PASS β€” 500k+ checks, 0 disagreements; rejects the old round-once mirror on 34% of inputs |
| `test_gates.js` | the exact kernel gate rejects real bugs, accepts the real kernel | PASS β€” 5/5 injected bugs rejected, clean kernel accepted, audit sees the sign of zero |
| `test_metamorphic.js` | correctness properties that need no reference implementation | PASS β€” 6/6 properties hold; catches stride/swap/acc= mutants |
| `test_corpus.js` | mutation-scores the oracles with an external bug taxonomy | PASS β€” properties 2/4 (2/2 loop, 0/2 math), differential 4/4, control clean |
| `test_optimizer.js` | DaisyAdam beats SGD through the units, deterministic replicas | PASS β€” 1.59 vs 1.95, replica diff 0.000e+0 |
| `test_transformer.js` | the transformer LM trains through the units end to end | PASS β€” loss 4.75 β†’ 1.26 (baseline 4.56), replica diff 0.000e+0 |
| `test_unit_backward.js` | int8 STE gradients do not damage convergence | PASS β€” units/float loss ratio 1.007 |

## The IEEE-754 oracle (`test_ieee.js`)

Built from the binary32 definition in exact BigInt arithmetic β€” no
`Math.fround` anywhere in the oracle, so neither side was tuned to the other.

```
i32ToF32Spec matches Math.fround on 200000 random int32 (incl. |s| > 2^24)
mulF32Spec matches the correctly-rounded product on 300000 draws (subnormal..overflow)
Verified.epi vs the oracle: 200000 random triples, 0 disagreements
tie-to-even ladder around 2^24: 378 cases, 0 disagreements
bgemmJS outputs rebuilt from the raw int32 accumulator via the oracle: 0 disagreements
the oracle REJECTS the round-once mirror shipped before: 68314/200000 inputs (34.16%)
```

That last line is the teeth: an oracle that never disagrees with anything
proves nothing. This one rejects the exact bug the old `1e-6` tolerance hid.

## Oracle mutation scores (`test_corpus.js`)

Bugs ported from an external taxonomy
([dipankarsarkar/gpuemu-corpus](https://huggingface.co/datasets/dipankarsarkar/gpuemu-corpus))
so the bug list has a different author than the checks.

| bug | lives in | properties | differential |
|---|---|---|---|
| `acc=` instead of `acc+=` | loop | CAUGHT (sensitivity) | CAUGHT |
| missing bounds guard (mult-of-8) | loop | CAUGHT (nonTriviality) | CAUGHT |
| dropped constant factor (2Γ—) | math | CAUGHT (unitScaleAnchor) | CAUGHT |
| wrong leaky-ReLU alpha | math | CAUGHT (reluRange) | CAUGHT |

**4/4 both oracles** β€” but the road there is the finding. The first score was
0/4; adding non-triviality and sensitivity got the loop bugs (2/4). The two
math bugs are provably invisible to any RELATION β€” if `out` satisfies every
relation, so does `cΒ·out` β€” so no cleverer relation exists. Closing them took
a different species of check: **definitional absolutes**. `reluRange` (ReLU
output cannot be negative β€” a range constraint from the definition) catches
the leaky alpha; `unitScaleAnchor` (at unit scales dequant is the identity, so
the output must equal the exact integer dot product, computed with plain
integer arithmetic β€” no LUT, no mirror) pins absolute scale and catches the
uniform 2Γ—. Still no reference implementation anywhere in the property suite;
the suite is now relations for the loop plus spec-pinned absolutes for the
values, and the differential gate remains an independent second opinion.

## Backward rework: bit-identity + GPU wall clock

The backward was reworked for dispatch efficiency: independent GEMMs
overlapped, the QKV weight-gradient and dln1in trios fused into single
batched (batch=3) GEMMs, and the `g.emb` operand quantized column-wise in one
pass instead of transpose-then-quantize. **None of this may change a bit** β€”
block scales are per-row/per-column per batch element, so fusion is exact,
and every fused sum keeps the original per-add f32 rounding schedule.

Bit-identity, old backward vs new (CPU mirrors, Node):

```
char-96,        float backward: loss + all  20480 gradient floats bit-identical
char-96,        unit  backward: loss + all  20480 gradient floats bit-identical
Spikewhale 16k, float backward: loss + all 545792 gradient floats bit-identical
Spikewhale 16k, unit  backward: loss + all 545792 gradient floats bit-identical
```

GPU (c=32 t=32 b=8 layers=2 heads=2, 16512-token vocab, 15 measured steps,
gradient FNV hash compared old vs new on-device):

| config | ms/step | grad hash |
|---|---|---|
| old backward, float | 286 | `7ff11308` |
| old backward, units | 466 | `62596547` |
| new backward, float | 286 | `7ff11308` (identical) |
| new backward, units | 346 | `62596547` (identical) |

- float path: unchanged speed, unchanged bits β€” this is what ships enabled.
- unit-backward path (dormant, `cfg.unitBackward`): cost drops **1.63Γ— β†’ 1.21Γ—**
  vs float. Because the rework is bit-identical, the convergence curves from
  the unit-backward experiment stand unchanged; only the wall-clock exchange
  rate moved. At equal wall clock float still wins (~1.5% at the 200-step
  horizon), so `unitBackward` stays off by default.

## QKV dual-GEMM fusion (CUTLASS ex. 45)

The q/k/v projections share the same left operand (`ln1.y`), so the forward
now quantizes it ONCE and runs all three as one batched (batch=3) dispatch β€”
2 fewer dispatches and 2 fewer full quantize passes per layer per step.

Bit-identity (old three-GEMM forward vs fused), 5 full training steps with
weight updates in between so a single-ulp divergence anywhere compounds:

```
char-96,        float backward: 5 losses + 5Γ—20480  grads + final weights bit-identical
char-96,        unit  backward: 5 losses + 5Γ—20480  grads + final weights bit-identical
Spikewhale 16k, float backward: 5 losses + 5Γ—545792 grads + final weights bit-identical
Spikewhale 16k, unit  backward: 5 losses + 5Γ—545792 grads + final weights bit-identical
generate() output identical (the fused output's subarray views feed attention)
```

On GPU (DP4A): gradient FNV hashes match the pre-fusion values exactly in
both modes (`7ff11308` float / `62596547` units); ~2% wall-clock gain at
width 32 (the shared operand is only 32 KB there β€” the saved quantize work
scales quadratically with model width). Two-device live run: both replicas
at step 71/300 with identical loss to the last digit, no sync-guard trips.

## B2B MLP chain (CUTLASS ex. 13 two-GEMM fusion + ex. 23 epilogue reduction)

The MLP's two GEMMs now run back-to-back on the GPU: gemm1 (ReLU fused) and a
per-row |max| reduction share one command encoder, ~1 KB of absmax comes back
to JS (scale derivation needs division, which WGSL only guarantees to 2.5 ULP
β€” JS f64 division is exactly rounded and device-identical), then h1 is
quantized ON-DEVICE and fed straight to gemm2. h1 returns to JS only because
the STE backward needs it; it never goes up again.

This required respeccing the intermediate quantize from `round(x / scale)` to
`floor(f32(x * invScale) + 0.5)` β€” WGSL multiply/add are correctly rounded and
floor/clamp exact, so the GPU kernel and the fround-stepped JS mirror agree
bit-for-bit, and CPU-fallback devices run the mirror so mixed fleets stay
bit-identical. The respec is a real (bounded) math change: old and new builds
cannot co-train, and the per-step divergence guard stops such mixed groups.

`test_b2b.js` (Node):

```
scales from the fused absmax bit-identical to quantizeRows (3958 rows incl. zero rows)
respec moves an int8 by at most 1 step (1/112363 = 0.001% of values moved)
chain gemm1 (hence h1 and the ReLU mask) byte-identical to the un-chained GEMM
chain output equals the manual composition of its stages; deterministic
convergence unchanged: old 2.0500 vs new 2.0512 final loss (0.1% apart, 40 steps)
```

On GPU: both chain variants (LUT shader and DP4A) pass an exact `!==` init
gate against the mirror chain over ragged shapes including pack-tail padding.
Discriminating proof that the kernel implements the RESPEC and not the old
spec: a searched-for boundary input (int8 68β†’69 under the respec) run through
the GPU chain matches the new-spec mirror exactly and differs from the
old-spec composition. Two-device live run: step 141/300 with identical loss
(6.23958) on both replicas, per-step weight-hash divergence checks silent.

## The sign of zero (RDNA2 ISA audit)

Reading the RDNA2 shader ISA against our determinism assumptions confirmed
three of them on real hardware and exposed one blind spot in our own gates:

- `V_DOT4_I32_I8` is an exact packed int8 dot with int32 accumulate β€” the
  DP4A path's exactness is an ISA guarantee, not a tested coincidence.
- f32 add/multiply are 0.5 ULP (correctly rounded) β€” the epilogue mirror's
  foundation. `V_RCP_F32` is 1 ULP β€” division stays off the GPU, as designed.
- Rounding mode and denorm flushing are **runtime MODE-register state**
  (`S_ROUND_MODE` / `S_DENORM_MODE`, per wave, driver-controlled) β€” which is
  why the exact gates re-run on every device at every init rather than
  trusting a device model. (Denorm flush is additionally unreachable in the
  shipped math: the 1e-8 scale floor keeps every epilogue product β‰₯ ~1e-16 in
  magnitude, far above the ~1.2e-38 subnormal threshold.)
- FMA contraction (`V_FMA_F32`: one rounding, not two) looked like a second
  hazard β€” WGSL permits contracting the quantize's `x*inv + 0.5` β€” but turned
  out to be an immunity: adding 0.5 is exact except at binade crossings, and
  there the double-rounding anomaly stays on the same side of every integer
  (RNE tie parity), so `floor()` β€” hence the int8 β€” is identical either way.
  `test_b2b.js` asserts both halves: last-ulp fused-vs-stepped differences DO
  occur (~175k per 2.8M edge-targeted draws), and zero survive floor. The
  `+0.5` respec is contraction-immune by construction; `round(x/scale)` was
  not. No gate can forbid the compiler an fma, so this had to be a theorem,
  not a check.
- The blind spot: RDNA2 has non-IEEE instruction variants a compiler may pick
  β€” output modifiers and DX9-legacy multiplies that **flush βˆ’0 to +0**. Our
  gates compared f32 outputs with JS `!==`, for which `-0 !== 0` is *false*:
  a βˆ’0-flushing kernel would pass every gate, then fork the fleet at the sync
  guard, which hashes raw bits. All gate and audit comparisons now compare
  **bit patterns** (`bitDiff`), i.e. exactly what the replica hash sees.
  `test_gates.js` proves the fix non-vacuously: a `-0` planted where the units
  produce `+0` is invisible to `!==` (sanity-checked in the test) and flagged
  by the repaired `auditTile`.

One bug was caught during the rework, by the bit-identity check itself: the
fused q+k+v sum initially ran in f64 and rounded once, where the old code
rounded to f32 after each add β€” a last-ulp fork that would have split
replicas. Fixed by matching the rounding schedule (`Math.fround` per add).
Same lesson as the epilogue mirror: match the rounding schedule, not just
the values.