Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Probe Setup: Interactive</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" /> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" /> | |
| <script src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script> | |
| <style> | |
| foreignObject .katex { font-size: 1em; } | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| background: #fff; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 100vh; | |
| padding: 32px; | |
| } | |
| .scoreBar { transition: width 0.5s cubic-bezier(.4,0,.2,1); } | |
| /* Color-blind friendly mode: swaps red/blue channels so the red | |
| degeneration signal becomes blue, kept in sync with the toggle in the | |
| "Visualization of Internal Representation" section via localStorage. */ | |
| body.colorblind-mode #viz { filter: url(#cb-recolor-filter); } | |
| </style> | |
| </head> | |
| <body> | |
| <svg id="viz" width="1110" height="534" viewBox="0 0 1060 510" | |
| style="font-family:Helvetica, Arial, sans-serif; display:block;"></svg> | |
| <svg width="0" height="0" style="position:absolute;overflow:hidden" aria-hidden="true"> | |
| <filter id="cb-recolor-filter" color-interpolation-filters="sRGB"> | |
| <feColorMatrix type="matrix" values="0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0" /> | |
| </filter> | |
| </svg> | |
| <script> | |
| // Sync color-blind mode with the toggle in the visualization section | |
| // (shared via localStorage; "storage" fires across same-origin iframes). | |
| (function() { | |
| function apply(on) { document.body.classList.toggle('colorblind-mode', on); } | |
| apply(localStorage.getItem('cb-mode') === '1'); | |
| window.addEventListener('storage', function(e) { | |
| if (e.key === 'cb-mode') apply(e.newValue === '1'); | |
| }); | |
| })(); | |
| </script> | |
| <script> | |
| const svg = document.getElementById('viz'); | |
| const ns = 'http://www.w3.org/2000/svg'; | |
| function el(tag, attrs, text) { | |
| const e = document.createElementNS(ns, tag); | |
| for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v); | |
| if (text !== undefined) e.textContent = text; | |
| return e; | |
| } | |
| function txt(x, y, str, attrs = {}) { | |
| return el('text', { x, y, 'text-anchor': 'middle', | |
| 'font-family': 'Inter,sans-serif', 'font-size': 13, fill: '#4b5563', ...attrs }, str); | |
| } | |
| // Render a LaTeX formula into the SVG via a foreignObject + KaTeX, centered on (cx, baselineY). | |
| // suffix: optional plain-text string appended after the formula (e.g. " Β· label"). | |
| function mathFO(cx, baselineY, tex, fontSize, color, suffix) { | |
| const fo = el('foreignObject', { x: cx, y: baselineY - fontSize * 0.32, width: 1, height: 1 }); | |
| fo.setAttribute('style', 'overflow: visible;'); | |
| const div = document.createElement('div'); | |
| div.style.cssText = 'position:absolute; left:0; top:0; transform:translate(-50%,-50%); ' | |
| + 'white-space:nowrap; line-height:1; font-size:' + fontSize + 'px; color:' + color + ';'; | |
| div.innerHTML = katex.renderToString(tex, { throwOnError: false }) | |
| + (suffix ? '<span style="font-family:Inter,sans-serif;">' + suffix + '</span>' : ''); | |
| fo.appendChild(div); | |
| return fo; | |
| } | |
| function arrowEl(x1, y1, x2, y2, color = '#9ca3af', dash = '') { | |
| const g = el('g', {}); | |
| g.appendChild(el('line', { x1, y1, x2, y2, stroke: color, 'stroke-width': 2, | |
| 'stroke-linecap': 'round', ...(dash ? { 'stroke-dasharray': dash } : {}) })); | |
| const a = Math.atan2(y2 - y1, x2 - x1), L = 8, s = 0.42; | |
| g.appendChild(el('polygon', { | |
| points: `${x2},${y2} ${x2 - L*Math.cos(a-s)},${y2 - L*Math.sin(a-s)} ${x2 - L*Math.cos(a+s)},${y2 - L*Math.sin(a+s)}`, | |
| fill: color | |
| })); | |
| return g; | |
| } | |
| // curved connector (S-curve) with arrowhead at the end | |
| function arrowCurve(x1, y1, x2, y2, color = '#9ca3af') { | |
| const g = el('g', {}); | |
| const mx = (x1 + x2) / 2; | |
| g.appendChild(el('path', { | |
| d: `M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`, | |
| stroke: color, 'stroke-width': 2, fill: 'none', 'stroke-linecap': 'round' | |
| })); | |
| const a = 0, L = 8, s = 0.45; | |
| g.appendChild(el('polygon', { | |
| points: `${x2},${y2} ${x2 - L*Math.cos(a-s)},${y2 - L*Math.sin(a-s)} ${x2 - L*Math.cos(a+s)},${y2 - L*Math.sin(a+s)}`, | |
| fill: color | |
| })); | |
| return g; | |
| } | |
| // auto-width token chip; returns next x | |
| function chip(parent, x, y, text, bg, tc) { | |
| const w = Math.max(text.length * 6 + 10, 20), h = 16; | |
| parent.appendChild(el('rect', { x, y, width: w, height: h, rx: 0, fill: bg })); | |
| parent.appendChild(el('text', { | |
| x: x + w/2, y: y + 11.5, 'text-anchor': 'middle', | |
| 'font-size': 9, 'font-weight': 600, fill: tc, | |
| 'font-family': 'JetBrains Mono, monospace' | |
| }, text)); | |
| return x + w + 4; | |
| } | |
| // fixed-width chip (for aligned tables); returns {rect, text} for later restyling | |
| function chipFixed(parent, x, y, text, w, h, bg, tc) { | |
| const rect = el('rect', { x, y, width: w, height: h, rx: 0, fill: bg }); | |
| parent.appendChild(rect); | |
| const t = el('text', { | |
| x: x + w/2, y: y + h/2 + 3.2, 'text-anchor': 'middle', | |
| 'font-size': 8.5, 'font-weight': 600, fill: tc, | |
| 'font-family': 'JetBrains Mono, monospace' | |
| }, text); | |
| parent.appendChild(t); | |
| return { rect, text: t }; | |
| } | |
| // ββ Palette (minimal: neutral grays + one signal color) βββ | |
| const C = { | |
| line: '#9ca3af', | |
| faint: '#d1d5db', | |
| rest: '#e5e7eb', | |
| sel: '#374151', | |
| selStk: '#1f2937', | |
| ink: '#374151', | |
| mute: '#9ca3af', | |
| signal: '#dc2626', // degeneration: the only meaningful color | |
| signalBg:'#fef2f2', | |
| okBg: '#f3f4f6', | |
| okInk: '#4b5563', | |
| barLow: '#cbd5e1', | |
| }; | |
| const THRESH = 0.5; | |
| // Single source of truth for the generated response + probe scores. | |
| // s = probe repetition score Ε·[t] in [0,1]; high (>THRESH) = degeneration. | |
| // y = deterministic repetition score computed beforehand (training target). | |
| const RESPONSE = [ | |
| { t: 'Fever', s: 0.05, y: 0.00 }, | |
| { t: 'results', s: 0.07, y: 0.02 }, | |
| { t: 'from', s: 0.05, y: 0.00 }, | |
| { t: 'immune', s: 0.10, y: 0.04 }, | |
| { t: 'response', s: 0.09, y: 0.05 }, | |
| { t: 'infection', s: 0.66, y: 0.74 }, | |
| { t: 'infection', s: 0.83, y: 0.88 }, | |
| { t: 'to', s: 0.78, y: 0.86 }, | |
| { t: 'infection', s: 0.90, y: 0.95 }, | |
| { t: 'infection', s: 0.93, y: 0.97 }, | |
| ]; | |
| const isHi = s => s >= THRESH; | |
| // ββ Per-layer variation βββββββββββββββββββββββββββββββββββ | |
| // Deterministic pseudo-noise so each layer looks distinct but stable. | |
| function rand(i, k) { | |
| const x = Math.sin(i * 127.1 + k * 311.7) * 43758.5453; | |
| return x - Math.floor(x); | |
| } | |
| // Probe quality by depth: poor in early layers, sharp in the deep ones. | |
| function quality(i) { | |
| const t = i / (N_LAYERS - 1); | |
| let q = Math.max(0, Math.min(1, (t - 0.04) / 0.5)); | |
| q = q * q * (3 - 2 * q); // smoothstep | |
| if (t > 0.82) q *= 1 - (t - 0.82) * 0.8; // slight late-layer decline | |
| return q; | |
| } | |
| // Probe score for a token at layer i: collapses toward an uninformative | |
| // baseline (β0.36) at shallow layers, sharpens toward the true score deep down. | |
| function layerScore(i, tok, k) { | |
| const base = 0.36, q = quality(i); | |
| const jit = (rand(i, k) - 0.5) * 0.22 * (1 - q); // noisier when uninformative | |
| const s = base + (tok.s - base) * q + jit; | |
| return Math.max(0.02, Math.min(0.98, s)); | |
| } | |
| // ββ Layout constants βββββββββββββββββββββββββββββββββββββ | |
| const CY = 250; | |
| const N_LAYERS = 32; | |
| const REKT_W = 72; | |
| const REKT_H = 9; | |
| const REKT_GAP = 3; | |
| const totalH = N_LAYERS * REKT_H + (N_LAYERS - 1) * REKT_GAP; | |
| const stackTopY = CY - totalH / 2; | |
| const stackCX = 350; | |
| const stackRight = stackCX + REKT_W / 2; | |
| const stackLeft = stackCX - REKT_W / 2; | |
| const layer0CY = stackTopY + REKT_H / 2; | |
| const DEFAULT_LAYER = 16; | |
| // Prompt box | |
| const PBX = 18, PBY = 100, PBW = 188, PBH = 300; | |
| // Probe panel | |
| const PRX = 470, PRW = 330, PRH = 364; | |
| const PRY = CY - PRH / 2; | |
| // ββ Groups (render order) βββββββββββββββββββββββββββββββββ | |
| const fanGroup = el('g', {}); | |
| const probeGroup = el('g', {}); | |
| const stackGroup = el('g', {}); | |
| const pBox = el('g', {}); | |
| const arrowGrp = el('g', {}); | |
| // ββ PROMPT / RESPONSE BOX ββββββββββββββββββββββββββββββββ | |
| pBox.appendChild(el('rect', { | |
| x: PBX, y: PBY, width: PBW, height: PBH, rx: 0, | |
| fill: '#fff', stroke: C.faint, 'stroke-width': 1.5 | |
| })); | |
| pBox.appendChild(txt(PBX+PBW/2, PBY+22, 'Input Sequence', | |
| { 'font-size': 12, 'font-weight': 700, fill: C.ink })); | |
| pBox.appendChild(txt(PBX+PBW/2, PBY+36, 'Apertus-8B Β· instruct mode', | |
| { 'font-size': 8, fill: C.mute, 'font-style': 'normal' })); | |
| // PROMPT section | |
| pBox.appendChild(el('text', { | |
| x: PBX+12, y: PBY+58, 'font-size': 7.5, 'font-weight': 700, fill: C.mute, | |
| 'font-family': 'Inter,sans-serif', 'letter-spacing': '0.1em' | |
| }, 'PROMPT')); | |
| let cx = PBX+12; | |
| cx = chip(pBox, cx, PBY+64, 'What', C.okBg, C.okInk); | |
| cx = chip(pBox, cx, PBY+64, 'causes', C.okBg, C.okInk); | |
| cx = PBX+12; | |
| cx = chip(pBox, cx, PBY+84, 'fever', C.okBg, C.okInk); | |
| cx = chip(pBox, cx, PBY+84, 'in', C.okBg, C.okInk); | |
| cx = chip(pBox, cx, PBY+84, 'patients?',C.okBg, C.okInk); | |
| pBox.appendChild(el('line', { | |
| x1: PBX+12, y1: PBY+110, x2: PBX+PBW-12, y2: PBY+110, | |
| stroke: C.rest, 'stroke-width': 1 | |
| })); | |
| // RESPONSE section: generated from RESPONSE (wraps within box) | |
| pBox.appendChild(el('text', { | |
| x: PBX+12, y: PBY+126, 'font-size': 7.5, 'font-weight': 700, fill: C.mute, | |
| 'font-family': 'Inter,sans-serif', 'letter-spacing': '0.1em' | |
| }, 'RESPONSE')); | |
| let rx = PBX+12, ry = PBY+132; | |
| const maxX = PBX + PBW - 12; | |
| for (const tok of RESPONSE) { | |
| const w = Math.max(tok.t.length*6+10, 20); | |
| if (rx + w > maxX) { rx = PBX+12; ry += 20; } | |
| const hi = isHi(tok.s); | |
| chip(pBox, rx, ry, tok.t, hi ? C.signalBg : C.okBg, hi ? C.signal : C.okInk); | |
| rx += w + 4; | |
| } | |
| pBox.appendChild(txt(PBX+PBW/2, PBY+PBH-14, 'Β· Β· Β·', { 'font-size': 13, fill: C.faint })); | |
| // ββ ARROW: prompt box β Layer 0 ββββββββββββββββββββββββββ | |
| const ax1 = PBX + PBW + 3; | |
| const ax2 = stackLeft - 6; | |
| arrowGrp.appendChild(arrowCurve(ax1, PBY + 110, ax2, layer0CY, C.line)); | |
| arrowGrp.appendChild(txt((ax1+ax2)/2 + 4, layer0CY - 10, 'tokens β Layer 0', | |
| { 'font-size': 9, fill: C.mute, 'font-style': 'normal' })); | |
| // ββ STACK ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| stackGroup.appendChild(txt(stackCX, stackTopY-30, 'Apertus-8B', | |
| { 'font-size': 13, 'font-weight': 700, fill: C.ink })); | |
| stackGroup.appendChild(txt(stackCX, stackTopY-16, '32 transformer layers', | |
| { 'font-size': 10, fill: C.mute })); | |
| stackGroup.appendChild(txt(stackRight + 8, stackTopY + 9, 'Layer 0', | |
| { 'font-size': 9, fill: C.faint, 'text-anchor': 'start' })); | |
| stackGroup.appendChild(txt(stackRight + 8, | |
| stackTopY + (N_LAYERS-1) * (REKT_H+REKT_GAP) + 9, | |
| 'Layer 31', { 'font-size': 9, fill: C.faint, 'text-anchor': 'start' })); | |
| stackGroup.appendChild(txt(stackCX, stackTopY + totalH + 20, | |
| 'hover layers: activations & probe scores change', | |
| { 'font-size': 10, fill: C.faint, 'font-style': 'normal' })); | |
| const hoverLabel = txt(stackRight + 8, stackTopY, '', | |
| { 'font-size': 9, fill: C.sel, 'font-weight': 700, 'text-anchor': 'start', opacity: 0 }); | |
| const layerRects = []; | |
| for (let i = 0; i < N_LAYERS; i++) { | |
| const lry = stackTopY + i * (REKT_H + REKT_GAP); | |
| const r = el('rect', { | |
| x: stackLeft, y: lry, width: REKT_W, height: REKT_H, rx: 0, | |
| fill: C.rest, stroke: C.faint, 'stroke-width': 0.8 | |
| }); | |
| r.style.cursor = 'pointer'; | |
| stackGroup.appendChild(r); | |
| layerRects.push(r); | |
| } | |
| stackGroup.appendChild(hoverLabel); | |
| // ββ PROBE PANEL ββββββββββββββββββββββββββββββββββββββββββ | |
| probeGroup.appendChild(el('rect', { | |
| x: PRX, y: PRY, width: PRW, height: PRH, rx: 0, | |
| fill: '#fff', stroke: C.faint, 'stroke-width': 1.5 | |
| })); | |
| const probeTitleEl = txt(PRX + PRW/2, PRY+22, 'Probe: Layer ?', | |
| { 'font-size': 13, 'font-weight': 700, fill: C.ink }); | |
| probeGroup.appendChild(probeTitleEl); | |
| probeGroup.appendChild(txt(PRX + PRW/2, PRY+37, 'a linear head reading the layer activations', | |
| { 'font-size': 8.5, fill: C.mute, 'font-style': 'normal' })); | |
| // ββ Mechanism strip: hβ (4096) β W β Ε·[t] βββββββββββββββββ | |
| const mY = PRY + 90; // mechanism centre line | |
| // hβ activation vector (segmented to suggest 4096 dims) | |
| const vx = PRX+26, vTop = mY-30, vw = 20, vh = 60; | |
| const cells = 9, ch = vh / cells; | |
| const actCells = []; | |
| for (let k = 0; k < cells; k++) { | |
| const rect = el('rect', { | |
| x: vx, y: vTop + k*ch + 0.5, width: vw, height: ch-1, fill: '#d1d5db' | |
| }); | |
| probeGroup.appendChild(rect); | |
| actCells.push(rect); // recoloured per layer in show() | |
| } | |
| probeGroup.appendChild(el('rect', { | |
| x: vx, y: vTop, width: vw, height: vh, rx: 0, | |
| fill: 'none', stroke: C.faint, 'stroke-width': 1.2 | |
| })); | |
| probeGroup.appendChild(txt(vx+vw/2, vTop-7, 'hβ[t]', | |
| { 'font-size': 9, fill: C.ink, 'font-weight': 700 })); | |
| probeGroup.appendChild(txt(vx+vw/2, vTop+vh+12, '4096', | |
| { 'font-size': 8.5, fill: C.mute, 'font-weight': 600 })); | |
| probeGroup.appendChild(txt(vx+vw/2, vTop+vh+22, 'activations', | |
| { 'font-size': 7.5, fill: C.mute })); | |
| // hβ β W | |
| const wbx = PRX+96, wbw = 44, wbh = 40, wby = mY-wbh/2; | |
| probeGroup.appendChild(arrowEl(vx+vw+2, mY, wbx-2, mY, C.line)); | |
| probeGroup.appendChild(el('rect', { | |
| x: wbx, y: wby, width: wbw, height: wbh, rx: 0, | |
| fill: '#fff', stroke: C.faint, 'stroke-width': 1.5 | |
| })); | |
| probeGroup.appendChild(txt(wbx+wbw/2, wby+11, 'linear', | |
| { 'font-size': 7, fill: C.mute })); | |
| probeGroup.appendChild(txt(wbx+wbw/2, wby+wbh/2+8, 'W', | |
| { 'font-size': 16, 'font-weight': 700, fill: C.ink })); | |
| // W β Ε·[t] | |
| const yNodeX = PRX+210; | |
| probeGroup.appendChild(arrowEl(wbx+wbw+2, mY, yNodeX-26, mY, C.line)); | |
| probeGroup.appendChild(mathFO(yNodeX, mY-4, '\\hat{y}[t]', 12, C.ink)); | |
| probeGroup.appendChild(mathFO(yNodeX, mY+9, '\\in [0,1]', 8, C.mute)); | |
| probeGroup.appendChild(txt(PRX+PRW-20, mY, 'one score', | |
| { 'font-size': 8, fill: C.mute, 'text-anchor': 'end' })); | |
| probeGroup.appendChild(txt(PRX+PRW-20, mY+10, 'per token', | |
| { 'font-size': 8, fill: C.mute, 'text-anchor': 'end' })); | |
| // dashed connector: Ε·[t] expands into the per-token list below | |
| const listTop = PRY + 152; | |
| probeGroup.appendChild(el('path', { | |
| d: `M ${yNodeX} ${mY+14} L ${yNodeX} ${listTop-14} L ${PRX+30} ${listTop-14}`, | |
| stroke: C.faint, 'stroke-width': 1, 'stroke-dasharray': '4 4', fill: 'none' | |
| })); | |
| // ββ Per-token score list ββββββββββββββββββββββββββββββββββ | |
| const colTokX = PRX+16, tokW = 58; | |
| const barX = PRX+82, barW = 178, barH = 7; | |
| const valX = PRX+PRW-16; | |
| probeGroup.appendChild(el('text', { | |
| x: colTokX, y: listTop-2, 'font-size': 7.5, 'font-weight': 700, | |
| fill: C.mute, 'font-family': 'Inter,sans-serif', 'letter-spacing': '0.05em' | |
| }, 'TOKEN')); | |
| probeGroup.appendChild(txt(barX+barW/2, listTop-2, 'repetition score Ε·[t]', | |
| { 'font-size': 7.5, 'font-weight': 700, fill: C.mute, 'letter-spacing': '0.05em' })); | |
| probeGroup.appendChild(el('text', { | |
| x: valX, y: listTop-2, 'font-size': 7.5, 'font-weight': 700, | |
| fill: C.mute, 'text-anchor': 'end', 'font-family': 'Inter,sans-serif' | |
| }, 'VAL')); | |
| probeGroup.appendChild(el('line', { | |
| x1: colTokX, y1: listTop+2, x2: valX, y2: listTop+2, | |
| stroke: C.rest, 'stroke-width': 1 | |
| })); | |
| const rowH = 18, rowY0 = listTop + 12, chipH = 13; | |
| const scoreRows = []; | |
| RESPONSE.forEach((tok, k) => { | |
| const yy = rowY0 + k*rowH; | |
| const by = yy + (chipH-barH)/2; | |
| const chip = chipFixed(probeGroup, colTokX, yy, tok.t, tokW, chipH, C.okBg, C.okInk); | |
| // bar track | |
| probeGroup.appendChild(el('rect', { | |
| x: barX, y: by, width: barW, height: barH, rx: 0, fill: C.okBg | |
| })); | |
| // bar fill (width/colour set per layer) | |
| const fill = el('rect', { x: barX, y: by, width: 0, height: barH, rx: 0, fill: C.barLow }); | |
| fill.setAttribute('class', 'scoreBar'); | |
| probeGroup.appendChild(fill); | |
| // value (set per layer) | |
| const valText = el('text', { | |
| x: valX, y: yy + chipH/2 + 3.2, 'text-anchor': 'end', | |
| 'font-size': 8.5, 'font-weight': 700, fill: C.okInk, | |
| 'font-family': 'JetBrains Mono, monospace' | |
| }, ''); | |
| probeGroup.appendChild(valText); | |
| scoreRows.push({ tok, k, chip, fill, valText, target: 0 }); | |
| }); | |
| // ββ TRAINING SIGNAL: MSE (comparison table) βββββββββββββ | |
| const TSX = 824, TSW = 210, TSY = 96, TSH = 320; | |
| probeGroup.appendChild(el('rect', { | |
| x: TSX, y: TSY, width: TSW, height: TSH, rx: 0, | |
| fill: '#fff', stroke: C.faint, 'stroke-width': 1.5 | |
| })); | |
| probeGroup.appendChild(txt(TSX+TSW/2, TSY+22, 'Training signal: MSE', | |
| { 'font-size': 12, 'font-weight': 700, fill: C.ink })); | |
| probeGroup.appendChild(mathFO(TSX+TSW/2, TSY+41, '\\mathcal{L} = \\tfrac{1}{N}\\sum (\\hat{y} - y)^2', 11, C.mute)); | |
| // column centres | |
| const tcTok = TSX+12, tcW = 54; | |
| const tcY = TSX+100, tcYh = TSX+140, tcErr = TSX+186; | |
| const hY2 = TSY+66; | |
| probeGroup.appendChild(el('text', { x: tcTok, y: hY2, 'font-size': 7.5, 'font-weight': 700, | |
| fill: C.mute, 'font-family': 'Inter,sans-serif', 'letter-spacing': '0.05em' }, 'TOKEN')); | |
| probeGroup.appendChild(mathFO(tcY, hY2, 'y', 9, C.mute)); | |
| probeGroup.appendChild(mathFO(tcYh, hY2, '\\hat{y}', 9, C.mute)); | |
| probeGroup.appendChild(mathFO(tcErr, hY2, '|\\hat{y} - y|', 8.5, C.mute)); | |
| probeGroup.appendChild(el('line', { x1: tcTok, y1: hY2+5, x2: TSX+TSW-12, y2: hY2+5, | |
| stroke: C.rest, 'stroke-width': 1 })); | |
| const tsRowH = 18, tsRowY0 = TSY+82, tsChipH = 13; | |
| const tsRows = []; | |
| RESPONSE.forEach((tok, k) => { | |
| const yy = tsRowY0 + k*tsRowH; | |
| const chip = chipFixed(probeGroup, tcTok, yy, tok.t, tcW, tsChipH, C.okBg, C.okInk); | |
| // y target (fixed, deterministic) | |
| probeGroup.appendChild(el('text', { x: tcY, y: yy+tsChipH/2+3.2, 'text-anchor': 'middle', | |
| 'font-size': 8.5, fill: C.mute, 'font-family': 'JetBrains Mono, monospace' }, tok.y.toFixed(2))); | |
| // Ε· prediction (per layer) | |
| const yhText = el('text', { x: tcYh, y: yy+tsChipH/2+3.2, 'text-anchor': 'middle', | |
| 'font-size': 8.5, 'font-weight': 700, fill: C.okInk, 'font-family': 'JetBrains Mono, monospace' }, ''); | |
| probeGroup.appendChild(yhText); | |
| // |Ε·βy| error (per layer) | |
| const errText = el('text', { x: tcErr, y: yy+tsChipH/2+3.2, 'text-anchor': 'middle', | |
| 'font-size': 8.5, 'font-weight': 600, fill: C.ink, 'font-family': 'JetBrains Mono, monospace' }, ''); | |
| probeGroup.appendChild(errText); | |
| tsRows.push({ chip, yhText, errText }); | |
| }); | |
| // loss readout | |
| probeGroup.appendChild(el('line', { x1: tcTok, y1: TSY+TSH-42, x2: TSX+TSW-12, y2: TSY+TSH-42, | |
| stroke: C.rest, 'stroke-width': 1 })); | |
| const lossFO = el('foreignObject', { x: TSX+TSW/2, y: (TSY+TSH-20) - 14 * 0.32, width: 1, height: 1 }); | |
| lossFO.setAttribute('style', 'overflow: visible;'); | |
| const lossDiv = document.createElement('div'); | |
| lossDiv.style.cssText = 'position:absolute; left:0; top:0; transform:translate(-50%,-50%); ' | |
| + 'white-space:nowrap; line-height:1; font-size:14px; color:' + C.sel + ';'; | |
| lossFO.appendChild(lossDiv); | |
| probeGroup.appendChild(lossFO); | |
| // forward arrow: probe scores Ε· β training signal | |
| probeGroup.appendChild(arrowEl(PRX+PRW+2, CY, TSX-3, CY, C.line)); | |
| // backprop: dashed loop from the loss back into the probe weights W | |
| probeGroup.appendChild(el('path', { | |
| d: `M ${TSX+TSW-8} ${TSY+TSH-20} L 1044 ${TSY+TSH-20} L 1044 30 L 530 30 L 530 ${mY} L 555 ${mY}`, | |
| stroke: C.line, 'stroke-width': 1.5, fill: 'none', 'stroke-dasharray': '5 4' | |
| })); | |
| probeGroup.appendChild(el('polygon', { | |
| points: `${wbx-2},${mY} ${wbx-11},${mY-4.5} ${wbx-11},${mY+4.5}`, fill: C.line | |
| })); | |
| probeGroup.appendChild(mathFO(787, 24, '\\partial\\mathcal{L}/\\partial W', 9.5, C.ink, 'Β Β Β·Β Β parameters updating')); | |
| // ββ ASSEMBLY βββββββββββββββββββββββββββββββββββββββββββββ | |
| svg.appendChild(fanGroup); | |
| svg.appendChild(probeGroup); | |
| svg.appendChild(pBox); | |
| svg.appendChild(arrowGrp); | |
| svg.appendChild(stackGroup); | |
| // ββ Interaction ββββββββββββββββββββββββββββββββββββββββββ | |
| function updateFan(i) { | |
| while (fanGroup.firstChild) fanGroup.removeChild(fanGroup.firstChild); | |
| const ly = stackTopY + i * (REKT_H + REKT_GAP); | |
| fanGroup.appendChild(el('polygon', { | |
| points: `${stackRight},${ly} ${PRX},${PRY} ${PRX},${PRY+PRH} ${stackRight},${ly+REKT_H}`, | |
| fill: '#f8fafc', stroke: 'none' | |
| })); | |
| fanGroup.appendChild(el('line', { | |
| x1: stackRight, y1: ly, x2: PRX, y2: PRY, | |
| stroke: C.faint, 'stroke-width': 1, 'stroke-dasharray': '5 4' | |
| })); | |
| fanGroup.appendChild(el('line', { | |
| x1: stackRight, y1: ly+REKT_H, x2: PRX, y2: PRY+PRH, | |
| stroke: C.faint, 'stroke-width': 1, 'stroke-dasharray': '5 4' | |
| })); | |
| } | |
| function animateBars() { | |
| scoreRows.forEach(r => r.fill.setAttribute('width', 0)); | |
| requestAnimationFrame(() => requestAnimationFrame(() => { | |
| scoreRows.forEach(r => r.fill.setAttribute('width', r.target)); | |
| })); | |
| } | |
| // recolour the 4096-activation cells + per-token scores + loss for layer i | |
| function updateForLayer(i) { | |
| // activations: grayscale pattern shifts with the layer | |
| actCells.forEach((c, k) => { | |
| const v = rand(i, k * 3.1 + 1); | |
| c.setAttribute('fill', `hsl(220,9%,${Math.round(82 - v * 42)}%)`); | |
| }); | |
| // scores: recompute, recolour, update values | |
| let loss = 0; | |
| scoreRows.forEach(row => { | |
| const ls = layerScore(i, row.tok, row.k); | |
| loss += (ls - row.tok.y) ** 2; | |
| row.target = ls * barW; | |
| const hi = ls >= THRESH; | |
| row.fill.setAttribute('fill', hi ? C.signal : C.barLow); | |
| row.valText.textContent = ls.toFixed(2); | |
| row.valText.setAttribute('fill', hi ? C.signal : C.okInk); | |
| row.chip.rect.setAttribute('fill', hi ? C.signalBg : C.okBg); | |
| row.chip.text.setAttribute('fill', hi ? C.signal : C.okInk); | |
| // training-signal comparison table | |
| const ts = tsRows[row.k]; | |
| ts.yhText.textContent = ls.toFixed(2); | |
| ts.yhText.setAttribute('fill', hi ? C.signal : C.okInk); | |
| ts.errText.textContent = Math.abs(ls - row.tok.y).toFixed(2); | |
| ts.chip.rect.setAttribute('fill', hi ? C.signalBg : C.okBg); | |
| ts.chip.text.setAttribute('fill', hi ? C.signal : C.okInk); | |
| }); | |
| lossDiv.innerHTML = katex.renderToString('\\mathcal{L} = ' + (loss / scoreRows.length).toFixed(4), { throwOnError: false }); | |
| } | |
| let current = null; | |
| function show(i) { | |
| if (current === i) return; | |
| current = i; | |
| layerRects.forEach((r, idx) => { | |
| r.setAttribute('fill', idx === i ? C.sel : C.rest); | |
| r.setAttribute('stroke', idx === i ? C.selStk : C.faint); | |
| r.setAttribute('stroke-width', idx === i ? 1.5 : 0.8); | |
| }); | |
| const ly = stackTopY + i * (REKT_H + REKT_GAP); | |
| hoverLabel.setAttribute('y', ly + 9); | |
| hoverLabel.textContent = `Layer ${i}`; | |
| hoverLabel.setAttribute('opacity', 1); | |
| probeTitleEl.textContent = `Probe: Layer ${i}`; | |
| updateFan(i); | |
| updateForLayer(i); | |
| animateBars(); | |
| } | |
| layerRects.forEach((r, i) => r.addEventListener('mouseenter', () => show(i))); | |
| // keep the probe attached at the middle layer by default | |
| svg.addEventListener('mouseleave', () => show(DEFAULT_LAYER)); | |
| // boot: probe attached to the middle layer | |
| show(DEFAULT_LAYER); | |
| </script> | |
| </body> | |
| </html> | |