JEV-CPU / webgpu-demo /index.html
Meanblock's picture
Add JEV-CPU: CPU port of SemIf + web UI
7845694 verified
Raw History Blame Contribute Delete
11.5 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="referrer" content="no-referrer" />
<meta name="description" content="Run typed option logits and autoregressive JSON locally with open models and WebGPU." />
<title>SemIf (formerly OpenJev) — local decisions in your browser</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20,600,0,0&amp;icon_names=add,celebration,check,check_circle,download,error,hourglass_top,memory,play_arrow,progress_activity,refresh,remove,replay&amp;display=block" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="page">
<aside class="research-notice" role="note"><strong>Independent research project.</strong> Formerly called OpenJev. Not affiliated with or endorsed by TypeSafe. No infringement is intended.</aside>
<header class="mast">
<a class="brand" href="./">SemIf (formerly OpenJev)</a>
<div class="mast-actions">
<span class="tagline">Semantic ifs from open models, in your browser.</span>
<label class="ui-toggle" for="ui-mode">
<input id="ui-mode" type="checkbox" role="switch" />
<span class="ui-toggle-track" aria-hidden="true"><i></i></span>
<b>Unsloppify site</b>
</label>
</div>
</header>
<section class="hero">
<p class="eyebrow">A live, local experiment</p>
<h1>Decision model <mark>in your browser.</mark></h1>
<p class="lede">A local model can either read probabilities for your allowed options without decoding them, or write the same kind of distribution token by token. Pick a size, run both on your own GPU, and measure the difference.</p>
<div class="truth-strip">
<span>browser only</span><span>no backend</span><span>your timings</span><span id="model-size">1.56 GB model</span>
</div>
<div class="waitlist-joke">
<p role="status"><span class="material-symbols-rounded" aria-hidden="true">celebration</span><strong>There is no waitlist!</strong> Just try it out ↓</p>
</div>
<p id="device-note" class="device-note">MiniCPM5 2B is selected by default. On a phone or smaller device, switch to Qwen3 0.6B in the model box if needed.</p>
</section>
<section class="machine" aria-labelledby="setup-title">
<div class="section-head">
<div><p class="eyebrow">00 / setup</p><h2 id="setup-title">Load the model once</h2></div>
<div class="setup-actions">
<label for="model-select">Model
<select id="model-select">
<option value="qwen3-0.6b">Qwen3 0.6B · recommended for phones and small devices</option>
<option value="minicpm5-2b" selected>MiniCPM5 2B · recommended on desktop</option>
<option value="qwen3.5-4b">Qwen3.5 4B · high-memory desktop</option>
</select>
</label>
<button id="load" type="button"><span class="material-symbols-rounded" aria-hidden="true">download</span> load MiniCPM5 2B</button>
</div>
</div>
<p id="model-notice" class="model-notice" role="note">Larger model. Loading may be slower or may not fit on some low-end devices.</p>
<div class="quality-card" aria-labelledby="quality-title">
<div class="quality-heading"><strong id="quality-title">Measured model quality</strong><span>native checkpoints · owned + public benchmarks</span></div>
<div class="quality-scroll">
<table>
<thead><tr><th>Model</th><th>Download</th><th>Authored</th><th>Perturbed</th><th>TypeSafe</th></tr></thead>
<tbody>
<tr data-quality-model="qwen3-0.6b"><td>Qwen3 0.6B</td><td>639 MB</td><td>44.0%</td><td>52.8%</td><td>40.7%</td></tr>
<tr data-quality-model="minicpm5-2b" class="selected"><td>MiniCPM5 2B</td><td>1.56 GB</td><td>68.6%</td><td>69.3%</td><td>63.7%</td></tr>
<tr data-quality-model="qwen3.5-4b"><td>Qwen3.5 4B</td><td>3.01 GB</td><td>81.3%</td><td>76.6%</td><td>84.5%</td></tr>
<tr><td>Published Jev</td><td>hosted</td><td>—</td><td>—</td><td>88.3%</td></tr>
</tbody>
</table>
</div>
<p>Owned columns are balanced accuracy. TypeSafe is equal-case agreement on the same 102-row public subset; Jev is the published value. Browser quantization may change model accuracy.</p>
</div>
<div id="support"><p :class="['support', kind]" role="status"><span class="material-symbols-rounded" aria-hidden="true">{{ icon }}</span>{{ text }}</p></div>
<div class="phases" aria-label="Model setup phases">
<div class="phase"><small>download / cache</small><strong id="download-value">—</strong><div class="meter"><i id="download-meter"></i></div><span id="download-detail">starts only when you click load</span></div>
<div class="phase"><small>model load</small><strong id="load-value">—</strong><span>download and prepare</span></div>
<div class="phase"><small>warmup</small><strong id="warmup-value">—</strong><span>compile passes for both methods</span></div>
</div>
<p class="fine">Weights come from Hugging Face and remain in your browser cache. Inputs never leave this page. First load can take several minutes depending on the selected model, network and GPU.</p>
</section>
<section class="workbench" aria-labelledby="workbench-title">
<div class="section-head">
<div><p class="eyebrow">01 / decision</p><h2 id="workbench-title">Give it a real choice</h2></div>
<button id="run" type="button" disabled><span class="material-symbols-rounded" aria-hidden="true">play_arrow</span> run both methods</button>
</div>
<div class="presets" aria-label="Prefill an example">
<span>Try an example</span>
<button type="button" data-preset="account">Account support</button>
<button type="button" data-preset="email">Email triage</button>
</div>
<div class="inputs">
<label>State<textarea id="state" rows="5">A customer says a password reset succeeded, but every login attempt still returns “account locked”. Two unlock emails were requested and neither arrived.</textarea></label>
<label>Question<input id="question" value="Which queue should handle this request?" /></label>
<fieldset><legend>Allowed options</legend>
<div id="option-list">
<label class="option-row"><b>A</b><input class="option" value="Account access support" /></label>
<label class="option-row"><b>B</b><input class="option" value="Billing support" /></label>
<label class="option-row"><b>C</b><input class="option" value="Close as resolved" /></label>
</div>
<div class="option-actions">
<button id="remove-option" type="button"><span class="material-symbols-rounded" aria-hidden="true">remove</span> remove</button>
<span id="option-count">3 / 20</span>
<button id="add-option" type="button"><span class="material-symbols-rounded" aria-hidden="true">add</span> add</button>
</div>
</fieldset>
</div>
<p class="fine">Both paths receive the same decision. One reads option probabilities directly; the other asks the model to write its option probabilities as JSON text.</p>
</section>
<section class="method-map" aria-label="The two decision paths">
<div class="method-input"><small>your decision</small><strong>state + question + options</strong></div>
<div class="arrow" aria-hidden="true">→</div>
<div class="method-model"><small>same local model</small><strong id="selected-model">MiniCPM5 · 2B</strong></div>
<div class="fork" aria-hidden="true">↗<br />↘</div>
<div class="method-outputs">
<div><small>read logits</small><strong>A…T probabilities</strong></div>
<div><small>write tokens</small><strong><i>{</i><i>options</i><i> + </i><i>probabilities</i><i>}</i></strong></div>
</div>
</section>
<section class="race" aria-label="Live comparison results">
<article class="lane direct">
<div class="lane-head"><div><p class="eyebrow">02A / direct readout</p><h2>Choice probabilities</h2></div><span class="badge">no decoding</span></div>
<p class="explain">Read the model’s choice logits and normalize only across the options you supplied.</p>
<div id="direct-output" class="output empty">waiting for a run</div>
<dl class="metrics">
<div><dt>total</dt><dd id="direct-total">—</dd></div>
<div><dt>input</dt><dd id="direct-input">—</dd></div>
<div><dt>output</dt><dd id="direct-readouts">1 readout</dd></div>
</dl>
</article>
<article class="lane generated">
<div class="lane-head"><div><p class="eyebrow">02B / generation</p><h2>JSON probabilities</h2></div><span class="badge pink">token by token</span></div>
<p class="explain">Ask the model to estimate the same displayed-option distribution and write it as JSON. Watch every token arrive.</p>
<pre id="generated-output" class="output empty">waiting for a run</pre>
<dl class="metrics">
<div><dt>first token</dt><dd id="generation-ttft">—</dd></div>
<div><dt>total</dt><dd id="generation-total">—</dd></div>
<div><dt>input</dt><dd id="generation-input">—</dd></div>
<div><dt>output</dt><dd id="generation-tokens">—</dd></div>
</dl>
</article>
</section>
<section class="verdict" aria-live="polite">
<span>measured wall-time ratio</span><strong id="ratio">run it on your GPU</strong>
<p id="run-note">The methods run sequentially on the same loaded model so they do not contend for one GPU. Direct runs first, then generation.</p>
</section>
<details>
<summary>What these numbers do—and do not—mean</summary>
<div class="details-grid">
<p><b>Conditional probabilities.</b> Direct scores are a softmax over only the displayed option tokens. They are not calibrated confidence and do not include every answer the model might prefer.</p>
<p><b>Local model tiers.</b> The phone model trades accuracy for size. MiniCPM is the desktop default. The 4B option needs substantially more memory. None is claimed to match Jev.</p>
<p><b>Real local timing.</b> Setup, warmup, prompt preparation, direct execution, first generated token and generation completion are timed with <code>performance.now()</code>. No canned results appear.</p>
<p><b>Quantized weights.</b> The demo uses pinned GGUF builds through wllama. Quantization can change both quality and speed.</p>
</div>
</details>
<footer><span>SemIf / browser lab</span><span><a href="README.md">implementation notes</a> · <a id="model-link" href="https://huggingface.co/openbmb/MiniCPM5-2B-GGUF">model</a> · <a href="https://github.com/ngxson/wllama">wllama</a></span></footer>
</main>
<script type="module" src="app.js"></script>
</body>
</html>