arithmetic-sorl-dashboard / gen_summary.py
amirali1985's picture
add 10K table; match on num_epochs once catalog has it
c1fc0b6 verified
Raw
History Blame Contribute Delete
7.28 kB
"""
Generate the Overall Accuracy summary markdown from live HF model metrics.
Finds models by config match (not hardcoded names): prefers VALID, falls back
to SUPERSEDED so the dashboard keeps working while jobs are in flight.
Run standalone:
python dashboard/gen_summary.py
Or import:
from gen_summary import build_summary_markdown
"""
import json
from huggingface_hub import hf_hub_download
MODEL_REPO = "thoughtworks/arithmetic-sorl"
CACHE_DIR = "/tmp/hf_dash_cache"
SUMMARY_CONFIGS = [
{
"label": "Standard model (2L/3H/510d) at 10K data",
"baseline_match": {"mode": "baseline", "ops": "add_sub", "dataset_size": 10000, "n_layer": 2, "n_head": 3, "n_embd": 510, "num_epochs": 20},
"sorl_match": {"mode": "sorl", "ops": "add_sub", "dataset_size": 10000, "n_layer": 2, "n_head": 3, "n_embd": 510, "abs_vocab": 30, "K": 1},
"sorl_label": "SoRL K=1 abs30",
"splits": ["add_S0", "add_C1", "add_C3", "add_C5", "add_C6", "sub_M0", "sub_M4"],
"split_labels": {
"add_S0": "S0 (no carry, easy)",
"add_C1": "C1 (1 carry)",
"add_C3": "C3 (3 hot carries)",
"add_C5": "C5 (5 hot carries)",
"add_C6": "C6 (6 hot carries)",
"sub_M0": "sub_M0 (no borrow, easy)",
"sub_M4": "sub_M4 (4 borrows)",
},
},
{
"label": "Standard model (2L/3H/510d) at 25K data",
"baseline_match": {"mode": "baseline", "ops": "add_sub", "dataset_size": 25000, "n_layer": 2, "n_head": 3, "n_embd": 510},
"sorl_match": {"mode": "sorl", "ops": "add_sub", "dataset_size": 25000, "n_layer": 2, "n_head": 3, "n_embd": 510, "abs_vocab": 30, "K": 1},
"sorl_label": "SoRL K=1 abs30",
"splits": ["add_S0", "add_C1", "add_C3", "add_C5", "add_C6", "sub_M0", "sub_M4"],
"split_labels": {
"add_S0": "S0 (no carry, easy)",
"add_C1": "C1 (1 carry)",
"add_C3": "C3 (3 hot carries)",
"add_C5": "C5 (5 hot carries)",
"add_C6": "C6 (6 hot carries)",
"sub_M0": "sub_M0 (no borrow, easy)",
"sub_M4": "sub_M4 (4 borrows)",
},
},
{
"label": "Undersized model (2L/1H/128d) at 100K — where both plateau below 100%",
"baseline_match": {"mode": "baseline", "ops": "add_sub", "dataset_size": 100000, "n_layer": 2, "n_head": 1, "n_embd": 128},
"sorl_match": {"mode": "sorl", "ops": "add_sub", "dataset_size": 100000, "n_layer": 2, "n_head": 1, "n_embd": 128, "abs_vocab": 30, "K": 1},
"sorl_label": "SoRL K=1 abs30",
"splits": ["add_S0", "add_C1", "add_C3", "add_C5", "add_C6", "sub_M0", "sub_M4"],
"split_labels": {
"add_S0": "S0 (no carry, easy)",
"add_C1": "C1 (1 carry)",
"add_C3": "C3 (3 hot carries)",
"add_C5": "C5 (5 hot carries)",
"add_C6": "C6 (6 hot carries)",
"sub_M0": "sub_M0 (no borrow, easy)",
"sub_M4": "sub_M4 (4 borrows)",
},
},
]
def _load_catalog():
try:
local = hf_hub_download(
MODEL_REPO, "model_catalog.json",
local_dir=CACHE_DIR, force_download=True,
)
return json.load(open(local))
except Exception:
return []
def _entry_matches(entry, match_cfg):
"""Check if a flat catalog entry matches all keys in match_cfg.
Handles n_layer/n_head/n_embd by parsing the 'arch' string (e.g. '2L/3H/510d').
"""
arch_map = {}
arch = entry.get("arch", "")
try:
parts = arch.replace("d", "").split("/")
arch_map["n_layer"] = int(parts[0].replace("L", ""))
arch_map["n_head"] = int(parts[1].replace("H", ""))
arch_map["n_embd"] = int(parts[2])
except Exception:
pass
for k, v in match_cfg.items():
# num_epochs may not exist in older catalog entries — skip if missing
if k == "num_epochs" and entry.get(k) is None:
continue
val = arch_map.get(k, entry.get(k))
if val != v:
return False
return True
def _find_model(catalog, match_cfg):
"""Find best matching model: VALID preferred over SUPERSEDED. Returns name or None."""
valid_match = None
superseded_match = None
for entry in catalog:
if _entry_matches(entry, match_cfg):
if entry.get("status") == "VALID":
valid_match = entry["name"]
break
elif entry.get("status") == "SUPERSEDED" and superseded_match is None:
superseded_match = entry["name"]
return valid_match or superseded_match
def _load_splits(model_name, eval_key):
try:
local = hf_hub_download(
MODEL_REPO, f"{model_name}/metrics.json",
local_dir=CACHE_DIR,
)
m = json.load(open(local))
for key in [eval_key, "sft_eval", "sorl_eval"]:
if key in m and "splits" in m[key]:
return m[key]["splits"], model_name
except Exception:
pass
return {}, model_name
def _fmt(v):
if v is None:
return "—"
return f"{v:.0%}"
def _build_table(cfg, catalog):
base_name = _find_model(catalog, cfg["baseline_match"])
sorl_name = _find_model(catalog, cfg["sorl_match"])
base_splits, _ = _load_splits(base_name, "sft_eval") if base_name else ({}, None)
sorl_splits, _ = _load_splits(sorl_name, "sorl_eval") if sorl_name else ({}, None)
if not base_splits and not sorl_splits:
return f"*Data unavailable for {cfg['label']}*\n"
note = ""
if not base_name:
note = " *(baseline not found)*"
if not sorl_name:
note += " *(SoRL not found)*"
header = f"**{cfg['label']}:**{note}\n\n"
header += f"| Split | Baseline | {cfg['sorl_label']} | Gap |\n"
header += "|-------|----------|----------------|-----|\n"
rows = []
for s in cfg["splits"]:
label = cfg["split_labels"].get(s, s)
b = base_splits.get(s, {}).get("full_accuracy")
sv = sorl_splits.get(s, {}).get("full_accuracy")
gap = (sv - b) if (b is not None and sv is not None) else None
gap_str = f"**{gap:+.0%}**" if gap and gap > 0 else (_fmt(gap) if gap else "—")
sorl_str = f"**{_fmt(sv)}**" if (sv and b and sv > b + 0.005) else _fmt(sv)
rows.append(f"| {label} | {_fmt(b)} | {sorl_str} | {gap_str} |")
return header + "\n".join(rows) + "\n"
def build_summary_markdown():
"""Return the full summary markdown, computed from live HF catalog + metrics."""
catalog = _load_catalog()
sections = [_build_table(cfg, catalog) for cfg in SUMMARY_CONFIGS]
body = "\n".join(sections)
return f"""### Overall Accuracy
**Summary:** SoRL v1 (K=1, abs30) **never loses** to the SFT baseline. The biggest gains are
on **hard carry/borrow cascades** — problems requiring multi-digit propagation with varied answers:
{body}
Even when the model is too small to reach 100%, SoRL's abstraction tokens provide
external scratch-pad memory that doubles or triples accuracy on hard cascades.
See the **Results** and **Interpretability** tabs for figures and analysis.
"""
if __name__ == "__main__":
print(build_summary_markdown())