| """ |
| 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(): |
| |
| 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()) |
|
|