#!/usr/bin/env python3 from __future__ import annotations import math import os import re from pathlib import Path os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib") import matplotlib.pyplot as plt ROOT = Path(__file__).resolve().parents[1] LOG_DIR = ROOT / "logs" FIG_DIR = ROOT / "figures" EXPERIMENTS = [ { "key": "a0_nanogpt_fineweb_adamw", "label": "Naive\nbaseline", "short": "baseline", "train_log": LOG_DIR / "pres_ablation_a0_nanogpt_fineweb_adamw_train.log", "eval_log": LOG_DIR / "pres_ablation_a0_nanogpt_fineweb_adamw_course_val_eval.log", }, { "key": "a1_nanogpt_mixed_adamw", "label": "Mixed\ndata", "short": "data", "train_log": LOG_DIR / "pres_ablation_a1_nanogpt_mixed_adamw_train.log", "eval_log": LOG_DIR / "pres_ablation_a1_nanogpt_mixed_adamw_course_val_eval.log", }, { "key": "a2_nanogpt_fineweb_muon", "label": "Muon\noptimizer", "short": "Muon", "train_log": LOG_DIR / "pres_ablation_a2_nanogpt_fineweb_muon_train.log", "eval_log": LOG_DIR / "pres_ablation_a2_nanogpt_fineweb_muon_course_val_eval.log", }, { "key": "a3_lyra_fineweb_adamw", "label": "Lyra\narchitecture", "short": "arch", "train_log": LOG_DIR / "pres_ablation_a3_lyra_fineweb_adamw_train.log", "eval_log": LOG_DIR / "pres_ablation_a3_lyra_fineweb_adamw_course_val_eval.log", }, { "key": "a4_lyra_mixed_muon", "label": "Combined\nshort run", "short": "combined", "train_log": LOG_DIR / "pres_ablation_a4_lyra_mixed_muon_train.log", "eval_log": LOG_DIR / "pres_ablation_a4_lyra_mixed_muon_course_val_eval.log", }, ] FINAL_LONG_RUN = { "label": "Final\n37k", "short": "long run", "ppl": 19.7822, } STEP_RE = re.compile(r"step\s+(\d+): train loss ([0-9.]+), val loss ([0-9.]+)") PPL_RE = re.compile(r"Perplexity:\s+([0-9.]+)") def parse_last_training_run(path: Path) -> list[tuple[int, float, float]]: if not path.exists(): return [] runs: list[list[tuple[int, float, float]]] = [] current: list[tuple[int, float, float]] = [] for line in path.read_text(errors="replace").splitlines(): match = STEP_RE.search(line) if not match: continue step = int(match.group(1)) row = (step, float(match.group(2)), float(match.group(3))) if step == 0 and current: runs.append(current) current = [] current.append(row) if current: runs.append(current) return runs[-1] if runs else [] def parse_ppl(path: Path) -> float | None: if not path.exists(): return None matches = PPL_RE.findall(path.read_text(errors="replace")) return float(matches[-1]) if matches else None def main() -> None: FIG_DIR.mkdir(exist_ok=True) train_runs = {exp["key"]: parse_last_training_run(exp["train_log"]) for exp in EXPERIMENTS} ppls = [parse_ppl(exp["eval_log"]) for exp in EXPERIMENTS] plt.style.use("seaborn-v0_8-whitegrid") fig, axes = plt.subplots(1, 2, figsize=(13.5, 5.2), constrained_layout=True) ax = axes[0] labels = [exp["label"] for exp in EXPERIMENTS] vals = [p if p is not None else math.nan for p in ppls] colors = ["#6b7280", "#2563eb", "#dc2626", "#059669", "#7c3aed"] bars = ax.bar(labels, vals, color=colors, width=0.68) ax.axhline(FINAL_LONG_RUN["ppl"], color="#111827", linewidth=1.8, linestyle="--") ax.text( 0.02, FINAL_LONG_RUN["ppl"] + 0.3, f"final long run: {FINAL_LONG_RUN['ppl']:.2f}", transform=ax.get_yaxis_transform(), ha="left", va="bottom", fontsize=9, color="#111827", ) ax.set_title("Course Public Validation Perplexity", fontsize=13, weight="bold") ax.set_ylabel("perplexity, lower is better") ax.tick_params(axis="x", labelrotation=0) for bar, val in zip(bars, vals): if math.isnan(val): ax.text( bar.get_x() + bar.get_width() / 2, 1, "pending", ha="center", va="bottom", fontsize=9, rotation=90, color="#374151", ) else: ax.text( bar.get_x() + bar.get_width() / 2, bar.get_height(), f"{val:.1f}", ha="center", va="bottom", fontsize=9, ) finite_vals = [v for v in vals if not math.isnan(v)] if finite_vals: low = min([FINAL_LONG_RUN["ppl"], *finite_vals]) high = max(finite_vals) ax.set_ylim(max(0, low * 0.85), high * 1.12) else: ax.set_ylim(0, 100) ax = axes[1] plotted_curves = 0 for exp, color in zip(EXPERIMENTS, colors): run = train_runs[exp["key"]] if not run: continue plotted_curves += 1 steps = [row[0] for row in run] val_losses = [row[2] for row in run] ax.plot(steps, val_losses, marker="o", linewidth=2.0, markersize=4, color=color, label=exp["short"]) ax.set_title("Short-Run Heldout Loss Curves", fontsize=13, weight="bold") ax.set_xlabel("training iteration") ax.set_ylabel("validation loss") if plotted_curves: ax.legend(frameon=True, fontsize=9) ax.text( 0.02, -0.18, "Each run changes one variable and uses the same 4,500-iteration budget; public PPL is the comparable metric.", transform=ax.transAxes, fontsize=8.5, color="#4b5563", ) fig.suptitle("Ablation Summary for the Presentation", fontsize=15, weight="bold") for suffix in ("png", "pdf"): out = FIG_DIR / f"presentation_ablation_summary.{suffix}" fig.savefig(out, dpi=220) print(out) if __name__ == "__main__": main()