fathom-code / scripts /make_plots.py
23f2002275
feat: phase 1 complete — smoke green on HF Jobs, training scripts, plot generator, Colab notebook, submission preflight
8787bd3
Raw
History Blame Contribute Delete
3.63 kB
"""Generate reward + loss curve PNGs from a TRL training run.
Reads trainer_state.json (TRL writes one per checkpoint) and creates:
outputs/plots/reward_curve.png
outputs/plots/loss_curve.png
outputs/plots/grad_norm.png
outputs/plots/training_summary.png (combined 2x2 panel)
These are committed-to-repo PNGs that judges expect in the README.
"""
from __future__ import annotations
import json
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
OUTPUTS = Path("outputs")
PLOTS = OUTPUTS / "plots"
PLOTS.mkdir(parents=True, exist_ok=True)
def find_trainer_state() -> Path | None:
candidates = list(OUTPUTS.rglob("trainer_state.json"))
if not candidates:
return None
candidates.sort(key=lambda p: p.stat().st_mtime, reverse=True)
return candidates[0]
def extract_series(log_history: list[dict]) -> dict[str, list]:
series: dict[str, list] = {"step": []}
for entry in log_history:
step = entry.get("step")
if step is None:
continue
for k, v in entry.items():
if k == "step":
continue
if not isinstance(v, (int, float)):
continue
series.setdefault(k, []).append((step, float(v)))
return series
def plot_metric(series: dict, key: str, title: str, ylabel: str, outfile: Path) -> bool:
if key not in series or len(series[key]) < 2:
print(f"[skip] no series for {key}")
return False
xs, ys = zip(*series[key])
fig, ax = plt.subplots(figsize=(8, 5), dpi=120)
ax.plot(xs, ys, marker=".", linewidth=2, color="#1f77b4")
ax.set_xlabel("Training step")
ax.set_ylabel(ylabel)
ax.set_title(title)
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.savefig(outfile)
plt.close(fig)
print(f"[ok] {outfile}")
return True
def plot_summary(series: dict, outfile: Path) -> None:
panels = [
("loss", "Loss", "loss"),
("reward", "Mean reward", "reward"),
("grad_norm", "Grad norm", "grad_norm"),
("kl", "KL divergence", "kl"),
]
fig, axes = plt.subplots(2, 2, figsize=(13, 9), dpi=120)
for ax, (key, title, ylabel) in zip(axes.flat, panels):
if key not in series or len(series[key]) < 2:
ax.set_title(f"{title} (no data)")
ax.axis("off")
continue
xs, ys = zip(*series[key])
ax.plot(xs, ys, marker=".", linewidth=2)
ax.set_xlabel("Step")
ax.set_ylabel(ylabel)
ax.set_title(title)
ax.grid(True, alpha=0.3)
fig.suptitle("FATHOM — GRPO training (Qwen 1.5B + LoRA, OpenEnv multi-turn)", fontsize=13)
fig.tight_layout()
fig.savefig(outfile)
plt.close(fig)
print(f"[ok] {outfile}")
def main() -> int:
state_path = find_trainer_state()
if state_path is None:
print("ERROR: no trainer_state.json found under outputs/")
return 1
print(f"Reading {state_path}")
state = json.loads(state_path.read_text())
series = extract_series(state.get("log_history", []))
print(f"Series found: {sorted(series.keys())}")
plot_metric(series, "loss", "Training loss", "loss", PLOTS / "loss_curve.png")
plot_metric(series, "reward", "Mean reward (composite)", "reward", PLOTS / "reward_curve.png")
plot_metric(series, "grad_norm", "Gradient norm", "grad_norm", PLOTS / "grad_norm.png")
plot_metric(series, "kl", "KL divergence", "KL", PLOTS / "kl_curve.png")
plot_summary(series, PLOTS / "training_summary.png")
return 0
if __name__ == "__main__":
raise SystemExit(main())