Quazim0t0 commited on
Commit
54e1054
·
verified ·
1 Parent(s): 9d01f35

web: property suite 4/4 on the external corpus (definitional absolutes join the relations); de-flaked test_b2b

Browse files
web/TEST_RESULTS.md CHANGED
@@ -43,12 +43,21 @@ so the bug list has a different author than the checks.
43
  |---|---|---|---|
44
  | `acc=` instead of `acc+=` | loop | CAUGHT (sensitivity) | CAUGHT |
45
  | missing bounds guard (mult-of-8) | loop | CAUGHT (nonTriviality) | CAUGHT |
46
- | dropped constant factor (2×) | math | missed | CAUGHT |
47
- | wrong leaky-ReLU alpha | math | missed | CAUGHT |
48
-
49
- The split is the finding: properties are a **loop-bug detector** (2/2), blind
50
- to structure-preserving value bugs (0/2). Those are owned by the differential
51
- gate against the spec-correct mirror. Each check class has an owner.
 
 
 
 
 
 
 
 
 
52
 
53
  ## Backward rework: bit-identity + GPU wall clock
54
 
 
43
  |---|---|---|---|
44
  | `acc=` instead of `acc+=` | loop | CAUGHT (sensitivity) | CAUGHT |
45
  | missing bounds guard (mult-of-8) | loop | CAUGHT (nonTriviality) | CAUGHT |
46
+ | dropped constant factor (2×) | math | CAUGHT (unitScaleAnchor) | CAUGHT |
47
+ | wrong leaky-ReLU alpha | math | CAUGHT (reluRange) | CAUGHT |
48
+
49
+ **4/4 both oracles** — but the road there is the finding. The first score was
50
+ 0/4; adding non-triviality and sensitivity got the loop bugs (2/4). The two
51
+ math bugs are provably invisible to any RELATION if `out` satisfies every
52
+ relation, so does `c·out` — so no cleverer relation exists. Closing them took
53
+ a different species of check: **definitional absolutes**. `reluRange` (ReLU
54
+ output cannot be negative — a range constraint from the definition) catches
55
+ the leaky alpha; `unitScaleAnchor` (at unit scales dequant is the identity, so
56
+ the output must equal the exact integer dot product, computed with plain
57
+ integer arithmetic — no LUT, no mirror) pins absolute scale and catches the
58
+ uniform 2×. Still no reference implementation anywhere in the property suite;
59
+ the suite is now relations for the loop plus spec-pinned absolutes for the
60
+ values, and the differential gate remains an independent second opinion.
61
 
62
  ## Backward rework: bit-identity + GPU wall clock
63
 
web/test_b2b.js CHANGED
@@ -44,10 +44,15 @@ const ok = (c, msg) => { console.log(`${c ? " ok " : " FAIL"} ${msg}`); if (
44
  }
45
  ok(bad === 0, `scales from the fused absmax are bit-identical to quantizeRows (${n} rows incl. zero rows)`);
46
  }
47
- // 2) the respec moves an int8 by at most ONE step
 
 
 
 
 
48
  {
49
  let maxd = 0, diff = 0, n = 0;
50
- for (let t = 0; t < 200; t++) {
51
  const rows = 1 + (Math.random() * 30 | 0), cols = 1 + (Math.random() * 70 | 0);
52
  const X = rnd(rows * cols);
53
  const old = V.quantizeRows(X, rows, cols);
@@ -60,10 +65,8 @@ const ok = (c, msg) => { console.log(`${c ? " ok " : " FAIL"} ${msg}`); if (
60
  if (d > maxd) maxd = d;
61
  }
62
  }
63
- ok(maxd <= 1, `respecced quantize differs by at most 1 step (max ${maxd}; ${diff}/${n} = ${(100 * diff / n).toFixed(3)}% of values moved)`);
64
- // and it must differ SOMEWHERE: if the respec silently became the old
65
- // spec, every "bit-identical to the mirror" claim downstream is untested
66
- ok(diff > 0, `the respec is a real change (${diff} boundary values moved) — the mirror equivalence is not vacuous`);
67
  }
68
  // 3) chain == its parts, and gemm1 is byte-identical to the un-chained GEMM
69
  {
 
44
  }
45
  ok(bad === 0, `scales from the fused absmax are bit-identical to quantizeRows (${n} rows incl. zero rows)`);
46
  }
