| |
| |
| |
| |
|
|
| const EPS = 1e-5; |
|
|
| |
| function linear(out, x, W, nOut, nIn) { |
| const w = W.trits ?? W.f32; |
| const scale = W.trits ? W.scale : 1; |
| for (let o = 0; o < nOut; o++) { |
| let acc = 0; |
| const row = o * nIn; |
| for (let i = 0; i < nIn; i++) acc += x[i] * w[row + i]; |
| out[o] = acc * scale; |
| } |
| } |
|
|
| function rmsnorm(out, x, gain, n) { |
| let ss = 0; |
| for (let i = 0; i < n; i++) ss += x[i] * x[i]; |
| const inv = 1 / Math.sqrt(ss / n + EPS); |
| for (let i = 0; i < n; i++) out[i] = x[i] * inv * gain[i]; |
| } |
|
|
| function silu(v) { |
| return v / (1 + Math.exp(-v)); |
| } |
|
|
| export class Model { |
| constructor({ header, tensors }) { |
| const cfg = header.config; |
| this.cfg = cfg; |
| this.headDim = cfg.n_embd / cfg.n_head; |
|
|
| const get = (name) => { |
| const t = tensors.get(name); |
| if (!t) throw new Error(`pack missing tensor ${name}`); |
| return t; |
| }; |
| this.tokEmb = get("tok_emb.weight").f32; |
| this.finalNorm = get("final_norm.weight").f32; |
| this.layers = []; |
| for (let i = 0; i < cfg.n_layer; i++) { |
| const p = `blocks.${i}.`; |
| this.layers.push({ |
| attnNorm: get(p + "attn_norm.weight").f32, |
| q: get(p + "attn.q.weight"), k: get(p + "attn.k.weight"), |
| v: get(p + "attn.v.weight"), o: get(p + "attn.o.weight"), |
| mlpNorm: get(p + "mlp_norm.weight").f32, |
| gate: get(p + "mlp.gate.weight"), up: get(p + "mlp.up.weight"), |
| down: get(p + "mlp.down.weight"), |
| }); |
| } |
| this.mlpHidden = get("blocks.0.mlp.gate.weight").trits |
| ? tensors.get("blocks.0.mlp.gate.weight").trits.length / cfg.n_embd |
| : tensors.get("blocks.0.mlp.gate.weight").f32.length / cfg.n_embd; |
|
|
| |
| const half = this.headDim / 2; |
| this.ropeCos = new Float32Array(cfg.ctx * half); |
| this.ropeSin = new Float32Array(cfg.ctx * half); |
| for (let p = 0; p < cfg.ctx; p++) { |
| for (let j = 0; j < half; j++) { |
| const angle = p / 10000 ** ((2 * j) / this.headDim); |
| this.ropeCos[p * half + j] = Math.cos(angle); |
| this.ropeSin[p * half + j] = Math.sin(angle); |
| } |
| } |
|
|
| |
| const E = cfg.n_embd; |
| this.kCache = this.layers.map(() => new Float32Array(cfg.ctx * E)); |
| this.vCache = this.layers.map(() => new Float32Array(cfg.ctx * E)); |
| this.pos = 0; |
| this.x = new Float32Array(E); |
| this.xn = new Float32Array(E); |
| this.qB = new Float32Array(E); |
| this.kB = new Float32Array(E); |
| this.vB = new Float32Array(E); |
| this.attnB = new Float32Array(E); |
| this.projB = new Float32Array(E); |
| this.gB = new Float32Array(this.mlpHidden); |
| this.uB = new Float32Array(this.mlpHidden); |
| this.scores = new Float32Array(cfg.ctx); |
| this.logits = new Float32Array(cfg.vocab); |
| } |
|
|
| reset() { |
| this.pos = 0; |
| } |
|
|
| applyRope(vec, pos) { |
| const { n_head } = this.cfg; |
| const hd = this.headDim, half = hd / 2; |
| for (let h = 0; h < n_head; h++) { |
| const base = h * hd; |
| for (let j = 0; j < half; j++) { |
| const c = this.ropeCos[pos * half + j]; |
| const s = this.ropeSin[pos * half + j]; |
| const a = vec[base + j], b = vec[base + j + half]; |
| vec[base + j] = a * c - b * s; |
| vec[base + j + half] = b * c + a * s; |
| } |
| } |
| } |
|
|
| |
| step(tokenId) { |
| const cfg = this.cfg, E = cfg.n_embd, hd = this.headDim; |
| const pos = this.pos; |
| if (pos >= cfg.ctx) throw new Error("context window full"); |
| this.x.set(this.tokEmb.subarray(tokenId * E, (tokenId + 1) * E)); |
|
|
| for (let l = 0; l < this.layers.length; l++) { |
| const L = this.layers[l]; |
| |
| rmsnorm(this.xn, this.x, L.attnNorm, E); |
| linear(this.qB, this.xn, L.q, E, E); |
| linear(this.kB, this.xn, L.k, E, E); |
| linear(this.vB, this.xn, L.v, E, E); |
| this.applyRope(this.qB, pos); |
| this.applyRope(this.kB, pos); |
| this.kCache[l].set(this.kB, pos * E); |
| this.vCache[l].set(this.vB, pos * E); |
|
|
| const K = this.kCache[l], V = this.vCache[l]; |
| const invSqrt = 1 / Math.sqrt(hd); |
| for (let h = 0; h < cfg.n_head; h++) { |
| const ho = h * hd; |
| let max = -Infinity; |
| for (let p = 0; p <= pos; p++) { |
| let dot = 0; |
| const ko = p * E + ho; |
| for (let j = 0; j < hd; j++) dot += this.qB[ho + j] * K[ko + j]; |
| const sc = dot * invSqrt; |
| this.scores[p] = sc; |
| if (sc > max) max = sc; |
| } |
| let sum = 0; |
| for (let p = 0; p <= pos; p++) { |
| const e = Math.exp(this.scores[p] - max); |
| this.scores[p] = e; |
| sum += e; |
| } |
| for (let j = 0; j < hd; j++) { |
| let acc = 0; |
| for (let p = 0; p <= pos; p++) acc += this.scores[p] * V[p * E + ho + j]; |
| this.attnB[ho + j] = acc / sum; |
| } |
| } |
| linear(this.projB, this.attnB, L.o, E, E); |
| for (let i = 0; i < E; i++) this.x[i] += this.projB[i]; |
|
|
| |
| rmsnorm(this.xn, this.x, L.mlpNorm, E); |
| linear(this.gB, this.xn, L.gate, this.mlpHidden, E); |
| linear(this.uB, this.xn, L.up, this.mlpHidden, E); |
| for (let i = 0; i < this.mlpHidden; i++) this.gB[i] = silu(this.gB[i]) * this.uB[i]; |
| linear(this.projB, this.gB, L.down, E, this.mlpHidden); |
| for (let i = 0; i < E; i++) this.x[i] += this.projB[i]; |
| } |
|
|
| rmsnorm(this.xn, this.x, this.finalNorm, E); |
| linear(this.logits, this.xn, { f32: this.tokEmb }, cfg.vocab, E); |
| this.pos++; |
| return this.logits; |
| } |
|
|
| |
| prefill(ids) { |
| let logits = null; |
| for (const id of ids) logits = this.step(id); |
| return logits; |
| } |
| } |
|
|
| |
| |
| |
| |
| export function mulberry32(seed) { |
| let a = (seed >>> 0) ^ 0x9e3779b9; |
| a = Math.imul(a ^ (a >>> 16), 0x21f0aaad); |
| a = Math.imul(a ^ (a >>> 15), 0x735a2d97); |
| a = (a ^ (a >>> 15)) >>> 0; |
| return function () { |
| 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; |
| }; |
| } |
|
|
| |
| |
| export function sampleToken(logits, { temperature, topK }, rand) { |
| const n = logits.length; |
| const t = Math.max(temperature, 1e-6); |
| const scaled = new Float32Array(n); |
| for (let i = 0; i < n; i++) scaled[i] = logits[i] / t; |
|
|
| if (topK) { |
| const k = Math.min(topK, n); |
| const sorted = Float32Array.from(scaled).sort().reverse(); |
| const threshold = sorted[k - 1]; |
| for (let i = 0; i < n; i++) if (scaled[i] < threshold) scaled[i] = -Infinity; |
| } |
|
|
| let max = -Infinity; |
| for (let i = 0; i < n; i++) if (scaled[i] > max) max = scaled[i]; |
| let sum = 0; |
| for (let i = 0; i < n; i++) { |
| scaled[i] = Math.exp(scaled[i] - max); |
| sum += scaled[i]; |
| } |
| const r = rand() * sum; |
| let cum = 0; |
| for (let i = 0; i < n; i++) { |
| cum += scaled[i]; |
| if (r < cum) return i; |
| } |
| return n - 1; |
| } |
|
|
| |
| export function greedy(model, ids, n) { |
| model.reset(); |
| let logits = model.prefill(ids); |
| const out = []; |
| for (let i = 0; i < n && model.pos < model.cfg.ctx; i++) { |
| let best = 0; |
| for (let j = 1; j < logits.length; j++) if (logits[j] > logits[best]) best = j; |
| out.push(best); |
| logits = model.step(best); |
| } |
| return out; |
| } |
|
|