- Create ml/eval/elo_tracker.py:
- EloTracker class with standard ELO formula (K=32, start=1200)
- EloResult dataclass for tournament output
- EloHistory class for JSON persistence (data/elo_history.json)
- run_elo_tournament(): round-robin ELO-rated matches using
existing run_match() from tournament.py
- run_generation_tournament(): discovers gen-*/best_policy.pt
checkpoints and runs cross-generation ELO tournament
- CLI entry point: python -m ml.eval.elo_tracker
- Add ELO API endpoints to routes/ml.py:
- POST /api/ml/elo/tournament — start background ELO tournament
- GET /api/ml/elo/tournament/status — poll progress
- GET /api/ml/elo/history — load ELO history from disk
- Integrate ELO convergence in distillation_loop.py:
- New config fields: elo_convergence_enabled (default False),
elo_convergence_threshold (20.0), elo_tournament_episodes (30)
- New field: GenerationRecord.elo_rating
- _run_elo_mini_tournament() runs after quality gate pass
- _check_convergence() checks ELO delta when enabled
- Add ELO UI to distillation page:
- ELO Ratings panel with canvas multi-line chart showing per-model
ELO progression across generations
- Run Tournament button with progress indicator
- ELO leaderboard table with rank, rating, and visual bars
- Responsive chart resize handling
Closes #70
204 lines
10 KiB
HTML
204 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Commander AI Lab — Distillation Loop</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
<link rel="stylesheet" href="global.css" />
|
|
<link rel="stylesheet" href="distillation.css" />
|
|
</head>
|
|
<body>
|
|
<script src="nav.js"></script>
|
|
<div class="page">
|
|
<div class="page-header" style="display:flex;align-items:center;justify-content:space-between;">
|
|
<div>
|
|
<h1>Closed-Loop Distillation</h1>
|
|
<p>Iterative supervised → PPO → quality gate → merge pipeline</p>
|
|
</div>
|
|
<button id="btn-refresh" class="btn btn-secondary" onclick="refreshAll()" title="Refresh status">
|
|
<span class="btn-icon" id="refresh-icon">↻</span> Refresh
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Status Overview Cards -->
|
|
<div class="status-grid stat-grid">
|
|
<div class="status-card stat-card" id="card-status">
|
|
<div class="card-icon">🔄</div>
|
|
<div class="card-label stat-label">Status</div>
|
|
<div class="card-value stat-value" id="val-status">Idle</div>
|
|
<div class="card-sub stat-sub" id="sub-status">not running</div>
|
|
</div>
|
|
<div class="status-card stat-card" id="card-generation">
|
|
<div class="card-icon">📈</div>
|
|
<div class="card-label stat-label">Generation</div>
|
|
<div class="card-value stat-value" id="val-generation">--</div>
|
|
<div class="card-sub stat-sub" id="sub-generation">of --</div>
|
|
</div>
|
|
<div class="status-card stat-card" id="card-winrate">
|
|
<div class="card-icon">🏆</div>
|
|
<div class="card-label stat-label">Best Win Rate</div>
|
|
<div class="card-value stat-value" id="val-winrate">--</div>
|
|
<div class="card-sub stat-sub" id="sub-winrate">across all generations</div>
|
|
</div>
|
|
<div class="status-card stat-card" id="card-passed">
|
|
<div class="card-icon">✅</div>
|
|
<div class="card-label stat-label">Passed</div>
|
|
<div class="card-value stat-value" id="val-passed">--</div>
|
|
<div class="card-sub stat-sub" id="sub-passed">quality gate passes</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Distillation Controls Panel -->
|
|
<section class="panel" id="control-panel">
|
|
<div class="panel-header">
|
|
<h2>Distillation Controls</h2>
|
|
</div>
|
|
<div class="train-controls">
|
|
<div class="param-grid form-row">
|
|
<div class="form-group">
|
|
<label class="form-label" for="param-iterations">Max Generations</label>
|
|
<input type="number" id="param-iterations" class="form-input" value="10" min="1" max="100" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="param-conv-window">Convergence Window</label>
|
|
<input type="number" id="param-conv-window" class="form-input" value="3" min="2" max="10" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="param-sup-epochs">Supervised Epochs</label>
|
|
<input type="number" id="param-sup-epochs" class="form-input" value="30" min="5" max="200" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="param-sup-lr">Supervised LR</label>
|
|
<input type="number" id="param-sup-lr" class="form-input" value="0.001" step="0.0001" min="0.00001" max="0.1" />
|
|
</div>
|
|
</div>
|
|
<div class="param-grid form-row">
|
|
<div class="form-group">
|
|
<label class="form-label" for="param-ppo-iters">PPO Iterations</label>
|
|
<input type="number" id="param-ppo-iters" class="form-input" value="50" min="10" max="500" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="param-ppo-episodes">PPO Episodes/Iter</label>
|
|
<input type="number" id="param-ppo-episodes" class="form-input" value="64" min="8" max="512" step="8" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="param-ppo-lr">PPO Learning Rate</label>
|
|
<input type="number" id="param-ppo-lr" class="form-input" value="0.0003" step="0.0001" min="0.00001" max="0.01" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="param-min-wr">Min Win Rate Gate</label>
|
|
<input type="number" id="param-min-wr" class="form-input" value="0.30" step="0.05" min="0.1" max="0.9" />
|
|
</div>
|
|
</div>
|
|
<div class="param-grid form-row distill-bottom-params">
|
|
<div class="form-group">
|
|
<label class="form-label" for="param-opponent">Opponent</label>
|
|
<select id="param-opponent" class="form-select">
|
|
<option value="heuristic">Heuristic</option>
|
|
<option value="random">Random</option>
|
|
<option value="self">Self-Play</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="param-playstyle">Playstyle</label>
|
|
<select id="param-playstyle" class="form-select">
|
|
<option value="midrange">Midrange</option>
|
|
<option value="aggro">Aggro</option>
|
|
<option value="control">Control</option>
|
|
<option value="combo">Combo</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="train-actions distill-actions">
|
|
<button id="btn-start" class="btn btn-primary" onclick="startDistillation()">
|
|
<span class="btn-icon">▶</span> Start Distillation
|
|
</button>
|
|
<button id="btn-stop" class="btn btn-danger" onclick="stopDistillation()" disabled>
|
|
<span class="btn-icon">■</span> Stop
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Progress Section (hidden when idle) -->
|
|
<div id="distill-progress" class="train-progress hidden">
|
|
<div class="progress-header">
|
|
<span id="progress-phase" class="phase-badge">IDLE</span>
|
|
<span id="progress-message">Waiting...</span>
|
|
</div>
|
|
<div class="progress-bar-container progress-track">
|
|
<div class="progress-bar progress-fill" id="progress-bar"></div>
|
|
</div>
|
|
<div class="progress-detail" id="progress-detail"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Win Rate Trend Chart -->
|
|
<section class="panel" id="chart-panel">
|
|
<div class="panel-header">
|
|
<h2>Win Rate Trend</h2>
|
|
</div>
|
|
<div id="winrate-chart" class="chart-container">
|
|
<canvas id="winrate-canvas"></canvas>
|
|
<div id="winrate-empty" class="empty-state">
|
|
<p>No generation data yet. Start a distillation run to see win rate trends.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Data Composition Chart -->
|
|
<section class="panel" id="composition-panel">
|
|
<div class="panel-header">
|
|
<h2>Data Composition</h2>
|
|
</div>
|
|
<div id="composition-chart" class="chart-container">
|
|
<canvas id="composition-canvas"></canvas>
|
|
<div id="composition-empty" class="empty-state">
|
|
<p>No generation data yet.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ELO Rating Chart -->
|
|
<section class="panel" id="elo-panel">
|
|
<div class="panel-header elo-panel-header">
|
|
<h2>ELO Ratings</h2>
|
|
<button id="btn-run-elo" class="btn btn-sm btn-secondary" onclick="runEloTournament()">
|
|
<span class="btn-icon">🏆</span> Run Tournament
|
|
</button>
|
|
</div>
|
|
<div id="elo-chart" class="chart-container">
|
|
<canvas id="elo-canvas"></canvas>
|
|
<div id="elo-empty" class="empty-state">
|
|
<p>No ELO data yet. Run a tournament or enable ELO tracking in distillation settings.</p>
|
|
</div>
|
|
</div>
|
|
<div id="elo-progress" class="train-progress hidden">
|
|
<div class="progress-header">
|
|
<span id="elo-progress-phase" class="phase-badge running">RUNNING</span>
|
|
<span id="elo-progress-message">Running ELO tournament...</span>
|
|
</div>
|
|
<div class="progress-bar-container progress-track">
|
|
<div class="progress-bar progress-fill" id="elo-progress-bar" style="width:50%"></div>
|
|
</div>
|
|
</div>
|
|
<div id="elo-leaderboard" class="mt-4"></div>
|
|
</section>
|
|
|
|
<!-- Generation History Table -->
|
|
<section class="panel" id="history-panel">
|
|
<div class="panel-header">
|
|
<h2>Generation History</h2>
|
|
</div>
|
|
<div id="history-table-container">
|
|
<div class="empty-state" id="history-empty">No generations completed yet.</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<script src="distillation.js"></script>
|
|
</body>
|
|
</html>
|