jvonrad commited on
Commit
4d00ec3
·
verified ·
1 Parent(s): 60c88bd

Upload src/xscript/runmatrix.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/xscript/runmatrix.py +131 -0
src/xscript/runmatrix.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generate per-run training configs from a base yaml.
2
+
3
+ The design covers 9 mixtures (5 monolingual + 4 EN-anchored bilingual
4
+ mixtures) x 2 tokenizer conditions (starved / destarved) for one chosen BPE
5
+ flavor = 18 runs at 30B tokens each. Four of them (EN-DE and EN-AR under both
6
+ conditions) are additionally extended to 100B via a WSD cooldown branch off a
7
+ long stable trunk -- the cheap-extension mechanism.
8
+
9
+ A GPU-hour budget shortfall (2026-07-16: ~1200h available vs ~2615h needed
10
+ for the full 30-run all-pairs matrix) forced cutting the 12 non-EN-anchored
11
+ pairwise mixtures (de-fr, de-ar, de-zh, fr-ar, fr-zh, ar-zh) that a prior
12
+ expansion had added here; reverted to the original EN-anchored-only design.
13
+ Their partial checkpoints (0-12.4B tokens) are left on disk, unused.
14
+
15
+ A run's config is this base block plus: name, langs, probs, tok_name, schedule
16
+ overrides, and (for cooldowns) a `branch` pointing at a trunk checkpoint.
17
+
18
+ Run-name convention (kept in sync with eval.bts):
19
+ "<mix>__<tok>" e.g. en-ar__bl_destarved (30B deliverable)
20
+ "<mix>__<tok>__trunk" long stable trunk for extension
21
+ "<mix>__<tok>__100b" 100B cooldown deliverable
22
+ where <mix> is "en" or "en-ar" etc., <tok> is "<flavor>_<condition>".
23
+ """
24
+ import copy
25
+ from pathlib import Path
26
+
27
+ from .langs import LANGS, ANCHOR, PARTNERS, tok_conditions, tok_name
28
+ from .paths import run_dir
29
+
30
+ BILINGUAL_MIXTURES = [f"{ANCHOR}-{p}" for p in PARTNERS]
31
+ MIXTURES = [*LANGS, *BILINGUAL_MIXTURES]
32
+ # Cells extended to 100B via cooldown branch (plan's recommendation): one
33
+ # same-script (EN-DE) and one cross-script (EN-AR) partner, both tokenizer
34
+ # conditions = 4 extended runs. The same-vs-cross contrast at 100B is the point.
35
+ EXTENDED_MIXTURES = [f"{ANCHOR}-de", f"{ANCHOR}-ar"]
36
+ TRUNK_TOKENS = 80e9
37
+ # cooldown budgets: {final_total_tokens: (branch_at_trunk_tokens, decay_tokens)}
38
+ COOLDOWNS = {30e9: (24e9, 6e9), 100e9: (80e9, 20e9)}
39
+
40
+
41
+ def load_base(path) -> dict:
42
+ from . import _yaml
43
+ return _yaml.load(path)
44
+
45
+
46
+ def mix_langs(mix: str) -> list[str]:
47
+ return mix.split("-")
48
+
49
+
50
+ def _stamp(base: dict, name: str, mix: str, tok: str) -> dict:
51
+ cfg = copy.deepcopy(base)
52
+ cfg["name"] = name
53
+ cfg["langs"] = mix_langs(mix)
54
+ cfg["tok_name"] = tok
55
+ cfg.setdefault("probs", None) # None -> uniform (token-level 50/50 bilingual)
56
+ return cfg
57
+
58
+
59
+ def wsd_run(base, mix, tok) -> dict:
60
+ """Self-contained warmup+stable+decay to 30B (base schedule as-is)."""
61
+ return _stamp(base, f"{mix}__{tok}", mix, tok)
62
+
63
+
64
+ def trunk_run(base, mix, tok) -> dict:
65
+ cfg = _stamp(base, f"{mix}__{tok}__trunk", mix, tok)
66
+ warm = cfg["schedule"]["warmup_tokens"]
67
+ cfg["schedule"] = {**cfg["schedule"],
68
+ "stable_tokens": TRUNK_TOKENS - warm, "decay_tokens": 0}
69
+ cfg["train"] = {**cfg["train"],
70
+ "stable_marks": sorted({b for b, _ in COOLDOWNS.values()})}
71
+ return cfg
72
+
73
+
74
+ def cooldown_run(base, mix, tok, total_tokens) -> dict:
75
+ branch_at, decay = COOLDOWNS[total_tokens]
76
+ suffix = "" if total_tokens == 30e9 else f"__{int(total_tokens/1e9)}b"
77
+ cfg = _stamp(base, f"{mix}__{tok}{suffix}", mix, tok)
78
+ cfg["schedule"] = {**cfg["schedule"], "warmup_tokens": 0,
79
+ "stable_tokens": 0, "decay_tokens": decay}
80
+ trunk_ckpt = (run_dir(f"{mix}__{tok}__trunk") / "checkpoints"
81
+ / f"stable_{int(branch_at/1e6)}M.pt")
82
+ cfg["branch"] = {"from": str(trunk_ckpt), "load_optim": True}
83
+ return cfg
84
+
85
+
86
+ def all_runs(base: dict, flavor: str) -> dict[str, dict]:
87
+ """Every run needed for the full experiment, keyed by run name."""
88
+ runs: dict[str, dict] = {}
89
+ for mix in MIXTURES:
90
+ for cond in tok_conditions(flavor):
91
+ tok = tok_name(flavor, cond)
92
+ if mix in EXTENDED_MIXTURES:
93
+ runs[f"{mix}__{tok}__trunk"] = trunk_run(base, mix, tok)
94
+ for total in COOLDOWNS:
95
+ r = cooldown_run(base, mix, tok, total)
96
+ runs[r["name"]] = r
97
+ else:
98
+ r = wsd_run(base, mix, tok)
99
+ runs[r["name"]] = r
100
+ return runs
101
+
102
+
103
+ def all_30b_runs(base: dict, flavor: str) -> dict[str, dict]:
104
+ """The 30 independent 30B cells, without 100B-extension trunks.
105
+
106
+ This mode is useful when the immediate experiment scope is only 30B. In
107
+ the full matrix, four of these names are cooldown branches from shared
108
+ trunks; here every name instead gets the base self-contained WSD schedule.
109
+ """
110
+ runs: dict[str, dict] = {}
111
+ for mix in MIXTURES:
112
+ for cond in tok_conditions(flavor):
113
+ tok = tok_name(flavor, cond)
114
+ run = wsd_run(base, mix, tok)
115
+ runs[run["name"]] = run
116
+ return runs
117
+
118
+
119
+ def get_run(base_path, flavor: str, name: str, only_30b: bool = False) -> dict:
120
+ base = load_base(base_path)
121
+ runs = all_30b_runs(base, flavor) if only_30b else all_runs(base, flavor)
122
+ if name not in runs:
123
+ raise KeyError(f"unknown run '{name}'. Available:\n " +
124
+ "\n ".join(sorted(runs)))
125
+ return runs[name]
126
+
127
+
128
+ def list_runs(base_path, flavor: str, only_30b: bool = False) -> list[str]:
129
+ base = load_base(base_path)
130
+ runs = all_30b_runs(base, flavor) if only_30b else all_runs(base, flavor)
131
+ return sorted(runs)