| | <div class="throughput-debug-1node"></div> |
| | <style> |
| | .throughput-debug-1node { position: relative; } |
| | .throughput-debug-1node .axis-label { fill: var(--text-color); font-size: 12px; font-weight: 700; } |
| | .throughput-debug-1node .axes path, .throughput-debug-1node .axes line { stroke: var(--axis-color); } |
| | .throughput-debug-1node .axes text { fill: var(--tick-color); } |
| | .throughput-debug-1node .grid line { stroke: var(--grid-color); } |
| | .throughput-debug-1node .chart-card { background: var(--surface-bg); border: 1px solid var(--border-color); border-radius: 10px; padding: 8px; } |
| | .throughput-debug-1node .chart-header { display:flex; align-items:flex-start; justify-content:flex-start; gap:12px; margin: 8px 0 0 0; flex-wrap: wrap; } |
| | .throughput-debug-1node .legend-bottom { display:flex; align-items:center; justify-content:flex-start; font-size:12px; color: var(--text-color); } |
| | .throughput-debug-1node .legend-bottom .items { display:flex; flex-wrap:wrap; gap:8px 14px; } |
| | .throughput-debug-1node .legend-bottom .item { display:inline-flex; align-items:center; gap:6px; white-space:nowrap; } |
| | .throughput-debug-1node .legend-bottom .swatch { width:14px; height:14px; border-radius:3px; border:1px solid var(--border-color); display:inline-block; } |
| | .throughput-debug-1node .legend-bottom .legend-title { font-size: 12px; font-weight: 700; color: var(--text-color); } |
| | .throughput-debug-1node .legend-bottom { flex-direction: column; align-items: flex-start; gap: 6px; } |
| | .throughput-debug-1node .lines path.active { stroke-width: 3; } |
| | .throughput-debug-1node .d3-tooltip { z-index: var(--z-elevated); backdrop-filter: saturate(1.12) blur(8px); } |
| | .throughput-debug-1node .d3-tooltip__inner { display:flex; flex-direction:column; gap:6px; min-width: 220px; } |
| | .throughput-debug-1node .d3-tooltip__inner > div:first-child { font-weight: 800; letter-spacing: 0.1px; margin-bottom: 0; } |
| | .throughput-debug-1node .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; } |
| | .throughput-debug-1node .d3-tooltip__color-dot { display:inline-block; width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--border-color); } |
| | |
| | .throughput-debug-1node.hovering .legend-bottom .item.ghost { opacity: .35; } |
| | .throughput-debug-1node.hovering .lines path.ghost { opacity: .25; } |
| | .throughput-debug-1node.hovering .points circle.ghost { opacity: .25; } |
| | </style> |
| | <script> |
| | (() => { |
| | const ensureD3 = (cb) => { |
| | if (window.d3 && typeof window.d3.select === 'function') return cb(); |
| | let s = document.getElementById('d3-cdn-script'); |
| | 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); } |
| | const onReady = () => { if (window.d3 && typeof window.d3.select === 'function') cb(); }; |
| | s.addEventListener('load', onReady, { once: true }); if (window.d3) onReady(); |
| | }; |
| | |
| | const bootstrap = () => { |
| | const scriptEl = document.currentScript; |
| | let container = scriptEl ? scriptEl.previousElementSibling : null; |
| | if (!(container && container.classList && container.classList.contains('throughput-debug-1node'))){ |
| | const cs = Array.from(document.querySelectorAll('.throughput-debug-1node')).filter(el => !(el.dataset && el.dataset.mounted === 'true')); |
| | container = cs[cs.length - 1] || null; |
| | } |
| | if (!container) return; |
| | if (container.dataset) { if (container.dataset.mounted === 'true') return; container.dataset.mounted = 'true'; } |
| | |
| | |
| | container.style.position = container.style.position || 'relative'; |
| | let tip = container.querySelector('.d3-tooltip'); let tipInner; |
| | if (!tip) { |
| | tip = document.createElement('div'); tip.className = 'd3-tooltip'; |
| | Object.assign(tip.style, { |
| | position:'absolute', top:'0px', left:'0px', transform:'translate(-9999px, -9999px)', pointerEvents:'none', |
| | padding:'8px 10px', borderRadius:'8px', fontSize:'12px', lineHeight:'1.35', border:'1px solid var(--border-color)', |
| | background:'var(--surface-bg)', color:'var(--text-color)', boxShadow:'0 4px 24px rgba(0,0,0,.18)', opacity:'0', transition:'opacity .12s ease' |
| | }); |
| | tipInner = document.createElement('div'); tipInner.className = 'd3-tooltip__inner'; tipInner.style.textAlign='left'; tip.appendChild(tipInner); container.appendChild(tip); |
| | } else { tipInner = tip.querySelector('.d3-tooltip__inner') || tip; } |
| | |
| | |
| | const header = document.createElement('div'); header.className = 'chart-header'; |
| | const legendBottom = document.createElement('div'); legendBottom.className = 'legend-bottom'; header.appendChild(legendBottom); |
| | |
| | |
| | const card = document.createElement('div'); card.className = 'chart-card'; container.appendChild(card); |
| | container.appendChild(header); |
| | |
| | |
| | const svg = d3.select(card).append('svg').attr('width','100%').style('display','block'); |
| | const gRoot = svg.append('g'); |
| | const gGrid = gRoot.append('g').attr('class','grid'); |
| | const gAxes = gRoot.append('g').attr('class','axes'); |
| | const gLines = gRoot.append('g').attr('class','lines'); |
| | const gPoints = gRoot.append('g').attr('class','points'); |
| | const overlay = gRoot.append('rect').attr('fill','transparent').style('cursor','crosshair'); |
| | const hoverLine = gRoot.append('line').attr('stroke-width',1).style('display','none'); |
| | |
| | |
| | let width = 800, height = 480; const margin = { top: 16, right: 32, bottom: 44, left: 80 }; |
| | const xScale = d3.scaleLinear(); |
| | const yScale = d3.scaleLinear(); |
| | const lineGen = d3.line().x(d => xScale(d.step)).y(d => yScale(d.value)); |
| | let data = []; |
| | |
| | |
| | let currentColors = ['var(--primary-color, #4e79a7)', 'var(--primary-color, #4e79a7)']; |
| | |
| | function refreshPalette(){ |
| | try { |
| | if (window.ColorPalettes && typeof window.ColorPalettes.getColors === 'function') { |
| | const colors = window.ColorPalettes.getColors('categorical', 2); |
| | if (colors && colors.length >= 2) { |
| | currentColors = colors; |
| | |
| | if (data.length > 0) render(); |
| | return; |
| | } |
| | } |
| | } catch(_){} |
| | |
| | currentColors = ['var(--primary-color, #4e79a7)', '#e15759']; |
| | |
| | if (data.length > 0) render(); |
| | } |
| | |
| | function getColors(){ |
| | return currentColors; |
| | } |
| | |
| | |
| | function formatK(v){ |
| | const abs = Math.abs(v); |
| | if (abs >= 1000) { |
| | const n = v / 1000; |
| | const s = d3.format('.1f')(n); |
| | return (s.endsWith('.0') ? s.slice(0, -2) : s) + 'k'; |
| | } |
| | return d3.format('d')(v); |
| | } |
| | |
| | |
| | function formatThroughput(v){ |
| | if (v >= 1000) { |
| | return d3.format('.1f')(v / 1000) + 'k'; |
| | } |
| | return d3.format('.1f')(v); |
| | } |
| | |
| | function updateLayout(){ |
| | const axisColor = getComputedStyle(container).getPropertyValue('--axis-color').trim() || 'rgba(0,0,0,0.25)'; |
| | width = container.clientWidth || 800; |
| | height = Math.max(280, Math.round(width / 3)); |
| | svg.attr('width', width).attr('height', height); |
| | gRoot.attr('transform', `translate(${margin.left},${margin.top})`); |
| | const innerWidth = width - margin.left - margin.right; |
| | const innerHeight = height - margin.top - margin.bottom; |
| | overlay.attr('x',0).attr('y',0).attr('width', innerWidth).attr('height', innerHeight); |
| | hoverLine.attr('y1',0).attr('y2', innerHeight).attr('stroke', axisColor); |
| | return { innerWidth, innerHeight }; |
| | } |
| | |
| | function render(){ |
| | if (data.length === 0) return; |
| | |
| | const { innerWidth, innerHeight } = updateLayout(); |
| | |
| | |
| | const sortedData = data.slice().sort((a, b) => a.step - b.step); |
| | |
| | |
| | const series = [ |
| | { |
| | name: 'Throughput for 32k steps', |
| | values: sortedData |
| | .filter(d => d.run1 !== null && d.run1 !== undefined && d.run1 !== '') |
| | .map(d => ({ step: d.step, value: d.run1 })) |
| | }, |
| | { |
| | name: 'Throughput for 3.2M steps', |
| | values: sortedData |
| | .filter(d => d.run2 !== null && d.run2 !== undefined && d.run2 !== '') |
| | .map(d => ({ step: d.step, value: d.run2 })) |
| | } |
| | ]; |
| | |
| | |
| | const allValues = [...series[0].values, ...series[1].values]; |
| | if (allValues.length === 0) return; |
| | |
| | const minStep = d3.min(allValues, d => d.step); |
| | const maxStep = d3.max(allValues, d => d.step); |
| | const minValue = d3.min(allValues, d => d.value); |
| | const maxValue = d3.max(allValues, d => d.value); |
| | |
| | xScale.domain([minStep, maxStep]).range([0, innerWidth]); |
| | yScale.domain([minValue, maxValue]).nice().range([innerHeight, 0]); |
| | |
| | |
| | gGrid.selectAll('*').remove(); |
| | gGrid.selectAll('line').data(yScale.ticks(6)).join('line') |
| | .attr('x1',0).attr('x2', innerWidth).attr('y1', d=>yScale(d)).attr('y2', d=>yScale(d)) |
| | .attr('stroke','var(--grid-color)').attr('stroke-width',1).attr('shape-rendering','crispEdges'); |
| | |
| | |
| | gAxes.selectAll('*').remove(); |
| | 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'); }); |
| | 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'); }); |
| | gAxes.append('text').attr('class','axis-label').attr('text-anchor','middle').attr('x', innerWidth/2).attr('y', innerHeight + 38).text('Training Step'); |
| | gAxes.append('text').attr('class','axis-label').attr('text-anchor','middle').attr('transform', `translate(${-60}, ${innerHeight/2}) rotate(-90)`).text('Tokens/sec/GPU'); |
| | |
| | |
| | const colors = getColors(); |
| | gLines.selectAll('*').remove(); |
| | series.forEach((s, i) => { |
| | if (s.values.length > 0) { |
| | gLines.append('path') |
| | .attr('class', `line line-${i}`) |
| | .attr('data-series', s.name) |
| | .attr('fill','none') |
| | .attr('stroke', colors[i % colors.length]) |
| | .attr('stroke-width', 2) |
| | .attr('d', lineGen(s.values)); |
| | } |
| | }); |
| | |
| | |
| | gPoints.selectAll('*').remove(); |
| | series.forEach((s, i) => { |
| | if (s.values.length > 0) { |
| | gPoints.selectAll(`circle.point-${i}`).data(s.values).join('circle') |
| | .attr('class', `point point-${i}`) |
| | .attr('data-series', s.name) |
| | .attr('r', 2) |
| | .attr('fill', colors[i % colors.length]) |
| | .attr('fill-opacity', 0.6) |
| | .attr('cx', d=>xScale(d.step)) |
| | .attr('cy', d=>yScale(d.value)); |
| | } |
| | }); |
| | |
| | |
| | const validSeries = series.filter(s => s.values.length > 0); |
| | legendBottom.innerHTML = `<div class="legend-title">Legend</div><div class="items">${validSeries.map((s, i) => `<span class="item" data-series="${s.name}"><span class="swatch" style="background:${colors[i % colors.length]}"></span><span>${s.name}</span></span>`).join('')}</div>`; |
| | |
| | |
| | try { |
| | const legendNode = legendBottom; |
| | legendNode.querySelectorAll('.item').forEach(el => { |
| | el.addEventListener('mouseenter', () => { |
| | const seriesName = el.getAttribute('data-series'); if (!seriesName) return; |
| | container.classList.add('hovering'); |
| | gLines.selectAll('path.line').classed('ghost', s => s.getAttribute && s.getAttribute('data-series') !== seriesName); |
| | gPoints.selectAll('circle.point').classed('ghost', p => p.getAttribute && p.getAttribute('data-series') !== seriesName); |
| | legendNode.querySelectorAll('.item').forEach(it => it.classList.toggle('ghost', it.getAttribute('data-series') !== seriesName)); |
| | }); |
| | el.addEventListener('mouseleave', () => { |
| | container.classList.remove('hovering'); |
| | gLines.selectAll('path.line').classed('ghost', false); |
| | gPoints.selectAll('circle.point').classed('ghost', false); |
| | legendNode.querySelectorAll('.item').forEach(it => it.classList.remove('ghost')); |
| | }); |
| | }); |
| | } catch {} |
| | |
| | |
| | function onMove(ev){ |
| | const [mx, my] = d3.pointer(ev, overlay.node()); |
| | const sx = xScale.invert(mx); |
| | |
| | |
| | const steps = Array.from(new Set(allValues.map(d => d.step))).sort((a,b) => a - b); |
| | const nearest = steps.reduce((best, s) => Math.abs(s - sx) < Math.abs(best - sx) ? s : best, steps[0]); |
| | const xpx = xScale(nearest); |
| | hoverLine.style('display', null).attr('x1', xpx).attr('x2', xpx); |
| | |
| | |
| | const dataPoint = sortedData.find(d => d.step === nearest); |
| | if (!dataPoint) return; |
| | |
| | |
| | let html = `<div style="font-weight:800;letter-spacing:.1px;">Throughput Debug (1 Node)</div><div style="font-size:11px;color:var(--muted-color);margin-top:-4px;margin-bottom:2px;">Step ${formatK(nearest)}</div>`; |
| | |
| | validSeries.forEach((s, i) => { |
| | const value = s.name === 'Throughput for 32k steps' ? dataPoint.run1 : dataPoint.run2; |
| | if (value !== null && value !== undefined && value !== '') { |
| | html += `<div style="display:flex;align-items:center;gap:6px;white-space:nowrap;"><span class="d3-tooltip__color-dot" style="background:${colors[i % colors.length]}"></span><strong>${s.name}</strong><span style="margin-left:auto;">${formatThroughput(value)}</span></div>`; |
| | } |
| | }); |
| | |
| | tipInner.innerHTML = html; |
| | tip.style.opacity = '1'; |
| | tip.style.transform = `translate(${Math.round(mx + margin.left + 12)}px, ${Math.round(my + margin.top + 12)}px)`; |
| | } |
| | |
| | function onLeave(){ |
| | tip.style.opacity='0'; |
| | tip.style.transform='translate(-9999px, -9999px)'; |
| | hoverLine.style('display','none'); |
| | } |
| | |
| | overlay.on('mousemove', onMove).on('mouseleave', onLeave); |
| | } |
| | |
| | |
| | (async () => { |
| | try { |
| | |
| | const csvPaths = [ |
| | '/data/throughput_debug_1node.csv', |
| | './assets/data/throughput_debug_1node.csv', |
| | '../assets/data/throughput_debug_1node.csv', |
| | '../../assets/data/throughput_debug_1node.csv' |
| | ]; |
| | |
| | let csvText = null; |
| | for (const path of csvPaths) { |
| | try { |
| | const response = await fetch(path, { cache: 'no-cache' }); |
| | if (response.ok) { |
| | csvText = await response.text(); |
| | break; |
| | } |
| | } catch(_) {} |
| | } |
| | |
| | if (!csvText) { |
| | throw new Error('CSV file not found: throughput_debug_1node.csv'); |
| | } |
| | |
| | const rows = d3.csvParse(csvText); |
| | |
| | |
| | const col1 = "06/04/2025_19:54:26_3B-nanotron-old-nn1-fwedu-32k - tokens_per_sec_per_gpu"; |
| | const col2 = "06/04/2025_19:50:35_3B-nanotron-old-nn1-fwedu- - tokens_per_sec_per_gpu"; |
| | |
| | data = rows.map(d => ({ |
| | step: +d.Step, |
| | run1: d[col1] === '' ? null : +d[col1], |
| | run2: d[col2] === '' ? null : +d[col2] |
| | })).filter(d => !isNaN(d.step)); |
| | |
| | |
| | refreshPalette(); |
| | document.addEventListener('palettes:updated', refreshPalette); |
| | |
| | render(); |
| | |
| | const rerender = () => render(); |
| | if (window.ResizeObserver) { |
| | const ro = new ResizeObserver(() => rerender()); |
| | ro.observe(container); |
| | } else { |
| | window.addEventListener('resize', rerender); |
| | } |
| | } catch (e) { |
| | const pre = document.createElement('pre'); |
| | pre.textContent = 'CSV load error: ' + (e && e.message ? e.message : e); |
| | pre.style.color = 'var(--danger, #b00020)'; |
| | pre.style.fontSize = '12px'; |
| | pre.style.whiteSpace = 'pre-wrap'; |
| | container.appendChild(pre); |
| | } |
| | })(); |
| | }; |
| | |
| | if (document.readyState === 'loading') { |
| | document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true }); |
| | } else { |
| | ensureD3(bootstrap); |
| | } |
| | })(); |
| | </script> |
| |
|