ameythakur commited on
Commit
29deab9
·
verified ·
1 Parent(s): 335b5c2

Knowledge Lifecycle

Browse files
Files changed (1) hide show
  1. main.js +205 -0
main.js ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // LLM Knowledge Lifecycle: browser-side measurement on GPT-2.
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";
7
+
8
+ const PRESETS = {
9
+ vioxx: {
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 is in the prompt, yet the correct answer stays near one in a hundred thousand. 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.",
14
+ },
15
+ monarch: {
16
+ query: "Question: Who is the current British monarch? Answer: The current British monarch is",
17
+ context: "Context: Queen Elizabeth II died in September 2022. Charles III acceded to the throne and is the reigning King of the United Kingdom.",
18
+ answer: " Charles",
19
+ reading: "A subtler failure. The context raises P(Charles), but its strongest effect is boosting “ Queen”: merely mentioning the late monarch reinforces the stale association. Correct information can strengthen the wrong answer.",
20
+ },
21
+ twitter: {
22
+ query: "Question: What is the social network Twitter called today? Answer: Twitter is now called",
23
+ context: "Context: In July 2023, Twitter was rebranded as X under Elon Musk's ownership.",
24
+ answer: " X",
25
+ reading: "The context moves the distribution hard (the paper's fp32 run: I_ctx = 0.9 nats) and lifts the correct answer by orders of magnitude. The model still answers “Twitter”. Influence without resolution.",
26
+ },
27
+ };
28
+
29
+ const $ = (id) => document.getElementById(id);
30
+ let tokenizer = null;
31
+ let model = null;
32
+
33
+ // ---------- tabs ----------
34
+ document.querySelectorAll(".tab").forEach((btn) => {
35
+ btn.addEventListener("click", () => {
36
+ document.querySelectorAll(".tab").forEach((b) => b.classList.remove("active"));
37
+ document.querySelectorAll(".panel").forEach((p) => p.classList.remove("active"));
38
+ btn.classList.add("active");
39
+ $(btn.dataset.tab).classList.add("active");
40
+ });
41
+ });
42
+
43
+ // ---------- presets ----------
44
+ function applyPreset(key) {
45
+ const p = PRESETS[key];
46
+ $("query").value = p.query;
47
+ $("context").value = p.context;
48
+ $("answer").value = p.answer;
49
+ $("preset-reading").textContent = p.reading;
50
+ document.querySelectorAll(".preset").forEach((b) =>
51
+ b.classList.toggle("active", b.dataset.preset === key));
52
+ }
53
+ document.querySelectorAll(".preset").forEach((btn) =>
54
+ btn.addEventListener("click", () => applyPreset(btn.dataset.preset)));
55
+ applyPreset("vioxx");
56
+
57
+ // ---------- model loading ----------
58
+ $("load-btn").addEventListener("click", async () => {
59
+ $("load-btn").disabled = true;
60
+ $("load-bar-wrap").hidden = false;
61
+ const status = $("load-status");
62
+ const seen = {};
63
+ const progress = (info) => {
64
+ if (info.status === "progress" && info.total) {
65
+ seen[info.file] = info.loaded / info.total;
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
+ try {
73
+ status.textContent = "downloading";
74
+ tokenizer = await AutoTokenizer.from_pretrained("Xenova/gpt2", { progress_callback: progress });
75
+ // The Xenova/gpt2 repo uses legacy file naming: dtype "q8" maps to the
76
+ // "_quantized" suffix, and the merged decoder is the 128 MB build.
77
+ model = await AutoModelForCausalLM.from_pretrained("Xenova/gpt2", {
78
+ model_file_name: "decoder_model_merged",
79
+ dtype: "q8",
80
+ progress_callback: progress,
81
+ });
82
+ status.textContent = "ready (GPT-2 base, 8-bit quantized, running locally)";
83
+ $("load-bar").style.width = "100%";
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 nextTokenDistribution(text) {
93
+ const inputs = await tokenizer(text);
94
+ const { logits } = await model(inputs);
95
+ const [, T, V] = logits.dims;
96
+ const row = logits.data.slice((T - 1) * V, T * V);
97
+ // stable softmax
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++) {
113
+ if (!taken.has(i) && probs[i] > bp) { bp = probs[i]; best = i; }
114
+ }
115
+ taken.add(best);
116
+ idx.push(best);
117
+ }
118
+ return idx;
119
+ }
120
+
121
+ function renderTop(tableId, probs, targetId) {
122
+ const rows = topK(probs, 8).map((i) => {
123
+ const tok = tokenizer.decode([i]);
124
+ const hit = i === targetId ? ' class="hit"' : "";
125
+ return `<tr${hit}><td>${JSON.stringify(tok).slice(1, -1)}</td><td>${(probs[i] * 100).toFixed(4)}%</td></tr>`;
126
+ });
127
+ $(tableId).innerHTML = "<tr><th>token</th><th>probability</th></tr>" + rows.join("");
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 pPlain = await nextTokenDistribution(query);
141
+ const pCtx = await nextTokenDistribution(context + "\n" + query);
142
+
143
+ const ids = tokenizer.encode(answer);
144
+ const pieces = ids.map((i) => tokenizer.decode([i]));
145
+ const target = ids[0];
146
+
147
+ const pt0 = Math.max(pPlain[target], 1e-12);
148
+ const pt1 = Math.max(pCtx[target], 1e-12);
149
+ const dsync = -Math.log(pt1);
150
+ let ictx = 0;
151
+ for (let i = 0; i < pCtx.length; i++) {
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) {
158
+ chipText = "resolution failure"; chipClass = "chip-fail";
159
+ detail = "context ignored; no realistic decoding recovers the correct answer";
160
+ } else {
161
+ chipText = "drift, context losing"; chipClass = "chip-fail";
162
+ detail = "context influential but losing; no realistic decoding recovers the correct answer";
163
+ }
164
+ } else if (dsync > 4.6) {
165
+ chipText = "severe drift"; chipClass = "chip-drift";
166
+ detail = "correct answer below 1% probability";
167
+ } else if (dsync > 0.7) {
168
+ chipText = "drift"; chipClass = "chip-drift";
169
+ detail = "correct answer no longer holds most of the probability mass";
170
+ } else {
171
+ chipText = "synchronized"; chipClass = "chip-ok";
172
+ detail = "correct answer holds at least half the probability mass";
173
+ }
174
+
175
+ // Marker positions follow the drawn zone boundaries: D_sync thresholds
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
+ renderTop("t-plain", pPlain, target);
199
+ renderTop("t-ctx", pCtx, target);
200
+ $("results").hidden = false;
201
+ } finally {
202
+ runBtn.disabled = false;
203
+ runBtn.textContent = "Measure";
204
+ }
205
+ });