(() => { 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 ? `${fmt(value, digits)}` : 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 = `
Long-horizon RL traininghpt_data → hpt_data_val

Runs the current filler trainer in a resumable subprocess. Start the same output again to continue from its optimizer and RNG checkpoint.

Preset policies
${presets.map(([id, label], i) => ``).join("")}
Run status
Loading…
Learning graphics
Best checkpoints
Validation history
`; $("#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) => ``).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 `${row.epoch ? `Epoch ${row.epoch}` : "Initial"}${fmt(row.train_reward, 5)}${fmt(row.reward ?? v.reward, 5)}${fmt(v.aggregate_score, 5)}${metricValue("mse", row.train_mse, row.train_baseline_mse, newObserved("train_mse", true), 3)}${metricValue("mse", v.mse, v.baseline_mse, newObserved("mse"), 3)}${metricValue("bpp", row.train_bpp, row.train_baseline_bpp, newObserved("train_bpp", true), 6)}${metricValue("bpp", v.bpp, v.baseline_bpp, newObserved("bpp"), 6)}${metricValue("work", row.train_work, row.train_baseline_work, newObserved("train_work", true), 0)}${metricValue("work", v.work, v.baseline_work, newObserved("work"), 0)}${fmt(row.train_encode_seconds, 3)}${fmt(v.encode_seconds, 3)}`; }); if (newRows.length) { root.innerHTML = `
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).
${newRows.join("")}
CheckpointTrain rewardVal rewardAggregate scoreTrain MSEVal MSETrain bppVal bppTrain grid coefficientsVal grid coefficientsTrain encode sVal encode s
`; } else { root.innerHTML = `

No validation rows yet. The first baseline pass can take a few minutes.

`; } 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 `${row.epoch ? `Epoch ${row.epoch}` : "Initial"}${fmt(row.train_reward, 5)}${fmt(row.reward ?? v.reward, 5)}${fmt(v.aggregate_score, 5)}${metricValue("mse", row.train_mse, row.train_baseline_mse, trainMse, 3)}${metricValue("mse", v.mse, v.baseline_mse, valMse, 3)}${metricValue("bpp", row.train_bpp, row.train_baseline_bpp, trainBpp, 6)}${metricValue("bpp", v.bpp, v.baseline_bpp, valBpp, 6)}${metricValue("work", row.train_work, row.train_baseline_work, trainWork, 0)}${metricValue("work", v.work, v.baseline_work, valWork, 0)}`; }); const baseline = (history.find((r) => r.validation)?.validation || {}); root.innerHTML = legacyRows.length ? `
PBC3.0 baseline: MSE ${fmt(baseline.baseline_mse, 3)} · bpp ${fmt(baseline.baseline_bpp, 6)}. Green = better · yellow = close · red = worse. Work uses the starting policy as its reference.
${legacyRows.join("")}
CheckpointTrain rewardVal rewardAggregate scoreTrain MSEVal MSETrain bppVal bppTrain workVal work
` : `

No validation rows yet. The first baseline pass can take a few minutes.

`; } 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 ? `${esc(data.output)}` : "No output selected"; status.innerHTML = `${state} · ${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) => `
Epoch ${entry.epoch} · 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)}` : ""}Download
`).join(""); return `

${label}

${rows || `

No checkpoint yet.

`}
`; }).join(""); return `

${split === "validation" ? "Validation results" : "Training results"}

${content}
`; }).join(""); } function renderCheckpoints() { checkpointRoot.innerHTML = `
Downloadable model checkpoints

Download an exported .npz after reviewing its validation history, then upload it into the main project when you want to test or promote it.

`; $("#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) => `
${esc(c.name)}${(c.bytes / 1024).toFixed(1)} KB
Download
`).join("") : `

No matching checkpoints.

`; } setInterval(() => { if (!$("#train-lab").hidden && activeTab === "run") refresh(); }, 5000); })();