Download code/scripts/dashboard.html from darioooooo0o/tiny-agent-112m: direct link, hf CLI and curl.
- Browser
- Download file 24.6 kB
-
https://huggingface.co/darioooooo0o/tiny-agent-112m/resolve/main/code/scripts/dashboard.html
- Command line
-
hf download hf://darioooooo0o/tiny-agent-112m/code/scripts/dashboard.html
-
curl -L -o dashboard.html https://huggingface.co/darioooooo0o/tiny-agent-112m/resolve/main/code/scripts/dashboard.html
24.6 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Tiny-agent Progress</title> | |
| <style> | |
| :root { | |
| --bg: #f6f7f9; --panel: #ffffff; --ink: #16181d; --muted: #667085; --line: #e4e7ec; --grid: #eef0f3; | |
| --accent: #3b6ef5; --good: #1f9d55; --warn: #c27c0e; --bad: #d64545; | |
| --c1: #3b6ef5; --c2: #e8833a; --c3: #1f9d55; --c4: #d64545; --c5: #8b5cf6; --c6: #0e9f9a; | |
| } | |
| @media (prefers-color-scheme: dark) { | |
| :root:not([data-theme="light"]) { | |
| --bg: #0f1115; --panel: #171a21; --ink: #e8eaf0; --muted: #8b93a7; --line: #262b36; --grid: #1f242e; | |
| --accent: #6d93ff; --good: #3ccf7e; --warn: #f0b13c; --bad: #ff6b6b; | |
| --c1: #6d93ff; --c2: #ff9f5a; --c3: #3ccf7e; --c4: #ff6b6b; --c5: #a78bfa; --c6: #2dd4bf; | |
| } | |
| } | |
| :root[data-theme="dark"] { | |
| --bg: #0f1115; --panel: #171a21; --ink: #e8eaf0; --muted: #8b93a7; --line: #262b36; --grid: #1f242e; | |
| --accent: #6d93ff; --good: #3ccf7e; --warn: #f0b13c; --bad: #ff6b6b; | |
| --c1: #6d93ff; --c2: #ff9f5a; --c3: #3ccf7e; --c4: #ff6b6b; --c5: #a78bfa; --c6: #2dd4bf; | |
| } | |
| * { box-sizing: border-box; } | |
| body { margin: 0; background: var(--bg); color: var(--ink); | |
| font: 14px/1.45 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; } | |
| .wrap { max-width: 1280px; margin: 0 auto; padding: 20px 16px 48px; } | |
| header { display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; margin-bottom: 16px; } | |
| h1 { font-size: 20px; margin: 0; letter-spacing: -0.01em; } | |
| .sub { color: var(--muted); font-size: 13px; } | |
| .pill { display: inline-block; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; | |
| background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent); } | |
| .pill.err { background: color-mix(in srgb, var(--bad) 15%, transparent); color: var(--bad); } | |
| .spacer { flex: 1; } | |
| button.theme { background: none; border: 1px solid var(--line); color: var(--muted); border-radius: 8px; | |
| padding: 3px 9px; font: inherit; font-size: 12px; cursor: pointer; } | |
| .pipeline { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin-bottom: 16px; } | |
| .stage { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; min-width: 0; } | |
| .stage .name { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; } | |
| .stage .val { font-weight: 600; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } | |
| .stage .bar { height: 4px; background: var(--grid); border-radius: 2px; margin-top: 8px; overflow: hidden; } | |
| .stage .bar > i { display: block; height: 100%; background: var(--accent); } | |
| .stage.done .bar > i { background: var(--good); } | |
| .stage.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; } | |
| .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--line); flex: none; } | |
| .stage.done .dot { background: var(--good); } | |
| .stage.active .dot { background: var(--accent); animation: pulse 1.6s infinite; } | |
| @keyframes pulse { 50% { opacity: .35; } } | |
| .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; margin-bottom: 16px; } | |
| .kpi { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; } | |
| .kpi .k { font-size: 12px; color: var(--muted); } | |
| .kpi .v { font-size: 22px; font-weight: 650; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; } | |
| .kpi .d { font-size: 12px; color: var(--muted); } | |
| h2 { font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 24px 0 8px; } | |
| .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: 12px; } | |
| .card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; min-width: 0; } | |
| .card h3 { font-size: 14px; margin: 0 0 2px; } | |
| .card .hint { font-size: 12px; color: var(--muted); margin-bottom: 6px; } | |
| .legend { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 12px; color: var(--muted); margin-bottom: 4px; } | |
| .legend i { display: inline-block; width: 10px; height: 3px; border-radius: 2px; vertical-align: middle; margin-right: 5px; } | |
| .chart { position: relative; } | |
| .chart svg { display: block; width: 100%; height: 190px; } | |
| .tip { position: absolute; pointer-events: none; background: var(--panel); border: 1px solid var(--line); | |
| border-radius: 8px; padding: 6px 8px; font-size: 12px; box-shadow: 0 4px 14px rgba(0,0,0,.12); white-space: nowrap; display: none; z-index: 2; } | |
| .empty { height: 190px; display: grid; place-items: center; color: var(--muted); font-size: 13px; text-align: center; padding: 0 20px; } | |
| table { width: 100%; border-collapse: collapse; font-size: 13px; font-variant-numeric: tabular-nums; } | |
| th, td { text-align: right; padding: 5px 6px; border-bottom: 1px solid var(--line); } | |
| th:first-child, td:first-child { text-align: left; } | |
| th { color: var(--muted); font-weight: 500; font-size: 12px; } | |
| td .tag { font-size: 11px; color: var(--muted); } | |
| .up { color: var(--good); } .down { color: var(--bad); } | |
| pre { margin: 0; white-space: pre-wrap; word-break: break-word; font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; | |
| color: var(--ink); max-height: 320px; overflow: auto; } | |
| .problems { color: var(--bad); } | |
| @media (max-width: 720px) { | |
| .pipeline { grid-template-columns: 1fr 1fr; } | |
| .grid { grid-template-columns: 1fr; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <header> | |
| <h1>Tiny-agent</h1> | |
| <span id="stage" class="pill">loading…</span> | |
| <span class="sub" id="updated"></span> | |
| <span class="spacer"></span> | |
| <button class="theme" id="themeBtn" title="Toggle theme">◐ theme</button> | |
| </header> | |
| <div class="pipeline" id="pipeline"></div> | |
| <div class="kpis" id="kpis"></div> | |
| <h2>Reinforcement learning (GRPO)</h2> | |
| <div class="grid" id="rlGrid"></div> | |
| <h2>Per-task accuracy</h2> | |
| <div class="grid"><div class="card" id="kinds"></div></div> | |
| <h2>Pretraining & decay</h2> | |
| <div class="grid" id="ptGrid"></div> | |
| <h2>Log</h2> | |
| <div class="grid"> | |
| <div class="card"><h3>Pipeline events</h3><div class="hint">orchestrator and RL waiter</div><pre id="events"></pre></div> | |
| <div class="card"><h3>RL notes</h3><div class="hint">changes made during RL and why (rl/NOTES.md)</div><pre id="notes"></pre></div> | |
| </div> | |
| </div> | |
| <script> | |
| const $ = id => document.getElementById(id); | |
| const css = v => getComputedStyle(document.documentElement).getPropertyValue(v).trim(); | |
| const C = () => ['--c1','--c2','--c3','--c4','--c5','--c6'].map(css); | |
| const fmt = (v, d = 2) => v == null || isNaN(v) ? '–' : (+v).toFixed(d); | |
| const pct = (v, d = 0) => v == null ? '–' : (100 * v).toFixed(d) + '%'; | |
| const esc = s => String(s).replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c])); | |
| // theme toggle (per-viewer convenience only) | |
| (function () { | |
| let t = null; try { t = localStorage.getItem('ta-theme'); } catch (e) {} | |
| if (t) document.documentElement.dataset.theme = t; | |
| $('themeBtn').onclick = () => { | |
| const dark = matchMedia('(prefers-color-scheme: dark)').matches; | |
| const cur = document.documentElement.dataset.theme || (dark ? 'dark' : 'light'); | |
| const nxt = cur === 'dark' ? 'light' : 'dark'; | |
| document.documentElement.dataset.theme = nxt; | |
| try { localStorage.setItem('ta-theme', nxt); } catch (e) {} | |
| if (last) render(last); | |
| }; | |
| })(); | |
| function ema(pts, a = 0.15) { | |
| let m = null; return pts.map(([x, y]) => (y == null ? [x, null] : (m = m == null ? y : m + a * (y - m), [x, m]))); | |
| } | |
| // minimal SVG line chart with hover readout | |
| function chart(card, title, hint, series, o = {}) { | |
| card.innerHTML = `<h3>${title}</h3><div class="hint">${hint}</div>`; | |
| const has = series.some(s => s.pts.some(p => p[1] != null)); | |
| if (!has) { card.insertAdjacentHTML('beforeend', `<div class="empty">${o.empty || 'no data yet'}</div>`); return; } | |
| card.insertAdjacentHTML('beforeend', '<div class="legend">' + series.filter(s => !s.noLegend).map(s => | |
| `<span><i style="background:${s.color};${s.dash ? 'opacity:.45' : ''}"></i>${s.name}</span>`).join('') + '</div>'); | |
| const box = document.createElement('div'); box.className = 'chart'; card.appendChild(box); | |
| const W = 600, H = 190, L = 44, R = 10, T = 8, B = 22; | |
| const all = series.flatMap(s => s.pts.filter(p => p[1] != null)); | |
| let x0 = Math.min(...all.map(p => p[0])), x1 = Math.max(...all.map(p => p[0])); | |
| if (x1 === x0) { x0 -= 1; x1 += 1; } | |
| let y0 = o.ymin ?? Math.min(...all.map(p => p[1])), y1 = o.ymax ?? Math.max(...all.map(p => p[1])); | |
| if (y1 === y0) { y0 -= 0.5; y1 += 0.5; } | |
| const pad = (y1 - y0) * 0.06; if (o.ymin == null) y0 -= pad; if (o.ymax == null) y1 += pad; | |
| const sx = x => L + (x - x0) / (x1 - x0) * (W - L - R), sy = y => T + (1 - (y - y0) / (y1 - y0)) * (H - T - B); | |
| const yf = o.yfmt || (v => Math.abs(y1 - y0) < 0.1 ? v.toFixed(3) : Math.abs(y1 - y0) < 5 ? v.toFixed(2) : v.toFixed(0)); | |
| const xf = o.xfmt || (v => (+v.toFixed(2)).toString()); | |
| let g = `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="none">`; | |
| for (let i = 0; i <= 4; i++) { | |
| const v = y0 + (y1 - y0) * i / 4, y = sy(v); | |
| g += `<line x1="${L}" x2="${W - R}" y1="${y}" y2="${y}" stroke="${css('--grid')}" />`; | |
| g += `<text x="${L - 6}" y="${y + 4}" text-anchor="end" font-size="10" fill="${css('--muted')}">${yf(v)}</text>`; | |
| } | |
| for (let i = 0; i <= 4; i++) { | |
| const v = x0 + (x1 - x0) * i / 4; | |
| g += `<text x="${sx(v)}" y="${H - 6}" text-anchor="middle" font-size="10" fill="${css('--muted')}">${xf(v)}</text>`; | |
| } | |
| if (o.shade) g += `<rect x="${sx(o.shade[0])}" y="${T}" width="${Math.max(0, sx(Math.min(o.shade[1], x1)) - sx(o.shade[0]))}" height="${H - T - B}" fill="${css('--accent')}" opacity=".07" />`; | |
| for (const s of series) { | |
| let d = '', pen = false; | |
| for (const [x, y] of s.pts) { if (y == null) { pen = false; continue; } d += (pen ? 'L' : 'M') + sx(x).toFixed(1) + ',' + sy(y).toFixed(1); pen = true; } | |
| g += `<path d="${d}" fill="none" stroke="${s.color}" stroke-width="${s.dash ? 1 : 2}" ${s.dash ? 'opacity=".4"' : ''} vector-effect="non-scaling-stroke" stroke-linejoin="round" />`; | |
| if (s.markers) for (const [x, y] of s.pts) if (y != null) g += `<circle cx="${sx(x)}" cy="${sy(y)}" r="3" fill="${s.color}" />`; | |
| } | |
| g += `<line id="hv" x1="0" x2="0" y1="${T}" y2="${H - B}" stroke="${css('--muted')}" stroke-dasharray="3 3" opacity="0" vector-effect="non-scaling-stroke"/></svg><div class="tip"></div>`; | |
| box.innerHTML = g; | |
| const svg = box.querySelector('svg'), tip = box.querySelector('.tip'), hv = box.querySelector('#hv'); | |
| svg.addEventListener('mousemove', ev => { | |
| const r = svg.getBoundingClientRect(), x = x0 + ((ev.clientX - r.left) / r.width * W - L) / (W - L - R) * (x1 - x0); | |
| const rows = series.filter(s => !s.noTip).map(s => { | |
| let best = null; for (const p of s.pts) if (p[1] != null && (best == null || Math.abs(p[0] - x) < Math.abs(best[0] - x))) best = p; | |
| return best && `<div><span style="color:${s.color}">●</span> ${s.name}: <b>${(o.tipfmt || yf)(best[1])}</b></div>`; | |
| }).filter(Boolean); | |
| const near = series[0].pts.reduce((b, p) => (b == null || Math.abs(p[0] - x) < Math.abs(b[0] - x) ? p : b), null); | |
| hv.setAttribute('x1', sx(near[0])); hv.setAttribute('x2', sx(near[0])); hv.setAttribute('opacity', 1); | |
| tip.innerHTML = `<div style="color:${css('--muted')}">${o.xname || 'x'} ${xf(near[0])}</div>` + rows.join(''); | |
| tip.style.display = 'block'; | |
| const px = (ev.clientX - r.left), w = tip.offsetWidth; | |
| tip.style.left = Math.min(Math.max(0, px + 12), r.width - w) + 'px'; tip.style.top = '8px'; | |
| }); | |
| svg.addEventListener('mouseleave', () => { tip.style.display = 'none'; hv.setAttribute('opacity', 0); }); | |
| } | |
| function stageBox(name, val, frac, cls) { | |
| return `<div class="stage ${cls}"><div class="name"><span class="dot"></span>${name}</div><div class="val">${val}</div>` + | |
| `<div class="bar"><i style="width:${Math.round(100 * Math.max(0, Math.min(1, frac || 0)))}%"></i></div></div>`; | |
| } | |
| const kpi = (k, v, d = '') => `<div class="kpi"><div class="k">${k}</div><div class="v">${v}</div><div class="d">${d}</div></div>`; | |
| const mins = m => m == null ? '' : m >= 90 ? `${(m / 60).toFixed(1)} h` : `${Math.round(m)} min`; | |
| let last = null; | |
| function render(s) { | |
| last = s; | |
| const c = C(), order = ['pretrain', 'teacher', 'decay', 'eval', 'rl', 'rl_done']; | |
| const at = order.indexOf(s.stage), state = i => (at > i || (s.stage === 'rl_done' && i <= 4) ? 'done' : at === i ? 'active' : ''); | |
| const labels = { sft: 'Warm start (SFT) before RL run 2', pretrain: 'Pretraining', teacher: 'Teacher data', decay: 'LR decay', eval: 'Eval gate', rl: 'GRPO', rl_done: 'GRPO finished', idle: 'Idle' }; | |
| $('stage').textContent = labels[s.stage] || s.stage; $('stage').className = 'pill'; | |
| $('updated').textContent = 'updated ' + s.now.slice(11) + ' · refreshes every 15 s'; | |
| const pt = s.pretrain, dc = pt && pt.decay, tb = s.teacher.build, rl = s.rl, eg = s.eval_gate; | |
| const rlSteps = rl ? rl.steps : [], rlLast = rl && rl.last; | |
| const RL_TOTAL = (() => { const a = (s.procs['grpo.py'] || {}).args || ''; const m = a.match(/--steps (\d+)/); return m ? +m[1] : 300; })(); | |
| const RUN = rl ? rl.run : ''; | |
| const preTok = dc ? (pt.series.find(r => r.step >= dc.start_step) || pt.last).tokens : pt && pt.last.tokens; | |
| $('pipeline').innerHTML = | |
| stageBox('Pretraining', pt ? `${(preTok / 1e9).toFixed(2)}B tokens` : '–', 1, at > 0 || s.stage === 'rl_done' ? 'done' : state(0)) + | |
| stageBox('Teacher data', tb ? `${tb.kept.toLocaleString()} episodes` : (s.teacher.stats ? `${s.teacher.stats.usable} usable` : '–'), tb ? 1 : 0.5, tb ? 'done' : state(1)) + | |
| stageBox('LR decay', dc ? `${pct(dc.frac)} · ${dc.eta_min != null && dc.frac < 1 ? mins(dc.eta_min) + ' left' : 'done'}` : 'pending', dc ? dc.frac : 0, pt && pt.final ? 'done' : state(2)) + | |
| stageBox('Eval gate', eg ? `pass@1 ${pct(eg._all.pass1, 1)}` : (s.stage === 'eval' ? 'running…' : 'pending'), eg ? 1 : (s.stage === 'eval' ? 0.5 : 0), eg ? 'done' : state(3)) + | |
| stageBox('GRPO' + (RUN ? ' · ' + RUN : ''), rlLast ? `step ${rlLast.step} / ${RL_TOTAL}` : 'pending', rlLast ? rlLast.step / RL_TOTAL : 0, s.stage === 'rl_done' ? 'done' : state(4)); | |
| // KPIs follow the active stage | |
| let k = ''; | |
| if (rlLast) { | |
| const accE = ema(rlSteps.map(r => [r.step, r.acc]), 0.1).pop()[1]; | |
| const ev = rl.evals.length ? rl.evals[rl.evals.length - 1] : null; | |
| const base = eg ? eg._held_out.pass1 : null; | |
| const ho = ev ? ev._held_out.pass1 : null; | |
| const delta = ho != null && base != null ? ho - base : null; | |
| k += kpi('Train accuracy (EMA)', pct(accE, 1), `last step ${pct(rlLast.acc, 1)}`); | |
| k += kpi('Held-out pass@1', pct(ho, 1), delta == null ? 'tasks never trained on' : `<span class="${delta >= 0 ? 'up' : 'down'}">${delta >= 0 ? '+' : ''}${(100 * delta).toFixed(1)} pts</span> vs eval gate`); | |
| k += kpi('Reasoning tokens / episode', fmt(rlLast.gen_tokens, 0), `${fmt(rlLast.turns, 1)} turns`); | |
| k += kpi('Step time', mins(rlLast.step_s / 60), `ETA ${mins((RL_TOTAL - rlLast.step) * rlLast.step_s / 60)}`); | |
| k += kpi('Train/decode mismatch', fmt(rlLast.mismatch, 3), `clipped ${pct(rlLast.clip_frac, 1)}`); | |
| k += kpi('Repeat calls / episode', fmt(rlLast.repeat_calls, 2), `flagged turns ${pct(rlLast.flagged_turns, 1)}`); | |
| } else if (pt) { | |
| const l = pt.last; | |
| k += kpi('Train loss', fmt(l.loss, 3), `step ${l.step.toLocaleString()}`); | |
| k += kpi('Tokens seen', (l.tokens / 1e9).toFixed(2) + 'B', `${(l.tok_s / 1000).toFixed(1)}k tok/s`); | |
| k += kpi('LR multiplier', fmt(l.lr_mult, 3), dc ? 'decaying' : 'stable phase'); | |
| if (dc) k += kpi('Decay progress', pct(dc.frac), `${dc.done} / ${dc.steps} steps · ${mins(dc.eta_min)} left`); | |
| if (tb) k += kpi('Teacher episodes kept', tb.kept.toLocaleString(), `of ${tb.total.toLocaleString()} generated`); | |
| if (eg) k += kpi('Eval gate pass@1', pct(eg._all.pass1, 1), `held-out ${pct(eg._held_out.pass1, 1)}`); | |
| } | |
| $('kpis').innerHTML = k; | |
| // RL charts | |
| const g = $('rlGrid'); g.innerHTML = ''; | |
| const add = () => { const d = document.createElement('div'); d.className = 'card'; g.appendChild(d); return d; }; | |
| const wait = s.stage === 'rl' || s.stage === 'rl_done' ? 'waiting for the first step…' : 'GRPO starts after the decay and the eval gate'; | |
| const S = key => rlSteps.map(r => [r.step, r[key]]); | |
| const xo = { xname: 'step', xfmt: v => v.toFixed(0), empty: wait }; | |
| chart(add(), 'Reward & accuracy', 'per step (thin) and smoothed (thick); 256 episodes per step', [ | |
| { name: 'accuracy', color: c[0], pts: ema(S('acc'), 0.1) }, { name: 'acc raw', color: c[0], pts: S('acc'), dash: 1, noLegend: 1, noTip: 1 }, | |
| { name: 'reward', color: c[1], pts: ema(S('reward'), 0.1) }, { name: 'reward raw', color: c[1], pts: S('reward'), dash: 1, noLegend: 1, noTip: 1 }, | |
| ], { ...xo, ymin: 0, ymax: 1, tipfmt: v => v.toFixed(3) }); | |
| const evPts = key => [...(eg ? [[0, eg[key].pass1]] : []), ...(rl ? rl.evals.map(e => [e.step, e[key].pass1]) : [])]; | |
| chart(add(), 'Eval pass@1', 'every 25 steps on 150 fixed tasks; step 0 = this run\'s pre-RL baseline; faint lines = earlier runs. Held-out = task kinds never trained on', [ | |
| { name: 'held-out', color: c[4], pts: evPts('_held_out'), markers: 1 }, | |
| { name: 'in-distribution', color: c[2], pts: evPts('_in_dist'), markers: 1 }, | |
| { name: 'all', color: c[0], pts: evPts('_all'), markers: 1 }, | |
| ...Object.entries((rl && rl.refs) || {}).flatMap(([nm, ev]) => [ | |
| { name: nm + ' held-out', color: c[4], dash: 1, pts: ev.map(e => [e.step, e._held_out.pass1]) }, | |
| { name: nm + ' all', color: c[0], dash: 1, pts: ev.map(e => [e.step, e._all.pass1]) }]), | |
| ], { ...xo, ymin: 0, ymax: 1, empty: eg ? 'baseline done; first RL eval at step 25' : wait, tipfmt: v => pct(v, 1) }); | |
| chart(add(), 'Reasoning length', 'generated tokens per episode (the length penalty should push this down without hurting accuracy)', [ | |
| { name: 'gen tokens', color: c[5], pts: ema(S('gen_tokens'), 0.15) }, { name: 'raw', color: c[5], pts: S('gen_tokens'), dash: 1, noLegend: 1, noTip: 1 }, | |
| ], xo); | |
| chart(add(), 'Bad behaviour', 'per episode: repeated tool calls, parse errors, truncations; flagged = share of turns penalised', [ | |
| { name: 'repeat calls', color: c[3], pts: S('repeat_calls') }, { name: 'parse errors', color: c[1], pts: S('parse_err') }, | |
| { name: 'truncated', color: c[4], pts: S('truncated') }, { name: 'flagged turns', color: c[0], pts: S('flagged_turns') }, | |
| ], xo); | |
| chart(add(), 'Answer honesty', 'per episode: invented = wrong answer seen in no tool output (penalised); NOT_FOUND on answerable tasks and wrong-but-copied answers watch for gaming that penalty', [ | |
| { name: 'invented', color: c[3], pts: S('invented') }, { name: 'NOT_FOUND on answerable', color: c[1], pts: S('nf_answerable') }, | |
| { name: 'wrong, copied from output', color: c[5], pts: S('wrong_copied') }, | |
| ], xo); | |
| chart(add(), 'Learning signal', 'share of sampled groups dropped because all 8 attempts scored the same (too easy or too hard)', [ | |
| { name: 'no-signal share', color: c[1], pts: rlSteps.map(r => [r.step, r.groups ? r.no_signal / r.groups : null]) }, | |
| ], { ...xo, ymin: 0, ymax: 1, tipfmt: v => pct(v, 0) }); | |
| chart(add(), 'Stability', 'train/decode log-prob mismatch and share of tokens outside the importance-ratio clip', [ | |
| { name: 'mismatch', color: c[4], pts: S('mismatch') }, { name: 'clip frac', color: c[2], pts: S('clip_frac') }, | |
| ], xo); | |
| chart(add(), 'Step time', 'seconds per step (rollouts are most of it)', [ | |
| { name: 'step', color: c[0], pts: S('step_s') }, { name: 'rollout', color: c[5], pts: S('rollout_s') }, | |
| ], { ...xo, yfmt: v => v.toFixed(0) + 's' }); | |
| if (rl && rl.problems.length) { | |
| const d = add(); d.innerHTML = `<h3>Warnings from ${esc(rl.run)}/stdout.log</h3><pre class="problems">${rl.problems.map(esc).join('\n')}</pre>`; | |
| } | |
| // per-kind table | |
| const kinds = new Set([...Object.keys(eg || {}), ...(rl && rl.evals.length ? Object.keys(rl.evals[rl.evals.length - 1]) : [])].filter(x => !x.startsWith('_') && x !== 'step')); | |
| const lastKA = rlLast && rlLast.kind_acc; // not in the trimmed series; fetched from last | |
| const evLast = rl && rl.evals.length ? rl.evals[rl.evals.length - 1] : null; | |
| if (!kinds.size) { | |
| $('kinds').innerHTML = '<h3>Per task kind</h3><div class="empty" style="height:80px">appears after the eval gate</div>'; | |
| } else { | |
| let t = `<h3>Per task kind</h3><div class="hint">pass@1 on the fixed eval set; train = last GRPO step's accuracy</div><table><tr><th>kind</th><th>eval gate</th><th>latest eval${evLast ? ' (step ' + evLast.step + ')' : ''}</th><th>change</th><th>train</th></tr>`; | |
| for (const kd of [...kinds].sort()) { | |
| const a = eg && eg[kd] ? eg[kd].pass1 : null, b = evLast && evLast[kd] ? evLast[kd].pass1 : null; | |
| const dlt = a != null && b != null ? b - a : null; | |
| const ho = (s.heldout_kinds || []).includes(kd); | |
| t += `<tr><td>${kd}${ho ? ' <span class="tag">held-out</span>' : ''}</td><td>${pct(a, 1)}</td><td>${pct(b, 1)}</td>` + | |
| `<td class="${dlt == null ? '' : dlt >= 0 ? 'up' : 'down'}">${dlt == null ? '–' : (dlt >= 0 ? '+' : '') + (100 * dlt).toFixed(1)}</td>` + | |
| `<td>${lastKA && lastKA[kd] != null ? pct(lastKA[kd], 0) : '–'}</td></tr>`; | |
| } | |
| $('kinds').innerHTML = t + '</table>'; | |
| } | |
| // pretraining charts | |
| const p = $('ptGrid'); p.innerHTML = ''; | |
| const addp = () => { const d = document.createElement('div'); d.className = 'card'; p.appendChild(d); return d; }; | |
| if (pt) { | |
| // skip the first ~0.1B tokens: the initial loss (~11) would flatten everything after it | |
| const ser = pt.series.filter(r => r.tokens >= 1e8).length > 5 ? pt.series.filter(r => r.tokens >= 1e8) : pt.series; | |
| const T = ser.map(r => r.tokens / 1e9); | |
| const val = pt.val.filter(r => r.tokens >= 1e8).length > 2 ? pt.val.filter(r => r.tokens >= 1e8) : pt.val; | |
| const shade = dc ? [pt.series.find(r => r.step >= dc.start_step).tokens / 1e9, Infinity] : null; | |
| chart(addp(), 'Training loss', 'smoothed; shaded = LR decay (teacher data enters the mix here)', [ | |
| { name: 'loss', color: c[0], pts: ema(ser.map((r, i) => [T[i], r.loss]), 0.08) }, | |
| ], { xname: 'tokens (B)', xfmt: v => v.toFixed(2) + 'B', shade }); | |
| const vk = pt.val.length ? Object.keys(pt.val[pt.val.length - 1]).filter(x => !['step', 'tokens'].includes(x)) : []; | |
| const pick = ['synth_grounded', 'code_python', 'code_shell', 'math', 'english'].filter(x => vk.includes(x)); | |
| chart(addp(), 'Validation loss', 'held-out shards per source, every 250 steps', pick.map((key, i) => ( | |
| { name: key, color: c[i % 6], pts: val.map(r => [r.tokens / 1e9, r[key]]), markers: 0 })), | |
| { xname: 'tokens (B)', xfmt: v => v.toFixed(2) + 'B', shade }); | |
| chart(addp(), 'Throughput & LR', 'tokens/s (k) and LR multiplier ×100', [ | |
| { name: 'k tok/s', color: c[2], pts: ser.map((r, i) => [T[i], r.tok_s > 10000 ? r.tok_s / 1000 : null]) }, | |
| { name: 'LR ×100', color: c[1], pts: ser.map((r, i) => [T[i], 100 * r.lr_mult]) }, | |
| ], { xname: 'tokens (B)', xfmt: v => v.toFixed(2) + 'B', ymin: 0 }); | |
| } | |
| if (tb || s.teacher.stats) { | |
| const d = addp(); const st = s.teacher.stats; | |
| d.innerHTML = '<h3>Teacher data (Qwen3.8-27B)</h3><div class="hint">kept = correct, grounded, well-formed, no repeated calls, fits 4k context</div>' + | |
| (tb ? `<table><tr><th>generated</th><th>kept</th><th>wrong</th><th>malformed</th><th>too long</th><th>repeats</th></tr>` + | |
| `<tr><td>${tb.total.toLocaleString()}</td><td>${tb.kept.toLocaleString()} (${pct(tb.kept / tb.total)})</td><td>${tb.wrong}</td><td>${tb.bad}</td><td>${tb.long}</td><td>${tb.repeat}</td></tr></table>` : '') + | |
| (st ? `<div class="hint" style="margin-top:10px">last generator stats: ${st.episodes} episodes, ${st.usable} usable, ${st.tok_per_call} tokens/call, cut ${pct(st.cut_call_rate, 1)}</div>` : ''); | |
| } | |
| $('events').textContent = s.events.join('\n') || '–'; | |
| $('notes').textContent = (rl && rl.notes) || 'no changes yet'; | |
| } | |
| async function tick() { | |
| try { | |
| const r = await fetch('/api/state', { cache: 'no-store' }); | |
| const s = await r.json(); | |
| if (s.error) throw new Error(s.error); | |
| render(s); | |
| } catch (e) { | |
| $('stage').textContent = 'cannot read state'; $('stage').className = 'pill err'; | |
| $('updated').textContent = String(e).slice(0, 120); | |
| } | |
| } | |
| tick(); setInterval(tick, 15000); | |
| </script> | |
| </body> | |
| </html> | |