| |
| |
| (async function () { |
| "use strict"; |
|
|
| const DATA = {}; |
| const files = ["meta", "cosine", "per_layer", "geometry", "accuracy", "seed_lr"]; |
| try { |
| const loaded = await Promise.all( |
| files.map((f) => fetch(`./data/${f}.json`).then((r) => { |
| if (!r.ok) throw new Error(`${f}.json ${r.status}`); |
| return r.json(); |
| })) |
| ); |
| files.forEach((f, i) => (DATA[f] = loaded[i])); |
| } catch (e) { |
| document.querySelector("main").insertAdjacentHTML( |
| "afterbegin", |
| `<p style="color:#c0392b">Could not load data: ${e.message}. Serve this folder over HTTP (e.g. <code>python3 -m http.server</code>).</p>` |
| ); |
| return; |
| } |
|
|
| const meta = DATA.meta; |
| const ORDER = ["sft", "rft", "dft", "rift", "grpo", "dpo", "online_grpo", "online_dapo"]; |
| const SHORT = { sft: "SFT", rft: "RFT", dft: "DFT", rift: "RIFT", grpo: "GRPO", dpo: "DPO", online_grpo: "oGRPO", online_dapo: "oDAPO" }; |
| const C = meta.methodColors; |
|
|
| |
| const css = (v) => getComputedStyle(document.body).getPropertyValue(v).trim(); |
| const INK = css("--ink") || "#211d18"; |
| const SOFT = css("--ink-soft") || "#50473c"; |
| const FAINT = css("--ink-faint") || "#897e6c"; |
| const ACCENT = css("--accent") || "#b23a23"; |
| const GRID = "rgba(31,27,22,0.10)"; |
| const AXIS = "rgba(31,27,22,0.55)"; |
| const MONO = "IBM Plex Mono, monospace"; |
|
|
| const CONFIG = { displayModeBar: false, responsive: true }; |
| function layout(extra) { |
| return Object.assign({ |
| paper_bgcolor: "rgba(0,0,0,0)", |
| plot_bgcolor: "rgba(0,0,0,0)", |
| font: { family: MONO, color: SOFT, size: 11.5 }, |
| margin: { l: 58, r: 22, t: 14, b: 46 }, |
| hoverlabel: { bgcolor: INK, bordercolor: INK, font: { family: MONO, size: 11.5, color: "#f3efe4" } }, |
| xaxis: { gridcolor: GRID, zerolinecolor: AXIS, linecolor: AXIS, ticks: "outside", tickcolor: AXIS, ticklen: 4 }, |
| yaxis: { gridcolor: GRID, zerolinecolor: AXIS, linecolor: AXIS, ticks: "outside", tickcolor: AXIS, ticklen: 4 }, |
| legend: { orientation: "h", y: -0.2, font: { family: MONO, size: 11 } }, |
| showlegend: false, |
| }, extra || {}); |
| } |
|
|
| |
| const idIndex = (id) => ORDER.indexOf(id); |
| const pairKey = (a, b) => [a, b].sort((x, y) => idIndex(x) - idIndex(y)).join("__"); |
| const pairLabel = (pk) => pk.split("__").map((m) => SHORT[m]).join(" · "); |
| function labelToId(label) { |
| const s = label.toLowerCase().replace(/\s+/g, "_"); |
| const map = { offline_grpo: "grpo", online_grpo: "online_grpo", online_dapo: "online_dapo" }; |
| return map[s] || s; |
| } |
| |
| const PAIR_PALETTE = ["#2f5d7c", "#bf4d2e", "#3f7a6d", "#c08a1e", "#7c5a86", "#5a6b3b", |
| "#9c5a2e", "#456a8a", "#8a6d2e", "#7a3b3b", "#3f6e6e", "#874a6a", |
| "#6a6a3b", "#9c3526", "#444038"]; |
| const masterPairs = Object.keys(DATA.per_layer.cosine); |
| const pairColor = (pk) => PAIR_PALETTE[Math.max(0, masterPairs.indexOf(pk)) % PAIR_PALETTE.length]; |
|
|
| |
| document.getElementById("venue").textContent = meta.venue; |
| document.getElementById("title").textContent = meta.title; |
| document.getElementById("subtitle").textContent = meta.subtitle; |
| document.getElementById("abstract-text").textContent = meta.abstract; |
| document.getElementById("footer-title").textContent = meta.title; |
| for (const id of ["repolink", "repolink2"]) document.getElementById(id).href = meta.repo; |
| if (meta.paper) document.getElementById("paperlink").href = meta.paper; |
| document.querySelectorAll("figcaption[data-cap]").forEach((el) => { |
| el.textContent = meta.captions[el.dataset.cap] || ""; |
| }); |
|
|
| |
| (function methodsTable() { |
| const t = document.getElementById("methods-table"); |
| t.innerHTML = |
| `<thead><tr><th>Method</th><th>Loss</th><th>Negatives</th><th>Reward</th><th>Ref. policy</th></tr></thead><tbody>` + |
| meta.methods.map((m) => |
| `<tr><td><span class="swatch" style="background:${C[m.id]}"></span>${m.name}</td>` + |
| `<td>${m.loss.includes("×") || m.loss.includes("sg(") ? `<code>${m.loss}</code>` : m.loss}</td>` + |
| `<td>${m.neg}</td><td>${m.reward}</td><td>${m.ref}</td></tr>` |
| ).join("") + `</tbody>`; |
|
|
| |
| const renderTeX = (tex) => |
| window.katex ? katex.renderToString(tex, { throwOnError: false, displayMode: false }) : tex; |
| document.getElementById("objectives").innerHTML = meta.methods.map((m) => |
| `<div><dt><span class="swatch" style="background:${C[m.id]}"></span>${m.name}</dt>` + |
| `<dd>${renderTeX(m.formula)}</dd></div>` |
| ).join(""); |
| const ceEq = document.getElementById("ce-eq"); |
| if (ceEq && window.katex) |
| ceEq.innerHTML = renderTeX("-\\,\\mathbb{E}\\,\\log \\pi_\\theta(y\\mid x)=\\operatorname{CE}(y,\\pi_\\theta)"); |
| })(); |
|
|
| |
| function buildChips(host, pairs, selected, onChange) { |
| host.classList.add("legend-toggles"); |
| host.innerHTML = ""; |
| pairs.forEach((pk) => { |
| const b = document.createElement("button"); |
| b.className = "chip" + (selected.has(pk) ? " on" : ""); |
| b.style.setProperty("--swatch", pairColor(pk)); |
| b.innerHTML = `<span class="dot"></span>${pairLabel(pk)}`; |
| b.addEventListener("click", () => { |
| if (selected.has(pk)) { |
| if (selected.size === 1) return; |
| selected.delete(pk); |
| b.classList.remove("on"); |
| } else { selected.add(pk); b.classList.add("on"); } |
| onChange(); |
| }); |
| host.appendChild(b); |
| }); |
| } |
|
|
| |
| const cosineState = { size: 6 }; |
| |
| |
| const BAND = ["#f0e9d7", "#e7d7bd", "#ddc29a", "#d3ab74", "#c8924f", "#bf7a39"]; |
| const STOP = [0, 0.25, 0.4167, 0.5833, 0.75, 0.9167, 1]; |
| const DISCRETE = []; |
| BAND.forEach((c, i) => { DISCRETE.push([STOP[i], c]); DISCRETE.push([STOP[i + 1], c]); }); |
| function drawCosine() { |
| const d = DATA.cosine; |
| const labels = cosineState.size === 8 ? d.labels8 : d.labels6; |
| const z = cosineState.size === 8 ? d.matrix8 : d.matrix6; |
| Plotly.react("chart-cosine", [{ |
| type: "heatmap", z, x: labels, y: labels, |
| zmin: -0.2, zmax: 1, colorscale: DISCRETE, showscale: false, |
| xgap: 2, ygap: 2, |
| text: z.map((row) => row.map((v) => v.toFixed(2))), |
| texttemplate: "%{text}", textfont: { size: 12, family: MONO, color: INK }, |
| hovertemplate: "%{y} · %{x} cos = %{z:.3f}<extra></extra>", |
| }], layout({ |
| margin: { l: 112, r: 24, t: 8, b: 96 }, |
| xaxis: { tickangle: -40, gridcolor: "rgba(0,0,0,0)", zerolinecolor: "rgba(0,0,0,0)", linecolor: "rgba(0,0,0,0)", ticks: "", side: "bottom", automargin: true, tickfont: { family: MONO, size: 11, color: INK } }, |
| yaxis: { autorange: "reversed", gridcolor: "rgba(0,0,0,0)", zerolinecolor: "rgba(0,0,0,0)", linecolor: "rgba(0,0,0,0)", ticks: "", automargin: true, tickfont: { family: MONO, size: 11, color: INK } }, |
| }), CONFIG); |
| } |
| document.querySelectorAll("#cosine .seg-btn").forEach((b) => |
| b.addEventListener("click", () => { |
| document.querySelectorAll("#cosine .seg-btn").forEach((x) => x.classList.remove("active")); |
| b.classList.add("active"); |
| cosineState.size = +b.dataset.mx; |
| drawCosine(); |
| })); |
| drawCosine(); |
| |
| document.getElementById("chart-cosine").on("plotly_click", (ev) => { |
| const p = ev.points[0]; |
| const a = labelToId(p.y), b = labelToId(p.x); |
| if (a === b) return; |
| const pk = pairKey(a, b); |
| if (!DATA.per_layer.cosine[pk]) return; |
| perlayerSel.clear(); perlayerSel.add(pk); |
| buildChips(document.getElementById("perlayer-pairs"), masterPairs, perlayerSel, drawPerlayer); |
| drawPerlayer(); |
| document.getElementById("perlayer").scrollIntoView({ behavior: "smooth" }); |
| }); |
|
|
| |
| const layers = DATA.per_layer.layers; |
| const sharedLayer = { idx: 30 }; |
| const perlayerSel = new Set(["sft__rft", "sft__grpo", "sft__dpo"]); |
|
|
| function lineTraces(source, pairs) { |
| return [...pairs].map((pk) => ({ |
| type: "scatter", mode: "lines", name: pairLabel(pk), |
| x: layers, y: source[pk], |
| line: { color: pairColor(pk), width: 2, shape: "linear" }, |
| hovertemplate: `${pairLabel(pk)} L%{x} = %{y:.3f}<extra></extra>`, |
| })); |
| } |
| function vLine() { |
| return [{ |
| type: "line", x0: sharedLayer.idx, x1: sharedLayer.idx, yref: "paper", y0: 0, y1: 1, |
| line: { color: SOFT, width: 1, dash: "dot" }, |
| }]; |
| } |
| function drawPerlayer() { |
| Plotly.react("chart-perlayer", lineTraces(DATA.per_layer.cosine, perlayerSel), |
| layout({ |
| showlegend: true, |
| xaxis: { title: { text: "transformer block", font: { size: 12 } }, gridcolor: GRID, dtick: 5 }, |
| yaxis: { title: { text: "cosine of ΔW", font: { size: 12 } }, gridcolor: GRID, range: [-0.25, 1.05], zeroline: true }, |
| shapes: vLine(), |
| }), CONFIG); |
| updateReadout(); |
| } |
| function updateReadout() { |
| const L = layers[sharedLayer.idx]; |
| const parts = [...perlayerSel].map((pk) => `${pairLabel(pk)} = ${DATA.per_layer.cosine[pk][sharedLayer.idx].toFixed(3)}`); |
| document.getElementById("layer-readout").textContent = `block ${L} · ` + parts.join(" · "); |
| } |
| buildChips(document.getElementById("perlayer-pairs"), masterPairs, perlayerSel, drawPerlayer); |
| drawPerlayer(); |
|
|
| const slider = document.getElementById("layer-slider"); |
| slider.value = sharedLayer.idx; |
| slider.addEventListener("input", () => { |
| sharedLayer.idx = +slider.value; |
| Plotly.relayout("chart-perlayer", { shapes: vLine() }); |
| Plotly.relayout("chart-cka", { shapes: vLine() }); |
| updateReadout(); |
| }); |
|
|
| |
| const ckaPairs = Object.keys(DATA.per_layer.cka); |
| const ckaSel = new Set(["grpo__dpo", "sft__grpo", "sft__rift", "dft__dpo"].filter((p) => DATA.per_layer.cka[p])); |
| if (ckaSel.size === 0) ckaPairs.slice(0, 3).forEach((p) => ckaSel.add(p)); |
| function drawCka() { |
| Plotly.react("chart-cka", lineTraces(DATA.per_layer.cka, ckaSel), |
| layout({ |
| showlegend: true, |
| xaxis: { title: { text: "transformer block", font: { size: 12 } }, gridcolor: GRID, dtick: 5 }, |
| yaxis: { title: { text: "linear CKA", font: { size: 12 } }, gridcolor: GRID, range: [0.3, 1.03] }, |
| shapes: vLine(), |
| }), CONFIG); |
| } |
| buildChips(document.getElementById("cka-pairs"), ckaPairs, ckaSel, drawCka); |
| drawCka(); |
|
|
| |
| const svdPairs = Object.keys(DATA.per_layer.svd_u); |
| const svdSel = new Set(["sft__rft", "sft__dpo", "sft__grpo"]); |
| function drawSvd() { |
| Plotly.react("chart-svd", lineTraces(DATA.per_layer.svd_u, svdSel), |
| layout({ |
| showlegend: true, |
| xaxis: { title: { text: "transformer block", font: { size: 12 } }, gridcolor: GRID, dtick: 5 }, |
| yaxis: { title: { text: "top-1 output direction cosine", font: { size: 12 } }, gridcolor: GRID, range: [-0.1, 1.05], zeroline: true }, |
| }), CONFIG); |
| } |
| buildChips(document.getElementById("svd-pairs"), svdPairs, svdSel, drawSvd); |
| drawSvd(); |
|
|
| |
| (function drawAngles() { |
| const pa = DATA.geometry.principal_angles; |
| const keys = Object.keys(pa); |
| const others = keys.map((k) => k.split("__").find((m) => m !== "sft")); |
| const labels = others.map((m) => SHORT[m]); |
| Plotly.react("chart-angles", [ |
| { type: "bar", name: "top-1 angle", x: labels, y: keys.map((k) => pa[k].median_top1), |
| marker: { color: others.map((m) => C[m]) }, |
| hovertemplate: "SFT · %{x}<br>top-1 = %{y:.1f}°<extra></extra>" }, |
| { type: "bar", name: "top-10 worst", x: labels, y: keys.map((k) => pa[k].median_top10_max), |
| marker: { color: others.map((m) => C[m]), opacity: 0.32 }, |
| hovertemplate: "SFT · %{x}<br>top-10 worst = %{y:.1f}°<extra></extra>" }, |
| ], layout({ |
| barmode: "group", bargap: 0.35, showlegend: true, |
| yaxis: { title: { text: "principal angle (degrees)", font: { size: 12 } }, gridcolor: GRID, range: [0, 95] }, |
| xaxis: { gridcolor: "rgba(0,0,0,0)" }, |
| }), CONFIG); |
| })(); |
|
|
| |
| (function drawGeometry() { |
| const g = DATA.geometry; |
| const ms = ["sft", "rft", "dft", "rift", "grpo", "dpo"]; |
| const labels = ms.map((m) => SHORT[m]); |
| Plotly.react("chart-geometry", [ |
| { type: "bar", name: "‖ΔW‖ (Frobenius)", x: labels, y: ms.map((m) => g.frobenius[m]), |
| marker: { color: ms.map((m) => C[m]) }, |
| hovertemplate: "%{x}<br>‖ΔW‖ = %{y:.3f}<extra></extra>" }, |
| { type: "scatter", name: "effective rank", x: labels, y: ms.map((m) => g.effective_rank[m]), |
| yaxis: "y2", mode: "lines+markers", |
| line: { color: SOFT, width: 1.5, dash: "dot" }, marker: { size: 9, color: SOFT, symbol: "diamond" }, |
| hovertemplate: "%{x}<br>eff. rank = %{y:.1f}<extra></extra>" }, |
| ], layout({ |
| showlegend: true, bargap: 0.45, |
| yaxis: { title: { text: "‖ΔW‖", font: { size: 12 } }, gridcolor: GRID, rangemode: "tozero" }, |
| yaxis2: { title: { text: "effective rank", font: { size: 12 } }, overlaying: "y", side: "right", |
| gridcolor: "rgba(0,0,0,0)", rangemode: "tozero", showgrid: false }, |
| xaxis: { gridcolor: "rgba(0,0,0,0)" }, |
| legend: { orientation: "h", y: -0.16, font: { size: 12 } }, |
| }), CONFIG); |
| })(); |
|
|
| |
| (function drawLmc() { |
| const g = DATA.geometry; |
| const alphas = g.alphas; |
| const traces = Object.entries(g.lmc).map(([k, v], i) => { |
| const [a, b] = k.split("__"); |
| return { |
| type: "scatter", mode: "lines+markers", |
| name: `${SHORT[a]} → ${SHORT[b]}`, x: alphas, y: v, |
| line: { color: PAIR_PALETTE[i % PAIR_PALETTE.length], width: 2, shape: "linear" }, |
| marker: { size: 5, symbol: "square" }, |
| hovertemplate: `${SHORT[a]}→${SHORT[b]}<br>α=%{x} · loss %{y:.2f}<extra></extra>`, |
| }; |
| }); |
| Plotly.react("chart-lmc", traces, layout({ |
| showlegend: true, |
| xaxis: { title: { text: "interpolation α (0 = first adapter, 1 = second)", font: { size: 12 } }, gridcolor: GRID, dtick: 0.25 }, |
| yaxis: { title: { text: "masked-answer loss", font: { size: 12 } }, gridcolor: GRID, rangemode: "tozero" }, |
| }), CONFIG); |
| })(); |
|
|
| |
| const accState = { bench: "gsm8k" }; |
| function drawAccuracy() { |
| const rows = DATA.accuracy.methods.filter((r) => r[accState.bench] != null); |
| Plotly.react("chart-accuracy", [{ |
| type: "bar", orientation: "h", |
| y: rows.map((r) => r.label).reverse(), |
| x: rows.map((r) => r[accState.bench]).reverse(), |
| marker: { color: rows.map((r) => C[r.method] || "#94A3B8").reverse() }, |
| text: rows.map((r) => `${r[accState.bench].toFixed(1)}%`).reverse(), |
| textposition: "outside", textfont: { family: MONO, size: 12, color: INK }, |
| hovertemplate: "%{y}<br>%{x:.1f}%<extra></extra>", |
| }], layout({ |
| margin: { l: 108, r: 40, t: 10, b: 42 }, |
| xaxis: { title: { text: "greedy pass@1 (%)", font: { size: 12 } }, gridcolor: GRID, |
| range: [0, accState.bench === "gsm8k" ? 104 : 26] }, |
| yaxis: { gridcolor: "rgba(0,0,0,0)", automargin: true }, |
| }), CONFIG); |
| } |
| document.querySelectorAll("#accuracy .seg-btn").forEach((b) => |
| b.addEventListener("click", () => { |
| document.querySelectorAll("#accuracy .seg-btn").forEach((x) => x.classList.remove("active")); |
| b.classList.add("active"); |
| accState.bench = b.dataset.bench; |
| drawAccuracy(); |
| })); |
| drawAccuracy(); |
|
|
| |
| const seedState = { view: "cos" }; |
| function drawSeedLr() { |
| const s = DATA.seed_lr; |
| const lrs = s.lrs; |
| if (seedState.view === "cos") { |
| |
| const sft = s.seed_cosine.sft; |
| const x = lrs.filter((lr) => sft[lr]); |
| Plotly.react("chart-seedlr", [ |
| { type: "bar", name: "raw weight cosine", x, y: x.map((lr) => sft[lr].cos), |
| marker: { color: C.sft }, hovertemplate: "lr %{x}<br>raw cosine = %{y:.3f}<extra></extra>" }, |
| { type: "scatter", name: "top-1 output dir (u₁)", x, y: x.map((lr) => sft[lr].top1_u), |
| mode: "lines+markers", line: { color: C.dft, width: 2.4 }, marker: { size: 9 }, |
| hovertemplate: "lr %{x}<br>u₁ cosine = %{y:.3f}<extra></extra>" }, |
| { type: "scatter", name: "top-1 input dir (v₁)", x, y: x.map((lr) => sft[lr].top1_v), |
| mode: "lines+markers", line: { color: C.online_dapo, width: 2, dash: "dot" }, marker: { size: 7 }, |
| hovertemplate: "lr %{x}<br>v₁ cosine = %{y:.3f}<extra></extra>" }, |
| ], layout({ |
| showlegend: true, |
| yaxis: { title: { text: "seed-42 vs seed-123 cosine (SFT)", font: { size: 12 } }, gridcolor: GRID, range: [0, 1.05] }, |
| xaxis: { title: { text: "learning rate", font: { size: 12 } }, type: "category", gridcolor: "rgba(0,0,0,0)" }, |
| }), CONFIG); |
| } else { |
| |
| const dapo = s.dapo_lr_sensitivity; |
| const steps = Object.keys(dapo); |
| const labels = steps.map((k) => k.replace(/_to_/g, " → ")); |
| Plotly.react("chart-seedlr", [ |
| { type: "bar", name: "cosine between adjacent LRs (DAPO)", x: labels, y: steps.map((k) => dapo[k].cos), |
| marker: { color: C.online_dapo }, hovertemplate: "%{x}<br>cosine = %{y:.3f}<extra></extra>" }, |
| { type: "scatter", name: "norm ratio (×)", x: labels, y: steps.map((k) => dapo[k].norm_ratio), |
| yaxis: "y2", mode: "lines+markers", line: { color: SOFT, width: 1.5, dash: "dot" }, marker: { size: 9, symbol: "diamond" }, |
| hovertemplate: "%{x}<br>norm ×%{y:.1f}<extra></extra>" }, |
| ], layout({ |
| showlegend: true, bargap: 0.5, |
| yaxis: { title: { text: "direction cosine", font: { size: 12 } }, gridcolor: GRID, range: [0, 1.05] }, |
| yaxis2: { title: { text: "norm ratio (×)", font: { size: 12 } }, overlaying: "y", side: "right", showgrid: false, rangemode: "tozero" }, |
| xaxis: { type: "category", gridcolor: "rgba(0,0,0,0)" }, |
| }), CONFIG); |
| } |
| } |
| document.querySelectorAll("#seedlr .seg-btn").forEach((b) => |
| b.addEventListener("click", () => { |
| document.querySelectorAll("#seedlr .seg-btn").forEach((x) => x.classList.remove("active")); |
| b.classList.add("active"); |
| seedState.view = b.dataset.seed; |
| drawSeedLr(); |
| })); |
| drawSeedLr(); |
|
|
| |
| (function scrollspy() { |
| const links = [...document.querySelectorAll("#toc a")]; |
| const map = new Map(links.map((a) => [a.getAttribute("href").slice(1), a])); |
| const obs = new IntersectionObserver((entries) => { |
| entries.forEach((e) => { |
| if (e.isIntersecting) { |
| links.forEach((l) => l.classList.remove("active")); |
| const a = map.get(e.target.id); |
| if (a) a.classList.add("active"); |
| } |
| }); |
| }, { rootMargin: "-30% 0px -60% 0px" }); |
| document.querySelectorAll("main section").forEach((s) => obs.observe(s)); |
| })(); |
| })(); |
|
|