EXDai/speculative-decoding-simulator
0
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="UTF-8">5<meta name="viewport" content="width=device-width, initial-scale=1.0">6<title>Speculative Decoding — EXD Ep5</title>7<style>8:root {9 --bg:#0d1117; --surface:#161b22; --border:#30363d;10 --text:#c9d1d9; --dim:#8b949e; --accent:#58a6ff;11 --hit:#3fb950; --miss:#f85149; --draft:#bc8cff;12}13*{margin:0;padding:0;box-sizing:border-box}14body{15 background:var(--bg);color:var(--text);16 font:14px ui-monospace,SFMono-Regular,monospace;17 display:flex;justify-content:center;padding:2rem 1rem;18}19.w{max-width:720px;width:100%}20h1{font-size:1.1rem;margin-bottom:.2rem}21.sub{font-size:.75rem;color:var(--dim);margin-bottom:1rem}22.controls{display:flex;gap:1rem;flex-wrap:wrap;align-items:flex-end;margin-bottom:1rem}23.cg{display:flex;flex-direction:column;gap:.2rem}24.cg label{font-size:.65rem;color:var(--dim);text-transform:uppercase}25.cg select{padding:.3rem .5rem;border-radius:4px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-family:monospace;font-size:.8rem;cursor:pointer}26.btn-row{display:flex;gap:.5rem;align-items:center}27.btn{background:var(--surface);color:var(--accent);border:1px solid var(--accent);padding:.35rem .8rem;border-radius:5px;cursor:pointer;font-family:monospace;font-size:.75rem;font-weight:600}28.btn:hover{background:rgba(88,166,255,.12)}29.btn.pri{background:var(--accent);color:var(--bg)}30.btn:disabled{opacity:.4;cursor:not-allowed}31.step{font-size:.7rem;color:var(--dim);margin-left:.5rem}32.sec{font-size:.65rem;color:var(--dim);text-transform:uppercase;margin-bottom:.3rem}33.pipe{display:flex;gap:6px;padding:.7rem;margin-bottom:1rem;background:var(--surface);border:1px solid var(--border);border-radius:8px;min-height:44px;flex-wrap:wrap;align-items:center}34.ps{padding:.4rem .6rem;border-radius:6px;border:2px solid var(--border);font-size:.7rem;text-align:center;transition:all .3s}35.ps.on{border-color:var(--accent);box-shadow:0 0 8px rgba(88,166,255,.25)}36.ps .l{font-size:.55rem;color:var(--dim);display:block}37.ps .v{font-size:.75rem;display:block;margin-top:1px}38.tokens{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:1rem;min-height:50px;align-items:center}39.tok{padding:5px 10px;border-radius:6px;font-size:.75rem;font-weight:600;border:2px solid transparent;text-align:center;transition:all .3s}40.tok .t{display:block;min-width:40px}41.tok .lb{font-size:.5rem;color:var(--dim);margin-top:2px}42.t-OK{background:var(--surface);border-color:var(--border);color:var(--text)}43.t-HIT{background:rgba(63,185,80,.12);border-color:var(--hit);color:var(--hit)}44.t-MISS{background:rgba(248,81,73,.12);border-color:var(--miss);color:var(--miss);text-decoration:line-through}45.t-DRAFT{background:rgba(188,140,255,.1);border-color:var(--draft);color:var(--draft)}46.info{font-size:.7rem;color:var(--dim);padding:.6rem .8rem;border-left:3px solid var(--accent);background:rgba(88,166,255,.04);border-radius:0 6px 6px 0;line-height:1.5;min-height:2.5em}47.info code{color:var(--accent);background:rgba(88,166,255,.1);padding:0 4px;border-radius:3px}48.stats{display:flex;gap:1rem;margin-bottom:1rem;padding:.6rem;background:var(--surface);border:1px solid var(--border);border-radius:8px;flex-wrap:wrap}49.stat{text-align:center;min-width:70px}50.stat .n{font-size:1.1rem;font-weight:700;color:var(--accent);font-family:monospace}51.stat .lb{font-size:.55rem;color:var(--dim)}52.stat.bad .n{color:var(--miss)}53</style>54</head>55<body>56<div class="w">57<h1>Speculative Decoding — Step by Step</h1>58<div class="sub">Draft model proposes tokens ahead, full model verifies · EXD Ep5</div>59 60<div class="controls">61 <div class="cg">62 <label>Spec Depth <span id="dv">2</span></label>63 <select id="depth">64 <option value="2" selected>2 tokens</option>65 <option value="3">3 tokens</option>66 </select>67 </div>68 <div class="btn-row">69 <button class="btn" id="prev">← Prev</button>70 <button class="btn pri" id="step">Next Step</button>71 <button class="btn" id="reset">Reset</button>72 <span class="step" id="st">Step 0</span>73 </div>74</div>75 76<div class="sec">Pipeline</div>77<div class="pipe" id="pipe"></div>78 79<div class="sec">Tokens</div>80<div class="tokens" id="trow"></div>81 82<div class="stats" id="stats"></div>83 84<div class="info" id="info">85 <strong>Speculative decoding</strong>: a lightweight draft model guesses tokens ahead.<br>86 The full model verifies them all in <em>one forward pass</em>.<br>87 <span style="color:var(--hit)">■ Green</span> = correct guess (free speed) ·88 <span style="color:var(--miss)">■ Red</span> = wrong guess (wasted, struck out)<br>89 <code>Space</code> = next, <code>←</code> = previous. Switch depth to compare.90</div>91</div>92 93<script>94var PREFIX = "The quick brown";95var d = function(id) { return document.getElementById(id); };96var idx = 0;97var depth = 2;98 99// ---- STATIC SCRIPTS ----100// Each script step: {phase, info, drafts: [{token,correct}] or null, confirm: [tokens_to_add_to_conf]}101// phase: "draft" | "verify" | "done"102// confirm only on verify steps: tokens to add to conf (hits + any miss correction, in order)103 104var SCRIPT_2 = [105 {phase:"draft", info:"<strong>Draft model</strong> guesses 2 tokens ahead: <em>fox</em>, <em>jumps</em>",106 drafts:[{token:"fox",correct:true},{token:"jumps",correct:true}]},107 {phase:"verify", info:"<strong>Full model</strong> verifies: 2 hit(s). Speed gained!",108 drafts:[{token:"fox",correct:true},{token:"jumps",correct:true}], confirm:["fox","jumps"]},109 {phase:"draft", info:"<strong>Draft model</strong> guesses 2 tokens ahead: <em>over</em>, <em>the</em> — but one is wrong!",110 drafts:[{token:"over",correct:true},{token:"???",correct:false}]},111 {phase:"verify", info:"<strong>Full model</strong> verifies: 1 hit(s), 1 miss. Corrects <em>??? → the</em>.",112 drafts:[{token:"over",correct:true},{token:"???",correct:false,correction:"the"}], confirm:["over","the"]},113 {phase:"draft", info:"<strong>Draft model</strong> guesses 2 tokens ahead: <em>lazy</em>, <em>dog</em>",114 drafts:[{token:"lazy",correct:true},{token:"dog",correct:true}]},115 {phase:"verify", info:"<strong>Full model</strong> verifies: 2 hit(s). Done! 🎉",116 drafts:[{token:"lazy",correct:true},{token:"dog",correct:true}], confirm:["lazy","dog"]},117 {phase:"done", info:"<strong>Done!</strong> Generated 9 tokens in 3 forward passes (6 steps). Speculation saved ~50% of the passes.", drafts:[]},118];119 120var SCRIPT_3 = [121 {phase:"draft", info:"<strong>Draft model</strong> guesses 3 tokens ahead: <em>fox</em>, <em>jumps</em>, <em>over</em>",122 drafts:[{token:"fox",correct:true},{token:"jumps",correct:true},{token:"over",correct:true}]},123 {phase:"verify", info:"<strong>Full model</strong> verifies: 3 hit(s) in one pass! 🚀",124 drafts:[{token:"fox",correct:true},{token:"jumps",correct:true},{token:"over",correct:true}], confirm:["fox","jumps","over"]},125 {phase:"draft", info:"<strong>Draft model</strong> guesses 3 tokens ahead: <em>the</em>, <em>lazy</em>, <em>dog</em> — but one is wrong!",126 drafts:[{token:"the",correct:true},{token:"???",correct:false},{token:"dog",correct:true}]},127 {phase:"verify", info:"<strong>Full model</strong> verifies: 1 hit(s), 1 miss. Corrects <em>??? → lazy</em>. Remaining drafts accepted. Done!",128 drafts:[{token:"the",correct:true},{token:"???",correct:false,correction:"lazy"},{token:"dog",correct:true}], confirm:["the","lazy","dog"]},129 {phase:"done", info:"<strong>Done!</strong> Generated 9 tokens in 2 forward passes (4 steps). Depth 3 got 3 tokens in one go, but a miss wasted extra work.", drafts:[]},130];131 132var script = SCRIPT_2.slice();133var conf = [];134var total_hits = 0, total_misses = 0;135 136function getScript() {137 return Number(d("depth").value) === 3 ? SCRIPT_3 : SCRIPT_2;138}139 140function reset() {141 var s = getScript();142 script = s.map(function(x) { return JSON.parse(JSON.stringify(x)); });143 conf = PREFIX.split(" ");144 total_hits = 0;145 total_misses = 0;146 idx = -1;147 depth = Number(d("depth").value);148 d("dv").textContent = depth;149 render();150 d("st").textContent = "Step " + idx;151 d("info").innerHTML = "Prefix: <strong>" + PREFIX + "</strong> → target: <strong>fox jumps over the lazy dog</strong>. Depth = " + depth + ". Click <code>Next Step</code>.";152 updateButtons();153}154 155function nextStep() {156 if (idx >= 0 && idx >= script.length) return;157 if (idx === -1) {158 // Initial state: just advance to first frame159 idx = 0;160 d("st").textContent = "Step " + idx;161 d("info").innerHTML = script[0].info;162 updateButtons();163 render();164 return;165 }166 var frame = script[idx];167 depth = Number(d("depth").value);168 d("dv").textContent = depth;169 170 if (frame.phase === "verify" && frame.confirm) {171 for (var i = 0; i < frame.confirm.length; i++) {172 conf.push(frame.confirm[i]);173 }174 // Count hits/misses from drafts175 if (frame.drafts) {176 for (var i = 0; i < frame.drafts.length; i++) {177 if (frame.drafts[i].correct) total_hits++;178 else total_misses++;179 }180 }181 }182 idx++;183 d("st").textContent = "Step " + idx;184 d("info").innerHTML = frame.info;185 updateButtons();186 render();187}188 189function prevStep() {190 if (idx <= -1) return;191 var target = idx - 1;192 // Replay from start up to target193 conf = PREFIX.split(" ");194 total_hits = 0;195 total_misses = 0;196 depth = Number(d("depth").value);197 script = getScript().map(function(x) { return JSON.parse(JSON.stringify(x)); });198 for (var i = 0; i < target; i++) {199 var frame = script[i];200 if (frame.phase === "verify" && frame.confirm) {201 for (var j = 0; j < frame.confirm.length; j++) conf.push(frame.confirm[j]);202 if (frame.drafts) {203 for (var j = 0; j < frame.drafts.length; j++) {204 if (frame.drafts[j].correct) total_hits++;205 else total_misses++;206 }207 }208 }209 }210 idx = target;211 d("st").textContent = idx === -1 ? "Step 0" : "Step " + idx;212 if (idx === -1) {213 d("info").innerHTML = "Prefix: <strong>" + PREFIX + "</strong> → target: <strong>fox jumps over the lazy dog</strong>. Depth = " + depth + ".";214 } else {215 d("info").innerHTML = script[idx] ? script[idx].info : "";216 }217 updateButtons();218 render();219}220 221function updateButtons() {222 var pastEnd = idx >= 0 && idx >= script.length;223 var atDone = idx >= 0 && idx < script.length && script[idx].phase === "done";224 d("step").disabled = pastEnd || atDone;225 d("prev").disabled = idx <= -1;226 if (pastEnd) d("st").textContent = "Done";227}228 229function render() {230 var cur = (idx >= 0 && idx < script.length) ? script[idx] : null;231 var pipe = d("pipe");232 var ph = idx === -1 ? "" : (cur ? cur.phase : "done");233 var dm = ph === "draft" ? ' on' : '';234 var fm = ph === "verify" ? ' on' : '';235 var dn = (ph === "done" || idx >= script.length) ? ' on' : '';236 pipe.innerHTML = '<div class="ps' + dm + '"><span class="l">Draft Model</span><span class="v">guesses ' + depth + ' tokens</span></div>' +237 '<span style="color:var(--dim);margin:0 4px">→</span>' +238 '<div class="ps' + fm + '"><span class="l">Full Model</span><span class="v">verifies in 1 pass</span></div>' +239 '<span style="color:var(--dim);margin:0 4px">→</span>' +240 '<div class="ps' + dn + '"><span class="l">' + ((ph === "done" || idx >= script.length) ? 'Done' : 'Repeat') + '</span><span class="v">' + ((ph === "done" || idx >= script.length) ? 'complete' : 'until EOS') + '</span></div>';241 242 var html = "";243 for (var i = 0; i < conf.length; i++) {244 var isPrefix = i < PREFIX.split(" ").length;245 html += '<div class="tok t-OK"><span class="t">' + conf[i] + '</span><span class="lb">' + (isPrefix ? 'prefix' : 'generated') + '</span></div>';246 }247 if (cur && cur.drafts && cur.drafts.length > 0) {248 var isVerify = cur.phase === "verify";249 if (isVerify) {250 html += '<div style="position:relative;display:flex;gap:4px;align-items:center;padding:6px 6px 4px;border:2px dashed var(--accent);border-radius:8px;background:rgba(88,166,255,.04);margin-top:2px">';251 html += '<span style="position:absolute;top:-8px;left:6px;font-size:.5rem;color:var(--accent);background:var(--surface);padding:0 4px">verified in 1 pass</span>';252 }253 for (var i = 0; i < cur.drafts.length; i++) {254 var cls, label;255 if (cur.phase === "draft") {256 cls = "t-DRAFT";257 label = "drafting...";258 } else {259 cls = cur.drafts[i].correct ? "t-HIT" : "t-MISS";260 label = cur.drafts[i].correct ? "hit" : "miss";261 }262 if (!cur.drafts[i].correct && cur.drafts[i].correction) {263 // Split card: left = struck-out miss, right = green correction264 html += '<div class="tok ' + cls + '" style="display:flex;gap:8px;align-items:center;text-decoration:none">' +265 '<div style="text-align:center"><span class="t" style="text-decoration:line-through">' + cur.drafts[i].token + '</span><span class="lb">' + label + '</span></div>' +266 '<span style="color:var(--dim);font-size:.65rem">→</span>' +267 '<div class="tok t-HIT" style="margin:0;text-decoration:none"><span class="t">' + cur.drafts[i].correction + '</span><span class="lb">corrected</span></div>' +268 '</div>';269 } else {270 html += '<div class="tok ' + cls + '"><span class="t">' + cur.drafts[i].token + '</span><span class="lb">' + label + '</span></div>';271 }272 } // end draft tokens loop273 if (isVerify) { html += '</div>'; }274 }275 d("trow").innerHTML = html || '<span style="color:var(--dim);font-size:.75rem">no tokens</span>';276 277 var passes = idx > 0 ? Math.ceil(idx / 2) : 0;278 d("stats").innerHTML =279 '<div class="stat"><div class="n">' + conf.length + '</div><div class="lb">tokens out</div></div>' +280 '<div class="stat"><div class="n">' + passes + '</div><div class="lb">forward passes</div></div>' +281 '<div class="stat"><div class="n">' + total_hits + '</div><div class="lb">hits</div></div>' +282 '<div class="stat' + (total_misses > 0 ? ' bad' : '') + '"><div class="n">' + total_misses + '</div><div class="lb">misses</div></div>';283}284 285d("step").onclick = nextStep;286d("prev").onclick = prevStep;287d("reset").onclick = reset;288d("depth").onchange = reset;289document.onkeydown = function(e) {290 if (e.key === " " || e.key === "Enter") { e.preventDefault(); nextStep(); }291 if (e.key === "ArrowLeft") { e.preventDefault(); prevStep(); }292};293reset();294</script>295</body>296</html>297 