Skip to content

Instantly share code, notes, and snippets.

@boatbomber
Last active August 31, 2025 18:18
Show Gist options
  • Save boatbomber/4cd4aac61d8fac8ff87790e2fcef6ea0 to your computer and use it in GitHub Desktop.
Save boatbomber/4cd4aac61d8fac8ff87790e2fcef6ea0 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Training State Dashboard</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
background: #0a0a0a;
background-image:
radial-gradient(ellipse at top left, rgba(0, 80, 120, 0.15) 0%, transparent 50%),
radial-gradient(ellipse at bottom right, rgba(120, 0, 80, 0.15) 0%, transparent 50%);
min-height: 100vh;
padding: 20px;
color: #e0e0e0;
}
.container {
max-width: 1400px;
margin: 0 auto;
position: relative;
z-index: 2;
}
.header {
background: rgba(10, 10, 10, 0.8);
backdrop-filter: blur(20px);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 4px;
padding: 30px;
margin-bottom: 30px;
box-shadow:
0 0 40px rgba(0, 255, 255, 0.1),
inset 0 0 20px rgba(0, 255, 255, 0.02);
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(0, 255, 255, 0.8) 50%,
transparent 100%);
}
h1 {
color: #00ffff;
font-size: 2.2em;
margin-bottom: 20px;
font-weight: 300;
letter-spacing: 2px;
text-transform: uppercase;
text-shadow:
0 0 20px rgba(0, 255, 255, 0.5),
0 0 40px rgba(0, 255, 255, 0.3);
}
.upload-section {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
}
.file-input-wrapper {
position: relative;
overflow: hidden;
display: inline-block;
}
.file-input-wrapper input[type=file] {
position: absolute;
left: -9999px;
}
.file-input-label {
display: inline-block;
padding: 12px 30px;
background: rgba(0, 255, 255, 0.1);
color: #00ffff;
border: 1px solid rgba(0, 255, 255, 0.4);
border-radius: 2px;
cursor: pointer;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 1px;
font-size: 0.9em;
position: relative;
overflow: hidden;
}
.file-input-label:hover {
background: rgba(0, 255, 255, 0.2);
border-color: #00ffff;
box-shadow:
0 0 20px rgba(0, 255, 255, 0.4),
inset 0 0 20px rgba(0, 255, 255, 0.1);
transform: translateY(-1px);
}
.file-input-label:active {
transform: translateY(0);
}
.file-name {
color: #888;
font-size: 0.9em;
margin-left: 10px;
font-family: 'SF Mono', monospace;
}
.dashboard {
display: none;
}
.dashboard.active {
display: block;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.stat-card {
background: rgba(10, 10, 10, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 255, 255, 0.1);
border-radius: 2px;
padding: 20px;
box-shadow:
0 0 20px rgba(0, 255, 255, 0.05),
inset 0 0 20px rgba(0, 255, 255, 0.01);
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 3px;
height: 100%;
background: linear-gradient(180deg, transparent, #00ffff, transparent);
opacity: 0;
}
.stat-card:hover::before {
opacity: 1;
}
.stat-card:hover {
border-color: rgba(0, 255, 255, 0.3);
transform: translateX(2px);
box-shadow:
0 0 30px rgba(0, 255, 255, 0.1),
inset 0 0 30px rgba(0, 255, 255, 0.02);
}
.stat-label {
color: #00ffff;
font-size: 0.75em;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 2px;
opacity: 0.7;
font-weight: 300;
}
.stat-value {
color: #ffffff;
font-size: 1.8em;
font-weight: 200;
font-family: 'SF Mono', monospace;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.3);
}
.chart-container {
background: rgba(10, 10, 10, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 255, 255, 0.1);
border-radius: 2px;
padding: 25px;
margin-bottom: 20px;
box-shadow:
0 0 30px rgba(0, 255, 255, 0.05),
inset 0 0 30px rgba(0, 255, 255, 0.01);
position: relative;
}
.chart-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(0, 255, 255, 0.3) 20%,
rgba(0, 255, 255, 0.3) 80%,
transparent 100%);
}
.chart-title {
color: #00ffff;
font-size: 1.2em;
margin-bottom: 20px;
font-weight: 300;
text-transform: uppercase;
letter-spacing: 2px;
opacity: 0.9;
}
.chart-wrapper {
position: relative;
height: 400px;
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(0, 255, 255, 0.05);
padding: 10px;
border-radius: 2px;
}
.progress-container {
background: rgba(10, 10, 10, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 255, 255, 0.1);
border-radius: 2px;
padding: 25px;
margin-bottom: 20px;
box-shadow:
0 0 30px rgba(0, 255, 255, 0.05),
inset 0 0 30px rgba(0, 255, 255, 0.01);
position: relative;
}
.progress-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(0, 255, 255, 0.3) 20%,
rgba(0, 255, 255, 0.3) 80%,
transparent 100%);
}
.progress-bar-wrapper {
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(0, 255, 255, 0.1);
border-radius: 2px;
height: 32px;
overflow: hidden;
margin-bottom: 8px;
position: relative;
}
.progress-bar-wrapper::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: repeating-linear-gradient(
90deg,
transparent,
transparent 10px,
rgba(0, 255, 255, 0.03) 10px,
rgba(0, 255, 255, 0.03) 20px
);
pointer-events: none;
}
.progress-bar {
background: linear-gradient(90deg,
rgba(0, 255, 255, 0.2),
rgba(0, 255, 255, 0.6),
rgba(0, 255, 255, 0.2));
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 300;
font-size: 1em;
letter-spacing: 1px;
box-shadow:
0 0 20px rgba(0, 255, 255, 0.4),
inset 0 0 20px rgba(0, 255, 255, 0.2);
position: relative;
overflow: hidden;
}
.error-message {
background: rgba(255, 0, 0, 0.1);
border: 1px solid rgba(255, 0, 0, 0.3);
color: #ff6666;
padding: 15px;
border-radius: 2px;
margin-top: 20px;
display: none;
font-family: 'SF Mono', monospace;
font-size: 0.9em;
}
.two-charts {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 968px) {
.two-charts {
grid-template-columns: 1fr;
}
}
.tooltip {
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 8px 12px;
border-radius: 5px;
font-size: 0.9em;
}
/* Skeleton loader styles */
.skeleton {
background-color: rgba(0, 255, 255, 0.1);
border-radius: 2px;
}
.skeleton-text {
width: 100%;
height: 0.8em;
margin-bottom: 0.5rem;
border-radius: 2px;
}
.skeleton-text:last-child {
width: 80%;
}
.skeleton-stat-value {
width: 60%;
height: 2em;
margin-top: 8px;
}
.loading-message {
text-align: center;
color: #00ffff;
font-size: 1em;
margin: 20px 0;
font-weight: 300;
text-transform: uppercase;
letter-spacing: 2px;
opacity: 0.7;
}
/* Smoothing controls */
.smoothing-controls {
background: rgba(10, 10, 10, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 255, 255, 0.1);
border-radius: 2px;
padding: 25px;
margin-bottom: 20px;
box-shadow:
0 0 30px rgba(0, 255, 255, 0.05),
inset 0 0 30px rgba(0, 255, 255, 0.01);
display: none;
position: relative;
}
.smoothing-controls::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg,
transparent 0%,
rgba(0, 255, 255, 0.3) 20%,
rgba(0, 255, 255, 0.3) 80%,
transparent 100%);
}
.smoothing-controls.active {
display: block;
}
.smoothing-title {
color: #00ffff;
font-size: 1.2em;
margin-bottom: 20px;
font-weight: 300;
text-transform: uppercase;
letter-spacing: 2px;
opacity: 0.9;
}
.slider-container {
display: flex;
align-items: center;
gap: 20px;
}
.slider-wrapper {
flex: 1;
min-width: 300px;
}
.slider-labels {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
color: #00ffff;
font-size: 0.8em;
text-transform: uppercase;
letter-spacing: 1px;
opacity: 0.6;
}
.slider {
width: 100%;
height: 4px;
border-radius: 0;
background: rgba(0, 255, 255, 0.1);
outline: none;
-webkit-appearance: none;
cursor: pointer;
position: relative;
}
.slider::before {
content: '';
position: absolute;
height: 100%;
background: rgba(0, 255, 255, 0.6);
width: var(--slider-fill, 50%);
pointer-events: none;
box-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 0;
background: #00ffff;
border: 2px solid rgba(0, 20, 20, 0.8);
cursor: pointer;
box-shadow:
0 0 20px rgba(0, 255, 255, 0.8),
inset 0 0 5px rgba(0, 255, 255, 0.3);
z-index: 2;
position: relative;
transform: rotate(45deg);
}
.slider::-webkit-slider-thumb:hover {
box-shadow:
0 0 30px rgba(0, 255, 255, 1),
inset 0 0 10px rgba(0, 255, 255, 0.5);
}
.slider::-webkit-slider-thumb:active {
transform: rotate(45deg) scale(0.9);
}
.slider::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: 0;
background: #00ffff;
border: 2px solid rgba(0, 20, 20, 0.8);
cursor: pointer;
box-shadow:
0 0 20px rgba(0, 255, 255, 0.8),
inset 0 0 5px rgba(0, 255, 255, 0.3);
transform: rotate(45deg);
}
.slider::-moz-range-thumb:hover {
box-shadow:
0 0 30px rgba(0, 255, 255, 1),
inset 0 0 10px rgba(0, 255, 255, 0.5);
}
.slider::-moz-range-thumb:active {
transform: rotate(45deg) scale(0.9);
}
.slider-value {
background: rgba(0, 255, 255, 0.1);
border: 1px solid rgba(0, 255, 255, 0.3);
color: #00ffff;
padding: 10px 20px;
border-radius: 2px;
font-weight: 300;
min-width: 100px;
text-align: center;
font-size: 1.2em;
font-family: 'SF Mono', monospace;
letter-spacing: 1px;
box-shadow:
0 0 20px rgba(0, 255, 255, 0.2),
inset 0 0 10px rgba(0, 255, 255, 0.05);
}
.smoothing-description {
color: #888;
font-size: 0.8em;
margin-top: 12px;
text-align: center;
text-transform: uppercase;
letter-spacing: 1px;
opacity: 0.6;
}
.updating-indicator {
display: inline-block;
margin-left: 10px;
color: #00ffff;
font-size: 0.85em;
opacity: 0;
text-transform: uppercase;
letter-spacing: 1px;
}
.updating-indicator.active {
opacity: 1;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>AI Training Monitor</h1>
<div class="upload-section">
<div class="file-input-wrapper">
<input type="file" id="fileInput" accept=".json">
<label for="fileInput" class="file-input-label">
Load Training State
</label>
</div>
<span class="file-name" id="fileName">No file selected</span>
</div>
<div class="error-message" id="errorMessage"></div>
</div>
<div class="dashboard" id="dashboard">
<!-- Progress Bar -->
<div class="progress-container">
<h2 class="chart-title">Training Progress</h2>
<div>
<div>
<div class="progress-bar-wrapper">
<div class="progress-bar" id="progressBar"></div>
</div>
<div id="progressText" style="color: #888; margin-top: 12px; font-size: 0.95em; text-align: center;"></div>
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="stats-grid" id="statsGrid"></div>
<!-- Smoothing Controls -->
<div class="smoothing-controls" id="smoothingControls">
<h3 class="smoothing-title">Smoothing Control</h3>
<div class="slider-container">
<div class="slider-wrapper">
<div class="slider-labels">
<span>Raw Data</span>
<span>Smooth Trends</span>
</div>
<input type="range" class="slider" id="smoothingSlider" min="0" max="1" step="0.01" value="0.5">
<div class="smoothing-description" id="smoothingDescription">
MODERATE SMOOTHING - BALANCED
</div>
</div>
<div class="slider-value" id="smoothingValue">50%</div>
</div>
<span class="updating-indicator" id="updatingIndicator">Updating...</span>
</div>
<!-- Main Loss Chart -->
<div class="chart-container">
<h2 class="chart-title">Loss Trajectory</h2>
<div class="chart-wrapper">
<canvas id="lossChart"></canvas>
</div>
</div>
<!-- Overall Reward Chart -->
<div class="chart-container">
<h2 class="chart-title">Overall Reward</h2>
<div class="chart-wrapper">
<canvas id="rewardChart"></canvas>
</div>
</div>
<!-- Individual Reward Functions Chart -->
<div class="chart-container">
<h2 class="chart-title">Individual Reward Functions</h2>
<div class="chart-wrapper">
<canvas id="individualRewardsChart"></canvas>
</div>
</div>
<!-- Completion Length Chart -->
<div class="chart-container">
<h2 class="chart-title">Completion Length</h2>
<div class="chart-wrapper">
<canvas id="completionLengthChart"></canvas>
</div>
</div>
<!-- Learning Rate and Gradient Norm Charts -->
<div class="two-charts">
<div class="chart-container">
<h2 class="chart-title">Learning Rate Schedule</h2>
<div class="chart-wrapper">
<canvas id="lrChart"></canvas>
</div>
</div>
<div class="chart-container">
<h2 class="chart-title">Gradient Magnitude</h2>
<div class="chart-wrapper">
<canvas id="gradChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script>
// Set Chart.js defaults for sci-fi theme with better readability
Chart.defaults.color = 'rgba(255, 255, 255, 0.8)'; // Brighter text
Chart.defaults.borderColor = 'rgba(0, 255, 255, 0.1)';
Chart.defaults.font.family = "'SF Mono', monospace";
Chart.defaults.font.size = 12; // Slightly larger
let charts = {};
let currentData = null;
let smoothingLevel = 0.5; // 0-1 scale, 0.5 is default (50% smoothing)
let updateTimeout = null; // For debouncing slider updates
let isUpdating = false; // Flag to prevent overlapping updates
// Convert smoothing level (0-1) to alpha value for exponential moving average
function getSmoothingAlpha(dataLength) {
// Calculate reasonable alpha range based on data size
// For no smoothing (level=0): alpha = 1.0 (all weight on current value)
// For max smoothing (level=1): alpha based on data length, but not too extreme
// Ensure maximum smoothing still shows some variation (not a straight line)
const minAlpha = Math.max(0.001, Math.min(0.1, 10 / dataLength)); // Cap maximum smoothing
const maxAlpha = 1.0; // No smoothing
// Invert the scale and use exponential mapping for better control
const invertedLevel = 1 - smoothingLevel;
// Use cubic mapping for even more intuitive control
const alpha = minAlpha + (maxAlpha - minAlpha) * Math.pow(invertedLevel, 3);
return alpha;
}
// Adaptive smoothing function using exponential moving average
// Optimized for performance with large datasets
function exponentialMovingAverage(data, alpha = null) {
// Calculate alpha based on smoothing level if not provided
if (alpha === null) {
alpha = getSmoothingAlpha(data.length);
}
const smoothed = new Array(data.length);
let ema = data[0];
// Optimize for performance by avoiding null checks when possible
const hasNulls = data.some(v => v === null || v === undefined);
if (!hasNulls) {
// Fast path when no nulls
for (let i = 0; i < data.length; i++) {
ema = alpha * data[i] + (1 - alpha) * ema;
smoothed[i] = ema;
}
} else {
// Handle nulls
for (let i = 0; i < data.length; i++) {
if (data[i] !== null && data[i] !== undefined) {
ema = alpha * data[i] + (1 - alpha) * ema;
smoothed[i] = ema;
} else {
smoothed[i] = null;
}
}
}
return smoothed;
}
// Calculate percentile for outlier handling - optimized version
function calculatePercentile(arr, percentile) {
// Pre-filter and sort
const sorted = arr.filter(v => v !== null && v !== undefined);
if (sorted.length === 0) return 0;
sorted.sort((a, b) => a - b);
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
return sorted[Math.max(0, index)];
}
// Get Y-axis limits that exclude outliers
function getYAxisLimits(data, lowerPercentile = 1, upperPercentile = 99) {
const validData = data.filter(v => v !== null && v !== undefined);
if (validData.length === 0) return { min: 0, max: 1 };
const lower = calculatePercentile(validData, lowerPercentile);
const upper = calculatePercentile(validData, upperPercentile);
// Add 5% padding
const range = upper - lower;
const padding = range * 0.05;
return {
min: Math.max(0, lower - padding),
max: upper + padding
};
}
document.getElementById('fileInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
document.getElementById('fileName').textContent = file.name;
// Show dashboard with skeletons immediately
showSkeletons();
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = JSON.parse(e.target.result);
// Process data asynchronously to avoid blocking
setTimeout(() => displayDashboard(data), 10);
} catch (error) {
showError('Error parsing JSON file: ' + error.message);
document.getElementById('dashboard').style.display = 'none';
}
};
reader.readAsText(file);
}
});
// Smoothing control event listener with debouncing
const smoothingSlider = document.getElementById('smoothingSlider');
if (smoothingSlider) {
smoothingSlider.addEventListener('input', function(e) {
smoothingLevel = parseFloat(e.target.value);
const percentage = Math.round(smoothingLevel * 100);
document.getElementById('smoothingValue').textContent = `${percentage}%`;
// Update slider fill
smoothingSlider.style.setProperty('--slider-fill', `${smoothingLevel * 100}%`);
// Update description based on level
const descElement = document.getElementById('smoothingDescription');
if (smoothingLevel === 0) {
descElement.textContent = 'RAW DATA - NO SMOOTHING';
} else if (smoothingLevel < 0.3) {
descElement.textContent = 'LIGHT SMOOTHING - HIGH DETAIL';
} else if (smoothingLevel < 0.7) {
descElement.textContent = 'MODERATE SMOOTHING - BALANCED';
} else {
descElement.textContent = 'HEAVY SMOOTHING - TREND FOCUS';
}
// Debounce the chart update
if (updateTimeout) {
clearTimeout(updateTimeout);
}
// Show a subtle indication that update is pending
const indicator = document.getElementById('updatingIndicator');
if (indicator) {
indicator.textContent = 'PENDING...';
indicator.classList.add('active');
}
updateTimeout = setTimeout(() => {
if (currentData && !isUpdating) {
if (indicator) {
indicator.textContent = 'UPDATING...';
}
updateAllChartsAsync();
}
}, 100); // Reduced to 100ms for more responsive feel
});
// Set initial fill
smoothingSlider.style.setProperty('--slider-fill', '50%');
}
// Function to update all charts with new smoothing
function updateAllCharts() {
if (!currentData || !currentData.log_history) return;
const chartFlags = configureChartVisibility(currentData);
// Recreate all charts with new smoothing (skip fade for instant update)
createLossChart(currentData.log_history, true);
if (chartFlags.showRewardChart) {
createRewardChart(currentData.log_history, true);
}
if (chartFlags.showIndividualRewards) {
createIndividualRewardsChart(currentData.log_history, true);
}
if (chartFlags.showCompletionLength) {
createCompletionLengthChart(currentData.log_history, true);
}
createGradientNormChart(currentData.log_history, true);
// Note: Learning rate chart doesn't use smoothing, but update for consistency
createLearningRateChart(currentData.log_history, true);
}
// Async version that doesn't block the UI
async function updateAllChartsAsync() {
if (!currentData || !currentData.log_history || isUpdating) return;
isUpdating = true;
// Show updating indicator
const indicator = document.getElementById('updatingIndicator');
if (indicator) {
indicator.classList.add('active');
}
const chartFlags = configureChartVisibility(currentData);
// Update charts one by one with breaks for UI responsiveness
await new Promise(resolve => {
requestAnimationFrame(() => {
createLossChart(currentData.log_history, true);
resolve();
});
});
await new Promise(resolve => setTimeout(resolve, 10));
if (chartFlags.showRewardChart) {
await new Promise(resolve => {
requestAnimationFrame(() => {
createRewardChart(currentData.log_history, true);
resolve();
});
});
await new Promise(resolve => setTimeout(resolve, 10));
}
if (chartFlags.showIndividualRewards) {
await new Promise(resolve => {
requestAnimationFrame(() => {
createIndividualRewardsChart(currentData.log_history, true);
resolve();
});
});
await new Promise(resolve => setTimeout(resolve, 10));
}
if (chartFlags.showCompletionLength) {
await new Promise(resolve => {
requestAnimationFrame(() => {
createCompletionLengthChart(currentData.log_history, true);
resolve();
});
});
await new Promise(resolve => setTimeout(resolve, 10));
}
await new Promise(resolve => {
requestAnimationFrame(() => {
createGradientNormChart(currentData.log_history, true);
resolve();
});
});
await new Promise(resolve => setTimeout(resolve, 10));
await new Promise(resolve => {
requestAnimationFrame(() => {
createLearningRateChart(currentData.log_history, true);
resolve();
});
});
// Hide updating indicator
if (indicator) {
setTimeout(() => {
indicator.classList.remove('active');
}, 200);
}
isUpdating = false;
}
function showSkeletons() {
const dashboard = document.getElementById('dashboard');
dashboard.style.display = 'block';
// Show skeleton stats
const statsGrid = document.getElementById('statsGrid');
statsGrid.innerHTML = '';
for (let i = 0; i < 4; i++) { // Only 4 stats now
const card = document.createElement('div');
card.className = 'stat-card';
card.innerHTML = `
<div class="skeleton skeleton-text" style="width: 70%;"></div>
<div class="skeleton skeleton-stat-value"></div>
`;
statsGrid.appendChild(card);
}
// Show skeleton progress bar
document.getElementById('progressBar').style.width = '0%';
document.getElementById('progressBar').innerHTML = '<span style="color: rgba(255,255,255,0.7);">LOADING...</span>';
document.getElementById('progressText').innerHTML = '<span style="color: #00ffff; opacity: 0.5;">CALCULATING TRAINING PROGRESS...</span>';
// Show skeleton charts
const charts = ['lossChart', 'rewardChart', 'individualRewardsChart', 'completionLengthChart', 'lrChart', 'gradChart'];
charts.forEach(chartId => {
const canvas = document.getElementById(chartId);
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Add loading message to canvas
ctx.fillStyle = '#999';
ctx.font = '16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Loading chart data...', canvas.width / 2, canvas.height / 2);
});
// Add loading message
if (!document.getElementById('loadingMessage')) {
const loadingMsg = document.createElement('div');
loadingMsg.id = 'loadingMessage';
loadingMsg.className = 'loading-message';
loadingMsg.innerHTML = 'Processing Training Data...';
dashboard.insertBefore(loadingMsg, dashboard.firstChild);
}
}
function showError(message) {
const errorEl = document.getElementById('errorMessage');
errorEl.textContent = message;
errorEl.style.display = 'block';
setTimeout(() => {
errorEl.style.display = 'none';
}, 5000);
}
async function displayDashboard(data) {
// Store data globally for chart updates
currentData = data;
// Remove loading message
const loadingMsg = document.getElementById('loadingMessage');
if (loadingMsg) {
loadingMsg.remove();
}
// Configure chart visibility based on available data
const chartFlags = configureChartVisibility(data);
// Show smoothing controls and set initial display
document.getElementById('smoothingControls').classList.add('active');
document.getElementById('smoothingValue').textContent = `${Math.round(smoothingLevel * 100)}%`;
document.getElementById('smoothingSlider').value = smoothingLevel;
document.getElementById('smoothingSlider').style.setProperty('--slider-fill', `${smoothingLevel * 100}%`);
// Set initial description
const descElement = document.getElementById('smoothingDescription');
if (smoothingLevel === 0) {
descElement.textContent = 'RAW DATA - NO SMOOTHING';
} else if (smoothingLevel < 0.3) {
descElement.textContent = 'LIGHT SMOOTHING - HIGH DETAIL';
} else if (smoothingLevel < 0.7) {
descElement.textContent = 'MODERATE SMOOTHING - BALANCED';
} else {
descElement.textContent = 'HEAVY SMOOTHING - TREND FOCUS';
}
// Display statistics with animation
await new Promise(resolve => {
setTimeout(() => {
displayStats(data);
resolve();
}, 10);
});
// Display progress bars with animation
await new Promise(resolve => {
setTimeout(() => {
displayProgress(data);
resolve();
}, 20);
});
// Create charts progressively - only create charts with available data
let delay = 30;
// Loss chart (always present)
await new Promise(resolve => {
setTimeout(() => {
createLossChart(data.log_history);
resolve();
}, delay);
});
delay += 10;
// Reward chart (conditional)
if (chartFlags.showRewardChart) {
await new Promise(resolve => {
setTimeout(() => {
createRewardChart(data.log_history);
resolve();
}, delay);
});
delay += 10;
}
// Individual rewards chart (conditional)
if (chartFlags.showIndividualRewards) {
await new Promise(resolve => {
setTimeout(() => {
createIndividualRewardsChart(data.log_history);
resolve();
}, delay);
});
delay += 10;
}
// Completion length chart (conditional)
if (chartFlags.showCompletionLength) {
await new Promise(resolve => {
setTimeout(() => {
createCompletionLengthChart(data.log_history);
resolve();
}, delay);
});
delay += 10;
}
// Learning rate chart (always present)
await new Promise(resolve => {
setTimeout(() => {
createLearningRateChart(data.log_history);
resolve();
}, delay);
});
delay += 10;
// Gradient norm chart (always present)
await new Promise(resolve => {
setTimeout(() => {
createGradientNormChart(data.log_history);
resolve();
}, delay);
});
}
function displayStats(data) {
const statsGrid = document.getElementById('statsGrid');
statsGrid.innerHTML = '';
const stats = [
{ label: 'Total FLOPs', value: data.total_flos ? (data.total_flos / 1e18).toFixed(2) + ' E' : 'N/A' }
];
// Add final loss if available
if (data.log_history && data.log_history.length > 0) {
const lastEntry = data.log_history[data.log_history.length - 1];
if (lastEntry.loss) {
stats.unshift({ label: 'Current Loss', value: lastEntry.loss.toFixed(4) });
}
if (lastEntry.learning_rate) {
stats.push({ label: 'Current LR', value: lastEntry.learning_rate.toExponential(2) });
}
if (lastEntry.grad_norm) {
stats.push({ label: 'Current Grad Norm', value: lastEntry.grad_norm.toFixed(4) });
}
}
stats.forEach(stat => {
const card = document.createElement('div');
card.className = 'stat-card';
card.innerHTML = `
<div class="stat-label">${stat.label}</div>
<div class="stat-value">${stat.value}</div>
`;
statsGrid.appendChild(card);
});
}
function displayProgress(data) {
// Calculate progress (both epoch and step should give same percentage)
const progress = (data.global_step / data.max_steps) * 100;
const progressBar = document.getElementById('progressBar');
progressBar.style.width = progress + '%';
progressBar.textContent = progress.toFixed(1) + '%';
// Show both epoch and step info in the text with better formatting
const epochInfo = `Epoch ${data.epoch?.toFixed(4) || 0} of ${data.num_train_epochs || 0}`;
const stepInfo = `Step ${data.global_step?.toLocaleString() || 0} of ${data.max_steps?.toLocaleString() || 0}`;
document.getElementById('progressText').innerHTML =
`<span style="color: #00ffff;">${epochInfo}</span> <span style="color: #444; margin: 0 10px;">|</span> <span style="color: #00ffff;">${stepInfo}</span>`;
}
// Helper function for chart compatibility
function fadeInChart(canvasId, skipFade = false) {
// No longer doing fade-in animations
const canvas = document.getElementById(canvasId);
canvas.style.opacity = '1';
}
function createLossChart(logHistory, skipFade = false) {
const ctx = document.getElementById('lossChart').getContext('2d');
fadeInChart('lossChart', skipFade);
if (charts.loss) {
charts.loss.destroy();
}
const lossData = logHistory.filter(entry => entry.loss !== undefined);
const rawLosses = lossData.map(entry => entry.loss);
// Calculate smoothed data - using adaptive EMA based on data size
const smoothedLosses = exponentialMovingAverage(rawLosses);
// Get Y-axis limits excluding outliers (1st to 99th percentile)
const yLimits = getYAxisLimits(rawLosses, 1, 99);
charts.loss = new Chart(ctx, {
type: 'line',
data: {
labels: lossData.map(entry => entry.step),
datasets: [{
label: 'Training Loss (Raw)',
data: rawLosses,
borderColor: 'rgba(0, 255, 255, 0.2)',
backgroundColor: 'rgba(0, 255, 255, 0.05)',
borderWidth: 1,
pointRadius: 0,
pointHoverRadius: 3,
tension: 0.1
}, {
label: 'Training Loss (Smoothed)',
data: smoothedLosses,
borderColor: 'rgba(0, 255, 255, 0.8)',
backgroundColor: 'rgba(0, 255, 255, 0.1)',
borderWidth: 2,
pointRadius: 0,
pointHoverRadius: 4,
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
color: 'rgba(255, 255, 255, 0.8)',
font: {
family: "'SF Mono', monospace",
size: 11
}
}
},
tooltip: {
mode: 'index',
intersect: false,
backgroundColor: 'rgba(0, 0, 0, 0.9)',
borderColor: 'rgba(0, 255, 255, 0.3)',
borderWidth: 1,
titleColor: '#00ffff',
bodyColor: 'rgba(255, 255, 255, 0.8)',
callbacks: {
label: function(context) {
const label = context.dataset.label || '';
return `${label}: ${context.parsed.y.toFixed(4)}`;
}
}
}
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'STEP',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
y: {
display: true,
title: {
display: true,
text: 'LOSS',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
},
min: yLimits.min,
max: yLimits.max
}
}
}
});
}
function createRewardChart(logHistory, skipFade = false) {
const ctx = document.getElementById('rewardChart').getContext('2d');
fadeInChart('rewardChart', skipFade);
if (charts.reward) {
charts.reward.destroy();
}
const rewardData = logHistory.filter(entry => entry.reward !== undefined);
const rawRewards = rewardData.map(entry => entry.reward);
// Calculate smoothed data
const smoothedRewards = exponentialMovingAverage(rawRewards);
// Get Y-axis limits excluding outliers
const yLimits = getYAxisLimits(rawRewards, 1, 99);
charts.reward = new Chart(ctx, {
type: 'line',
data: {
labels: rewardData.map(entry => entry.step),
datasets: [{
label: 'Overall Reward (Raw)',
data: rawRewards,
borderColor: 'rgba(0, 255, 128, 0.2)',
backgroundColor: 'rgba(0, 255, 128, 0.05)',
borderWidth: 1,
pointRadius: 0,
pointHoverRadius: 3,
tension: 0.1
}, {
label: 'Overall Reward (Smoothed)',
data: smoothedRewards,
borderColor: 'rgba(0, 255, 128, 0.8)',
backgroundColor: 'rgba(0, 255, 128, 0.1)',
borderWidth: 2,
pointRadius: 0,
pointHoverRadius: 4,
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
color: 'rgba(255, 255, 255, 0.8)',
font: {
family: "'SF Mono', monospace",
size: 11
}
}
},
tooltip: {
mode: 'index',
intersect: false,
backgroundColor: 'rgba(0, 0, 0, 0.9)',
borderColor: 'rgba(0, 255, 255, 0.3)',
borderWidth: 1,
titleColor: '#00ffff',
bodyColor: 'rgba(255, 255, 255, 0.8)',
callbacks: {
label: function(context) {
const label = context.dataset.label || '';
return `${label}: ${context.parsed.y.toFixed(4)}`;
}
}
}
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'STEP',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
y: {
display: true,
title: {
display: true,
text: 'REWARD',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
},
min: yLimits.min,
max: yLimits.max
}
}
}
});
}
// Helper functions to detect data availability
function hasRewardData(logHistory) {
return logHistory.some(entry => entry.reward !== undefined && entry.reward !== null);
}
function hasIndividualRewardData(logHistory) {
const rewardKeys = extractRewardFunctions(logHistory);
return rewardKeys.length > 0;
}
function hasCompletionLengthData(logHistory) {
return logHistory.some(entry => entry['completions/mean_length'] !== undefined && entry['completions/mean_length'] !== null);
}
// Function to show/hide chart containers based on data availability
function configureChartVisibility(data) {
const logHistory = data.log_history || [];
// Check data availability
const showRewardChart = hasRewardData(logHistory);
const showIndividualRewards = hasIndividualRewardData(logHistory);
const showCompletionLength = hasCompletionLengthData(logHistory);
// Show/hide chart containers
const rewardChartContainer = document.querySelector('#rewardChart').closest('.chart-container');
const individualRewardsContainer = document.querySelector('#individualRewardsChart').closest('.chart-container');
const completionLengthContainer = document.querySelector('#completionLengthChart').closest('.chart-container');
if (rewardChartContainer) {
rewardChartContainer.style.display = showRewardChart ? 'block' : 'none';
}
if (individualRewardsContainer) {
individualRewardsContainer.style.display = showIndividualRewards ? 'block' : 'none';
}
if (completionLengthContainer) {
completionLengthContainer.style.display = showCompletionLength ? 'block' : 'none';
}
// Return flags for use in chart creation
return {
showRewardChart,
showIndividualRewards,
showCompletionLength
};
}
function extractRewardFunctions(logHistory) {
const rewardKeys = new Set();
// Check first few entries to find all reward keys
const sampleSize = Math.min(10, logHistory.length);
for (let i = 0; i < sampleSize; i++) {
const entry = logHistory[i];
if (!entry) continue;
for (const key in entry) {
if (key.startsWith('rewards/') && key.endsWith('/mean')) {
rewardKeys.add(key);
}
}
}
return Array.from(rewardKeys);
}
// Extract reward function name from key like "rewards/selene/mean" -> "Selene"
function getRewardDisplayName(key) {
const name = key.replace('rewards/', '').replace('/mean', '');
// Capitalize first letter for display
return name.charAt(0).toUpperCase() + name.slice(1);
}
// Color palette for reward functions
function getRewardColor(index, total) {
const colors = [
'rgba(255, 99, 132, 0.8)', // Red
'rgba(54, 162, 235, 0.8)', // Blue
'rgba(255, 206, 86, 0.8)', // Yellow
'rgba(75, 192, 192, 0.8)', // Teal
'rgba(153, 102, 255, 0.8)', // Purple
'rgba(255, 159, 64, 0.8)', // Orange
'rgba(199, 199, 199, 0.8)', // Grey
'rgba(83, 102, 255, 0.8)', // Indigo
'rgba(255, 99, 255, 0.8)', // Pink
'rgba(99, 255, 132, 0.8)' // Green
];
return colors[index % colors.length];
}
function getRewardBackgroundColor(index, total) {
const color = getRewardColor(index, total);
// Convert border color to background color (lower opacity)
return color.replace('0.8)', '0.1)');
}
function createIndividualRewardsChart(logHistory, skipFade = false) {
const ctx = document.getElementById('individualRewardsChart').getContext('2d');
fadeInChart('individualRewardsChart', skipFade);
if (charts.individualRewards) {
charts.individualRewards.destroy();
}
// Dynamically detect reward functions
const rewardKeys = extractRewardFunctions(logHistory);
if (rewardKeys.length === 0) {
// No reward functions found, show message
ctx.fillStyle = '#999';
ctx.font = '16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('No reward functions found', ctx.canvas.width / 2, ctx.canvas.height / 2);
return;
}
// Use all log entries that have step information
const rewardData = logHistory.filter(entry => entry.step !== undefined);
if (rewardData.length === 0) {
ctx.fillStyle = '#999';
ctx.font = '16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('No step data found', ctx.canvas.width / 2, ctx.canvas.height / 2);
return;
}
// Create datasets for each reward function
const datasets = rewardKeys.map((rewardKey, index) => {
// Extract reward data for this function, keeping undefined as undefined (not null)
const rawRewardData = rewardData.map(entry => {
const value = entry[rewardKey];
return (value !== undefined && value !== null) ? value : undefined;
});
// Only apply smoothing to entries that have actual data for this reward
const validIndices = [];
const validValues = [];
rawRewardData.forEach((value, i) => {
if (value !== undefined) {
validIndices.push(i);
validValues.push(value);
}
});
let smoothedRewardData;
if (validValues.length > 0) {
const smoothedValidValues = exponentialMovingAverage(validValues);
// Map smoothed values back to full array
smoothedRewardData = new Array(rawRewardData.length);
validIndices.forEach((originalIndex, smoothedIndex) => {
smoothedRewardData[originalIndex] = smoothedValidValues[smoothedIndex];
});
} else {
smoothedRewardData = new Array(rawRewardData.length);
}
return {
label: getRewardDisplayName(rewardKey),
data: smoothedRewardData,
borderColor: getRewardColor(index, rewardKeys.length),
backgroundColor: getRewardBackgroundColor(index, rewardKeys.length),
borderWidth: 2,
pointRadius: 0,
pointHoverRadius: 3,
tension: 0.1,
spanGaps: false // This ensures gaps are shown where data is missing
};
});
charts.individualRewards = new Chart(ctx, {
type: 'line',
data: {
labels: rewardData.map(entry => entry.step),
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
color: 'rgba(255, 255, 255, 0.8)',
font: {
family: "'SF Mono', monospace",
size: 11
}
}
},
tooltip: {
mode: 'index',
intersect: false,
backgroundColor: 'rgba(0, 0, 0, 0.9)',
borderColor: 'rgba(0, 255, 255, 0.3)',
borderWidth: 1,
titleColor: '#00ffff',
bodyColor: 'rgba(255, 255, 255, 0.8)',
callbacks: {
label: function(context) {
const label = context.dataset.label || '';
const value = context.parsed.y;
if (value === null || value === undefined) {
return `${label}: N/A`;
}
return `${label}: ${value.toFixed(4)}`;
}
}
}
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'STEP',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
y: {
display: true,
title: {
display: true,
text: 'INDIVIDUAL REWARD',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
}
}
}
});
}
function createCompletionLengthChart(logHistory, skipFade = false) {
const ctx = document.getElementById('completionLengthChart').getContext('2d');
fadeInChart('completionLengthChart', skipFade);
if (charts.completionLength) {
charts.completionLength.destroy();
}
const completionData = logHistory.filter(entry => entry['completions/mean_length'] !== undefined);
const rawLengths = completionData.map(entry => entry['completions/mean_length']);
// Calculate smoothed data
const smoothedLengths = exponentialMovingAverage(rawLengths);
// Get Y-axis limits excluding outliers
const yLimits = getYAxisLimits(rawLengths, 1, 99);
charts.completionLength = new Chart(ctx, {
type: 'line',
data: {
labels: completionData.map(entry => entry.step),
datasets: [{
label: 'Completion Length (Raw)',
data: rawLengths,
borderColor: 'rgba(153, 102, 255, 0.2)',
backgroundColor: 'rgba(153, 102, 255, 0.05)',
borderWidth: 1,
pointRadius: 0,
pointHoverRadius: 3,
tension: 0.1
}, {
label: 'Completion Length (Smoothed)',
data: smoothedLengths,
borderColor: 'rgba(153, 102, 255, 0.8)',
backgroundColor: 'rgba(153, 102, 255, 0.1)',
borderWidth: 2,
pointRadius: 0,
pointHoverRadius: 4,
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
color: 'rgba(255, 255, 255, 0.8)',
font: {
family: "'SF Mono', monospace",
size: 11
}
}
},
tooltip: {
mode: 'index',
intersect: false,
backgroundColor: 'rgba(0, 0, 0, 0.9)',
borderColor: 'rgba(0, 255, 255, 0.3)',
borderWidth: 1,
titleColor: '#00ffff',
bodyColor: 'rgba(255, 255, 255, 0.8)',
callbacks: {
label: function(context) {
const label = context.dataset.label || '';
return `${label}: ${Math.round(context.parsed.y)} tokens`;
}
}
}
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'STEP',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
y: {
display: true,
title: {
display: true,
text: 'COMPLETION LENGTH',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
},
min: yLimits.min,
max: yLimits.max
}
}
}
});
}
function createLearningRateChart(logHistory, skipFade = false) {
const ctx = document.getElementById('lrChart').getContext('2d');
fadeInChart('lrChart', skipFade);
if (charts.lr) {
charts.lr.destroy();
}
const lrData = logHistory.filter(entry => entry.learning_rate !== undefined);
charts.lr = new Chart(ctx, {
type: 'line',
data: {
labels: lrData.map(entry => entry.step),
datasets: [{
label: 'Learning Rate',
data: lrData.map(entry => entry.learning_rate),
borderColor: 'rgba(255, 200, 0, 0.8)',
backgroundColor: 'rgba(255, 200, 0, 0.1)',
borderWidth: 2,
pointRadius: 0,
pointHoverRadius: 4,
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
color: 'rgba(255, 255, 255, 0.8)',
font: {
family: "'SF Mono', monospace",
size: 11
}
}
},
tooltip: {
mode: 'index',
intersect: false,
backgroundColor: 'rgba(0, 0, 0, 0.9)',
borderColor: 'rgba(0, 255, 255, 0.3)',
borderWidth: 1,
titleColor: '#00ffff',
bodyColor: 'rgba(255, 255, 255, 0.8)',
callbacks: {
label: function(context) {
const value = context.parsed.y;
// Format tooltip value nicely
if (value === 0) return 'LR: 0';
if (value >= 0.01) return `LR: ${value.toFixed(4)}`;
if (value >= 0.0001) return `LR: ${value.toFixed(6)}`;
return `LR: ${value.toExponential(2)}`;
}
}
}
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'STEP',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
y: {
display: true,
title: {
display: true,
text: 'LEARNING RATE',
color: 'rgba(255, 255, 255, 0.8)'
},
type: 'logarithmic',
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)',
callback: function(value, index, values) {
// Format the tick labels nicely
if (value === 0) return '0';
// For very small numbers, use concise exponential notation
if (value < 0.0001) {
// Convert to exponential and format nicely
const exp = value.toExponential(0);
// Replace 'e' with '×10^' for better readability, or keep it simple
return exp.replace('e', 'e');
}
// For larger numbers, use regular notation
if (value >= 0.01) {
return value.toFixed(2);
}
// For medium range, use appropriate decimal places
if (value >= 0.001) {
return value.toFixed(3);
}
return value.toFixed(4);
},
// Reduce the number of ticks for cleaner display
maxTicksLimit: 8,
// Ensure we show important values
autoSkip: true,
autoSkipPadding: 10
}
}
}
}
});
}
function createGradientNormChart(logHistory, skipFade = false) {
const ctx = document.getElementById('gradChart').getContext('2d');
fadeInChart('gradChart', skipFade);
if (charts.grad) {
charts.grad.destroy();
}
const gradData = logHistory.filter(entry => entry.grad_norm !== undefined);
const rawGradNorms = gradData.map(entry => entry.grad_norm);
// Calculate smoothed data with adaptive smoothing
const smoothedGradNorms = exponentialMovingAverage(rawGradNorms);
// Get Y-axis limits excluding outliers
const yLimits = getYAxisLimits(rawGradNorms, 1, 99);
charts.grad = new Chart(ctx, {
type: 'line',
data: {
labels: gradData.map(entry => entry.step),
datasets: [{
label: 'Gradient Norm (Raw)',
data: rawGradNorms,
borderColor: 'rgba(255, 0, 128, 0.2)',
backgroundColor: 'rgba(255, 0, 128, 0.05)',
borderWidth: 1,
pointRadius: 0,
pointHoverRadius: 3,
tension: 0.1
}, {
label: 'Gradient Norm (Smoothed)',
data: smoothedGradNorms,
borderColor: 'rgba(255, 0, 128, 0.8)',
backgroundColor: 'rgba(255, 0, 128, 0.1)',
borderWidth: 2,
pointRadius: 0,
pointHoverRadius: 4,
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
color: 'rgba(255, 255, 255, 0.8)',
font: {
family: "'SF Mono', monospace",
size: 11
}
}
},
tooltip: {
mode: 'index',
intersect: false,
backgroundColor: 'rgba(0, 0, 0, 0.9)',
borderColor: 'rgba(0, 255, 255, 0.3)',
borderWidth: 1,
titleColor: '#00ffff',
bodyColor: 'rgba(255, 255, 255, 0.8)',
callbacks: {
label: function(context) {
const label = context.dataset.label || '';
return `${label}: ${context.parsed.y.toFixed(4)}`;
}
}
}
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'STEP',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
y: {
display: true,
title: {
display: true,
text: 'GRADIENT NORM',
color: 'rgba(255, 255, 255, 0.8)'
},
grid: {
color: 'rgba(0, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
},
min: yLimits.min,
max: yLimits.max
}
}
}
});
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment