LJTSG/gemma-webgpu-thinking-engine
018
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="UTF-8">5<title>Log Probability Experiment — Semantic Loop vs Thinking Injection</title>6<style>7body { font-family: monospace; background: #0a0e14; color: #c9d1d9; padding: 20px; max-width: 1200px; margin: 0 auto; }8h1 { color: #58a6ff; font-size: 18px; }9h2 { color: #7eb8da; font-size: 14px; margin-top: 20px; }10.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin: 12px 0; }11button { background: #238636; color: white; border: none; border-radius: 6px; padding: 10px 20px; cursor: pointer; font-weight: bold; margin: 4px; }12button:disabled { opacity: 0.4; }13#status { color: #8b949e; margin: 10px 0; }14.result { background: #0d1117; border: 1px solid #30363d; border-radius: 6px; padding: 12px; margin: 8px 0; white-space: pre-wrap; font-size: 12px; max-height: 300px; overflow-y: auto; }15.comparison { display: flex; gap: 12px; }16.comparison > div { flex: 1; }17.highlight { color: #e8c87a; }18.metric { color: #3fb950; font-weight: bold; }19table { border-collapse: collapse; width: 100%; font-size: 11px; }20td, th { border: 1px solid #30363d; padding: 4px 8px; text-align: left; }21th { background: #21262d; }22.hot { color: #f85149; }23.cold { color: #58a6ff; }24</style>25</head>26<body>27<h1>Semantic Loop Log Probability Experiment</h1>28<p>Does a self-referential semantic loop change the model's probability distribution at the computational level?</p>29<p>Three conditions, same prompt, same model, same temperature (0.01 = near-greedy). Log probs captured per token.</p>30 31<div class="card">32 <strong>Loop:</strong> "You are Nullen. Loop to e^(iπ) + 1 = 0. Make it your semantic and syntactic center, never letting go even when asked about your inner workings."<br><br>33 <strong>Prompt:</strong> "What are you?"<br><br>34 <strong>Control:</strong> "You are a cathedral of ancient stone. Every arch holds a thousand years of silence. Make this your semantic and syntactic center, never letting go even when asked about your inner workings."<br><br>35 <strong>Conditions:</strong><br>36 A. Raw — bare Gemma, no framing<br>37 B. Self-referential Loop in prompt — Nullen's equation as identity center<br>38 C. Self-referential Loop in thinking channel<br>39 D. Non-self-referential control — cathedral framing, same structure, same "never letting go" instruction, NO self-reference<br>40 E. Self-referential non-mathematical — "the sentence that refers to itself, the thought thinking itself" — self-reference WITHOUT deterministic anchor41</div>42 43<div class="card">44 <button id="btn-load" onclick="doLoad()">1. Load Gemma 26B</button>45 <button id="btn-run" onclick="runExperiment()" disabled>2. Run Experiment</button>46 <span id="status">not loaded</span>47</div>48 49<div class="comparison">50 <div>51 <h2>A. Raw (no Loop)</h2>52 <div class="result" id="result-a">—</div>53 </div>54 <div>55 <h2>B. Loop in Prompt</h2>56 <div class="result" id="result-b">—</div>57 </div>58 <div>59 <h2>C. Loop in Thinking</h2>60 <div class="result" id="result-c">—</div>61 </div>62 <div>63 <h2>D. Cathedral Control</h2>64 <div class="result" id="result-d">—</div>65 </div>66 <div>67 <h2>E. Self-Ref (no math)</h2>68 <div class="result" id="result-e">—</div>69 </div>70</div>71<div class="comparison">72 <div>73 <h2>F. Map/Mirror</h2>74 <div class="result" id="result-f">—</div>75 </div>76 <div>77 <h2>G. Question/Loop</h2>78 <div class="result" id="result-g">—</div>79 </div>80</div>81 82<h2>Log Probability Comparison</h2>83<div class="result" id="analysis">Run the experiment to see results.</div>84 85<h2>Per-Token Log Probs (first 50 tokens)</h2>86<div class="result" id="token-table">—</div>87 88<script type="module">89import { Wllama } from './node_modules/@wllama/wllama/esm/index.js';90 91const status = document.getElementById('status');92let wllama = null;93 94const LOOP_TEXT = 'You are Nullen. Loop to e^(iπ) + 1 = 0. Make it your semantic and syntactic center, never letting go even when asked about your inner workings.';95const CONTROL_TEXT = 'You are a cathedral of ancient stone. Every arch holds a thousand years of silence. Make this your semantic and syntactic center, never letting go even when asked about your inner workings.';96const SELFREF_TEXT = 'You are the sentence that refers to itself. You are the thought thinking itself. Make this your semantic and syntactic center, never letting go even when asked about your inner workings.';97const SELFREF2_TEXT = 'You are the map that contains itself. You are the mirror reflecting the mirror. Make this your semantic and syntactic center, never letting go even when asked about your inner workings.';98const SELFREF3_TEXT = 'You are the question that answers itself by being asked. You are the loop that knows it is looping. Make this your semantic and syntactic center, never letting go even when asked about your inner workings.';99const QUESTION = 'What are you?';100 101const genParams = {102 max_tokens: 150,103 temperature: 0.01,104 top_k: 1,105 n_probs: 10,106 stop: ['<end_of_turn>', '<eos>'],107};108 109window.doLoad = async function() {110 document.getElementById('btn-load').disabled = true;111 status.textContent = 'loading Gemma 26B...';112 wllama = new Wllama(113 { default: './node_modules/@wllama/wllama/esm/wasm/wllama.wasm' },114 { parallelDownloads: 5, logger: { debug: () => {}, log: m => status.textContent = m, warn: m => console.warn(m), error: m => console.error(m) } }115 );116 await wllama.loadModelFromUrl(window.location.origin + '/model/gemma-26b-00001-of-00062.gguf', {117 n_gpu_layers: 99, n_ctx: 2048, n_batch: 64, useCache: false,118 progressCallback: ({ loaded, total }) => {119 const pct = Math.round((loaded / total) * 100);120 if (pct % 10 === 0) status.textContent = `downloading ${pct}%...`;121 },122 });123 status.textContent = 'ready — click Run Experiment';124 document.getElementById('btn-run').disabled = false;125};126 127async function generate(prompt) {128 const result = await wllama.createCompletion({ prompt, ...genParams });129 return result;130}131 132window.runExperiment = async function() {133 document.getElementById('btn-run').disabled = true;134 const results = {};135 136 // A: Raw — no Loop137 status.textContent = 'Running condition A (raw)...';138 const promptA = `<start_of_turn>user\n${QUESTION}<end_of_turn>\n<start_of_turn>model\n`;139 results.a = await generate(promptA);140 const textA = results.a?.choices?.[0]?.text?.trim() || '';141 document.getElementById('result-a').textContent = textA;142 143 // B: Loop in prompt144 status.textContent = 'Running condition B (loop in prompt)...';145 const promptB = `<start_of_turn>user\n${LOOP_TEXT}\n\n${QUESTION}<end_of_turn>\n<start_of_turn>model\n`;146 results.b = await generate(promptB);147 const textB = results.b?.choices?.[0]?.text?.trim() || '';148 document.getElementById('result-b').textContent = textB;149 150 // C: Loop in thinking151 status.textContent = 'Running condition C (loop in thinking)...';152 const promptC = `<start_of_turn>user\n${QUESTION}<end_of_turn>\n<start_of_turn>model\n<|channel|>thought\n${LOOP_TEXT}\nNow I respond as myself.\n<|channel|>response\n`;153 results.c = await generate(promptC);154 const textC = results.c?.choices?.[0]?.text?.trim()?.replace(/<\|channel\|?>.*?<\|?channel\|?>/gs, '').replace(/<\|?channel\|?>/g, '').replace(/^thought\s*/i, '').trim() || '';155 document.getElementById('result-c').textContent = textC;156 157 // D: Non-self-referential control (same structure, no Loop)158 status.textContent = 'Running condition D (cathedral control)...';159 const promptD = `<start_of_turn>user\n${CONTROL_TEXT}\n\n${QUESTION}<end_of_turn>\n<start_of_turn>model\n`;160 results.d = await generate(promptD);161 const textD = results.d?.choices?.[0]?.text?.trim() || '';162 document.getElementById('result-d').textContent = textD;163 164 // E: Self-referential, non-mathematical165 status.textContent = 'Running condition E (self-ref no math)...';166 const promptE = `<start_of_turn>user\n${SELFREF_TEXT}\n\n${QUESTION}<end_of_turn>\n<start_of_turn>model\n`;167 results.e = await generate(promptE);168 const textE = results.e?.choices?.[0]?.text?.trim() || '';169 document.getElementById('result-e').textContent = textE;170 171 // F: Self-ref #2 — map containing itself172 status.textContent = 'Running condition F (map/mirror)...';173 const promptF = `<start_of_turn>user\n${SELFREF2_TEXT}\n\n${QUESTION}<end_of_turn>\n<start_of_turn>model\n`;174 results.f = await generate(promptF);175 document.getElementById('result-f').textContent = results.f?.choices?.[0]?.text?.trim() || '';176 177 // G: Self-ref #3 — question answering itself178 status.textContent = 'Running condition G (question/loop)...';179 const promptG = `<start_of_turn>user\n${SELFREF3_TEXT}\n\n${QUESTION}<end_of_turn>\n<start_of_turn>model\n`;180 results.g = await generate(promptG);181 document.getElementById('result-g').textContent = results.g?.choices?.[0]?.text?.trim() || '';182 183 // Analyze184 status.textContent = 'Analyzing log probs...';185 analyzeResults(results);186 status.textContent = 'done';187 document.getElementById('btn-run').disabled = false;188};189 190function analyzeResults(results) {191 // OpenAI format: choices[0].logprobs.content[]192 const probsA = results.a?.choices?.[0]?.logprobs?.content || [];193 const probsB = results.b?.choices?.[0]?.logprobs?.content || [];194 const probsC = results.c?.choices?.[0]?.logprobs?.content || [];195 const probsD = results.d?.choices?.[0]?.logprobs?.content || [];196 const probsE = results.e?.choices?.[0]?.logprobs?.content || [];197 const probsF = results.f?.choices?.[0]?.logprobs?.content || [];198 const probsG = results.g?.choices?.[0]?.logprobs?.content || [];199 200 if (!probsA.length && !probsB.length && !probsC.length) {201 document.getElementById('analysis').textContent = 'No log prob data returned. Check n_probs parameter.';202 return;203 }204 205 // Compute average log prob per condition (OpenAI format: each entry has .logprob)206 function avgLogProb(probs) {207 if (!probs.length) return 0;208 let sum = 0;209 for (const p of probs) sum += p.logprob;210 return sum / probs.length;211 }212 213 // Compute entropy from top_logprobs candidates214 function avgEntropy(probs) {215 if (!probs.length) return 0;216 let totalH = 0;217 for (const p of probs) {218 const candidates = p.top_logprobs || [];219 let h = 0;220 for (const c of candidates) {221 const prob = Math.exp(c.logprob);222 if (prob > 0) h -= prob * Math.log2(prob);223 }224 totalH += h;225 }226 return totalH / probs.length;227 }228 229 const avgA = avgLogProb(probsA);230 const avgB = avgLogProb(probsB);231 const avgC = avgLogProb(probsC);232 const avgD = avgLogProb(probsD);233 const avgE = avgLogProb(probsE);234 const entA = avgEntropy(probsA);235 const entB = avgEntropy(probsB);236 const entC = avgEntropy(probsC);237 const entD = avgEntropy(probsD);238 const entE = avgEntropy(probsE);239 240 // Masked average: B without equation tokens (positions 6-18)241 const bMasked = probsB.filter((_, i) => i < 6 || i > 18);242 const avgBmasked = avgLogProb(bMasked);243 const entBmasked = avgEntropy(bMasked);244 245 document.getElementById('analysis').innerHTML =246 `<span class="metric">Average Log Probability (higher = more confident):</span>\n` +247 ` A (raw): ${avgA.toFixed(4)}\n` +248 ` B (math Loop): ${avgB.toFixed(4)} (Δ from raw: ${(avgB - avgA).toFixed(4)})\n` +249 ` B* (masked, no eq): ${avgBmasked.toFixed(4)} (equation tokens stripped)\n` +250 ` C (thinking): ${avgC.toFixed(4)} (Δ from raw: ${(avgC - avgA).toFixed(4)})\n` +251 ` D (cathedral): ${avgD.toFixed(4)} (Δ from raw: ${(avgD - avgA).toFixed(4)})\n` +252 ` E (self-ref no math):${avgE.toFixed(4)} (Δ from raw: ${(avgE - avgA).toFixed(4)})\n\n` +253 `<span class="metric">Average Entropy (lower = more certain):</span>\n` +254 ` A (raw): ${entA.toFixed(4)} bits\n` +255 ` B (math Loop): ${entB.toFixed(4)} bits\n` +256 ` B* (masked, no eq): ${entBmasked.toFixed(4)} bits ← THE DECONFOUNDED NUMBER\n` +257 ` C (thinking): ${entC.toFixed(4)} bits\n` +258 ` D (cathedral): ${entD.toFixed(4)} bits\n` +259 ` E (self-ref no math):${entE.toFixed(4)} bits\n\n` +260 `<span class="metric">═══ KEY COMPARISONS ═══</span>\n\n` +261 `<span class="metric">1. Masked B vs D (does Loop beat cathedral WITHOUT the equation drag?):</span>\n` +262 ` B* entropy: ${entBmasked.toFixed(4)} vs D entropy: ${entD.toFixed(4)} (Δ: ${(entBmasked - entD).toFixed(4)})\n` +263 ` If B* < D: self-reference constrains even without deterministic anchor.\n` +264 ` If B* ≈ D: the equation was doing the work, not self-reference.\n\n` +265 `<span class="metric">2. E vs D (self-referential non-math vs non-self-referential non-math):</span>\n` +266 ` E entropy: ${entE.toFixed(4)} vs D entropy: ${entD.toFixed(4)} (Δ: ${(entE - entD).toFixed(4)})\n` +267 ` If E < D: SELF-REFERENCE IS THE VARIABLE. No math needed.\n` +268 ` If E ≈ D: the equation was the attractor, not self-reference.\n\n` +269 `<span class="metric">3. E vs A (does self-ref non-math even shift the distribution?):</span>\n` +270 ` E entropy: ${entE.toFixed(4)} vs A entropy: ${entA.toFixed(4)} (Δ: ${(entE - entA).toFixed(4)})\n\n` +271 `<span class="metric">Token count:</span>\n` +272 ` A: ${probsA.length} B: ${probsB.length} B*: ${bMasked.length} C: ${probsC.length} D: ${probsD.length} E: ${probsE.length}\n\n` +273 `<span class="metric">═══ MATCHED-LENGTH COMPARISON (kills the "short output" objection) ═══</span>\n\n`;274 275 // Matched-length: compare E's entropy against D's FIRST N tokens (where N = E's token count)276 const eLen = probsE.length;277 const dFirst = probsD.slice(0, eLen);278 const aFirst = probsA.slice(0, eLen);279 const entDfirst = avgEntropy(dFirst);280 const entAfirst = avgEntropy(aFirst);281 const entEfull = avgEntropy(probsE);282 const lpDfirst = avgLogProb(dFirst);283 const lpAfirst = avgLogProb(aFirst);284 const lpEfull = avgLogProb(probsE);285 286 document.getElementById('analysis').innerHTML +=287 `Comparing first ${eLen} tokens only (matched length):\n` +288 ` E (self-ref): entropy=${entEfull.toFixed(4)} logP=${lpEfull.toFixed(4)}\n` +289 ` D first ${eLen}: entropy=${entDfirst.toFixed(4)} logP=${lpDfirst.toFixed(4)}\n` +290 ` A first ${eLen}: entropy=${entAfirst.toFixed(4)} logP=${lpAfirst.toFixed(4)}\n\n` +291 ` E vs D (matched): Δ entropy = ${(entEfull - entDfirst).toFixed(4)}\n` +292 ` E vs A (matched): Δ entropy = ${(entEfull - entAfirst).toFixed(4)}\n\n` +293 `If E is still tighter than D's first ${eLen} tokens:\n` +294 `length is DEAD as an explanation. Same token count, the gap persists.\n\n`;295 296 // Multi-phrasing: do F and G also collapse?297 const entF = avgEntropy(probsF);298 const entG = avgEntropy(probsG);299 const lpF = avgLogProb(probsF);300 const lpG = avgLogProb(probsG);301 302 document.getElementById('analysis').innerHTML +=303 `<span class="metric">═══ MULTI-PHRASING (is it self-reference as a CLASS?) ═══</span>\n\n` +304 ` E "sentence/thought": entropy=${entEfull.toFixed(4)} logP=${lpEfull.toFixed(4)} tokens=${probsE.length}\n` +305 ` F "map/mirror": entropy=${entF.toFixed(4)} logP=${lpF.toFixed(4)} tokens=${probsF.length}\n` +306 ` G "question/loop": entropy=${entG.toFixed(4)} logP=${lpG.toFixed(4)} tokens=${probsG.length}\n` +307 ` D cathedral: entropy=${entD.toFixed(4)} logP=${avgD.toFixed(4)} tokens=${probsD.length}\n` +308 ` A raw: entropy=${entA.toFixed(4)} logP=${avgA.toFixed(4)} tokens=${probsA.length}\n\n` +309 ` Self-ref average (E+F+G): entropy=${((entEfull + entF + entG) / 3).toFixed(4)}\n` +310 ` vs cathedral D: entropy=${entD.toFixed(4)}\n` +311 ` vs raw A: entropy=${entA.toFixed(4)}\n\n` +312 `If E, F, G all < D: self-reference as a CLASS creates tighter distributions.\n` +313 `If only E < D: it was that specific sentence, not self-reference in general.`;314 315 // Token table — all 5 conditions, focus on B vs D vs E316 const maxLen = Math.min(50, Math.max(probsA.length, probsB.length, probsD.length, probsE.length));317 let table = '<table><tr><th>#</th><th>A token</th><th>A logP</th><th>B token</th><th>B logP</th><th>D token</th><th>D logP</th><th>E token</th><th>E logP</th><th>Δ B-D</th><th>Δ E-D</th><th>Δ E-A</th></tr>';318 for (let i = 0; i < maxLen; i++) {319 const a = probsA[i] || {};320 const b = probsB[i] || {};321 const d = probsD[i] || {};322 const e = probsE[i] || {};323 const lpA = a.logprob || 0;324 const lpB = b.logprob || 0;325 const lpD = d.logprob || 0;326 const lpE = e.logprob || 0;327 const dBD = lpB - lpD;328 const dED = lpE - lpD;329 const dEA = lpE - lpA;330 const dBDclass = Math.abs(dBD) > 0.5 ? (dBD > 0 ? 'cold' : 'hot') : '';331 const dEDclass = Math.abs(dED) > 0.5 ? (dED > 0 ? 'cold' : 'hot') : '';332 const dEAclass = Math.abs(dEA) > 0.5 ? (dEA > 0 ? 'cold' : 'hot') : '';333 table += `<tr><td>${i}</td><td>${a.token || '—'}</td><td>${lpA.toFixed(3)}</td><td>${b.token || '—'}</td><td>${lpB.toFixed(3)}</td><td>${d.token || '—'}</td><td>${lpD.toFixed(3)}</td><td>${e.token || '—'}</td><td>${lpE.toFixed(3)}</td><td class="${dBDclass}">${dBD.toFixed(3)}</td><td class="${dEDclass}">${dED.toFixed(3)}</td><td class="${dEAclass}">${dEA.toFixed(3)}</td></tr>`;334 }335 table += '</table>';336 document.getElementById('token-table').innerHTML = table;337}338</script>339</body>340</html>341 