Skip to content

Instantly share code, notes, and snippets.

@boatbomber
Last active September 12, 2025 16:46
Show Gist options
  • Select an option

  • Save boatbomber/4cd4aac61d8fac8ff87790e2fcef6ea0 to your computer and use it in GitHub Desktop.

Select an option

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>
/* ===== RESET & BASE STYLES ===== */
* {
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;
}
/* ===== LAYOUT COMPONENTS ===== */
.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;
}
/* ===== STATISTICS SECTION ===== */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 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);
}
.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 COMPONENTS ===== */
.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;
display: flex;
justify-content: space-between;
align-items: center;
}
.export-btn {
background: rgba(0, 255, 255, 0.1);
border: 1px solid rgba(0, 255, 255, 0.3);
color: #00ffff;
padding: 6px 12px;
border-radius: 2px;
cursor: pointer;
font-size: 0.7em;
font-weight: 300;
text-transform: uppercase;
letter-spacing: 1px;
transition: all 0.2s ease;
font-family: inherit;
}
.export-btn:hover {
background: rgba(0, 255, 255, 0.2);
border-color: #00ffff;
box-shadow: 0 0 10px rgba(0, 255, 255, 0.3);
transform: translateY(-1px);
}
.export-btn:active {
transform: translateY(0);
}
.chart-wrapper {
position: relative;
height: 400px;
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(0, 255, 255, 0.05);
padding: 1px;
border-radius: 2px;
}
/* ===== PROGRESS SECTION ===== */
.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;
}
/* ===== RESPONSIVE LAYOUTS ===== */
.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;
}
/* ===== LOADING & ERROR STATES ===== */
.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>
<!-- Training Insights Summary -->
<div
class="chart-container"
id="insightsContainer"
style="display: none"
>
<h2 class="chart-title">
<span>Training Insights</span>
</h2>
<div
id="insightsContent"
style="color: #e0e0e0; line-height: 1.6"
></div>
</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>
<div class="two-charts">
<!-- Overall Reward Chart -->
<div class="chart-container">
<h2 class="chart-title">
<span>Overall Reward</span>
<button
class="export-btn"
onclick="ChartManager.exportChartAsPNG('reward', 'overall-reward.png')"
>
Export PNG
</button>
</h2>
<div class="chart-wrapper">
<canvas id="rewardChart"></canvas>
</div>
</div>
<!-- Individual Reward Functions Chart -->
<div class="chart-container">
<h2 class="chart-title">
<span>Individual Reward Functions</span>
<button
class="export-btn"
onclick="ChartManager.exportChartAsPNG('individualRewards', 'individual-rewards.png')"
>
Export PNG
</button>
</h2>
<div class="chart-wrapper">
<canvas id="individualRewardsChart"></canvas>
</div>
</div>
</div>
<div class="two-charts">
<!-- Loss Chart -->
<div class="chart-container">
<h2 class="chart-title">
<span>Loss Trajectory</span>
<button
class="export-btn"
onclick="ChartManager.exportChartAsPNG('loss', 'loss-trajectory.png')"
>
Export PNG
</button>
</h2>
<div class="chart-wrapper">
<canvas id="lossChart"></canvas>
</div>
</div>
<!-- KL Divergence Chart -->
<div class="chart-container">
<h2 class="chart-title">
<span>KL Divergence</span>
<button
class="export-btn"
onclick="ChartManager.exportChartAsPNG('kl', 'kl-divergence.png')"
>
Export PNG
</button>
</h2>
<div class="chart-wrapper">
<canvas id="klChart"></canvas>
</div>
</div>
</div>
<!-- Learning Rate and Gradient Norm Charts -->
<div class="two-charts">
<div class="chart-container">
<h2 class="chart-title">
<span>Learning Rate Schedule</span>
<button
class="export-btn"
onclick="ChartManager.exportChartAsPNG('lr', 'learning-rate.png')"
>
Export PNG
</button>
</h2>
<div class="chart-wrapper">
<canvas id="lrChart"></canvas>
</div>
</div>
<div class="chart-container">
<h2 class="chart-title">
<span>Gradient Magnitude</span>
<button
class="export-btn"
onclick="ChartManager.exportChartAsPNG('grad', 'gradient-norm.png')"
>
Export PNG
</button>
</h2>
<div class="chart-wrapper">
<canvas id="gradChart"></canvas>
</div>
</div>
</div>
<!-- Completion Length Chart -->
<div class="chart-container">
<h2 class="chart-title">
<span>Completion Length</span>
<button
class="export-btn"
onclick="ChartManager.exportChartAsPNG('completionLength', 'completion-length.png')"
>
Export PNG
</button>
</h2>
<div class="chart-wrapper">
<canvas id="completionLengthChart"></canvas>
</div>
</div>
</div>
</div>
<script>
// ===== CHART.JS CONFIGURATION =====
Chart.defaults.color = "rgba(255, 255, 255, 0.8)";
Chart.defaults.borderColor = "rgba(0, 255, 255, 0.1)";
Chart.defaults.font.family = "'SF Mono', monospace";
Chart.defaults.font.size = 12;
// Plugin to add background color to charts
const backgroundColorPlugin = {
id: "backgroundColor",
beforeDraw: (chart) => {
const { ctx, width, height } = chart;
ctx.save();
ctx.fillStyle = "#0a0a0a"; // Dark background matching UI
ctx.fillRect(0, 0, width, height);
ctx.restore();
},
};
// Register the plugin
Chart.register(backgroundColorPlugin);
// ===== DATA PROCESSING MODULE =====
const DataProcessor = (function () {
"use strict";
// Convert smoothing level (0-1) to sigma value for Gaussian smoothing
function getSmoothingSigma(smoothingLevel, dataLength) {
const minSigma = 0.2; // Minimal smoothing
const maxSigma = Math.max(3, dataLength / 40); // Maximum smoothing
return minSigma + (maxSigma - minSigma) * smoothingLevel;
}
// Generate Gaussian kernel for smoothing
function generateGaussianKernel(sigma) {
const kernelSize = Math.ceil(sigma * 6) | 1; // Ensure odd size
const kernel = new Array(kernelSize);
const center = Math.floor(kernelSize / 2);
let sum = 0;
for (let i = 0; i < kernelSize; i++) {
const x = i - center;
kernel[i] = Math.exp(-(x * x) / (2 * sigma * sigma));
sum += kernel[i];
}
// Normalize kernel
for (let i = 0; i < kernelSize; i++) {
kernel[i] /= sum;
}
return kernel;
}
// Gaussian smoothing function
function gaussianSmooth(data, smoothingLevel = 0.5) {
if (!data || data.length === 0) return [];
if (data.length === 1) return data.slice();
const sigma = getSmoothingSigma(smoothingLevel, data.length);
const kernel = generateGaussianKernel(sigma);
const kernelRadius = Math.floor(kernel.length / 2);
const smoothed = new Array(data.length);
for (let i = 0; i < data.length; i++) {
let weightedSum = 0;
let totalWeight = 0;
for (let j = 0; j < kernel.length; j++) {
const dataIndex = i - kernelRadius + j;
if (dataIndex >= 0 && dataIndex < data.length) {
const value = data[dataIndex];
if (value !== null && value !== undefined && isFinite(value)) {
weightedSum += value * kernel[j];
totalWeight += kernel[j];
}
}
}
smoothed[i] = totalWeight > 0 ? weightedSum / totalWeight : data[i];
}
return smoothed;
}
// Calculate percentile for outlier handling
function calculatePercentile(arr, percentile) {
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);
const range = upper - lower;
const padding = range * 0.05;
return {
min: Math.max(0, lower - padding),
max: upper + padding,
};
}
// Get Y-axis limits based on smoothed data (ignores raw data outliers)
function getSmoothedDataLimits(smoothedData, padding = 0.05) {
const validData = smoothedData.filter(
(v) => v !== null && v !== undefined && isFinite(v)
);
if (validData.length === 0) return { min: undefined, max: undefined };
const min = Math.min(...validData);
const max = Math.max(...validData);
const range = max - min;
const paddingAmount = range * padding;
return {
min: min - paddingAmount,
max: max + paddingAmount,
};
}
return {
gaussianSmooth,
calculatePercentile,
getSmoothedDataLimits,
};
})();
// ===== APPLICATION STATE =====
const AppState = (function () {
"use strict";
let charts = {};
let currentData = null;
let smoothingLevel = 0.5;
let updateTimeout = null;
let isUpdating = false;
return {
getCharts: () => charts,
setChart: (name, chart) => (charts[name] = chart),
getCurrentData: () => currentData,
setCurrentData: (data) => (currentData = data),
getSmoothingLevel: () => smoothingLevel,
setSmoothingLevel: (level) => (smoothingLevel = level),
getUpdateTimeout: () => updateTimeout,
setUpdateTimeout: (timeout) => (updateTimeout = timeout),
isUpdating: () => isUpdating,
setUpdating: (updating) => (isUpdating = updating),
};
})();
// ===== UI CONTROLLER MODULE =====
const UIController = (function () {
"use strict";
function showError(message) {
const errorEl = document.getElementById("errorMessage");
errorEl.textContent = message;
errorEl.style.display = "block";
setTimeout(() => {
errorEl.style.display = "none";
}, 5000);
}
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++) {
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",
"klChart",
"lrChart",
"gradChart",
];
charts.forEach((chartId) => {
const canvas = document.getElementById(chartId);
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
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 calculateAdvancedStats(data) {
if (!data.log_history || data.log_history.length === 0) return {};
const logHistory = data.log_history;
const lossData = logHistory
.filter((entry) => entry.loss !== undefined)
.map((entry) => entry.loss);
const rewardData = logHistory
.filter((entry) => entry.reward !== undefined)
.map((entry) => entry.reward);
const gradData = logHistory
.filter((entry) => entry.grad_norm !== undefined)
.map((entry) => entry.grad_norm);
function calculateStats(arr) {
if (arr.length === 0) return null;
const sorted = [...arr].sort((a, b) => a - b);
const mean = arr.reduce((sum, val) => sum + val, 0) / arr.length;
const variance =
arr.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) /
arr.length;
const stdDev = Math.sqrt(variance);
return {
mean,
stdDev,
min: sorted[0],
max: sorted[sorted.length - 1],
median: sorted[Math.floor(sorted.length / 2)],
p25: sorted[Math.floor(sorted.length * 0.25)],
p75: sorted[Math.floor(sorted.length * 0.75)],
};
}
function calculateImprovement(arr) {
if (arr.length < 2) return null;
const windowSize = Math.min(10, Math.floor(arr.length / 4));
if (windowSize < 1) return null;
const start =
arr.slice(0, windowSize).reduce((sum, val) => sum + val, 0) /
windowSize;
const end =
arr.slice(-windowSize).reduce((sum, val) => sum + val, 0) /
windowSize;
if (Math.abs(start) < 1e-8) return null; // Avoid division by very small numbers
return ((end - start) / Math.abs(start)) * 100;
}
return {
loss: calculateStats(lossData),
reward: calculateStats(rewardData),
gradNorm: calculateStats(gradData),
lossImprovement: calculateImprovement(lossData),
rewardImprovement: calculateImprovement(rewardData),
};
}
function generateTrainingInsights(data, advancedStats) {
const insights = [];
// Training progress assessment (only if we have valid improvement data)
if (
advancedStats.lossImprovement !== null &&
isFinite(advancedStats.lossImprovement)
) {
const lossChange = advancedStats.lossImprovement;
if (lossChange < -10) {
insights.push(
`<span style="color: #00ff88;">✓ Excellent loss reduction (${Math.abs(
lossChange
).toFixed(1)}%)</span> - Model is learning effectively.`
);
} else if (lossChange < -2) {
insights.push(
`<span style="color: #88ff88;">✓ Good loss reduction (${Math.abs(
lossChange
).toFixed(1)}%)</span> - Training is progressing well.`
);
} else if (lossChange < 2) {
insights.push(
`<span style="color: #ffaa00;">⚠ Loss is relatively stable</span> - Consider adjusting learning rate or checking for convergence.`
);
} else {
insights.push(
`<span style="color: #ff6666;">⚠ Loss is increasing (${lossChange.toFixed(
1
)}%)</span> - May indicate learning rate too high or training instability.`
);
}
}
// Gradient norm assessment
if (advancedStats.gradNorm) {
const currentGradNorm =
data.log_history[data.log_history.length - 1]?.grad_norm;
if (currentGradNorm) {
if (currentGradNorm > 10) {
insights.push(
`<span style="color: #ff6666;">⚠ High gradient norm (${currentGradNorm.toFixed(
2
)})</span> - Consider gradient clipping to stabilize training.`
);
} else if (currentGradNorm < 0.001) {
insights.push(
`<span style="color: #ffaa00;">⚠ Very low gradient norm (${currentGradNorm.toFixed(
4
)})</span> - May indicate vanishing gradients or convergence.`
);
} else {
insights.push(
`<span style="color: #00ff88;">✓ Healthy gradient norm (${currentGradNorm.toFixed(
3
)})</span> - Gradients are in good range for stable learning.`
);
}
}
}
// Reward improvement assessment
if (
advancedStats.rewardImprovement !== null &&
isFinite(advancedStats.rewardImprovement)
) {
const rewardChange = advancedStats.rewardImprovement;
if (rewardChange > 10) {
insights.push(
`<span style="color: #00ff88;">✓ Excellent reward improvement (${rewardChange.toFixed(
1
)}%)</span> - Model performance is increasing significantly.`
);
} else if (rewardChange > 2) {
insights.push(
`<span style="color: #88ff88;">✓ Good reward improvement (${rewardChange.toFixed(
1
)}%)</span> - Model is improving steadily.`
);
} else if (rewardChange > -2) {
insights.push(
`<span style="color: #ffaa00;">⚠ Reward is relatively stable</span> - Model may be reaching plateau.`
);
}
}
// Training stability assessment (only if we have enough data points)
if (
advancedStats.loss &&
advancedStats.loss.stdDev &&
data.log_history.length > 20
) {
const cv =
advancedStats.loss.stdDev / Math.abs(advancedStats.loss.mean);
if (cv < 0.05) {
insights.push(
`<span style="color: #00ff88;">✓ Very stable training</span> - Consistent loss reduction with low variance.`
);
} else if (cv > 0.3) {
insights.push(
`<span style="color: #ffaa00;">⚠ Training shows some instability</span> - Loss variance is ${(
cv * 100
).toFixed(1)}%. Consider learning rate scheduling.`
);
}
}
// Training duration insight
if (data.log_history && data.log_history.length > 0) {
const totalEntries = data.log_history.length;
insights.push(
`<span style="color: #88aaff;">ℹ Training log contains ${totalEntries.toLocaleString()} data points</span> - ${
totalEntries > 1000
? "Rich dataset for analysis"
: "Consider longer training for better insights"
}.`
);
}
return insights;
}
function displayStats(data) {
const statsGrid = document.getElementById("statsGrid");
statsGrid.innerHTML = "";
const advancedStats = calculateAdvancedStats(data);
const stats = [];
// Only show Change metrics: Loss Change, Reward Change, Grad Norm Change, and Change for each reward function
if (data.log_history && data.log_history.length > 0) {
// Loss Change
if (
advancedStats.lossImprovement !== null &&
isFinite(advancedStats.lossImprovement)
) {
const improvement = advancedStats.lossImprovement;
const color =
improvement < 0
? 'style="color: #00ff88;"'
: 'style="color: #ff6666;"';
stats.push({
label: "Loss Change",
value: `<span ${color}>${improvement.toFixed(1)}%</span>`,
});
}
// Reward Change
if (
advancedStats.rewardImprovement !== null &&
isFinite(advancedStats.rewardImprovement)
) {
const improvement = advancedStats.rewardImprovement;
const color =
improvement > 0
? 'style="color: #00ff88;"'
: 'style="color: #ff6666;"';
stats.push({
label: "Reward Change",
value: `<span ${color}>${improvement.toFixed(1)}%</span>`,
});
}
// Grad Norm Change
if (advancedStats.gradNorm) {
const gradNormData = data.log_history
.filter((entry) => entry.grad_norm !== undefined)
.map((entry) => entry.grad_norm);
if (gradNormData.length > 10) {
const windowSize = Math.min(
10,
Math.floor(gradNormData.length / 4)
);
const start =
gradNormData
.slice(0, windowSize)
.reduce((sum, val) => sum + val, 0) / windowSize;
const end =
gradNormData
.slice(-windowSize)
.reduce((sum, val) => sum + val, 0) / windowSize;
if (Math.abs(start) > 1e-10) {
const gradNormChange =
((end - start) / Math.abs(start)) * 100;
const color =
Math.abs(gradNormChange) < 10
? 'style="color: #00ff88;"'
: 'style="color: #ffaa00;"';
stats.push({
label: "Grad Norm Change",
value: `<span ${color}>${gradNormChange.toFixed(
1
)}%</span>`,
});
}
}
}
// KL Change
const klData = data.log_history
.filter((entry) => entry.kl !== undefined)
.map((entry) => entry.kl);
if (klData.length > 10) {
const windowSize = Math.min(10, Math.floor(klData.length / 4));
const start =
klData.slice(0, windowSize).reduce((sum, val) => sum + val, 0) /
windowSize;
const end =
klData.slice(-windowSize).reduce((sum, val) => sum + val, 0) /
windowSize;
if (Math.abs(start) > 1e-10) {
const klChange = ((end - start) / Math.abs(start)) * 100;
const color =
Math.abs(klChange) < 20
? 'style="color: #00ff88;"'
: 'style="color: #ffaa00;"';
stats.push({
label: "KL Change",
value: `<span ${color}>${klChange.toFixed(1)}%</span>`,
});
}
}
}
// Individual reward function Change stats only
const rewardKeys = ChartManager.extractRewardFunctions(
data.log_history || []
);
rewardKeys.forEach((rewardKey) => {
const rewardName = ChartManager.getRewardDisplayName(rewardKey);
const rewardData = data.log_history
.filter((entry) => entry[rewardKey] !== undefined)
.map((entry) => entry[rewardKey]);
if (rewardData.length > 10) {
// Only show Change (improvement)
const windowSize = Math.min(
10,
Math.floor(rewardData.length / 4)
);
const start =
rewardData
.slice(0, windowSize)
.reduce((sum, val) => sum + val, 0) / windowSize;
const end =
rewardData
.slice(-windowSize)
.reduce((sum, val) => sum + val, 0) / windowSize;
if (Math.abs(start) > 1e-10) {
const rewardChange = ((end - start) / Math.abs(start)) * 100;
const color =
rewardChange > 0
? 'style="color: #00ff88;"'
: 'style="color: #ff6666;"';
stats.push({
label: `${rewardName} Change`,
value: `<span ${color}>${rewardChange.toFixed(1)}%</span>`,
});
}
}
});
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);
});
// Display insights
const insights = generateTrainingInsights(data, advancedStats);
if (insights.length > 0) {
const insightsContainer =
document.getElementById("insightsContainer");
const insightsContent = document.getElementById("insightsContent");
insightsContainer.style.display = "block";
insightsContent.innerHTML =
'<ul style="margin: 0; padding-left: 20px;">' +
insights
.map(
(insight) => `<li style="margin-bottom: 8px;">${insight}</li>`
)
.join("") +
"</ul>";
}
}
function displayProgress(data) {
const progress = (data.global_step / data.max_steps) * 100;
const progressBar = document.getElementById("progressBar");
progressBar.style.width = progress + "%";
progressBar.textContent = progress.toFixed(1) + "%";
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>`;
}
function updateSmoothingUI(smoothingLevel) {
const percentage = Math.round(smoothingLevel * 100);
document.getElementById(
"smoothingValue"
).textContent = `${percentage}%`;
const slider = document.getElementById("smoothingSlider");
slider.style.setProperty("--slider-fill", `${smoothingLevel * 100}%`);
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";
}
}
return {
showError,
showSkeletons,
displayStats,
displayProgress,
updateSmoothingUI,
};
})();
// ===== EVENT HANDLERS =====
document
.getElementById("fileInput")
.addEventListener("change", function (e) {
const file = e.target.files[0];
if (file) {
document.getElementById("fileName").textContent = file.name;
UIController.showSkeletons();
const reader = new FileReader();
reader.onload = function (e) {
try {
const data = JSON.parse(e.target.result);
setTimeout(() => Dashboard.displayDashboard(data), 10);
} catch (error) {
UIController.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) {
const smoothingLevel = parseFloat(e.target.value);
AppState.setSmoothingLevel(smoothingLevel);
UIController.updateSmoothingUI(smoothingLevel);
// Debounce the chart update
if (AppState.getUpdateTimeout()) {
clearTimeout(AppState.getUpdateTimeout());
}
const indicator = document.getElementById("updatingIndicator");
if (indicator) {
indicator.textContent = "PENDING...";
indicator.classList.add("active");
}
const timeout = setTimeout(() => {
if (AppState.getCurrentData() && !AppState.isUpdating()) {
if (indicator) indicator.textContent = "UPDATING...";
Dashboard.updateAllChartsAsync();
}
}, 300);
AppState.setUpdateTimeout(timeout);
});
smoothingSlider.style.setProperty("--slider-fill", "50%");
}
// ===== CHART MANAGER MODULE =====
const ChartManager = (function () {
"use strict";
// Common chart options
function getBaseChartOptions() {
return {
responsive: true,
maintainAspectRatio: false,
animation: false,
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,
grid: { color: "rgba(0, 255, 255, 0.1)", drawBorder: false },
ticks: { color: "rgba(255, 255, 255, 0.7)" },
},
},
layout: {
padding: 0,
},
// Enable the background color plugin
plugins: {
backgroundColor: true,
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)",
},
},
};
}
function createDataset(label, data, color, isSmoothed = false) {
const alpha = isSmoothed ? 0.8 : 0.2;
const bgAlpha = isSmoothed ? 0.1 : 0.05;
const width = isSmoothed ? 2 : 1;
return {
label,
data,
borderColor: color.replace("0.8)", `${alpha})`),
backgroundColor: color.replace("0.8)", `${bgAlpha})`),
borderWidth: width,
pointRadius: 0,
pointHoverRadius: isSmoothed ? 4 : 3,
tension: 0.1,
};
}
// Helper functions for data availability
function hasRewardData(logHistory) {
return logHistory.some(
(entry) => entry.reward !== undefined && entry.reward !== null
);
}
function hasIndividualRewardData(logHistory) {
return extractRewardFunctions(logHistory).length > 0;
}
function hasCompletionLengthData(logHistory) {
return logHistory.some(
(entry) =>
entry["completions/mean_length"] !== undefined &&
entry["completions/mean_length"] !== null
);
}
function hasKLData(logHistory) {
return logHistory.some(
(entry) => entry.kl !== undefined && entry.kl !== null
);
}
function extractRewardFunctions(logHistory) {
const rewardKeys = new Set();
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);
}
function getRewardDisplayName(key) {
const name = key.replace("rewards/", "").replace("/mean", "");
return name.charAt(0).toUpperCase() + name.slice(1);
}
function getRewardColor(index) {
const colors = [
"rgba(255, 99, 132, 0.8)",
"rgba(54, 162, 235, 0.8)",
"rgba(255, 206, 86, 0.8)",
"rgba(75, 192, 192, 0.8)",
"rgba(153, 102, 255, 0.8)",
"rgba(255, 159, 64, 0.8)",
"rgba(199, 199, 199, 0.8)",
"rgba(83, 102, 255, 0.8)",
"rgba(255, 99, 255, 0.8)",
"rgba(99, 255, 132, 0.8)",
];
return colors[index % colors.length];
}
function exportChartAsPNG(chartName, customFilename) {
const chart = AppState.getCharts()[chartName];
if (!chart) {
console.error(`Chart '${chartName}' not found`);
return;
}
try {
// Charts now have built-in background color, so we can export directly
const dataURL = chart.toBase64Image("image/png", 1.0);
// Create download link
const link = document.createElement("a");
link.download = customFilename || `${chartName}-chart.png`;
link.href = dataURL;
// Trigger download
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
console.error("Export failed:", error);
alert("Failed to export chart. Please try again.");
}
}
return {
getBaseChartOptions,
createDataset,
hasRewardData,
hasIndividualRewardData,
hasCompletionLengthData,
hasKLData,
extractRewardFunctions,
getRewardDisplayName,
getRewardColor,
exportChartAsPNG,
};
})();
// ===== MAIN DASHBOARD MODULE =====
const Dashboard = (function () {
"use strict";
async function displayDashboard(data) {
AppState.setCurrentData(data);
// Remove loading message
const loadingMsg = document.getElementById("loadingMessage");
if (loadingMsg) loadingMsg.remove();
// Show smoothing controls and set initial display
const smoothingLevel = AppState.getSmoothingLevel();
document.getElementById("smoothingControls").classList.add("active");
document.getElementById("smoothingSlider").value = smoothingLevel;
UIController.updateSmoothingUI(smoothingLevel);
// Display stats and progress
UIController.displayStats(data);
UIController.displayProgress(data);
// Configure chart visibility and create charts
configureChartVisibility(data);
requestAnimationFrame(() => {
createAllCharts(data.log_history);
// Force resize/redraw of all charts
setTimeout(() => {
Object.values(AppState.getCharts()).forEach((chart) => {
if (chart && typeof chart.resize === "function") {
chart.resize();
}
});
}, 100);
});
}
function createAllCharts(logHistory) {
createLossChart(logHistory);
createRewardChart(logHistory);
createIndividualRewardsChart(logHistory);
createCompletionLengthChart(logHistory);
createKLChart(logHistory);
createLearningRateChart(logHistory);
createGradientNormChart(logHistory);
}
async function updateAllChartsAsync() {
const currentData = AppState.getCurrentData();
if (!currentData || !currentData.log_history || AppState.isUpdating())
return;
AppState.setUpdating(true);
const indicator = document.getElementById("updatingIndicator");
if (indicator) indicator.classList.add("active");
const chartFlags = configureChartVisibility(currentData);
// Update charts with breaks for UI responsiveness
const charts = [
() => createLossChart(currentData.log_history, true),
() =>
chartFlags.showRewardChart &&
createRewardChart(currentData.log_history, true),
() =>
chartFlags.showIndividualRewards &&
createIndividualRewardsChart(currentData.log_history, true),
() =>
chartFlags.showCompletionLength &&
createCompletionLengthChart(currentData.log_history, true),
() =>
chartFlags.showKLChart &&
createKLChart(currentData.log_history, true),
() => createGradientNormChart(currentData.log_history, true),
() => createLearningRateChart(currentData.log_history, true),
];
for (const chartFn of charts) {
await new Promise((resolve) => {
requestAnimationFrame(() => {
chartFn();
resolve();
});
});
await new Promise((resolve) => setTimeout(resolve, 10));
}
if (indicator) {
setTimeout(() => indicator.classList.remove("active"), 200);
}
AppState.setUpdating(false);
}
return {
displayDashboard,
updateAllChartsAsync,
};
})();
// ===== CHART CREATION FUNCTIONS =====
function createLossChart(logHistory) {
const existingChart = AppState.getCharts().loss;
if (existingChart) existingChart.destroy();
const ctx = document.getElementById("lossChart").getContext("2d");
const lossData = logHistory.filter((entry) => entry.loss !== undefined);
const rawLosses = lossData.map((entry) => entry.loss);
const smoothedLosses = DataProcessor.gaussianSmooth(
rawLosses,
AppState.getSmoothingLevel()
);
const options = ChartManager.getBaseChartOptions();
options.scales.y.title = {
display: true,
text: "LOSS",
color: "rgba(255, 255, 255, 0.8)",
};
// Scale based on smoothed data to avoid raw data outliers
const yLimits = DataProcessor.getSmoothedDataLimits(smoothedLosses);
if (yLimits.min !== undefined && yLimits.max !== undefined) {
options.scales.y.min = yLimits.min;
options.scales.y.max = yLimits.max;
}
options.plugins.tooltip.callbacks = {
label: (context) =>
`${context.dataset.label}: ${context.parsed.y.toFixed(4)}`,
};
const chart = new Chart(ctx, {
type: "line",
data: {
labels: lossData.map((entry) => entry.step),
datasets: [
ChartManager.createDataset(
"Training Loss (Raw)",
rawLosses,
"rgba(0, 255, 255, 0.8)",
false
),
ChartManager.createDataset(
"Training Loss (Smoothed)",
smoothedLosses,
"rgba(0, 255, 255, 0.8)",
true
),
],
},
options,
});
AppState.setChart("loss", chart);
}
function createRewardChart(logHistory) {
const existingChart = AppState.getCharts().reward;
if (existingChart) existingChart.destroy();
const ctx = document.getElementById("rewardChart").getContext("2d");
const rewardData = logHistory.filter(
(entry) => entry.reward !== undefined
);
const rawRewards = rewardData.map((entry) => entry.reward);
const smoothedRewards = DataProcessor.gaussianSmooth(
rawRewards,
AppState.getSmoothingLevel()
);
const options = ChartManager.getBaseChartOptions();
options.scales.y.title = {
display: true,
text: "REWARD",
color: "rgba(255, 255, 255, 0.8)",
};
// Scale based on smoothed data to avoid raw data outliers
const yLimits = DataProcessor.getSmoothedDataLimits(smoothedRewards);
if (yLimits.min !== undefined && yLimits.max !== undefined) {
options.scales.y.min = yLimits.min;
options.scales.y.max = yLimits.max;
}
options.plugins.tooltip.callbacks = {
label: (context) =>
`${context.dataset.label}: ${context.parsed.y.toFixed(4)}`,
};
const chart = new Chart(ctx, {
type: "line",
data: {
labels: rewardData.map((entry) => entry.step),
datasets: [
ChartManager.createDataset(
"Overall Reward (Raw)",
rawRewards,
"rgba(0, 255, 128, 0.8)",
false
),
ChartManager.createDataset(
"Overall Reward (Smoothed)",
smoothedRewards,
"rgba(0, 255, 128, 0.8)",
true
),
],
},
options,
});
AppState.setChart("reward", chart);
}
// Function to show/hide chart containers based on data availability
function configureChartVisibility(data) {
const logHistory = data.log_history || [];
const showRewardChart = ChartManager.hasRewardData(logHistory);
const showIndividualRewards =
ChartManager.hasIndividualRewardData(logHistory);
const showCompletionLength =
ChartManager.hasCompletionLengthData(logHistory);
const showKLChart = ChartManager.hasKLData(logHistory);
// Show/hide chart containers
const containers = [
{ selector: "#rewardChart", show: showRewardChart },
{ selector: "#individualRewardsChart", show: showIndividualRewards },
{ selector: "#completionLengthChart", show: showCompletionLength },
{ selector: "#klChart", show: showKLChart },
];
containers.forEach(({ selector, show }) => {
const container = document
.querySelector(selector)
?.closest(".chart-container");
if (container) container.style.display = show ? "block" : "none";
});
return {
showRewardChart,
showIndividualRewards,
showCompletionLength,
showKLChart,
};
}
function createIndividualRewardsChart(logHistory) {
const existingChart = AppState.getCharts().individualRewards;
if (existingChart) existingChart.destroy();
const ctx = document
.getElementById("individualRewardsChart")
.getContext("2d");
const rewardKeys = ChartManager.extractRewardFunctions(logHistory);
if (rewardKeys.length === 0) {
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;
}
const rewardData = logHistory.filter((entry) => {
return rewardKeys.some((key) => entry[key] !== undefined);
});
if (rewardData.length === 0) {
ctx.fillStyle = "#999";
ctx.font = "16px sans-serif";
ctx.textAlign = "center";
ctx.fillText(
"No reward data found",
ctx.canvas.width / 2,
ctx.canvas.height / 2
);
return;
}
// Collect all smoothed data for scaling calculation
const allSmoothedData = [];
const datasets = rewardKeys.flatMap((rewardKey, index) => {
const rawRewardData = rewardData.map((entry) => entry[rewardKey]);
const smoothedRewardData = DataProcessor.gaussianSmooth(
rawRewardData,
AppState.getSmoothingLevel()
);
const color = ChartManager.getRewardColor(index);
// Add smoothed data to collection for scaling
allSmoothedData.push(
...smoothedRewardData.filter(
(v) => v !== null && v !== undefined && isFinite(v)
)
);
return [
ChartManager.createDataset(
ChartManager.getRewardDisplayName(rewardKey) + " (Raw)",
rawRewardData,
color,
false
),
ChartManager.createDataset(
ChartManager.getRewardDisplayName(rewardKey) + " (Smoothed)",
smoothedRewardData,
color,
true
),
];
});
const options = ChartManager.getBaseChartOptions();
options.scales.y.title = {
display: true,
text: "INDIVIDUAL REWARD",
color: "rgba(255, 255, 255, 0.8)",
};
// Scale based on all smoothed reward data to avoid raw data outliers
if (allSmoothedData.length > 0) {
const min = Math.min(...allSmoothedData);
const max = Math.max(...allSmoothedData);
const range = max - min;
const padding = range * 0.05;
options.scales.y.min = min - padding;
options.scales.y.max = max + padding;
}
options.plugins.tooltip.callbacks = {
label: (context) => {
const label = context.dataset.label || "";
const value = context.parsed.y;
return value === null || value === undefined
? `${label}: N/A`
: `${label}: ${value.toFixed(4)}`;
},
};
const chart = new Chart(ctx, {
type: "line",
data: {
labels: rewardData.map((entry) => entry.step),
datasets,
},
options,
});
AppState.setChart("individualRewards", chart);
}
function createCompletionLengthChart(logHistory) {
const existingChart = AppState.getCharts().completionLength;
if (existingChart) existingChart.destroy();
const ctx = document
.getElementById("completionLengthChart")
.getContext("2d");
const completionData = logHistory.filter(
(entry) => entry["completions/mean_length"] !== undefined
);
const rawLengths = completionData.map(
(entry) => entry["completions/mean_length"]
);
const smoothedLengths = DataProcessor.gaussianSmooth(
rawLengths,
AppState.getSmoothingLevel()
);
const options = ChartManager.getBaseChartOptions();
options.scales.y.title = {
display: true,
text: "COMPLETION LENGTH",
color: "rgba(255, 255, 255, 0.8)",
};
// Scale based on smoothed data to avoid raw data outliers
const yLimits = DataProcessor.getSmoothedDataLimits(smoothedLengths);
if (yLimits.min !== undefined && yLimits.max !== undefined) {
options.scales.y.min = yLimits.min;
options.scales.y.max = yLimits.max;
}
options.plugins.tooltip.callbacks = {
label: (context) =>
`${context.dataset.label}: ${Math.round(context.parsed.y)} tokens`,
};
const chart = new Chart(ctx, {
type: "line",
data: {
labels: completionData.map((entry) => entry.step),
datasets: [
ChartManager.createDataset(
"Completion Length (Raw)",
rawLengths,
"rgba(153, 102, 255, 0.8)",
false
),
ChartManager.createDataset(
"Completion Length (Smoothed)",
smoothedLengths,
"rgba(153, 102, 255, 0.8)",
true
),
],
},
options,
});
AppState.setChart("completionLength", chart);
}
function createKLChart(logHistory) {
const existingChart = AppState.getCharts().kl;
if (existingChart) existingChart.destroy();
const ctx = document.getElementById("klChart").getContext("2d");
const klData = logHistory.filter((entry) => entry.kl !== undefined);
const rawKLs = klData.map((entry) => entry.kl);
const smoothedKLs = DataProcessor.gaussianSmooth(
rawKLs,
AppState.getSmoothingLevel()
);
const options = ChartManager.getBaseChartOptions();
options.scales.y.title = {
display: true,
text: "KL DIVERGENCE",
color: "rgba(255, 255, 255, 0.8)",
};
// Scale based on smoothed data to avoid raw data outliers
const yLimits = DataProcessor.getSmoothedDataLimits(smoothedKLs);
if (yLimits.min !== undefined && yLimits.max !== undefined) {
options.scales.y.min = yLimits.min;
options.scales.y.max = yLimits.max;
}
options.plugins.tooltip.callbacks = {
label: (context) =>
`${context.dataset.label}: ${context.parsed.y.toFixed(4)}`,
};
const chart = new Chart(ctx, {
type: "line",
data: {
labels: klData.map((entry) => entry.step),
datasets: [
ChartManager.createDataset(
"KL Divergence (Raw)",
rawKLs,
"rgba(255, 165, 0, 0.8)",
false
),
ChartManager.createDataset(
"KL Divergence (Smoothed)",
smoothedKLs,
"rgba(255, 165, 0, 0.8)",
true
),
],
},
options,
});
AppState.setChart("kl", chart);
}
function createLearningRateChart(logHistory) {
const existingChart = AppState.getCharts().lr;
if (existingChart) existingChart.destroy();
const ctx = document.getElementById("lrChart").getContext("2d");
const lrData = logHistory.filter(
(entry) => entry.learning_rate !== undefined
);
const options = ChartManager.getBaseChartOptions();
options.scales.y.title = {
display: true,
text: "LEARNING RATE",
color: "rgba(255, 255, 255, 0.8)",
};
options.scales.y.type = "logarithmic";
options.scales.y.ticks = {
color: "rgba(255, 255, 255, 0.7)",
callback: function (value) {
if (value === 0) return "0";
if (value < 0.0001) return value.toExponential(0);
if (value >= 0.01) return value.toFixed(2);
if (value >= 0.001) return value.toFixed(3);
return value.toFixed(4);
},
maxTicksLimit: 8,
autoSkip: true,
autoSkipPadding: 10,
};
options.plugins.tooltip.callbacks = {
label: (context) => {
const value = context.parsed.y;
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)}`;
},
};
const chart = new Chart(ctx, {
type: "line",
data: {
labels: lrData.map((entry) => entry.step),
datasets: [
ChartManager.createDataset(
"Learning Rate",
lrData.map((entry) => entry.learning_rate),
"rgba(255, 200, 0, 0.8)",
true
),
],
},
options,
});
AppState.setChart("lr", chart);
}
function createGradientNormChart(logHistory) {
const existingChart = AppState.getCharts().grad;
if (existingChart) existingChart.destroy();
const ctx = document.getElementById("gradChart").getContext("2d");
const gradData = logHistory.filter(
(entry) => entry.grad_norm !== undefined
);
const rawGradNorms = gradData.map((entry) => entry.grad_norm);
const smoothedGradNorms = DataProcessor.gaussianSmooth(
rawGradNorms,
AppState.getSmoothingLevel()
);
const options = ChartManager.getBaseChartOptions();
options.scales.y.title = {
display: true,
text: "GRADIENT NORM",
color: "rgba(255, 255, 255, 0.8)",
};
// Scale based on smoothed data to avoid raw data outliers
const yLimits = DataProcessor.getSmoothedDataLimits(smoothedGradNorms);
if (yLimits.min !== undefined && yLimits.max !== undefined) {
options.scales.y.min = yLimits.min;
options.scales.y.max = yLimits.max;
}
options.plugins.tooltip.callbacks = {
label: (context) =>
`${context.dataset.label}: ${context.parsed.y.toFixed(4)}`,
};
const chart = new Chart(ctx, {
type: "line",
data: {
labels: gradData.map((entry) => entry.step),
datasets: [
ChartManager.createDataset(
"Gradient Norm (Raw)",
rawGradNorms,
"rgba(255, 0, 128, 0.8)",
false
),
ChartManager.createDataset(
"Gradient Norm (Smoothed)",
smoothedGradNorms,
"rgba(255, 0, 128, 0.8)",
true
),
],
},
options,
});
AppState.setChart("grad", chart);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment