Upload web/test_ieee.js with huggingface_hub
Browse files- web/test_ieee.js +121 -0
web/test_ieee.js
CHANGED
|
@@ -78,6 +78,39 @@ function mulF32Spec(a, b) {
|
|
| 78 |
// what WGSL `f32(s) * a * b` means: left-associative, rounded after every step
|
| 79 |
const epiSpec = (s, a, b) => mulF32Spec(mulF32Spec(i32ToF32Spec(s), a), b);
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
// ---- checks -----------------------------------------------------------------
|
| 82 |
let pass = true;
|
| 83 |
const ok = (c, msg) => { console.log(`${c ? " ok " : " FAIL"} ${msg}`); if (!c) pass = false; };
|
|
@@ -123,6 +156,94 @@ const asF32 = (x) => { f32buf[0] = x; return f32buf[0]; };
|
|
| 123 |
ok(bad === 0, `mulF32Spec matches the correctly-rounded product on ${n} draws (subnormal..overflow)`);
|
| 124 |
}
|
| 125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
// ---- the actual point: is Verified.epi the spec, or just agreeable? ---------
|
| 127 |
console.log("\nVerified.epi vs the oracle, over the range the kernels produce:");
|
| 128 |
let mism = 0, n = 0;
|
|
|
|
| 78 |
// what WGSL `f32(s) * a * b` means: left-associative, rounded after every step
|
| 79 |
const epiSpec = (s, a, b) => mulF32Spec(mulF32Spec(i32ToF32Spec(s), a), b);
|
| 80 |
|
| 81 |
+
// ADDITION, same discipline: exact BigInt sum of the two scaled significands,
|
| 82 |
+
// one rounding. An exact zero sum from nonzero operands is +0 under RNE;
|
| 83 |
+
// -0 + -0 keeps its -0 (IEEE 754-2019 §6.3).
|
| 84 |
+
function addF32Spec(a, b) {
|
| 85 |
+
if (a === 0 && b === 0) return (signbit(a) && signbit(b)) ? -0 : 0;
|
| 86 |
+
if (a === 0) return b;
|
| 87 |
+
if (b === 0) return a;
|
| 88 |
+
const A = decomposeF32(a), B = decomposeF32(b);
|
| 89 |
+
const e0 = Math.min(A.exp2, B.exp2);
|
| 90 |
+
const T = (A.neg ? -A.mant : A.mant) * (1n << BigInt(A.exp2 - e0))
|
| 91 |
+
+ (B.neg ? -B.mant : B.mant) * (1n << BigInt(B.exp2 - e0));
|
| 92 |
+
if (T === 0n) return 0;
|
| 93 |
+
return roundToF32(T < 0n, T < 0n ? -T : T, e0);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// FUSED multiply-add: the product is NEVER rounded — exact BigInt product plus
|
| 97 |
+
// the exact addend, ONE rounding. This is what a hardware FMA computes, and
|
| 98 |
+
// (per neural-rdna2's fma golden, which this mirrors) the float64 shortcut
|
| 99 |
+
// fround(a*b + c) double-rounds on rare ties and is NOT a valid reference in
|
| 100 |
+
// general. Finite inputs only, like the rest of this oracle.
|
| 101 |
+
function fmaF32Spec(a, b, c) {
|
| 102 |
+
const spNeg = signbit(a) !== signbit(b);
|
| 103 |
+
let P = 0n, ep = 0;
|
| 104 |
+
if (a !== 0 && b !== 0) { const A = decomposeF32(a), B = decomposeF32(b); P = A.mant * B.mant; ep = A.exp2 + B.exp2; }
|
| 105 |
+
let Cm = 0n, ec = 0;
|
| 106 |
+
const scNeg = signbit(c);
|
| 107 |
+
if (c !== 0) { const C = decomposeF32(c); Cm = C.mant; ec = C.exp2; }
|
| 108 |
+
const e0 = Math.min(ep, ec);
|
| 109 |
+
const T = (spNeg ? -P : P) * (1n << BigInt(ep - e0)) + (scNeg ? -Cm : Cm) * (1n << BigInt(ec - e0));
|
| 110 |
+
if (T === 0n) return (spNeg && scNeg) ? -0 : 0; // exact zero: -0 only if both parts negative
|
| 111 |
+
return roundToF32(T < 0n, T < 0n ? -T : T, e0);
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
// ---- checks -----------------------------------------------------------------
|
| 115 |
let pass = true;
|
| 116 |
const ok = (c, msg) => { console.log(`${c ? " ok " : " FAIL"} ${msg}`); if (!c) pass = false; };
|
|
|
|
| 156 |
ok(bad === 0, `mulF32Spec matches the correctly-rounded product on ${n} draws (subnormal..overflow)`);
|
| 157 |
}
|
| 158 |
|
| 159 |
+
// ---- addition: certify every fround(a+b) mirror step in the codebase --------
|
| 160 |
+
// fround(a+b) is correctly rounded for f32 operands because f64 has >= 2p+2
|
| 161 |
+
// bits of precision (53 >= 50, Figueroa's theorem) — double rounding is
|
| 162 |
+
// innocuous for ADD. That claim guards every per-add rounding schedule we
|
| 163 |
+
// ship, so it gets checked against the from-the-definition oracle, including
|
| 164 |
+
// operand pairs with exponent gaps far beyond 53 bits.
|
| 165 |
+
console.log("\naddF32Spec vs Math.fround(a+b), including extreme exponent gaps:");
|
| 166 |
+
{
|
| 167 |
+
let bad = 0, n = 0;
|
| 168 |
+
const mags = [1, 1e-3, 1e-8, 1e-20, 1e-38, 1e-42, 3e38, 1e30, 0.5, 127];
|
| 169 |
+
for (let t = 0; t < 300000; t++) {
|
| 170 |
+
const a = asF32((Math.random() * 2 - 1) * mags[(Math.random() * mags.length) | 0]);
|
| 171 |
+
const b = asF32((Math.random() * 2 - 1) * mags[(Math.random() * mags.length) | 0]);
|
| 172 |
+
n++;
|
| 173 |
+
if (!Object.is(addF32Spec(a, b), f32(a + b))) {
|
| 174 |
+
if (bad++ < 3) console.log(` ${a} + ${b} spec=${addF32Spec(a, b)} fround=${f32(a + b)}`);
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
ok(bad === 0, `addF32Spec matches fround(a+b) on ${n} draws — the per-add mirror schedule is the correctly-rounded sum`);
|
| 178 |
+
ok(Object.is(addF32Spec(-0, -0), -0) && Object.is(addF32Spec(asF32(1e-20), asF32(-1e-20)), 0),
|
| 179 |
+
"signed zero on sums: -0 + -0 = -0, exact cancellation = +0");
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
// ---- fused multiply-add ------------------------------------------------------
|
| 183 |
+
console.log("\nfmaF32Spec: single rounding, cross-checked against an independent oracle:");
|
| 184 |
+
{
|
| 185 |
+
// 1) fused must actually differ from the round-twice composition, or the
|
| 186 |
+
// oracle proves nothing (cancellation is where they part ways)
|
| 187 |
+
let differ = 0, n = 0;
|
| 188 |
+
for (let t = 0; t < 100000; t++) {
|
| 189 |
+
const a = asF32((Math.random() * 2 - 1) * 1e3);
|
| 190 |
+
const b = asF32((Math.random() * 2 - 1) * 1e3);
|
| 191 |
+
const c = asF32(-(a * b));
|
| 192 |
+
n++;
|
| 193 |
+
if (!Object.is(fmaF32Spec(a, b, c), f32(f32(a * b) + c))) differ++;
|
| 194 |
+
}
|
| 195 |
+
ok(differ > 0, `fused differs from round-twice on ${differ}/${n} cancellation cases (single rounding is real)`);
|
| 196 |
+
|
| 197 |
+
// 2) cross-oracle: vectors generated by neural-rdna2's golden_fma_scalar
|
| 198 |
+
// (verbatim, exact big-int, different codebase/author-time). Bit-for-bit
|
| 199 |
+
// agreement means neither oracle was tuned to the other.
|
| 200 |
+
let vecs = null;
|
| 201 |
+
try { vecs = JSON.parse(fs.readFileSync(path.join(__dirname, "test_vectors_fma.json"), "utf8")); } catch (e) {}
|
| 202 |
+
if (vecs) {
|
| 203 |
+
const buf = new ArrayBuffer(4), dv = new DataView(buf);
|
| 204 |
+
const fromBits = (u) => { dv.setUint32(0, u); return dv.getFloat32(0); };
|
| 205 |
+
const toBits = (x) => { dv.setFloat32(0, x); return dv.getUint32(0); };
|
| 206 |
+
let bad = 0;
|
| 207 |
+
for (const [ah, bh, ch, rh] of vecs.vectors) {
|
| 208 |
+
const r = fmaF32Spec(fromBits(parseInt(ah, 16)), fromBits(parseInt(bh, 16)), fromBits(parseInt(ch, 16)));
|
| 209 |
+
if (toBits(r) !== parseInt(rh, 16)) {
|
| 210 |
+
if (bad++ < 3) console.log(` ${ah},${bh},${ch}: js=${toBits(r).toString(16)} py=${rh}`);
|
| 211 |
+
}
|
| 212 |
+
}
|
| 213 |
+
ok(bad === 0, `agrees bit-for-bit with neural-rdna2's big-int fma golden on ${vecs.vectors.length} vectors (quantize-domain + cancellation + raw finite bits)`);
|
| 214 |
+
} else {
|
| 215 |
+
console.log(" note test_vectors_fma.json not present — cross-oracle check skipped");
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
// 3) the b2b immunity scan's fused emulation, validated: on the quantize
|
| 219 |
+
// domain (x >= 0, x*inv <= ~127.5, addend 0.5) the exact product spans
|
| 220 |
+
// <= 53 bits including the 0.5, so fround(x*inv + 0.5) IS the true fma
|
| 221 |
+
// there. That was an argument; this makes it a check.
|
| 222 |
+
let embad = 0, en = 0;
|
| 223 |
+
for (let t = 0; t < 300000; t++) {
|
| 224 |
+
const inv = asF32(0.25 + Math.random() * 500);
|
| 225 |
+
const x = asF32(Math.random() * 127.4 / inv);
|
| 226 |
+
en++;
|
| 227 |
+
if (!Object.is(f32(x * inv + 0.5), fmaF32Spec(x, inv, 0.5))) embad++;
|
| 228 |
+
}
|
| 229 |
+
ok(embad === 0, `on the quantize domain the f64 emulation equals the true fma (${en} draws) — test_b2b's immunity scan stands on a checked fact`);
|
| 230 |
+
|
| 231 |
+
// 4) and the immunity conclusion itself, re-run against the TRUE fma at the
|
| 232 |
+
// binade edges: raw last-ulp differences exist, none survives floor()
|
| 233 |
+
let raw = 0, fl = 0;
|
| 234 |
+
for (let k = 1; k <= 7; k++) {
|
| 235 |
+
const u = Math.pow(2, k - 23);
|
| 236 |
+
for (let t = 0; t < 150000; t++) {
|
| 237 |
+
const inv = asF32(0.25 + Math.random() * 500);
|
| 238 |
+
const x = asF32((Math.pow(2, k) - 0.5 + (Math.random() * 8 - 6) * u) / inv);
|
| 239 |
+
if (!(x >= 0)) continue;
|
| 240 |
+
const two = f32(f32(x * inv) + 0.5), fused = fmaF32Spec(x, inv, 0.5);
|
| 241 |
+
if (!Object.is(two, fused)) { raw++; if (Math.floor(two) !== Math.floor(fused)) fl++; }
|
| 242 |
+
}
|
| 243 |
+
}
|
| 244 |
+
ok(raw > 0 && fl === 0, `true-fma immunity: ${raw} last-ulp diffs at binade edges, ${fl} floor-visible`);
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
// ---- the actual point: is Verified.epi the spec, or just agreeable? ---------
|
| 248 |
console.log("\nVerified.epi vs the oracle, over the range the kernels produce:");
|
| 249 |
let mism = 0, n = 0;
|