// ========================================================================= // §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 += ``; svg += `${formatY(v)}`; }); // Axis title (rotated, left side) svg += `THROUGHPUT · BP/S · LOG`; // x-axis ticks + labels PROMPTS.forEach(n => { const x = xFor(n); svg += ``; svg += `${n}`; }); // x-axis title svg += `NUMBER OF PROMPTS · LOG`; // baseline at the bottom of the plot zone svg += ``; // 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 += ``; s.throughput.forEach((v, i) => { const x = xFor(PROMPTS[i]); const y = yFor(v); svg += ``; }); }); // 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 += `${l.name}`; const sub = l.capLabel ? `batch ${l.capLabel}` : `${formatBp(l.value)} bp/s`; svg += `${sub}`; }); 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(); })();