From f429bd42231ca1899fbe30ba1107601a9f83e93b Mon Sep 17 00:00:00 2001 From: Max Gorog Date: Fri, 8 May 2026 16:31:42 -0500 Subject: [PATCH] perf scatter: log-x + alternating-position labels (kill overlap) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- training/dashboard/static/dashboard.js | 108 ++++++++++++++++++++----- training/dashboard/static/index.html | 2 +- 2 files changed, 88 insertions(+), 22 deletions(-) diff --git a/training/dashboard/static/dashboard.js b/training/dashboard/static/dashboard.js index 45b9793..0d5b233 100644 --- a/training/dashboard/static/dashboard.js +++ b/training/dashboard/static/dashboard.js @@ -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', () => { [ diff --git a/training/dashboard/static/index.html b/training/dashboard/static/index.html index dc09048..1b96fdc 100644 --- a/training/dashboard/static/index.html +++ b/training/dashboard/static/index.html @@ -1314,6 +1314,6 @@ - +