smol-training-playbook / app /src /content /embeds /d3-memory-analysis.html
tfrere's picture
tfrere HF Staff
update html embeds
7772913
<div class="d3-memory-analysis"></div>
<style>
.d3-memory-analysis {
position: relative;
width: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
.d3-memory-analysis svg {
display: block;
width: 100%;
}
.d3-memory-analysis .charts-wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 32px;
margin-bottom: 16px;
}
.d3-memory-analysis .chart-section {
position: relative;
}
.d3-memory-analysis .chart-title {
font-size: 16px;
font-weight: 700;
color: var(--text-color);
margin-bottom: 16px;
text-align: center;
}
.d3-memory-analysis .axes path,
.d3-memory-analysis .axes line {
stroke: var(--axis-color);
}
.d3-memory-analysis .axes text {
fill: var(--tick-color);
font-size: 11px;
}
.d3-memory-analysis .grid line {
stroke: var(--grid-color);
stroke-dasharray: 2, 2;
}
.d3-memory-analysis .bar {
cursor: pointer;
transition: opacity 0.15s ease;
}
.d3-memory-analysis .bar:hover {
opacity: 0.8;
}
.d3-memory-analysis .bar-label {
font-size: 11px;
font-weight: 600;
fill: var(--text-color);
text-anchor: middle;
}
.d3-memory-analysis .common-legend {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
margin-top: 16px;
}
.d3-memory-analysis .common-legend .legend-title {
font-size: 12px;
font-weight: 700;
color: var(--text-color);
}
.d3-memory-analysis .common-legend .items {
display: flex;
flex-wrap: wrap;
gap: 8px 14px;
justify-content: center;
}
.d3-memory-analysis .common-legend .item {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
font-size: 12px;
color: var(--text-color);
}
.d3-memory-analysis .common-legend .swatch {
width: 14px;
height: 14px;
border-radius: 3px;
border: 1px solid var(--border-color);
}
.d3-memory-analysis .axis-label {
font-size: 12px;
font-weight: 600;
fill: var(--text-color);
}
.d3-memory-analysis .d3-tooltip {
position: absolute;
top: 0;
left: 0;
transform: translate(-9999px, -9999px);
pointer-events: none;
padding: 10px 12px;
border-radius: 8px;
font-size: 12px;
line-height: 1.5;
border: 1px solid var(--border-color);
background: var(--surface-bg);
color: var(--text-color);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.18);
opacity: 0;
transition: opacity 0.12s ease;
z-index: 1000;
}
.d3-memory-analysis .d3-tooltip.visible {
opacity: 1;
}
.d3-memory-analysis .d3-tooltip__inner {
text-align: left;
}
.d3-memory-analysis .d3-tooltip__inner strong {
color: var(--text-color);
font-weight: 700;
}
.d3-memory-analysis .d3-tooltip__inner .tooltip-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.d3-memory-analysis .d3-tooltip__inner .tooltip-swatch {
width: 16px;
height: 16px;
border-radius: 3px;
border: 1px solid var(--border-color);
flex-shrink: 0;
}
@media (max-width: 900px) {
.d3-memory-analysis .charts-wrapper {
grid-template-columns: 1fr;
}
}
</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('d3-memory-analysis'))) {
const candidates = Array.from(document.querySelectorAll('.d3-memory-analysis'))
.filter((el) => !(el.dataset && el.dataset.mounted === 'true'));
container = candidates[candidates.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';
// Tooltip setup
let tip = container.querySelector('.d3-tooltip');
let tipInner;
if (!tip) {
tip = document.createElement('div');
tip.className = 'd3-tooltip';
tipInner = document.createElement('div');
tipInner.className = 'd3-tooltip__inner';
tip.appendChild(tipInner);
container.appendChild(tip);
} else {
tipInner = tip.querySelector('.d3-tooltip__inner') || tip;
}
// Data
const breakdownData = [
{ component: 'Model BF16', value: 5865.0 },
{ component: 'FP32 Parameters', value: 11730.0 },
{ component: 'FP32 Gradients', value: 11730.0 },
{ component: 'Optimizer States', value: 23460.0 },
{ component: 'DDP Gradient Buffers', value: 0.0 },
{ component: 'ZERO-3 Buffers', value: 0.0 },
{ component: 'Overhead', value: 104.0 },
{ component: 'Activations', value: 21288.0 }
];
const timelineData = [
{
stage: 'Model Init', segments: [
{ name: 'Model BF16', value: 5865.0 }
]
},
{
stage: 'Gradient Accumulator Init',
segments: [
{ name: 'Model BF16', value: 5865.0 },
{ name: 'FP32 Parameters', value: 11730.0 },
{ name: 'FP32 Gradients', value: 11730.0 }
]
},
{
stage: 'Fwd-Bwd Peak',
segments: [
{ name: 'Model BF16', value: 5865.0 },
{ name: 'FP32 Parameters', value: 11730.0 },
{ name: 'FP32 Gradients', value: 11730.0 },
{ name: 'Activations', value: 21288.0 }
]
},
{
stage: 'Optimizer Step',
segments: [
{ name: 'Model BF16', value: 5865.0 },
{ name: 'FP32 Parameters', value: 11730.0 },
{ name: 'FP32 Gradients', value: 11730.0 },
{ name: 'Optimizer States', value: 23460.0 }
]
},
{
stage: '2nd Fwd-Bwd Peak',
segments: [
{ name: 'Model BF16', value: 5865.0 },
{ name: 'FP32 Parameters', value: 11730.0 },
{ name: 'FP32 Gradients', value: 11730.0 },
{ name: 'Optimizer States', value: 23460.0 },
{ name: 'Activations', value: 21288.0 }
]
},
{
stage: '2nd Optimizer Step',
segments: [
{ name: 'Model BF16', value: 5865.0 },
{ name: 'FP32 Parameters', value: 11730.0 },
{ name: 'FP32 Gradients', value: 11730.0 },
{ name: 'Optimizer States', value: 23460.0 }
]
}
];
// Calculate totals for timeline
timelineData.forEach(d => {
d.total = d.segments.reduce((sum, seg) => sum + seg.value, 0);
});
// Get all unique components with stable order:
// 1) order from breakdownData (non-zero), then
// 2) any remaining from timeline in first-seen order.
const componentsFromBreakdown = breakdownData
.filter(d => d.value > 0)
.map(d => d.component);
const componentsFromTimeline = timelineData
.flatMap(d => d.segments.map(s => s.name));
const allComponents = Array.from(new Set([
...componentsFromBreakdown,
...componentsFromTimeline
]));
// Color mapping
const colorMap = {};
const getColors = (n) => {
if (window.ColorPalettes && window.ColorPalettes.getColors) {
return window.ColorPalettes.getColors('categorical', n);
}
return ['#4E79A7', '#F28E2B', '#E15759', '#76B7B2', '#59A14F', '#EDC948', '#AF7AA1', '#FF9D9A'];
};
function updateColors() {
const colors = getColors(allComponents.length);
allComponents.forEach((comp, i) => {
colorMap[comp] = colors[i];
});
}
// Initial color setup
updateColors();
// Listen for palette changes
if (window.ColorPalettes && window.ColorPalettes.onChange) {
window.ColorPalettes.onChange(() => {
updateColors();
render();
});
}
// Create container structure
const wrapper = document.createElement('div');
wrapper.innerHTML = `
<div class="charts-wrapper">
<div class="chart-section" id="breakdown-section">
<div class="chart-title">Memory Component Breakdown</div>
</div>
<div class="chart-section" id="timeline-section">
<div class="chart-title">Memory Timeline</div>
</div>
</div>
<div class="common-legend">
<div class="legend-title">Legend</div>
<div class="items"></div>
</div>
`;
container.appendChild(wrapper);
const breakdownSection = wrapper.querySelector('#breakdown-section');
const timelineSection = wrapper.querySelector('#timeline-section');
const commonLegend = wrapper.querySelector('.common-legend .items');
// Create SVGs
const svg1 = d3.select(breakdownSection).append('svg').attr('width', '100%').style('display', 'block');
const svg2 = d3.select(timelineSection).append('svg').attr('width', '100%').style('display', 'block');
const g1 = svg1.append('g');
const g2 = svg2.append('g');
let width1 = 800, height1 = 400;
let width2 = 800, height2 = 400;
function updateSize1() {
width1 = breakdownSection.clientWidth || 400;
height1 = Math.max(300, Math.round(width1 / 1.5));
// Calculate dynamic margins based on width
const baseMargin = { top: 40, right: 20, left: 80 };
// For bottom margin, increase it for smaller widths where labels need more space
const bottomMargin = width1 < 600 ? 120 : width1 < 800 ? 100 : 80;
const margin = { ...baseMargin, bottom: bottomMargin };
svg1.attr('width', width1).attr('height', height1);
g1.attr('transform', `translate(${margin.left},${margin.top})`);
return {
innerWidth: width1 - margin.left - margin.right,
innerHeight: height1 - margin.top - margin.bottom,
margin
};
}
function updateSize2() {
width2 = timelineSection.clientWidth || 400;
height2 = Math.max(300, Math.round(width2 / 1.5));
// Calculate dynamic margins based on width
const baseMargin = { top: 40, right: 20, left: 80 };
// For bottom margin, increase it for smaller widths where labels need more space
const bottomMargin = width2 < 600 ? 120 : width2 < 800 ? 100 : 80;
const margin = { ...baseMargin, bottom: bottomMargin };
svg2.attr('width', width2).attr('height', height2);
g2.attr('transform', `translate(${margin.left},${margin.top})`);
return {
innerWidth: width2 - margin.left - margin.right,
innerHeight: height2 - margin.top - margin.bottom,
margin
};
}
function makeCommonLegend() {
commonLegend.innerHTML = '';
allComponents.forEach(comp => {
const el = document.createElement('span');
el.className = 'item';
const sw = document.createElement('span');
sw.className = 'swatch';
sw.style.background = colorMap[comp];
const txt = document.createElement('span');
txt.textContent = comp;
el.appendChild(sw);
el.appendChild(txt);
commonLegend.appendChild(el);
});
}
// Helper function to create a rect path with rounded top corners only
function roundedTopRect(x, y, width, height, radius) {
// Adjust radius to prevent overlap
const r = Math.min(radius, width / 2, height);
return `M ${x + r},${y}
L ${x + width - r},${y}
Q ${x + width},${y} ${x + width},${y + r}
L ${x + width},${y + height}
L ${x},${y + height}
L ${x},${y + r}
Q ${x},${y} ${x + r},${y}
Z`;
}
function renderBreakdown() {
const { innerWidth, innerHeight, margin } = updateSize1();
// Scales
const x = d3.scaleBand()
.domain(breakdownData.map(d => d.component))
.range([0, innerWidth])
.padding(0.2);
const y = d3.scaleLinear()
.domain([0, d3.max(breakdownData, d => d.value)])
.range([innerHeight, 0])
.nice();
// Grid
g1.selectAll('.grid').remove();
const grid = g1.append('g').attr('class', 'grid');
grid.selectAll('line')
.data(y.ticks(6))
.join('line')
.attr('x1', 0)
.attr('x2', innerWidth)
.attr('y1', d => y(d))
.attr('y2', d => y(d));
// Bars
const bars = g1.selectAll('.bar').data(breakdownData);
bars.exit().remove();
const barsEnter = bars.enter()
.append('path')
.attr('class', 'bar')
.on('mouseenter', function (event, d) {
const color = colorMap[d.component];
tipInner.innerHTML = `
<div class="tooltip-header">
<div class="tooltip-swatch" style="background: ${color}"></div>
<strong>${d.component}</strong>
</div>
<div>Memory: <strong>${d.value.toLocaleString()} MiB</strong></div>
`;
tip.classList.add('visible');
})
.on('mousemove', function (event) {
const [mx, my] = d3.pointer(event, container);
tip.style.transform = `translate(${mx + 10}px, ${my - 10}px)`;
})
.on('mouseleave', function () {
tip.classList.remove('visible');
tip.style.transform = 'translate(-9999px, -9999px)';
});
barsEnter.merge(bars)
.transition()
.duration(200)
.attr('d', d => {
if (d.value <= 0) return '';
const barX = x(d.component);
const barWidth = x.bandwidth();
const barY = y(d.value);
const barHeight = innerHeight - y(d.value);
const radius = Math.min(barWidth / 8, 3);
return roundedTopRect(barX, barY, barWidth, barHeight, radius);
})
.attr('fill', d => colorMap[d.component]);
// Labels
const labels = g1.selectAll('.bar-label').data(breakdownData);
labels.exit().remove();
const labelsEnter = labels.enter()
.append('text')
.attr('class', 'bar-label')
.style('visibility', 'hidden');
labelsEnter.merge(labels)
.attr('x', d => x(d.component) + x.bandwidth() / 2)
.attr('y', d => d.value > 0 ? y(d.value) - 5 : innerHeight - 5)
.text(d => d.value > 0 ? d.value.toFixed(0) : '')
.style('visibility', d => d.value > 0 ? 'visible' : 'hidden');
// Axes
g1.selectAll('.x-axis').remove();
g1.selectAll('.y-axis').remove();
const xAxis = g1.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0,${innerHeight})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
.selectAll('text')
.attr('transform', 'rotate(-45)')
.style('text-anchor', 'end');
const yAxis = g1.append('g')
.attr('class', 'y-axis')
.call(d3.axisLeft(y).ticks(6).tickSizeOuter(0));
// Y-axis label
g1.selectAll('.y-axis-label').remove();
g1.append('text')
.attr('class', 'axis-label y-axis-label')
.attr('transform', 'rotate(-90)')
.attr('x', -innerHeight / 2)
.attr('y', -margin.left + 20)
.style('text-anchor', 'middle')
.text('Memory (MiB)');
}
function renderTimeline() {
const { innerWidth, innerHeight, margin } = updateSize2();
// Scales
const x = d3.scaleBand()
.domain(timelineData.map(d => d.stage))
.range([0, innerWidth])
.padding(0.15);
const maxTotal = d3.max(timelineData, d => d.total);
const y = d3.scaleLinear()
.domain([0, maxTotal])
.range([innerHeight, 0])
.nice();
// Grid
g2.selectAll('.grid').remove();
const grid = g2.append('g').attr('class', 'grid');
grid.selectAll('line')
.data(y.ticks(8))
.join('line')
.attr('x1', 0)
.attr('x2', innerWidth)
.attr('y1', d => y(d))
.attr('y2', d => y(d));
// Stack generator
const stack = d3.stack()
.keys(allComponents)
.order(d3.stackOrderNone)
.offset(d3.stackOffsetNone);
// Transform data for stacking
const stackedData = stack(timelineData.map(d => {
const obj = {};
allComponents.forEach(comp => {
const segment = d.segments.find(s => s.name === comp);
obj[comp] = segment ? segment.value : 0;
});
obj.stage = d.stage;
obj.total = d.total;
return obj;
}));
// Draw stacked bars
const groups = g2.selectAll('.bar-group').data(stackedData);
groups.exit().remove();
const groupsEnter = groups.enter()
.append('g')
.attr('class', 'bar-group');
const groupsMerge = groupsEnter.merge(groups);
groupsMerge.each(function (series) {
const seriesKey = series.key;
const bars = d3.select(this).selectAll('.bar').data(series);
bars.exit().remove();
const barsEnter = bars.enter()
.append('path')
.attr('class', 'bar')
.on('mouseenter', function (event, d) {
const stage = d.data.stage;
const value = d[1] - d[0];
const color = colorMap[seriesKey];
tipInner.innerHTML = `
<div class="tooltip-header">
<div class="tooltip-swatch" style="background: ${color}"></div>
<strong>${seriesKey}</strong>
</div>
<div>${stage}</div>
<div>Memory: <strong>${value.toLocaleString()} MiB</strong></div>
`;
tip.classList.add('visible');
})
.on('mousemove', function (event) {
const [mx, my] = d3.pointer(event, container);
tip.style.transform = `translate(${mx + 10}px, ${my - 10}px)`;
})
.on('mouseleave', function () {
tip.classList.remove('visible');
tip.style.transform = 'translate(-9999px, -9999px)';
});
barsEnter.merge(bars)
.each(function (d) {
const isTopSegment = d[1] === d.data.total;
const barX = x(d.data.stage);
const barWidth = x.bandwidth();
const barY = y(d[1]);
const barHeight = y(d[0]) - y(d[1]);
if (barHeight <= 0) {
d3.select(this).attr('d', '');
return;
}
let path;
if (isTopSegment) {
// Top segment - rounded top corners
const radius = Math.min(barWidth / 8, 3);
path = roundedTopRect(barX, barY, barWidth, barHeight, radius);
} else {
// Other segments - rectangular
path = `M ${barX},${barY}
L ${barX + barWidth},${barY}
L ${barX + barWidth},${barY + barHeight}
L ${barX},${barY + barHeight}
Z`;
}
d3.select(this).attr('d', path);
})
.transition()
.duration(200)
.attr('fill', colorMap[seriesKey] || '#000000');
});
// Total labels on top of bars
const totalLabels = g2.selectAll('.total-label').data(timelineData);
totalLabels.exit().remove();
const totalLabelsEnter = totalLabels.enter()
.append('text')
.attr('class', 'total-label bar-label')
.style('visibility', 'hidden');
totalLabelsEnter.merge(totalLabels)
.attr('x', d => x(d.stage) + x.bandwidth() / 2)
.attr('y', d => y(d.total) - 5)
.text(d => d.total.toFixed(0))
.style('visibility', 'visible');
// Axes
g2.selectAll('.x-axis').remove();
g2.selectAll('.y-axis').remove();
const xAxis = g2.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0,${innerHeight})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
.selectAll('text')
.attr('transform', 'rotate(-45)')
.style('text-anchor', 'end');
const yAxis = g2.append('g')
.attr('class', 'y-axis')
.call(d3.axisLeft(y).ticks(8).tickSizeOuter(0));
// Y-axis label
g2.selectAll('.y-axis-label').remove();
g2.append('text')
.attr('class', 'axis-label y-axis-label')
.attr('transform', 'rotate(-90)')
.attr('x', -innerHeight / 2)
.attr('y', -margin.left + 20)
.style('text-anchor', 'middle')
.text('Memory (MiB)');
}
function render() {
makeCommonLegend();
renderBreakdown();
renderTimeline();
}
// Initial render + resize handling
render();
const rerender = () => render();
if (window.ResizeObserver) {
const ro = new ResizeObserver(() => rerender());
ro.observe(container);
} else {
window.addEventListener('resize', rerender);
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => ensureD3(bootstrap), { once: true });
} else {
ensureD3(bootstrap);
}
})();
</script>