perf scatter: log-x + alternating-position labels (kill overlap)
Two issues with the accuracy-vs-latency scatter: 1. Linear x crammed RNN/GRU/LSTM into ~25 px of axis (380/520/700 μs) while BERT alone took the right 80 % (3200 μs). 2. Labels placed at fixed +12 right of each point overlapped both neighbouring points and other labels in the recurrent cluster. Fixes: - X-axis switched to log10 with bounds 10μs–10ms; tick labels and marks added at 10μs / 100μs / 1ms / 10ms so the audience can read the scale. - Y-axis bounds tightened to [0.5, 1.0] (was [0.7, 1.0]) so KNN's ~0.43 cross-host F1 falls within the visible plot area instead of off-bottom; ticks added at 0.6 / 0.8 / 1.0. - Anti-overlap label placement: sort points by x, alternate above (-12) / below (+18) the circle. Adjacent labels can no longer share both x and y bands. repaintLabels() re-runs on each model_perf event so late arrivals slot into the staircase. Y-axis title also updated: "held-out accuracy" → "held-out macro-F1" to match the actual metric the producer reports. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9e7d9999a3
commit
f429bd4223
2 changed files with 88 additions and 22 deletions
|
|
@ -2387,32 +2387,80 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
|
|||
window.dashboard.scene('references', { onEnter: loadFirstIfReady });
|
||||
})();
|
||||
|
||||
// ── Performance scatter — DEMO ONLY ───────────────────────────
|
||||
// ── Performance scatter (log-x, anti-overlap labels) ──────────
|
||||
// X-axis is log10(latency_us) so the 35× range KNN→BERT spreads
|
||||
// evenly. Labels are sorted by x and placed alternately above /
|
||||
// below their points so adjacent labels can't horizontally
|
||||
// overlap even when points cluster (e.g. RNN/GRU/LSTM near each
|
||||
// other in latency).
|
||||
(function () {
|
||||
const svg = document.getElementById('perf-scatter');
|
||||
const ns = 'http://www.w3.org/2000/svg';
|
||||
const W = 600, H = 360;
|
||||
const xmin = 0, xmax = 4000, ymin = 0.7, ymax = 1.0;
|
||||
const points = new Map();
|
||||
// Log-scale x bounds: 10 μs (1e1) to 10 ms (1e4).
|
||||
const xLogMin = 1, xLogMax = 4;
|
||||
const ymin = 0.5, ymax = 1.0;
|
||||
const points = new Map(); // model → { g, latency, accuracy }
|
||||
|
||||
function setupAxes() {
|
||||
svg.innerHTML = '';
|
||||
const ax = document.createElementNS(ns, 'line');
|
||||
ax.setAttribute('x1', 50); ax.setAttribute('y1', H - 30);
|
||||
ax.setAttribute('x2', W - 10); ax.setAttribute('y2', H - 30);
|
||||
ax.setAttribute('x1', 50); ax.setAttribute('y1', H - 40);
|
||||
ax.setAttribute('x2', W - 10); ax.setAttribute('y2', H - 40);
|
||||
ax.setAttribute('class', 'axis'); svg.appendChild(ax);
|
||||
const ay = document.createElementNS(ns, 'line');
|
||||
ay.setAttribute('x1', 50); ay.setAttribute('y1', 10);
|
||||
ay.setAttribute('x2', 50); ay.setAttribute('y2', H - 30);
|
||||
ay.setAttribute('x2', 50); ay.setAttribute('y2', H - 40);
|
||||
ay.setAttribute('class', 'axis'); svg.appendChild(ay);
|
||||
// x-axis log ticks at 10μs, 100μs, 1ms, 10ms.
|
||||
[
|
||||
{ v: 10, lbl: '10μs' },
|
||||
{ v: 100, lbl: '100μs' },
|
||||
{ v: 1000, lbl: '1ms' },
|
||||
{ v: 10000, lbl: '10ms' },
|
||||
].forEach(({ v, lbl }) => {
|
||||
const lx = Math.log10(v);
|
||||
const x = 50 + ((lx - xLogMin) / (xLogMax - xLogMin)) * (W - 60);
|
||||
const tick = document.createElementNS(ns, 'line');
|
||||
tick.setAttribute('x1', x.toFixed(1)); tick.setAttribute('x2', x.toFixed(1));
|
||||
tick.setAttribute('y1', H - 40); tick.setAttribute('y2', H - 35);
|
||||
tick.setAttribute('class', 'axis');
|
||||
svg.appendChild(tick);
|
||||
const tlbl = document.createElementNS(ns, 'text');
|
||||
tlbl.setAttribute('x', x.toFixed(1));
|
||||
tlbl.setAttribute('y', H - 22);
|
||||
tlbl.setAttribute('class', 'axis-label');
|
||||
tlbl.setAttribute('text-anchor', 'middle');
|
||||
tlbl.style.fontSize = '11px';
|
||||
tlbl.textContent = lbl;
|
||||
svg.appendChild(tlbl);
|
||||
});
|
||||
// y-axis ticks at 0.6, 0.8, 1.0
|
||||
[0.6, 0.8, 1.0].forEach(v => {
|
||||
const y = (H - 40) - ((v - ymin) / (ymax - ymin)) * (H - 50);
|
||||
const tick = document.createElementNS(ns, 'line');
|
||||
tick.setAttribute('x1', 45); tick.setAttribute('x2', 50);
|
||||
tick.setAttribute('y1', y.toFixed(1)); tick.setAttribute('y2', y.toFixed(1));
|
||||
tick.setAttribute('class', 'axis');
|
||||
svg.appendChild(tick);
|
||||
const tlbl = document.createElementNS(ns, 'text');
|
||||
tlbl.setAttribute('x', 42);
|
||||
tlbl.setAttribute('y', (y + 4).toFixed(1));
|
||||
tlbl.setAttribute('class', 'axis-label');
|
||||
tlbl.setAttribute('text-anchor', 'end');
|
||||
tlbl.style.fontSize = '11px';
|
||||
tlbl.textContent = v.toFixed(1);
|
||||
svg.appendChild(tlbl);
|
||||
});
|
||||
const xl = document.createElementNS(ns, 'text');
|
||||
xl.setAttribute('x', W / 2); xl.setAttribute('y', H - 8);
|
||||
xl.setAttribute('x', W / 2); xl.setAttribute('y', H - 6);
|
||||
xl.setAttribute('class', 'axis-label'); xl.setAttribute('text-anchor', 'middle');
|
||||
xl.textContent = 'inference latency (μs / window) →'; svg.appendChild(xl);
|
||||
xl.textContent = 'inference latency (log scale, μs / window) →';
|
||||
svg.appendChild(xl);
|
||||
const yl = document.createElementNS(ns, 'text');
|
||||
yl.setAttribute('transform', `translate(14,${H/2}) rotate(-90)`);
|
||||
yl.setAttribute('class', 'axis-label'); yl.setAttribute('text-anchor', 'middle');
|
||||
yl.textContent = '↑ held-out accuracy'; svg.appendChild(yl);
|
||||
yl.textContent = '↑ held-out macro-F1'; svg.appendChild(yl);
|
||||
}
|
||||
function emptyState() {
|
||||
points.clear(); svg.innerHTML = '';
|
||||
|
|
@ -2423,15 +2471,33 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
|
|||
svg.appendChild(t);
|
||||
}
|
||||
function project(latency, accuracy) {
|
||||
const x = 50 + Math.min(1, (latency - xmin) / (xmax - xmin)) * (W - 60);
|
||||
const y = (H - 30) - Math.max(0, Math.min(1, (accuracy - ymin) / (ymax - ymin))) * (H - 40);
|
||||
const lat = Math.max(1, latency);
|
||||
const lx = Math.log10(lat);
|
||||
const x = 50 + Math.min(1, Math.max(0, (lx - xLogMin) / (xLogMax - xLogMin))) * (W - 60);
|
||||
const y = (H - 40) - Math.max(0, Math.min(1, (accuracy - ymin) / (ymax - ymin))) * (H - 50);
|
||||
return [x, y];
|
||||
}
|
||||
// Anti-overlap: sort points by x, place labels alternately
|
||||
// above (even index) and below (odd index) so adjacent labels
|
||||
// never share both x and y bands. Re-runs every render so a
|
||||
// late-arriving model_perf event slots into the staircase.
|
||||
function repaintLabels() {
|
||||
const entries = Array.from(points.entries()).map(([model, rec]) => ({
|
||||
model, rec, x: rec.cx, y: rec.cy,
|
||||
})).sort((a, b) => a.x - b.x);
|
||||
entries.forEach((e, i) => {
|
||||
const t = e.rec.g.querySelector('text');
|
||||
const above = (i % 2 === 0);
|
||||
t.setAttribute('x', e.x.toFixed(1));
|
||||
t.setAttribute('y', (above ? e.y - 12 : e.y + 18).toFixed(1));
|
||||
t.setAttribute('text-anchor', 'middle');
|
||||
});
|
||||
}
|
||||
function render(model, latency, accuracy) {
|
||||
let g = points.get(model);
|
||||
if (!g) {
|
||||
let rec = points.get(model);
|
||||
if (!rec) {
|
||||
if (!points.size) setupAxes();
|
||||
g = document.createElementNS(ns, 'g');
|
||||
const g = document.createElementNS(ns, 'g');
|
||||
const c = document.createElementNS(ns, 'circle');
|
||||
c.setAttribute('r', 7); c.setAttribute('class', 'perf-point');
|
||||
g.appendChild(c);
|
||||
|
|
@ -2439,15 +2505,15 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
|
|||
t.setAttribute('class', 'perf-label');
|
||||
g.appendChild(t);
|
||||
svg.appendChild(g);
|
||||
points.set(model, g);
|
||||
rec = { g, cx: 0, cy: 0 };
|
||||
points.set(model, rec);
|
||||
}
|
||||
const [px, py] = project(latency, accuracy);
|
||||
g.querySelector('circle').setAttribute('cx', px.toFixed(1));
|
||||
g.querySelector('circle').setAttribute('cy', py.toFixed(1));
|
||||
const t = g.querySelector('text');
|
||||
t.setAttribute('x', (px + 12).toFixed(1));
|
||||
t.setAttribute('y', (py + 4).toFixed(1));
|
||||
t.textContent = model;
|
||||
rec.cx = px; rec.cy = py;
|
||||
rec.g.querySelector('circle').setAttribute('cx', px.toFixed(1));
|
||||
rec.g.querySelector('circle').setAttribute('cy', py.toFixed(1));
|
||||
rec.g.querySelector('text').textContent = model;
|
||||
repaintLabels();
|
||||
}
|
||||
on('demo_start', () => {
|
||||
[
|
||||
|
|
|
|||
|
|
@ -1314,6 +1314,6 @@
|
|||
</article>
|
||||
</div>
|
||||
|
||||
<script src="/static/dashboard.js?v=eb69e920"></script>
|
||||
<script src="/static/dashboard.js?v=7f398906"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue