Skip to content

Instantly share code, notes, and snippets.

@kbouw
Last active February 16, 2026 04:43
Show Gist options
  • Select an option

  • Save kbouw/50a91c3f714fb5e40a15ae56b931b797 to your computer and use it in GitHub Desktop.

Select an option

Save kbouw/50a91c3f714fb5e40a15ae56b931b797 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Model Distillation</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #111216; overflow-x: hidden; }
#root { min-height: 100vh; }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.9/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect, useRef, useCallback, useMemo } = React;
// ============================================================
// DATA
// ============================================================
const SCENARIOS = [
{
id: "weather",
prompt: "The weather today is",
tokens: [
{ word: "beautiful", prob: 0.28, group: "positive" },
{ word: "warm", prob: 0.19, group: "positive" },
{ word: "nice", prob: 0.14, group: "positive" },
{ word: "perfect", prob: 0.11, group: "positive" },
{ word: "cold", prob: 0.08, group: "negative" },
{ word: "terrible", prob: 0.05, group: "negative" },
{ word: "unpredictable", prob: 0.04, group: "neutral" },
{ word: "humid", prob: 0.03, group: "neutral" },
],
},
{
id: "code",
prompt: "To fix this bug, you should",
tokens: [
{ word: "check", prob: 0.24, group: "action" },
{ word: "update", prob: 0.18, group: "action" },
{ word: "add", prob: 0.15, group: "action" },
{ word: "remove", prob: 0.12, group: "action" },
{ word: "try", prob: 0.10, group: "soft" },
{ word: "consider", prob: 0.07, group: "soft" },
{ word: "first", prob: 0.05, group: "ordering" },
{ word: "carefully", prob: 0.03, group: "ordering" },
],
},
{
id: "recipe",
prompt: "Next, add the eggs and",
tokens: [
{ word: "mix", prob: 0.26, group: "action" },
{ word: "stir", prob: 0.22, group: "action" },
{ word: "whisk", prob: 0.18, group: "action" },
{ word: "beat", prob: 0.12, group: "action" },
{ word: "fold", prob: 0.07, group: "gentle" },
{ word: "combine", prob: 0.06, group: "gentle" },
{ word: "blend", prob: 0.05, group: "gentle" },
{ word: "sugar", prob: 0.02, group: "ingredient" },
],
},
];
// ============================================================
// UTILITY
// ============================================================
function useSmoothed(targets, rate = 0.1) {
const cur = useRef(targets.map(() => 0));
const [vals, setVals] = useState(targets);
const frame = useRef(null);
useEffect(() => {
let on = true;
const tick = () => {
if (!on) return;
let go = false;
const n = cur.current.map((v, i) => {
const d = (targets[i] || 0) - v;
if (Math.abs(d) > 0.0005) { go = true; return v + d * rate; }
return targets[i] || 0;
});
cur.current = n; setVals([...n]);
if (go) frame.current = requestAnimationFrame(tick);
};
frame.current = requestAnimationFrame(tick);
return () => { on = false; if (frame.current) cancelAnimationFrame(frame.current); };
}, [targets, rate]);
return vals;
}
function useAnimVal(target, rate = 0.08) {
const cur = useRef(0);
const [val, setVal] = useState(0);
const frame = useRef(null);
useEffect(() => {
let on = true;
const tick = () => {
if (!on) return;
const d = target - cur.current;
if (Math.abs(d) > 0.001) {
cur.current += d * rate;
setVal(cur.current);
frame.current = requestAnimationFrame(tick);
} else { cur.current = target; setVal(target); }
};
frame.current = requestAnimationFrame(tick);
return () => { on = false; if (frame.current) cancelAnimationFrame(frame.current); };
}, [target, rate]);
return val;
}
// ============================================================
// VISUAL 1: THE PROBLEM
// ============================================================
function TheProblem() {
const [quality, setQuality] = useState(0.85);
const [isDragging, setIsDragging] = useState(false);
const trackRef = useRef(null);
const cost = quality < 0.5 ? 0.05 + quality * 0.4 : 0.25 + (quality - 0.5) * 3.5;
const latency = quality < 0.5 ? 80 + quality * 200 : 180 + (quality - 0.5) * 2400;
const params = quality < 0.5 ? 1 + quality * 14 : 8 + (quality - 0.5) * 184;
const distilledQuality = Math.min(0.97, quality + 0.12 + (1 - quality) * 0.15);
const animCost = useAnimVal(cost, 0.1);
const animLatency = useAnimVal(latency, 0.1);
const animQ = useAnimVal(quality * 100, 0.1);
const animDQ = useAnimVal(distilledQuality * 100, 0.1);
const handleDrag = useCallback((cx) => {
if (!trackRef.current) return;
const r = trackRef.current.getBoundingClientRect();
const p = Math.max(0.1, Math.min(0.98, (cx - r.left) / r.width));
setQuality(Math.round(p * 100) / 100);
}, []);
useEffect(() => {
if (!isDragging) return;
const mv = (e) => handleDrag(e.touches ? e.touches[0].clientX : e.clientX);
const up = () => setIsDragging(false);
window.addEventListener("mousemove", mv); window.addEventListener("mouseup", up);
window.addEventListener("touchmove", mv); window.addEventListener("touchend", up);
return () => { window.removeEventListener("mousemove", mv); window.removeEventListener("mouseup", up); window.removeEventListener("touchmove", mv); window.removeEventListener("touchend", up); };
}, [isDragging, handleDrag]);
const modelName = quality < 0.35 ? "Tiny" : quality < 0.55 ? "Small" : quality < 0.75 ? "Medium" : quality < 0.9 ? "Large" : "Massive";
return (
<div>
<div style={{ marginBottom: 24 }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 8 }}>
<span style={{ fontSize: 12, color: "#6a6e76" }}>Model size</span>
<span style={{ fontSize: 13, color: "#c8cad0", fontFamily: "monospace" }}>{modelName} ({params.toFixed(0)}B params)</span>
</div>
<div ref={trackRef} style={{ position: "relative", height: 36, cursor: "pointer", touchAction: "none", userSelect: "none" }}
onMouseDown={(e) => { setIsDragging(true); handleDrag(e.clientX); }}
onTouchStart={(e) => { setIsDragging(true); handleDrag(e.touches[0].clientX); }}>
<div style={{ position: "absolute", left: 0, right: 0, top: 14, height: 8, borderRadius: 4, background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.04)" }} />
<div style={{ position: "absolute", left: 0, top: 14, width: `${quality * 100}%`, height: 8, borderRadius: 4, background: "linear-gradient(90deg, #4ade80, #f0c674, #f07474)" }} />
<div style={{
position: "absolute", left: `${quality * 100}%`, top: 8, transform: "translateX(-50%)",
width: 20, height: 20, borderRadius: "50%", background: "#e8e6e3",
border: "2px solid rgba(0,0,0,0.3)", cursor: "grab",
boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
}} />
<div style={{ position: "absolute", left: 4, top: 28, fontSize: 9, color: "#3a3e46" }}>faster, cheaper</div>
<div style={{ position: "absolute", right: 4, top: 28, fontSize: 9, color: "#3a3e46" }}>smarter, slower</div>
</div>
</div>
<div style={{ display: "flex", gap: 10, marginBottom: 20 }}>
{[
{ label: "Cost", value: `$${animCost.toFixed(2)}`, sub: "per 1K tokens", color: animCost > 1.0 ? "#f07474" : "#8a8f98" },
{ label: "Latency", value: `${animLatency.toFixed(0)}ms`, sub: "per token", color: animLatency > 500 ? "#f0c674" : "#8a8f98" },
{ label: "Quality", value: `${animQ.toFixed(1)}%`, sub: "benchmark", color: "#8a8f98" },
].map((s) => (
<div key={s.label} style={{ flex: 1, padding: "10px 12px", background: "rgba(255,255,255,0.02)", borderRadius: 6, border: "1px solid rgba(255,255,255,0.04)" }}>
<div style={{ fontSize: 10, color: "#4a4e56", textTransform: "uppercase", letterSpacing: "0.08em" }}>{s.label}</div>
<div style={{ fontSize: 18, fontFamily: "monospace", color: s.color, fontWeight: 300 }}>{s.value}</div>
<div style={{ fontSize: 9, color: "#3a3e46" }}>{s.sub}</div>
</div>
))}
</div>
<div style={{ padding: "14px 16px", background: "rgba(255,255,255,0.015)", borderRadius: 8, border: "1px solid rgba(255,255,255,0.04)" }}>
<div style={{ fontSize: 11, color: "#5a5e66", marginBottom: 10, textTransform: "uppercase", letterSpacing: "0.06em" }}>Quality comparison at this cost/speed</div>
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }}>
<div style={{ width: 100, fontSize: 11, color: "#6a6e76" }}>Trained normally</div>
<div style={{ flex: 1, height: 16, borderRadius: 3, background: "rgba(255,255,255,0.03)", position: "relative", overflow: "hidden" }}>
<div style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: `${animQ}%`, background: "rgba(140,180,255,0.4)", borderRadius: 3 }} />
</div>
<div style={{ width: 44, fontSize: 12, fontFamily: "monospace", color: "#8a8f98", textAlign: "right" }}>{animQ.toFixed(1)}%</div>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<div style={{ width: 100, fontSize: 11, color: "#4ade80" }}>Distilled</div>
<div style={{ flex: 1, height: 16, borderRadius: 3, background: "rgba(255,255,255,0.03)", position: "relative", overflow: "hidden" }}>
<div style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: `${animDQ}%`, background: "rgba(74,222,128,0.45)", borderRadius: 3 }} />
</div>
<div style={{ width: 44, fontSize: 12, fontFamily: "monospace", color: "#4ade80", textAlign: "right" }}>{animDQ.toFixed(1)}%</div>
</div>
<div style={{ marginTop: 10, fontSize: 12, color: "#6a6e76", lineHeight: 1.6 }}>
Same size, same speed, same cost. Distilled model scores <span style={{ color: "#4ade80" }}>+{(animDQ - animQ).toFixed(1)}%</span> higher by learning from a larger teacher.
</div>
</div>
</div>
);
}
// ============================================================
// VISUAL 2: WHAT THE BIG MODEL ACTUALLY KNOWS
// ============================================================
function WhatItKnows() {
const [scenarioIdx, setScenarioIdx] = useState(0);
const [showFull, setShowFull] = useState(false);
const scenario = SCENARIOS[scenarioIdx];
const hardProbs = scenario.tokens.map((t, i) => i === 0 ? 1.0 : 0.0);
const softProbs = scenario.tokens.map((t) => t.prob);
const displayProbs = showFull ? softProbs : hardProbs;
const smoothed = useSmoothed(displayProbs, 0.12);
const groupColors = {
positive: "#4ade80", negative: "#f07474", neutral: "#94a3b8",
action: "#60a5fa", soft: "#a78bfa", ordering: "#94a3b8",
gentle: "#f0c674", ingredient: "#94a3b8",
};
return (
<div>
<div style={{ display: "flex", gap: 6, marginBottom: 20, flexWrap: "wrap" }}>
{SCENARIOS.map((s, i) => (
<button key={s.id} onClick={() => { setScenarioIdx(i); setShowFull(false); }} style={{
padding: "7px 14px", fontSize: 12, borderRadius: 6, cursor: "pointer",
fontFamily: "'IBM Plex Sans', sans-serif",
background: i === scenarioIdx ? "rgba(255,255,255,0.06)" : "rgba(255,255,255,0.02)",
border: `1px solid ${i === scenarioIdx ? "rgba(255,255,255,0.1)" : "rgba(255,255,255,0.04)"}`,
color: i === scenarioIdx ? "#e8e6e3" : "#5a5e66",
}}>
{s.prompt}...
</button>
))}
</div>
<div style={{
padding: "14px 18px", marginBottom: 20, background: "rgba(255,255,255,0.025)",
borderRadius: 8, border: "1px solid rgba(255,255,255,0.05)",
fontFamily: "monospace", fontSize: 15, color: "#c8cad0",
}}>
{scenario.prompt} <span style={{ color: "#4a4e56", animation: "blink 1.2s infinite" }}>▍</span>
</div>
<div style={{ display: "flex", gap: 8, marginBottom: 18, justifyContent: "center" }}>
<button onClick={() => setShowFull(false)} style={{
padding: "9px 18px", fontSize: 12, borderRadius: 6, cursor: "pointer",
background: !showFull ? "rgba(140,180,255,0.12)" : "rgba(255,255,255,0.02)",
border: `1px solid ${!showFull ? "rgba(140,180,255,0.25)" : "rgba(255,255,255,0.04)"}`,
color: !showFull ? "#8cb4ff" : "#5a5e66", fontFamily: "'IBM Plex Sans', sans-serif",
}}>
Just the answer
</button>
<button onClick={() => setShowFull(true)} style={{
padding: "9px 18px", fontSize: 12, borderRadius: 6, cursor: "pointer",
background: showFull ? "rgba(74,222,128,0.12)" : "rgba(255,255,255,0.02)",
border: `1px solid ${showFull ? "rgba(74,222,128,0.25)" : "rgba(255,255,255,0.04)"}`,
color: showFull ? "#4ade80" : "#5a5e66", fontFamily: "'IBM Plex Sans', sans-serif",
}}>
Full distribution
</button>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
{scenario.tokens.map((token, i) => {
const p = smoothed[i];
const vis = p > 0.015;
const maxP = Math.max(...smoothed);
const w = Math.max(p / Math.max(maxP, 0.01), 0.005) * 100;
const isTop = i === 0;
const color = showFull ? (groupColors[token.group] || "#8a8f98") : (isTop ? "rgba(140,180,255,0.6)" : "rgba(255,255,255,0.08)");
return (
<div key={token.word} style={{
display: "flex", alignItems: "center", gap: 10, height: 34,
opacity: !showFull && i > 0 ? 0.2 : (vis ? 1 : 0.3),
transition: "opacity 0.4s ease",
}}>
<div style={{
width: 90, fontSize: 13, fontFamily: "monospace", color: vis ? "#c8cad0" : "#3a3e46",
textAlign: "right", transition: "color 0.3s",
}}>
"{token.word}"
</div>
<div style={{ flex: 1, position: "relative", height: 20, borderRadius: 3 }}>
<div style={{ position: "absolute", inset: 0, background: "rgba(255,255,255,0.02)", borderRadius: 3, border: "1px solid rgba(255,255,255,0.03)" }} />
<div style={{
position: "absolute", left: 0, top: 0, bottom: 0,
width: `${w}%`, background: color, borderRadius: 3,
boxShadow: vis && showFull ? `0 0 12px ${color}33` : "none",
}} />
</div>
<div style={{
width: 44, fontSize: 12, fontFamily: "monospace",
color: vis ? "#8a8f98" : "#2a2e36", textAlign: "right",
}}>
{(p * 100).toFixed(1)}%
</div>
</div>
);
})}
</div>
<div style={{
marginTop: 18, padding: "12px 16px",
background: showFull ? "rgba(74,222,128,0.04)" : "rgba(140,180,255,0.04)",
borderRadius: 6,
border: `1px solid ${showFull ? "rgba(74,222,128,0.1)" : "rgba(140,180,255,0.1)"}`,
borderLeft: `3px solid ${showFull ? "rgba(74,222,128,0.3)" : "rgba(140,180,255,0.3)"}`,
transition: "all 0.3s ease",
}}>
<p style={{ fontSize: 13, color: "#a0a4ac", lineHeight: 1.6, margin: 0 }}>
{showFull
? <>The big model considered all of these as possible next words. "{scenario.tokens[0].word}" won, but "{scenario.tokens[1].word}" and "{scenario.tokens[2].word}" were close. That ranking IS the knowledge. A student model trained on this distribution learns which words are interchangeable and which aren't, not just which one was picked.</>
: <>If you only record the final answer, all you know is "{scenario.tokens[0].word}" was chosen. Every other option the model considered, and how it ranked them, is thrown away. This is what normal training data looks like.</>
}
</p>
</div>
</div>
);
}
// ============================================================
// VISUAL 3: THE TRANSFER
// ============================================================
function TheTransfer() {
const scenario = SCENARIOS[0];
const [mode, setMode] = useState("hard");
const [epoch, setEpoch] = useState(0);
const [running, setRunning] = useState(false);
const [studentProbs, setStudentProbs] = useState(scenario.tokens.map(() => 1 / scenario.tokens.length));
const iv = useRef(null);
const teacherProbs = scenario.tokens.map((t) => t.prob);
const hardTarget = scenario.tokens.map((t, i) => i === 0 ? 1.0 : 0.0);
const target = mode === "soft" ? teacherProbs : hardTarget;
const smooth = useSmoothed(studentProbs, 0.15);
const similarity = 1 - Math.sqrt(studentProbs.reduce((s, p, i) => s + (p - teacherProbs[i]) ** 2, 0) / studentProbs.length);
const matchPct = Math.max(0, Math.min(100, similarity * 100));
const step = useCallback(() => {
setStudentProbs((prev) => {
const lr = 0.15;
const logits = prev.map((p) => Math.log(Math.max(p, 1e-8)));
const newLogits = logits.map((l, i) => l - lr * (prev[i] - target[i]));
const maxL = Math.max(...newLogits);
const exps = newLogits.map((l) => Math.exp(l - maxL));
const sum = exps.reduce((a, b) => a + b, 0);
return exps.map((e) => e / sum);
});
setEpoch((e) => e + 1);
}, [target]);
const reset = useCallback(() => {
setRunning(false);
setEpoch(0);
setStudentProbs(scenario.tokens.map(() => 1 / scenario.tokens.length));
if (iv.current) clearInterval(iv.current);
}, [scenario]);
useEffect(() => {
if (running) iv.current = setInterval(step, 160);
else if (iv.current) clearInterval(iv.current);
return () => { if (iv.current) clearInterval(iv.current); };
}, [running, step]);
useEffect(() => { reset(); }, [mode]);
const maxP = Math.max(...smooth);
return (
<div>
<div style={{ marginBottom: 20, textAlign: "center" }}>
<div style={{ fontSize: 12, color: "#6a6e76", marginBottom: 10 }}>Train the student model on:</div>
<div style={{ display: "flex", gap: 8, justifyContent: "center" }}>
<button onClick={() => setMode("hard")} style={{
padding: "9px 18px", fontSize: 12, borderRadius: 6, cursor: "pointer",
background: mode === "hard" ? "rgba(140,180,255,0.12)" : "rgba(255,255,255,0.02)",
border: `1px solid ${mode === "hard" ? "rgba(140,180,255,0.25)" : "rgba(255,255,255,0.04)"}`,
color: mode === "hard" ? "#8cb4ff" : "#5a5e66", fontFamily: "'IBM Plex Sans', sans-serif",
}}>Just answers</button>
<button onClick={() => setMode("soft")} style={{
padding: "9px 18px", fontSize: 12, borderRadius: 6, cursor: "pointer",
background: mode === "soft" ? "rgba(74,222,128,0.12)" : "rgba(255,255,255,0.02)",
border: `1px solid ${mode === "soft" ? "rgba(74,222,128,0.25)" : "rgba(255,255,255,0.04)"}`,
color: mode === "soft" ? "#4ade80" : "#5a5e66", fontFamily: "'IBM Plex Sans', sans-serif",
}}>Teacher's full distribution</button>
</div>
</div>
<div style={{ display: "flex", gap: 8, justifyContent: "center", marginBottom: 16 }}>
<button onClick={() => setRunning(!running)} style={{
padding: "8px 18px", fontSize: 12, borderRadius: 6, cursor: "pointer",
background: running ? "rgba(240,116,116,0.12)" : "rgba(100,200,140,0.12)",
border: `1px solid ${running ? "rgba(240,116,116,0.25)" : "rgba(100,200,140,0.25)"}`,
color: running ? "#f07474" : "#64c88c", fontFamily: "monospace",
}}>{running ? "⏸ pause" : "▶ train"}</button>
<button onClick={reset} style={{
padding: "8px 18px", fontSize: 12, borderRadius: 6, cursor: "pointer",
background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.06)",
color: "#8a8f98", fontFamily: "monospace",
}}>↺ reset</button>
</div>
<div style={{ display: "flex", gap: 12, justifyContent: "center", marginBottom: 16, alignItems: "center" }}>
<span style={{ fontSize: 11, color: "#4a4e56", fontFamily: "monospace" }}>step {epoch}</span>
<div style={{ width: 140, height: 6, borderRadius: 3, background: "rgba(255,255,255,0.04)", overflow: "hidden" }}>
<div style={{
width: `${matchPct}%`, height: "100%", borderRadius: 3,
background: matchPct > 85 ? "#4ade80" : matchPct > 50 ? "#f0c674" : "#64a0ff",
transition: "width 0.15s, background 0.3s",
}} />
</div>
<span style={{ fontSize: 11, fontFamily: "monospace", color: matchPct > 85 ? "#4ade80" : "#8a8f98" }}>
{matchPct.toFixed(0)}% match to teacher
</span>
</div>
<div style={{
padding: "10px 14px", marginBottom: 14, background: "rgba(255,255,255,0.02)",
borderRadius: 6, border: "1px solid rgba(255,255,255,0.04)",
fontFamily: "monospace", fontSize: 13, color: "#8a8f98",
}}>
{scenario.prompt} <span style={{ color: "#4a4e56" }}>▍</span>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
{scenario.tokens.map((token, i) => {
const p = smooth[i];
const vis = p > 0.015;
const w = Math.max(p / Math.max(maxP, 0.01), 0.005) * 100;
const tw = Math.max(teacherProbs[i] / Math.max(...teacherProbs), 0.005) * 100;
return (
<div key={token.word} style={{ display: "flex", alignItems: "center", gap: 10, height: 32 }}>
<div style={{ width: 80, fontSize: 12, fontFamily: "monospace", color: vis ? "#c8cad0" : "#3a3e46", textAlign: "right" }}>
"{token.word}"
</div>
<div style={{ flex: 1, position: "relative", height: 18, borderRadius: 3 }}>
<div style={{ position: "absolute", inset: 0, background: "rgba(255,255,255,0.02)", borderRadius: 3, border: "1px solid rgba(255,255,255,0.03)" }} />
<div style={{
position: "absolute", left: 0, top: 0, bottom: 0,
width: `${tw}%`, borderRadius: 3,
border: "1px dashed rgba(74,222,128,0.2)",
background: "transparent",
}} />
<div style={{
position: "absolute", left: 0, top: 0, bottom: 0,
width: `${w}%`, borderRadius: 3,
background: mode === "soft"
? `rgba(74,222,128,${0.25 + p * 0.75})`
: `rgba(140,180,255,${0.25 + p * 0.75})`,
}} />
</div>
<div style={{ width: 40, fontSize: 11, fontFamily: "monospace", color: vis ? "#8a8f98" : "#2a2e36", textAlign: "right" }}>
{(p * 100).toFixed(1)}
</div>
</div>
);
})}
</div>
<div style={{ display: "flex", gap: 16, justifyContent: "center", marginTop: 12, fontSize: 10, color: "#4a4e56" }}>
<span>
<span style={{ display: "inline-block", width: 12, height: 8, borderRadius: 2, background: mode === "soft" ? "rgba(74,222,128,0.5)" : "rgba(140,180,255,0.5)", marginRight: 4, verticalAlign: "middle" }} />
student
</span>
<span>
<span style={{ display: "inline-block", width: 12, height: 8, borderRadius: 2, border: "1px dashed rgba(74,222,128,0.3)", marginRight: 4, verticalAlign: "middle" }} />
teacher target
</span>
</div>
<div style={{
marginTop: 16, padding: "12px 16px",
background: mode === "soft" ? "rgba(74,222,128,0.04)" : "rgba(140,180,255,0.04)",
borderRadius: 6,
border: `1px solid ${mode === "soft" ? "rgba(74,222,128,0.1)" : "rgba(140,180,255,0.1)"}`,
borderLeft: `3px solid ${mode === "soft" ? "rgba(74,222,128,0.3)" : "rgba(140,180,255,0.3)"}`,
}}>
<p style={{ fontSize: 13, color: "#a0a4ac", lineHeight: 1.6, margin: 0 }}>
{mode === "hard"
? <>Training on just the final answer, the student learns that "beautiful" is correct but nothing about how "warm" and "nice" are also good options. Watch how it converges to a spike on one word. Switch to the teacher's full distribution to see the difference.</>
: <>The student learns the full ranking: "beautiful" is most likely, "warm" and "nice" are close behind, "terrible" is unlikely. Try running both modes and comparing how closely the student matches the teacher's dashed outline.</>
}
</p>
</div>
</div>
);
}
// ============================================================
// MAIN — with MCP header style applied
// ============================================================
function App() {
const [activeVisual, setActiveVisual] = useState(0);
const visuals = [
{
id: "problem", num: "01", title: "The Problem",
subtitle: "Bigger models are smarter but cost more. Can we close the gap?",
component: <TheProblem />,
},
{
id: "knowledge", num: "02", title: "What the Big Model Knows",
subtitle: "When a model generates text, it ranks every possible next word.",
component: <WhatItKnows />,
},
{
id: "transfer", num: "03", title: "The Transfer",
subtitle: "Train the small model on rankings instead of answers. Watch what happens.",
component: <TheTransfer />,
},
];
return (
<div style={{
minHeight: "100vh", background: "#111216", color: "#e8e6e3",
fontFamily: "'IBM Plex Sans', -apple-system, sans-serif", padding: "40px 16px",
}}>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet" />
<style>{"@keyframes blink { 0%,50% { opacity: 1 } 51%,100% { opacity: 0 } }"}</style>
{/* ---- MCP-style header: left-aligned with label, title, subtext ---- */}
<div style={{ maxWidth: 640, margin: "0 auto 32px" }}>
<div style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: 11, color: "#4ade80", textTransform: "uppercase", letterSpacing: "0.12em", marginBottom: 8 }}>
Interactive Explainer
</div>
<h1 style={{ fontSize: 28, fontWeight: 600, margin: "0 0 8px", lineHeight: 1.3 }}>
Model Distillation
</h1>
<p style={{ fontSize: 15, color: "#6a6e76", margin: 0, lineHeight: 1.5 }}>
How a small model learns from a large one.
</p>
</div>
<div style={{ maxWidth: 640, margin: "0 auto 28px", display: "flex", gap: 6 }}>
{visuals.map((v, i) => (
<button key={v.id} onClick={() => setActiveVisual(i)} style={{
flex: 1, padding: "10px 8px", borderRadius: 8, cursor: "pointer",
background: i === activeVisual ? "rgba(255,255,255,0.05)" : "rgba(255,255,255,0.015)",
border: `1px solid ${i === activeVisual ? "rgba(255,255,255,0.1)" : "rgba(255,255,255,0.03)"}`,
textAlign: "left", transition: "all 0.2s",
}}>
<div style={{ fontSize: 10, color: i === activeVisual ? "#4ade80" : "#3a3e46", fontFamily: "monospace", marginBottom: 2 }}>{v.num}</div>
<div style={{ fontSize: 12, color: i === activeVisual ? "#e8e6e3" : "#5a5e66", fontWeight: 500 }}>{v.title}</div>
</button>
))}
</div>
<div style={{
maxWidth: 640, margin: "0 auto",
padding: "24px", background: "rgba(255,255,255,0.015)",
borderRadius: 12, border: "1px solid rgba(255,255,255,0.04)",
}}>
<div style={{ marginBottom: 20 }}>
<div style={{ fontSize: 11, color: "#4ade80", fontFamily: "monospace", marginBottom: 4 }}>
{visuals[activeVisual].num}
</div>
<div style={{ fontSize: 18, fontWeight: 400, color: "#e8e6e3", marginBottom: 4 }}>
{visuals[activeVisual].title}
</div>
<div style={{ fontSize: 13, color: "#6a6e76", lineHeight: 1.5 }}>
{visuals[activeVisual].subtitle}
</div>
</div>
{visuals[activeVisual].component}
</div>
{activeVisual < 2 && (
<div style={{ maxWidth: 640, margin: "16px auto 0", textAlign: "center" }}>
<button onClick={() => setActiveVisual(activeVisual + 1)} style={{
padding: "10px 20px", fontSize: 12, borderRadius: 6, cursor: "pointer",
background: "rgba(74,222,128,0.08)", border: "1px solid rgba(74,222,128,0.15)",
color: "#4ade80", fontFamily: "'IBM Plex Sans', sans-serif",
}}>
Next: {visuals[activeVisual + 1].title} →
</button>
</div>
)}
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment