// ── Tab Navigation ──
document.querySelectorAll('.tab').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
btn.classList.add('active');
document.getElementById(btn.dataset.tab).classList.add('active');
});
});
document.querySelectorAll('.next-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelector(`.tab[data-tab="${btn.dataset.next}"]`).click();
window.scrollTo({ top: 0, behavior: 'smooth' });
});
});
// ── Helpers ──
function softmax(scores) {
const max = Math.max(...scores);
const exps = scores.map(s => Math.exp(s - max));
const sum = exps.reduce((a, b) => a + b, 0);
return exps.map(e => e / sum);
}
// ══════════════════════════════════════════
// TAB 1: Softmax Temperature Demo
// ══════════════════════════════════════════
const rawScores = [1.2, 0.5, 3.8, 0.9, 1.5]; // index 2 is the "target"
const scoreLabels = ['slot 0', 'slot 1', 'slot 2', 'slot 3', 'slot 4'];
function renderSoftmaxBars(temp) {
const scaled = rawScores.map(s => s * temp);
const weights = softmax(scaled);
const maxW = Math.max(...weights);
const container = document.getElementById('softmaxBars');
container.innerHTML = '';
weights.forEach((w, i) => {
const col = document.createElement('div');
col.className = 'bar-col';
const wrapper = document.createElement('div');
wrapper.className = 'bar-wrapper';
const bar = document.createElement('div');
bar.className = 'bar' + (w === maxW ? ' winner' : '');
bar.style.height = (w * 130) + 'px';
wrapper.appendChild(bar);
const val = document.createElement('div');
val.className = 'bar-value';
val.textContent = (w * 100).toFixed(1) + '%';
const lbl = document.createElement('div');
lbl.className = 'bar-label';
lbl.textContent = scoreLabels[i] + (i === 2 ? ' ★' : '');
col.append(val, wrapper, lbl);
container.appendChild(col);
});
const insight = document.getElementById('tempInsight');
if (temp < 5) insight.textContent = 'At low temperature, attention is spread out — fuzzy, not useful for exact computation.';
else if (temp < 20) insight.textContent = 'Getting sharper! The target slot is winning, but there\'s still leakage to other slots.';
else insight.textContent = 'Nearly 100% on the target. The softmax now acts like an exact array read — this is how weights produce deterministic lookups.';
}
document.getElementById('tempSlider').addEventListener('input', e => {
const v = +e.target.value;
document.getElementById('tempVal').textContent = v;
renderSoftmaxBars(v);
});
renderSoftmaxBars(1);
// ══════════════════════════════════════════
// TAB 2: Memory Lookup via Attention
// ══════════════════════════════════════════
const memValues = [42, 17, 99, 8, 55, 73];
let queryTarget = 2;
function makeColVec(values, cls, label) {
// Returns a DOM element showing a column vector with bracket notation
const wrap = document.createElement('div');
wrap.className = 'col-vec ' + cls;
wrap.innerHTML = '
';
values.forEach(v => {
const cell = document.createElement('div');
cell.className = 'cell';
cell.textContent = v;
wrap.appendChild(cell);
});
if (label) {
const lbl = document.createElement('div');
lbl.className = 'vec-label';
lbl.innerHTML = label;
wrap.appendChild(lbl);
}
return wrap;
}
function renderMemory() {
// Memory slots
const container = document.getElementById('memorySlots');
container.innerHTML = '';
memValues.forEach((v, i) => {
const slot = document.createElement('div');
slot.className = 'mem-slot' + (i === queryTarget ? ' active' : '');
slot.innerHTML = `addr ${i}
${v}
`;
slot.addEventListener('click', () => { queryTarget = i; renderMemory(); });
container.appendChild(slot);
});
// Query vector
const qEl = document.getElementById('queryVec');
qEl.innerHTML = '';
const qVec = makeColVec([queryTarget, 1], 'query', `q`);
const qNote = document.createElement('span');
qNote.style.cssText = 'font-size:0.82rem;color:var(--dim);margin-left:12px';
qNote.innerHTML = `= (i, 1) where i = ${queryTarget} ← "I want to read address ${queryTarget}"`;
qEl.appendChild(qVec);
qEl.appendChild(qNote);
// Key vectors + dot products
const vecEl = document.getElementById('vecColumns');
vecEl.innerHTML = '';
const scores = memValues.map((_, j) => 2 * queryTarget * j - j * j);
const maxScore = Math.max(...scores);
const minScore = Math.min(...scores);
const scoreRange = maxScore - minScore || 1;
const weights = softmax(scores.map(s => s * 10));
memValues.forEach((val, j) => {
const isWin = j === queryTarget;
const k = [2 * j, -(j * j)];
const group = document.createElement('div');
group.className = 'vec-group' + (isWin ? ' winner' : '');
// Column vector
const vec = makeColVec(k, isWin ? 'winner' : '', `k${j}`);
group.appendChild(vec);
// Dot product computation
const comp = document.createElement('div');
comp.className = 'dot-computation';
comp.innerHTML = `${queryTarget}×${k[0]} + 1×${k[1] >= 0 ? k[1] : '(' + k[1] + ')'}`;
group.appendChild(comp);
// Score + weight
const dpLine = document.createElement('div');
dpLine.className = 'dot-product-line';
dpLine.innerHTML = `${scores[j]}${(weights[j] * 100).toFixed(1)}%`;
group.appendChild(dpLine);
// Mini bar
const bar = document.createElement('div');
bar.className = 'dp-bar-mini';
bar.style.width = Math.max(2, ((scores[j] - minScore) / scoreRange) * 60) + 'px';
group.appendChild(bar);
// Value stored
const valLabel = document.createElement('div');
valLabel.style.cssText = `font-size:0.7rem;margin-top:4px;color:${isWin ? 'var(--gold)' : 'var(--dim)'};font-family:monospace`;
valLabel.textContent = `val=${val}`;
group.appendChild(valLabel);
vecEl.appendChild(group);
// Add "·" or "=" separator between groups (except last)
if (j < memValues.length - 1) {
const sep = document.createElement('div');
sep.style.cssText = 'align-self:center;color:var(--border);font-size:1.2rem;padding:0 2px';
sep.textContent = '';
vecEl.appendChild(sep);
}
});
// Read result
document.getElementById('readResult').innerHTML =
`Read result: mem[${queryTarget}] = ${memValues[queryTarget]} — key k${queryTarget} gets score ${maxScore} (softmax weight ${(weights[queryTarget] * 100).toFixed(2)}%), all others are penalized by −(i−j)²`;
}
renderMemory();
// ══════════════════════════════════════════
// TAB 2b: Side-by-Side Comparison
// ══════════════════════════════════════════
const sbsWords = ['The', 'cake', 'delicious', 'was', 'very'];
// Simulated high-dim embeddings (4D slice for display) — designed to show semantic similarity spread
const sbsTradKeys = [
[0.2, -0.1, 0.8, 0.3], // The
[0.9, 0.7, 0.1, -0.2], // cake
[0.8, 0.9, -0.1, 0.3], // delicious
[0.1, -0.3, 0.7, 0.5], // was
[0.3, 0.1, 0.2, 0.9], // very
];
// Queries are similar to the target but with overlap to neighbors (semantic similarity)
const sbsTradQueries = [
[0.3, -0.2, 0.9, 0.2], // attending to "The"
[0.8, 0.6, 0.2, -0.1], // attending to "cake"
[0.7, 0.8, 0.0, 0.4], // attending to "delicious"
[0.2, -0.2, 0.8, 0.4], // attending to "was"
[0.4, 0.2, 0.1, 0.8], // attending to "very"
];
let sbsTarget = 2;
function makeSbsColVec(values, cls, label) {
const wrap = document.createElement('div');
wrap.className = 'col-vec sbs-vec ' + cls;
wrap.innerHTML = '';
values.forEach(v => {
const cell = document.createElement('div');
cell.className = 'cell';
cell.textContent = typeof v === 'number' ? (Number.isInteger(v) ? v : v.toFixed(1)) : v;
wrap.appendChild(cell);
});
if (label) {
const lbl = document.createElement('div');
lbl.className = 'vec-label';
lbl.innerHTML = label;
wrap.appendChild(lbl);
}
return wrap;
}
function renderSBS() {
const target = sbsTarget;
document.getElementById('sbsTargetLabel').textContent = sbsWords[target];
// ── Traditional side ──
const tradQEl = document.getElementById('sbsTradQ');
tradQEl.innerHTML = '';
const tradQLabel = document.createElement('span');
tradQLabel.style.cssText = 'font-size:0.75rem;color:var(--dim);margin-right:4px';
tradQLabel.textContent = 'query:';
tradQEl.appendChild(tradQLabel);
tradQEl.appendChild(makeSbsColVec(sbsTradQueries[target], 'query', 'q'));
const tradKeysEl = document.getElementById('sbsTradKeys');
tradKeysEl.innerHTML = '';
const tradScores = sbsTradKeys.map(k =>
k.reduce((sum, ki, d) => sum + ki * sbsTradQueries[target][d], 0)
);
const tradWeights = softmax(tradScores.map(s => s * 4)); // moderate temperature
const tradMaxW = Math.max(...tradWeights);
sbsWords.forEach((word, j) => {
const grp = document.createElement('div');
const isTop = tradWeights[j] === tradMaxW;
grp.className = 'sbs-key-group' + (isTop ? ' trad-winner' : '');
const wordEl = document.createElement('div');
wordEl.className = 'sbs-word';
wordEl.textContent = word;
grp.appendChild(wordEl);
grp.appendChild(makeSbsColVec(sbsTradKeys[j], '', `k${j}`));
const score = document.createElement('div');
score.className = 'sbs-score';
score.textContent = tradScores[j].toFixed(2);
grp.appendChild(score);
const weight = document.createElement('div');
weight.className = 'sbs-weight';
weight.textContent = (tradWeights[j] * 100).toFixed(1) + '%';
grp.appendChild(weight);
const bar = document.createElement('div');
bar.className = 'sbs-weight-bar';
bar.style.width = (tradWeights[j] / tradMaxW * 50) + 'px';
grp.appendChild(bar);
tradKeysEl.appendChild(grp);
});
const tradResult = document.getElementById('sbsTradResult');
const topTrad = tradWeights.map((w, i) => ({w, i})).sort((a, b) => b.w - a.w);
tradResult.innerHTML = `Output: blend of "${sbsWords[topTrad[0].i]}" (${(topTrad[0].w*100).toFixed(0)}%) + "${sbsWords[topTrad[1].i]}" (${(topTrad[1].w*100).toFixed(0)}%) + others
→ A fuzzy mix of semantically related tokens`;
// ── Lookup side ──
const lookupQEl = document.getElementById('sbsLookupQ');
lookupQEl.innerHTML = '';
const lookupQLabel = document.createElement('span');
lookupQLabel.style.cssText = 'font-size:0.75rem;color:var(--dim);margin-right:4px';
lookupQLabel.textContent = 'query:';
lookupQEl.appendChild(lookupQLabel);
lookupQEl.appendChild(makeSbsColVec([target, 1], 'query', 'q'));
const lookupKeysEl = document.getElementById('sbsLookupKeys');
lookupKeysEl.innerHTML = '';
const lookupScores = sbsWords.map((_, j) => 2 * target * j - j * j);
const lookupWeights = softmax(lookupScores.map(s => s * 10));
const lookupMaxW = Math.max(...lookupWeights);
sbsWords.forEach((word, j) => {
const grp = document.createElement('div');
const isWin = lookupWeights[j] === lookupMaxW;
grp.className = 'sbs-key-group' + (isWin ? ' winner' : '');
const wordEl = document.createElement('div');
wordEl.className = 'sbs-word';
wordEl.textContent = `addr ${j}`;
grp.appendChild(wordEl);
grp.appendChild(makeSbsColVec([2 * j, -(j * j)], isWin ? 'winner' : '', `k${j}`));
const score = document.createElement('div');
score.className = 'sbs-score';
score.textContent = lookupScores[j];
grp.appendChild(score);
const weight = document.createElement('div');
weight.className = 'sbs-weight';
weight.textContent = (lookupWeights[j] * 100).toFixed(1) + '%';
grp.appendChild(weight);
const bar = document.createElement('div');
bar.className = 'sbs-weight-bar';
bar.style.width = (lookupWeights[j] / (lookupMaxW || 1) * 50) + 'px';
grp.appendChild(bar);
lookupKeysEl.appendChild(grp);
});
const lookupResult = document.getElementById('sbsLookupResult');
lookupResult.innerHTML = `Output: value at addr ${target} with ${(lookupWeights[target] * 100).toFixed(2)}% weight
→ An exact read of one specific address`;
}
document.getElementById('sbsTargetSlider').addEventListener('input', e => {
sbsTarget = +e.target.value;
renderSBS();
});
renderSBS();
// ══════════════════════════════════════════
// TAB 3: Parabola Visualization
// ══════════════════════════════════════════
function drawParabola(queryIdx) {
const canvas = document.getElementById('parabolaCanvas');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const n = 8;
const pad = 40;
const xScale = (W - 2 * pad) / (2 * (n - 1));
const maxJ2 = (n - 1) * (n - 1);
const yScale = (H - 2 * pad) / maxJ2;
// Axes
ctx.strokeStyle = '#2a2a44';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(pad, H - pad);
ctx.lineTo(W - pad, H - pad);
ctx.moveTo(pad, H - pad);
ctx.lineTo(pad, pad);
ctx.stroke();
ctx.fillStyle = '#666680';
ctx.font = '10px monospace';
ctx.fillText('2j →', W - pad - 20, H - pad + 15);
ctx.fillText('−j²', pad - 5, pad - 5);
// Parabola curve
ctx.strokeStyle = '#333355';
ctx.lineWidth = 1.5;
ctx.beginPath();
for (let j = 0; j < n; j++) {
const x = pad + (2 * j) * xScale;
const y = H - pad - (j * j) * yScale;
j === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
}
ctx.stroke();
// Points
for (let j = 0; j < n; j++) {
const x = pad + (2 * j) * xScale;
const y = H - pad - (j * j) * yScale;
ctx.beginPath();
ctx.arc(x, y, j === queryIdx ? 8 : 5, 0, Math.PI * 2);
ctx.fillStyle = j === queryIdx ? '#ffd54f' : '#4fc3f7';
ctx.fill();
ctx.fillStyle = '#999';
ctx.font = '10px monospace';
ctx.fillText(`j=${j}`, x - 8, y + 18);
}
// Query direction arrow
const qx = pad + (2 * queryIdx) * xScale;
const qy = H - pad - (queryIdx * queryIdx) * yScale;
ctx.strokeStyle = '#ff7043';
ctx.lineWidth = 2;
ctx.setLineDash([4, 3]);
ctx.beginPath();
ctx.moveTo(pad + W / 4, H - pad);
ctx.lineTo(qx, qy);
ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle = '#ff7043';
ctx.font = 'bold 11px sans-serif';
ctx.fillText(`q=(${queryIdx},1)`, pad + W / 4 - 15, H - pad + 15);
}
function drawScores(queryIdx) {
const canvas = document.getElementById('scoresCanvas');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const n = 8;
const pad = 40;
const barW = (W - 2 * pad) / n - 4;
const scores = [];
for (let j = 0; j < n; j++) {
scores.push(2 * queryIdx * j - j * j);
}
const maxS = Math.max(...scores);
const minS = Math.min(...scores);
const range = maxS - minS || 1;
// Axes
ctx.strokeStyle = '#2a2a44';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(pad, H - pad);
ctx.lineTo(W - pad, H - pad);
ctx.stroke();
ctx.fillStyle = '#666680';
ctx.font = '10px monospace';
ctx.fillText('j →', W - pad - 15, H - pad + 15);
ctx.fillText('score', pad - 5, pad + 10);
// Bars
for (let j = 0; j < n; j++) {
const x = pad + j * ((W - 2 * pad) / n) + 2;
const h = ((scores[j] - minS) / range) * (H - 2 * pad - 20);
const y = H - pad - h;
ctx.fillStyle = j === queryIdx ? '#ffd54f' : '#4fc3f7';
ctx.fillRect(x, y, barW, h);
ctx.fillStyle = '#999';
ctx.font = '9px monospace';
ctx.fillText(j.toString(), x + barW / 2 - 3, H - pad + 12);
ctx.fillStyle = j === queryIdx ? '#ffd54f' : '#aaa';
ctx.font = '9px monospace';
ctx.fillText(scores[j].toString(), x + barW / 2 - 6, y - 4);
}
document.getElementById('parabolaInsight').textContent =
`Index ${queryIdx} gets the highest score (${maxS}). The penalty −(i−j)² ensures only the exact match wins.`;
}
document.getElementById('queryIdxSlider').addEventListener('input', e => {
const v = +e.target.value;
document.getElementById('queryIdxVal').textContent = v;
drawParabola(v);
drawScores(v);
});
// Defer canvas drawing until tab is visible
const tab3Observer = new MutationObserver(() => {
if (document.getElementById('tab3').classList.contains('active')) {
drawParabola(3);
drawScores(3);
tab3Observer.disconnect();
}
});
tab3Observer.observe(document.getElementById('tab3'), { attributes: true, attributeFilter: ['class'] });
// ══════════════════════════════════════════
// TAB 4: Write-Read Trace Demo
// ══════════════════════════════════════════
const wrSteps = [
{
type: 'write',
instr: 'i32.const 3',
desc: 'Push 3 onto stack',
token: { label: 'token 0', instr: 'const 3', k: 'k=[2, −1]', v: 'v=3', addr: 1, val: 3 },
action: 'WRITE: i32.const 3
The model emits a new trace token. WK maps it to key [2, −1] (stack depth 1 on the parabola). WV extracts value 3. The token now sits in the sequence — that\'s the write. No memory chip needed.',
},
{
type: 'write',
instr: 'i32.const 5',
desc: 'Push 5 onto stack',
token: { label: 'token 1', instr: 'const 5', k: 'k=[4, −4]', v: 'v=5', addr: 2, val: 5 },
action: 'WRITE: i32.const 5
Another trace token emitted. Key [4, −4] (stack depth 2). Value 5. Now two tokens sit in the sequence = two stack entries.',
},
{
type: 'read',
instr: 'i32.add (read operands)',
desc: 'Read top two stack values',
readTargets: [1, 0], // indices into tokens array
action: 'READ: i32.add needs operands
The add instruction needs the top two stack values. The stack head produces query q=[2, 1] → attention scans all past tokens\' keys → finds token 1 (score = 2×2×2 − 4 = 4, highest) → retrieves value 5. Then query q=[1, 1] → finds token 0 → retrieves 3. No memory was accessed — just attention over past tokens.',
},
{
type: 'write',
instr: 'i32.add (result)',
desc: 'Push result 8',
token: { label: 'token 2', instr: 'add → 8', k: 'k=[2, −1]', v: 'v=8', addr: 1, val: 8 },
shadowIdx: 0, // token 0 gets overshadowed (same addr)
action: 'WRITE: push result 8
The FFN computed 3 + 5 = 8. A new token is emitted with key [2, −1] (stack depth 1 — the stack shrank by 1). Value 8.
Notice: token 0 also had key [2, −1] (depth 1, value 3). But token 2 is later in the sequence, so the parabola trick gives it a higher score. The old value 3 is overshadowed, not erased.',
},
{
type: 'read',
instr: 'output',
desc: 'Read top of stack',
readTargets: [2],
action: 'READ: output top of stack
Query q=[1, 1] for stack depth 1. Both token 0 and token 2 have key [2, −1], but token 2 is later → higher score → attention returns 8 (not the old 3). Output: 8. ✓',
},
];
let wrStep = 0;
let wrTokens = [];
let wrShadowed = new Set();
function wrReset() {
wrStep = 0;
wrTokens = [];
wrShadowed = new Set();
renderWR();
}
function wrStepExec() {
if (wrStep >= wrSteps.length) return;
const step = wrSteps[wrStep];
if (step.type === 'write') {
wrTokens.push({ ...step.token, isNew: true });
if (step.shadowIdx !== undefined) wrShadowed.add(step.shadowIdx);
}
wrStep++;
renderWR();
// Clear "new" flag after animation
setTimeout(() => {
wrTokens.forEach(t => t.isNew = false);
// Don't re-render, just let CSS animation finish
}, 700);
}
function renderWR() {
const traceEl = document.getElementById('wrTrace');
traceEl.innerHTML = '';
const step = wrStep > 0 ? wrSteps[wrStep - 1] : null;
const isRead = step && step.type === 'read';
const readSet = isRead ? new Set(step.readTargets) : new Set();
if (wrTokens.length === 0) {
traceEl.innerHTML = 'No tokens yet. The sequence is empty — no memory exists.
Click Step to emit the first trace token.
';
}
wrTokens.forEach((tok, i) => {
const div = document.createElement('div');
let cls = 'wr-token';
if (tok.isNew) cls += ' new-token';
if (isRead && readSet.has(i)) cls += ' found';
if (wrShadowed.has(i)) cls += ' shadowed';
div.className = cls;
let inner = `${tok.label}
`;
inner += `${tok.instr}
`;
inner += `${tok.k}
${tok.v}
`;
if (isRead && readSet.has(i)) {
inner += `↑ READ
`;
}
if (wrShadowed.has(i) && !readSet.has(i)) {
inner += `overshadowed
`;
}
div.innerHTML = inner;
traceEl.appendChild(div);
});
const actionEl = document.getElementById('wrAction');
if (step) {
actionEl.innerHTML = step.action;
} else {
actionEl.innerHTML = 'Click Step to begin execution. Watch how each token becomes a memory cell.';
}
}
document.getElementById('wrStep').addEventListener('click', wrStepExec);
document.getElementById('wrReset').addEventListener('click', wrReset);
renderWR();
// ══════════════════════════════════════════
// TAB 4: Stack Machine Step-Through
// ══════════════════════════════════════════
const smProgram = [
{ op: 'i32.const', arg: 3, desc: 'Push 3' },
{ op: 'i32.const', arg: 5, desc: 'Push 5' },
{ op: 'i32.add', arg: null, desc: 'Pop two, push sum' },
{ op: 'i32.const', arg: 10, desc: 'Push 10' },
{ op: 'i32.sub', arg: null, desc: 'Pop two, subtract' },
{ op: 'output', arg: null, desc: 'Output top of stack' },
{ op: 'halt', arg: null, desc: 'Stop' },
];
let smState = { ip: 0, stack: [], output: null, done: false, headLog: [] };
function smReset() {
smState = { ip: 0, stack: [], output: null, done: false, headLog: [] };
renderSM();
}
function smStepExec() {
if (smState.done) return;
const instr = smProgram[smState.ip];
const heads = [];
// IP head: cumulative sum
heads.push({ name: 'IP Head', action: `sum of deltas → IP = ${smState.ip}`, detail: `query: uniform avg × t = ${smState.ip}` });
if (instr.op === 'i32.const') {
smState.stack.push(instr.arg);
heads.push({ name: 'Stack Head', action: `WRITE ${instr.arg} at depth ${smState.stack.length}`, detail: `key=(${2 * smState.stack.length}, -${smState.stack.length ** 2}) val=${instr.arg}` });
heads.push({ name: 'FFN (ALU)', action: 'passthrough (no arithmetic)', detail: 'gate=1, val=input' });
} else if (instr.op === 'i32.add') {
const b = smState.stack.pop(), a = smState.stack.pop();
const r = a + b;
heads.push({ name: 'Stack Head ×2', action: `READ depth ${smState.stack.length + 2} → ${b}, depth ${smState.stack.length + 1} → ${a}`, detail: `q=(${smState.stack.length + 2},1) → ${b}; q=(${smState.stack.length + 1},1) → ${a}` });
heads.push({ name: 'FFN (ALU)', action: `${a} + ${b} = ${r}`, detail: `ReLU gate selects ADD path` });
smState.stack.push(r);
} else if (instr.op === 'i32.sub') {
const b = smState.stack.pop(), a = smState.stack.pop();
const r = a - b;
heads.push({ name: 'Stack Head ×2', action: `READ depth ${smState.stack.length + 2} → ${b}, depth ${smState.stack.length + 1} → ${a}`, detail: `q=(${smState.stack.length + 2},1) → ${b}; q=(${smState.stack.length + 1},1) → ${a}` });
heads.push({ name: 'FFN (ALU)', action: `${a} - ${b} = ${r}`, detail: `ReLU gate selects SUB path` });
smState.stack.push(r);
} else if (instr.op === 'output') {
const top = smState.stack[smState.stack.length - 1];
smState.output = top;
heads.push({ name: 'Stack Head', action: `READ top (depth ${smState.stack.length}) → ${top}`, detail: `q=(${smState.stack.length},1) → ${top}` });
} else if (instr.op === 'halt') {
smState.done = true;
heads.push({ name: 'Control Head', action: 'HALT detected', detail: 'opcode matches halt pattern' });
}
smState.headLog = heads;
smState.ip++;
renderSM();
}
function renderSM() {
// Program listing
const progEl = document.getElementById('smProgram');
progEl.innerHTML = 'Program
';
smProgram.forEach((instr, i) => {
const line = document.createElement('div');
line.className = 'instr-line' + (i === smState.ip - 1 && !smState.done ? ' current' : '') + (i < smState.ip - 1 ? ' done' : '') + (i === smState.ip - 1 && smState.done ? ' current' : '');
line.textContent = `${i}: ${instr.op}${instr.arg !== null ? ' ' + instr.arg : ''}`;
progEl.appendChild(line);
});
// State
const stateEl = document.getElementById('smState');
stateEl.innerHTML = 'VM State
';
const rows = [
['IP', smState.ip >= smProgram.length ? 'HALT' : smState.ip],
['Stack depth', smState.stack.length],
['Output', smState.output !== null ? smState.output : '—'],
];
rows.forEach(([l, v]) => {
const row = document.createElement('div');
row.className = 'state-row';
row.innerHTML = `${l}${v}`;
stateEl.appendChild(row);
});
const stackLabel = document.createElement('div');
stackLabel.style.cssText = 'font-size:0.75rem;color:var(--dim);margin-top:8px;margin-bottom:4px';
stackLabel.textContent = 'Stack (top ↑):';
stateEl.appendChild(stackLabel);
[...smState.stack].reverse().forEach((v, i) => {
const item = document.createElement('div');
item.className = 'stack-item' + (i === 0 ? ' top' : '');
item.textContent = v;
stateEl.appendChild(item);
});
// Heads
const headsEl = document.getElementById('smHeads');
headsEl.innerHTML = 'Attention Heads Active
';
if (smState.headLog.length === 0) {
headsEl.innerHTML += 'Click Step to begin
';
}
smState.headLog.forEach(h => {
const div = document.createElement('div');
div.className = 'head-info';
div.innerHTML = `${h.name}
${h.action}
${h.detail}
`;
headsEl.appendChild(div);
});
}
document.getElementById('smStep').addEventListener('click', smStepExec);
document.getElementById('smReset').addEventListener('click', smReset);
renderSM();
// IP demo (mini)
(function () {
const el = document.querySelector('#ipDemo .ip-trace');
const deltas = [1, 1, 1, 1, -2, 1, 1];
let sum = 0;
deltas.forEach(d => {
sum += d;
const cell = document.createElement('div');
cell.className = 'ip-cell';
cell.innerHTML = `${d > 0 ? '+' : ''}${d}
IP=${sum}
`;
el.appendChild(cell);
});
})();
// Stack demo (mini)
(function () {
const el = document.querySelector('#stackDemo .stack-vis');
[3, 5, 8].forEach(v => {
const item = document.createElement('div');
item.className = 'sv-item';
item.textContent = v;
el.appendChild(item);
});
})();
// ══════════════════════════════════════════
// TAB 5: Full Execution Trace
// ══════════════════════════════════════════
const feProgram = [
{ op: 'i32.const', bytes: '03 00 00 00', desc: 'Push 3 onto stack' },
{ op: 'i32.const', bytes: '05 00 00 00', desc: 'Push 5 onto stack' },
{ op: 'i32.add', bytes: '00 00 00 00', desc: 'Pop 3 and 5, push 8' },
{ op: 'output', bytes: '00 00 00 00', desc: 'Output top of stack' },
];
const feTraceSteps = [
{ tok: '03 00 00 00', meta: 'commit(+1,sts=1,bt=0)', detail: 'IP Head reads instruction 0 → i32.const. Stack Head writes 3 at depth 1. Stack: [3]' },
{ tok: '05 00 00 00', meta: 'commit(+1,sts=2,bt=0)', detail: 'IP Head reads instruction 1 → i32.const. Stack Head writes 5 at depth 2. Stack: [3, 5]' },
{ tok: '08 00 00 00', meta: 'commit(-1,sts=1,bt=0)', detail: 'IP Head reads instruction 2 → i32.add. Stack Head reads depth 2 → 5, depth 1 → 3. FFN computes 3+5=8. Writes 8 at depth 1. Stack: [8]' },
{ tok: 'out(08)', meta: '', detail: 'IP Head reads instruction 3 → output. Stack Head reads top → 8. Output token emitted.' },
{ tok: 'halt', meta: '', detail: 'Program complete. All computation happened inside the transformer\'s forward pass.' },
];
let feStep = 0;
function feReset() {
feStep = 0;
renderFE();
}
function feStepExec() {
if (feStep < feTraceSteps.length) feStep++;
renderFE();
}
function feRunAll() {
feStep = feTraceSteps.length;
renderFE();
}
function renderFE() {
// Program
const progEl = document.getElementById('feProgram');
progEl.innerHTML = 'WASM Program
';
feProgram.forEach((instr, i) => {
const line = document.createElement('div');
line.className = 'instr-line' + (i < feStep ? ' done' : '') + (i === feStep - 1 ? ' current' : '');
line.textContent = `${instr.op} ${instr.bytes}`;
progEl.appendChild(line);
});
// Trace
const traceEl = document.getElementById('feTrace');
traceEl.innerHTML = 'Execution Trace (tokens)
';
for (let i = 0; i < feStep; i++) {
const t = feTraceSteps[i];
const div = document.createElement('div');
div.className = 'trace-token' + (i === feStep - 1 ? ' new' : '');
div.innerHTML = `${t.tok}${t.meta}`;
traceEl.appendChild(div);
}
if (feStep === 0) {
traceEl.innerHTML += 'Click Step to generate trace tokens...
';
}
// Detail
const detailEl = document.getElementById('feDetail');
detailEl.innerHTML = 'What Happened (weight level)
';
if (feStep > 0) {
const d = feTraceSteps[feStep - 1];
const div = document.createElement('div');
div.style.cssText = 'font-size:0.82rem;line-height:1.6;color:var(--text)';
div.textContent = d.detail;
detailEl.appendChild(div);
// Visual: which heads fired
const heads = [];
if (feStep <= 3) heads.push({ name: 'IP Head', color: 'var(--accent)' });
if (feStep <= 4) heads.push({ name: 'Stack Head', color: 'var(--green)' });
if (feStep === 3) heads.push({ name: 'FFN (ALU)', color: 'var(--gold)' });
if (feStep === 5) heads.push({ name: 'Control', color: 'var(--warn)' });
const hDiv = document.createElement('div');
hDiv.style.cssText = 'margin-top:12px;display:flex;gap:6px;flex-wrap:wrap';
heads.forEach(h => {
const chip = document.createElement('span');
chip.style.cssText = `font-size:0.75rem;padding:3px 10px;border-radius:12px;border:1px solid ${h.color};color:${h.color}`;
chip.textContent = h.name;
hDiv.appendChild(chip);
});
detailEl.appendChild(hDiv);
} else {
detailEl.innerHTML += 'Each step shows which attention heads fire and what they compute.
';
}
}
document.getElementById('feStep').addEventListener('click', feStepExec);
document.getElementById('feReset').addEventListener('click', feReset);
document.getElementById('feRunAll').addEventListener('click', feRunAll);
renderFE();