原文
${p.label}`;
btn.addEventListener("click", () => applyPalette(key));
container.appendChild(btn);
});
})();
document.querySelectorAll(".tab-btn").forEach(btn => {
btn.addEventListener("click", () => {
document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
document.querySelectorAll(".tab-panel").forEach(p => p.classList.remove("active"));
btn.classList.add("active");
el("panel-" + btn.dataset.tab).classList.add("active");
});
});
const select = el("benchmark-select");
Object.keys(benchmarks).forEach(name => {
const opt = document.createElement("option");
opt.value = name;
opt.textContent = name;
select.appendChild(opt);
});
function drawBenchmarkChart(benchmarkName) {
const canvas = el("benchmark-canvas");
if (!canvas) return;
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
const W = rect.width, H = rect.height;
const data = benchmarks[benchmarkName];
const sorted = benchmarkModels
.map(m => ({ name: m, value: data[m] }))
.sort((a, b) => b.value - a.value);
const mob = isMobile();
const ml = mob ? 36 : 56, mr = mob ? 8 : 16, mt = mob ? 10 : 16, mb = mob ? 80 : 110;
const pw = W - ml - mr, ph = H - mt - mb;
const maxVal = 100;
const n = sorted.length;
const gap = mob ? 2 : 4;
const barW = Math.max(6, (pw - gap * (n - 1)) / n);
const p = palettes[currentPalette];
ctx.clearRect(0, 0, W, H);
ctx.strokeStyle = "rgba(0,0,0,0.08)";
ctx.lineWidth = 1;
for (let v = 0; v {
const x = ml + i * (barW + gap);
const barH = (d.value / maxVal) * ph;
const y = mt + ph - barH;
const isHighlight = d.name.includes("Bonsai");
if (isHighlight) {
const grad = ctx.createLinearGradient(x, y, x, mt + ph);
grad.addColorStop(0, p.gradient[0]);
grad.addColorStop(1, p.gradient[1]);
ctx.fillStyle = grad;
} else {
ctx.fillStyle = p.benchmark[nonHighlightIdx % p.benchmark.length];
ctx.globalAlpha = 0.75;
nonHighlightIdx++;
}
ctx.fillRect(x, y, barW, barH);
ctx.globalAlpha = 1;
if (isHighlight && bonsaiImgLight.complete) {
const biw = mob ? 24 : 40, bih = mob ? 16 : 28;
ctx.drawImage(bonsaiImgLight, x + barW / 2 - biw / 2, y + barH / 2 - bih / 2, biw, bih);
}
ctx.font = dm(400, mob?8:11);
ctx.fillStyle = isHighlight ? p.highlightLabel : "#4c5159";
ctx.textAlign = "center";
ctx.fillText(d.value.toString(), x + barW / 2, y - (mob ? 3 : 5));
ctx.save();
ctx.translate(x + barW / 2, mt + ph + 8);
ctx.rotate(-Math.PI / 3);
ctx.font = `${isHighlight ? "600" : "400"} ${mob ? 9 : 11}px "Rethink Sans", sans-serif`;
ctx.fillStyle = isHighlight ? p.highlightLabel : "#4c5159";
ctx.textAlign = "right";
ctx.fillText(d.name, 0, 0);
ctx.restore();
});
}
function updateBenchmarkFooter() {
const sub = el("benchmark-subtitle");
if (sub) sub.textContent = select.value === "Avg"
? "Average score (IFEval, GSM8K, HumanEval+, BFCL, MuSR, MMLU-Redux)"
: "1-bit Bonsai 8B vs. comparable models";
}
select.addEventListener("change", () => { drawBenchmarkChart(select.value); updateBenchmarkFooter(); });
function stepBenchmark(dir) {
const opts = select.options;
select.selectedIndex = (select.selectedIndex + dir + opts.length) % opts.length;
drawBenchmarkChart(select.value);
updateBenchmarkFooter();
}
el("bench-prev").addEventListener("click", () => stepBenchmark(-1));
el("bench-next").addEventListener("click", () => stepBenchmark(1));
drawBenchmarkChart(select.value);
updateBenchmarkFooter();
(() => {
const leg = el("throughput-legend");
throughputModels.forEach(m => {
const item = document.createElement("div");
item.className = "leg-item";
const stripeStyle = m.key === "4-bit 8B" ? "background-image:repeating-linear-gradient(-45deg,#fff 0 1px,transparent 1px 4px);" : "";
item.innerHTML = `` +
`${m.key}`;
leg.appendChild(item);
});
})();
function drawThroughput() {
const canvas = el("throughput-canvas");
if (!canvas) return;
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
const W = rect.width;
const H = rect.height;
const mob = isMobile();
const ml = mob ? 40 : 64, mr = mob ? 12 : 24, mt = mob ? 10 : 16, mb = mob ? 70 : 84;
const pw = W - ml - mr;
const ph = H - mt - mb;
const maxVal = 400;
function yPos(v) { return mt + ph - (v / maxVal) * ph; }
ctx.clearRect(0, 0, W, H);
ctx.font = dm(400, mob?10:13);
ctx.fillStyle = "#4c5159";
ctx.textAlign = "right";
for (let v = 0; v {
const gx = ml + gi * groupW;
ctx.font = rs(500, mob?10:13);
ctx.fillStyle = "#262c35";
ctx.textAlign = "center";
const labelX = gx + groupW / 2;
const parts = mob && hw === "iPhone 17 Pro Max" ? ["iPhone 17", "Pro Max"] : hw === "Android (Adreno 830)" ? ["Android", "(Adreno 830)"] : [hw];
parts.forEach((p, pi) => {
ctx.fillText(p, labelX, yPos(0) + (mob ? 14 : 20) + pi * (mob ? 12 : 16));
});
const slotCount = throughputModels.length;
const slotW = barsW / slotCount;
throughputModels.forEach((model, mi) => {
const val = throughputData[model.key][gi];
if (val === null) return;
const bx = gx + groupPad + mi * slotW + barGap / 2;
const bw = slotW - barGap;
if (val === null || val === "dnf") {
ctx.font = dm(400, mob?8:11);
ctx.fillStyle = "#999";
ctx.textAlign = "center";
ctx.fillText("N/A*", bx + bw / 2, yPos(0) - (mob ? 4 : 6));
return;
}
const barH = (val / maxVal) * ph;
const by = yPos(val);
if (model.highlight) {
const p = palettes[currentPalette];
const grad = ctx.createLinearGradient(bx, by + barH, bx, by);
grad.addColorStop(0, p.gradient[1]);
grad.addColorStop(1, p.gradient[0]);
ctx.fillStyle = grad;
} else {
ctx.fillStyle = model.color;
}
ctx.fillRect(bx, by, bw, barH);
if (model.key === "4-bit 8B") {
ctx.save();
ctx.beginPath();
ctx.rect(bx, by, bw, barH);
ctx.clip();
ctx.strokeStyle = "#fff";
ctx.lineWidth = 1;
for (let d = -bw; d {
const leg = el("energy-legend");
energyModels.forEach(m => {
const item = document.createElement("div");
item.className = "leg-item";
item.innerHTML = `` +
`${m.key}`;
leg.appendChild(item);
});
})();
function drawEnergy() {
const canvas = el("energy-canvas");
if (!canvas) return;
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
const W = rect.width;
const H = rect.height;
const mob = isMobile();
const ml = mob ? 40 : 64, mr = mob ? 12 : 24, mt = mob ? 10 : 16, mb = mob ? 54 : 64;
const pw = W - ml - mr;
const ph = H - mt - mb;
const maxVal = 1.4;
function yPos(v) { return mt + ph - (v / maxVal) * ph; }
ctx.clearRect(0, 0, W, H);
ctx.font = dm(400, mob?10:13);
ctx.fillStyle = "#4c5159";
ctx.textAlign = "right";
for (let vi = 0; vi {
const gx = ml + gi * groupW;
ctx.font = rs(500, mob?10:13);
ctx.fillStyle = "#262c35";
ctx.textAlign = "center";
const labelX = gx + groupW / 2;
const eParts = mob && hw === "iPhone 17 Pro Max" ? ["iPhone 17", "Pro Max"] : [hw];
eParts.forEach((ep, epi) => {
ctx.fillText(ep, labelX, yPos(0) + (mob ? 14 : 20) + epi * (mob ? 12 : 16));
});
energyModels.forEach((model, bi) => {
const val = energyData[model.key][gi];
const bx = gx + groupPad + bi * barW + barGap / 2;
const bw = barW - barGap;
if (val === null || val === "dnf") {
if (val === "dnf") {
ctx.font = dm(400, mob?8:11);
ctx.fillStyle = "#999";
ctx.textAlign = "center";
ctx.fillText("N/A*", bx + bw / 2, yPos(0) - (mob ? 4 : 6));
}
return;
}
const barH = (val / maxVal) * ph;
const by = yPos(val);
if (model.highlight) {
const p = palettes[currentPalette];
const grad = ctx.createLinearGradient(bx, by + barH, bx, by);
grad.addColorStop(0, p.gradient[1]);
grad.addColorStop(1, p.gradient[0]);
ctx.fillStyle = grad;
} else {
ctx.fillStyle = model.color;
}
ctx.fillRect(bx, by, bw, barH);
const p = palettes[currentPalette];
ctx.font = dm(400, mob?8:11);
ctx.fillStyle = model.highlight ? p.highlightLabel : "#999";
ctx.textAlign = "center";
ctx.fillText(val, bx + bw / 2, by - (mob ? 3 : 6));
});
});
const iphoneIdx = energyHardware.indexOf("iPhone 17 Pro Max");
const noteX = ml + iphoneIdx * groupW + groupW / 2;
ctx.font = rs(400, mob?10:14);
ctx.fillStyle = "#4c5159";
ctx.textAlign = "center";
ctx.fillText("*16-bit 8B doesn't fit on iPhone 17 Pro Max", noteX, H - mb + (mob ? 34 : 48));
}
function drawIntelDensity() {
const canvas = el("intel-density-canvas");
if (!canvas) return;
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
const W = rect.width, H = rect.height;
const mob = isMobile();
const ml = mob ? 36 : 64, mr = mob ? 8 : 16, mt = mob ? 10 : 16, mb = mob ? 80 : 110;
const pw = W - ml - mr, ph = H - mt - mb;
const maxVal = 1.2;
const data = intelDensityData;
const n = data.length;
const gap = mob ? 2 : 4;
const barW = Math.max(6, (pw - gap * (n - 1)) / n);
const p = palettes[currentPalette];
ctx.clearRect(0, 0, W, H);
ctx.strokeStyle = "rgba(0,0,0,0.08)";
ctx.lineWidth = 1;
for (let v = 0; v {
const x = ml + i * (barW + gap);
const barH = (d.value / maxVal) * ph;
const y = mt + ph - barH;
if (d.highlight) {
const grad = ctx.createLinearGradient(x, y, x, mt + ph);
grad.addColorStop(0, p.gradient[0]);
grad.addColorStop(1, p.gradient[1]);
ctx.fillStyle = grad;
} else {
const ci = i - data.filter((dd, ii) => ii {
const px = xPos(x);
ctx.beginPath(); ctx.moveTo(px, mt); ctx.lineTo(px, H - mb); ctx.stroke();
});
ctx.strokeStyle = "#262c35";
ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(ml, H - mb); ctx.lineTo(W - mr, H - mb); ctx.stroke();
ctx.beginPath(); ctx.moveTo(ml, mt); ctx.lineTo(ml, H - mb); ctx.stroke();
ctx.font = dm(400, mob?9:13);
ctx.fillStyle = "#4c5159";
ctx.textAlign = "center";
xTicks.forEach(x => { ctx.fillText(x + " GB", xPos(x), H - mb + (mob ? 16 : 24)); });
ctx.textAlign = "right";
for (let y = 40; y 0) {
const steps = Math.round(grayCurveP * 100);
ctx.save();
ctx.strokeStyle = "rgba(38,44,53,0.35)";
ctx.lineWidth = 2;
ctx.setLineDash([6, 4]);
ctx.beginPath();
for (let i = 0; i 0) {
const endX = bLineS.x + (bLineE.x - bLineS.x) * darkLineP;
const endY = bLineS.y + (bLineE.y - bLineS.y) * darkLineP;
ctx.save();
ctx.strokeStyle = "#262c35";
ctx.lineWidth = 2;
ctx.setLineDash([6, 4]);
ctx.beginPath();
ctx.moveTo(bLineS.x, bLineS.y);
ctx.lineTo(endX, endY);
ctx.stroke();
ctx.setLineDash([]);
ctx.restore();
}
scatterDots = [];
const sorted = [...scatterData].sort((a, b) => (a.highlight ? 1 : 0) - (b.highlight ? 1 : 0));
const newPts = scatterData.filter(d => d.highlight).sort((a, b) => a.size - b.size);
const bonsaiXNorm = newPts.map(d => (xPos(d.size) - bLineS.x) / (bLineE.x - bLineS.x));
sorted.forEach(d => {
const cx = xPos(d.size);
const cy = yPos(d.accuracy);
const isHovered = hoveredName === d.name;
const r = d.highlight ? 7 : (isHovered ? 7 : 5);
scatterDots.push({ name: d.name, cx, cy, r: 5, data: d });
let bAlpha = 1;
if (d.highlight) {
const bIdx = newPts.findIndex(b => b.name === d.name);
const xn = bIdx >= 0 ? bonsaiXNorm[bIdx] : 1;
if (darkLineP {
if (el("panel-accuracy-size").classList.contains("active")) drawScatter();
if (el("panel-scatter-nocurves").classList.contains("active")) drawScatterNC();
};
const scatterNCData = [
{ name: "1-bit Bonsai 8B", size: 1.15, accuracy: 70.5, highlight: true, color: "#12151a" },
{ name: "Qwen3 0.6B", size: 1.19, accuracy: 48, color: "#666" },
{ name: "Gemma3 1B", size: 2, accuracy: 45.5, color: "#666" },
{ name: "LFM2 1.2B", size: 2.34, accuracy: 46.7, color: "#666" },
{ name: "Llama 3.2 1B",size: 2.47, accuracy: 39.9, color: "#666" },
{ name: "Qwen3 8B", size: 16.38,accuracy: 79.3, color: "#666" },
{ name: "Olmo3 7B", size: 14.6, accuracy: 70.9, color: "#666" },
{ name: "RNJ 8B", size: 16.62,accuracy: 73.1, color: "#666" },
{ name: "Ministral3 8B",size:16.04,accuracy: 71, color: "#666" },
{ name: "LFM2 8B", size: 16.68,accuracy: 69.6, color: "#666" },
{ name: "Llama 3.1 8B",size: 16.06,accuracy: 67.1, color: "#666" },
{ name: "Hermes 3 8B", size: 16.06,accuracy: 65.4, color: "#666" },
{ name: "DeepSeek R1-Qwen 7B", size: 15.23, accuracy: 55, color: "#666" },
{ name: "Marin 8B", size: 16.06,accuracy: 56.6, color: "#666" }
];
let scatterNCDots = [];
let scatterNCAnimPhase = 0; // 0=not started, 1=right, 2=+left, 3=+bonsai (complete)
let scatterNCHasAnimated = false;
let scatterNCAnimStart = 0;
const scatterNCPhaseDuration = 1000;
const scatterNCFixedLabels = ["Llama 3.1 8B", "Qwen3 8B", "Ministral3 8B", "Gemma3 1B", "Qwen3 0.6B"];
function scatterNCGroup(d) {
if (d.highlight) return "bonsai";
return d.size >= 12 ? "right" : "left";
}
function drawScatterNC(hoveredName) {
const canvas = el("scatter-nc-canvas");
if (!canvas) return;
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
const W = rect.width, H = rect.height;
const mob = isMobile();
const ml = mob ? 40 : 64, mr = mob ? 16 : 30, mt = mob ? 24 : 36, mb = mob ? 48 : 64;
const pw = W - ml - mr, ph = H - mt - mb;
const xMin = 0, xMax = 20;
const yMin = 20, yMax = 84;
function xPos(v) { return ml + ((v - xMin) / (xMax - xMin)) * pw; }
function yPos(v) { return mt + ph - ((v - yMin) / (yMax - yMin)) * ph; }
ctx.clearRect(0, 0, W, H);
ctx.strokeStyle = "rgba(0,0,0,0.08)";
ctx.lineWidth = 1;
for (let y = 20; y {
const px = xPos(x);
ctx.beginPath(); ctx.moveTo(px, mt); ctx.lineTo(px, H - mb); ctx.stroke();
});
ctx.strokeStyle = "#262c35"; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(ml, H - mb); ctx.lineTo(W - mr, H - mb); ctx.stroke();
ctx.beginPath(); ctx.moveTo(ml, mt); ctx.lineTo(ml, H - mb); ctx.stroke();
ctx.font = dm(400, mob?9:13);
ctx.fillStyle = "#4c5159";
ctx.textAlign = "center";
xTicks.forEach(x => { ctx.fillText(x + " GB", xPos(x), H - mb + (mob ? 16 : 24)); });
ctx.textAlign = "right";
for (let y = 20; y {
const phaseFor = g === "right" ? 1 : g === "left" ? 2 : 3;
if (phase > phaseFor) return 1;
if (phase scatterNCGroup(d) === "right");
const leftPts = scatterNCData.filter(d => scatterNCGroup(d) === "left");
const rCx = rightPts.reduce((s, d) => s + xPos(d.size), 0) / rightPts.length;
const rCy = rightPts.reduce((s, d) => s + yPos(d.accuracy), 0) / rightPts.length;
const rRx = Math.max(...rightPts.map(d => Math.abs(xPos(d.size) - rCx))) + 50;
const rRy = Math.max(...rightPts.map(d => Math.abs(yPos(d.accuracy) - rCy))) + 50;
drawOval(rCx, rCy, rRx, rRy, "7B\u20138B models", phaseAlpha("right"));
const lCx = leftPts.reduce((s, d) => s + xPos(d.size), 0) / leftPts.length;
const lCy = leftPts.reduce((s, d) => s + yPos(d.accuracy), 0) / leftPts.length;
const lRx = Math.max(...leftPts.map(d => Math.abs(xPos(d.size) - lCx))) + 30;
const lRy = Math.max(...leftPts.map(d => Math.abs(yPos(d.accuracy) - lCy))) + 30;
drawOval(lCx, lCy, lRx, lRy, "0.5\u20131.5B models", phaseAlpha("left"));
scatterNCDots = [];
const sorted = [...scatterNCData].sort((a, b) => (a.highlight ? 1 : 0) - (b.highlight ? 1 : 0));
sorted.forEach(d => {
const cx = xPos(d.size), cy = yPos(d.accuracy);
const grp = scatterNCGroup(d);
const alpha = phaseAlpha(grp);
if (alpha = scatterNCPhaseDuration && scatterNCAnimPhase {
const canvas = el("scatter-nc-canvas");
const tooltip = el("scatter-nc-tooltip");
const wrap = el("scatter-nc-wrap");
if (!canvas || !tooltip || !wrap) return;
let currentHover = null;
canvas.addEventListener("mousemove", e => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left, my = e.clientY - rect.top;
let closest = null, minD = 20;
scatterNCDots.forEach(dot => {
const d = Math.hypot(dot.cx - mx, dot.cy - my);
if (d {
if (currentHover) {
currentHover = null;
drawScatterNC(null);
}
tooltip.classList.remove("visible");
});
})();
(() => {
const canvas = el("scatter-canvas");
const tooltip = el("scatter-tooltip");
const wrap = el("scatter-wrap");
let currentHover = null;
canvas.addEventListener("mousemove", (e) => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
let closest = null;
let closestDist = Infinity;
scatterDots.forEach(dot => {
const dx = mx - dot.cx;
const dy = my - dot.cy;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist Avg: ${closest.data.accuracy}`;
tooltip.classList.add("visible");
const wrapRect = wrap.getBoundingClientRect();
let tx = e.clientX - wrapRect.left + 14;
let ty = e.clientY - wrapRect.top - 10;
if (tx + 160 > wrapRect.width) tx = e.clientX - wrapRect.left - 170;
tooltip.style.left = tx + "px";
tooltip.style.top = ty + "px";
} else {
canvas.style.cursor = "default";
if (currentHover !== null) {
currentHover = null;
drawScatter(null);
}
tooltip.classList.remove("visible");
}
});
canvas.addEventListener("mouseleave", () => {
canvas.style.cursor = "default";
if (currentHover !== null) {
currentHover = null;
drawScatter(null);
}
tooltip.classList.remove("visible");
});
})();
window.addEventListener("resize", () => {
if (el("panel-benchmarks").classList.contains("active")) drawBenchmarkChart(select.value);
if (el("panel-accuracy-size").classList.contains("active")) drawScatter();
if (el("panel-scatter-nocurves").classList.contains("active")) drawScatterNC();
if (el("panel-intel-density").classList.contains("active")) drawIntelDensity();
if (el("panel-throughput").classList.contains("active")) drawThroughput();
if (el("panel-energy").classList.contains("active")) drawEnergy();
});
document.querySelectorAll(".tab-btn").forEach(btn => {
btn.addEventListener("click", () => {
const t = btn.dataset.tab;
if (t === "benchmarks") raf2(() => drawBenchmarkChart(select.value));
if (t === "accuracy-size") raf2(() => {
if (paretoHasAnimated) { scatterAnimProgress = 1; drawScatter(); }
else { paretoHasAnimated = true; animateParetoCurves(); }
});
if (t === "scatter-nocurves") raf2(() => {
if (scatterNCHasAnimated) { scatterNCAnimPhase = 4; drawScatterNC(); }
else { scatterNCHasAnimated = true; animateScatterNC(); }
});
if (t === "intel-density") raf2(drawIntelDensity);
if (t === "throughput") raf2(drawThroughput);
if (t === "energy") raf2(drawEnergy);
});
});
applyPalette("rose");
const vizEl = el("prism-viz");
const paretoObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !paretoHasAnimated) {
if (el("panel-accuracy-size").classList.contains("active")) {
paretoHasAnimated = true;
animateParetoCurves();
}
}
});
}, { threshold: 1.0 });
paretoObserver.observe(vizEl);