47
+ // 2) the respec moves an int8 by at most ONE step — and must move at least
48
+ // one somewhere, or every "bit-identical to the mirror" claim downstream
49
+ // is untested. Boundary hits are ~1 in 10^5 values, so scan ADAPTIVELY:
50
+ // keep drawing until one is found (a fixed small sample made this test
51
+ // flip a coin — it failed 2 runs in 3, which is its own lesson about
52
+ // asserting the existence of rare events from bounded randomness).
53
  {
54
  let maxd = 0, diff = 0, n = 0;
55
+ while (diff === 0 && n < 3_000_000) {
56
  const rows = 1 + (Math.random() * 30 | 0), cols = 1 + (Math.random() * 70 | 0);
57
  const X = rnd(rows * cols);
58
  const old = V.quantizeRows(X, rows, cols);
 
65
  if (d > maxd) maxd = d;
66
  }
67
  }
68
+ ok(maxd <= 1, `respecced quantize differs by at most 1 step (max ${maxd} over ${n} values)`);
69
+ ok(diff > 0, `the respec is a real change (${diff} boundary value(s) in ${n} scanned) — the mirror equivalence is not vacuous`);
 
 
70
  }
71
  // 3) chain == its parts, and gemm1 is byte-identical to the un-chained GEMM
72
  {
web/test_corpus.js CHANGED
@@ -129,6 +129,36 @@ function propertySuite(K) {
129
  if (!moved) return `sensitivity(A[.,${p}] ignored)`;
130
  }
131
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  // zero row of A -> zero row out
133
  {
134
  const d = { ...SHAPES };
 
129
  if (!moved) return `sensitivity(A[.,${p}] ignored)`;
130
  }
131
  }
132
+ // RELU RANGE. relu(x) >= 0 is part of the kernel's DEFINITION when the
133
+ // fused ReLU is on — a range constraint, not a relation between calls.
134
+ // This is what catches a wrong negative slope: the structure survives a
135
+ // leak, the sign does not.
136
+ {
137
+ const d = { ...SHAPES, relu: true };
138
+ const A = randf(d.m * d.k), B = randf(d.k * d.n);
139
+ const o = call(K, A, B, d);
140
+ for (let i = 0; i < o.length; i++) if (o[i] < 0) return "reluRange";
141
+ }
142
+ // UNIT-SCALE ANCHOR. With rs = cs = 1 the dequant is the identity, so the
143
+ // definition pins ABSOLUTE values: out must equal the exact integer dot
144
+ // product (f32-exact far below 2^24). No RELATION can do this — if out
145
+ // satisfies every relation, so does c*out — so the suite needs one point
146
+ // where the spec fixes the scale. Expected values come from plain integer
147
+ // arithmetic: no reference implementation, no LUT, no mirror.
148
+ {
149
+ const d = { m: 3, k: 5, n: 4, batch: 1 };
150
+ const Xq = new Int8Array(d.m * d.k), Wq = new Int8Array(d.k * d.n);
151
+ for (let i = 0; i < Xq.length; i++) Xq[i] = ((i * 37 + 11) % 25) - 12;
152
+ for (let i = 0; i < Wq.length; i++) Wq[i] = ((i * 53 + 7) % 25) - 12;
153
+ const rs = new Float32Array(d.m).fill(1), cs = new Float32Array(d.n).fill(1);
154
+ const o = K(Xq, Wq, rs, cs, d);
155
+ for (let i = 0; i < d.m; i++)
156
+ for (let j = 0; j < d.n; j++) {
157
+ let dot = 0;
158
+ for (let p = 0; p < d.k; p++) dot += Xq[i * d.k + p] * Wq[p * d.n + j];
159
+ if (o[i * d.n + j] !== dot) return "unitScaleAnchor";
160
+ }
161
+ }
162
  // zero row of A -> zero row out
