lerobot/robot-learning-tutorial
508
1<div class="throughput-weka-drops"></div>2<style>3 .throughput-weka-drops { position: relative; }4 .throughput-weka-drops .axis-label { fill: var(--text-color); font-size: 12px; font-weight: 700; }5 .throughput-weka-drops .axes path, .throughput-weka-drops .axes line { stroke: var(--axis-color); }6 .throughput-weka-drops .axes text { fill: var(--tick-color); }7 .throughput-weka-drops .grid line { stroke: var(--grid-color); }8 .throughput-weka-drops .chart-card { background: var(--surface-bg); border: 1px solid var(--border-color); border-radius: 10px; padding: 8px; }9 .throughput-weka-drops .chart-header { display:flex; align-items:flex-start; justify-content:flex-start; gap:12px; margin: 8px 0 0 0; flex-wrap: wrap; }10 .throughput-weka-drops .legend-bottom { display:flex; align-items:center; justify-content:flex-start; font-size:12px; color: var(--text-color); }11 .throughput-weka-drops .legend-bottom .items { display:flex; flex-wrap:wrap; gap:8px 14px; }12 .throughput-weka-drops .legend-bottom .item { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }13 .throughput-weka-drops .legend-bottom .swatch { width:14px; height:14px; border-radius:3px; border:1px solid var(--border-color); display:inline-block; }14 .throughput-weka-drops .legend-bottom .legend-title { font-size: 12px; font-weight: 700; color: var(--text-color); }15 .throughput-weka-drops .legend-bottom { flex-direction: column; align-items: flex-start; gap: 6px; }16 .throughput-weka-drops .lines path.active { stroke-width: 3; }17 .throughput-weka-drops .d3-tooltip { z-index: var(--z-elevated); backdrop-filter: saturate(1.12) blur(8px); }18 .throughput-weka-drops .d3-tooltip__inner { display:flex; flex-direction:column; gap:6px; min-width: 220px; }19 .throughput-weka-drops .d3-tooltip__inner > div:first-child { font-weight: 800; letter-spacing: 0.1px; margin-bottom: 0; }20 .throughput-weka-drops .d3-tooltip__inner > div:nth-child(2) { font-size: 11px; color: var(--muted-color); display: block; margin-top: -4px; margin-bottom: 2px; letter-spacing: 0.1px; }21 .throughput-weka-drops .d3-tooltip__color-dot { display:inline-block; width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--border-color); }22</style>23<script>24 (() => {25 const ensureD3 = (cb) => {26 if (window.d3 && typeof window.d3.select === 'function') return cb();27 let s = document.getElementById('d3-cdn-script');28 if (!s) { s = document.createElement('script'); s.id = 'd3-cdn-script'; s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js'; document.head.appendChild(s); }29 const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); };30 s.addEventListener('load', onReady, { once: true }); if (window.d3) onReady();31 };32 33 const bootstrap = () => {34 const scriptEl = document.currentScript;35 let container = scriptEl ? scriptEl.previousElementSibling : null;36 if (!(container && container.classList && container.classList.contains('throughput-weka-drops'))){37 const cs = Array.from(document.querySelectorAll('.throughput-weka-drops')).filter(el => !(el.dataset && el.dataset.mounted === 'true'));38 container = cs[cs.length - 1] || null;39 }40 if (!container) return;41 if (container.dataset) { if (container.dataset.mounted === 'true') return; container.dataset.mounted = 'true'; }42 43 // Tooltip44 container.style.position = container.style.position || 'relative';45 let tip = container.querySelector('.d3-tooltip'); let tipInner;46 if (!tip) {47 tip = document.createElement('div'); tip.className = 'd3-tooltip';48 Object.assign(tip.style, {49 position:'absolute', top:'0px', left:'0px', transform:'translate(-9999px, -9999px)', pointerEvents:'none',50 padding:'8px 10px', borderRadius:'8px', fontSize:'12px', lineHeight:'1.35', border:'1px solid var(--border-color)',51 background:'var(--surface-bg)', color:'var(--text-color)', boxShadow:'0 4px 24px rgba(0,0,0,.18)', opacity:'0', transition:'opacity .12s ease'52 });53 tipInner = document.createElement('div'); tipInner.className = 'd3-tooltip__inner'; tipInner.style.textAlign='left'; tip.appendChild(tipInner); container.appendChild(tip);54 } else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; }55 56 // Header (legend) placed after the chart57 const header = document.createElement('div'); header.className = 'chart-header';58 const legendBottom = document.createElement('div'); legendBottom.className = 'legend-bottom'; header.appendChild(legendBottom);59 60 // Chart card (SVG)61 const card = document.createElement('div'); card.className = 'chart-card'; container.appendChild(card);62 container.appendChild(header);63 64 // SVG65 const svg = d3.select(card).append('svg').attr('width','100%').style('display','block');66 const gRoot = svg.append('g');67 const gGrid = gRoot.append('g').attr('class','grid');68 const gAxes = gRoot.append('g').attr('class','axes');69 const gLines = gRoot.append('g').attr('class','lines');70 const gPoints = gRoot.append('g').attr('class','points');71 const overlay = gRoot.append('rect').attr('fill','transparent').style('cursor','crosshair');72 const hoverLine = gRoot.append('line').attr('stroke-width',1).style('display','none');73 74 // State/data75 let width = 800, height = 480; const margin = { top: 16, right: 32, bottom: 44, left: 80 };76 const xScale = d3.scaleLinear();77 const yScale = d3.scaleLinear();78 const lineGen = d3.line().x(d => xScale(d.step)).y(d => yScale(d.value));79 let data = [];80 81 // Colors - following guidelines to use ColorPalettes82 let currentColor = 'var(--primary-color, #4e79a7)';83 84 function refreshPalette(){85 try { 86 if (window.ColorPalettes && typeof window.ColorPalettes.getColors === 'function') {87 const colors = window.ColorPalettes.getColors('categorical', 1);88 if (colors && colors.length > 0) {89 currentColor = colors[0];90 // Re-render with new colors91 if (data.length > 0) render();92 return;93 }94 }95 } catch(_){}96 // Fallback to CSS variable or default97 currentColor = 'var(--primary-color, #4e79a7)';98 // Re-render with fallback colors99 if (data.length > 0) render();100 }101 102 function getColor(){103 return currentColor;104 }105 106 // Format helper for thousands (5000 -> 5k, 1500 -> 1.5k)107 function formatK(v){108 const abs = Math.abs(v);109 if (abs >= 1000) {110 const n = v / 1000;111 const s = d3.format('.1f')(n);112 return (s.endsWith('.0') ? s.slice(0, -2) : s) + 'k';113 }114 return d3.format('d')(v);115 }116 117 // Format helper for throughput values118 function formatThroughput(v){119 if (v >= 1000) {120 return d3.format('.1f')(v / 1000) + 'k';121 }122 return d3.format('.1f')(v);123 }124 125 function updateLayout(){126 const axisColor = getComputedStyle(container).getPropertyValue('--axis-color').trim() || 'rgba(0,0,0,0.25)';127 width = container.clientWidth || 800;128 height = Math.max(280, Math.round(width / 3));129 svg.attr('width', width).attr('height', height);130 gRoot.attr('transform', `translate(${margin.left},${margin.top})`);131 const innerWidth = width - margin.left - margin.right;132 const innerHeight = height - margin.top - margin.bottom;133 overlay.attr('x',0).attr('y',0).attr('width', innerWidth).attr('height', innerHeight);134 hoverLine.attr('y1',0).attr('y2', innerHeight).attr('stroke', axisColor);135 return { innerWidth, innerHeight };136 }137 138 function render(){139 if (data.length === 0) return;140 141 const { innerWidth, innerHeight } = updateLayout();142 143 // Sort data by step144 const sortedData = data.slice().sort((a, b) => a.step - b.step);145 146 // domains147 const minStep = d3.min(sortedData, d => d.step);148 const maxStep = d3.max(sortedData, d => d.step);149 const minValue = d3.min(sortedData, d => d.value);150 const maxValue = d3.max(sortedData, d => d.value);151 152 xScale.domain([minStep, maxStep]).range([0, innerWidth]);153 yScale.domain([minValue, maxValue]).nice().range([innerHeight, 0]);154 155 // grid156 gGrid.selectAll('*').remove();157 gGrid.selectAll('line').data(yScale.ticks(6)).join('line')158 .attr('x1',0).attr('x2', innerWidth).attr('y1', d=>yScale(d)).attr('y2', d=>yScale(d))159 .attr('stroke','var(--grid-color)').attr('stroke-width',1).attr('shape-rendering','crispEdges');160 161 // axes162 gAxes.selectAll('*').remove();163 gAxes.append('g').attr('transform', `translate(0,${innerHeight})`).call(d3.axisBottom(xScale).ticks(8).tickFormat(formatK)).call(g=>{ g.selectAll('path, line').attr('stroke','var(--axis-color)'); g.selectAll('text').attr('fill','var(--tick-color)').style('font-size','12px'); });164 gAxes.append('g').call(d3.axisLeft(yScale).ticks(6).tickFormat(formatThroughput)).call(g=>{ g.selectAll('path, line').attr('stroke','var(--axis-color)'); g.selectAll('text').attr('fill','var(--tick-color)').style('font-size','12px'); });165 gAxes.append('text').attr('class','axis-label').attr('text-anchor','middle').attr('x', innerWidth/2).attr('y', innerHeight + 38).text('Training Step');166 gAxes.append('text').attr('class','axis-label').attr('text-anchor','middle').attr('transform', `translate(${-60}, ${innerHeight/2}) rotate(-90)`).text('Tokens/sec/GPU');167 168 // line169 const color = getColor();170 gLines.selectAll('*').remove();171 gLines.append('path')172 .attr('class','line')173 .attr('fill','none')174 .attr('stroke', color)175 .attr('stroke-width', 2)176 .attr('d', lineGen(sortedData));177 178 // point markers179 gPoints.selectAll('*').remove();180 gPoints.selectAll('circle').data(sortedData).join('circle')181 .attr('class','point')182 .attr('r', 2)183 .attr('fill', color)184 .attr('fill-opacity', 0.6)185 .attr('cx', d=>xScale(d.step))186 .attr('cy', d=>yScale(d.value));187 188 // legend189 legendBottom.innerHTML = `<div class="legend-title">Throughput</div><div class="items"><span class="item"><span class="swatch" style="background:${color}"></span><span>Tokens/sec/GPU</span></span></div>`;190 191 // hover192 function onMove(ev){193 const [mx, my] = d3.pointer(ev, overlay.node());194 const sx = xScale.invert(mx);195 196 // Find nearest point197 const nearest = sortedData.reduce((best, d) => Math.abs(d.step - sx) < Math.abs(best.step - sx) ? d : best, sortedData[0]);198 const xpx = xScale(nearest.step);199 hoverLine.style('display', null).attr('x1', xpx).attr('x2', xpx);200 201 // tooltip content202 let html = `<div style="font-weight:800;letter-spacing:.1px;">Training Throughput</div><div style="font-size:11px;color:var(--muted-color);margin-top:-4px;margin-bottom:2px;">Step ${formatK(nearest.step)}</div>`;203 html += `<div style="display:flex;align-items:center;gap:6px;white-space:nowrap;"><span class="d3-tooltip__color-dot" style="background:${color}"></span><strong>Tokens/sec/GPU</strong><span style="margin-left:auto;">${formatThroughput(nearest.value)}</span></div>`;204 205 tipInner.innerHTML = html; 206 tip.style.opacity = '1'; 207 tip.style.transform = `translate(${Math.round(mx + margin.left + 12)}px, ${Math.round(my + margin.top + 12)}px)`;208 }209 210 function onLeave(){ 211 tip.style.opacity='0'; 212 tip.style.transform='translate(-9999px, -9999px)'; 213 hoverLine.style('display','none'); 214 }215 216 overlay.on('mousemove', onMove).on('mouseleave', onLeave);217 }218 219 // load CSV and init220 (async () => {221 try {222 // Try multiple possible paths for the CSV file223 const csvPaths = [224 '/data/weka_drop_0404.csv',225 './assets/data/weka_drop_0404.csv',226 '../assets/data/weka_drop_0404.csv',227 '../../assets/data/weka_drop_0404.csv'228 ];229 230 let csvText = null;231 for (const path of csvPaths) {232 try {233 const response = await fetch(path, { cache: 'no-cache' });234 if (response.ok) {235 csvText = await response.text();236 break;237 }238 } catch(_) {}239 }240 241 if (!csvText) {242 throw new Error('CSV file not found: weka_drop_0404.csv');243 }244 245 const rows = d3.csvParse(csvText);246 247 // Parse the data - use the second column (tokens_per_sec_per_gpu) and ignore min/max248 data = rows.map(d => ({249 step: +d.Step,250 value: +d["04/04/2025_02:44:57_elie-smollm3-training-3p56G-smollm3-3B-start-0404-stage-1-wo-zloss-seed6-ddp-256-seed-6-_tp_group_1 - tokens_per_sec_per_gpu"]251 })).filter(d => !isNaN(d.step) && !isNaN(d.value));252 253 // Initialize palette and listen for changes254 refreshPalette();255 document.addEventListener('palettes:updated', refreshPalette);256 257 render();258 259 const rerender = () => render();260 if (window.ResizeObserver) { 261 const ro = new ResizeObserver(() => rerender()); 262 ro.observe(container); 263 } else { 264 window.addEventListener('resize', rerender); 265 }266 } catch (e) {267 const pre = document.createElement('pre'); 268 pre.textContent = 'CSV load error: ' + (e && e.message ? e.message : e);269 pre.style.color = 'var(--danger, #b00020)'; 270 pre.style.fontSize = '12px'; 271 pre.style.whiteSpace = 'pre-wrap'; 272 container.appendChild(pre);273 }274 })();275 };276 277 if (document.readyState === 'loading') { 278 document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); 279 } else { 280 ensureD3(bootstrap); 281 }282 })();283</script>284 