File size: 6,348 Bytes
0d70486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"""Generate static index.html for the leaderboard from results.csv.

Static HF Spaces are free; this replaces the Gradio app (kept as app.py for a
future PRO upgrade). Run: python3 build_static.py
"""
import csv
import html
from pathlib import Path

HERE = Path(__file__).parent
DATASET = "https://huggingface.co/datasets/ModelBrew/sequential-forgetting-benchmark"

SUITES = {
    "5-domain-realworld": "Suite A — 5 real-world domains · Mistral-7B · 3 seeds",
    "4-domain-MLCF": "Suite B — Medical→Legal→Code→Finance",
    "4-domain-MLCF-history": "Suite B history — CL-technique stacks (within-version comparisons only)",
    "mquake-5skill-vault": "Suite C — MQuAKE 5-skill retention · Qwen3-4B",
}

rows = list(csv.DictReader(open(HERE / "results.csv")))


def table(subset):
    if not subset:
        return ""
    subset = sorted(subset, key=lambda r: abs(float(r["retention_value_pct"])))
    out = ["<div class='tablewrap'><table><thead><tr>"]
    heads = ["method", "base model", "domains", "seeds", "metric", "value %", "status", "source", "notes"]
    out += [f"<th>{h}</th>" for h in heads] + ["</tr></thead><tbody>"]
    for r in subset:
        src = r["source_file"]
        link = f"<a href='{DATASET}/blob/main/results/{html.escape(src)}'>{html.escape(src.split('/')[-1])}</a>"
        status_cls = "ok" if r["status"].startswith("valid") else "bad"
        cells = [
            f"<td><code>{html.escape(r['method'])}</code></td>",
            f"<td>{html.escape(r['base_model'])}</td>",
            f"<td>{r['n_domains']}</td>",
            f"<td>{r['n_seeds']}</td>",
            f"<td>{html.escape(r['retention_metric'])}</td>",
            f"<td class='num'>{r['retention_value_pct']}</td>",
            f"<td><span class='pill {status_cls}'>{html.escape(r['status'])}</span></td>",
            f"<td>{link}</td>",
            f"<td class='notes'>{html.escape(r['notes'])}</td>",
        ]
        out.append("<tr>" + "".join(cells) + "</tr>")
    out.append("</tbody></table></div>")
    return "".join(out)


sections = []
for suite, label in SUITES.items():
    valid = [r for r in rows if r["suite"] == suite and r["status"].startswith("valid")]
    if valid:
        sections.append(f"<h2>{html.escape(label)}</h2>{table(valid)}")

disclosed = [r for r in rows if not r["status"].startswith("valid")]
sections.append(
    "<h2>Invalid &amp; incomplete runs (disclosed)</h2>"
    "<p>Buggy or unfinished runs are <strong>relabeled, not deleted</strong>. Highlight: our early "
    "O-LoRA arm appeared to win (−2.0% forgetting) until we found a gradient-clipping bug that had "
    "frozen the model — so O-LoRA is listed as <em>invalid, never validly measured here</em>, not as "
    "<em>beaten</em>.</p>" + table(disclosed)
)

sections.append(
    "<h2>Submit your method</h2><ol>"
    f"<li>Run your method on a suite (<a href='{DATASET}/blob/main/protocol/PROTOCOL.md'>protocol</a>).</li>"
    f"<li>Score it with <a href='{DATASET}/blob/main/scoring/score.py'><code>scoring/score.py</code></a> "
    "(<code>--nll</code> or <code>--matrix</code>).</li>"
    "<li>Open a PR on the dataset repo adding your raw log, a <code>results.csv</code> row, and a provenance line.</li>"
    "</ol><p>Single-seed submissions are accepted and labeled <code>valid_single_run</code>. If your run "
    "later turns out buggy, it moves to the disclosed section — that's the deal for everyone, including us.</p>"
)

page = f"""<!doctype html>
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sequential Forgetting Leaderboard</title>
<style>
:root {{ --bg:#ffffff; --fg:#1a1a1a; --muted:#6a6a6a; --line:#e3e3e3; --accent:#e17100;
         --ok-bg:#e8f5e9; --ok-fg:#1b5e20; --bad-bg:#fdecea; --bad-fg:#8c1d18; --code:#f4f4f4; }}
@media (prefers-color-scheme: dark) {{
  :root {{ --bg:#101010; --fg:#eaeaea; --muted:#9a9a9a; --line:#2b2b2b; --accent:#ff9e2c;
           --ok-bg:#12331a; --ok-fg:#8fdf9f; --bad-bg:#3a1512; --bad-fg:#ff9d94; --code:#1d1d1d; }} }}
body {{ margin:0; background:var(--bg); color:var(--fg);
       font:16px/1.55 system-ui,-apple-system,Segoe UI,Roboto,sans-serif; }}
main {{ max-width:1100px; margin:0 auto; padding:2.5rem 1.25rem 4rem; }}
h1 {{ font-size:1.7rem; margin:0 0 .3rem; }} h2 {{ margin:2.2rem 0 .6rem; font-size:1.2rem; }}
.sub {{ color:var(--muted); margin:0 0 1.4rem; }}
a {{ color:var(--accent); text-decoration:none; }} a:hover {{ text-decoration:underline; }}
.tablewrap {{ overflow-x:auto; border:1px solid var(--line); border-radius:8px; }}
table {{ border-collapse:collapse; width:100%; font-size:.85rem; }}
th,td {{ text-align:left; padding:.5rem .65rem; border-bottom:1px solid var(--line); vertical-align:top; }}
th {{ color:var(--muted); font-weight:600; white-space:nowrap; }}
tr:last-child td {{ border-bottom:none; }}
td.num {{ font-variant-numeric:tabular-nums; font-weight:600; white-space:nowrap; }}
td.notes {{ color:var(--muted); min-width:220px; }}
code {{ background:var(--code); padding:.1rem .35rem; border-radius:4px; font-size:.82em; }}
.pill {{ padding:.12rem .5rem; border-radius:99px; font-size:.75rem; white-space:nowrap; }}
.pill.ok {{ background:var(--ok-bg); color:var(--ok-fg); }}
.pill.bad {{ background:var(--bad-bg); color:var(--bad-fg); }}
footer {{ margin-top:3rem; color:var(--muted); font-size:.85rem; border-top:1px solid var(--line); padding-top:1rem; }}
</style></head><body><main>
<h1>📉 Sequential Forgetting Leaderboard</h1>
<p class="sub">How much does sequential fine-tuning destroy what the model already learned?
Lower magnitude = better retention. <strong>Every number links to the raw run file</strong> in the
<a href="{DATASET}">benchmark dataset</a>; transcriptions are hand-checked
(<a href="{DATASET}/blob/main/results/PROVENANCE.md">provenance</a>).
Suites are <strong>not</strong> cross-comparable; each table ranks within its own protocol.</p>
{"".join(sections)}
<footer>Maintained by <a href="https://modelbrew.ai">ModelBrew</a> — fine-tuning without catastrophic
forgetting (patent-pending CRMA adapters). The <code>modular_crma</code> rows are our method;
independent replications welcome.</footer>
</main></body></html>
"""

(HERE / "index.html").write_text(page)
print(f"wrote index.html ({len(page)} bytes, {len(rows)} rows)")