File size: 11,505 Bytes
7845694
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<!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>