vcabeli commited on
Commit
1b82226
路
verified 路
1 Parent(s): f6231d0

Highlight model families across both scatterplots on hover and remove planned corpus sentence

Browse files
Files changed (3) hide show
  1. assets/site.css +9 -0
  2. corpus.html +1 -1
  3. 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. The context-extension corpora at 16, 32, 64 and 128 kbp are planned for the same repository.</p>
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-use-case">
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="img" aria-label="${title}">`;
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
- g.addEventListener('mousemove', (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));
318
- g.addEventListener('mouseleave', hideTip);
 
 
 
 
 
 
 
 
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