Spaces:
Running
Running
live decode profile ?bench=perf: boosted-clock steady tok/s vs roofline
Browse files- 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); }
|