Upload web/test_gates.js with huggingface_hub
Browse files- web/test_gates.js +53 -0
web/test_gates.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
| 9 |
const fs = require("fs");
|
| 10 |
const path = require("path");
|
| 11 |
const V = require("./public/verified_core.js");
|
|
|
|
| 12 |
|
| 13 |
const mul = new Int16Array(fs.readFileSync(path.join(__dirname, "public", "mul_lut.bin")).buffer.slice(0));
|
| 14 |
const L = { mul };
|
|
@@ -181,6 +182,58 @@ console.log("\nthe audit must catch a LAST-COLUMN bug (uniform sampling cannot):
|
|
| 181 |
ok(fp === 0, `no false positives on the clean kernel (${TRIALS} audits)`);
|
| 182 |
}
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
console.log(pass ? "\nGATE TEST PASSED — the gate rejects real bugs and accepts the real kernel."
|
| 185 |
: "\nGATE TEST FAILED");
|
| 186 |
process.exit(pass ? 0 : 1);
|
|
|
|
| 9 |
const fs = require("fs");
|
| 10 |
const path = require("path");
|
| 11 |
const V = require("./public/verified_core.js");
|
| 12 |
+
const TC = require("./public/traincore.js");
|
| 13 |
|
| 14 |
const mul = new Int16Array(fs.readFileSync(path.join(__dirname, "public", "mul_lut.bin")).buffer.slice(0));
|
| 15 |
const L = { mul };
|
|
|
|
| 182 |
ok(fp === 0, `no false positives on the clean kernel (${TRIALS} audits)`);
|
| 183 |
}
|
| 184 |
|
| 185 |
+
// ---- the attention kernels now get a LIVE audit too --------------------------
|
| 186 |
+
// They had exact init gates but nothing at live shapes — the same gap the GEMM
|
| 187 |
+
// audit exists to close. These must reject a corrupted attention output and
|
| 188 |
+
// accept a clean one, at a shape the init gate never sees.
|
| 189 |
+
console.log("\nthe attention audits must reject corrupted output:");
|
| 190 |
+
{
|
| 191 |
+
const d = { B: 2, T: 24, heads: 3, hd: 8 }; // not one of the gate's shapes
|
| 192 |
+
const C = d.heads * d.hd, BH = d.B * d.heads;
|
| 193 |
+
const qq = rnd(d.B * d.T * C, () => (Math.random() * 256 - 128) | 0);
|
| 194 |
+
const kq = rnd(d.B * d.T * C, () => (Math.random() * 256 - 128) | 0);
|
| 195 |
+
const qs = Float32Array.from({ length: d.B * d.T * d.heads }, () => Math.random() + 0.5);
|
| 196 |
+
const ks = Float32Array.from({ length: d.B * d.T * d.heads }, () => Math.random() + 0.5);
|
| 197 |
+
const good = V.attScoresJS(qq, kq, qs, ks, d, L);
|
| 198 |
+
ok(V.auditAttScores(qq, kq, qs, ks, d, good, L, 12) === null, "att.scores audit accepts the clean kernel");
|
| 199 |
+
// corrupt the LAST head's last token pair — the classic head-stride bug
|
| 200 |
+
const badS = Float32Array.from(good);
|
| 201 |
+
badS[((d.B * d.heads - 1) * d.T + (d.T - 1)) * d.T + (d.T - 1)] = 0;
|
| 202 |
+
ok(V.auditAttScores(qq, kq, qs, ks, d, badS, L, 12) !== null, "att.scores audit rejects a corrupted last head/token");
|
| 203 |
+
|
| 204 |
+
const aq = rnd(BH * d.T * d.T, () => (Math.random() * 127) | 0);
|
| 205 |
+
const vq = rnd(d.B * d.T * C, () => (Math.random() * 256 - 128) | 0);
|
| 206 |
+
const as = Float32Array.from({ length: BH * d.T }, () => Math.random() + 0.5);
|
| 207 |
+
const vs = Float32Array.from({ length: BH * d.hd }, () => Math.random() + 0.5);
|
| 208 |
+
const goodC = V.attCtxJS(aq, vq, as, vs, d, L);
|
| 209 |
+
ok(V.auditAttCtx(aq, vq, as, vs, d, goodC, L, 12) === null, "att.ctx audit accepts the clean kernel");
|
| 210 |
+
const badC = Float32Array.from(goodC); // last channel of the last head
|
| 211 |
+
badC[((d.B - 1) * d.T + (d.T - 1)) * C + (d.heads - 1) * d.hd + (d.hd - 1)] = 0;
|
| 212 |
+
ok(V.auditAttCtx(aq, vq, as, vs, d, badC, L, 12) !== null, "att.ctx audit rejects a corrupted last channel (scatter write-back)");
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
// ---- the f32 backward GEMM is no longer gated by a tolerance -----------------
|
| 216 |
+
// fgemmMirror reproduces split-K's partition and accumulation order, so the
|
| 217 |
+
// gate can compare with `!==`. This checks the mirror is self-consistent and
|
| 218 |
+
// that the two rounding schedules it offers are genuinely different (i.e. the
|
| 219 |
+
// gate is choosing between real alternatives, not two names for one thing).
|
| 220 |
+
console.log("\nthe split-K mirror is exact and discriminating:");
|
| 221 |
+
{
|
| 222 |
+
const m0 = 6, k0 = 5000, n0 = 4; // k > 4096 exercises split-K
|
| 223 |
+
const A = Float32Array.from({ length: m0 * k0 }, () => Math.random() - 0.5);
|
| 224 |
+
const Bm = Float32Array.from({ length: k0 * n0 }, () => Math.random() - 0.5);
|
| 225 |
+
const dG = { m: m0, k: k0, n: n0 };
|
| 226 |
+
const stepped = V.fgemmMirror(A, Bm, dG, false), fused = V.fgemmMirror(A, Bm, dG, true);
|
| 227 |
+
ok(V.fgemmMirror(A, Bm, dG, false).every((v, i) => !V.bitDiff(v, stepped[i])), "mirror is deterministic");
|
| 228 |
+
let diff = 0;
|
| 229 |
+
for (let i = 0; i < stepped.length; i++) if (V.bitDiff(stepped[i], fused[i])) diff++;
|
| 230 |
+
ok(diff > 0, `the two rounding schedules really differ (${diff}/${stepped.length} cells) — the gate picks between real alternatives`);
|
| 231 |
+
const naive = TC.matmul(A, Bm, m0, k0, n0);
|
| 232 |
+
let vsNaive = 0;
|
| 233 |
+
for (let i = 0; i < naive.length; i++) if (V.bitDiff(naive[i], stepped[i])) vsNaive++;
|
| 234 |
+
console.log(` note split-K order differs from a naive matmul on ${vsNaive}/${naive.length} cells — which is why the old gate had to use a tolerance, and why the mirror had to be written`);
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
console.log(pass ? "\nGATE TEST PASSED — the gate rejects real bugs and accepts the real kernel."
|
| 238 |
: "\nGATE TEST FAILED");
|
| 239 |
process.exit(pass ? 0 : 1);
|