| |
| |
| |
| |
| |
| |
| |
| |
| |
| (function (root) { |
| "use strict"; |
|
|
| let TC, V; |
|
|
| function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; } |
| function randn(n, rng) { const o = new Float32Array(n); for (let i = 0; i < n; i += 2) { let u = 0, v = 0; while (u === 0) u = rng(); while (v === 0) v = rng(); const m = Math.sqrt(-2 * Math.log(u)); o[i] = m * Math.cos(2 * Math.PI * v); if (i + 1 < n) o[i + 1] = m * Math.sin(2 * Math.PI * v); } return o; } |
|
|
| |
| const W_ADJ = ["mossy", "golden", "amber", "quiet", "little", "misty", "sunny", "wild", "cozy", "dusty", "merry", "brave"]; |
| const W_NOUN = ["fox", "hare", "owl", "badger", "toad", "sparrow", "otter", "deer", "mushroom", "acorn", "willow", "robin", "river", "meadow", "garden", "lantern"]; |
| const W_VERB = ["naps", "sings", "wanders", "hides", "dreams", "waits", "dances", "listens", "rests", "grows"]; |
| const W_PREP = ["by", "under", "near", "beside", "beyond", "inside"]; |
| function buildCorpus() { |
| const rng = mulberry32(20260712); |
| const pick = (a) => a[Math.floor(rng() * a.length)]; |
| let s = ""; |
| while (s.length < 60000) |
| s += `the ${pick(W_ADJ)} ${pick(W_NOUN)} ${pick(W_VERB)} ${pick(W_PREP)} the ${pick(W_ADJ)} ${pick(W_NOUN)}. `; |
| return s; |
| } |
| |
| |
| |
| |
| |
| const FALLBACK_CHARS = [...Array(95)].map((_, i) => String.fromCharCode(32 + i)).concat(["\n"]); |
| let tok = { |
| name: "char-96 (fallback)", |
| vocab: Object.fromEntries(FALLBACK_CHARS.map((c, i) => [c, i])), |
| ids: FALLBACK_CHARS, maxLen: 1, size: FALLBACK_CHARS.length, |
| unk: 0, specials: new Set(), |
| }; |
| tok.unk = tok.vocab[" "]; |
| function vocabSize() { return tok.size; } |
| function tokenizerName() { return tok.name; } |
| function loadTokenizerData(d) { |
| const ids = new Array(d.vocab_size); |
| for (const [t, i] of Object.entries(d.vocab)) ids[i] = t; |
| tok = { name: `Spikewhale length-max (${d.vocab_size} tokens)`, |
| vocab: d.vocab, ids, maxLen: d.max_token_len || 24, size: d.vocab_size, |
| unk: d.vocab["<unk>"] ?? 1, |
| specials: new Set(["<pad>", "<unk>", "<bos>", "<eos>", ...(d.special_tokens || [])]) }; |
| IDS = encode(CORPUS); |
| return tok.name; |
| } |
| async function loadTokenizer(url) { |
| const r = await fetch(url || "tokenizer.json"); |
| if (!r.ok) throw new Error(`tokenizer.json HTTP ${r.status}`); |
| return loadTokenizerData(await r.json()); |
| } |
| function toLatin1(s) { const b = new TextEncoder().encode(s); let o = ""; for (const x of b) o += String.fromCharCode(x); return o; } |
| function encode(text) { |
| const s = toLatin1(text), out = []; |
| let i = 0; |
| while (i < s.length) { |
| let m = null; |
| for (let L = Math.min(tok.maxLen, s.length - i); L > 0; L--) { |
| const sub = s.substr(i, L); |
| if (sub in tok.vocab) { m = sub; break; } |
| } |
| if (m === null) { out.push(tok.unk); i++; continue; } |
| out.push(tok.vocab[m]); i += m.length; |
| } |
| return Int32Array.from(out); |
| } |
| function decode(idArr) { |
| let s = ""; |
| for (const id of idArr) { |
| const t = tok.ids[id]; |
| if (t === undefined || tok.specials.has(t)) continue; |
| s += t; |
| } |
| const bytes = Uint8Array.from([...s].map(c => c.charCodeAt(0))); |
| return new TextDecoder().decode(bytes); |
| } |
| let CORPUS = buildCorpus(); |
| let IDS = encode(CORPUS); |
| let DATASET = "built-in corpus"; |
|
|
| |
| |
| |
| |
| |
| |
| const DEFAULT_DS = "HuggingFaceFW/fineweb-edu"; |
| async function streamDataset() { |
| const r = await fetch("data"); |
| if (!r.ok) throw new Error(`/data HTTP ${r.status}`); |
| const text = (await r.text()).replace(/[^\x20-\x7e\n]/g, " "); |
| if (text.length < 10000) throw new Error("too little text returned"); |
| CORPUS = text.slice(0, 500000); |
| IDS = encode(CORPUS); |
| DATASET = `${DEFAULT_DS} · 10BT sample (parquet via this Space)`; |
| return { name: DATASET, chars: CORPUS.length }; |
| } |
| const streamFineWebEdu = () => streamDataset(); |
| function datasetName() { return DATASET; } |
|
|
| |
| |
| |
| |
| |
| async function vmm(Xf, Wf, m, k, n, ctx, relu) { |
| |
| return V.vgemmBlock(Xf, Wf, { m, k, n, batch: 1, relu: !!relu }, ctx.L, ctx.bgemm, ctx.audit); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function vmmShared3(Xf, Wa, Wb, Wc, m, k, n, ctx) { |
| const x = V.quantizeRows(Xf, m, k); |
| const xq = new Int8Array(3 * m * k), xs = new Float32Array(3 * m); |
| for (let i = 0; i < 3; i++) { xq.set(x.q, i * m * k); xs.set(x.s, i * m); } |
| const wq = new Int8Array(3 * k * n), ws = new Float32Array(3 * n); |
| [Wa, Wb, Wc].forEach((W, i) => { const w = V.quantizeCols(W, k, n); wq.set(w.q, i * k * n); ws.set(w.s, i * n); }); |
| const d = { m, k, n, batch: 3 }; |
| let out; |
| if (ctx.bgemm) { |
| out = await ctx.bgemm(xq, wq, xs, ws, d); |
| if (ctx.audit && ctx.audit.due()) { |
| const bad = V.auditTile(xq, wq, xs, ws, d, out, ctx.L, ctx.audit.cells); |
| if (bad) ctx.audit.fail(bad); |
| } |
| } else { |
| out = V.bgemmJS(xq, wq, xs, ws, d, ctx.L); |
| } |
| const MN = m * n; |
| return [out.subarray(0, MN), out.subarray(MN, 2 * MN), out.subarray(2 * MN, 3 * MN)]; |
| } |
|
|
| |
| function lnFwd(x, rows, C) { |
| const y = new Float32Array(rows * C), sig = new Float32Array(rows); |
| for (let r = 0; r < rows; r++) { |
| let mu = 0; for (let j = 0; j < C; j++) mu += x[r * C + j]; mu /= C; |
| let v = 0; for (let j = 0; j < C; j++) { const d = x[r * C + j] - mu; v += d * d; } |
| const s = Math.sqrt(v / C + 1e-5); sig[r] = s; |
| for (let j = 0; j < C; j++) y[r * C + j] = (x[r * C + j] - mu) / s; |
| } |
| return { y, sig }; |
| } |
| function lnBwd(dy, y, sig, rows, C) { |
| const dx = new Float32Array(rows * C); |
| for (let r = 0; r < rows; r++) { |
| let mdy = 0, mdyy = 0; |
| for (let j = 0; j < C; j++) { mdy += dy[r * C + j]; mdyy += dy[r * C + j] * y[r * C + j]; } |
| mdy /= C; mdyy /= C; |
| for (let j = 0; j < C; j++) dx[r * C + j] = (dy[r * C + j] - mdy - y[r * C + j] * mdyy) / sig[r]; |
| } |
| return dx; |
| } |
|
|
| |
| |
| |
| |
| function init(cfg, L, engine, audit) { |
| const c = cfg.c, layers = cfg.layers || 2, heads = cfg.heads || 2, hidden = 2 * c; |
| let seed = 100; |
| const mk = (nEl, scale) => { const w = randn(nEl, mulberry32(seed++)); for (let i = 0; i < nEl; i++) w[i] *= scale; return w; }; |
| const params = [], names = []; |
| const add = (name, w) => { params.push(w); names.push(name); return w; }; |
| const m = { |
| cfg: { ...cfg, layers, heads, hidden, vocab: vocabSize() }, |
| ctx: { L, bgemm: (engine && engine.bgemm) || null, |
| att: (engine && engine.att) || null, fgemm: (engine && engine.fgemm) || null, |
| fgemm2: (engine && engine.fgemm2) || null, |
| mlp: (engine && engine.mlp) || null, |
| audit: audit || null, unitBackward: !!cfg.unitBackward }, |
| emb: add("emb", mk(vocabSize() * c, 0.08)), |
| pos: add("pos", mk(cfg.t * c, 0.02)), |
| blocks: [], params, names, |
| }; |
| for (let l = 0; l < layers; l++) |
| m.blocks.push({ |
| Wq: add(`b${l}.Wq`, mk(c * c, 0.08)), Wk: add(`b${l}.Wk`, mk(c * c, 0.08)), |
| Wv: add(`b${l}.Wv`, mk(c * c, 0.08)), Wo: add(`b${l}.Wo`, mk(c * c, 0.08)), |
| W1: add(`b${l}.W1`, mk(c * hidden, 0.08)), W2: add(`b${l}.W2`, mk(hidden * c, 0.08)), |
| }); |
| |
| |
| |
| m.nParams = params.reduce((a, p) => a + p.length, 0); |
| return m; |
| } |
|
|
| function sampleBatch(cfg) { |
| const { b, t } = cfg; |
| const X = new Int32Array(b * t), Y = new Int32Array(b * t); |
| for (let i = 0; i < b; i++) { |
| const off = Math.floor(Math.random() * (IDS.length - t - 1)); |
| for (let j = 0; j < t; j++) { X[i * t + j] = IDS[off + j]; Y[i * t + j] = IDS[off + j + 1]; } |
| } |
| return { X, Y }; |
| } |
|
|
| |
| |
| |
| |
| |
| function gatherHeads(x, B, T, C, heads, hd) { |
| const out = new Float32Array(B * heads * T * hd); |
| for (let bi = 0; bi < B; bi++) |
| for (let h = 0; h < heads; h++) { |
| const bz = bi * heads + h; |
| for (let ti = 0; ti < T; ti++) |
| for (let j = 0; j < hd; j++) out[(bz * T + ti) * hd + j] = x[(bi * T + ti) * C + h * hd + j]; |
| } |
| return out; |
| } |
| function scatterHeadsAcc(dst, src, B, T, C, heads, hd) { |
| for (let bi = 0; bi < B; bi++) |
| for (let h = 0; h < heads; h++) { |
| const bz = bi * heads + h; |
| for (let ti = 0; ti < T; ti++) |
| for (let j = 0; j < hd; j++) dst[(bi * T + ti) * C + h * hd + j] += src[(bz * T + ti) * hd + j]; |
| } |
| } |
| function batchedTranspose(x, batch, rows, cols) { |
| const out = new Float32Array(batch * rows * cols); |
| for (let b = 0; b < batch; b++) { |
| const o = b * rows * cols; |
| for (let r = 0; r < rows; r++) |
| for (let c = 0; c < cols; c++) out[o + c * rows + r] = x[o + r * cols + c]; |
| } |
| return out; |
| } |
|
|
| |
| async function forward(m, X, Y) { |
| const { c: C, t: T, b: B, layers, heads, hidden, vocab } = m.cfg; |
| const BT = B * T, hd = C / heads, ctx = m.ctx; |
| const cache = { X, Y, blocks: [] }; |
| let x = new Float32Array(BT * C); |
| for (let i = 0; i < BT; i++) { |
| const id = X[i], tpos = i % T; |
| for (let j = 0; j < C; j++) x[i * C + j] = m.emb[id * C + j] + m.pos[tpos * C + j]; |
| } |
| for (let l = 0; l < layers; l++) { |
| const bl = m.blocks[l], cb = { xin: x }; |
| const l1 = lnFwd(x, BT, C); cb.ln1 = l1; |
| |
| |
| const [q, k, v] = await vmmShared3(l1.y, bl.Wq, bl.Wk, bl.Wv, BT, C, C, ctx); |
| cb.q = q; cb.k = k; cb.v = v; |
| const scale = 1 / Math.sqrt(hd); |
| |
| |
| |
| |
| const BH = B * heads, dAtt = { B, T, heads, hd }; |
| |
| const qq = V.quantizeRows(q, BT * heads, hd), kq = V.quantizeRows(k, BT * heads, hd); |
| const sAll = ctx.att ? await ctx.att.scores(qq.q, kq.q, qq.s, kq.s, dAtt) |
| : V.attScoresJS(qq.q, kq.q, qq.s, kq.s, dAtt, ctx.L); |
| |
| if (ctx.att && ctx.audit && ctx.audit.due()) { |
| const bad = V.auditAttScores(qq.q, kq.q, qq.s, kq.s, dAtt, sAll, ctx.L, ctx.audit.cells); |
| if (bad) ctx.audit.fail(bad); |
| } |
| const aAll = new Float32Array(BH * T * T); |
| for (let bz = 0; bz < BH; bz++) { |
| const so = bz * T * T; |
| for (let ti = 0; ti < T; ti++) { |
| let mx = -1e30; |
| for (let tj = 0; tj <= ti; tj++) mx = Math.max(mx, sAll[so + ti * T + tj] * scale); |
| let z = 0; |
| for (let tj = 0; tj <= ti; tj++) { const e = Math.exp(sAll[so + ti * T + tj] * scale - mx); aAll[so + ti * T + tj] = e; z += e; } |
| for (let tj = 0; tj <= ti; tj++) aAll[so + ti * T + tj] /= z; |
| } |
| } |
| const aq = V.quantizeRows(aAll, BH * T, T); |
| const vq = V.quantizeHeadCols(v, B, T, heads, hd); |
| const ctxOut = ctx.att ? await ctx.att.ctx(aq.q, vq.q, aq.s, vq.s, dAtt) |
| : V.attCtxJS(aq.q, vq.q, aq.s, vq.s, dAtt, ctx.L); |
| if (ctx.att && ctx.audit && ctx.audit.due()) { |
| const bad = V.auditAttCtx(aq.q, vq.q, aq.s, vq.s, dAtt, ctxOut, ctx.L, ctx.audit.cells); |
| if (bad) ctx.audit.fail(bad); |
| } |
| cb.aAll = aAll; |
| cb.ctxOut = ctxOut; |
| const attnOut = await vmm(ctxOut, bl.Wo, BT, C, C, ctx); |
| const x2 = new Float32Array(BT * C); |
| for (let i = 0; i < x2.length; i++) x2[i] = x[i] + attnOut[i]; |
| cb.x2 = x2; |
| const l2 = lnFwd(x2, BT, C); cb.ln2 = l2; |
| |
| |
| |
| |
| |
| const { h1, out: mlpOut } = await V.vmlpBlock(l2.y, bl.W1, bl.W2, |
| { m: BT, k: C, h: hidden, n: C }, ctx.L, ctx.mlp, ctx.audit); |
| const mask = new Uint8Array(h1.length); |
| for (let i = 0; i < h1.length; i++) if (h1[i] > 0) mask[i] = 1; |
| cb.h1 = h1; cb.mask = mask; |
| x = new Float32Array(BT * C); |
| for (let i = 0; i < x.length; i++) x[i] = x2[i] + mlpOut[i]; |
| cache.blocks.push(cb); |
| } |
| const lf = lnFwd(x, BT, C); cache.lnf = lf; cache.xf = x; |
| const logits = await vmm(lf.y, TC.transpose(m.emb, vocab, C), BT, C, vocab, ctx); |
| |
| let loss = 0; |
| const dlogits = new Float32Array(BT * vocab); |
| for (let i = 0; i < BT; i++) { |
| let mx = -1e30; |
| for (let j = 0; j < vocab; j++) mx = Math.max(mx, logits[i * vocab + j]); |
| let z = 0; |
| for (let j = 0; j < vocab; j++) z += Math.exp(logits[i * vocab + j] - mx); |
| const lz = Math.log(z) + mx; |
| loss += lz - logits[i * vocab + Y[i]]; |
| for (let j = 0; j < vocab; j++) |
| dlogits[i * vocab + j] = (Math.exp(logits[i * vocab + j] - lz) - (j === Y[i] ? 1 : 0)) / BT; |
| } |
| loss /= BT; |
| cache.dlogits = dlogits; |
| return { loss, cache, logits }; |
| } |
|
|
| |
| |
| |
| async function backward(m, cache) { |
| const { c: C, t: T, b: B, layers, heads, hidden, vocab } = m.cfg; |
| const BT = B * T, hd = C / heads, tr = TC.transpose; |
| const g = m.params.map(p => new Float32Array(p.length)); |
| const gi = Object.fromEntries(m.names.map((n, i) => [n, i])); |
| |
| |
| |
| |
| |
| const units = !!m.ctx.unitBackward; |
| const bmm = units |
| ? (A, Bm, mm_, k, n) => vmm(A, Bm, mm_, k, n, m.ctx) |
| : async (A, Bm, mm_, k, n) => TC.matmul(A, Bm, mm_, k, n); |
| |
| |
| |
| const bmmB = units |
| ? (A, Bm, rows, k, n, batch) => |
| V.vgemmBlock(A, Bm, { m: rows, k, n, batch }, m.ctx.L, m.ctx.bgemm, m.ctx.audit) |
| : async (A, Bm, rows, k, n, batch) => { |
| const out = new Float32Array(batch * rows * n); |
| for (let bz = 0; bz < batch; bz++) |
| out.set(TC.matmul(A.subarray(bz * rows * k, (bz + 1) * rows * k), |
| Bm.subarray(bz * k * n, (bz + 1) * k * n), rows, k, n), bz * rows * n); |
| return out; |
| }; |
| |
| |
| let dlnfIn; |
| if (m.ctx.fgemm2 && !units) { |
| |
| |
| |
| |
| [g[gi.emb], dlnfIn] = await m.ctx.fgemm2( |
| cache.dlogits, |
| cache.lnf.y, { m: vocab, k: BT, n: C, transA: true }, |
| m.emb, { m: BT, k: vocab, n: C }); |
| } else if (m.ctx.fgemm && !units) { |
| [g[gi.emb], dlnfIn] = await Promise.all([ |
| m.ctx.fgemm(cache.dlogits, cache.lnf.y, { m: vocab, k: BT, n: C, transA: true }), |
| m.ctx.fgemm(cache.dlogits, m.emb, { m: BT, k: vocab, n: C }), |
| ]); |
| } else if (units && m.ctx.bgemm) { |
| |
| |
| |
| |
| |
| |
| const quantizeColsAsRows = (X, rows, cols) => { |
| const q = new Int8Array(cols * rows), s = new Float32Array(cols); |
| for (let c = 0; c < cols; c++) { |
| let mx = 0; |
| for (let r = 0; r < rows; r++) { const a = Math.abs(X[r * cols + c]); if (a > mx) mx = a; } |
| const sc = Math.max(mx / 127, 1e-8); s[c] = sc; |
| for (let r = 0; r < rows; r++) { |
| const v = Math.round(X[r * cols + c] / sc); |
| q[c * rows + r] = v < -128 ? -128 : v > 127 ? 127 : v; |
| } |
| } |
| return { q, s }; |
| }; |
| const dlq = quantizeColsAsRows(cache.dlogits, BT, vocab); |
| const wq2 = V.quantizeCols(cache.lnf.y, BT, C); |
| const dEmb = { m: vocab, k: BT, n: C, batch: 1 }; |
| const [gEmb, dIn] = await Promise.all([ |
| m.ctx.bgemm(dlq.q, wq2.q, dlq.s, wq2.s, dEmb), |
| bmm(cache.dlogits, m.emb, BT, vocab, C), |
| ]); |
| if (m.ctx.audit && m.ctx.audit.due()) { |
| const bad = V.auditTile(dlq.q, wq2.q, dlq.s, wq2.s, dEmb, gEmb, m.ctx.L, m.ctx.audit.cells); |
| if (bad) m.ctx.audit.fail(bad); |
| } |
| g[gi.emb] = gEmb; dlnfIn = dIn; |
| } else { |
| |
| |
| [g[gi.emb], dlnfIn] = await Promise.all([ |
| bmm(tr(cache.dlogits, BT, vocab), cache.lnf.y, vocab, BT, C), |
| bmm(cache.dlogits, m.emb, BT, vocab, C), |
| ]); |
| } |
| let dx = lnBwd(dlnfIn, cache.lnf.y, cache.lnf.sig, BT, C); |
| const scale = 1 / Math.sqrt(hd); |
| |
| const cat = (...arrs) => { |
| const out = new Float32Array(arrs.reduce((a, x) => a + x.length, 0)); |
| let o = 0; for (const x of arrs) { out.set(x, o); o += x.length; } |
| return out; |
| }; |
| for (let l = layers - 1; l >= 0; l--) { |
| const bl = m.blocks[l], cb = cache.blocks[l]; |
| |
| |
| |
| |
| |
| const dmlpOut = dx; |
| const [gW2, dh1] = await Promise.all([ |
| bmm(tr(cb.h1, BT, hidden), dmlpOut, hidden, BT, C), |
| bmm(dmlpOut, tr(bl.W2, hidden, C), BT, C, hidden), |
| ]); |
| g[gi[`b${l}.W2`]] = gW2; |
| for (let i = 0; i < dh1.length; i++) if (!cb.mask[i]) dh1[i] = 0; |
| const [gW1, dln2raw] = await Promise.all([ |
| bmm(tr(cb.ln2.y, BT, C), dh1, C, BT, hidden), |
| bmm(dh1, tr(bl.W1, C, hidden), BT, hidden, C), |
| ]); |
| g[gi[`b${l}.W1`]] = gW1; |
| const dln2in = lnBwd(dln2raw, cb.ln2.y, cb.ln2.sig, BT, C); |
| const dx2 = new Float32Array(BT * C); |
| for (let i = 0; i < dx2.length; i++) dx2[i] = dx[i] + dln2in[i]; |
| |
| const [gWo, dctx] = await Promise.all([ |
| bmm(tr(cb.ctxOut, BT, C), dx2, C, BT, C), |
| bmm(dx2, tr(bl.Wo, C, C), BT, C, C), |
| ]); |
| g[gi[`b${l}.Wo`]] = gWo; |
| |
| |
| const BH = B * heads; |
| const qb = gatherHeads(cb.q, B, T, C, heads, hd); |
| const kb = gatherHeads(cb.k, B, T, C, heads, hd); |
| const vb = gatherHeads(cb.v, B, T, C, heads, hd); |
| const dchb = gatherHeads(dctx, B, T, C, heads, hd); |
| const aT = batchedTranspose(cb.aAll, BH, T, T); |
| const vT = batchedTranspose(vb, BH, T, hd); |
| const [dvAll, daAll] = await Promise.all([ |
| bmmB(aT, dchb, T, T, hd, BH), |
| bmmB(dchb, vT, T, hd, T, BH), |
| ]); |
| |
| |
| const dsAll = new Float32Array(BH * T * T); |
| for (let bz = 0; bz < BH; bz++) { |
| const o = bz * T * T; |
| for (let ti = 0; ti < T; ti++) { |
| let dot = 0; |
| for (let tj = 0; tj <= ti; tj++) dot += daAll[o + ti * T + tj] * cb.aAll[o + ti * T + tj]; |
| for (let tj = 0; tj <= ti; tj++) |
| dsAll[o + ti * T + tj] = cb.aAll[o + ti * T + tj] * (daAll[o + ti * T + tj] - dot) * scale; |
| } |
| } |
| const dsT = batchedTranspose(dsAll, BH, T, T); |
| const [dqAll, dkAll] = await Promise.all([ |
| bmmB(dsAll, kb, T, T, hd, BH), |
| bmmB(dsT, qb, T, T, hd, BH), |
| ]); |
| const dq = new Float32Array(BT * C), dk = new Float32Array(BT * C), dv = new Float32Array(BT * C); |
| scatterHeadsAcc(dq, dqAll, B, T, C, heads, hd); |
| scatterHeadsAcc(dk, dkAll, B, T, C, heads, hd); |
| scatterHeadsAcc(dv, dvAll, B, T, C, heads, hd); |
| |
| |
| |
| |
| |
| const ln1T = tr(cb.ln1.y, BT, C); |
| const [gQKV, dIn3] = await Promise.all([ |
| bmmB(cat(ln1T, ln1T, ln1T), cat(dq, dk, dv), C, BT, C, 3), |
| bmmB(cat(dq, dk, dv), cat(tr(bl.Wq, C, C), tr(bl.Wk, C, C), tr(bl.Wv, C, C)), BT, C, C, 3), |
| ]); |
| const CC = C * C, BTC = BT * C; |
| g[gi[`b${l}.Wq`]] = gQKV.slice(0, CC); |
| g[gi[`b${l}.Wk`]] = gQKV.slice(CC, 2 * CC); |
| g[gi[`b${l}.Wv`]] = gQKV.slice(2 * CC, 3 * CC); |
| |
| |
| |
| |
| |
| const dln1in = new Float32Array(BTC); |
| for (let i = 0; i < BTC; i++) |
| dln1in[i] = Math.fround(Math.fround(dIn3[i] + dIn3[BTC + i]) + dIn3[2 * BTC + i]); |
| const dxin = lnBwd(dln1in, cb.ln1.y, cb.ln1.sig, BT, C); |
| dx = new Float32Array(BT * C); |
| for (let i = 0; i < dx.length; i++) dx[i] = dx2[i] + dxin[i]; |
| } |
| |
| const ge = g[gi.emb], gp = g[gi.pos]; |
| for (let i = 0; i < BT; i++) { |
| const id = cache.X[i], tpos = i % T; |
| for (let j = 0; j < C; j++) { ge[id * C + j] += dx[i * C + j]; gp[tpos * C + j] += dx[i * C + j]; } |
| } |
| |
| const flat = new Float32Array(m.nParams); |
| let off = 0; |
| for (const t of g) { flat.set(t, off); off += t.length; } |
| return flat; |
| } |
|
|
| async function trainStep(m) { |
| const { X, Y } = sampleBatch(m.cfg); |
| const { loss, cache } = await forward(m, X, Y); |
| const grad = await backward(m, cache); |
| return { loss, grad }; |
| } |
|
|
| function applyUpdate(m, upd) { |
| let off = 0; |
| for (const p of m.params) { for (let i = 0; i < p.length; i++) p[i] -= upd[off + i]; off += p.length; } |
| } |
| function getFlatParams(m) { |
| const flat = new Float32Array(m.nParams); |
| let off = 0; |
| for (const p of m.params) { flat.set(p, off); off += p.length; } |
| return flat; |
| } |
| function setFlatParams(m, flat) { |
| let off = 0; |
| for (const p of m.params) { p.set(flat.subarray(off, off + p.length)); off += p.length; } |
| } |
|
|
| |
| async function generate(m, prompt, nChars) { |
| const { t: T } = m.cfg; |
| let ids = [...encode(prompt)]; |
| for (let step = 0; step < nChars; step++) { |
| const win = ids.slice(-T); |
| const X = new Int32Array(T), Y = new Int32Array(T); |
| for (let i = 0; i < win.length; i++) X[T - win.length + i] = win[i]; |
| const save = m.cfg.b; m.cfg.b = 1; |
| const { logits } = await forward(m, X, Y); |
| m.cfg.b = save; |
| const row = (T - 1) * m.cfg.vocab; |
| let best = 0, bv = -1e30; |
| for (let j = 0; j < m.cfg.vocab; j++) if (logits[row + j] > bv) { bv = logits[row + j]; best = j; } |
| ids.push(best); |
| } |
| return decode(ids); |
| } |
|
|
| const api = { init, trainStep, applyUpdate, getFlatParams, setFlatParams, generate, |
| streamFineWebEdu, streamDataset, datasetName, loadTokenizer, loadTokenizerData, |
| vocabSize, tokenizerName, encode, decode }; |
| if (typeof module !== "undefined" && module.exports) { TC = require("./traincore.js"); V = require("./verified_core.js"); module.exports = api; } |
| else { TC = root.TrainCore; V = root.Verified; root.Transformer = api; } |
| })(typeof self !== "undefined" ? self : this); |
|
|