Download web/test_reductions.js from DaisyChainAI/DaisyChain-Train: direct link, hf CLI and curl.
- Browser
- Download file 5.79 kB
-
https://huggingface.co/DaisyChainAI/DaisyChain-Train/resolve/main/web/test_reductions.js
- Command line
-
hf download hf://DaisyChainAI/DaisyChain-Train/web/test_reductions.js
-
curl -L -o test_reductions.js https://huggingface.co/DaisyChainAI/DaisyChain-Train/resolve/main/web/test_reductions.js
5.79 kB
| // Reductions and accumulators that used to accept bad input silently (2026-09-30). Each check | |
| // fails on the previous code: | |
| // 1. the GPU MLP chain's row max comes from an atomicMax over bitcast(abs(h)) and never passed a | |
| // non-finite guard -- scalesFromAbsMax now refuses, and the bit-order argument that makes | |
| // that ONE check complete is checked here too; | |
| // 2. averageGrads averaged a truncated gradient over a prefix, and fed NaN/Inf into DaisyAdam | |
| // on every peer at once -- it now refuses both; | |
| // 3. the Int32Array / WGSL i32 accumulators WRAP past K = 131071 (CPU and GPU agree on the wrong | |
| // sum) -- the GEMMs now refuse, the same bound as the Python kernel's MAX_INT32_K. | |
| ; | |
| const V = require("./public/verified_core.js"); | |
| const TC = require("./public/traincore.js"); | |
| let fails = 0; | |
| function ck(name, cond) { console.log((cond ? " ok " : " FAIL ") + name); if (!cond) fails++; } | |
| function throwsName(fn, name) { try { fn(); return false; } catch (e) { return e.name === name; } } | |
| // ---- 1. the GPU row-max readback ------------------------------------------------------------ | |
| for (const bad of [NaN, Infinity, -Infinity]) { | |
| ck("scalesFromAbsMax refuses a row max of " + bad, | |
| throwsName(() => V.scalesFromAbsMax(Float32Array.from([1.5, bad, 0.25])), "NonFiniteError")); | |
| } | |
| const fin = V.scalesFromAbsMax(Float32Array.from([127, 0, 1e-12])); | |
| ck("finite row maxes give the same scales as before (1, 1e-8 floor)", | |
| fin.scale[0] === 1 && Math.abs(fin.scale[1] - 1e-8) < 1e-15 && fin.inv[0] === 1); | |
| // What the WGSL kernel computes: atomicMax over the u32 bits of |h|. Emulated exactly with | |
| // typed-array views. The guard above is complete iff "max bits non-finite <=> some h non-finite". | |
| function gpuRowMax(h) { | |
| const f = new Float32Array(1), u = new Uint32Array(f.buffer); | |
| let mx = 0; | |
| for (const v of h) { f[0] = Math.abs(v); if (u[0] > mx) mx = u[0]; } | |
| u[0] = mx; return f[0]; | |
| } | |
| let rng = 12345; | |
| const rnd = () => ((rng = (rng * 1103515245 + 12345) >>> 0) / 4294967296); | |
| let complete = true, exact = true; | |
| for (let trial = 0; trial < 2000; trial++) { | |
| const h = new Float32Array(16).map(() => (rnd() - 0.5) * Math.pow(2, Math.floor(rnd() * 200) - 100)); | |
| const want = h.reduce((a, v) => Math.max(a, Math.abs(v)), 0); | |
| if (gpuRowMax(h) !== Math.fround(want)) exact = false; | |
| if (trial % 2) h[Math.floor(rnd() * 16)] = [NaN, Infinity, -Infinity, -NaN][trial % 4]; | |
| const nonfinite = h.some(v => !Number.isFinite(v)); | |
| if (Number.isFinite(gpuRowMax(h)) === nonfinite) complete = false; | |
| } | |
| ck("bit-order row max equals the true |max| on finite rows (2000 rows, wide exponents)", exact); | |
| ck("bit-order row max is non-finite exactly when some element is (NaN/Inf/-Inf injected)", complete); | |
| // ---- 2. averageGrads ------------------------------------------------------------------------ | |
| const g1 = Float32Array.from([1, 2, 3, 4]), g2 = Float32Array.from([3, 2, 1, 0]); | |
| const avg = TC.averageGrads([g1, g2]); | |
| ck("equal-length finite gradients average as before", Array.from(avg).join() === "2,2,2,2"); | |
| ck("a truncated gradient is refused (was averaged over a prefix)", | |
| throwsName(() => TC.averageGrads([g1, Float32Array.from([1, 2])]), "GradientError")); | |
| ck("a longer gradient is refused (its tail was silently dropped)", | |
| throwsName(() => TC.averageGrads([g1, Float32Array.from([1, 2, 3, 4, 5])]), "GradientError")); | |
| for (const bad of [NaN, Infinity]) { | |
| ck("a gradient containing " + bad + " is refused before DaisyAdam sees it", | |
| throwsName(() => TC.averageGrads([g1, Float32Array.from([0, bad, 0, 0])]), "GradientError")); | |
| } | |
| // why refusing matters: one NaN step poisons Adam's moments for good | |
| const adam = TC.makeAdam(1, { lr: 0.1 }); | |
| adam.step(Float32Array.from([NaN])); | |
| ck("(context) one NaN step leaves Adam's moments NaN on a finite gradient afterwards", | |
| Number.isNaN(adam.step(Float32Array.from([1]))[0])); | |
| // ---- 3. int32 accumulator headroom ---------------------------------------------------------- | |
| ck("MAX_INT32_K matches the Python kernel: floor((2^31-1) / 16384) = 131071", V.MAX_INT32_K === 131071); | |
| const L = { mul: new Int32Array(65536) }; | |
| for (let a = 0; a < 256; a++) for (let b = 0; b < 256; b++) L.mul[a * 256 + b] = (a << 24 >> 24) * (b << 24 >> 24); | |
| const K = V.MAX_INT32_K + 1; | |
| const worst = new Int8Array(K).fill(-128); | |
| ck("lutMatmulJS refuses K = 131072 (all -128*-128: the sum 2^31 wraps to -2^31)", | |
| throwsName(() => V.lutMatmulJS(worst, worst, 1, K, 1, L), "AccumulatorBoundError")); | |
| const acc = new Int32Array(1); acc[0] = 2 ** 31; | |
| ck("(context) Int32Array really wraps 2^31 to -2^31 -- the silent failure being refused", acc[0] === -(2 ** 31)); | |
| ck("bgemmJS refuses the same K", | |
| throwsName(() => V.bgemmJS(worst, worst, new Float32Array(1), new Float32Array(1), | |
| { m: 1, k: K, n: 1 }, L), "AccumulatorBoundError")); | |
| ck("attScoresJS refuses head width hd > 131071 (the CPU mirror of gpuAttScores)", | |
| throwsName(() => V.attScoresJS(worst, worst, new Float32Array(1), new Float32Array(1), | |
| { B: 1, T: 1, heads: 1, hd: K }, L), "AccumulatorBoundError")); | |
| ck("attCtxJS refuses T > 131071 (it contracts over keys, like gpuAttCtx)", | |
| throwsName(() => V.attCtxJS(new Int8Array(1), new Int8Array(1), new Float32Array(1), new Float32Array(1), | |
| { B: 1, T: K, heads: 1, hd: 1 }, L), "AccumulatorBoundError")); | |
| const okK = worst.subarray(0, V.MAX_INT32_K); | |
| ck("K = 131071 still runs and is exact (131071 * 16384)", | |
| V.lutMatmulJS(okK, okK, 1, V.MAX_INT32_K, 1, L)[0] === 131071 * 16384); | |
| if (fails) { console.log("REDUCTIONS TEST FAILED: " + fails); process.exit(1); } | |
| console.log("REDUCTIONS TEST PASSED — non-finite row maxes, bad gradients and int32 overflow are refused."); | |