163
  {
164
  const d = { ...SHAPES };
web/test_metamorphic.js CHANGED
@@ -12,6 +12,17 @@
12
  // the behaviour is wrong, regardless of which side is "right". They hold exactly
13
  // (not approximately) because per-row/per-column quantization commutes with
14
  // permutation, and because int32 accumulation is exactly associative.
 
 
 
 
 
 
 
 
 
 
 
15
  const fs = require("fs");
16
  const path = require("path");
17
  const V = require("./public/verified_core.js");
@@ -60,6 +71,16 @@ function makeKernel(bug) {
60
  if (m > 1) for (let j = 0; j < n; j++) { const t = out[0 * n + j]; out[0 * n + j] = out[1 * n + j]; out[1 * n + j] = t; }
61
  return out;
62
  }
 
 
 
 
 
 
 
 
 
 
63
  return V.bgemmJS(x.q, wq, x.s, ws, d, L);
64
  };
65
  }
@@ -104,6 +125,39 @@ const PROPS = {
104
  }
105
  return null;
106
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  // a zero row of A must produce a zero row of output, whatever the scales are
108
  async zeroRow(K) {
109
  const d = { m: 6, k: 32, n: 5, batch: 1 };
@@ -182,6 +236,12 @@ const PROPS = {
182
  const accPerm = await PROPS.rowPermutation(makeKernel("accOverwrite"));
183
  ok(accSens !== null, `sensitivity catches acc= instead of acc+= (${accSens || "MISSED"})`);
184
  console.log(` note rowPermutation vs that same acc= bug: ${accPerm ? "caught" : "missed — it is structure-preserving, which is the whole trap"}`);
 
 
 
 
 
 
185
 
186
  console.log(pass ? "\nMETAMORPHIC TEST PASSED" : "\nMETAMORPHIC TEST FAILED");
187
  process.exit(pass ? 0 : 1);
 
12
  // the behaviour is wrong, regardless of which side is "right". They hold exactly
13
  // (not approximately) because per-row/per-column quantization commutes with
14
  // permutation, and because int32 accumulation is exactly associative.
15
+ //
16
+ // The suite is TWO species of check, and the distinction matters:
17
+ // RELATIONS (permutation, zero-row, batch, sensitivity) — compare calls to
18
+ // each other. Provably blind to value bugs: if out satisfies every
19
+ // relation, so does c·out. An external bug corpus scored exactly this
20
+ // hole (2/2 on loop bugs, 0/2 on math bugs).
21
+ // DEFINITIONAL ABSOLUTES (reluRange, unitScaleAnchor) — points where the
22
+ // spec pins the value itself: ReLU output cannot be negative, and at unit
23
+ // scales the output IS the integer dot product. Still no reference
24
+ // implementation anywhere — the expected values are plain integer
25
+ // arithmetic — but they close the value-bug hole the relations cannot.
26
  const fs = require("fs");
27
  const path = require("path");
28
  const V = require("./public/verified_core.js");
 
71
  if (m > 1) for (let j = 0; j < n; j++) { const t = out[0 * n + j]; out[0 * n + j] = out[1 * n + j]; out[1 * n + j] = t; }
72
  return out;
73
  }
74
+ if (bug === "factor2") { // corpus: gelu_triton_buggy — uniform 2x
75
+ const out = V.bgemmJS(x.q, wq, x.s, ws, d, L);
76
+ for (let i = 0; i < out.length; i++) out[i] = Math.fround(out[i] * 2);
77
+ return out;
78
+ }
79
+ if (bug === "leakyAlpha") { // corpus: leaky_relu_buggy — leaks instead of clamping
80
+ const out = V.bgemmJS(x.q, wq, x.s, ws, { ...d, relu: false }, L);
81
+ if (d.relu) for (let i = 0; i < out.length; i++) if (out[i] < 0) out[i] = Math.fround(out[i] * 0.1);
82
+ return out;
83
+ }
84
  return V.bgemmJS(x.q, wq, x.s, ws, d, L);
85
  };
