diff --git a/tinygrad/viz/js/index.js b/tinygrad/viz/js/index.js index 2ffcddd658..6947b7b2b4 100644 --- a/tinygrad/viz/js/index.js +++ b/tinygrad/viz/js/index.js @@ -121,6 +121,19 @@ const colorScheme = {TINY:["#1b5745", "#354f52", "#354f52", "#1d2e62", "#63b0cd" CATEGORICAL:["#ff8080", "#F4A261", "#C8F9D4", "#8D99AE", "#F4A261", "#ffffa2", "#ffffc0", "#87CEEB"],} const cycleColors = (lst, i) => lst[i%lst.length]; +const createPolygons = (source, area) => { + const shapes = []; + const yscale = d3.scaleLinear().domain([0, source.peak]).range([area, 0]); + for (const [i,e] of source.shapes.entries()) { + const x = e.x.map((i,_) => (source.timestamps[i] ?? data.et)-data.st); + const y0 = e.y.map(yscale); + const y1 = e.y.map(y => yscale(y+e.arg.nbytes)); + const arg = { tooltipText:`${e.arg.dtype} len:${formatUnit(e.arg.sz)}\n${formatUnit(e.arg.nbytes, "B")}` }; + shapes.push({ x, y0, y1, arg, fillColor:cycleColors(colorScheme.BUFFER, i) }); + } + return shapes; +} + const drawLine = (ctx, x, y) => { ctx.beginPath(); ctx.moveTo(x[0], y[0]); @@ -129,16 +142,18 @@ const drawLine = (ctx, x, y) => { ctx.stroke(); } -var profileRet, focusedDevice, canvasZoom, zoomLevel = d3.zoomIdentity; +var data, focusedDevice, canvasZoom, zoomLevel = d3.zoomIdentity; async function renderProfiler() { displayGraph("profiler"); d3.select(".metadata").html(""); + // layout once! + if (data != null) return; const profiler = d3.select(".profiler").html(""); const deviceList = profiler.append("div").attr("id", "device-list").node(); const canvas = profiler.append("canvas").attr("id", "timeline").node(); // NOTE: scrolling via mouse can only zoom the graph canvas.addEventListener("wheel", e => (e.stopPropagation(), e.preventDefault()), { passive:false }); - if (profileRet == null) profileRet = await (await fetch("/get_profile")).json() + const profileRet = await (await fetch("/get_profile")).json() const { layout, st, et } = profileRet; // place devices on the y axis and set vertical positions const [tickSize, padding] = [10, 8]; @@ -147,7 +162,7 @@ async function renderProfiler() { const canvasTop = rect(canvas).top; // color by key (name/category/device) const colorMap = new Map(); - const data = {shapes:[], axes:{}}; + data = {tracks:new Map(), axes:{}, st, et}; const areaScale = d3.scaleLinear().domain([0, Object.entries(layout).reduce((peak, [_,d]) => Math.max(peak, d.mem.peak), 0)]).range([4,maxArea=100]); for (const [k, { timeline, mem }] of Object.entries(layout)) { if (timeline.shapes.length === 0 && mem.shapes.length == 0) continue; @@ -155,14 +170,30 @@ async function renderProfiler() { div.innerText = k; div.style.padding = `${padding}px`; div.onclick = () => { // TODO: make this feature more visible - focusedDevice = k === focusedDevice ? null : k; const prevScroll = profiler.node().scrollTop; - renderProfiler(); + let newOffset = null; + for (const [track, v] of data.tracks) { + if (track === `${k} memory`) { + // expand the y axis or reset to default size + const pick = [areaScale(mem.peak), maxArea*4]; + const expand = k !== focusedDevice; + const [newArea, prevArea] = expand ? pick.reverse() : pick; + focusedDevice = expand ? k : null; + data.axes.y = expand ? { domain:[0, mem.peak], range:[v.offsetY+newArea, v.offsetY], fmt:"B" } : null; + // either way update all offsets + v.shapes = createPolygons(mem, newArea); + newOffset = newArea-prevArea; + v.div.style.height = rect(v.div).height+newOffset+"px"; + } else if (newOffset != null) v.offsetY += newOffset; + } + d3.select(canvas).call(canvasZoom.transform, zoomLevel); if (prevScroll) profiler.node().scrollTop = prevScroll; } const { y:baseY, height:baseHeight } = rect(div); const levelHeight = baseHeight-padding; const offsetY = baseY-canvasTop+padding/2; + const shapes = []; + data.tracks.set(k, { shapes, offsetY }); let colorKey, ref; for (const e of timeline.shapes) { if (e.depth === 0) colorKey = e.cat ?? e.name; @@ -177,27 +208,15 @@ async function renderProfiler() { } const arg = { tooltipText:formatTime(e.dur)+(e.info != null ? "\n"+e.info : ""), ...ref }; // offset y by depth - data.shapes.push({x:e.st-st, y:offsetY+levelHeight*e.depth, width:e.dur, height:levelHeight, arg, label, fillColor }); + shapes.push({x:e.st-st, y:levelHeight*e.depth, width:e.dur, height:levelHeight, arg, label, fillColor }); } // position shapes on the canvas and scale to fit fixed area let area = mem.shapes.length === 0 ? 0 : areaScale(mem.peak); if (area === 0) div.style.pointerEvents = "none"; else { const startY = offsetY+(levelHeight*timeline.maxDepth)+padding/2; + data.tracks.set(`${k} memory`, { shapes:createPolygons(mem, area), offsetY:startY, div }); div.style.cursor = "pointer"; - if (k === focusedDevice) { - // expand memory graph for the focused device - area = maxArea*4; - data.axes.y = { domain:[0, mem.peak], range:[startY+area, startY], fmt:"B" }; - } - const yscale = d3.scaleLinear().domain([0, mem.peak]).range([startY+area, startY]); - for (const [i,e] of mem.shapes.entries()) { - const x = e.x.map((i,_) => (mem.timestamps[i] ?? et)-st); - const y0 = e.y.map(yscale); - const y1 = e.y.map(y => yscale(y+e.arg.nbytes)); - const arg = { tooltipText:`${e.arg.dtype} len:${formatUnit(e.arg.sz)}\n${formatUnit(e.arg.nbytes, "B")}` }; - data.shapes.push({ x, y0, y1, arg, fillColor:cycleColors(colorScheme.BUFFER, i) }); - } } // lastly, adjust device rect by number of levels div.style.height = `${Math.max(levelHeight*timeline.maxDepth, baseHeight)+area+padding}px`; @@ -221,49 +240,51 @@ async function renderProfiler() { yscale = d3.scaleLinear().domain(data.axes.y.domain).range(data.axes.y.range); } // draw shapes - for (const e of data.shapes) { - const [start, end] = e.width != null ? [e.x, e.x+e.width] : [e.x[0], e.x[e.x.length-1]]; - if (zoomDomain != null && (start>zoomDomain[1]|| end=0; i--) ctx.lineTo(x[i], e.y1[i]); - ctx.closePath(); - ctx.fill(); - // NOTE: y coordinates are in reverse order - for (let i = 0; i < x.length - 1; i++) { - let tooltipText = e.arg.tooltipText; - if (yscale != null && ((yaxisVal=yscale.invert(e.y1[i]))>0)) { - tooltipText += `\nTotal: ${formatUnit(yaxisVal, data.axes.y.fmt)}`; + for (const [_, { offsetY, shapes }] of data.tracks) { + for (const e of shapes) { + const [start, end] = e.width != null ? [e.x, e.x+e.width] : [e.x[0], e.x[e.x.length-1]]; + if (zoomDomain != null && (start>zoomDomain[1]|| end=0; i--) ctx.lineTo(x[i], offsetY+e.y1[i]); + ctx.closePath(); + ctx.fill(); + // NOTE: y coordinates are in reverse order + for (let i = 0; i < x.length - 1; i++) { + let tooltipText = e.arg.tooltipText; + if (yscale != null && ((yaxisVal=yscale.invert(offsetY+e.y1[i]))>0)) { + tooltipText += `\nTotal: ${formatUnit(yaxisVal, data.axes.y.fmt)}`; + } + rectLst.push({ x0:x[i], x1:x[i+1], y0:offsetY+e.y1[i], y1:offsetY+e.y0[i], arg:{...e.arg, tooltipText} }); } - rectLst.push({ x0:x[i], x1:x[i+1], y0:e.y1[i], y1:e.y0[i], arg:{...e.arg, tooltipText} }); + continue; } - continue; - } - // contiguous rect - const x = xscale(start); - const width = xscale(end)-x; - ctx.fillRect(x, e.y, width, e.height); - rectLst.push({ y0:e.y, y1:e.y+e.height, x0:x, x1:x+width, arg:e.arg }); - // add label - if (e.label == null) continue; - ctx.textAlign = "left"; - ctx.textBaseline = "middle"; - let [labelX, labelWidth] = [x+2, 0]; - const labelY = e.y+e.height/2; - for (const [i,l] of e.label.entries()) { - if (labelWidth+l.width+(i===e.label.length-1 ? 0 : ellipsisWidth)+2 > width) { - if (labelWidth !== 0) ctx.fillText("...", labelX, labelY); - break; + // contiguous rect + const x = xscale(start); + const width = xscale(end)-x; + ctx.fillRect(x, offsetY+e.y, width, e.height); + rectLst.push({ y0:offsetY+e.y, y1:offsetY+e.y+e.height, x0:x, x1:x+width, arg:e.arg }); + // add label + if (e.label == null) continue; + ctx.textAlign = "left"; + ctx.textBaseline = "middle"; + let [labelX, labelWidth] = [x+2, 0]; + const labelY = offsetY+e.y+e.height/2; + for (const [i,l] of e.label.entries()) { + if (labelWidth+l.width+(i===e.label.length-1 ? 0 : ellipsisWidth)+2 > width) { + if (labelWidth !== 0) ctx.fillText("...", labelX, labelY); + break; + } + ctx.fillStyle = l.color; + ctx.fillText(l.st, labelX, labelY); + labelWidth += l.width; + labelX += l.width; } - ctx.fillStyle = l.color; - ctx.fillText(l.st, labelX, labelY); - labelWidth += l.width; - labelX += l.width; } } // draw axes