carbon-demo / assets /js /sections /efficiency.js
tfrere's picture
tfrere HF Staff
Demo polish: SEO/AI-agent discoverability + new hexagonal logo + §1 helix per-letter fade + sandbox empty-state
1d5de93
raw
history blame
8.41 kB
// =========================================================================
// §13, Efficiency: inference throughput vs number of prompts
// =========================================================================
// Numbers come from the public release dataset:
// HuggingFaceBio/carbon-inference-evals (data/train-00000-of-00001.parquet),
// filtered to non-remote prompt-scan runs through 512 prompts. Carbon and
// GENERator runs use vLLM dynamic batching (so batch ≡ num_prompts).
// Evo2 caps batch size to fit a single H100: Evo2 1B caps at 34 prompts,
// 7B at 13, 20B and 40B at 4. Past those caps the throughput curve flattens
// because additional prompts spill into a second sequential mini-batch
// instead of widening the live one.
//
// Two-family palette: cool greens for the Carbon checkpoints (light → dark
// = small → large), warm browns for the Evo2 baselines. Echoes the rest of
// the demo so "Carbon vs Evo2" reads at a glance even when several lines
// stack inside the lower decade of the log axis.
(function initDemo13() {
const host = document.getElementById("d13-throughput");
if (!host) return;
const PROMPTS = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512];
// throughput in output bp/s, indexed positionally against PROMPTS above.
// Source: carbon-inference-evals · output_bp_per_second column.
const SERIES = [
{ name: "Carbon-500M", color: "#A8DCB4", weight: 2.8,
throughput: [3416.78, 5395.16, 11071.54, 21723.72, 43443.40, 74971.36, 110803.57, 161581.87, 146471.03, 152044.37] },
{ name: "Carbon-3B", color: "#6DBF7E", weight: 3.0,
throughput: [1489.33, 2916.85, 5561.60, 11309.62, 20109.93, 40027.81, 65218.26, 100126.89, 125130.82, 122840.91] },
{ name: "Carbon-8B", color: "#1A7A40", weight: 3.2,
throughput: [ 811.71, 1737.51, 3274.83, 6576.03, 12675.68, 22560.89, 39248.04, 46435.76, 76582.70, 85084.69] },
{ name: "Evo2 1B", color: "#C9A06A", weight: 2.6, capLabel: "min(n, 34)",
throughput: [ 42.32, 86.48, 176.82, 351.63, 705.34, 1409.67, 1436.89, 1435.27, 1342.48, 1400.36] },
{ name: "Evo2 7B", color: "#8C7355", weight: 2.6, capLabel: "min(n, 13)",
throughput: [ 33.28, 68.78, 132.86, 269.00, 267.13, 360.91, 441.94, 432.32, 453.76, 444.48] },
{ name: "Evo2 20B", color: "#5A4A38", weight: 2.6, capLabel: "min(n, 4)",
throughput: [ 43.97, 86.04, 169.45, 175.90, 176.45, 177.16, 177.88, 180.47, 177.46, 177.17] },
{ name: "Evo2 40B", color: "#2A211A", weight: 2.6, capLabel: "min(n, 4)",
throughput: [ 21.75, 43.01, 85.12, 85.74, 84.94, 87.20, 87.93, 85.63, 87.07, 86.78] },
];
// ----- Layout ----------------------------------------------------------
// Right gutter is wider than longcontext.js's NIAH chart because we have
// 7 lines to label end-on (instead of 3) and Evo2 capLabels add a second
// line under each model name. Font sizes are bumped one notch above the
// §6 NIAH chart because this figure renders inside the same ~828 px
// section column as a 1000 px viewBox, so the type optically lands at
// ~10–11 px on screen — too small for a 7-series log–log plot. Sticking
// with the 1000×460 viewBox keeps the section grid intact and just
// restyles the contents.
const W = 1000, H = 460;
const padL = 86, padR = 178, padT = 30, padB = 64;
const innerW = W - padL - padR;
const innerH = H - padT - padB;
const xLo = Math.log2(1), xHi = Math.log2(512);
const yLo = Math.log10(10), yHi = Math.log10(300000);
const xFor = n => padL + ((Math.log2(n) - xLo) / (xHi - xLo)) * innerW;
const yFor = v => padT + ((yHi - Math.log10(v)) / (yHi - yLo)) * innerH;
// ----- Render ----------------------------------------------------------
function render() {
let svg = "";
// y-axis gridlines + labels (1-3-1-3 log decades, matches the source ref)
const Y_TICKS = [10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000, 300000];
Y_TICKS.forEach(v => {
const y = yFor(v);
const isMajor = v === 10 || v === 100 || v === 1000 || v === 10000 || v === 100000;
svg += `<line x1="${padL}" y1="${y.toFixed(1)}" x2="${(W - padR).toFixed(1)}" y2="${y.toFixed(1)}" stroke="${isMajor ? "#e5e5e5" : "#f4f4f1"}" stroke-width="1"/>`;
svg += `<text x="${padL - 10}" y="${(y + 4).toFixed(1)}" font-family="JetBrains Mono" font-size="13" fill="${isMajor ? "#666" : "#b5b5b5"}" text-anchor="end">${formatY(v)}</text>`;
});
// Axis title (rotated, left side)
svg += `<text x="22" y="${(padT + innerH / 2).toFixed(1)}" font-family="JetBrains Mono" font-size="12" fill="#666" text-anchor="middle" transform="rotate(-90 22 ${(padT + innerH / 2).toFixed(1)})" letter-spacing="1.2">THROUGHPUT · BP/S · LOG</text>`;
// x-axis ticks + labels
PROMPTS.forEach(n => {
const x = xFor(n);
svg += `<line x1="${x.toFixed(1)}" y1="${(padT + innerH).toFixed(1)}" x2="${x.toFixed(1)}" y2="${(padT + innerH + 5).toFixed(1)}" stroke="#666" stroke-width="1"/>`;
svg += `<text x="${x.toFixed(1)}" y="${(padT + innerH + 22).toFixed(1)}" font-family="JetBrains Mono" font-size="13" fill="#1f1f1d" text-anchor="middle">${n}</text>`;
});
// x-axis title
svg += `<text x="${(padL + innerW / 2).toFixed(1)}" y="${(padT + innerH + 50).toFixed(1)}" font-family="JetBrains Mono" font-size="12" fill="#666" text-anchor="middle" letter-spacing="1.2">NUMBER OF PROMPTS · LOG</text>`;
// baseline at the bottom of the plot zone
svg += `<line x1="${padL}" y1="${(padT + innerH).toFixed(1)}" x2="${(W - padR).toFixed(1)}" y2="${(padT + innerH).toFixed(1)}" stroke="#1f1f1d" stroke-width="1.2"/>`;
// lines (drawn lightest → darkest so the dark Carbon-8B sits on top of
// its lighter Carbon siblings where the curves cross at low n)
const drawOrder = [
"Carbon-500M", "Evo2 1B", "Evo2 20B", "Evo2 40B", "Evo2 7B",
"Carbon-3B", "Carbon-8B"
];
drawOrder.forEach(name => {
const s = SERIES.find(s => s.name === name);
let d = "";
s.throughput.forEach((v, i) => {
const x = xFor(PROMPTS[i]);
const y = yFor(v);
d += (i === 0 ? "M" : "L") + x.toFixed(1) + " " + y.toFixed(1);
});
svg += `<path d="${d}" fill="none" stroke="${s.color}" stroke-width="${s.weight}" stroke-linejoin="round" stroke-linecap="round"/>`;
s.throughput.forEach((v, i) => {
const x = xFor(PROMPTS[i]);
const y = yFor(v);
svg += `<circle cx="${x.toFixed(1)}" cy="${y.toFixed(1)}" r="3.6" fill="${s.color}" stroke="#fff" stroke-width="1.2"/>`;
});
});
// End-of-line labels in the right gutter. Each label sits at the y of
// the n=512 data point; we then nudge overlapping labels apart so the
// 7-line stack stays legible (Carbon-500M / Carbon-3B are within 0.09
// log-decades of each other).
const xEnd = xFor(512) + 14;
const labels = SERIES.map(s => {
const last = s.throughput[s.throughput.length - 1];
return { name: s.name, color: s.color, capLabel: s.capLabel, value: last, y: yFor(last) };
}).sort((a, b) => a.y - b.y);
const minGap = 36; // px, room for two text lines per label
for (let i = 1; i < labels.length; i++) {
if (labels[i].y - labels[i - 1].y < minGap) {
labels[i].y = labels[i - 1].y + minGap;
}
}
labels.forEach(l => {
svg += `<text x="${xEnd.toFixed(1)}" y="${l.y.toFixed(1)}" font-family="JetBrains Mono" font-size="14" fill="${l.color}" font-weight="600">${l.name}</text>`;
const sub = l.capLabel
? `<tspan fill="#aaa">batch </tspan>${l.capLabel}`
: `<tspan fill="#aaa">${formatBp(l.value)} bp/s</tspan>`;
svg += `<text x="${xEnd.toFixed(1)}" y="${(l.y + 16).toFixed(1)}" font-family="JetBrains Mono" font-size="11" fill="#888">${sub}</text>`;
});
host.setAttribute("viewBox", `0 0 ${W} ${H}`);
host.innerHTML = svg;
}
// 10 → "10", 1000 → "1k", 30000 → "30k", 100000 → "100k"
function formatY(v) {
if (v >= 1000) return (v / 1000) + "k";
return String(v);
}
function formatBp(v) {
if (v >= 10000) return Math.round(v / 1000) + "k";
if (v >= 1000) return (v / 1000).toFixed(1) + "k";
return Math.round(v).toLocaleString();
}
render();
})();