GroundFlow / visualize /plot_appendix_train_dynamics.py
TerryPei's picture
Update appendix training-dynamics figures (GroundFlow only, SD-RPN removed) + nips.tex + plot script
6b81445 verified
Raw
History Blame Contribute Delete
4.62 kB
#!/usr/bin/env python3
"""Appendix training-dynamics figures (4B / 8B), one single-figure PDF+PNG per
panel — composed into a 2x2 layout in LaTeX via \\subfigure. Follows doc/vis.md:
restricted academic palette, sans-serif, large fonts, tight crop, B&W-safe
(solid/dashed + circle/square markers), "Ours" (GroundFlow) gets the prominent
coral colour consistently.
Outputs (to doc/figures/ and figures/):
figA_loss_4b.{pdf,png} figA_loss_8b.{pdf,png}
figA_gn_4b.{pdf,png} figA_gn_8b.{pdf,png}
"""
import json, math, os
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
M = "/mnt/bn/leonworkspace/terry/model"
TRAINER = {
"4B": {
"SD-RPN": f"{M}/qwen3vl-4b-roi-K24T3-185k-ddp-verify/trainer_state.json",
"GroundFlow": f"{M}/qwen3vl-4b-roi-K24T3-185k-selfdistill-run7b-sdpa-iwa/trainer_state.json",
},
"8B": {
"SD-RPN": f"{M}/qwen3vl-8b-roi-K24T3-185k-sdrpn/trainer_state.json",
"GroundFlow": f"{M}/qwen3vl-8b-roi-K24T3-185k-a0.667-t1.5-iwa-g1.0/trainer_state.json",
},
}
# doc/vis.md palette: GroundFlow (=Ours) = coral #f57c6e (prominent, consistent).
# Per request, only the GroundFlow curve is plotted (SD-RPN's loss is not shown).
STYLE = {
"GroundFlow": dict(color="#f57c6e", ls="-", marker="o", label="GroundFlow"),
}
ORDER = ["GroundFlow"]
OUTDIRS = ["/opt/tiger/thothvl_pretrain/doc/figures", "/opt/tiger/thothvl_pretrain/figures"]
def load(path):
lh = json.load(open(path))["log_history"]
rows = [(d["step"], d["loss"], d.get("grad_norm")) for d in lh if "loss" in d]
step = np.array([r[0] for r in rows], float)
loss = np.array([r[1] for r in rows], float)
gn = np.array([(r[2] if r[2] is not None else np.nan) for r in rows], float)
fin = [d for d in lh if "train_loss" in d]
return step, loss, gn, (fin[-1]["train_loss"] if fin else float(loss[-1]))
def ema(x, alpha=0.06):
y = np.empty_like(x); m = next(v for v in x if math.isfinite(v))
for i, v in enumerate(x):
if math.isfinite(v):
m = alpha * v + (1 - alpha) * m
y[i] = m
return y
plt.rcParams.update({
"font.family": "sans-serif",
"font.sans-serif": ["Helvetica", "Arial", "DejaVu Sans"],
"mathtext.fontset": "dejavusans",
"font.size": 11, "axes.titlesize": 13, "axes.labelsize": 12,
"xtick.labelsize": 10.5, "ytick.labelsize": 10.5, "legend.fontsize": 10,
"axes.linewidth": 0.9, "xtick.direction": "in", "ytick.direction": "in",
"xtick.major.size": 3.2, "ytick.major.size": 3.2,
"legend.frameon": False, "axes.spines.top": False, "axes.spines.right": False,
"savefig.bbox": "tight", "savefig.pad_inches": 0.02,
})
def make_panel(model, kind, fname):
"""kind in {'loss','gn'}"""
fig, ax = plt.subplots(figsize=(4.0, 3.0))
for name in ORDER:
st, ls, gn, fl = load(TRAINER[model][name])
y = ls if kind == "loss" else gn
s = STYLE[name]
idx = np.linspace(0, len(st) - 1, 8).round().astype(int) # 8 markers, B&W aid
ax.plot(st, y, color=s["color"], lw=0.6, alpha=0.16) # raw, faint
ax.plot(st, ema(y), color=s["color"], ls=s["ls"], lw=2.0, # EMA
marker=s["marker"], markevery=list(idx), ms=4.5, mew=0,
label=s["label"], zorder=3 if name == "GroundFlow" else 2)
if kind == "loss":
yi = ema(y)[-1]
ax.annotate(f"{fl:.2f}" if fl >= 0.1 else f"{fl:.3f}",
xy=(st[-1], yi), xytext=(5, 0), textcoords="offset points",
va="center", fontsize=10, color=s["color"], fontweight="bold")
if kind == "loss":
ax.set_ylabel("training loss")
ax.set_ylim(bottom=0) # linear: clean single-curve shape
else:
ax.set_yscale("log"); ax.set_ylabel("gradient norm") # spans ~2 decades
ax.set_xlabel("optimizer step")
ax.set_title(f"Qwen3-VL-{model}")
ax.set_xlim(-30, 1480)
ax.xaxis.set_major_locator(mticker.MultipleLocator(500))
ax.grid(True, which="both", ls=":", lw=0.5, alpha=0.35)
ax.legend(loc="upper right", handlelength=2.4, borderaxespad=0.4)
fig.tight_layout()
for d in OUTDIRS:
os.makedirs(d, exist_ok=True)
for ext in ("pdf", "png"):
fig.savefig(f"{d}/{fname}.{ext}", dpi=300)
plt.close(fig)
print(f" wrote {fname}.{{pdf,png}}")
for model in ("4B", "8B"):
make_panel(model, "loss", f"figA_loss_{model.lower()}")
make_panel(model, "gn", f"figA_gn_{model.lower()}")
print("done.")