sergiopaniego HF Staff
Publish the deck source under source/ so the next deck can start from it
3bc016b verified Download source/src/embeds/d3-rollout-graph.html from FineEnvs/multi-harness-rl-slides: direct link, hf CLI and curl.
- Browser
- Download file 11.9 kB
-
https://huggingface.co/spaces/FineEnvs/multi-harness-rl-slides/resolve/main/source/src/embeds/d3-rollout-graph.html
- Command line
-
hf download hf://spaces/FineEnvs/multi-harness-rl-slides/source/src/embeds/d3-rollout-graph.html
-
curl -L -o d3-rollout-graph.html https://huggingface.co/spaces/FineEnvs/multi-harness-rl-slides/resolve/main/source/src/embeds/d3-rollout-graph.html
11.9 kB
| <div class="rollout-graph" style="width:100%;margin:14px 0;"></div> | |
| <style> | |
| .rollout-graph { position: relative; } | |
| .rollout-graph svg { display:block; width:100%; height:auto; overflow:visible; } | |
| .rollout-graph .rg-pick { display:flex; flex-wrap:wrap; gap:6px; margin-bottom:12px; } | |
| .rollout-graph .rg-btn { appearance:none; cursor:pointer; font:inherit; font-size:12px; line-height:1; | |
| padding:7px 12px; border-radius:999px; border:1px solid var(--border-color); | |
| background:transparent; color:var(--muted-color); transition:color .15s, border-color .15s; } | |
| .rollout-graph .rg-btn:hover { color:var(--text-color); } | |
| .rollout-graph .rg-btn[aria-pressed="true"] { color:var(--text-color); border-color:var(--text-color); font-weight:600; } | |
| .rollout-graph .rg-btn:focus-visible { outline:2px solid var(--text-color); outline-offset:2px; } | |
| .rollout-graph .rg-score { display:flex; flex-wrap:wrap; gap:8px 22px; margin-top:12px; } | |
| .rollout-graph .rg-stat { display:flex; flex-direction:column; gap:2px; } | |
| .rollout-graph .rg-stat b { font-size:17px; font-weight:700; color:var(--text-color); | |
| font-variant-numeric:tabular-nums; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; } | |
| .rollout-graph .rg-stat span { font-size:11px; letter-spacing:.05em; text-transform:uppercase; color:var(--muted-color); } | |
| .rollout-graph .rg-lg { display:flex; flex-wrap:wrap; gap:6px 16px; margin-top:12px; font-size:12px; color:var(--muted-color); } | |
| .rollout-graph .rg-lg-t { width:100%; font-size:11px; letter-spacing:.08em; text-transform:uppercase; opacity:.75; } | |
| .rollout-graph .rg-k { display:inline-flex; align-items:center; gap:7px; } | |
| .rollout-graph .rg-sw { width:11px; height:11px; } | |
| .rollout-graph .rg-nt { margin-top:10px; font-size:12px; line-height:1.55; color:var(--muted-color); } | |
| </style> | |
| <script> | |
| (() => { | |
| const C = 'rollout-graph'; | |
| const boot = () => { | |
| const nodes0 = [...document.querySelectorAll('.'+C)].filter(e => e.dataset.mounted !== 'true'); | |
| const r = nodes0[nodes0.length-1]; if (!r) return; r.dataset.mounted = 'true'; | |
| const V = (k,f) => (getComputedStyle(r).getPropertyValue(k).trim() || f); | |
| const P = (i) => { | |
| if (window.ColorPalettes && window.ColorPalettes.categorical) { | |
| try { return window.ColorPalettes.categorical(4)[i%4]; } catch(e) {} | |
| } | |
| return ['rgb(78,165,183)','rgb(146,176,234)','rgb(206,192,250)','rgb(232,137,171)'][i%4]; | |
| }; | |
| const esc = (t) => String(t).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); | |
| // A trace is a DAG of unique messages, each linked to its predecessor. Every | |
| // root-to-leaf path is a branch, and a branch is what a trainer can treat as | |
| // one contiguous sample. Two things create branches here: compaction, which | |
| // reattaches the summarised continuation at the root system prompt rather | |
| // than at the deep node it came from, and a subagent, which brings its own | |
| // root. A resample that was thrown away is a dead end, not a branch. | |
| // Every number under this figure is derived from this list, so the drawing | |
| // and the counts cannot drift apart. | |
| const N = [ | |
| { id:'S1', p:null, d:0, lane:0, kind:'system', t:'system' }, | |
| { id:'u1', p:'S1', d:1, lane:0, kind:'user', t:'task' }, | |
| { id:'a1', p:'u1', d:2, lane:0, kind:'assistant', t:'turn 1' }, | |
| { id:'t1', p:'a1', d:3, lane:0, kind:'tool', t:'result' }, | |
| { id:'a2', p:'t1', d:4, lane:0, kind:'assistant', t:'turn 2' }, | |
| { id:'t2', p:'a2', d:5, lane:0, kind:'tool', t:'result' }, | |
| { id:'a3', p:'t2', d:6, lane:0, kind:'assistant', t:'summary', leaf:true }, | |
| { id:'xx', p:'a2', d:5, lane:1, kind:'assistant', t:'resample', dead:true }, | |
| { id:'u2', p:'S1', d:1, lane:2, kind:'user', t:'summary in', compact:true }, | |
| { id:'a4', p:'u2', d:2, lane:2, kind:'assistant', t:'turn 3' }, | |
| { id:'t3', p:'a4', d:3, lane:2, kind:'tool', t:'result' }, | |
| { id:'a5', p:'t3', d:4, lane:2, kind:'assistant', t:'submit', leaf:true }, | |
| { id:'S2', p:null, d:0, lane:3, kind:'system', t:'subagent', root2:true }, | |
| { id:'u3', p:'S2', d:1, lane:3, kind:'user', t:'sub-task' }, | |
| { id:'a6', p:'u3', d:2, lane:3, kind:'assistant', t:'turn 1' }, | |
| { id:'a7', p:'a6', d:3, lane:3, kind:'assistant', t:'return', leaf:true }, | |
| ]; | |
| const byId = Object.fromEntries(N.map(n => [n.id, n])); | |
| const pathTo = (id) => { const out = []; let c = byId[id]; while (c) { out.unshift(c.id); c = c.p ? byId[c.p] : null; } return out; }; | |
| const BRANCHES = N.filter(n => n.leaf).map((n, i) => ({ leaf: n.id, path: pathTo(n.id), i })); | |
| const DEAD = N.filter(n => n.dead).map(n => pathTo(n.id)); | |
| // What a per-turn store would have cost: every assistant turn keeps its whole | |
| // prompt plus its completion, so shared prefixes are copied again and again. | |
| const copies = N.filter(n => n.kind === 'assistant') | |
| .reduce((sum, n) => sum + pathTo(n.id).length, 0); | |
| const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; | |
| let sel = 0, timer = 0, visible = true; // sel: 0..BRANCHES.length-1, or -1 for all | |
| const draw = () => { | |
| const W = Math.max(320, r.clientWidth); | |
| const bd = V('--border-color','rgba(128,128,128,.3)'); | |
| const mu = V('--muted-color','rgba(128,128,128,.8)'); | |
| const tx = V('--text-color','#16181d'); | |
| const sf = V('--surface-bg','#fff'); | |
| const mono = 'ui-monospace,SFMono-Regular,Menlo,monospace'; | |
| const KC = { system: P(2), user: P(1), assistant: P(0), tool: P(3) }; | |
| const narrow = W < 620; | |
| const padL = 16, padR = 16, padT = 22; | |
| const maxD = Math.max(...N.map(n => n.d)); | |
| const colW = (W - padL - padR) / (maxD + 1); | |
| const laneH = narrow ? 56 : 62; | |
| const laneY = [0, 0.62, 1.75, 3.0]; // lane 1 tucks under lane 0 | |
| const H = padT + laneY[3]*laneH + laneH + 26; | |
| const X = (n) => Math.round(padL + colW*n.d + colW*0.5); | |
| const Y = (n) => Math.round(padT + laneY[n.lane]*laneH + 16); | |
| const active = sel >= 0 ? new Set(BRANCHES[sel].path) : null; | |
| const onPath = (id) => !active || active.has(id); | |
| let s = `<svg viewBox="0 0 ${W} ${H}" role="img" aria-label="A rollout trace as a graph of ${N.length} unique messages. Three root-to-leaf branches are trainable samples; one resampled turn is a dead end. The compacted continuation reattaches at the root system prompt.">`; | |
| // edges | |
| N.forEach(n => { | |
| if (!n.p) return; | |
| const q = byId[n.p]; | |
| const x1 = X(q), y1 = Y(q), x2 = X(n), y2 = Y(n); | |
| const lit = onPath(n.id) && onPath(n.p) && !n.dead; | |
| const c = n.dead ? mu : (n.compact ? KC.user : KC.assistant); | |
| const mx = (x1 + x2) / 2; | |
| s += `<path d="M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}" fill="none" | |
| stroke="${c}" stroke-opacity="${n.dead ? .35 : (lit ? .9 : .18)}" | |
| stroke-width="${n.dead ? 1.2 : (lit ? 2.4 : 1.3)}" | |
| ${n.dead ? 'stroke-dasharray="4 3"' : ''}/>`; | |
| }); | |
| // nodes | |
| N.forEach(n => { | |
| const x = X(n), y = Y(n), c = n.dead ? mu : KC[n.kind]; | |
| const lit = onPath(n.id) && !n.dead; | |
| const op = n.dead ? .6 : (lit ? 1 : .22); | |
| if (n.kind === 'system') { | |
| s += `<rect x="${x-6}" y="${y-6}" width="12" height="12" rx="3" fill="${c}" fill-opacity="${op}" stroke="${c}" stroke-opacity="${op}"/>`; | |
| } else if (n.kind === 'user') { | |
| s += `<circle cx="${x}" cy="${y}" r="5.5" fill="${sf}" stroke="${c}" stroke-width="2" stroke-opacity="${op}"/>`; | |
| } else if (n.kind === 'tool') { | |
| s += `<path d="M ${x} ${y-6} L ${x+6} ${y} L ${x} ${y+6} L ${x-6} ${y} z" fill="${c}" fill-opacity="${op}"/>`; | |
| } else { | |
| s += `<circle cx="${x}" cy="${y}" r="6" fill="${c}" fill-opacity="${op}"/>`; | |
| } | |
| if (n.leaf && lit) s += `<circle cx="${x}" cy="${y}" r="11" fill="none" stroke="${c}" stroke-opacity=".4"/>`; | |
| s += `<text x="${x}" y="${y+19}" text-anchor="middle" font-size="9.5" | |
| fill="${n.dead ? mu : (lit ? tx : mu)}" fill-opacity="${n.dead ? .6 : (lit ? .85 : .3)}">${esc(n.t)}</text>`; | |
| }); | |
| // the two structural callouts | |
| const u2 = byId['u2'], S2 = byId['S2']; | |
| s += `<text x="${X(u2)+14}" y="${Y(u2)-8}" font-size="10" fill="${mu}" font-family="${mono}">compaction rejoins the root</text>`; | |
| s += `<text x="${X(S2)-4}" y="${Y(S2)-13}" font-size="10" fill="${mu}" font-family="${mono}">subagent: its own root</text>`; | |
| const xx = byId['xx']; | |
| s += `<text x="${X(xx)+14}" y="${Y(xx)+4}" font-size="10" fill="${mu}" fill-opacity=".75" font-family="${mono}">discarded</text>`; | |
| s += '</svg>'; | |
| const bl = sel >= 0 ? BRANCHES[sel] : null; | |
| const score = `<div class="rg-score"> | |
| <span class="rg-stat"><b>${N.length}</b><span>unique messages</span></span> | |
| <span class="rg-stat"><b>${BRANCHES.length}</b><span>branches, so ${BRANCHES.length} samples</span></span> | |
| <span class="rg-stat"><b>${copies}</b><span>copies if stored per turn</span></span> | |
| <span class="rg-stat"><b>${bl ? bl.path.length : '—'}</b><span>${bl ? 'messages in this branch' : 'showing every branch'}</span></span> | |
| </div>`; | |
| const legend = `<div class="rg-lg"><span class="rg-lg-t">One node is one message</span>` | |
| + `<span class="rg-k"><span class="rg-sw" style="background:${KC.system};border-radius:3px"></span>system</span>` | |
| + `<span class="rg-k"><span class="rg-sw" style="border:2px solid ${KC.user};border-radius:50%"></span>user</span>` | |
| + `<span class="rg-k"><span class="rg-sw" style="background:${KC.assistant};border-radius:50%"></span>assistant, the sampled part</span>` | |
| + `<span class="rg-k"><span class="rg-sw" style="background:${KC.tool};transform:rotate(45deg)"></span>tool result</span>` | |
| + `</div>`; | |
| const note = `<div class="rg-nt">Turns are linked by exact token prefix, which works the same way in every harness.</div>`; | |
| r.innerHTML = | |
| `<div class="rg-pick" role="group" aria-label="Highlight one branch">` | |
| + BRANCHES.map((b,i) => `<button type="button" class="rg-btn" data-b="${i}" aria-pressed="${sel===i}">Branch ${i+1}</button>`).join('') | |
| + `<button type="button" class="rg-btn" data-b="-1" aria-pressed="${sel===-1}">All</button>` | |
| + `</div>` + s + score + legend + note; | |
| r.querySelectorAll('.rg-btn').forEach(b => b.addEventListener('click', () => { | |
| sel = +b.dataset.b; clearInterval(timer); timer = 0; draw(); | |
| })); | |
| }; | |
| draw(); | |
| if (!reduce) { | |
| timer = setInterval(() => { | |
| if (!visible) return; | |
| sel = (sel + 1) % BRANCHES.length; | |
| draw(); | |
| }, 2800); | |
| } | |
| if ('IntersectionObserver' in window && !reduce) { | |
| new IntersectionObserver((es) => { visible = es.some(e => e.isIntersecting); }, | |
| { rootMargin:'80px' }).observe(r); | |
| } | |
| // Colours are read when drawing, so redraw when the reader switches theme. | |
| new MutationObserver(draw).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); | |
| if ('ResizeObserver' in window) { | |
| let t; new ResizeObserver(() => { clearTimeout(t); t = setTimeout(draw, 120); }).observe(r); | |
| } else window.addEventListener('resize', draw); | |
| }; | |
| if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); else boot(); | |
| })(); | |
| </script> | |