Skip to content

Commit 8d79e31

Browse files
committed
Snapshot before JURECA access ends
1 parent bd6a16f commit 8d79e31

9 files changed

Lines changed: 723 additions & 61 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ data/
4646
*.log
4747
logs/
4848

49+
# JURECA-specific scripts (kept locally; backed up separately)
50+
*.slurm
51+
core.*
52+
4953
# OS
5054
.DS_Store
5155
Thumbs.db

scripts/analyze.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,31 @@ def build_parser() -> argparse.ArgumentParser:
238238
help="Truncated LeCun Normal initialization (HRM-style)")
239239
arith.add_argument("--use-adam-atan2", action="store_true",
240240
help="Adam-atan2 scale-invariant optimizer (HRM-style)")
241+
# H/L split (Ablation 3)
242+
arith.add_argument("--use-hl-split", action="store_true",
243+
help="H/L module split: slow H layers + fast L inner loop")
244+
arith.add_argument("--n-h-layers", type=int, default=2,
245+
help="Number of H (slow) layers in H/L split")
246+
arith.add_argument("--t-inner", type=int, default=2,
247+
help="L inner loop steps in H/L split")
248+
arith.add_argument("--t-outer", type=int, default=2,
249+
help="H outer loop steps in H/L split")
250+
# Q-learning ACT (Ablation 5)
251+
arith.add_argument("--use-q-act", action="store_true",
252+
help="Q-learning ACT: replace exit gate with Q-head")
253+
arith.add_argument("--q-weight", type=float, default=0.1,
254+
help="Weight for Q-learning loss")
255+
arith.add_argument("--q-gamma", type=float, default=0.99,
256+
help="Discount factor for Q-learning TD targets")
257+
# MoE-layer recurrence (Ablation 6)
258+
arith.add_argument("--use-moe-recurrence", action="store_true",
259+
help="MoE-layer recurrence: route to expert layers per step")
260+
arith.add_argument("--num-expert-layers", type=int, default=4,
261+
help="Number of expert TransformerBlocks")
262+
arith.add_argument("--moe-top-k", type=int, default=2,
263+
help="Experts activated per token")
264+
arith.add_argument("--moe-load-balance-weight", type=float, default=0.01,
265+
help="Load balancing loss weight for MoE")
241266
arith.add_argument("--extrap-eval", nargs="+", type=int, default=None,
242267
help="Test at these T values after training (extrapolation)")
243268
arith.add_argument("--n-eval", type=int, default=500)
@@ -718,6 +743,17 @@ def run_arith(args) -> None:
718743
use_postnorm=args.use_postnorm,
719744
use_lecun_init=args.use_lecun_init,
720745
use_adam_atan2=args.use_adam_atan2,
746+
use_hl_split=args.use_hl_split,
747+
n_h_layers=args.n_h_layers,
748+
t_inner=args.t_inner,
749+
t_outer=args.t_outer,
750+
use_q_act=args.use_q_act,
751+
q_weight=args.q_weight,
752+
q_gamma=args.q_gamma,
753+
use_moe_recurrence=args.use_moe_recurrence,
754+
num_expert_layers=args.num_expert_layers,
755+
moe_top_k=args.moe_top_k,
756+
moe_load_balance_weight=args.moe_load_balance_weight,
721757
extrap_eval_steps=args.extrap_eval,
722758
n_eval=args.n_eval,
723759
eval_every=args.eval_every,

scripts/eval_checkpoints.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env python
2+
"""Evaluate LoopLM checkpoints on lm-eval benchmarks.
3+
4+
Usage:
5+
uv run scripts/eval_checkpoints.py \
6+
--checkpoint /path/to/step_0008000.pt \
7+
--tasks hellaswag,arc_easy \
8+
--limit 500
9+
"""
10+
11+
import argparse
12+
import json
13+
import sys
14+
from pathlib import Path
15+
16+
import torch
17+
18+
sys.path.insert(0, str(Path(__file__).parent.parent))
19+
20+
from src.model.config import LoopLMConfig
21+
from src.model.looplm import LoopLM
22+
from src.inference.lm_eval_wrapper import LoopLMLM, run_eval, _extract_acc
23+
from transformers import AutoTokenizer
24+
25+
26+
def main():
27+
p = argparse.ArgumentParser(description="Evaluate LoopLM checkpoint")
28+
p.add_argument("--checkpoint", required=True, help="Path to .pt checkpoint")
29+
p.add_argument("--tasks", default="hellaswag,arc_easy")
30+
p.add_argument("--limit", type=int, default=None)
31+
p.add_argument("--tokenizer-id", default="HuggingFaceTB/SmolLM2-135M")
32+
p.add_argument("--batch-size", type=int, default=4)
33+
args = p.parse_args()
34+
35+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
36+
tasks = [t.strip() for t in args.tasks.split(",") if t.strip()]
37+
38+
print(f"Loading checkpoint: {args.checkpoint}")
39+
ckpt = torch.load(args.checkpoint, map_location="cpu", weights_only=False)
40+
41+
model_cfg = LoopLMConfig(**ckpt["model_config"])
42+
print(f"Model: L={model_cfg.num_layers}, T={model_cfg.max_recurrent_steps}, "
43+
f"h={model_cfg.hidden_size} ({model_cfg.num_parameters()/1e6:.1f}M params)")
44+
45+
# Detect if model was trained with Q-ACT by checking for q_head in state dict
46+
state_dict = ckpt["model_state_dict"]
47+
use_q_act = any("q_head" in k for k in state_dict)
48+
49+
model = LoopLM(model_cfg, use_q_act=use_q_act).to(device)
50+
model.load_state_dict(state_dict)
51+
model.eval()
52+
53+
tokenizer = AutoTokenizer.from_pretrained(args.tokenizer_id)
54+
max_T = model_cfg.max_recurrent_steps
55+
56+
print(f"\nEvaluating tasks={tasks}, T=1..{max_T}, limit={args.limit}")
57+
print(f"{'T':>3} | " + " | ".join(f"{t:^14}" for t in tasks))
58+
print("-" * (6 + 17 * len(tasks)))
59+
60+
all_results = {}
61+
for T in range(1, max_T + 1):
62+
wrapper = LoopLMLM(model, tokenizer, device, num_steps=T, batch_size=args.batch_size)
63+
raw = run_eval(wrapper, tasks, args.limit)
64+
row_parts = []
65+
for task in tasks:
66+
acc = _extract_acc(raw, task)
67+
if acc is not None:
68+
all_results[f"{task}/T{T}"] = acc
69+
row_parts.append(f"{acc:^14.4f}")
70+
else:
71+
row_parts.append(f"{'N/A':^14}")
72+
print(f"{T:>3} | " + " | ".join(row_parts))
73+
74+
# Save results
75+
out_path = Path(args.checkpoint).parent.parent / "eval_results.json"
76+
with open(out_path, "w") as f:
77+
json.dump(all_results, f, indent=2)
78+
print(f"\nResults saved to {out_path}")
79+
80+
81+
if __name__ == "__main__":
82+
main()

