Spaces:
Running
Running
Highlight model families across both scatterplots on hover and remove planned corpus sentence
Browse files- assets/site.css +9 -0
- corpus.html +1 -1
- leaderboard.html +29 -9
assets/site.css
CHANGED
|
@@ -323,6 +323,15 @@ input[type="search"] { min-width: 250px; }
|
|
| 323 |
.chart .ptlabel.ours { fill: var(--ink); font-weight: 600; }
|
| 324 |
.chart .leader { stroke: var(--ink-3); stroke-width: 1; }
|
| 325 |
.chart .family { fill: none; stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; opacity: 0.55; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
.chart .ann { fill: var(--ink-3); font-size: 11.5px; }
|
| 327 |
.chart .lbl { fill: var(--ink); font-size: 12.5px; }
|
| 328 |
.chart .lbl-2 { fill: var(--ink-2); font-size: 12px; }
|
|
|
|
| 323 |
.chart .ptlabel.ours { fill: var(--ink); font-weight: 600; }
|
| 324 |
.chart .leader { stroke: var(--ink-3); stroke-width: 1; }
|
| 325 |
.chart .family { fill: none; stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; opacity: 0.55; }
|
| 326 |
+
.scatter [data-family] { transition: opacity .15s ease, stroke-width .15s ease; }
|
| 327 |
+
.scatter .is-muted { opacity: .16; }
|
| 328 |
+
.scatter .family-line.is-family { opacity: 1; stroke-width: 3; }
|
| 329 |
+
.scatter .pt.is-family .point-dot { stroke: var(--ink); stroke-width: 2.5; }
|
| 330 |
+
.scatter .pt.is-family .ptlabel { fill: var(--ink); }
|
| 331 |
+
.scatter .pt { outline: none; }
|
| 332 |
+
.scatter .point-dot { pointer-events: none; }
|
| 333 |
+
.scatter .point-halo { fill: none; stroke: var(--ink); stroke-width: 1.5; opacity: 0; pointer-events: none; }
|
| 334 |
+
.scatter .pt:hover .point-halo, .scatter .pt:focus-visible .point-halo { opacity: 1; }
|
| 335 |
.chart .ann { fill: var(--ink-3); font-size: 11.5px; }
|
| 336 |
.chart .lbl { fill: var(--ink); font-size: 12.5px; }
|
| 337 |
.chart .lbl-2 { fill: var(--ink-2); font-size: 12px; }
|
corpus.html
CHANGED
|
@@ -52,7 +52,7 @@
|
|
| 52 |
</table>
|
| 53 |
</div>
|
| 54 |
|
| 55 |
-
<p class="note">The 8 kbp corpus is released as the <a href="https://huggingface.co/datasets/living-models/Botanic1-pretraining">Botanic1-pretraining</a> dataset, with the original sequence case, augmentation margins, train/test separation, source manifests and assembly metadata.
|
| 56 |
</main>
|
| 57 |
|
| 58 |
<footer class="site-footer">
|
|
|
|
| 52 |
</table>
|
| 53 |
</div>
|
| 54 |
|
| 55 |
+
<p class="note">The 8 kbp corpus is released as the <a href="https://huggingface.co/datasets/living-models/Botanic1-pretraining">Botanic1-pretraining</a> dataset, with the original sequence case, augmentation margins, train/test separation, source manifests and assembly metadata.</p>
|
| 56 |
</main>
|
| 57 |
|
| 58 |
<footer class="site-footer">
|
leaderboard.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
| 8 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700;800&family=Spectral:ital,wght@0,400;0,500;0,600;1,400&family=IBM+Plex+Mono:wght@400;500;600&display=swap">
|
| 11 |
-
<link rel="stylesheet" href="assets/site.css?v=20260908-
|
| 12 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
|
| 13 |
</head>
|
| 14 |
<body>
|
|
@@ -62,8 +62,8 @@
|
|
| 62 |
<h2 id="efficiency">Size and training tokens</h2>
|
| 63 |
<p>Plotting the aggregated score against model size highlights the efficiency of this frontier: the Botanic family leads at every scale, and even Botanic1-S (318M parameters) outperforms baselines with more than an order of magnitude larger, including Carbon-8B (8.3B, 0.727) and Evo 2 (7B, 0.695); the most parameter-efficient baseline, GPN (66M, 0.731), still trails on performance. The right panel replots the same models against the total number of genomic base pairs seen during training: the tokens seen, reconstructed for every model from its publication or model card. The Botanic scores are attained after only 314.6B base pairs, where the strongest competitor, PlantCAD2-L, had to process 4.03T base-pairs and NTv3 12.1T. Interestingly, the Evo 2 family does not scale well with capacity: it peaks at 7B (0.695, from 0.670 at 1B) and declines through 20B (0.678) to 40B (0.629).</p>
|
| 64 |
<div class="two">
|
| 65 |
-
<div class="chart" id="scatter-params"></div>
|
| 66 |
-
<div class="chart" id="scatter-tokens"></div>
|
| 67 |
</div>
|
| 68 |
<p class="chartnote">Tokens are converted to base pairs using approximations for 6-mer models. Models without a documented token count are omitted from the right panel.</p>
|
| 69 |
|
|
@@ -231,6 +231,15 @@
|
|
| 231 |
];
|
| 232 |
const modelFamily = m => MODEL_FAMILIES.find(f => m.id.startsWith(f.prefix));
|
| 233 |
const scatterColour = m => modelFamily(m) ? css(modelFamily(m).colour) : colour(m);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
// Every point is labelled. Labels are placed greedily (our models first, then by
|
| 235 |
// score) in the first free slot around the point; slots further out get a leader
|
| 236 |
// line. A label that fits nowhere is dropped, the tooltip still names the point.
|
|
@@ -271,7 +280,7 @@
|
|
| 271 |
const lx = (v) => L + (Math.log10(v) - Math.log10(xdom[0])) / (Math.log10(xdom[1]) - Math.log10(xdom[0])) * (W - L - R);
|
| 272 |
const y0 = 0.5, y1 = 0.8;
|
| 273 |
const y = (v) => T + (1 - (v - y0) / (y1 - y0)) * (H - T - B);
|
| 274 |
-
let s = `<svg viewBox="0 0 ${W} ${H}" role="
|
| 275 |
s += `<text x="${L}" y="16" style="fill:var(--ink);font-size:13.5px;font-weight:600">${title}</text>`;
|
| 276 |
s += '<g class="grid axis">';
|
| 277 |
for (let v = y0; v <= y1 + 1e-9; v += 0.05) { s += `<line x1="${L}" x2="${W - R}" y1="${y(v)}" y2="${y(v)}"/><text x="${L - 8}" y="${y(v) + 4}" text-anchor="end">${v.toFixed(2)}</text>`; }
|
|
@@ -284,6 +293,9 @@
|
|
| 284 |
cx: lx(Math.min(Math.max(m[xkey], xdom[0]), xdom[1])), cy: y(Math.max(m.s_bal, y0)),
|
| 285 |
}));
|
| 286 |
const labels = placeLabels(pts, { x0: L + 2, x1: W - 2, y0: T - 12, y1: H - B - 2 });
|
|
|
|
|
|
|
|
|
|
| 287 |
// Connect measured checkpoints in each family in increasing model size.
|
| 288 |
MODEL_FAMILIES.forEach(family => {
|
| 289 |
const members = pts.filter(pt => modelFamily(pt.m) === family).sort((a,b) => a.m.params-b.m.params);
|
|
@@ -296,26 +308,34 @@
|
|
| 296 |
const pt = pts[l.i];
|
| 297 |
const tx = l.dx > 0 ? l.box.x0 - 2 : l.dx < 0 ? l.box.x1 + 2 : pt.cx;
|
| 298 |
const ty = l.dx === 0 ? (l.dy > 0 ? l.box.y0 - 1 : l.box.y1 + 1) : (l.box.y0 + l.box.y1) / 2;
|
| 299 |
-
s += `<line class="leader" x1="${pt.cx + l.dx * pt.r}" y1="${pt.cy + l.dy * pt.r * 0.8}" x2="${tx}" y2="${ty}"/>`;
|
| 300 |
});
|
| 301 |
s += '</g>';
|
| 302 |
pts.forEach((pt, i) => {
|
| 303 |
const m = pt.m;
|
| 304 |
-
s += `<g data-id="${esc(m.id)}" class="pt"><circle cx="${pt.cx}" cy="${pt.cy}" r="${pt.r}" fill="${scatterColour(m)}" stroke="var(--paper)" stroke-width="2"/>`;
|
| 305 |
const l = labels.find(x => x.i === i);
|
| 306 |
if (l) {
|
| 307 |
const anchor = l.dx > 0 ? 'start' : l.dx < 0 ? 'end' : 'middle';
|
| 308 |
const x = l.dx > 0 ? l.box.x0 : l.dx < 0 ? l.box.x1 : (l.box.x0 + l.box.x1) / 2;
|
| 309 |
s += `<text class="ptlabel${m.ours ? ' ours' : ''}" x="${x}" y="${l.box.y1 - 3}" text-anchor="${anchor}">${esc(m.name)}</text>`;
|
| 310 |
}
|
| 311 |
-
s += `<circle class="hit" cx="${pt.cx}" cy="${pt.cy}" r="12"/></g>`;
|
| 312 |
});
|
| 313 |
s += '</svg>';
|
| 314 |
const el = document.getElementById(id); el.innerHTML = s;
|
| 315 |
el.querySelectorAll('g.pt').forEach(g => {
|
| 316 |
const m = M.find(mm => mm.id === g.dataset.id);
|
| 317 |
-
|
| 318 |
-
g.addEventListener('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
});
|
| 320 |
}
|
| 321 |
|
|
|
|
| 8 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700;800&family=Spectral:ital,wght@0,400;0,500;0,600;1,400&family=IBM+Plex+Mono:wght@400;500;600&display=swap">
|
| 11 |
+
<link rel="stylesheet" href="assets/site.css?v=20260908-family-hover">
|
| 12 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
|
| 13 |
</head>
|
| 14 |
<body>
|
|
|
|
| 62 |
<h2 id="efficiency">Size and training tokens</h2>
|
| 63 |
<p>Plotting the aggregated score against model size highlights the efficiency of this frontier: the Botanic family leads at every scale, and even Botanic1-S (318M parameters) outperforms baselines with more than an order of magnitude larger, including Carbon-8B (8.3B, 0.727) and Evo 2 (7B, 0.695); the most parameter-efficient baseline, GPN (66M, 0.731), still trails on performance. The right panel replots the same models against the total number of genomic base pairs seen during training: the tokens seen, reconstructed for every model from its publication or model card. The Botanic scores are attained after only 314.6B base pairs, where the strongest competitor, PlantCAD2-L, had to process 4.03T base-pairs and NTv3 12.1T. Interestingly, the Evo 2 family does not scale well with capacity: it peaks at 7B (0.695, from 0.670 at 1B) and declines through 20B (0.678) to 40B (0.629).</p>
|
| 64 |
<div class="two">
|
| 65 |
+
<div class="chart scatter" id="scatter-params"></div>
|
| 66 |
+
<div class="chart scatter" id="scatter-tokens"></div>
|
| 67 |
</div>
|
| 68 |
<p class="chartnote">Tokens are converted to base pairs using approximations for 6-mer models. Models without a documented token count are omitted from the right panel.</p>
|
| 69 |
|
|
|
|
| 231 |
];
|
| 232 |
const modelFamily = m => MODEL_FAMILIES.find(f => m.id.startsWith(f.prefix));
|
| 233 |
const scatterColour = m => modelFamily(m) ? css(modelFamily(m).colour) : colour(m);
|
| 234 |
+
const scatterFamily = m => modelFamily(m)?.name || m.id;
|
| 235 |
+
let hoveredPoint = null, focusedPoint = null;
|
| 236 |
+
function highlightScatterFamily() {
|
| 237 |
+
const family = (hoveredPoint || focusedPoint)?.dataset.family;
|
| 238 |
+
document.querySelectorAll('.scatter [data-family]').forEach(node => {
|
| 239 |
+
node.classList.toggle('is-family', Boolean(family) && node.dataset.family === family);
|
| 240 |
+
node.classList.toggle('is-muted', Boolean(family) && node.dataset.family !== family);
|
| 241 |
+
});
|
| 242 |
+
}
|
| 243 |
// Every point is labelled. Labels are placed greedily (our models first, then by
|
| 244 |
// score) in the first free slot around the point; slots further out get a leader
|
| 245 |
// line. A label that fits nowhere is dropped, the tooltip still names the point.
|
|
|
|
| 280 |
const lx = (v) => L + (Math.log10(v) - Math.log10(xdom[0])) / (Math.log10(xdom[1]) - Math.log10(xdom[0])) * (W - L - R);
|
| 281 |
const y0 = 0.5, y1 = 0.8;
|
| 282 |
const y = (v) => T + (1 - (v - y0) / (y1 - y0)) * (H - T - B);
|
| 283 |
+
let s = `<svg viewBox="0 0 ${W} ${H}" role="group" aria-label="${title}">`;
|
| 284 |
s += `<text x="${L}" y="16" style="fill:var(--ink);font-size:13.5px;font-weight:600">${title}</text>`;
|
| 285 |
s += '<g class="grid axis">';
|
| 286 |
for (let v = y0; v <= y1 + 1e-9; v += 0.05) { s += `<line x1="${L}" x2="${W - R}" y1="${y(v)}" y2="${y(v)}"/><text x="${L - 8}" y="${y(v) + 4}" text-anchor="end">${v.toFixed(2)}</text>`; }
|
|
|
|
| 293 |
cx: lx(Math.min(Math.max(m[xkey], xdom[0]), xdom[1])), cy: y(Math.max(m.s_bal, y0)),
|
| 294 |
}));
|
| 295 |
const labels = placeLabels(pts, { x0: L + 2, x1: W - 2, y0: T - 12, y1: H - B - 2 });
|
| 296 |
+
// Keep generous hover targets without covering neighbouring model dots.
|
| 297 |
+
const cells = d3.Delaunay.from(pts, pt => pt.cx, pt => pt.cy).voronoi([0, 0, W, H]);
|
| 298 |
+
s += '<defs>' + pts.map((pt, i) => `<clipPath id="${id}-hit-${i}"><path d="${cells.renderCell(i) || ''}"/></clipPath>`).join('') + '</defs>';
|
| 299 |
// Connect measured checkpoints in each family in increasing model size.
|
| 300 |
MODEL_FAMILIES.forEach(family => {
|
| 301 |
const members = pts.filter(pt => modelFamily(pt.m) === family).sort((a,b) => a.m.params-b.m.params);
|
|
|
|
| 308 |
const pt = pts[l.i];
|
| 309 |
const tx = l.dx > 0 ? l.box.x0 - 2 : l.dx < 0 ? l.box.x1 + 2 : pt.cx;
|
| 310 |
const ty = l.dx === 0 ? (l.dy > 0 ? l.box.y0 - 1 : l.box.y1 + 1) : (l.box.y0 + l.box.y1) / 2;
|
| 311 |
+
s += `<line class="leader" data-family="${esc(scatterFamily(pt.m))}" x1="${pt.cx + l.dx * pt.r}" y1="${pt.cy + l.dy * pt.r * 0.8}" x2="${tx}" y2="${ty}"/>`;
|
| 312 |
});
|
| 313 |
s += '</g>';
|
| 314 |
pts.forEach((pt, i) => {
|
| 315 |
const m = pt.m;
|
| 316 |
+
s += `<g data-id="${esc(m.id)}" data-family="${esc(scatterFamily(m))}" class="pt" tabindex="0" role="img" aria-label="${esc(m.name)}, ${esc(m.params_str)} parameters, aggregate score ${fmt(m.s_bal, 4)}"><circle class="point-dot" cx="${pt.cx}" cy="${pt.cy}" r="${pt.r}" fill="${scatterColour(m)}" stroke="var(--paper)" stroke-width="2"/><circle class="point-halo" cx="${pt.cx}" cy="${pt.cy}" r="${pt.r + 4}"/>`;
|
| 317 |
const l = labels.find(x => x.i === i);
|
| 318 |
if (l) {
|
| 319 |
const anchor = l.dx > 0 ? 'start' : l.dx < 0 ? 'end' : 'middle';
|
| 320 |
const x = l.dx > 0 ? l.box.x0 : l.dx < 0 ? l.box.x1 : (l.box.x0 + l.box.x1) / 2;
|
| 321 |
s += `<text class="ptlabel${m.ours ? ' ours' : ''}" x="${x}" y="${l.box.y1 - 3}" text-anchor="${anchor}">${esc(m.name)}</text>`;
|
| 322 |
}
|
| 323 |
+
s += `<circle class="hit" cx="${pt.cx}" cy="${pt.cy}" r="12" clip-path="url(#${id}-hit-${i})"/></g>`;
|
| 324 |
});
|
| 325 |
s += '</svg>';
|
| 326 |
const el = document.getElementById(id); el.innerHTML = s;
|
| 327 |
el.querySelectorAll('g.pt').forEach(g => {
|
| 328 |
const m = M.find(mm => mm.id === g.dataset.id);
|
| 329 |
+
const showPointTip = ev => showTip(`<b>${esc(m.name)}</b> 路 ${esc(m.org)}<br>${m.params_str} parameters${m.bp_equivalent ? ' 路 ' + esc(m.bp_equivalent) + ' bp seen' : ''}<br>S<sub>bal</sub><sup>test</sup> = <b>${fmt(m.s_bal, 4)}</b>`, ev);
|
| 330 |
+
g.addEventListener('mouseenter', ev => { hoveredPoint = g; highlightScatterFamily(); showPointTip(ev); });
|
| 331 |
+
g.addEventListener('mousemove', showPointTip);
|
| 332 |
+
g.addEventListener('mouseleave', () => { hoveredPoint = null; highlightScatterFamily(); hideTip(); });
|
| 333 |
+
g.addEventListener('focus', () => {
|
| 334 |
+
focusedPoint = g; highlightScatterFamily();
|
| 335 |
+
const rect = g.querySelector('.point-dot').getBoundingClientRect();
|
| 336 |
+
showPointTip({ clientX: rect.x + rect.width / 2, clientY: rect.y + rect.height / 2 });
|
| 337 |
+
});
|
| 338 |
+
g.addEventListener('blur', () => { focusedPoint = null; highlightScatterFamily(); hideTip(); });
|
| 339 |
});
|
| 340 |
}
|
| 341 |
|