86
  }
 
125
  }
126
  return null;
127
  },
128
+ // relu(x) >= 0 is part of the DEFINITION when the fused ReLU is on — a range
129
+ // constraint, not a relation. Catches a wrong negative slope, which every
130
+ // relation survives (the structure of a leak is fine; its sign is not).
131
+ async reluRange(K) {
132
+ const d = { m: 6, k: 32, n: 5, batch: 1, relu: true };
133
+ const out = await K(randf(d.m * d.k), randf(d.k * d.n), d);
134
+ for (let i = 0; i < out.length; i++)
135
+ if (out[i] < 0) return `negative output ${out[i]} at [${(i / d.n) | 0},${i % d.n}] under fused ReLU`;
136
+ return null;
137
+ },
138
+ // With unit scales the dequant is the identity, so the definition pins
139
+ // ABSOLUTE values: out must equal the exact integer dot product. No RELATION
140
+ // can catch a uniform c× — if out satisfies every relation, c·out does too —
141
+ // so the suite needs one point where the spec fixes the scale. Inputs are
142
+ // floats that quantize exactly (row/col absmax = 127 ⇒ scale 1), and the
143
+ // expected values are plain integer arithmetic: no reference implementation.
144
+ async unitScaleAnchor(K) {
145
+ const d = { m: 2, k: 4, n: 2, batch: 1 };
146
+ const A = Float32Array.from([127, 1, -2, 3,
147
+ 0, 5, -127, 2]);
148
+ const B = Float32Array.from([127, 3, // k×n, each COLUMN has absmax 127
149
+ -1, 127,
150
+ 2, -5,
151
+ 0, 1]);
152
+ const out = await K(A, B, d);
153
+ for (let i = 0; i < d.m; i++)
154
+ for (let j = 0; j < d.n; j++) {
155
+ let dot = 0;
156
+ for (let p = 0; p < d.k; p++) dot += A[i * d.k + p] * B[p * d.n + j];
157
+ if (out[i * d.n + j] !== dot) return `unit-scale output [${i},${j}] = ${out[i * d.n + j]}, definition says ${dot}`;
158
+ }
159
+ return null;
160
+ },
161
  // a zero row of A must produce a zero row of output, whatever the scales are
162
  async zeroRow(K) {
163
  const d = { m: 6, k: 32, n: 5, batch: 1 };
 
236
  const accPerm = await PROPS.rowPermutation(makeKernel("accOverwrite"));
237
  ok(accSens !== null, `sensitivity catches acc= instead of acc+= (${accSens || "MISSED"})`);
238
  console.log(` note rowPermutation vs that same acc= bug: ${accPerm ? "caught" : "missed — it is structure-preserving, which is the whole trap"}`);
239
+ // value bugs: invisible to every RELATION (c·out satisfies whatever out
240
+ // does), caught by the definitional absolutes — range and unit-scale anchor
241
+ const leakBad = await PROPS.reluRange(makeKernel("leakyAlpha"));
242
+ ok(leakBad !== null, `reluRange catches a wrong leaky slope (${leakBad || "MISSED"})`);
243
+ const facBad = await PROPS.unitScaleAnchor(makeKernel("factor2"));
244
+ ok(facBad !== null, `unitScaleAnchor catches a uniform 2x (${facBad || "MISSED"})`);
245
 
246
  console.log(pass ? "\nMETAMORPHIC TEST PASSED" : "\nMETAMORPHIC TEST FAILED");
247
  process.exit(pass ? 0 : 1);