scripts/train.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ def parse_args():
3535
# Model
3636
p.add_argument("--model-config", default="small",
3737
choices=["small", "ouro_1_4b", "ouro_2_6b"])
38+
# Model config overrides (applied after loading preset)
39+
p.add_argument("--hidden-size", type=int, default=None)
40+
p.add_argument("--num-layers", type=int, default=None)
41+
p.add_argument("--num-heads", type=int, default=None)
42+
p.add_argument("--intermediate-size", type=int, default=None)
43+
# Ablation flags
44+
p.add_argument("--use-q-act", action="store_true")
45+
p.add_argument("--q-weight", type=float, default=0.1)
46+
p.add_argument("--q-gamma", type=float, default=0.99)
3847

3948
# Data
4049
p.add_argument("--dataset", default="wikitext")
@@ -91,14 +100,25 @@ def parse_args():
91100
return p.parse_args()
92101

93102

94-
def build_model_config(name: str, seq_len: int) -> LoopLMConfig:
103+
def build_model_config(args) -> LoopLMConfig:
95104
configs = {
96105
"small": LoopLMConfig.small(),
97106
"ouro_1_4b": LoopLMConfig.ouro_1_4b(),
98107
"ouro_2_6b": LoopLMConfig.ouro_2_6b(),
99108
}
100-
cfg = configs[name]
101-
cfg.max_seq_len = seq_len
109+
cfg = configs[args.model_config]
110+
cfg.max_seq_len = args.seq_len
111+
# Apply overrides
112+
if args.hidden_size is not None:
113+
cfg.hidden_size = args.hidden_size
114+
if args.num_layers is not None:
115+
cfg.num_layers = args.num_layers
116+
if args.num_heads is not None:
117+
cfg.num_heads = args.num_heads
118+
if args.intermediate_size is not None:
119+
cfg.intermediate_size = args.intermediate_size
120+
if args.num_recurrent_steps is not None:
121+
cfg.max_recurrent_steps = args.num_recurrent_steps
102122
return cfg
103123

104124

@@ -188,7 +208,7 @@ def main():
188208
rank = dist.get_rank()
189209
world_size = dist.get_world_size()
190210

191-
model_cfg = build_model_config(args.model_config, args.seq_len)
211+
model_cfg = build_model_config(args)
192212
eval_tasks = [t.strip() for t in args.eval_tasks.split(",") if t.strip()]
193213

194214
# Build multi-stage list (each non-zero step value enables that stage)
@@ -222,12 +242,25 @@ def main():
222242
eval_tasks=eval_tasks,
223243
eval_limit=args.eval_limit,
224244
tokenizer_id=args.tokenizer_id,
245+
q_weight=args.q_weight,
246+
q_gamma=args.q_gamma,
225247
)
226248

249+
# Model kwargs for ablation flags
250+
model_kwargs = {}
251+
if args.use_q_act:
252+
model_kwargs["use_q_act"] = True
253+
227254
n_params = model_cfg.num_parameters()
228255
eff_batch_tokens = args.batch_size * args.grad_accum_steps * args.seq_len * world_size
229256
if rank == 0:
230-
print(f"Model: {args.model_config} ({n_params/1e6:.1f}M params)")
257+
overrides = []
258+
if args.hidden_size: overrides.append(f"h={args.hidden_size}")
259+
if args.num_layers: overrides.append(f"L={args.num_layers}")
260+
if args.num_recurrent_steps: overrides.append(f"T={args.num_recurrent_steps}")
261+
if args.use_q_act: overrides.append("Q-ACT")
262+
override_str = f" [{', '.join(overrides)}]" if overrides else ""
263+
print(f"Model: {args.model_config}{override_str} ({n_params/1e6:.1f}M params)")
231264
if distributed:
232265
print(f"Distributed: {world_size} GPUs (DDP)")
233266
if stages:
@@ -262,7 +295,7 @@ def main():
262295
max_chunks=args.max_chunks,
263296
)
264297

265-
trainer = Trainer(model_cfg, trainer_cfg)
298+
trainer = Trainer(model_cfg, trainer_cfg, model_kwargs=model_kwargs)
266299
num_steps = args.num_recurrent_steps or model_cfg.max_recurrent_steps
267300

268301
if args.gradient_checkpointing:

0 commit comments

Comments
 (0)