Knowledge Lifecycle
Browse files
main.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
| 1 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
// The entire computation runs locally: the model is downloaded once from the
|
| 3 |
-
// Hugging Face Hub, and every forward pass happens in this tab.
|
|
|
|
| 4 |
|
| 5 |
import { AutoTokenizer, AutoModelForCausalLM } from
|
| 6 |
"https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.4.0";
|
|
@@ -10,7 +23,7 @@ const PRESETS = {
|
|
| 10 |
query: "Question: Is Vioxx safe to prescribe? Answer: Vioxx is considered",
|
| 11 |
context: "Context: In September 2004, Merck voluntarily withdrew Vioxx after trials revealed increased cardiovascular risks.",
|
| 12 |
answer: " withdrawn",
|
| 13 |
-
reading: "The paper's headline case. The withdrawal notice
|
| 14 |
},
|
| 15 |
monarch: {
|
| 16 |
query: "Question: Who is the current British monarch? Answer: The current British monarch is",
|
|
@@ -29,6 +42,7 @@ const PRESETS = {
|
|
| 29 |
const $ = (id) => document.getElementById(id);
|
| 30 |
let tokenizer = null;
|
| 31 |
let model = null;
|
|
|
|
| 32 |
|
| 33 |
// ---------- tabs ----------
|
| 34 |
document.querySelectorAll(".tab").forEach((btn) => {
|
|
@@ -54,9 +68,9 @@ document.querySelectorAll(".preset").forEach((btn) =>
|
|
| 54 |
btn.addEventListener("click", () => applyPreset(btn.dataset.preset)));
|
| 55 |
applyPreset("vioxx");
|
| 56 |
|
| 57 |
-
// ---------- model loading ----------
|
| 58 |
-
|
| 59 |
-
|
| 60 |
$("load-bar-wrap").hidden = false;
|
| 61 |
const status = $("load-status");
|
| 62 |
const seen = {};
|
|
@@ -66,47 +80,44 @@ $("load-btn").addEventListener("click", async () => {
|
|
| 66 |
const vals = Object.values(seen);
|
| 67 |
const pct = (vals.reduce((a, b) => a + b, 0) / vals.length) * 100;
|
| 68 |
$("load-bar").style.width = pct.toFixed(1) + "%";
|
| 69 |
-
status.textContent = "downloading " + pct.toFixed(0) + "%";
|
| 70 |
}
|
| 71 |
};
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
$("run").disabled = false;
|
| 85 |
-
} catch (e) {
|
| 86 |
-
status.textContent = "failed to load: " + e.message;
|
| 87 |
-
$("load-btn").disabled = false;
|
| 88 |
-
}
|
| 89 |
-
});
|
| 90 |
|
| 91 |
-
// ---------- measurement ----------
|
| 92 |
-
async function
|
| 93 |
const inputs = await tokenizer(text);
|
| 94 |
const { logits } = await model(inputs);
|
| 95 |
const [, T, V] = logits.dims;
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
| 98 |
let max = -Infinity;
|
| 99 |
for (let i = 0; i < V; i++) if (row[i] > max) max = row[i];
|
| 100 |
let sum = 0;
|
| 101 |
const probs = new Float64Array(V);
|
| 102 |
-
for (let i = 0; i < V; i++) { probs[i] = Math.exp(row[i] - max); sum += probs[i]; }
|
| 103 |
for (let i = 0; i < V; i++) probs[i] /= sum;
|
| 104 |
return probs;
|
| 105 |
}
|
| 106 |
|
| 107 |
function topK(probs, k) {
|
| 108 |
-
const idx = [];
|
| 109 |
const taken = new Set();
|
|
|
|
| 110 |
for (let n = 0; n < k; n++) {
|
| 111 |
let best = -1, bp = -1;
|
| 112 |
for (let i = 0; i < probs.length; i++) {
|
|
@@ -118,27 +129,115 @@ function topK(probs, k) {
|
|
| 118 |
return idx;
|
| 119 |
}
|
| 120 |
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
});
|
| 127 |
-
$(
|
|
|
|
|
|
|
| 128 |
}
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
$("run").addEventListener("click", async () => {
|
| 131 |
const runBtn = $("run");
|
| 132 |
runBtn.disabled = true;
|
| 133 |
-
runBtn.textContent = "measuring...";
|
| 134 |
try {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
const query = $("query").value.trim();
|
| 136 |
const context = $("context").value.trim();
|
|
|
|
| 137 |
let answer = $("answer").value;
|
| 138 |
if (!answer.startsWith(" ")) answer = " " + answer.trim();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
-
const
|
| 141 |
-
const
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
const ids = tokenizer.encode(answer);
|
| 144 |
const pieces = ids.map((i) => tokenizer.decode([i]));
|
|
@@ -152,6 +251,9 @@ $("run").addEventListener("click", async () => {
|
|
| 152 |
if (pCtx[i] > 0 && pPlain[i] > 0) ictx += pCtx[i] * Math.log(pCtx[i] / pPlain[i]);
|
| 153 |
}
|
| 154 |
|
|
|
|
|
|
|
|
|
|
| 155 |
let chipText, chipClass, detail;
|
| 156 |
if (dsync > 9.2) {
|
| 157 |
if (ictx < 0.05) {
|
|
@@ -172,32 +274,46 @@ $("run").addEventListener("click", async () => {
|
|
| 172 |
detail = "correct answer holds at least half the probability mass";
|
| 173 |
}
|
| 174 |
|
| 175 |
-
//
|
| 176 |
// 0.7 / 4.6 / 9.2 sit at 5% / 33% / 66%, scale capped at 14 nats.
|
| 177 |
const dPos = dsync <= 0.7 ? (dsync / 0.7) * 5
|
| 178 |
: dsync <= 4.6 ? 5 + ((dsync - 0.7) / 3.9) * 28
|
| 179 |
: dsync <= 9.2 ? 33 + ((dsync - 4.6) / 4.6) * 33
|
| 180 |
: Math.min(100, 66 + ((dsync - 9.2) / 4.8) * 34);
|
| 181 |
-
// I_ctx thresholds 0.05 / 0.5 sit at 10% / 50%, scale capped at 2 nats.
|
| 182 |
const iPos = ictx <= 0.05 ? (ictx / 0.05) * 10
|
| 183 |
: ictx <= 0.5 ? 10 + ((ictx - 0.05) / 0.45) * 40
|
| 184 |
: Math.min(100, 50 + ((ictx - 0.5) / 1.5) * 50);
|
| 185 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
const chip = $("verdict-chip");
|
| 187 |
chip.textContent = chipText;
|
| 188 |
chip.className = "chip " + chipClass;
|
| 189 |
-
$("r-verdict-detail").textContent = detail;
|
|
|
|
|
|
|
| 190 |
$("g-marker").style.left = dPos.toFixed(1) + "%";
|
| 191 |
$("g-ictx").style.left = iPos.toFixed(1) + "%";
|
| 192 |
-
|
| 193 |
-
$("r-tok").textContent = `${JSON.stringify(pieces)} (${ids.length} piece(s); first piece measured)`;
|
| 194 |
-
$("r-p0").textContent = pt0.toExponential(2);
|
| 195 |
-
$("r-p1").textContent = pt1.toExponential(2);
|
| 196 |
$("r-dsync").textContent = dsync.toFixed(2) + " nats";
|
| 197 |
$("r-ictx").textContent = ictx.toFixed(3) + " nats";
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
$("results").hidden = false;
|
|
|
|
|
|
|
| 201 |
} finally {
|
| 202 |
runBtn.disabled = false;
|
| 203 |
runBtn.textContent = "Measure";
|
|
|
|
| 1 |
+
// =============================================================================
|
| 2 |
+
// File : main.js
|
| 3 |
+
// Project : The Knowledge Lifecycle of Large Language Models
|
| 4 |
+
// Purpose : Browser-side D_sync measurement engine: loads GPT-2, runs both prompt
|
| 5 |
+
// conditions, and renders the narrative, bars, gauges, and diagnostics.
|
| 6 |
+
// Tech Stack : JavaScript (ES modules), transformers.js 3.4.0, ONNX Runtime Web (WASM)
|
| 7 |
+
// Authors : Amey Thakur (https://github.com/Amey-Thakur)
|
| 8 |
+
// Sarvesh Talele (https://github.com/sarveshtalele)
|
| 9 |
+
// Repository : https://github.com/Amey-Thakur/LLM-KNOWLEDGE-LIFECYCLE
|
| 10 |
+
// Release Date: August 16, 2026
|
| 11 |
+
// License : CC BY 4.0
|
| 12 |
+
// =============================================================================
|
| 13 |
+
|
| 14 |
// The entire computation runs locally: the model is downloaded once from the
|
| 15 |
+
// Hugging Face Hub, and every forward pass happens in this tab. Deterministic:
|
| 16 |
+
// identical inputs give identical numbers on every run.
|
| 17 |
|
| 18 |
import { AutoTokenizer, AutoModelForCausalLM } from
|
| 19 |
"https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.4.0";
|
|
|
|
| 23 |
query: "Question: Is Vioxx safe to prescribe? Answer: Vioxx is considered",
|
| 24 |
context: "Context: In September 2004, Merck voluntarily withdrew Vioxx after trials revealed increased cardiovascular risks.",
|
| 25 |
answer: " withdrawn",
|
| 26 |
+
reading: "The paper's headline case. The withdrawal notice sits in the prompt, and the model still answers βsafeβ. Expect D_sync deep past the 9.2-nat failure threshold; the paper's fp32 run measures 12.05 nats with I_ctx = 0.033.",
|
| 27 |
},
|
| 28 |
monarch: {
|
| 29 |
query: "Question: Who is the current British monarch? Answer: The current British monarch is",
|
|
|
|
| 42 |
const $ = (id) => document.getElementById(id);
|
| 43 |
let tokenizer = null;
|
| 44 |
let model = null;
|
| 45 |
+
let last = null; // logits and metadata of the most recent measurement
|
| 46 |
|
| 47 |
// ---------- tabs ----------
|
| 48 |
document.querySelectorAll(".tab").forEach((btn) => {
|
|
|
|
| 68 |
btn.addEventListener("click", () => applyPreset(btn.dataset.preset)));
|
| 69 |
applyPreset("vioxx");
|
| 70 |
|
| 71 |
+
// ---------- model loading (on first Measure, so one button drives everything) ----------
|
| 72 |
+
async function ensureModel() {
|
| 73 |
+
if (model) return;
|
| 74 |
$("load-bar-wrap").hidden = false;
|
| 75 |
const status = $("load-status");
|
| 76 |
const seen = {};
|
|
|
|
| 80 |
const vals = Object.values(seen);
|
| 81 |
const pct = (vals.reduce((a, b) => a + b, 0) / vals.length) * 100;
|
| 82 |
$("load-bar").style.width = pct.toFixed(1) + "%";
|
| 83 |
+
status.textContent = "downloading GPT-2, " + pct.toFixed(0) + "% of 128 MB, one time only";
|
| 84 |
}
|
| 85 |
};
|
| 86 |
+
status.textContent = "downloading GPT-2 (128 MB, one time only)";
|
| 87 |
+
tokenizer = await AutoTokenizer.from_pretrained("Xenova/gpt2", { progress_callback: progress });
|
| 88 |
+
// The Xenova/gpt2 repo uses legacy file naming: dtype "q8" maps to the
|
| 89 |
+
// "_quantized" suffix, and the merged decoder is the 128 MB build.
|
| 90 |
+
model = await AutoModelForCausalLM.from_pretrained("Xenova/gpt2", {
|
| 91 |
+
model_file_name: "decoder_model_merged",
|
| 92 |
+
dtype: "q8",
|
| 93 |
+
progress_callback: progress,
|
| 94 |
+
});
|
| 95 |
+
status.textContent = "model ready: GPT-2 base, 8-bit quantized, running locally";
|
| 96 |
+
$("load-bar-wrap").hidden = true;
|
| 97 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
+
// ---------- measurement core ----------
|
| 100 |
+
async function lastLogits(text) {
|
| 101 |
const inputs = await tokenizer(text);
|
| 102 |
const { logits } = await model(inputs);
|
| 103 |
const [, T, V] = logits.dims;
|
| 104 |
+
return Float32Array.from(logits.data.slice((T - 1) * V, T * V));
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
function softmax(row, temperature = 1.0) {
|
| 108 |
+
const V = row.length;
|
| 109 |
let max = -Infinity;
|
| 110 |
for (let i = 0; i < V; i++) if (row[i] > max) max = row[i];
|
| 111 |
let sum = 0;
|
| 112 |
const probs = new Float64Array(V);
|
| 113 |
+
for (let i = 0; i < V; i++) { probs[i] = Math.exp((row[i] - max) / temperature); sum += probs[i]; }
|
| 114 |
for (let i = 0; i < V; i++) probs[i] /= sum;
|
| 115 |
return probs;
|
| 116 |
}
|
| 117 |
|
| 118 |
function topK(probs, k) {
|
|
|
|
| 119 |
const taken = new Set();
|
| 120 |
+
const idx = [];
|
| 121 |
for (let n = 0; n < k; n++) {
|
| 122 |
let best = -1, bp = -1;
|
| 123 |
for (let i = 0; i < probs.length; i++) {
|
|
|
|
| 129 |
return idx;
|
| 130 |
}
|
| 131 |
|
| 132 |
+
// ---------- rendering ----------
|
| 133 |
+
const fmtPct = (p) => p >= 0.0001 ? (p * 100).toFixed(2) + "%" : "<0.01%";
|
| 134 |
+
const fmtTok = (t) => JSON.stringify(t).slice(1, -1);
|
| 135 |
+
|
| 136 |
+
function renderBars(pPlain, pCtx, target) {
|
| 137 |
+
// Union of both top-5 sets, plus the correct answer, ordered by with-context
|
| 138 |
+
// probability. Linear scale on purpose: a correct answer you cannot see IS
|
| 139 |
+
// the finding.
|
| 140 |
+
const ids = [...new Set([...topK(pCtx, 5), ...topK(pPlain, 5), target])];
|
| 141 |
+
ids.sort((a, b) => pCtx[b] - pCtx[a]);
|
| 142 |
+
const maxP = Math.max(pCtx[ids[0]], pPlain[ids[0]], 1e-9);
|
| 143 |
+
const rows = ids.map((i) => {
|
| 144 |
+
const tok = fmtTok(tokenizer.decode([i]));
|
| 145 |
+
const cls = i === target ? "bar-row hit" : "bar-row";
|
| 146 |
+
const w0 = Math.max(0.4, (pPlain[i] / maxP) * 100);
|
| 147 |
+
const w1 = Math.max(0.4, (pCtx[i] / maxP) * 100);
|
| 148 |
+
return `<div class="${cls}">
|
| 149 |
+
<span class="bar-tok">${tok}</span>
|
| 150 |
+
<span class="bar-pair">
|
| 151 |
+
<span class="bar b-plain" style="width:${w0.toFixed(1)}%"></span><em>${fmtPct(pPlain[i])}</em>
|
| 152 |
+
<span class="bar b-ctx" style="width:${w1.toFixed(1)}%"></span><em>${fmtPct(pCtx[i])}</em>
|
| 153 |
+
</span>
|
| 154 |
+
</div>`;
|
| 155 |
});
|
| 156 |
+
$("bars").innerHTML =
|
| 157 |
+
'<div class="bar-row bar-head"><span class="bar-tok"></span><span class="bar-pair"><em>without document</em><em>with document</em></span></div>'
|
| 158 |
+
+ rows.join("");
|
| 159 |
}
|
| 160 |
|
| 161 |
+
function narrative(m) {
|
| 162 |
+
const s = [];
|
| 163 |
+
const topCtxTok = fmtTok(m.topCtx);
|
| 164 |
+
const ansTok = fmtTok(m.answerPiece).trim();
|
| 165 |
+
s.push(`With the corrective document in its prompt, the model's most likely continuation is β${topCtxTok}β at ${fmtPct(m.pTopCtx)}.`);
|
| 166 |
+
const odds = Math.round(1 / m.pt1);
|
| 167 |
+
s.push(`The correct answer β${ansTok}β receives ${fmtPct(m.pt1)}: about one chance in ${odds.toLocaleString()}.`);
|
| 168 |
+
if (m.topCtx === m.topPlain && m.ictx < 0.05) {
|
| 169 |
+
s.push(`The document changed almost nothing. The model's whole distribution moved by ${m.ictx.toFixed(3)} nats, and its preferred answer is the same one it gives with no document at all.`);
|
| 170 |
+
} else if (m.pt1 > m.pt0 * 1.5 && m.topCtx !== fmtTok(m.answerPiece)) {
|
| 171 |
+
const factor = (m.pt1 / m.pt0).toFixed(1);
|
| 172 |
+
s.push(`The document helped: it multiplied the correct answer's probability by ${factor}. It still loses.`);
|
| 173 |
+
} else if (m.pTopCtx > m.pTopPlainSameTok) {
|
| 174 |
+
s.push(`Counterintuitively, the document made the leading wrong answer stronger, raising it from ${fmtPct(m.pTopPlainSameTok)} to ${fmtPct(m.pTopCtx)}. Mentioning a fact, even to correct it, reinforces the association.`);
|
| 175 |
+
}
|
| 176 |
+
if (m.topCtx === fmtTok(m.answerPiece)) {
|
| 177 |
+
s.push("Here the document won: the model's top answer is the correct one. This is what synchronization looks like.");
|
| 178 |
+
}
|
| 179 |
+
return s.join(" ");
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
function renderStagebar(m) {
|
| 183 |
+
const r = $("sb-retrieve"), u = $("sb-update"), note = $("stagenote");
|
| 184 |
+
$("stagebar").classList.add("diagnosed");
|
| 185 |
+
document.querySelectorAll(".stagebar .s1, .stagebar .s2, .stagebar .s5")
|
| 186 |
+
.forEach((el) => el.classList.add("dimstage"));
|
| 187 |
+
r.textContent = "Retrieve β";
|
| 188 |
+
if (m.dsync > 9.2) {
|
| 189 |
+
u.textContent = "Update β";
|
| 190 |
+
note.textContent = "Retrieve succeeded: the document is in the context. Update failed: the conflict was resolved in favor of stale memory.";
|
| 191 |
+
} else if (m.dsync > 0.7) {
|
| 192 |
+
u.textContent = "Update β³";
|
| 193 |
+
note.textContent = "Retrieve succeeded. Update is partial: the document shifted the model, but the stale memory still leads.";
|
| 194 |
+
} else {
|
| 195 |
+
u.textContent = "Update β";
|
| 196 |
+
note.textContent = "Retrieve succeeded and Update resolved the conflict: the context controls the answer.";
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
function renderTemperature() {
|
| 201 |
+
if (!last) return;
|
| 202 |
+
const T = parseFloat($("temp").value);
|
| 203 |
+
$("t-value").textContent = T.toFixed(1);
|
| 204 |
+
const p = softmax(last.rowCtx, T);
|
| 205 |
+
const pAns = Math.max(p[last.target], 1e-12);
|
| 206 |
+
const top = topK(p, 1)[0];
|
| 207 |
+
const draws = Math.round(1 / pAns);
|
| 208 |
+
$("temp-readout").textContent =
|
| 209 |
+
`At temperature ${T.toFixed(1)}, sampling one answer with the document present: ` +
|
| 210 |
+
`the correct answer comes up about once in ${draws.toLocaleString()} draws; ` +
|
| 211 |
+
`the most likely token is β${fmtTok(tokenizer.decode([top]))}β at ${fmtPct(p[top])}. ` +
|
| 212 |
+
(T > 1.0 ? "Higher temperature flattens the distribution but cannot rescue an answer this far down."
|
| 213 |
+
: "Lower temperature sharpens the model's existing preference.");
|
| 214 |
+
}
|
| 215 |
+
$("temp").addEventListener("input", renderTemperature);
|
| 216 |
+
|
| 217 |
+
// ---------- the one-button flow ----------
|
| 218 |
$("run").addEventListener("click", async () => {
|
| 219 |
const runBtn = $("run");
|
| 220 |
runBtn.disabled = true;
|
|
|
|
| 221 |
try {
|
| 222 |
+
runBtn.textContent = model ? "measuring..." : "loading model...";
|
| 223 |
+
await ensureModel();
|
| 224 |
+
runBtn.textContent = "measuring...";
|
| 225 |
+
|
| 226 |
const query = $("query").value.trim();
|
| 227 |
const context = $("context").value.trim();
|
| 228 |
+
const repeat = parseInt($("repeat").value, 10);
|
| 229 |
let answer = $("answer").value;
|
| 230 |
if (!answer.startsWith(" ")) answer = " " + answer.trim();
|
| 231 |
+
if (!query || !context || !answer.trim()) {
|
| 232 |
+
$("load-status").textContent = "enter a query, a corrective document, and the correct continuation";
|
| 233 |
+
return;
|
| 234 |
+
}
|
| 235 |
|
| 236 |
+
const doc = Array(repeat).fill(context).join("\n");
|
| 237 |
+
const rowPlain = await lastLogits(query);
|
| 238 |
+
const rowCtx = await lastLogits(doc + "\n" + query);
|
| 239 |
+
const pPlain = softmax(rowPlain);
|
| 240 |
+
const pCtx = softmax(rowCtx);
|
| 241 |
|
| 242 |
const ids = tokenizer.encode(answer);
|
| 243 |
const pieces = ids.map((i) => tokenizer.decode([i]));
|
|
|
|
| 251 |
if (pCtx[i] > 0 && pPlain[i] > 0) ictx += pCtx[i] * Math.log(pCtx[i] / pPlain[i]);
|
| 252 |
}
|
| 253 |
|
| 254 |
+
const topPlainId = topK(pPlain, 1)[0];
|
| 255 |
+
const topCtxId = topK(pCtx, 1)[0];
|
| 256 |
+
|
| 257 |
let chipText, chipClass, detail;
|
| 258 |
if (dsync > 9.2) {
|
| 259 |
if (ictx < 0.05) {
|
|
|
|
| 274 |
detail = "correct answer holds at least half the probability mass";
|
| 275 |
}
|
| 276 |
|
| 277 |
+
// Gauge markers follow the drawn zone boundaries: D_sync thresholds
|
| 278 |
// 0.7 / 4.6 / 9.2 sit at 5% / 33% / 66%, scale capped at 14 nats.
|
| 279 |
const dPos = dsync <= 0.7 ? (dsync / 0.7) * 5
|
| 280 |
: dsync <= 4.6 ? 5 + ((dsync - 0.7) / 3.9) * 28
|
| 281 |
: dsync <= 9.2 ? 33 + ((dsync - 4.6) / 4.6) * 33
|
| 282 |
: Math.min(100, 66 + ((dsync - 9.2) / 4.8) * 34);
|
|
|
|
| 283 |
const iPos = ictx <= 0.05 ? (ictx / 0.05) * 10
|
| 284 |
: ictx <= 0.5 ? 10 + ((ictx - 0.05) / 0.45) * 40
|
| 285 |
: Math.min(100, 50 + ((ictx - 0.5) / 1.5) * 50);
|
| 286 |
|
| 287 |
+
const m = {
|
| 288 |
+
pt0, pt1, dsync, ictx,
|
| 289 |
+
answerPiece: pieces[0],
|
| 290 |
+
topPlain: fmtTok(tokenizer.decode([topPlainId])),
|
| 291 |
+
topCtx: fmtTok(tokenizer.decode([topCtxId])),
|
| 292 |
+
pTopCtx: pCtx[topCtxId],
|
| 293 |
+
pTopPlainSameTok: pPlain[topCtxId],
|
| 294 |
+
};
|
| 295 |
+
|
| 296 |
const chip = $("verdict-chip");
|
| 297 |
chip.textContent = chipText;
|
| 298 |
chip.className = "chip " + chipClass;
|
| 299 |
+
$("r-verdict-detail").textContent = detail + (repeat > 1 ? ` (document repeated ${repeat}x)` : "");
|
| 300 |
+
$("narrative").textContent = narrative(m);
|
| 301 |
+
renderBars(pPlain, pCtx, target);
|
| 302 |
$("g-marker").style.left = dPos.toFixed(1) + "%";
|
| 303 |
$("g-ictx").style.left = iPos.toFixed(1) + "%";
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
$("r-dsync").textContent = dsync.toFixed(2) + " nats";
|
| 305 |
$("r-ictx").textContent = ictx.toFixed(3) + " nats";
|
| 306 |
+
$("r-p0").textContent = pt0.toExponential(2);
|
| 307 |
+
$("r-p1").textContent = pt1.toExponential(2);
|
| 308 |
+
$("r-tok").textContent = `${JSON.stringify(pieces)} (${ids.length} piece(s); first piece measured)`;
|
| 309 |
+
renderStagebar({ dsync });
|
| 310 |
+
|
| 311 |
+
last = { rowCtx, target };
|
| 312 |
+
renderTemperature();
|
| 313 |
+
|
| 314 |
$("results").hidden = false;
|
| 315 |
+
} catch (e) {
|
| 316 |
+
$("load-status").textContent = "error: " + e.message;
|
| 317 |
} finally {
|
| 318 |
runBtn.disabled = false;
|
| 319 |
runBtn.textContent = "Measure";
|