Download web/test_unit_backward.js from DaisyChainAI/DaisyChain-Train: direct link, hf CLI and curl.
- Browser
- Download file 3.62 kB
-
https://huggingface.co/DaisyChainAI/DaisyChain-Train/resolve/2841ec9218506e90ea508ac6b71bb8c740983db5/web/test_unit_backward.js
- Command line
-
hf download hf://DaisyChainAI/DaisyChain-Train@2841ec9218506e90ea508ac6b71bb8c740983db5/web/test_unit_backward.js
-
curl -L -o test_unit_backward.js https://huggingface.co/DaisyChainAI/DaisyChain-Train/resolve/2841ec9218506e90ea508ac6b71bb8c740983db5/web/test_unit_backward.js
3.62 kB
| // Does computing the STE gradient THROUGH the verified units (block-scaled int8) | |
| // instead of in float damage convergence? | |
| // | |
| // Identical seeds, identical data, identical optimizer. The only difference is | |
| // whether backward()'s matmuls run in f32 or through mul8. Runs on the CPU LUT | |
| // mirrors, so this measures the arithmetic question and nothing else. | |
| const fs = require("fs"); | |
| const path = require("path"); | |
| const T = require("./public/traincore.js"); | |
| const V = require("./public/verified_core.js"); | |
| const X = require("./public/transformer.js"); | |
| const p = (f) => path.join(__dirname, "public", f); | |
| const L = { mul: new Int16Array(fs.readFileSync(p("mul_lut.bin")).buffer.slice(0)), | |
| requant: new Int8Array(fs.readFileSync(p("requant_lut.bin")).buffer.slice(0)), | |
| relu: new Int8Array(fs.readFileSync(p("relu_lut.bin")).buffer.slice(0)) }; | |
| // the real Spikewhale tokenizer if asked — 16512 tokens is where gradient | |
| // dynamic range actually hurts, so the 96-char fallback would flatter the result | |
| if (process.env.REAL_TOK) { | |
| X.loadTokenizerData(JSON.parse(fs.readFileSync(p("tokenizer.json"), "utf8"))); | |
| console.log(`tokenizer: ${X.tokenizerName()}`); | |
| } | |
| const STEPS = +(process.env.STEPS || 150); | |
| const base = { c: 32, t: 32, b: 8, layers: 2, heads: 2, steps: STEPS, lr: 0.02 }; | |
| // deterministic batches so both runs see byte-identical data | |
| function makeBatches(n, cfg) { | |
| let seed = 1234; | |
| const rnd = () => { seed = (Math.imul(seed, 1103515245) + 12345) & 0x7fffffff; return seed / 0x7fffffff; }; | |
| const out = []; | |
| for (let s = 0; s < n; s++) { | |
| const ids = []; | |
| for (let i = 0; i < cfg.b; i++) ids.push(Math.floor(rnd() * 1e6)); | |
| out.push(ids); | |
| } | |
| return out; | |
| } | |
| async function run(label, unitBackward, dataSeed, lr) { | |
| const cfg = { ...base, unitBackward, lr: lr || base.lr }; | |
| const m = X.init(cfg, L, null); | |
| const opt = T.makeAdam(m.nParams, { lr: cfg.lr }); | |
| // pin Math.random so both runs draw identical batch windows | |
| let seed = dataSeed || 777; | |
| const orig = Math.random; | |
| Math.random = () => { seed = (Math.imul(seed, 1103515245) + 12345) & 0x7fffffff; return seed / 0x7fffffff; }; | |
| const curve = []; | |
| const t0 = Date.now(); | |
| for (let s = 0; s < STEPS; s++) { | |
| const r = await X.trainStep(m); | |
| X.applyUpdate(m, opt.step(r.grad)); | |
| curve.push(r.loss); | |
| } | |
| Math.random = orig; | |
| const ms = (Date.now() - t0) / STEPS; | |
| const tail = curve.slice(-10).reduce((a, b) => a + b) / 10; | |
| return { label, curve, tail, ms, first: curve[0] }; | |
| } | |
| (async () => { | |
| console.log(`\nvocab=${X.vocabSize()} steps=${STEPS} (CPU LUT mirrors, identical seeds)\n`); | |
| const f = await run("float backward", false); | |
| const u = await run("unit backward ", true); | |
| console.log("step float units"); | |
| for (const s of [0, 25, 50, 75, 100, Math.min(125, STEPS - 1), STEPS - 1]) { | |
| if (s >= STEPS) continue; | |
| console.log(`${String(s).padStart(4)} ${f.curve[s].toFixed(4).padStart(9)} ${u.curve[s].toFixed(4).padStart(9)}`); | |
| } | |
| console.log(`\nfinal (avg last 10) float ${f.tail.toFixed(4)} units ${u.tail.toFixed(4)}`); | |
| console.log(`ms/step float ${f.ms.toFixed(0)} units ${u.ms.toFixed(0)}`); | |
| const ratio = u.tail / f.tail; | |
| console.log(`\nunits/float loss ratio: ${ratio.toFixed(3)} (1.00 = no damage)`); | |
| const converged = u.tail < u.first * 0.75 && u.tail < Math.log(X.vocabSize()); | |
| console.log(converged ? "unit backward CONVERGES" : "unit backward FAILED TO CONVERGE"); | |
| console.log(ratio < 1.15 ? "damage within 15% — viable" : "damage exceeds 15% — needs work (try 2-pass gradients)"); | |
| })(); | |