Humuhumu33 commited on
Commit
e74320a
·
verified ·
1 Parent(s): 3653faa

live decode profile ?bench=perf: boosted-clock steady tok/s vs roofline

Browse files
Files changed (1) hide show
  1. index.html +39 -0
index.html CHANGED
@@ -178,6 +178,44 @@ async function runSpecBench() {
178
  console.log("[specbench]", rows);
179
  }
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  try {
182
  if (!navigator.gpu) throw new Error("This browser has no WebGPU — open in Chrome, Edge, or a recent mobile browser.");
183
  st.textContent = `loading ${m.name} (${m.size})…`;
@@ -191,6 +229,7 @@ try {
191
  st.textContent = `${m.name} · ${m.size} · in your browser · ready`;
192
  input.placeholder = "Message Q…";
193
  if (params.get("bench") === "spec") { globalThis.__spec = false; await runSpecBench(); }
 
194
  else if (pending) { const w = [...log.querySelectorAll(".a")].reverse().find((x) => x.dataset.pending); if (w) w.remove(); const p = pending; pending = null; generate(p, true); }
195
  else await proactiveGreeting();
196
  } catch (e) { st.textContent = "⚠ " + e.message; bubble("a", "Could not start: " + e.message); }
 
178
  console.log("[specbench]", rows);
179
  }
180
 
181
+ // ── LIVE DECODE PROFILE (?bench=perf) ── warms the GPU to boost clock, then measures the REAL decode path
182
+ // (engine.generate) steady-state tok/s at constant clock — separating "is there a lever left" from the boost-clock
183
+ // noise that makes cold vs warm runs differ ~2.4×. Compares to the 220 tok/s bandwidth roofline.
184
+ async function runPerfBench() {
185
+ log.innerHTML = ""; input.disabled = send.disabled = true;
186
+ const rep = m.rep ?? 1.3;
187
+ const ids = engine.tokenize(frameSystem() + engine.frameTurn("Write a detailed paragraph about how ocean currents move heat around the planet.", false));
188
+ globalThis.__spec = false;
189
+ st.textContent = "warming up (boosting GPU clock)…";
190
+ engine.reset(); await engine.generate(ids.slice(), { maxNew: 64, repPenalty: rep }); // warmup → boost clock + warm caches
191
+ const runs = [];
192
+ for (let i = 0; i < 3; i++) {
193
+ st.textContent = `measuring run ${i + 1}/3…`;
194
+ engine.reset();
195
+ const t0 = performance.now();
196
+ const r = await engine.generate(ids.slice(), { maxNew: 128, repPenalty: rep });
197
+ const dt = performance.now() - t0;
198
+ runs.push({ n: r.outIds.length, wall: dt, e2e: r.outIds.length / (dt / 1000), steady: (r.stats && r.stats.tokps) || 0, msExec: (r.stats && r.stats.msExec) || 0 });
199
+ }
200
+ const best = runs.slice().sort((a, b) => b.steady - a.steady)[0];
201
+ const ROOF = 220, KERNEL = 158; // measured: bandwidth roofline · boosted sustained single-matmul
202
+ const pct = 100 * best.steady / ROOF, msTok = best.steady ? 1000 / best.steady : 0;
203
+ const near = best.steady >= 0.6 * KERNEL;
204
+ const tbl = `<div style="font-family:ui-monospace,monospace;font-size:13px;max-width:860px;margin:0 auto;padding:8px">
205
+ <div style="font-size:18px;font-weight:700;margin-bottom:4px">Live decode — measured at boosted clock</div>
206
+ <div style="color:var(--dim);margin-bottom:12px">BitNet-2B · real engine.generate path · warmed then timed ×3 · bandwidth roofline 220 tok/s</div>
207
+ <table style="width:100%;border-collapse:collapse">
208
+ <tr style="color:var(--dim);text-align:left"><th style="padding:6px 8px">run</th><th style="padding:6px 8px;text-align:right">steady tok/s</th><th style="padding:6px 8px;text-align:right">end-to-end tok/s</th><th style="padding:6px 8px;text-align:right">ms/token</th><th style="padding:6px 8px;text-align:right">GPU ms/tok</th></tr>
209
+ ${runs.map((r, i) => `<tr style="border-top:1px solid var(--line)"><td style="padding:6px 8px">run ${i + 1}</td><td style="padding:6px 8px;text-align:right">${r.steady.toFixed(0)}</td><td style="padding:6px 8px;text-align:right">${r.e2e.toFixed(0)}</td><td style="padding:6px 8px;text-align:right">${(r.steady ? 1000 / r.steady : 0).toFixed(1)}</td><td style="padding:6px 8px;text-align:right">${r.msExec ? r.msExec.toFixed(1) : "—"}</td></tr>`).join("")}
210
+ </table>
211
+ <div style="margin-top:12px">Best steady: <b>${best.steady.toFixed(0)} tok/s</b> = <b>${pct.toFixed(0)}%</b> of the 220 bandwidth roofline (sustained single-matmul reference ≈ ${KERNEL} tok/s).</div>
212
+ <div style="margin-top:10px;font-weight:600;color:${near ? "#48c26c" : "#e0a94a"}">${near
213
+ ? "✓ Live decode is near the sustained-kernel rate — little recoverable overhead. The kernel/roofline is the ceiling; further tok/s needs fewer weight-bytes (lower-bit/MoE), spec-decode on echo text, or more bandwidth (discrete GPU)."
214
+ : `⚠ Live decode (${best.steady.toFixed(0)}) sits well below the sustained kernel (~${KERNEL}) at the SAME clock — the gap is per-token CPU round-trips (fences / JS embed / detokenize) letting the GPU idle. Decode-loop saturation is the real lever, and it's what also unlocks spec-decode's ~free batched verify.`}</div>
215
+ </div>`;
216
+ log.innerHTML = tbl; st.textContent = "live decode profile · done"; console.log("[perfbench]", runs);
217
+ }
218
+
219
  try {
220
  if (!navigator.gpu) throw new Error("This browser has no WebGPU — open in Chrome, Edge, or a recent mobile browser.");
221
  st.textContent = `loading ${m.name} (${m.size})…`;
 
229
  st.textContent = `${m.name} · ${m.size} · in your browser · ready`;
230
  input.placeholder = "Message Q…";
231
  if (params.get("bench") === "spec") { globalThis.__spec = false; await runSpecBench(); }
232
+ else if (params.get("bench") === "perf") { await runPerfBench(); }
233
  else if (pending) { const w = [...log.querySelectorAll(".a")].reverse().find((x) => x.dataset.pending); if (w) w.remove(); const p = pending; pending = null; generate(p, true); }
234
  else await proactiveGreeting();
235
  } catch (e) { st.textContent = "⚠ " + e.message; bubble("a", "Could not start: " + e.message); }