| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { createSplitArena } from './arena.js'; |
| import { dispatchGemm, dispatchAttention, dispatchAddLn, dispatchEmbed, dispatchScatterRows } from './pipelines.js'; |
| import { |
| D_MODEL, HEADS, HEAD_DIM, FFN, ENC_LAYERS, DEC_LAYERS, assertModelActive, |
| } from './constants.js'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function runEncoder(ctx, weights, { ids, lens, B, S }, { recordInto = null, gemmOverrides = null, attnOverrides = null, packed = 'auto', sg = false, attnQbAlign8 = false, retainEncOut = true, splitSubmits = false } = {}) { |
| assertModelActive(weights.model, 'runEncoder weights'); |
| const HD = HEADS * HEAD_DIM; |
| const QKV_N = 3 * HD; |
| const CROSS_KV_N = 2 * HD; |
| const { device } = ctx; |
| const t = weights.dtype; |
| const eb = t === 'f16' ? 2 : 4; |
| const padRows = B * S; |
| let total = 0; |
| for (let i = 0; i < B; i++) total += lens[i]; |
| const usePacked = (packed === 'auto' ? !attnOverrides : !!packed) |
| && total < padRows && S <= 0xffff; |
| const rows = usePacked ? total : padRows; |
| const flags = { t }; |
| |
| |
| |
| const lnFlags = sg ? { ...flags, sg: true } : flags; |
| |
| |
| |
| |
| |
| const gemmFlags = { ...flags, tiled: true, tm8: true, ...(gemmOverrides ?? {}) }; |
| |
| |
| const attnFlags = { ...flags, block: true, packed: usePacked, ...(attnQbAlign8 ? { qbAlign8: true } : {}), ...(attnOverrides ?? {}) }; |
|
|
| const arena = createSplitArena(device); |
| const scratch = []; |
| try { |
| const retained = arena.retained; |
| const scratchArena = arena.scratch; |
| const upload = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST; |
| const act = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC; |
|
|
| const idsBuf = scratchArena.buf(rows * 4, upload, 'enc ids'); |
| const lensBuf = retained.buf(B * 4, upload, 'enc lens'); |
| let startsBuf = null; |
| if (usePacked) { |
| |
| |
| const words = new Uint32Array(rows); |
| const starts = new Uint32Array(B); |
| let r = 0; |
| for (let bi = 0; bi < B; bi++) { |
| starts[bi] = r; |
| for (let m = 0; m < lens[bi]; m++, r++) words[r] = (m << 16) | ids[bi * S + m]; |
| } |
| startsBuf = scratchArena.buf(B * 4, upload, 'enc starts'); |
| device.queue.writeBuffer(idsBuf, 0, words); |
| device.queue.writeBuffer(startsBuf, 0, starts); |
| } else { |
| device.queue.writeBuffer(idsBuf, 0, ids); |
| } |
| device.queue.writeBuffer(lensBuf, 0, lens); |
|
|
| |
| |
| const aArena = retainEncOut && !usePacked ? retained : scratchArena; |
| const a = aArena.buf(rows * D_MODEL * eb, act, 'enc hidden a'); |
| const b = scratchArena.buf(rows * D_MODEL * eb, act, 'enc hidden b'); |
| const y = scratchArena.buf(rows * D_MODEL * eb, act, 'enc sublayer y'); |
| const qkvOut = scratchArena.buf(rows * QKV_N * eb, act, 'enc qkv out'); |
| const attnOut = scratchArena.buf(rows * HD * eb, act, 'enc attn out'); |
| const ffnTmp = scratchArena.buf(rows * FFN * eb, act, 'enc ffn tmp'); |
| |
| |
| const crossKV = [ |
| retained.buf(padRows * CROSS_KV_N * eb, act, 'crossKV dec.0'), |
| retained.buf(padRows * CROSS_KV_N * eb, act, 'crossKV dec.1'), |
| ]; |
| const crossKVP = usePacked ? [ |
| scratchArena.buf(rows * CROSS_KV_N * eb, act, 'crossKV packed dec.0'), |
| scratchArena.buf(rows * CROSS_KV_N * eb, act, 'crossKV packed dec.1'), |
| ] : crossKV; |
|
|
| const W = (name) => weights.bindingFor(name); |
| let encoder = recordInto ? null : device.createCommandEncoder({ label: 'encoder pass' }); |
| let pass = recordInto ?? encoder.beginComputePass({ label: 'encoder pass' }); |
| const rec = ({ scratch: s }) => scratch.push(...s); |
| |
| |
| |
| const split = !recordInto && splitSubmits; |
| let chunkIdx = 0; |
| const cut = () => { |
| if (!split) return; |
| pass.end(); |
| device.queue.submit([encoder.finish()]); |
| chunkIdx++; |
| encoder = device.createCommandEncoder({ label: `encoder chunk ${chunkIdx}` }); |
| pass = encoder.beginComputePass({ label: `encoder chunk ${chunkIdx}` }); |
| }; |
|
|
| |
| rec(dispatchEmbed(device, pass, { |
| ids: idsBuf, table: W('shared.weight'), posEmbed: W('pos_embed'), y: a, |
| mode: 'src', nRows: rows, batch: B, s: S, packed: usePacked, flags, |
| })); |
| cut(); |
|
|
| |
| for (let l = 0; l < ENC_LAYERS; l++) { |
| const p = (name) => `enc.${l}.${name}`; |
| |
| rec(dispatchGemm(device, pass, { |
| x: a, w: W(p('qkv.weight')), b: W(p('qkv.bias')), y: qkvOut, |
| M: rows, K: D_MODEL, N: QKV_N, flags: gemmFlags, |
| })); |
| rec(dispatchAttention(device, pass, { |
| q: qkvOut, k: qkvOut, v: qkvOut, lens: lensBuf, y: attnOut, starts: startsBuf, |
| B, M: S, L: S, lenMode: 1, |
| qStride: QKV_N, qOff: 0, kvStride: QKV_N, kOff: HD, vOff: 2 * HD, |
| flags: attnFlags, |
| })); |
| rec(dispatchGemm(device, pass, { |
| x: attnOut, w: W(p('out.weight')), b: W(p('out.bias')), y, |
| M: rows, K: HD, N: D_MODEL, flags: gemmFlags, |
| })); |
| rec(dispatchAddLn(device, pass, { |
| x: y, r: a, gamma: W(p('ln1.weight')), beta: W(p('ln1.bias')), y: b, |
| rows, flags: lnFlags, |
| })); |
| |
| rec(dispatchGemm(device, pass, { |
| x: b, w: W(p('fc1.weight')), b: W(p('fc1.bias')), y: ffnTmp, |
| M: rows, K: D_MODEL, N: FFN, flags: { ...gemmFlags, silu: true }, |
| })); |
| rec(dispatchGemm(device, pass, { |
| x: ffnTmp, w: W(p('fc2.weight')), b: W(p('fc2.bias')), y, |
| M: rows, K: FFN, N: D_MODEL, flags: gemmFlags, |
| })); |
| rec(dispatchAddLn(device, pass, { |
| x: y, r: b, gamma: W(p('ln2.weight')), beta: W(p('ln2.bias')), y: a, |
| rows, flags: lnFlags, |
| })); |
| cut(); |
| } |
|
|
| |
| |
| for (let l = 0; l < DEC_LAYERS; l++) { |
| rec(dispatchGemm(device, pass, { |
| x: a, w: W(`dec.${l}.cross_kv.weight`), b: W(`dec.${l}.cross_kv.bias`), y: crossKVP[l], |
| M: rows, K: D_MODEL, N: CROSS_KV_N, flags: gemmFlags, |
| })); |
| } |
|
|
| |
| |
| |
| let encOut = retainEncOut && !usePacked ? a : null; |
| if (usePacked) { |
| for (let l = 0; l < DEC_LAYERS; l++) { |
| rec(dispatchScatterRows(device, pass, { |
| x: crossKVP[l], y: crossKV[l], starts: startsBuf, lens: lensBuf, |
| B, S, N: CROSS_KV_N, flags, |
| })); |
| } |
| if (retainEncOut) { |
| encOut = retained.buf(padRows * D_MODEL * eb, act, 'enc out padded'); |
| rec(dispatchScatterRows(device, pass, { |
| x: a, y: encOut, starts: startsBuf, lens: lensBuf, |
| B, S, N: D_MODEL, flags, |
| })); |
| } |
| } |
|
|
| if (recordInto) { |
| return { encOut, crossKV, lensBuf, B, S, arena, scratch, packed: usePacked }; |
| } |
| pass.end(); |
| device.queue.submit([encoder.finish()]); |
| |
| |
| |
| const submittedDone = device.queue.onSubmittedWorkDone(); |
| for (const buf of scratch) buf.destroy(); |
| |
| |
| |
| |
| arena.retireScratch(submittedDone); |
|
|
| return { encOut, crossKV, lensBuf, B, S, arena, packed: usePacked, submittedDone }; |
| } catch (err) { |
| |
| |
| |
| for (const buf of scratch) { |
| try { buf.destroy(); } catch {} |
| } |
| try { arena.destroy(); } catch {} |
| throw err; |
| } |
| } |
|
|