| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <title>DEPTH WGSL timing (grid-parallel)</title> |
| <style> |
| html, body { |
| margin: 0; |
| padding: 0; |
| background: #0b0f14; |
| color: #e6edf3; |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; |
| } |
| #wrap { |
| max-width: 880px; |
| margin: 0 auto; |
| padding: 28px 24px 60px; |
| } |
| h1 { |
| font-size: 26px; |
| font-weight: 700; |
| margin: 0 0 6px; |
| } |
| .sub { color: #9aa7b4; font-size: 14px; margin-bottom: 22px; } |
| #status { |
| font-size: 16px; |
| padding: 14px 16px; |
| border-radius: 10px; |
| background: #16202b; |
| border: 1px solid #243240; |
| margin-bottom: 22px; |
| white-space: pre-wrap; |
| } |
| .running { color: #ffd479; } |
| .ok { color: #7ee787; } |
| .bad { color: #ff7b72; } |
| #result { display: none; } |
| .card { |
| background: #111a23; |
| border: 1px solid #243240; |
| border-radius: 12px; |
| padding: 18px 20px; |
| margin-bottom: 18px; |
| } |
| .row { |
| display: flex; |
| justify-content: space-between; |
| align-items: baseline; |
| padding: 6px 0; |
| border-bottom: 1px solid #1c2733; |
| gap: 16px; |
| } |
| .row:last-child { border-bottom: none; } |
| .row .k { color: #9aa7b4; font-size: 14px; } |
| .row .v { font-size: 16px; font-weight: 600; text-align: right; word-break: break-word; } |
| .hero { |
| text-align: center; |
| padding: 26px 18px; |
| } |
| .hero .label { color: #9aa7b4; font-size: 14px; letter-spacing: .08em; text-transform: uppercase; } |
| .hero .big { |
| font-size: 64px; |
| font-weight: 800; |
| line-height: 1.05; |
| margin: 8px 0 2px; |
| font-variant-numeric: tabular-nums; |
| } |
| .hero .unit { font-size: 26px; color: #9aa7b4; font-weight: 600; } |
| .badge { |
| display: inline-block; |
| padding: 6px 16px; |
| border-radius: 999px; |
| font-size: 20px; |
| font-weight: 800; |
| letter-spacing: .05em; |
| } |
| .badge.pass { background: #133a1e; color: #7ee787; border: 1px solid #2ea043; } |
| .badge.fail { background: #3a1414; color: #ff7b72; border: 1px solid #da3633; } |
| pre.codes { |
| font-family: ui-monospace, SFMono-Regular, Menlo, monospace; |
| font-size: 13px; |
| color: #cdd9e5; |
| background: #0d141b; |
| padding: 10px 12px; |
| border-radius: 8px; |
| overflow-x: auto; |
| margin: 6px 0 0; |
| } |
| .err { |
| color: #ff7b72; |
| font-family: ui-monospace, SFMono-Regular, Menlo, monospace; |
| font-size: 13px; |
| white-space: pre-wrap; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="wrap"> |
| <h1>Magenta-RT2 small — DEPTH (WGSL) timing</h1> |
| <div class="sub">Grid-parallel WebGPU depth transformer · W0/W1 split storage buffers · bit-exact correctness + real device timing</div> |
| <div id="status" class="running">starting…</div> |
| <div id="result"></div> |
| </div> |
|
|
| <script type="module"> |
| "use strict"; |
| |
| const BASE = "https://huggingface.co/magenta-community/magenta-rt-onnx-small/resolve/main/depth_wgsl/"; |
| const CACHE_NAME = "depth-wgsl-v1"; |
| |
| const statusEl = document.getElementById("status"); |
| const resultEl = document.getElementById("result"); |
| |
| function setStatus(msg, cls) { |
| statusEl.textContent = msg; |
| statusEl.className = cls || ""; |
| } |
| function showError(where, e) { |
| const msg = (e && (e.stack || e.message)) ? (e.stack || e.message) : String(e); |
| statusEl.className = "bad"; |
| statusEl.innerHTML = '<b>Error (' + where + ')</b>\n<span class="err">' + escapeHtml(msg) + '</span>'; |
| console.error(where, e); |
| } |
| function escapeHtml(s) { |
| return String(s).replace(/[&<>]/g, c => ({ "&": "&", "<": "<", ">": ">" }[c])); |
| } |
| |
| |
| const D_IN = 1024; |
| const D_MODEL = 768; |
| const D_FF = 3072; |
| const VOCAB = 12294; |
| const NUM_CODEBOOKS = 12; |
| const CODEBOOK_SIZE = 1024; |
| const NUM_RESERVED = 6; |
| const NUM_LAYERS = 2; |
| const NUM_HEADS = 6; |
| const UPH = 128; |
| const HD = NUM_HEADS * UPH; |
| const QKV = 3 * HD; |
| const SOFT_CAP = 30.0; |
| const EPS = 1e-6; |
| const R_SOFTPLUS_0 = 1.442695041; |
| |
| |
| |
| |
| const TILE_ROWS = 64; |
| const LANES = 4; |
| const WG_SIZE = TILE_ROWS * LANES; |
| function nWG(N) { return Math.ceil(N / TILE_ROWS); } |
| |
| |
| |
| |
| |
| |
| |
| const RED = 32; |
| const ROWS = 8; |
| const T_WG = ROWS * RED; |
| function nWGT(N) { return Math.ceil(N / ROWS); } |
| let USE_SUBGROUPS = false; |
| |
| |
| let DISPATCH_COUNT = 0; |
| const _origDispatch = GPUComputePassEncoder.prototype.dispatchWorkgroups; |
| GPUComputePassEncoder.prototype.dispatchWorkgroups = function (x, y, z) { |
| DISPATCH_COUNT++; |
| return _origDispatch.call(this, x, y, z); |
| }; |
| |
| |
| async function cachedFetch(url, onProgress) { |
| let cache = null; |
| try { cache = await caches.open(CACHE_NAME); } catch (e) { cache = null; } |
| if (cache) { |
| const hit = await cache.match(url); |
| if (hit) return hit; |
| } |
| const resp = await fetch(url); |
| if (!resp.ok) throw new Error("fetch " + url + " -> HTTP " + resp.status); |
| |
| |
| const total = Number(resp.headers.get("content-length")) || 0; |
| if (resp.body && onProgress) { |
| const reader = resp.body.getReader(); |
| const chunks = []; |
| let received = 0; |
| for (;;) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| chunks.push(value); |
| received += value.byteLength; |
| onProgress(received, total); |
| } |
| const blob = new Blob(chunks); |
| const buffered = new Response(blob, { |
| headers: { "content-type": resp.headers.get("content-type") || "application/octet-stream" }, |
| }); |
| if (cache) { try { await cache.put(url, buffered.clone()); } catch (e) {} } |
| return buffered; |
| } |
| if (cache) { try { await cache.put(url, resp.clone()); } catch (e) {} } |
| return resp; |
| } |
| |
| function fmtBytes(n) { |
| if (n >= 1 << 30) return (n / (1 << 30)).toFixed(2) + " GB"; |
| if (n >= 1 << 20) return (n / (1 << 20)).toFixed(1) + " MB"; |
| if (n >= 1 << 10) return (n / (1 << 10)).toFixed(1) + " KB"; |
| return n + " B"; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const CONSTS = ` |
| const D_IN : u32 = ${D_IN}u; |
| const D_MODEL : u32 = ${D_MODEL}u; |
| const D_FF : u32 = ${D_FF}u; |
| const VOCAB : u32 = ${VOCAB}u; |
| const NUM_HEADS : u32 = ${NUM_HEADS}u; |
| const UPH : u32 = ${UPH}u; |
| const HD : u32 = ${HD}u; // 768 |
| const QKV : u32 = ${QKV}u; // 2304 |
| const SOFT_CAP : f32 = ${SOFT_CAP}; |
| const EPS : f32 = ${EPS}; |
| const R_SOFTPLUS_0 : f32 = ${R_SOFTPLUS_0}; |
| const NUM_RESERVED : u32 = ${NUM_RESERVED}u; |
| const CODEBOOK_SIZE : u32 = ${CODEBOOK_SIZE}u; |
| const TILE_ROWS : u32 = ${TILE_ROWS}u; |
| const LANES : u32 = ${LANES}u; |
| const RED : u32 = ${RED}u; |
| const ROWS : u32 = ${ROWS}u; |
| `; |
| |
| |
| |
| function buildHeaders(W_SPLIT) { |
| const WBUFS = ` |
| @group(0) @binding(0) var<storage, read> W0 : array<f32>; |
| @group(0) @binding(6) var<storage, read> W1 : array<f32>; |
| const W_SPLIT : u32 = ${W_SPLIT}u; |
| fn wv(i : u32) -> f32 { |
| if (i < W_SPLIT) { return W0[i]; } |
| return W1[i - W_SPLIT]; |
| } |
| |
| fn softplus(x: f32) -> f32 { |
| return log(1.0 + exp(-abs(x))) + max(x, 0.0); |
| } |
| fn gelu_tanh(x: f32) -> f32 { |
| let c : f32 = 0.7978845608028654; // sqrt(2/pi) |
| return 0.5 * x * (1.0 + tanh(c * (x + 0.044715 * x * x * x))); |
| } |
| `; |
| return { HEADER: CONSTS + WBUFS }; |
| } |
| |
| |
| function gridMatmul1(HEADER, opts) { |
| const K = opts.K; |
| const inDecl = |
| `@group(0) @binding(${opts.inBinding}) var<storage, read> vin : array<f32>;`; |
| const outDecl = |
| `@group(0) @binding(${opts.outBinding}) var<storage, read_write> vout : array<f32>;`; |
| const uniformDecl = |
| `struct U { wOff:u32, colBase:u32, normOff:u32, biasOff:u32, }; |
| @group(0) @binding(5) var<uniform> u : U;`; |
| |
| const normReduce = opts.rmsnorm |
| ? ` |
| // ---- fold pre-RMSNorm: reduce sum of squares over K (==D_MODEL) ---- |
| var lss : f32 = 0.0; |
| for (var i : u32 = tid; i < ${K}u; i = i + ${WG_SIZE}u) { |
| let xv = vin[i]; lss = lss + xv * xv; |
| } |
| red[tid] = lss; |
| workgroupBarrier(); |
| for (var s : u32 = ${WG_SIZE / 2}u; s > 0u; s = s >> 1u) { |
| if (tid < s) { red[tid] = red[tid] + red[tid + s]; } |
| workgroupBarrier(); |
| } |
| let rms = 1.0 / sqrt(red[0] / f32(${K}u) + EPS); |
| workgroupBarrier(); |
| // stage normed input |
| for (var i : u32 = tid; i < ${K}u; i = i + ${WG_SIZE}u) { |
| inS[i] = (vin[i] * rms) * wv(u.normOff + i); |
| } |
| workgroupBarrier();` |
| : ` |
| // stage raw input |
| for (var i : u32 = tid; i < ${K}u; i = i + ${WG_SIZE}u) { |
| inS[i] = vin[i]; |
| } |
| workgroupBarrier();`; |
| |
| let epi = ""; |
| if (opts.epilogue === "geluBias") { |
| epi = `acc = gelu_tanh(acc + wv(u.biasOff + col));`; |
| } else if (opts.epilogue === "softcap") { |
| const biasAdd = opts.hasBias ? `acc = acc + wv(u.biasOff + col);` : ``; |
| epi = `${biasAdd} |
| acc = SOFT_CAP * tanh(acc / SOFT_CAP);`; |
| } else { |
| epi = opts.hasBias ? `acc = acc + wv(u.biasOff + col);` : ``; |
| } |
| |
| return HEADER + ` |
| ${inDecl} |
| ${outDecl} |
| ${uniformDecl} |
| |
| var<workgroup> inS : array<f32, ${K}>; |
| var<workgroup> red : array<f32, ${WG_SIZE}>; |
| var<workgroup> part : array<f32, ${WG_SIZE}>; |
| |
| @compute @workgroup_size(${WG_SIZE}) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>, |
| @builtin(workgroup_id) wid : vec3<u32>) { |
| let tid = lid.x; |
| let row = tid / LANES; // 0..TILE_ROWS-1 |
| let lane = tid % LANES; // 0..LANES-1 |
| ${normReduce} |
| |
| let outIdx = wid.x * TILE_ROWS + row; // logical output column index |
| var acc : f32 = 0.0; |
| let col = u.colBase + outIdx; // physical column in W |
| let wbase = u.wOff + col; |
| for (var d : u32 = lane; d < ${K}u; d = d + LANES) { |
| acc = acc + inS[d] * wv(wbase + d * ${opts.NCOL}u); |
| } |
| part[tid] = acc; |
| workgroupBarrier(); |
| if (lane == 0u) { |
| var s : f32 = part[row * LANES]; |
| for (var l : u32 = 1u; l < LANES; l = l + 1u) { |
| s = s + part[row * LANES + l]; |
| } |
| var acc : f32 = s; |
| ${epi} |
| vout[outIdx] = acc; |
| } |
| } |
| `; |
| } |
| |
| |
| function gridMatmulT(HEADER, opts) { |
| const K = opts.K; |
| const sg = USE_SUBGROUPS; |
| const inDecl = |
| `@group(0) @binding(${opts.inBinding}) var<storage, read> vin : array<f32>;`; |
| const outDecl = |
| `@group(0) @binding(${opts.outBinding}) var<storage, read_write> vout : array<f32>;`; |
| const uniformDecl = |
| `struct U { wOff:u32, colBase:u32, normOff:u32, biasOff:u32, }; |
| @group(0) @binding(5) var<uniform> u : U;`; |
| const wtDecl = `@group(0) @binding(7) var<storage, read> WT : array<f32>;`; |
| |
| const normReduce = opts.rmsnorm |
| ? ` |
| var lss : f32 = 0.0; |
| for (var i : u32 = tid; i < ${K}u; i = i + ${T_WG}u) { |
| let xv = vin[i]; lss = lss + xv * xv; |
| } |
| red[tid] = lss; |
| workgroupBarrier(); |
| for (var s : u32 = ${T_WG / 2}u; s > 0u; s = s >> 1u) { |
| if (tid < s) { red[tid] = red[tid] + red[tid + s]; } |
| workgroupBarrier(); |
| } |
| let rms = 1.0 / sqrt(red[0] / f32(${K}u) + EPS); |
| workgroupBarrier(); |
| for (var i : u32 = tid; i < ${K}u; i = i + ${T_WG}u) { |
| inS[i] = (vin[i] * rms) * wv(u.normOff + i); |
| } |
| workgroupBarrier();` |
| : ` |
| for (var i : u32 = tid; i < ${K}u; i = i + ${T_WG}u) { inS[i] = vin[i]; } |
| workgroupBarrier();`; |
| |
| let epi = ""; |
| if (opts.epilogue === "geluBias") { |
| epi = `acc = gelu_tanh(acc + wv(u.biasOff + col));`; |
| } else if (opts.epilogue === "softcap") { |
| const biasAdd = opts.hasBias ? `acc = acc + wv(u.biasOff + col);` : ``; |
| epi = `${biasAdd} |
| acc = SOFT_CAP * tanh(acc / SOFT_CAP);`; |
| } else { |
| epi = opts.hasBias ? `acc = acc + wv(u.biasOff + col);` : ``; |
| } |
| |
| const reduceWrite = sg |
| ? ` |
| let total = subgroupAdd(acc); |
| if (lane == 0u) { |
| var accf : f32 = total; |
| ${epi.replace(/\bacc\b/g, "accf")} |
| vout[outIdx] = accf; |
| }` |
| : ` |
| part[tid] = acc; |
| workgroupBarrier(); |
| for (var s : u32 = ${RED / 2}u; s > 0u; s = s >> 1u) { |
| if (lane < s) { part[tid] = part[tid] + part[tid + s]; } |
| workgroupBarrier(); |
| } |
| if (lane == 0u) { |
| var accf : f32 = part[row * RED]; |
| ${epi.replace(/\bacc\b/g, "accf")} |
| vout[outIdx] = accf; |
| }`; |
| |
| const enableSg = sg ? "enable subgroups;\n" : ""; |
| |
| return enableSg + HEADER + ` |
| ${inDecl} |
| ${outDecl} |
| ${uniformDecl} |
| ${wtDecl} |
| |
| var<workgroup> inS : array<f32, ${K}>; |
| ${opts.rmsnorm ? `var<workgroup> red : array<f32, ${T_WG}>;` : ``} |
| ${sg ? "" : `var<workgroup> part : array<f32, ${T_WG}>;`} |
| |
| @compute @workgroup_size(${T_WG}) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>, |
| @builtin(workgroup_id) wid : vec3<u32>) { |
| let tid = lid.x; |
| let row = tid / RED; |
| let lane = tid % RED; |
| ${normReduce} |
| |
| let outIdx = wid.x * ROWS + row; |
| let col = u.colBase + outIdx; |
| let wbase = u.wOff + col * ${K}u; |
| var acc : f32 = 0.0; |
| for (var k : u32 = lane; k < ${K}u; k = k + RED) { |
| acc = acc + inS[k] * WT[wbase + k]; |
| } |
| ${reduceWrite} |
| } |
| `; |
| } |
| |
| |
| function gridMatmul2(HEADER, opts) { |
| const K = opts.K; |
| return HEADER + ` |
| @group(0) @binding(${opts.inBinding}) var<storage, read> vin : array<f32>; |
| @group(0) @binding(${opts.outBinding}) var<storage, read_write> vout : array<f32>; |
| struct U { wOff:u32, colBase:u32, normOff:u32, biasOff:u32, }; |
| @group(0) @binding(5) var<uniform> u : U; |
| |
| var<workgroup> inS : array<f32, ${K}>; |
| var<workgroup> part : array<f32, ${WG_SIZE}>; |
| |
| @compute @workgroup_size(${WG_SIZE}) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>, |
| @builtin(workgroup_id) wid : vec3<u32>) { |
| let tid = lid.x; |
| let row = tid / LANES; |
| let lane = tid % LANES; |
| for (var i : u32 = tid; i < ${K}u; i = i + ${WG_SIZE}u) { inS[i] = vin[i]; } |
| workgroupBarrier(); |
| let outIdx = wid.x * TILE_ROWS + row; |
| let col = u.colBase + outIdx; |
| let wbase = u.wOff + col * ${K}u; |
| var acc : f32 = 0.0; |
| for (var d : u32 = lane; d < ${K}u; d = d + LANES) { |
| acc = acc + inS[d] * wv(wbase + d); |
| } |
| part[tid] = acc; |
| workgroupBarrier(); |
| if (lane == 0u) { |
| var s : f32 = part[row * LANES]; |
| for (var l : u32 = 1u; l < LANES; l = l + 1u) { s = s + part[row * LANES + l]; } |
| vout[outIdx] = s; |
| } |
| } |
| `; |
| } |
| |
| |
| |
| |
| function buildQKV(HEADER) { |
| const sg = USE_SUBGROUPS; |
| const enableSg = sg ? "enable subgroups;\n" : ""; |
| const reduceWrite = sg |
| ? ` |
| let sq = subgroupAdd(qa); |
| let sk = subgroupAdd(ka); |
| let sv = subgroupAdd(va); |
| if (lane == 0u) { |
| qbuf[oi] = sq; |
| let cbase = u.slot * HD; |
| kcache[cbase + oi] = sk; |
| vcache[cbase + oi] = sv; |
| }` |
| : ` |
| pq[tid] = qa; pk[tid] = ka; pv[tid] = va; |
| workgroupBarrier(); |
| for (var s : u32 = ${RED / 2}u; s > 0u; s = s >> 1u) { |
| if (lane < s) { |
| pq[tid] = pq[tid] + pq[tid + s]; |
| pk[tid] = pk[tid] + pk[tid + s]; |
| pv[tid] = pv[tid] + pv[tid + s]; |
| } |
| workgroupBarrier(); |
| } |
| if (lane == 0u) { |
| qbuf[oi] = pq[row * RED]; |
| let cbase = u.slot * HD; |
| kcache[cbase + oi] = pk[row * RED]; |
| vcache[cbase + oi] = pv[row * RED]; |
| }`; |
| return enableSg + HEADER + ` |
| @group(0) @binding(1) var<storage, read> vin : array<f32>; // x768 residual |
| @group(0) @binding(2) var<storage, read_write> qbuf : array<f32>; // [768] |
| @group(0) @binding(3) var<storage, read_write> kcache : array<f32>; // [T,768] |
| @group(0) @binding(4) var<storage, read_write> vcache : array<f32>; // [T,768] |
| @group(0) @binding(7) var<storage, read> WT : array<f32>; |
| struct U { normOff:u32, Wq:u32, Wk:u32, Wv:u32, slot:u32, }; |
| @group(0) @binding(5) var<uniform> u : U; |
| |
| var<workgroup> inS : array<f32, ${D_MODEL}>; |
| var<workgroup> red : array<f32, ${T_WG}>; |
| ${sg ? "" : `var<workgroup> pq : array<f32, ${T_WG}>; |
| var<workgroup> pk : array<f32, ${T_WG}>; |
| var<workgroup> pv : array<f32, ${T_WG}>;`} |
| |
| @compute @workgroup_size(${T_WG}) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>, |
| @builtin(workgroup_id) wid : vec3<u32>) { |
| let tid = lid.x; |
| let row = tid / RED; |
| let lane = tid % RED; |
| // fold attn-pre RMSNorm over D_MODEL |
| var lss : f32 = 0.0; |
| for (var i : u32 = tid; i < D_MODEL; i = i + ${T_WG}u) { |
| let xv = vin[i]; lss = lss + xv * xv; |
| } |
| red[tid] = lss; |
| workgroupBarrier(); |
| for (var s : u32 = ${T_WG / 2}u; s > 0u; s = s >> 1u) { |
| if (tid < s) { red[tid] = red[tid] + red[tid + s]; } |
| workgroupBarrier(); |
| } |
| let rms = 1.0 / sqrt(red[0] / f32(D_MODEL) + EPS); |
| workgroupBarrier(); |
| for (var i : u32 = tid; i < D_MODEL; i = i + ${T_WG}u) { |
| inS[i] = (vin[i] * rms) * wv(u.normOff + i); |
| } |
| workgroupBarrier(); |
| |
| let oi = wid.x * ROWS + row; // output column in [0,768) |
| let qb = u.Wq + oi * HD; |
| let kb = u.Wk + oi * HD; |
| let vb = u.Wv + oi * HD; |
| var qa : f32 = 0.0; var ka : f32 = 0.0; var va : f32 = 0.0; |
| for (var k : u32 = lane; k < D_MODEL; k = k + RED) { |
| let hv = inS[k]; |
| qa = qa + hv * WT[qb + k]; |
| ka = ka + hv * WT[kb + k]; |
| va = va + hv * WT[vb + k]; |
| } |
| ${reduceWrite} |
| } |
| `; |
| } |
| |
| |
| function buildAttnCore(HEADER) { |
| return HEADER + ` |
| @group(0) @binding(1) var<storage, read> qbuf : array<f32>; // [768] |
| @group(0) @binding(2) var<storage, read> kcache : array<f32>; // [T,768] |
| @group(0) @binding(3) var<storage, read> vcache : array<f32>; // [T,768] |
| @group(0) @binding(4) var<storage, read_write> ctxbuf : array<f32>;// [768] |
| struct U { per_dim_scale:u32, cacheLen:u32, }; |
| @group(0) @binding(5) var<uniform> u : U; |
| |
| var<workgroup> logitsW : array<f32, ${NUM_HEADS * NUM_CODEBOOKS}>; |
| var<workgroup> red : array<f32, 2>; |
| |
| @compute @workgroup_size(256) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>) { |
| let t = lid.x; |
| let T = u.cacheLen; |
| let qscale = R_SOFTPLUS_0 * (1.0 / sqrt(f32(UPH))); |
| for (var h : u32 = 0u; h < NUM_HEADS; h = h + 1u) { |
| for (var k : u32 = t; k < T; k = k + 256u) { |
| var acc : f32 = 0.0; |
| let kbase = k * HD + h * UPH; |
| let qbase = h * UPH; |
| for (var d : u32 = 0u; d < UPH; d = d + 1u) { |
| let sv = qscale * softplus(wv(u.per_dim_scale + d)); |
| acc = acc + (qbuf[qbase + d] * sv) * kcache[kbase + d]; |
| } |
| logitsW[h * T + k] = acc; |
| } |
| } |
| workgroupBarrier(); |
| for (var h : u32 = 0u; h < NUM_HEADS; h = h + 1u) { |
| if (t == 0u) { |
| var m : f32 = logitsW[h * T + 0u]; |
| for (var k : u32 = 1u; k < T; k = k + 1u) { m = max(m, logitsW[h * T + k]); } |
| red[0] = m; |
| } |
| workgroupBarrier(); |
| let m = red[0]; |
| if (t == 0u) { |
| var s : f32 = 0.0; |
| for (var k : u32 = 0u; k < T; k = k + 1u) { |
| let e = exp(logitsW[h * T + k] - m); |
| logitsW[h * T + k] = e; s = s + e; |
| } |
| red[1] = s; |
| } |
| workgroupBarrier(); |
| let denom = red[1]; |
| for (var d : u32 = t; d < UPH; d = d + 256u) { |
| var acc : f32 = 0.0; |
| for (var k : u32 = 0u; k < T; k = k + 1u) { |
| let w = logitsW[h * T + k] / denom; |
| acc = acc + w * vcache[k * HD + h * UPH + d]; |
| } |
| ctxbuf[h * UPH + d] = acc; |
| } |
| workgroupBarrier(); |
| } |
| } |
| `; |
| } |
| |
| |
| function buildResid(HEADER) { |
| return HEADER + ` |
| @group(0) @binding(1) var<storage, read_write> x : array<f32>; // [768] residual |
| @group(0) @binding(2) var<storage, read> y : array<f32>; // [768] block out |
| struct U { post:u32, }; |
| @group(0) @binding(5) var<uniform> u : U; |
| |
| var<workgroup> red : array<f32, 256>; |
| @compute @workgroup_size(256) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>) { |
| let t = lid.x; |
| var ss : f32 = 0.0; |
| for (var i : u32 = t; i < D_MODEL; i = i + 256u) { let v = y[i]; ss = ss + v*v; } |
| red[t] = ss; |
| workgroupBarrier(); |
| for (var s : u32 = 128u; s > 0u; s = s >> 1u) { |
| if (t < s) { red[t] = red[t] + red[t + s]; } |
| workgroupBarrier(); |
| } |
| let rms = 1.0 / sqrt(red[0] / f32(D_MODEL) + EPS); |
| workgroupBarrier(); |
| for (var i : u32 = t; i < D_MODEL; i = i + 256u) { |
| x[i] = x[i] + (y[i] * rms) * wv(u.post + i); |
| } |
| } |
| `; |
| } |
| |
| |
| function buildFinalLN(HEADER) { |
| return HEADER + ` |
| @group(0) @binding(1) var<storage, read> x : array<f32>; // [768] |
| @group(0) @binding(2) var<storage, read_write> xn : array<f32>; // [768] |
| struct U { final_scale:u32, final_bias:u32, }; |
| @group(0) @binding(5) var<uniform> u : U; |
| |
| var<workgroup> red : array<f32, 256>; |
| @compute @workgroup_size(256) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>) { |
| let t = lid.x; |
| var sm : f32 = 0.0; |
| for (var i : u32 = t; i < D_MODEL; i = i + 256u) { sm = sm + x[i]; } |
| red[t] = sm; |
| workgroupBarrier(); |
| for (var s : u32 = 128u; s > 0u; s = s >> 1u) { |
| if (t < s) { red[t] = red[t] + red[t + s]; } |
| workgroupBarrier(); |
| } |
| let mean = red[0] / f32(D_MODEL); |
| workgroupBarrier(); |
| var vs : f32 = 0.0; |
| for (var i : u32 = t; i < D_MODEL; i = i + 256u) { let dd = x[i] - mean; vs = vs + dd*dd; } |
| red[t] = vs; |
| workgroupBarrier(); |
| for (var s : u32 = 128u; s > 0u; s = s >> 1u) { |
| if (t < s) { red[t] = red[t] + red[t + s]; } |
| workgroupBarrier(); |
| } |
| let rstd = 1.0 / sqrt(red[0] / f32(D_MODEL) + EPS); |
| workgroupBarrier(); |
| for (var i : u32 = t; i < D_MODEL; i = i + 256u) { |
| xn[i] = ((x[i] - mean) * rstd) * wv(u.final_scale + i) + wv(u.final_bias + i); |
| } |
| } |
| `; |
| } |
| |
| |
| |
| |
| function buildSample() { |
| return CONSTS + ` |
| @group(0) @binding(1) var<storage, read> logits1024 : array<f32>; // [1024] |
| @group(0) @binding(2) var<storage, read> noise : array<f32>; // [Q*VOCAB] |
| @group(0) @binding(3) var<storage, read_write> outTok : array<u32>;// [NUM_CODEBOOKS] |
| struct U { lo:u32, temperature:f32, slot:u32, noiseBase:u32, }; |
| @group(0) @binding(5) var<uniform> u : U; |
| |
| var<workgroup> bestScore : array<f32, 256>; |
| var<workgroup> bestIdx : array<u32, 256>; |
| @compute @workgroup_size(256) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>) { |
| let t = lid.x; |
| let lo = u.lo; |
| var bScore : f32 = -3.0e38; |
| var bIdx : u32 = 0xffffffffu; |
| for (var j : u32 = t; j < CODEBOOK_SIZE; j = j + 256u) { |
| var score : f32 = logits1024[j]; // already soft-capped, in-slice |
| let gidx = lo + j; // global token index |
| if (u.temperature > 0.0) { |
| var un = noise[u.noiseBase + gidx]; |
| un = clamp(un, 1.0e-10, 1.0 - 1.0e-7); |
| let g = -log(-log(un)); |
| score = score + g * u.temperature; |
| } |
| if (score > bScore || (score == bScore && gidx < bIdx)) { |
| bScore = score; bIdx = gidx; |
| } |
| } |
| bestScore[t] = bScore; bestIdx[t] = bIdx; |
| workgroupBarrier(); |
| for (var s : u32 = 128u; s > 0u; s = s >> 1u) { |
| if (t < s) { |
| let a = bestScore[t]; let ai = bestIdx[t]; |
| let b = bestScore[t + s]; let bi = bestIdx[t + s]; |
| if (b > a || (b == a && bi < ai)) { bestScore[t] = b; bestIdx[t] = bi; } |
| } |
| workgroupBarrier(); |
| } |
| // Write sampled token into this step's slot of the GPU tokens buffer. |
| // No CPU readback mid-loop: the next step's embed kernel reads outTok[slot]. |
| if (t == 0u) { outTok[u.slot] = bestIdx[0]; } |
| } |
| `; |
| } |
| |
| |
| function buildEmbed(HEADER) { |
| return HEADER + ` |
| @group(0) @binding(1) var<storage, read> tokBuf : array<u32>; // [NUM_CODEBOOKS] |
| @group(0) @binding(2) var<storage, read_write> xout : array<f32>; // [1024] |
| struct EU { embeddingOff : u32, embed_scale : f32, slot : u32 }; |
| @group(0) @binding(3) var<uniform> u : EU; |
| |
| @compute @workgroup_size(256) |
| fn main(@builtin(local_invocation_id) lid : vec3<u32>) { |
| let t = lid.x; |
| // Gather row of the token sampled at the PREVIOUS step (slot), GPU-side. |
| let tok = tokBuf[u.slot]; |
| let base = u.embeddingOff + tok * D_IN; |
| for (var i : u32 = t; i < D_IN; i = i + 256u) { |
| xout[i] = wv(base + i) * u.embed_scale; |
| } |
| } |
| `; |
| } |
| |
| |
| async function main() { |
| if (!navigator.gpu) { |
| throw new Error("navigator.gpu unavailable — this browser/origin has no WebGPU."); |
| } |
| |
| |
| setStatus("downloading weights + test data (depth.bin is 148MB; cached after first run)…", "running"); |
| |
| const [layoutResp, testsResp, binResp] = await Promise.all([ |
| cachedFetch(BASE + "depth_layout.json"), |
| cachedFetch(BASE + "testcases.json"), |
| cachedFetch(BASE + "depth.bin", (recv, total) => { |
| const pct = total ? " (" + (100 * recv / total).toFixed(0) + "%)" : ""; |
| setStatus("downloading depth.bin: " + fmtBytes(recv) + |
| (total ? " / " + fmtBytes(total) : "") + pct + "…", "running"); |
| }), |
| ]); |
| |
| const layout = await layoutResp.json(); |
| const tests = await testsResp.json(); |
| const binBytes = new Uint8Array(await binResp.arrayBuffer()); |
| const EMBED_SCALE = layout.embed_scale; |
| const TOTAL_FLOATS = layout.total_floats; |
| |
| const W_SPLIT = Math.ceil(TOTAL_FLOATS / 2); |
| |
| function off(name) { return layout.tensors[name].offset; } |
| function layerOffsets(li) { |
| const p = "l" + li + "_"; |
| return { |
| attn_pre: off(p + "attn_pre"), attn_post: off(p + "attn_post"), |
| Wq: off(p + "Wq"), Wk: off(p + "Wk"), Wv: off(p + "Wv"), |
| per_dim_scale: off(p + "per_dim_scale"), Wo: off(p + "Wo"), |
| ffn_pre: off(p + "ffn_pre"), ffn_post: off(p + "ffn_post"), |
| Wi: off(p + "Wi"), bi: off(p + "bi"), |
| Wo_ffn: off(p + "Wo_ffn"), bo: off(p + "bo"), |
| }; |
| } |
| |
| |
| setStatus("initializing WebGPU device and uploading weights…", "running"); |
| |
| const adapter = await navigator.gpu.requestAdapter({ powerPreference: "high-performance" }); |
| if (!adapter) throw new Error("no WebGPU adapter"); |
| |
| |
| let adapterInfo = { vendor: "", architecture: "", device: "", description: "" }; |
| try { |
| if (adapter.info) adapterInfo = adapter.info; |
| else if (adapter.requestAdapterInfo) adapterInfo = await adapter.requestAdapterInfo(); |
| } catch (e) {} |
| |
| |
| |
| |
| const subgroupsOk = adapter.features.has("subgroups") && |
| adapter.limits.minSubgroupSize === RED && |
| adapter.limits.maxSubgroupSize === RED; |
| USE_SUBGROUPS = subgroupsOk; |
| |
| const device = await adapter.requestDevice({ |
| requiredFeatures: subgroupsOk ? ["subgroups"] : [], |
| requiredLimits: { |
| maxStorageBufferBindingSize: Math.min( |
| Math.max(256 * 1024 * 1024, binBytes.byteLength), |
| adapter.limits.maxStorageBufferBindingSize), |
| maxBufferSize: Math.min( |
| Math.max(256 * 1024 * 1024, binBytes.byteLength), |
| adapter.limits.maxBufferSize), |
| maxComputeWorkgroupStorageSize: Math.min( |
| 32768, adapter.limits.maxComputeWorkgroupStorageSize), |
| }, |
| }); |
| let deviceLost = null; |
| device.lost.then((info) => { deviceLost = info; }); |
| console.log("subgroups: " + USE_SUBGROUPS + " (RED=" + RED + " ROWS=" + ROWS + " T_WG=" + T_WG + ")"); |
| |
| function makeStorage(floats, usageExtra = 0) { |
| return device.createBuffer({ |
| size: Math.max(4, floats * 4), |
| usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | |
| GPUBufferUsage.COPY_SRC | usageExtra, |
| }); |
| } |
| |
| |
| |
| |
| |
| const W0_BYTES = W_SPLIT * 4; |
| const W1_BYTES = binBytes.byteLength - W0_BYTES; |
| const w0Buf = device.createBuffer({ |
| size: W0_BYTES, |
| usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, |
| }); |
| const w1Buf = device.createBuffer({ |
| size: W1_BYTES, |
| usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, |
| }); |
| device.queue.writeBuffer(w0Buf, 0, binBytes, 0, W0_BYTES); |
| device.queue.writeBuffer(w1Buf, 0, binBytes, W0_BYTES, W1_BYTES); |
| |
| |
| |
| |
| |
| |
| const binF32 = new Float32Array( |
| binBytes.buffer, binBytes.byteOffset, binBytes.byteLength / 4); |
| const WT_TENSORS = [{ src: "adapter", K: D_IN, N: D_MODEL }]; |
| for (let li = 0; li < NUM_LAYERS; li++) { |
| const p = "l" + li + "_"; |
| WT_TENSORS.push( |
| { src: p + "Wq", K: D_MODEL, N: HD }, |
| { src: p + "Wk", K: D_MODEL, N: HD }, |
| { src: p + "Wv", K: D_MODEL, N: HD }, |
| { src: p + "Wi", K: D_MODEL, N: D_FF }, |
| { src: p + "Wo_ffn", K: D_FF, N: D_MODEL }); |
| } |
| WT_TENSORS.push({ src: "to_logits_w", K: D_MODEL, N: VOCAB }); |
| const wtOffset = {}; |
| let wtFloats = 0; |
| for (const t of WT_TENSORS) { wtOffset[t.src] = wtFloats; wtFloats += t.K * t.N; } |
| const wtOff = (name) => wtOffset[name]; |
| const wtHost = new Float32Array(wtFloats); |
| for (const t of WT_TENSORS) { |
| const srcOff = off(t.src), dstOff = wtOffset[t.src], K = t.K, N = t.N; |
| for (let k = 0; k < K; k++) { |
| const srcRow = srcOff + k * N; |
| for (let n = 0; n < N; n++) wtHost[dstOff + n * K + k] = binF32[srcRow + n]; |
| } |
| } |
| const wtBuf = device.createBuffer({ |
| size: wtHost.byteLength, |
| usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, |
| }); |
| device.queue.writeBuffer(wtBuf, 0, wtHost); |
| |
| |
| const xinBuf = makeStorage(D_IN); |
| const x768Buf = makeStorage(D_MODEL); |
| const qBuf = makeStorage(HD); |
| const ctxBuf = makeStorage(HD); |
| const attnOutBuf = makeStorage(D_MODEL); |
| const ffBuf = makeStorage(D_FF); |
| const ffnOutBuf = makeStorage(D_MODEL); |
| const xnBuf = makeStorage(D_MODEL); |
| const logits1024Buf = makeStorage(CODEBOOK_SIZE); |
| const kBufs = [makeStorage(NUM_CODEBOOKS * D_MODEL), makeStorage(NUM_CODEBOOKS * D_MODEL)]; |
| const vBufs = [makeStorage(NUM_CODEBOOKS * D_MODEL), makeStorage(NUM_CODEBOOKS * D_MODEL)]; |
| |
| |
| |
| const tokBuf = device.createBuffer({ |
| size: NUM_CODEBOOKS * 4, |
| usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, |
| }); |
| const tokReadBuf = device.createBuffer({ |
| size: NUM_CODEBOOKS * 4, |
| usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, |
| }); |
| |
| const noiseBuf = makeStorage(NUM_CODEBOOKS * VOCAB); |
| |
| function makeUniform(bytes) { |
| return device.createBuffer({ |
| size: bytes, |
| usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, |
| }); |
| } |
| |
| function pipeline(code) { |
| const module = device.createShaderModule({ code }); |
| return device.createComputePipeline({ |
| layout: "auto", |
| compute: { module, entryPoint: "main" }, |
| }); |
| } |
| |
| |
| const { HEADER } = buildHeaders(W_SPLIT); |
| |
| |
| const pAdapter = pipeline(gridMatmulT(HEADER, { |
| K: D_IN, inBinding: 1, outBinding: 2, |
| rmsnorm: false, epilogue: "none", hasBias: false, |
| })); |
| const pQKV = pipeline(buildQKV(HEADER)); |
| const pAttnCore = pipeline(buildAttnCore(HEADER)); |
| const pWo = pipeline(gridMatmul2(HEADER, { K: HD, inBinding: 1, outBinding: 2 })); |
| const pResid = pipeline(buildResid(HEADER)); |
| const pWi = pipeline(gridMatmulT(HEADER, { |
| K: D_MODEL, inBinding: 1, outBinding: 2, |
| rmsnorm: true, epilogue: "geluBias", hasBias: true, |
| })); |
| const pWoFfn = pipeline(gridMatmulT(HEADER, { |
| K: D_FF, inBinding: 1, outBinding: 2, |
| rmsnorm: false, epilogue: "none", hasBias: true, |
| })); |
| const pFinalLN = pipeline(buildFinalLN(HEADER)); |
| const pToLogits = pipeline(gridMatmulT(HEADER, { |
| K: D_MODEL, inBinding: 1, outBinding: 2, |
| rmsnorm: false, epilogue: "softcap", hasBias: true, |
| })); |
| const pSample = pipeline(buildSample()); |
| const pEmbed = pipeline(buildEmbed(HEADER)); |
| |
| |
| |
| |
| |
| const uAdapter = makeUniform(16); |
| const uWo = [makeUniform(16), makeUniform(16)]; |
| const uWi = [makeUniform(16), makeUniform(16)]; |
| const uWoFfn = [makeUniform(16), makeUniform(16)]; |
| |
| const uToLogits = Array.from({ length: NUM_CODEBOOKS }, () => makeUniform(16)); |
| |
| const uQKV = Array.from({ length: NUM_CODEBOOKS }, |
| () => [makeUniform(32), makeUniform(32)]); |
| |
| const uAttnCore = Array.from({ length: NUM_CODEBOOKS }, |
| () => [makeUniform(16), makeUniform(16)]); |
| const uResidAttn = [makeUniform(16), makeUniform(16)]; |
| const uResidFfn = [makeUniform(16), makeUniform(16)]; |
| const uFinalLN = makeUniform(16); |
| |
| const uSample = Array.from({ length: NUM_CODEBOOKS }, () => makeUniform(16)); |
| |
| const uEmbed = Array.from({ length: NUM_CODEBOOKS }, () => makeUniform(16)); |
| |
| |
| |
| { |
| const a = new Uint32Array(4); |
| a[0] = wtOff("adapter"); |
| a[1] = 0; |
| device.queue.writeBuffer(uAdapter, 0, a); |
| } |
| for (let li = 0; li < NUM_LAYERS; li++) { |
| const L = layerOffsets(li); |
| const p = "l" + li + "_"; |
| |
| |
| for (let q = 0; q < NUM_CODEBOOKS; q++) { |
| const a = new Uint32Array(8); |
| a[0] = L.attn_pre; a[1] = wtOff(p + "Wq"); a[2] = wtOff(p + "Wk"); a[3] = wtOff(p + "Wv"); |
| a[4] = q; |
| device.queue.writeBuffer(uQKV[q][li], 0, a); |
| const c = new Uint32Array(4); |
| c[0] = L.per_dim_scale; |
| c[1] = q + 1; |
| device.queue.writeBuffer(uAttnCore[q][li], 0, c); |
| } |
| { |
| const a = new Uint32Array(4); |
| a[0] = L.attn_post; |
| device.queue.writeBuffer(uResidAttn[li], 0, a); |
| } |
| { |
| const a = new Uint32Array(4); |
| a[0] = L.Wo; a[1] = 0; |
| device.queue.writeBuffer(uWo[li], 0, a); |
| } |
| { |
| const a = new Uint32Array(4); |
| a[0] = wtOff(p + "Wi"); a[1] = 0; a[2] = L.ffn_pre; a[3] = L.bi; |
| device.queue.writeBuffer(uWi[li], 0, a); |
| } |
| { |
| const a = new Uint32Array(4); |
| a[0] = wtOff(p + "Wo_ffn"); a[1] = 0; a[2] = 0; a[3] = L.bo; |
| device.queue.writeBuffer(uWoFfn[li], 0, a); |
| } |
| { |
| const a = new Uint32Array(4); |
| a[0] = L.ffn_post; |
| device.queue.writeBuffer(uResidFfn[li], 0, a); |
| } |
| } |
| { |
| const a = new Uint32Array(4); |
| a[0] = off("final_scale"); |
| a[1] = off("final_bias"); |
| device.queue.writeBuffer(uFinalLN, 0, a); |
| } |
| |
| |
| for (let q = 0; q < NUM_CODEBOOKS; q++) { |
| const e = new ArrayBuffer(16); |
| new Uint32Array(e, 0, 1)[0] = off("embedding"); |
| new Float32Array(e, 4, 1)[0] = EMBED_SCALE; |
| new Uint32Array(e, 8, 1)[0] = q; |
| device.queue.writeBuffer(uEmbed[q], 0, e); |
| } |
| |
| for (let q = 0; q < NUM_CODEBOOKS; q++) { |
| const lo = NUM_RESERVED + q * CODEBOOK_SIZE; |
| const a = new Uint32Array(4); |
| a[0] = wtOff("to_logits_w"); |
| a[1] = lo; |
| a[2] = 0; |
| a[3] = off("to_logits_b"); |
| device.queue.writeBuffer(uToLogits[q], 0, a); |
| } |
| |
| |
| |
| |
| |
| function bgMatmul1(p, inBuf, outBuf, uBuf, usesWv = true) { |
| const entries = [ |
| { binding: 7, resource: { buffer: wtBuf } }, |
| { binding: 1, resource: { buffer: inBuf } }, |
| { binding: 2, resource: { buffer: outBuf } }, |
| { binding: 5, resource: { buffer: uBuf } }, |
| ]; |
| if (usesWv) { |
| entries.push( |
| { binding: 0, resource: { buffer: w0Buf } }, |
| { binding: 6, resource: { buffer: w1Buf } }); |
| } |
| return device.createBindGroup({ layout: p.getBindGroupLayout(0), entries }); |
| } |
| const bgAdapter = bgMatmul1(pAdapter, xinBuf, x768Buf, uAdapter, false); |
| const bgWo = [0, 1].map((li) => device.createBindGroup({ |
| layout: pWo.getBindGroupLayout(0), |
| entries: [ |
| { binding: 0, resource: { buffer: w0Buf } }, |
| { binding: 6, resource: { buffer: w1Buf } }, |
| { binding: 1, resource: { buffer: ctxBuf } }, |
| { binding: 2, resource: { buffer: attnOutBuf } }, |
| { binding: 5, resource: { buffer: uWo[li] } }, |
| ], |
| })); |
| |
| |
| const bgQKV = Array.from({ length: NUM_CODEBOOKS }, (_, q) => |
| [0, 1].map((li) => device.createBindGroup({ |
| layout: pQKV.getBindGroupLayout(0), |
| entries: [ |
| { binding: 0, resource: { buffer: w0Buf } }, |
| { binding: 6, resource: { buffer: w1Buf } }, |
| { binding: 7, resource: { buffer: wtBuf } }, |
| { binding: 1, resource: { buffer: x768Buf } }, |
| { binding: 2, resource: { buffer: qBuf } }, |
| { binding: 3, resource: { buffer: kBufs[li] } }, |
| { binding: 4, resource: { buffer: vBufs[li] } }, |
| { binding: 5, resource: { buffer: uQKV[q][li] } }, |
| ], |
| }))); |
| const bgAttnCore = Array.from({ length: NUM_CODEBOOKS }, (_, q) => |
| [0, 1].map((li) => device.createBindGroup({ |
| layout: pAttnCore.getBindGroupLayout(0), |
| entries: [ |
| { binding: 0, resource: { buffer: w0Buf } }, |
| { binding: 6, resource: { buffer: w1Buf } }, |
| { binding: 1, resource: { buffer: qBuf } }, |
| { binding: 2, resource: { buffer: kBufs[li] } }, |
| { binding: 3, resource: { buffer: vBufs[li] } }, |
| { binding: 4, resource: { buffer: ctxBuf } }, |
| { binding: 5, resource: { buffer: uAttnCore[q][li] } }, |
| ], |
| }))); |
| const bgResidAttn = [0, 1].map((li) => device.createBindGroup({ |
| layout: pResid.getBindGroupLayout(0), |
| entries: [ |
| { binding: 0, resource: { buffer: w0Buf } }, |
| { binding: 6, resource: { buffer: w1Buf } }, |
| { binding: 1, resource: { buffer: x768Buf } }, |
| { binding: 2, resource: { buffer: attnOutBuf } }, |
| { binding: 5, resource: { buffer: uResidAttn[li] } }, |
| ], |
| })); |
| const bgWi = [0, 1].map((li) => bgMatmul1(pWi, x768Buf, ffBuf, uWi[li])); |
| const bgWoFfn = [0, 1].map((li) => bgMatmul1(pWoFfn, ffBuf, ffnOutBuf, uWoFfn[li])); |
| const bgResidFfn = [0, 1].map((li) => device.createBindGroup({ |
| layout: pResid.getBindGroupLayout(0), |
| entries: [ |
| { binding: 0, resource: { buffer: w0Buf } }, |
| { binding: 6, resource: { buffer: w1Buf } }, |
| { binding: 1, resource: { buffer: x768Buf } }, |
| { binding: 2, resource: { buffer: ffnOutBuf } }, |
| { binding: 5, resource: { buffer: uResidFfn[li] } }, |
| ], |
| })); |
| const bgFinalLN = device.createBindGroup({ |
| layout: pFinalLN.getBindGroupLayout(0), |
| entries: [ |
| { binding: 0, resource: { buffer: w0Buf } }, |
| { binding: 6, resource: { buffer: w1Buf } }, |
| { binding: 1, resource: { buffer: x768Buf } }, |
| { binding: 2, resource: { buffer: xnBuf } }, |
| { binding: 5, resource: { buffer: uFinalLN } }, |
| ], |
| }); |
| |
| const bgToLogits = Array.from({ length: NUM_CODEBOOKS }, |
| (_, q) => bgMatmul1(pToLogits, xnBuf, logits1024Buf, uToLogits[q])); |
| const bgSample = Array.from({ length: NUM_CODEBOOKS }, (_, q) => |
| device.createBindGroup({ |
| layout: pSample.getBindGroupLayout(0), |
| entries: [ |
| { binding: 1, resource: { buffer: logits1024Buf } }, |
| { binding: 2, resource: { buffer: noiseBuf } }, |
| { binding: 3, resource: { buffer: tokBuf } }, |
| { binding: 5, resource: { buffer: uSample[q] } }, |
| ], |
| })); |
| const bgEmbed = Array.from({ length: NUM_CODEBOOKS }, (_, q) => |
| device.createBindGroup({ |
| layout: pEmbed.getBindGroupLayout(0), |
| entries: [ |
| { binding: 0, resource: { buffer: w0Buf } }, |
| { binding: 6, resource: { buffer: w1Buf } }, |
| { binding: 1, resource: { buffer: tokBuf } }, |
| { binding: 2, resource: { buffer: xinBuf } }, |
| { binding: 3, resource: { buffer: uEmbed[q] } }, |
| ], |
| })); |
| |
| |
| const WG_ADAPTER = nWGT(D_MODEL); |
| const WG_QKV = nWGT(HD); |
| const WG_WO = nWG(D_MODEL); |
| const WG_WI = nWGT(D_FF); |
| const WG_WOFFN = nWGT(D_MODEL); |
| const WG_TOLOGITS = nWGT(CODEBOOK_SIZE); |
| |
| |
| async function runDepth(temporalOut, noiseFlat, temperature) { |
| |
| device.queue.writeBuffer(xinBuf, 0, temporalOut); |
| |
| |
| device.queue.writeBuffer(noiseBuf, 0, noiseFlat); |
| |
| for (let q = 0; q < NUM_CODEBOOKS; q++) { |
| const lo = NUM_RESERVED + q * CODEBOOK_SIZE; |
| const buf = new ArrayBuffer(16); |
| new Uint32Array(buf, 0, 1)[0] = lo; |
| new Float32Array(buf, 4, 1)[0] = temperature; |
| new Uint32Array(buf, 8, 1)[0] = q; |
| new Uint32Array(buf, 12, 1)[0] = q * VOCAB; |
| device.queue.writeBuffer(uSample[q], 0, buf); |
| } |
| |
| |
| |
| |
| |
| for (let q = 0; q < NUM_CODEBOOKS; q++) { |
| const enc = device.createCommandEncoder(); |
| const pass = enc.beginComputePass(); |
| |
| pass.setPipeline(pAdapter); |
| pass.setBindGroup(0, bgAdapter); |
| pass.dispatchWorkgroups(WG_ADAPTER); |
| |
| for (let li = 0; li < NUM_LAYERS; li++) { |
| pass.setPipeline(pQKV); |
| pass.setBindGroup(0, bgQKV[q][li]); |
| pass.dispatchWorkgroups(WG_QKV); |
| |
| pass.setPipeline(pAttnCore); |
| pass.setBindGroup(0, bgAttnCore[q][li]); |
| pass.dispatchWorkgroups(1); |
| |
| pass.setPipeline(pWo); |
| pass.setBindGroup(0, bgWo[li]); |
| pass.dispatchWorkgroups(WG_WO); |
| |
| pass.setPipeline(pResid); |
| pass.setBindGroup(0, bgResidAttn[li]); |
| pass.dispatchWorkgroups(1); |
| |
| pass.setPipeline(pWi); |
| pass.setBindGroup(0, bgWi[li]); |
| pass.dispatchWorkgroups(WG_WI); |
| |
| pass.setPipeline(pWoFfn); |
| pass.setBindGroup(0, bgWoFfn[li]); |
| pass.dispatchWorkgroups(WG_WOFFN); |
| |
| pass.setPipeline(pResid); |
| pass.setBindGroup(0, bgResidFfn[li]); |
| pass.dispatchWorkgroups(1); |
| } |
| |
| pass.setPipeline(pFinalLN); |
| pass.setBindGroup(0, bgFinalLN); |
| pass.dispatchWorkgroups(1); |
| |
| pass.setPipeline(pToLogits); |
| pass.setBindGroup(0, bgToLogits[q]); |
| pass.dispatchWorkgroups(WG_TOLOGITS); |
| |
| pass.setPipeline(pSample); |
| pass.setBindGroup(0, bgSample[q]); |
| pass.dispatchWorkgroups(1); |
| |
| if (q < NUM_CODEBOOKS - 1) { |
| pass.setPipeline(pEmbed); |
| pass.setBindGroup(0, bgEmbed[q]); |
| pass.dispatchWorkgroups(1); |
| } |
| pass.end(); |
| device.queue.submit([enc.finish()]); |
| } |
| |
| |
| const enc2 = device.createCommandEncoder(); |
| enc2.copyBufferToBuffer(tokBuf, 0, tokReadBuf, 0, NUM_CODEBOOKS * 4); |
| device.queue.submit([enc2.finish()]); |
| await tokReadBuf.mapAsync(GPUMapMode.READ); |
| const toks = new Uint32Array(tokReadBuf.getMappedRange().slice(0)); |
| tokReadBuf.unmap(); |
| |
| const codes = []; |
| for (let q = 0; q < NUM_CODEBOOKS; q++) { |
| const tok = toks[q]; |
| const code = ((Number(tok) - NUM_RESERVED) % CODEBOOK_SIZE + CODEBOOK_SIZE) % CODEBOOK_SIZE; |
| codes.push(code); |
| } |
| return codes; |
| } |
| |
| |
| setStatus("running correctness over testcases…", "running"); |
| const allCases = tests.cases; |
| const caseReports = []; |
| let allMatch = true; |
| let dispatchesPerDepth = 0; |
| |
| for (let ci = 0; ci < allCases.length; ci++) { |
| const c = allCases[ci]; |
| DISPATCH_COUNT = 0; |
| const gpuCodes = await runDepth( |
| new Float32Array(c.temporal_out), |
| new Float32Array(c.noise), |
| c.temperature, |
| ); |
| const ref = c.ref_codes; |
| const match = gpuCodes.length === ref.length && gpuCodes.every((v, i) => v === ref[i]); |
| allMatch = allMatch && match; |
| if (ci === 0) dispatchesPerDepth = DISPATCH_COUNT; |
| caseReports.push({ ci, gpuCodes, ref, match }); |
| console.log("case " + ci + ": GPU=[" + gpuCodes.join(",") + |
| "] REF=[" + ref.join(",") + "] MATCH=" + match); |
| } |
| if (deviceLost) throw new Error("device lost: " + deviceLost.message); |
| |
| |
| setStatus("timing depth (warmup + 50 runs)…", "running"); |
| const c0 = allCases[0]; |
| const t_temporal = new Float32Array(c0.temporal_out); |
| const t_noise = new Float32Array(c0.noise); |
| const t_temp = c0.temperature; |
| |
| const WARMUP = 5; |
| const RUNS = 50; |
| for (let i = 0; i < WARMUP; i++) { |
| await runDepth(t_temporal, t_noise, t_temp); |
| await device.queue.onSubmittedWorkDone(); |
| } |
| const times = []; |
| for (let i = 0; i < RUNS; i++) { |
| const t0 = performance.now(); |
| await runDepth(t_temporal, t_noise, t_temp); |
| await device.queue.onSubmittedWorkDone(); |
| const t1 = performance.now(); |
| times.push(t1 - t0); |
| if (i % 10 === 0) { |
| setStatus("timing depth (run " + (i + 1) + "/" + RUNS + ")…", "running"); |
| } |
| } |
| times.sort((a, b) => a - b); |
| const median = times[Math.floor(times.length / 2)]; |
| const tmin = times[0]; |
| const tmax = times[times.length - 1]; |
| |
| if (deviceLost) throw new Error("device lost: " + deviceLost.message); |
| |
| |
| renderResult({ |
| adapterInfo, dispatchesPerDepth, median, tmin, tmax, RUNS, |
| allMatch, caseReports, binBytes, |
| }); |
| setStatus("done.", "ok"); |
| } |
| |
| function renderResult(r) { |
| const ai = r.adapterInfo || {}; |
| const adapterStr = [ai.vendor, ai.architecture, ai.device, ai.description] |
| .filter(Boolean).join(" · ") || "(adapter info unavailable)"; |
| |
| const casesHtml = r.caseReports.map((c) => |
| '<div class="row"><span class="k">case ' + c.ci + |
| ' — MATCH=' + c.match + '</span>' + |
| '<span class="v ' + (c.match ? "ok" : "bad") + '">' + |
| (c.match ? "MATCH" : "MISMATCH") + '</span></div>' + |
| '<pre class="codes">GPU [' + c.gpuCodes.join(", ") + ']\nREF [' + c.ref.join(", ") + ']</pre>' |
| ).join(""); |
| |
| resultEl.innerHTML = |
| '<div class="card hero">' + |
| '<div class="label">median depth time</div>' + |
| '<div class="big">' + r.median.toFixed(2) + '<span class="unit"> ms</span></div>' + |
| '<div class="sub">12-step depth, median of ' + r.RUNS + ' runs ' + |
| '(min ' + r.tmin.toFixed(2) + ' / max ' + r.tmax.toFixed(2) + ' ms)</div>' + |
| '<div style="margin-top:14px"><span class="badge ' + |
| (r.allMatch ? "pass" : "fail") + '">' + |
| (r.allMatch ? "CORRECTNESS PASS" : "CORRECTNESS FAIL") + '</span></div>' + |
| '</div>' + |
| '<div class="card">' + |
| '<div class="row"><span class="k">GPU adapter</span><span class="v">' + |
| escapeHtml(adapterStr) + '</span></div>' + |
| '<div class="row"><span class="k">dispatchWorkgroups / depth</span><span class="v">' + |
| r.dispatchesPerDepth + '</span></div>' + |
| '<div class="row"><span class="k">weights uploaded (W0+W1)</span><span class="v">' + |
| fmtBytes(r.binBytes.byteLength) + '</span></div>' + |
| '<div class="row"><span class="k">median depth time</span><span class="v">' + |
| r.median.toFixed(3) + ' ms</span></div>' + |
| '</div>' + |
| '<div class="card">' + |
| '<div class="row"><span class="k">testcase correctness (bit-exact codes)</span>' + |
| '<span class="v ' + (r.allMatch ? "ok" : "bad") + '">' + |
| (r.allMatch ? "all MATCH" : "FAIL") + '</span></div>' + |
| casesHtml + |
| '</div>'; |
| resultEl.style.display = "block"; |
| } |
| |
| |
| main().catch((e) => showError("main", e)); |
| </script> |
| </body> |
| </html> |
|
|