Last active
August 31, 2025 18:18
-
-
Save boatbomber/4cd4aac61d8fac8ff87790e2fcef6ea0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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