File size: 25,949 Bytes
a15e50d 94a9bf8 112a4cb a15e50d 38d12b7 979c011 38d12b7 979c011 a15e50d 112a4cb a15e50d 38d12b7 979c011 a15e50d 38d12b7 a15e50d 7faa636 979c011 7faa636 a15e50d 94a9bf8 38d12b7 b60c4c8 a15e50d 112a4cb a15e50d 38d12b7 a15e50d 38d12b7 a15e50d 38d12b7 7faa636 38d12b7 7faa636 38d12b7 b60c4c8 38d12b7 b60c4c8 38d12b7 a15e50d 38d12b7 a15e50d b60c4c8 38d12b7 94a9bf8 7faa636 94a9bf8 7faa636 94a9bf8 a15e50d 94a9bf8 a15e50d 94a9bf8 b60c4c8 94a9bf8 979c011 a15e50d b60c4c8 7faa636 b60c4c8 7faa636 b60c4c8 979c011 7faa636 979c011 a15e50d 94a9bf8 a15e50d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | (() => {
const $ = (selector) => document.querySelector(selector);
const runRoot = $("#view-train");
const checkpointRoot = $("#view-train-checkpoints");
if (!runRoot || !checkpointRoot) return;
let poll = null;
let checkpoints = [];
let activeTab = "run";
let latestStatus = {};
let customOutputName = false;
const presets = [
["compression", "Compression"],
["balanced", "Balanced"],
["quality", "Quality"],
["high_quality", "High quality"],
];
const esc = (value) => String(value ?? "").replace(/[&<>\"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", "\"": """ }[c]));
const api = async (path, options) => {
const response = await fetch(path, options);
const data = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`);
return data;
};
const metricColor = (value, baseline, observed) => {
if (value == null || baseline == null) return "";
const values = observed.filter((item) => Number.isFinite(Number(item)));
const best = Math.min(Number(baseline), ...(values.length ? values : [Number(baseline)]));
const worst = Math.max(Number(baseline), ...(values.length ? values : [Number(baseline)]));
const current = Number(value);
if (current <= Number(baseline)) {
const span = Number(baseline) - best;
const amount = span > 0 ? Math.min(1, (Number(baseline) - current) / span) : 0;
return `rgb(${Math.round(250 - 198 * amount)}, ${Math.round(204 + 31 * amount)}, ${Math.round(21 + 79 * amount)})`;
}
const span = worst - Number(baseline);
const amount = span > 0 ? Math.min(1, (current - Number(baseline)) / span) : 0;
return `rgb(${Math.round(250 - 11 * amount)}, ${Math.round(204 - 136 * amount)}, ${Math.round(21 + 47 * amount)})`;
};
const metricValue = (metric, value, baseline, observed, digits) => {
const color = metricColor(value, baseline, observed);
return color ? `<span style="color:${color}" title="Baseline ${fmt(baseline, digits)}">${fmt(value, digits)}</span>` : fmt(value, digits);
};
const download = (path) => window.open(`/api/train/download_checkpoint?path=${encodeURIComponent(path)}`, "_blank");
const fmt = (value, digits = 4) => value == null ? "—" : Number(value).toFixed(digits);
function showLab() {
["landing", "app", "sweep-lab"].forEach((id) => { const el = $(`#${id}`); if (el) el.hidden = true; });
$("#train-lab").hidden = false;
renderRun();
loadCheckpoints();
refresh();
}
function showLanding() {
$("#train-lab").hidden = true;
$("#landing").hidden = false;
if (poll) clearInterval(poll);
poll = null;
}
$("#enter-train")?.addEventListener("click", showLab);
$("#train-back")?.addEventListener("click", showLanding);
document.querySelectorAll("[data-tr-tab]").forEach((tab) => tab.addEventListener("click", () => {
activeTab = tab.dataset.trTab;
document.querySelectorAll("[data-tr-tab]").forEach((item) => item.classList.toggle("active", item.dataset.trTab === activeTab));
runRoot.hidden = activeTab !== "run";
checkpointRoot.hidden = activeTab !== "checkpoints";
if (activeTab === "checkpoints") renderCheckpoints();
}));
function renderRun() {
customOutputName = false;
runRoot.innerHTML = `
<div class="params">
<div class="params-head"><span class="field-label">Long-horizon RL training</span><span class="card-date">hpt_data → hpt_data_val</span></div>
<p class="card-date">Runs the current filler trainer in a resumable subprocess. Start the same output again to continue from its optimizer and RNG checkpoint.</p>
<div class="tr-fields">
<div class="tr-f full"><span>Preset policies</span><div class="tr-presets">${presets.map(([id, label], i) => `<label><input type="checkbox" data-tr-preset="${id}" ${i === 3 ? "checked" : ""}> ${label}</label>`).join("")}</div></div>
<label class="tr-f"><span>Additional epochs</span><input id="tr-epochs" type="number" min="1" step="1" value="100"></label>
<label class="tr-f"><span>Batch size</span><input id="tr-batch" type="number" min="1" step="1" value="4"></label>
<label class="tr-f"><span>Initial model</span><select id="tr-init"></select><input id="tr-upload" type="file" accept=".npz"></label>
<label class="tr-f"><span>Output name</span><input id="tr-output" value="rl_high_quality_top2.npz"></label>
<label class="tr-f"><span>Rate weight</span><input id="tr-rate" type="number" step="0.05" value="1.5"></label>
<label class="tr-f"><span>Speed weight</span><input id="tr-speed" type="number" step="0.05" value="0.15"></label>
<label class="tr-f"><span>Temperature</span><input id="tr-temp" type="number" step="0.05" value="0.9"></label>
<label class="tr-f"><span>Entropy weight</span><input id="tr-entropy" type="number" step="0.0005" value="0.003"></label>
<label class="tr-f"><span>KL weight</span><input id="tr-kl" type="number" step="0.001" value="0.015"></label>
</div>
<div class="tr-actions"><button id="tr-start" class="primary-btn">Start / resume</button><button id="tr-stop" class="hold-btn">Stop</button><button id="tr-refresh" class="hold-btn">Refresh</button><button id="tr-log" class="hold-btn">Download log</button><button id="tr-copy-results" class="hold-btn">Copy results</button><button id="tr-download-results" class="hold-btn">Download results</button><label class="tr-check tr-live"><input id="tr-pause" type="checkbox"> Pause live table/log updates</label><label class="tr-check"><input id="tr-resume" type="checkbox" checked> Resume existing output state</label></div>
</div>
<div class="params"><div class="params-head"><span class="field-label">Run status</span></div><div id="tr-status" class="tr-ck">Loading…</div><pre id="tr-log-tail" class="tr-log-tail"></pre></div>
<div class="params"><div class="params-head"><span class="field-label">Learning graphics</span></div><div class="tr-chart-grid"><div id="tr-chart-reward"></div><div id="tr-chart-metrics"></div><div id="tr-chart-work"></div><div id="tr-chart-train"></div><div id="tr-chart-speed"></div><div id="tr-chart-rd" class="tr-chart-wide"></div></div></div>
<div class="params"><div class="params-head"><span class="field-label">Best checkpoints</span></div><div id="tr-best-checkpoints"></div></div>
<div class="params"><div class="params-head"><span class="field-label">Validation history</span><label class="tr-sort">Sort by <select id="tr-sort"><option value="epoch">Epoch</option><option value="reward">Per-image reward</option><option value="aggregate_score">Aggregate score</option><option value="mse">Validation MSE</option><option value="bpp">Validation bpp</option><option value="work">Validation grid coefficients</option><option value="encode_seconds">Validation encode seconds</option><option value="train_reward">Training reward</option><option value="train_mse">Training MSE</option><option value="train_bpp">Training bpp</option><option value="train_work">Training grid coefficients</option><option value="train_encode_seconds">Training encode seconds</option></select><button id="tr-sort-dir" class="hold-btn sm">↓</button></label></div><div id="tr-history"></div></div>`;
$("#tr-start").onclick = start;
$("#tr-stop").onclick = async () => { await api("/api/train/stop", { method: "POST" }); refresh(); };
$("#tr-refresh").onclick = refresh;
$("#tr-log").onclick = () => window.open("/api/train/log", "_blank");
$("#tr-copy-results").onclick = copyResults;
$("#tr-download-results").onclick = downloadResults;
$("#tr-upload").onchange = uploadCheckpoint;
$("#tr-sort").onchange = () => renderHistory(latestStatus.history || []);
$("#tr-sort-dir").onclick = () => {
const button = $("#tr-sort-dir");
button.dataset.direction = button.dataset.direction === "1" ? "-1" : "1";
button.textContent = button.dataset.direction === "1" ? "↑" : "↓";
renderHistory(latestStatus.history || []);
};
fillInitialModels();
const output = $("#tr-output");
output.oninput = () => { customOutputName = true; };
const updateOutputName = () => {
if (!customOutputName) output.value = `rl_${selectedPresets().join("_") || "training"}_top2.npz`;
};
document.querySelectorAll("[data-tr-preset]").forEach((input) => { input.onchange = updateOutputName; });
updateOutputName();
}
async function loadCheckpoints() {
const data = await api("/api/train/checkpoints").catch(() => ({ checkpoints: [] }));
checkpoints = data.checkpoints || [];
fillInitialModels();
if (activeTab === "checkpoints") renderCheckpoints();
}
function fillInitialModels() {
const select = $("#tr-init");
if (!select) return;
const old = select.value;
select.innerHTML = checkpoints.map((c) => `<option value="${esc(c.path)}">${esc(c.name)}</option>`).join("");
if (old && checkpoints.some((c) => c.path === old)) select.value = old;
else if (checkpoints.length) select.value = checkpoints.find((c) => c.name.includes("f26_a20_h512"))?.path || checkpoints[0].path;
}
function selectedPresets() {
return [...document.querySelectorAll("[data-tr-preset]:checked")].map((el) => el.dataset.trPreset);
}
async function start() {
const selected = selectedPresets();
if (!selected.length) return;
const body = {
presets: selected.join(","), epochs: Number($("#tr-epochs").value), batch: Number($("#tr-batch").value),
init: $("#tr-init").value, output: $("#tr-output").value,
resume: $("#tr-resume").checked,
rate_weight: Number($("#tr-rate").value), speed_weight: Number($("#tr-speed").value),
temperature: Number($("#tr-temp").value), entropy_weight: Number($("#tr-entropy").value), kl_weight: Number($("#tr-kl").value),
};
try { await api("/api/train/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); refresh(); } catch (error) { $("#tr-status").textContent = error.message; }
}
async function uploadCheckpoint() {
const input = $("#tr-upload");
const file = input.files?.[0];
if (!file) return;
const form = new FormData();
form.append("file", file);
try {
const data = await api("/api/train/upload_checkpoint", { method: "POST", body: form });
await loadCheckpoints();
$("#tr-init").value = data.path;
$("#tr-resume").checked = false;
const stem = file.name.replace(/\\.npz$/i, "").replace(/[^A-Za-z0-9_-]+/g, "_");
$("#tr-output").value = "rl_" + (selectedPresets().join("_") || "training") + "_from_" + stem + "_top2.npz";
customOutputName = true;
$("#tr-status").textContent = "Uploaded " + data.name + ". Resume is disabled so training starts from this checkpoint.";
} catch (error) {
$("#tr-status").textContent = error.message;
} finally {
input.value = "";
}
}
function renderHistory(history) {
const root = $("#tr-history");
if (!root) return;
const newBaseline = (history.find((row) => row.validation)?.validation || history.find((row) => row.baseline_mse != null) || {});
const newTrainBaseline = history.find((row) => row.train_baseline_mse != null) || {};
const newObserved = (field, training = false) => history.map((row) => training ? row[field] : (row.validation || row)[field]);
const newSorted = history.filter((row) => row.epoch || row.validation).slice();
const newKey = $("#tr-sort")?.value || "epoch";
const newDirection = Number($("#tr-sort-dir")?.dataset.direction || (newKey === "epoch" ? "1" : "-1"));
newSorted.sort((a, b) => {
const value = (row) => newKey === "epoch" ? (row.epoch ?? -1) : (newKey.startsWith("train_") ? row : (row.validation || row))[newKey];
return (Number(value(a) ?? -Infinity) - Number(value(b) ?? -Infinity)) * newDirection;
});
const newRows = newSorted.map((row) => {
const v = row.validation || row;
return `<tr><td>${row.epoch ? `Epoch ${row.epoch}` : "Initial"}</td><td>${fmt(row.train_reward, 5)}</td><td>${fmt(row.reward ?? v.reward, 5)}</td><td>${fmt(v.aggregate_score, 5)}</td><td>${metricValue("mse", row.train_mse, row.train_baseline_mse, newObserved("train_mse", true), 3)}</td><td>${metricValue("mse", v.mse, v.baseline_mse, newObserved("mse"), 3)}</td><td>${metricValue("bpp", row.train_bpp, row.train_baseline_bpp, newObserved("train_bpp", true), 6)}</td><td>${metricValue("bpp", v.bpp, v.baseline_bpp, newObserved("bpp"), 6)}</td><td>${metricValue("work", row.train_work, row.train_baseline_work, newObserved("train_work", true), 0)}</td><td>${metricValue("work", v.work, v.baseline_work, newObserved("work"), 0)}</td><td>${fmt(row.train_encode_seconds, 3)}</td><td>${fmt(v.encode_seconds, 3)}</td></tr>`;
});
if (newRows.length) {
root.innerHTML = `<div class="tr-baseline-line">Training reference: MSE ${fmt(newTrainBaseline.train_baseline_mse, 3)} · bpp ${fmt(newTrainBaseline.train_baseline_bpp, 6)} · grid coefficients ${fmt(newTrainBaseline.train_baseline_work, 0)}. Validation reference: MSE ${fmt(newBaseline.baseline_mse, 3)} · bpp ${fmt(newBaseline.baseline_bpp, 6)}. Speed is measured rollout encode time; colors fade from green (best observed) through yellow (reference) to red (worst observed).</div><div class="tr-table-wrap"><table class="tr-table"><thead><tr><th>Checkpoint</th><th>Train reward</th><th>Val reward</th><th>Aggregate score</th><th>Train MSE</th><th>Val MSE</th><th>Train bpp</th><th>Val bpp</th><th>Train grid coefficients</th><th>Val grid coefficients</th><th>Train encode s</th><th>Val encode s</th></tr></thead><tbody>${newRows.join("")}</tbody></table></div>`;
} else {
root.innerHTML = `<p class="card-date">No validation rows yet. The first baseline pass can take a few minutes.</p>`;
}
return;
const legacyKey = $("#tr-sort")?.value || "epoch";
const legacyDirection = Number($("#tr-sort-dir")?.dataset.direction || (legacyKey === "epoch" ? "1" : "-1"));
const legacySorted = history.filter((row) => row.epoch || row.validation).slice().sort((a, b) => {
const value = (row) => legacyKey === "epoch" ? (row.epoch ?? -1) : (legacyKey.startsWith("train_") ? row : (row.validation || row))[legacyKey];
const av = value(a);
const bv = value(b);
return (Number(av ?? -Infinity) - Number(bv ?? -Infinity)) * legacyDirection;
});
const observed = (field, training = false) => sorted.map((row) => training ? row[field] : (row.validation || row)[field]);
const valMse = observed("mse");
const valBpp = observed("bpp");
const valWork = observed("work");
const trainMse = observed("train_mse", true);
const trainBpp = observed("train_bpp", true);
const trainWork = observed("train_work", true);
const legacyRows = legacySorted.map((row) => {
const v = row.validation || row;
return `<tr><td>${row.epoch ? `Epoch ${row.epoch}` : "Initial"}</td><td>${fmt(row.train_reward, 5)}</td><td>${fmt(row.reward ?? v.reward, 5)}</td><td>${fmt(v.aggregate_score, 5)}</td><td>${metricValue("mse", row.train_mse, row.train_baseline_mse, trainMse, 3)}</td><td>${metricValue("mse", v.mse, v.baseline_mse, valMse, 3)}</td><td>${metricValue("bpp", row.train_bpp, row.train_baseline_bpp, trainBpp, 6)}</td><td>${metricValue("bpp", v.bpp, v.baseline_bpp, valBpp, 6)}</td><td>${metricValue("work", row.train_work, row.train_baseline_work, trainWork, 0)}</td><td>${metricValue("work", v.work, v.baseline_work, valWork, 0)}</td></tr>`;
});
const baseline = (history.find((r) => r.validation)?.validation || {});
root.innerHTML = legacyRows.length ? `<div class="tr-baseline-line">PBC3.0 baseline: MSE ${fmt(baseline.baseline_mse, 3)} · bpp ${fmt(baseline.baseline_bpp, 6)}. <span class="tr-better">Green = better</span> · <span class="tr-close">yellow = close</span> · <span class="tr-worse">red = worse</span>. Work uses the starting policy as its reference.</div><div class="tr-table-wrap"><table class="tr-table"><thead><tr><th>Checkpoint</th><th>Train reward</th><th>Val reward</th><th>Aggregate score</th><th>Train MSE</th><th>Val MSE</th><th>Train bpp</th><th>Val bpp</th><th>Train work</th><th>Val work</th></tr></thead><tbody>${legacyRows.join("")}</tbody></table></div>` : `<p class="card-date">No validation rows yet. The first baseline pass can take a few minutes.</p>`;
}
function resultText() {
const history = latestStatus.history || [];
return history.map((row) => {
const v = row.validation || row;
return [row.epoch ? `Epoch ${row.epoch}` : "Initial", fmt(row.train_reward, 5), fmt(row.reward ?? v.reward, 5), fmt(v.aggregate_score, 5), fmt(row.train_mse, 3), fmt(v.mse, 3), fmt(row.train_bpp, 6), fmt(v.bpp, 6), fmt(row.train_work, 0), fmt(v.work, 0), fmt(row.train_encode_seconds, 3), fmt(v.encode_seconds, 3)].join("\t");
}).join("\n");
}
async function copyResults() {
const text = ["Checkpoint\tTrain reward\tVal reward\tAggregate score\tTrain MSE\tVal MSE\tTrain bpp\tVal bpp\tTrain grid coefficients\tVal grid coefficients\tTrain encode s\tVal encode s", resultText()].filter(Boolean).join("\n");
try { await navigator.clipboard.writeText(text); $("#tr-pause").checked = true; } catch { window.prompt("Copy training results", text); }
}
function downloadResults() {
const blob = new Blob([JSON.stringify(latestStatus, null, 2)], { type: "application/json" });
const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = "pbc_training_results.json"; link.click(); URL.revokeObjectURL(link.href);
}
async function refresh() {
const data = await api("/api/train/status").catch((error) => ({ running: false, log_tail: error.message, history: [] }));
latestStatus = data;
const status = $("#tr-status");
if (!status) return;
const state = data.running ? "Running" : data.return_code == null ? "Idle" : data.return_code === 0 ? "Finished" : `Stopped / failed (${data.return_code})`;
const output = data.output ? `<code>${esc(data.output)}</code>` : "No output selected";
status.innerHTML = `<b>${state}</b> · ${output}${data.spec?.presets ? ` · ${esc(data.spec.presets)}` : ""}`;
if (!$("#tr-pause")?.checked) {
$("#tr-log-tail").textContent = data.log_tail || "";
renderHistory(data.history || []);
drawCharts(data.history || []);
}
renderBestCheckpoints(data.checkpoint_groups || {});
if (data.running && !poll) poll = setInterval(refresh, 3000);
if (!data.running && poll) { clearInterval(poll); poll = null; loadCheckpoints(); }
}
function chartLayout(title, yTitle, extra = {}) {
return Object.assign({ title, height: 290, margin: { l: 52, r: 48, t: 38, b: 42 }, paper_bgcolor: "rgba(0,0,0,0)", plot_bgcolor: "rgba(0,0,0,0)", font: { color: "#cfd3dc", size: 11 }, xaxis: { title: "Epoch", gridcolor: "rgba(255,255,255,.07)" }, yaxis: { title: yTitle, gridcolor: "rgba(255,255,255,.07)" }, legend: { orientation: "h" } }, extra);
}
function drawCharts(history) {
if (!window.Plotly) return;
const points = history.filter((row) => row.epoch || row.validation);
if (!points.length) return;
const x = points.map((row) => row.epoch ?? 0);
const values = points.map((row) => row.validation || row);
const color = points.map((_, i) => i);
Plotly.react($("#tr-chart-reward"), [
{ x, y: points.map((row) => row.train_reward), name: "train reward", mode: "lines", line: { color: "#60a5fa" } },
{ x, y: points.map((row) => row.reward ?? (row.validation || {}).reward), name: "validation reward", mode: "lines", line: { color: "#f59e0b" } },
{ x, y: values.map((row) => row.aggregate_score), name: "aggregate score", mode: "lines", line: { color: "#22c55e" } },
], chartLayout("Reward and aggregate score", "score"), { responsive: true, displayModeBar: false });
Plotly.react($("#tr-chart-metrics"), [
{ x, y: points.map((row) => row.train_mse), name: "train MSE", mode: "lines", line: { color: "#93c5fd" } },
{ x, y: values.map((row) => row.mse), name: "val MSE", mode: "lines", line: { color: "#ef4444" } },
{ x, y: values.map((row) => row.baseline_mse), name: "PBC3.0 MSE", mode: "lines", line: { color: "#fca5a5", dash: "dot" } },
], chartLayout("MSE", "MSE"), { responsive: true, displayModeBar: false });
Plotly.react($("#tr-chart-work"), [
{ x, y: points.map((row) => row.train_work), name: "train grid coefficients", mode: "lines", line: { color: "#a78bfa" } },
{ x, y: values.map((row) => row.work), name: "val grid coefficients", mode: "lines", line: { color: "#c084fc" } },
], chartLayout("Grid coefficients", "coefficients"), { responsive: true, displayModeBar: false });
Plotly.react($("#tr-chart-train"), [
{ x, y: points.map((row) => row.train_bpp), name: "train bpp", mode: "lines", line: { color: "#93c5fd" } },
{ x, y: values.map((row) => row.bpp), name: "val bpp", mode: "lines", line: { color: "#38bdf8" } },
{ x, y: values.map((row) => row.baseline_bpp), name: "PBC3.0 bpp", mode: "lines", line: { color: "#7dd3fc", dash: "dot" } },
], chartLayout("Bitrate", "bits / pixel"), { responsive: true, displayModeBar: false });
Plotly.react($("#tr-chart-speed"), [
{ x, y: points.map((row) => row.train_encode_seconds), name: "train encode seconds", mode: "lines", line: { color: "#f59e0b" } },
{ x, y: values.map((row) => row.encode_seconds), name: "val encode seconds", mode: "lines", line: { color: "#ef4444" } },
{ x, y: values.map((row) => row.baseline_speed_seconds), name: "reference seconds", mode: "lines", line: { color: "#fca5a5", dash: "dot" } },
], chartLayout("Encode speed", "seconds"), { responsive: true, displayModeBar: false });
const baseline = values[0];
Plotly.react($("#tr-chart-rd"), [
{ x: values.map((row) => row.bpp), y: values.map((row) => row.mse), text: x.map((epoch) => `Epoch ${epoch}`), mode: "markers", name: "validation RD", marker: { size: 9, color, colorscale: "YlOrRd", showscale: true, colorbar: { title: "newer" } } },
{ x: [baseline.baseline_bpp], y: [baseline.baseline_mse], text: ["PBC3.0 baseline"], mode: "markers+text", name: "PBC3.0 baseline", textposition: "top center", marker: { size: 13, color: "#fff", symbol: "diamond", line: { color: "#ef4444", width: 2 } } },
], chartLayout("Rate–distortion history", "MSE", { xaxis: { title: "bpp", gridcolor: "rgba(255,255,255,.07)" } }), { responsive: true, displayModeBar: false });
}
function renderBestCheckpoints(groups) {
const root = $("#tr-best-checkpoints");
if (!root) return;
const normalized = groups.validation || groups.training ? groups : { validation: groups, training: {} };
const labels = { mse: "Best MSE checkpoints", bpp: "Best bpp checkpoints", work: "Best grid-coefficient checkpoints", rd: "Best RD checkpoints", overall: "Best overall checkpoint" };
root.innerHTML = ["validation", "training"].map((split) => {
const splitGroups = normalized[split] || {};
const allEntries = Object.values(splitGroups).flat();
const observed = (metric) => allEntries.map((entry) => entry[metric]);
const content = Object.entries(labels).map(([metric, label]) => {
const entries = (splitGroups[metric] || []).filter((entry, index, all) => index === all.findIndex((other) => other.mse === entry.mse && other.bpp === entry.bpp && other.work === entry.work));
const rows = entries.map((entry) => `<div class="tr-best-row"><span><b>Epoch ${entry.epoch}</b> · MSE ${metricValue("mse", entry.mse, entry.baseline_mse, observed("mse"), 3)} · bpp ${metricValue("bpp", entry.bpp, entry.baseline_bpp, observed("bpp"), 6)} · grid ${metricValue("work", entry.work, entry.baseline_work, observed("work"), 0)} · speed ${fmt(entry.speed_seconds, 3)}s${metric === "rd" ? ` · RD ${fmt(entry.rd_score, 5)}` : ""}${metric === "overall" ? ` · score ${fmt(entry.overall_score, 5)}` : ""}</span><a class="hold-btn" download href="/api/train/download_checkpoint?path=${encodeURIComponent(entry.path)}">Download</a></div>`).join("");
return `<div class="tr-best-group"><h4>${label}</h4>${rows || `<p class="card-date">No checkpoint yet.</p>`}</div>`;
}).join("");
return `<div class="tr-best-split"><h3>${split === "validation" ? "Validation results" : "Training results"}</h3>${content}</div>`;
}).join("");
}
function renderCheckpoints() {
checkpointRoot.innerHTML = `<div class="params"><div class="params-head"><span class="field-label">Downloadable model checkpoints</span></div><p class="card-date">Download an exported <code>.npz</code> after reviewing its validation history, then upload it into the main project when you want to test or promote it.</p><input id="tr-ckpt-filter" class="tr-filter" placeholder="Filter checkpoints…"><div id="tr-checkpoint-list" class="tr-checkpoint-grid"></div></div>`;
$("#tr-ckpt-filter").oninput = renderCheckpointList;
renderCheckpointList();
}
function renderCheckpointList() {
const root = $("#tr-checkpoint-list");
if (!root) return;
const filter = ($("#tr-ckpt-filter").value || "").toLowerCase();
const visible = checkpoints.filter((c) => c.name.toLowerCase().includes(filter));
root.innerHTML = visible.length ? visible.map((c) => `<div class="tr-checkpoint"><div><b>${esc(c.name)}</b><small>${(c.bytes / 1024).toFixed(1)} KB</small></div><a class="hold-btn" download href="/api/train/download_checkpoint?path=${encodeURIComponent(c.path)}">Download</a></div>`).join("") : `<p class="card-date">No matching checkpoints.</p>`;
}
setInterval(() => { if (!$("#train-lab").hidden && activeTab === "run") refresh(); }, 5000);
})();
|