Spaces:
Running
Running
File size: 8,411 Bytes
1d5de93 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | // =========================================================================
// §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();
})();
|