Theskywalker07/full_model_1
119
1import os
2import math
3import time
4import json
5import random
6import inspect
7import shutil
8import subprocess
9import argparse
10
11# ─────────────────────────────────────────────────────────────
12# 1. CORE PIPELINE FUNCTION AND HELPERS
13# ─────────────────────────────────────────────────────────────
14
15def is_zero_shot_cache_valid(cache_dir):
16 if not os.path.exists(cache_dir):
17 return False
18 pred_count = 0
19 for root, dirs, files in os.walk(cache_dir):
20 if "predictions.json" in files:
21 pred_count += 1
22 return pred_count >= 3
23
24def is_finetune_cache_valid(cache_dir):
25 if not os.path.exists(cache_dir):
26 return False
27 pred_count = 0
28 for root, dirs, files in os.walk(cache_dir):
29 if "predictions.json" in files:
30 pred_count += 1
31 return pred_count >= 3
32
33def verify_eval_run(path_to_check, description):
34 print(f"[Eval] Verifying {description} path: {path_to_check}...")
35 if not os.path.exists(path_to_check):
36 raise RuntimeError(f"CRITICAL ERROR: {description} directory was NOT created at: {path_to_check}")
37
38 # Check if predictions.json or results.txt or surprisal.json exists and is non-empty
39 found_valid = False
40 for root, dirs, files in os.walk(path_to_check):
41 for f in files:
42 if f in ["predictions.json", "results.txt", "surprisal.json"]:
43 file_path = os.path.join(root, f)
44 if os.path.getsize(file_path) > 0:
45 found_valid = True
46 break
47 if found_valid:
48 break
49
50 if not found_valid:
51 raise RuntimeError(f"CRITICAL ERROR: {description} completed but no valid prediction/result files were written in: {path_to_check}")
52 print(f"[Eval] Success! Verified {description} results are stored correctly.")
53
54def run_pipeline(model_name: str, epochs: int = 10, skip_eval: bool = False, skip_aoa: bool = True, skip_glue: bool = False):
55 # Configure persistent cache paths locally to avoid duplicate downloads
56 os.environ["HF_HOME"] = os.path.abspath("./hf_cache")
57 os.environ["NLTK_DATA"] = os.path.abspath("./nltk_data")
58 os.makedirs("./hf_cache", exist_ok=True)
59 os.makedirs("./nltk_data", exist_ok=True)
60
61 # Programmatic Hugging Face Hub Login if HF_TOKEN is in environment
62 hf_token = os.environ.get("HF_TOKEN")
63
64 if hf_token:
65 try:
66 from huggingface_hub import login
67 login(token=hf_token)
68 print("[HF] Programmatic login successful using HF_TOKEN.")
69 except Exception as e:
70 print(f"[HF] Warning: Programmatic login failed: {e}")
71
72 import torch
73 import torch.nn as nn
74 import torch.nn.functional as F
75 from datasets import load_dataset
76 from tokenizers import Tokenizer
77 from transformers import PreTrainedTokenizerFast
78
79 # Import model architecture
80 from modeling_xpertgpt import (
81 XpertGPTModel,
82 XpertGPTModelConfig,
83 XpertGPTConfig
84 )
85
86 # Print GPU details
87 if torch.cuda.is_available():
88 gpu_name = torch.cuda.get_device_name(0)
89 print(f"\n[GPU] CUDA is available! Using GPU: {gpu_name}\n")
90 else:
91 print("\n[GPU] Warning: CUDA is NOT available! Running on CPU.\n")
92
93 # ─────────────────────────────────────────────────────────────
94 # GLOBAL HYPERPARAMETERS
95 # ─────────────────────────────────────────────────────────────
96 VOCAB_SIZE = 16384
97 MASK_TOKEN_ID = 16383
98 BLOCK_SIZE = 512
99 BATCH_SIZE = 16
100 GRAD_ACCUM_STEPS = 1 # grad_acc_step = 1
101 EPOCHS = epochs
102 LEARNING_RATE = 3e-4 # lr = 3e-4
103 LR_MIN = LEARNING_RATE * 0.05
104 WARMUP_STEPS = 800 # warmup_steps = 800
105 WEIGHT_DECAY = 0.1
106 GRAD_CLIP = 1.0
107
108 NUM_THIN_BLOCKS = 4
109 EC_CAPACITY_FACTOR = 2.0
110 CAUSAL_RATIO = 1 / 1
111
112 MASK_PROB_START = 0.20
113 MASK_PROB_END = 0.10
114
115 # Output directories locally
116 model_dir = os.path.abspath(f"./checkpoints/{model_name}")
117 os.makedirs(model_dir, exist_ok=True)
118 local_results_dir = os.path.abspath(f"./results/{model_name}")
119 os.makedirs(local_results_dir, exist_ok=True)
120
121
122
123 # ─────────────────────────────────────────────────────────────
124 # Helper: Save Hugging Face Compliant Checkpoint
125 # ─────────────────────────────────────────────────────────────
126 def save_hf_checkpoint(raw_model, checkpoint_dir_name, tokenizer):
127 save_dir = os.path.join(model_dir, checkpoint_dir_name)
128 os.makedirs(save_dir, exist_ok=True)
129 print(f"\n[Checkpoint] Saving Hugging Face format checkpoint to '{save_dir}'...")
130
131 # A. Convert state dict keys to CausalLM wrapper naming
132 state_dict = raw_model.state_dict()
133 new_state_dict = {}
134 for k, v in state_dict.items():
135 name = k
136 if name.startswith("_orig_mod."):
137 name = name[10:]
138 if name.startswith("model."):
139 name = name[6:]
140
141 if name == "lm_head.weight":
142 new_state_dict["lm_head.weight"] = v
143 else:
144 new_state_dict[f"transformer.{name}"] = v
145
146 torch.save(new_state_dict, os.path.join(save_dir, "pytorch_model.bin"))
147
148 # B. Copy modeling.py and configuration.py
149 shutil.copy("modeling_xpertgpt.py", os.path.join(save_dir, "modeling_xpertgpt.py"))
150 shutil.copy("configuration_xpertgpt.py", os.path.join(save_dir, "configuration_xpertgpt.py"))
151
152 # C. Create config.json
153 config_dict = {
154 "auto_map": {
155 "AutoConfig": "configuration_xpertgpt.XpertGPTConfig",
156 "AutoModel": "modeling_xpertgpt.XpertGPTModelWrapper",
157 "AutoModelForCausalLM": "modeling_xpertgpt.XpertGPTForCausalLM"
158 },
159 "vocab_size": VOCAB_SIZE,
160 "block_size": BLOCK_SIZE,
161 "d_model": 256, # d_model = 256
162 "hidden_size": 256, # hidden_size = 256
163 "d_thin": 384,
164 "num_layers": 6,
165 "num_blocks": NUM_THIN_BLOCKS,
166 "capacity_factor": EC_CAPACITY_FACTOR,
167 "dropout": 0.1,
168 "model_type": "xpertgpt"
169 }
170 with open(os.path.join(save_dir, "config.json"), "w") as f:
171 json.dump(config_dict, f, indent=2)
172
173 # D. Save tokenizer config files
174 fast_tokenizer = PreTrainedTokenizerFast(
175 tokenizer_object=tokenizer,
176 bos_token="[CLS]",
177 eos_token="[SEP]",
178 unk_token="[UNK]",
179 pad_token="[PAD]",
180 mask_token="[MASK]"
181 )
182 # E. Copy loss metrics
183 main_loss_csv = os.path.join(model_dir, "loss_history.csv")
184 main_loss_json = os.path.join(model_dir, "loss_metrics.json")
185 if os.path.exists(main_loss_csv):
186 shutil.copy2(main_loss_csv, os.path.join(save_dir, "loss_history.csv"))
187 if os.path.exists(main_loss_json):
188 shutil.copy2(main_loss_json, os.path.join(save_dir, "loss_metrics.json"))
189
190 print(f"[Checkpoint] Checkpoint '{checkpoint_dir_name}' successfully saved.")
191
192 # ─────────────────────────────────────────────────────────────
193 # Tokenizer Training
194 # ─────────────────────────────────────────────────────────────
195 def build_and_train_tokenizer(texts: list) -> Tokenizer:
196 from tokenizers.models import BPE
197 from tokenizers.trainers import BpeTrainer
198 from tokenizers.pre_tokenizers import Whitespace
199
200 vocab_path = os.path.join(model_dir, "bpe_vocab_16k.json")
201 if os.path.exists(vocab_path):
202 print(f"[Tokenizer] Loading trained BPE model layout from '{vocab_path}'...")
203 return Tokenizer.from_file(vocab_path)
204
205 print(f"[Tokenizer] Generating fresh HuggingFace BPE Tokenizer model with {VOCAB_SIZE} slots...")
206 tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
207 tokenizer.pre_tokenizer = Whitespace()
208
209 trainer = BpeTrainer(
210 vocab_size=VOCAB_SIZE,
211 special_tokens=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]
212 )
213 tokenizer.train_from_iterator(texts, trainer)
214 tokenizer.save(vocab_path)
215 print(f"[Tokenizer] Tokenizer training completed and saved to '{vocab_path}'.")
216 return tokenizer
217
218 # ─────────────────────────────────────────────────────────────
219 # Data Loader Setup
220 # ─────────────────────────────────────────────────────────────
221 class DataLoaderLite:
222 def __init__(self, B: int, T: int, texts: list, tokenizer: Tokenizer, name: str):
223 self.B = B
224 self.T = T
225
226 print(f"[DataLoader:{name}] Tokenising dataset sequences...")
227 all_ids = []
228 for t in texts:
229 if t.strip():
230 encoded = tokenizer.encode(t).ids
231 all_ids.extend(encoded)
232
233 self.tokens = torch.tensor(all_ids, dtype=torch.long)
234 self.chunk_size = B * T
235 self.n_chunks = (len(self.tokens) - 1) // self.chunk_size
236 self.indices = list(range(self.n_chunks))
237 self.pos = 0
238 self._shuffle()
239
240 print(f"[DataLoader:{name}] Total tokens: {len(self.tokens):,} | Epoch steps: {self.n_chunks:,}")
241
242 def _shuffle(self):
243 random.shuffle(self.indices)
244 self.pos = 0
245
246 def steps_per_epoch(self) -> int:
247 return self.n_chunks
248
249 def next_batch(self):
250 B, T = self.B, self.T
251 if self.pos >= len(self.indices):
252 self._shuffle()
253
254 chunk_idx = self.indices[self.pos]
255 self.pos += 1
256
257 start_pos = chunk_idx * self.chunk_size
258 temp = self.tokens[start_pos : start_pos + self.chunk_size + 1]
259
260 x = temp[:-1].view(B, T)
261 y = temp[1:].view(B, T)
262 return x, y
263
264 # ─────────────────────────────────────────────────────────────
265 # Batch preparation and schedules
266 # ─────────────────────────────────────────────────────────────
267 def get_current_mask_prob(global_step: int, total_steps: int) -> float:
268 ratio = min(1.0, global_step / total_steps)
269 return MASK_PROB_START + ratio * (MASK_PROB_END - MASK_PROB_START)
270
271 def prepare_causal_batch(x: torch.Tensor, y: torch.Tensor):
272 return x, y, False
273
274 def prepare_masked_batch(x: torch.Tensor, y: torch.Tensor, mask_prob: float, mask_token_id: int):
275 B, T = x.size()
276 mask = torch.rand(B, T, device=x.device) < mask_prob
277 masked_x = x.clone()
278 masked_x[mask] = mask_token_id
279
280 targets = torch.full_like(y, -100)
281 targets[mask] = y[mask]
282
283 return masked_x, targets, True
284
285 def get_hybrid_batch(train_loader: DataLoaderLite, global_step: int, total_steps: int, device: torch.device):
286 x, y = train_loader.next_batch()
287 x, y = x.to(device), y.to(device)
288
289 if random.random() < CAUSAL_RATIO:
290 input_ids, targets, bidir = prepare_causal_batch(x, y)
291 else:
292 mask_prob = get_current_mask_prob(global_step, total_steps)
293 input_ids, targets, bidir = prepare_masked_batch(x, y, mask_prob, MASK_TOKEN_ID)
294
295 return input_ids, targets, bidir
296
297 def get_lr(it: int, total_steps: int) -> float:
298 if it < WARMUP_STEPS:
299 return LEARNING_RATE * (it + 1) / WARMUP_STEPS
300 if it >= total_steps:
301 return LR_MIN
302 decay_ratio = (it - WARMUP_STEPS) / (total_steps - WARMUP_STEPS)
303 coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio))
304 return LR_MIN + coeff * (LEARNING_RATE - LR_MIN)
305
306 # ─────────────────────────────────────────────────────────────
307 # Dataset Preparation
308 # ─────────────────────────────────────────────────────────────
309 print("\n[Data] Loading BabyLM-2026-Strict-Small ...")
310 ds = load_dataset("BabyLM-community/BabyLM-2026-Strict-Small")
311 all_text = list(ds['train']['text'])
312
313 tokenizer = build_and_train_tokenizer(all_text)
314
315 split = int(len(all_text) * 0.95)
316 train_texts = all_text[:split]
317 val_texts = all_text[split:]
318
319 train_loader = DataLoaderLite(BATCH_SIZE, BLOCK_SIZE, train_texts, tokenizer, "train")
320 val_loader = DataLoaderLite(BATCH_SIZE, BLOCK_SIZE, val_texts, tokenizer, "val")
321
322 def evaluate_val_loss(model_to_eval, v_loader, dev, autocast_context, num_batches=30):
323 raw_m = model_to_eval._orig_mod if hasattr(model_to_eval, '_orig_mod') else model_to_eval
324 raw_m.eval()
325 total_v_loss = 0.0
326 eval_steps = min(num_batches, v_loader.steps_per_epoch())
327 with torch.no_grad():
328 for _ in range(eval_steps):
329 x_v, y_v = v_loader.next_batch()
330 x_v, y_v = x_v.to(dev), y_v.to(dev)
331 with autocast_context:
332 _, loss_v = raw_m(x_v, y_v, bidirectional=False)
333 total_v_loss += loss_v.item()
334 raw_m.train()
335 return total_v_loss / max(1, eval_steps)
336
337 chunks_per_epoch = train_loader.steps_per_epoch()
338 steps_per_epoch = chunks_per_epoch // GRAD_ACCUM_STEPS
339 total_steps = steps_per_epoch * EPOCHS
340
341 cfg = XpertGPTModelConfig()
342 device = "cuda" if torch.cuda.is_available() else "cpu"
343 torch.manual_seed(42)
344 if torch.cuda.is_available():
345 torch.cuda.manual_seed(42)
346 random.seed(42)
347 if hasattr(torch, 'set_float32_matmul_precision'):
348 torch.set_float32_matmul_precision('high')
349
350 model = XpertGPTModel(cfg).to(device)
351
352 # ─────────────────────────────────────────────────────────────
353 # Training Resume Check
354 # ─────────────────────────────────────────────────────────────
355 words_trained = 0
356 next_milestone_idx = 0
357 global_step = 0
358
359 milestones = sorted(list(set([i * 1_000_000 for i in range(1, 11)] + [i * 10_000_000 for i in range(1, 11)])))
360
361 resume_checkpoint_dir = None
362 for idx in range(len(milestones) - 1, -1, -1):
363 m = milestones[idx]
364 ckpt_name = f"chck_{m // 1_000_000}M"
365 ckpt_path = os.path.join(model_dir, ckpt_name)
366 if os.path.exists(os.path.join(ckpt_path, "pytorch_model.bin")):
367 config_json_path = os.path.join(ckpt_path, "config.json")
368 if os.path.exists(config_json_path):
369 try:
370 with open(config_json_path, "r") as f:
371 saved_config = json.load(f)
372 if saved_config.get("d_model") == 256:
373 resume_checkpoint_dir = ckpt_path
374 next_milestone_idx = idx + 1
375 words_trained = m
376 global_step = words_trained // (BATCH_SIZE * BLOCK_SIZE)
377 print(f"[Training] Found existing milestone checkpoint '{ckpt_name}'. Resuming from step {global_step:,} ({words_trained:,} tokens trained)...")
378 break
379 else:
380 print(f"[Training] Found checkpoint '{ckpt_name}' but it has mismatch d_model={saved_config.get('d_model')}. Starting fresh.")
381 except Exception as e:
382 pass
383
384 # Load weights if resuming
385 if resume_checkpoint_dir is not None:
386 print(f"[Model] Loading weights from checkpoint '{resume_checkpoint_dir}'...")
387 state_dict = torch.load(os.path.join(resume_checkpoint_dir, "pytorch_model.bin"), map_location=device)
388 model_state_dict = {}
389 for k, v in state_dict.items():
390 name = k
391 if name.startswith("transformer."):
392 name = name[12:]
393 model_state_dict[name] = v
394 model.load_state_dict(model_state_dict)
395
396 # Check if final main model exists
397 main_ckpt_path = os.path.join(model_dir, "main")
398 if os.path.exists(os.path.join(main_ckpt_path, "pytorch_model.bin")):
399 print("\n[Pipeline] Final checkpoint 'main' already exists. Skipping training phase and transitioning directly to evaluations!")
400 else:
401 # torch.compile
402 try:
403 model = torch.compile(model)
404 print("[Model] torch.compile() successfully verified graph optimizations")
405 except Exception as e:
406 print(f"[Model] torch.compile() skipped ({e})")
407
408 # Optimizer
409 param_dict = {n: p for n, p in model.named_parameters() if p.requires_grad}
410 decay_params = [p for p in param_dict.values() if p.dim() >= 2]
411 nodecay_params = [p for p in param_dict.values() if p.dim() < 2]
412 groups = [
413 {'params': decay_params, 'weight_decay': WEIGHT_DECAY},
414 {'params': nodecay_params, 'weight_decay': 0.0},
415 ]
416 fused_ok = 'fused' in inspect.signature(torch.optim.AdamW).parameters
417 use_fused = fused_ok and ('cuda' in device)
418 optimizer = torch.optim.AdamW(groups, lr=LEARNING_RATE, betas=(0.9, 0.95), eps=1e-8, fused=use_fused)
419
420 # ─────────────────────────────────────────────────────────────
421 # Training Loop & Loss Metrics Logging
422 # ─────────────────────────────────────────────────────────────
423 model.train()
424 autocast_ctx = torch.autocast(device_type="cuda" if "cuda" in device else "cpu", dtype=torch.bfloat16, enabled=True)
425
426 loss_history_csv = os.path.join(model_dir, "loss_history.csv")
427 loss_history_json = os.path.join(model_dir, "loss_metrics.json")
428 os.makedirs(model_dir, exist_ok=True)
429 if not os.path.exists(loss_history_csv):
430 with open(loss_history_csv, "w", encoding="utf-8") as f_csv:
431 f_csv.write("global_step,epoch,words_trained,train_loss,val_loss,lr\n")
432
433 loss_records = []
434 if os.path.exists(loss_history_json):
435 try:
436 with open(loss_history_json, "r", encoding="utf-8") as f_json:
437 loss_records = json.load(f_json)
438 except Exception:
439 loss_records = []
440
441 start_epoch = global_step // steps_per_epoch
442 start_chunk = (global_step % steps_per_epoch) * GRAD_ACCUM_STEPS
443
444 print(f"\n[Training] Starting XpertGPT MoEP training for {EPOCHS} epochs...")
445 for epoch in range(start_epoch, EPOCHS):
446 train_loader._shuffle()
447 if epoch == start_epoch and start_chunk > 0:
448 print(f"[Training] Fast-forwarding dataloader to chunk index {start_chunk}...")
449 train_loader.pos = start_chunk
450
451 optimizer.zero_grad(set_to_none=True)
452 loss_accum = 0.0
453
454 start_chunk_idx = start_chunk if epoch == start_epoch else 0
455 for chunk_step in range(start_chunk_idx, chunks_per_epoch):
456 t0 = time.perf_counter()
457
458 lr = get_lr(global_step, total_steps)
459 for pg in optimizer.param_groups:
460 pg['lr'] = lr
461
462 input_ids, targets, bidir = get_hybrid_batch(train_loader, global_step, total_steps, device)
463 words_trained += input_ids.numel()
464
465 with autocast_ctx:
466 _, loss = model(input_ids, targets, bidirectional=bidir)
467 scaled_loss = loss / GRAD_ACCUM_STEPS
468 loss_accum += scaled_loss.item()
469 scaled_loss.backward()
470
471 # Optimizer Step
472 if (chunk_step + 1) % GRAD_ACCUM_STEPS == 0:
473 norm = torch.nn.utils.clip_grad_norm_(model.parameters(), GRAD_CLIP)
474 optimizer.step()
475 optimizer.zero_grad(set_to_none=True)
476 if "cuda" in device:
477 torch.cuda.synchronize()
478
479 dt = (time.perf_counter() - t0) * 1000
480 mode_tag = "MLM" if bidir else "CLM"
481 mask_p = get_current_mask_prob(global_step, total_steps)
482 current_step = (chunk_step + 1) // GRAD_ACCUM_STEPS
483
484 # Evaluate validation loss every 50 steps (and at step 1)
485 val_loss = None
486 if (global_step + 1) == 1 or (global_step + 1) % 50 == 0:
487 val_loss = evaluate_val_loss(model, val_loader, device, autocast_ctx)
488
489 val_str = f" val={val_loss:.4f}" if val_loss is not None else ""
490 print(
491 f"[E{epoch+1:02d} {current_step:>5d}/{steps_per_epoch} G{global_step+1:>7d}|{mode_tag}] "
492 f"train={loss_accum:.4f}{val_str} mask={mask_p:.1%} norm={norm:.3f} lr={lr:.2e} dt={dt:6.1f}ms words={words_trained:,}"
493 )
494
495 # Log to CSV and JSON
496 rec = {
497 "global_step": global_step + 1,
498 "epoch": epoch + 1,
499 "words_trained": words_trained,
500 "train_loss": round(loss_accum, 5),
501 "val_loss": round(val_loss, 5) if val_loss is not None else None,
502 "lr": lr
503 }
504 loss_records.append(rec)
505
506 val_csv_str = f"{val_loss:.5f}" if val_loss is not None else ""
507 with open(loss_history_csv, "a", encoding="utf-8") as f_csv:
508 f_csv.write(f"{global_step+1},{epoch+1},{words_trained},{loss_accum:.5f},{val_csv_str},{lr}\n")
509
510 with open(loss_history_json, "w", encoding="utf-8") as f_json:
511 json.dump(loss_records, f_json, indent=2)
512
513 loss_accum = 0.0
514 global_step += 1
515
516 # Check if we passed a milestone for checkpointing
517 if next_milestone_idx < len(milestones) and words_trained >= milestones[next_milestone_idx]:
518 milestone_val = milestones[next_milestone_idx]
519 if milestone_val < 10_000_000:
520 milestone_name = f"chck_{milestone_val // 1_000_000}M"
521 else:
522 milestone_name = f"chck_{(milestone_val // 10_000_000) * 10}M"
523
524 raw_model = model._orig_mod if hasattr(model, '_orig_mod') else model
525 save_hf_checkpoint(raw_model, milestone_name, tokenizer)
526 next_milestone_idx += 1
527
528 # Save final model as 'main'
529 raw_model = model._orig_mod if hasattr(model, '_orig_mod') else model
530 save_hf_checkpoint(raw_model, "main", tokenizer)
531 print("\n[Training] Training phase complete!")
532
533 if skip_eval:
534 print("[Pipeline] Skipping evaluations phase as requested.")
535 return
536
537 # ─────────────────────────────────────────────────────────────
538 # 2. RUN EVALUATION PIPELINE
539 # ─────────────────────────────────────────────────────────────
540 local_results_dir = os.path.abspath(f"./results/{model_name}")
541 os.makedirs(local_results_dir, exist_ok=True)
542 local_main_res = os.path.join(local_results_dir, "main")
543
544 # Ensure clone_dir exists and has the global_piqa files
545 clone_parent = os.path.abspath("./babylm_eval_repo")
546
547 # Self-healing check for global_piqa presence
548 has_global_piqa = False
549 for potential_strict in [os.path.join(clone_parent, "babylm-eval", "strict"), os.path.join(clone_parent, "strict")]:
550 if os.path.exists(os.path.join(potential_strict, "evaluation_pipeline", "global_piqa")):
551 has_global_piqa = True
552 break
553
554 if not has_global_piqa:
555 print("[Eval] Cloned repository does not contain global_piqa tasks.")
556 print("[Eval] Deleting and cloning official main branch...")
557 if os.path.exists(clone_parent):
558 shutil.rmtree(clone_parent)
559 subprocess.run([
560 "git", "clone", "-b", "main",
561 "https://github.com/babylm-org/babylm-eval.git",
562 clone_parent
563 ], check=True)
564
565 # Determine strict_dir path dynamically
566 if os.path.exists(os.path.join(clone_parent, "strict")):
567 strict_dir = os.path.join(clone_parent, "strict")
568 else:
569 strict_dir = os.path.join(clone_parent, "babylm-eval", "strict")
570
571 print(f"[Eval] Using strict directory: {strict_dir}")
572 os.environ["PYTHONPATH"] = strict_dir
573
574 def patch_evaluation_run_script(strict_dir):
575 import pathlib
576 run_file = os.path.join(strict_dir, "evaluation_pipeline", "sentence_zero_shot", "run.py")
577 if not os.path.exists(run_file):
578 print(f"[GlobalPIQA] Warning: {run_file} not found. Cannot patch.")
579 return
580
581 print(f"[GlobalPIQA] Patching local checkpoint loader in {run_file}...")
582 with open(run_file, "r") as f:
583 content = f.read()
584
585 # Check if already patched
586 if "Local checkpoint directory patch" in content:
587 print("[GlobalPIQA] Script already patched.")
588 return
589
590 target_str = """def main():
591 args = _parse_arguments()
592 if args.images_path is not None:
593 assert args.batch_size == 1, "Multimodal only works in batch size 1!"
594 dataset = args.data_path.stem
595 args.model_name = pathlib.Path(args.model_path_or_name).stem
596 if args.revision_name is None:
597 revision_name = "main"
598 else:
599 revision_name = args.revision_name"""
600
601 patch_str = """def main():
602 args = _parse_arguments()
603 if args.images_path is not None:
604 assert args.batch_size == 1, "Multimodal only works in batch size 1!"
605 dataset = args.data_path.stem
606
607 # Local checkpoint directory patch
608 import os
609 model_path = args.model_path_or_name
610 args.model_name = pathlib.Path(model_path).stem
611 revision_name = args.revision_name if args.revision_name else "main"
612
613 if os.path.isdir(model_path):
614 target_revision = args.revision_name if args.revision_name else "main"
615 if os.path.exists(os.path.join(model_path, target_revision)):
616 args.model_path_or_name = os.path.join(model_path, target_revision)
617 args.revision_name = None"""
618
619 if target_str in content:
620 new_content = content.replace(target_str, patch_str)
621 with open(run_file, "w") as f:
622 f.write(new_content)
623 print("[GlobalPIQA] Successfully patched run.py")
624 else:
625 print("[GlobalPIQA] Warning: Could not find target pattern in run.py. Manual patch may be needed.")
626
627 # Patch sentence zero shot loader inside cloned repo
628 patch_evaluation_run_script(strict_dir)
629
630 print("[Eval] Stripping Windows-specific packages from requirements.txt...")
631 req_file_path = os.path.join(strict_dir, "requirements.txt")
632 if os.path.exists(req_file_path):
633 with open(req_file_path, "r") as f:
634 lines = f.readlines()
635 with open(req_file_path, "w") as f:
636 for line in lines:
637 if "pywin" not in line.lower() and "wintypes" not in line.lower():
638 f.write(line)
639
640 print("[Eval] Verifying and installing evaluation dependencies programmatically...")
641 required_packages = {
642 "nltk": "nltk",
643 "pandas": "pandas",
644 "statsmodels": "statsmodels",
645 "sklearn": "scikit-learn",
646 "scipy": "scipy"
647 }
648 for pkg_import, pkg_install in required_packages.items():
649 try:
650 __import__(pkg_import)
651 except ImportError:
652 print(f"[Eval] Package '{pkg_install}' not found. Installing it programmatically...")
653 import sys
654 subprocess.run([sys.executable, "-m", "pip", "install", pkg_install], check=True)
655
656 print("[Eval] Downloading NLTK tokenizer resources...")
657 import nltk
658 nltk.download('punkt', download_dir=os.environ["NLTK_DATA"])
659 nltk.download('punkt_tab', download_dir=os.environ["NLTK_DATA"])
660
661 # Ensure standard zero-shot datasets are downloaded
662 blimp_fast_dir = os.path.join(strict_dir, "evaluation_data", "fast_eval", "blimp_fast")
663 if not os.path.exists(blimp_fast_dir) or not os.listdir(blimp_fast_dir):
664 print("[Eval] Standard zero-shot datasets not found. Downloading...")
665 subprocess.run(["python", "-m", "scripts.download_evals"], cwd=strict_dir, check=True)
666
667 # Unzip EWoK fast
668 ewok_zip = os.path.join(strict_dir, "evaluation_data/fast_eval/ewok_fast.zip")
669 if os.path.exists(ewok_zip):
670 print("[Eval] Unzipping EWoK fast data...")
671 bad_nested_dir = os.path.join(strict_dir, "evaluation_data/fast_eval/evaluation_data")
672 if os.path.exists(bad_nested_dir):
673 shutil.rmtree(bad_nested_dir)
674 subprocess.run(["unzip", "-o", "-P", "BabyLM2025", "evaluation_data/fast_eval/ewok_fast.zip", "-d", "."], cwd=strict_dir, check=True)
675
676 # Download EWoK full
677 print("[Eval] Downloading and filtering full EWoK dataset...")
678 subprocess.run(["python", "-m", "evaluation_pipeline.ewok.dl_and_filter"], cwd=strict_dir, check=True)
679
680 # Download GlobalPIQA dataset
681 global_piqa_parallel_dir = os.path.join(strict_dir, "evaluation_data", "fast_eval", "global_piqa_parallel")
682 if not os.path.exists(global_piqa_parallel_dir) or not os.listdir(global_piqa_parallel_dir):
683 print("[Eval] GlobalPIQA dataset not found. Downloading...")
684 subprocess.run(["python", "evaluation_pipeline/global_piqa/dl.py"], cwd=strict_dir, check=True)
685
686 # Ensure all scripts are executable
687 print("[Eval] Making evaluation shell scripts executable...")
688 subprocess.run("chmod +x scripts/*.sh", shell=True, cwd=strict_dir, check=True)
689
690 def run_task_with_cache(checkpoint, task, output_subpath, cmd):
691 # Determine paths
692 local_cache_path = os.path.join("./results", model_name, checkpoint, "zero_shot", "causal", task, output_subpath)
693 if task == "reading":
694 local_cache_path = os.path.join("./results", model_name, checkpoint, "zero_shot", "causal", "reading")
695 elif task == "comps":
696 local_cache_path = os.path.join("./results", model_name, checkpoint, "zero_shot", "causal", "comps", "comps")
697
698 target_results_dir = os.path.join(strict_dir, "results", model_name, checkpoint, "zero_shot", "causal", task, output_subpath)
699 if task == "reading":
700 target_results_dir = os.path.join(strict_dir, "results", model_name, checkpoint, "zero_shot", "causal", "reading")
701 elif task == "comps":
702 target_results_dir = os.path.join(strict_dir, "results", model_name, checkpoint, "zero_shot", "causal", "comps", "comps")
703
704 # If cached, copy it over
705 cache_file = os.path.join(local_cache_path, "predictions.json")
706
707 # Self-healing: invalidate old unfiltered entity_tracking caches
708 if task == "entity_tracking" and os.path.exists(cache_file):
709 try:
710 import json
711 with open(cache_file, "r") as f:
712 preds = json.load(f)
713 is_valid_cache = True
714 for k, v in preds.items():
715 if len(v.get("predictions", [])) in [605, 606, 607, 615, 529, 156, 187, 159]:
716 is_valid_cache = False
717 break
718 if not is_valid_cache:
719 print(f"[Eval] Cached entity_tracking for '{checkpoint}' has incorrect old sizes. Invalidate and re-run fresh...")
720 shutil.rmtree(local_cache_path, ignore_errors=True)
721 except Exception:
722 pass
723
724 if os.path.exists(cache_file):
725 print(f"[Eval] Task '{task}' ({output_subpath}) for checkpoint '{checkpoint}' is cached. Restoring...")
726 if os.path.exists(target_results_dir):
727 shutil.rmtree(target_results_dir)
728 os.makedirs(target_results_dir, exist_ok=True)
729 for item in os.listdir(local_cache_path):
730 s = os.path.join(local_cache_path, item)
731 d = os.path.join(target_results_dir, item)
732 if os.path.isdir(s):
733 shutil.copytree(s, d)
734 else:
735 shutil.copy2(s, d)
736 return
737
738 print(f"[Eval] Running task '{task}' ({output_subpath}) for checkpoint '{checkpoint}'...")
739 subprocess.run(cmd, cwd=strict_dir, check=True)
740
741 # Relocate from actual_model_basename, main, or checkpoint subfolder if needed
742 actual_model_basename = os.path.basename(model_dir.rstrip("/"))
743 possible_actual_path = os.path.join(strict_dir, "results", actual_model_basename, checkpoint, "zero_shot", "causal", task, output_subpath)
744 if task == "reading":
745 possible_actual_path = os.path.join(strict_dir, "results", actual_model_basename, checkpoint, "zero_shot", "causal", "reading")
746 elif task == "comps":
747 possible_actual_path = os.path.join(strict_dir, "results", actual_model_basename, checkpoint, "zero_shot", "causal", "comps", "comps")
748
749 possible_main_path = os.path.join(strict_dir, "results", "main", checkpoint, "zero_shot", "causal", task, output_subpath)
750 if task == "reading":
751 possible_main_path = os.path.join(strict_dir, "results", "main", checkpoint, "zero_shot", "causal", "reading")
752 elif task == "comps":
753 possible_main_path = os.path.join(strict_dir, "results", "main", checkpoint, "zero_shot", "causal", "comps", "comps")
754
755 possible_local_reading_path = os.path.join(strict_dir, "results", checkpoint, "main", "zero_shot", "causal", "reading")
756
757 for p_path in [possible_actual_path, possible_main_path, possible_local_reading_path]:
758 if os.path.exists(p_path) and p_path != target_results_dir:
759 print(f"[Eval] Relocating results from {p_path} to {target_results_dir}...")
760 if os.path.exists(target_results_dir):
761 shutil.rmtree(target_results_dir)
762 os.makedirs(os.path.dirname(target_results_dir), exist_ok=True)
763 shutil.move(p_path, target_results_dir)
764 break
765
766 # Verify
767 verify_eval_run(target_results_dir, f"{checkpoint} {task} ({output_subpath})")
768
769 # Save to local cache
770 if os.path.exists(local_cache_path):
771 shutil.rmtree(local_cache_path)
772 os.makedirs(local_cache_path, exist_ok=True)
773 for item in os.listdir(target_results_dir):
774 s = os.path.join(target_results_dir, item)
775 d = os.path.join(local_cache_path, item)
776 if os.path.isdir(s):
777 shutil.copytree(s, d)
778 else:
779 shutil.copy2(s, d)
780
781 def run_finetune_task_with_cache(task, cmd):
782 local_cache_path = os.path.join("./results", model_name, "main", "finetune", task)
783 target_results_dir = os.path.join(strict_dir, "results", model_name, "main", "finetune", task)
784
785 if os.path.exists(os.path.join(local_cache_path, "predictions.json")):
786 print(f"[Eval] GLUE task '{task}' is cached. Restoring...")
787 if os.path.exists(target_results_dir):
788 shutil.rmtree(target_results_dir)
789 os.makedirs(target_results_dir, exist_ok=True)
790 for item in os.listdir(local_cache_path):
791 s = os.path.join(local_cache_path, item)
792 d = os.path.join(target_results_dir, item)
793 if os.path.isdir(s):
794 shutil.copytree(s, d)
795 else:
796 shutil.copy2(s, d)
797 return
798
799 print(f"[Eval] Running GLUE task '{task}'...")
800 subprocess.run(cmd, cwd=strict_dir, check=True)
801
802 # Relocate from results/main/main if needed
803 possible_main_path = os.path.join(strict_dir, "results", "main", "main", "finetune", task)
804 if os.path.exists(possible_main_path) and possible_main_path != target_results_dir:
805 print(f"[Eval] Relocating results from {possible_main_path} to {target_results_dir}...")
806 if os.path.exists(target_results_dir):
807 shutil.rmtree(target_results_dir)
808 os.makedirs(os.path.dirname(target_results_dir), exist_ok=True)
809 shutil.move(possible_main_path, target_results_dir)
810
811 # Verify
812 verify_eval_run(target_results_dir, f"GLUE task {task}")
813
814 # Cache locally
815 if os.path.exists(local_cache_path):
816 shutil.rmtree(local_cache_path)
817 os.makedirs(local_cache_path, exist_ok=True)
818 for item in os.listdir(target_results_dir):
819 s = os.path.join(target_results_dir, item)
820 d = os.path.join(local_cache_path, item)
821 if os.path.isdir(s):
822 shutil.copytree(s, d)
823 else:
824 shutil.copy2(s, d)
825
826 def run_aoa_with_cache(cmd):
827 local_cache_path = os.path.join("./results", model_name, "main", "aoa")
828 target_results_dir = os.path.join(strict_dir, "results", model_name, "main", "aoa")
829
830 if os.path.exists(os.path.join(local_cache_path, "aoa_score.json")) or os.path.exists(os.path.join(local_cache_path, "surprisal.json")):
831 print(f"[Eval] AoA task is cached. Restoring...")
832 if os.path.exists(target_results_dir):
833 shutil.rmtree(target_results_dir)
834 os.makedirs(target_results_dir, exist_ok=True)
835 for item in os.listdir(local_cache_path):
836 s = os.path.join(local_cache_path, item)
837 d = os.path.join(target_results_dir, item)
838 if os.path.isdir(s):
839 shutil.copytree(s, d)
840 else:
841 shutil.copy2(s, d)
842 return
843
844 print("[Eval] Running AoA task...")
845 subprocess.run(cmd, cwd=strict_dir, check=True)
846
847 # Relocate from results/main/main if needed
848 possible_main_path = os.path.join(strict_dir, "results", "main", "main", "aoa")
849 if os.path.exists(possible_main_path) and possible_main_path != target_results_dir:
850 print(f"[Eval] Relocating results from {possible_main_path} to {target_results_dir}...")
851 if os.path.exists(target_results_dir):
852 shutil.rmtree(target_results_dir)
853 os.makedirs(os.path.dirname(target_results_dir), exist_ok=True)
854 shutil.move(possible_main_path, target_results_dir)
855
856 # Verify
857 verify_eval_run(target_results_dir, "AoA task")
858
859 # Cache locally
860 if os.path.exists(local_cache_path):
861 shutil.rmtree(local_cache_path)
862 os.makedirs(local_cache_path, exist_ok=True)
863 for item in os.listdir(target_results_dir):
864 s = os.path.join(target_results_dir, item)
865 d = os.path.join(local_cache_path, item)
866 if os.path.isdir(s):
867 shutil.copytree(s, d)
868 else:
869 shutil.copy2(s, d)
870
871 # ─────────────────────────────────────────────────────────────
872 # B. FINAL MODEL 'main' FULL ZERO-SHOT EVALUATION
873 # ─────────────────────────────────────────────────────────────
874 main_ckpt_path = os.path.join(model_dir, "main")
875 if os.path.exists(main_ckpt_path):
876 print(f"[Eval] Running full zero-shot evaluation on main...")
877 # blimp filtered
878 run_task_with_cache(
879 "main", "blimp", "blimp_filtered",
880 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "blimp", "--data_path", "evaluation_data/full_eval/blimp_filtered", "--save_predictions"]
881 )
882 # supplement filtered
883 run_task_with_cache(
884 "main", "blimp", "supplement_filtered",
885 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "blimp", "--data_path", "evaluation_data/full_eval/supplement_filtered", "--save_predictions"]
886 )
887 # ewok filtered
888 run_task_with_cache(
889 "main", "ewok", "ewok_filtered",
890 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "ewok", "--data_path", "evaluation_data/full_eval/ewok_filtered", "--save_predictions"]
891 )
892 # entity tracking
893 run_task_with_cache(
894 "main", "entity_tracking", "entity_tracking",
895 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "entity_tracking", "--data_path", "evaluation_data/full_eval/entity_tracking", "--save_predictions"]
896 )
897 # comps
898 run_task_with_cache(
899 "main", "comps", "comps",
900 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "comps", "--data_path", "evaluation_data/full_eval/comps", "--save_predictions"]
901 )
902 # reading
903 run_task_with_cache(
904 "main", "reading", "reading",
905 ["python", "-m", "evaluation_pipeline.reading.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--data_path", "evaluation_data/full_eval/reading/reading_data.csv"]
906 )
907 # global piqa parallel
908 run_task_with_cache(
909 "main", "global_piqa_parallel", "global_piqa_parallel",
910 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "global_piqa_parallel", "--data_path", "evaluation_data/full_eval/global_piqa_parallel", "--save_predictions"]
911 )
912 # global piqa nonparallel
913 run_task_with_cache(
914 "main", "global_piqa_nonparallel", "global_piqa_nonparallel",
915 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "global_piqa_nonparallel", "--data_path", "evaluation_data/full_eval/global_piqa_nonparallel", "--save_predictions"]
916 )
917
918 # ─────────────────────────────────────────────────────────────
919 # C. FINAL MODEL GLUE AND AOA EVALUATION
920 # ─────────────────────────────────────────────────────────────
921 if os.path.exists(main_ckpt_path):
922 # 1. GLUE fine-tuning
923 if skip_glue:
924 print("[Eval] Skipping GLUE fine-tuning evaluations as requested.")
925 else:
926 print("[Eval] Running GLUE fine-tuning evaluations on main task-by-task...")
927 glue_tasks = {
928 "boolq": ["boolq", "16", "10"],
929 "multirc": ["multirc", "16", "10"],
930 "rte": ["rte", "32", "10"],
931 "wsc": ["wsc", "32", "30"],
932 "mrpc": ["mrpc", "32", "10"],
933 "qqp": ["qqp", "32", "10"],
934 "mnli": ["mnli", "32", "10"]
935 }
936 for task_name, (task, bsz, max_epochs) in glue_tasks.items():
937 num_labels = "3" if task == "mnli" else "2"
938 metric_for_valid = "accuracy"
939 if task in ["mrpc", "qqp"]:
940 metric_for_valid = "f1"
941 metrics = ["accuracy"]
942 if task != "mnli":
943 metrics = ["accuracy", "f1", "mcc"]
944
945 cmd = [
946 "python", "-m", "evaluation_pipeline.finetune.run",
947 "--model_name_or_path", main_ckpt_path,
948 "--train_data", f"evaluation_data/full_eval/glue_filtered/{task}.train.jsonl",
949 "--valid_data", f"evaluation_data/full_eval/glue_filtered/{task}.valid.jsonl",
950 "--predict_data", f"evaluation_data/full_eval/glue_filtered/{task}.valid.jsonl",
951 "--task", task,
952 "--num_labels", num_labels,
953 "--batch_size", bsz,
954 "--learning_rate", "3e-5",
955 "--num_epochs", max_epochs,
956 "--sequence_length", "512",
957 "--results_dir", "results",
958 "--save",
959 "--save_dir", "models",
960 "--metric_for_valid", metric_for_valid,
961 "--seed", "42",
962 "--verbose",
963 "--padding_side", "left",
964 "--take_final"
965 ]
966 cmd.append("--metrics")
967 cmd.extend(metrics)
968
969 run_finetune_task_with_cache(task_name, cmd)
970
971 # 2. AoA
972 if skip_aoa:
973 print("[Eval] Skipping AoA evaluations as requested.")
974 else:
975 run_aoa_with_cache([
976 "python", "-m", "evaluation_pipeline.AoA_word.run",
977 "--model_name", model_dir,
978 "--backend", "causal",
979 "--track_name", "strict-small",
980 "--word_path", "evaluation_data/full_eval/aoa/cdi_childes.json",
981 "--output_dir", "results"
982 ])
983
984 # ─────────────────────────────────────────────────────────────
985 # A. INTERMEDIATE CHECKPOINTS FAST EVALUATION
986 # ─────────────────────────────────────────────────────────────
987 print(f"[Eval] Running zero-shot fast evaluations on intermediate checkpoints...")
988 checkpoints = [f"chck_{i}M" for i in range(1, 10)] + [f"chck_{i}M" for i in range(10, 110, 10)]
989
990 eval_model_path = model_dir
991
992 for checkpoint in checkpoints:
993 ckpt_full_path = os.path.join(model_dir, checkpoint)
994 if not os.path.exists(ckpt_full_path):
995 continue
996
997 # blimp fast
998 run_task_with_cache(
999 checkpoint, "blimp", "blimp_fast",
1000 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "blimp", "--data_path", "evaluation_data/fast_eval/blimp_fast", "--save_predictions", "--revision_name", checkpoint]
1001 )
1002 # supplement fast
1003 run_task_with_cache(
1004 checkpoint, "blimp", "supplement_fast",
1005 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "blimp", "--data_path", "evaluation_data/fast_eval/supplement_fast", "--save_predictions", "--revision_name", checkpoint]
1006 )
1007 # ewok fast
1008 run_task_with_cache(
1009 checkpoint, "ewok", "ewok_fast",
1010 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "ewok", "--data_path", "evaluation_data/fast_eval/ewok_fast", "--save_predictions", "--revision_name", checkpoint]
1011 )
1012 # entity tracking fast
1013 run_task_with_cache(
1014 checkpoint, "entity_tracking", "entity_tracking_fast",
1015 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "entity_tracking", "--data_path", "evaluation_data/fast_eval/entity_tracking_fast", "--save_predictions", "--revision_name", checkpoint]
1016 )
1017 # reading fast
1018 run_task_with_cache(
1019 checkpoint, "reading", "reading",
1020 ["python", "-m", "evaluation_pipeline.reading.run", "--model_path_or_name", ckpt_full_path, "--backend", "causal", "--data_path", "evaluation_data/fast_eval/reading/reading_data.csv"]
1021 )
1022 # global piqa parallel fast
1023 run_task_with_cache(
1024 checkpoint, "global_piqa_parallel", "global_piqa_parallel",
1025 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "global_piqa_parallel", "--data_path", "evaluation_data/fast_eval/global_piqa_parallel", "--save_predictions", "--revision_name", checkpoint]
1026 )
1027 # global piqa nonparallel fast
1028 run_task_with_cache(
1029 checkpoint, "global_piqa_nonparallel", "global_piqa_nonparallel",
1030 ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "global_piqa_nonparallel", "--data_path", "evaluation_data/fast_eval/global_piqa_nonparallel", "--save_predictions", "--revision_name", checkpoint]
1031 )
1032
1033 # ─────────────────────────────────────────────────────────────
1034 # D. COLLATE RESULTS AND CLEANUP
1035 # ─────────────────────────────────────────────────────────────
1036 print("[Eval] Collating predictions into submission file...")
1037 # Clean collation destination in evaluation repo
1038 collate_results_dir = os.path.join(strict_dir, "results", model_name)
1039 if os.path.exists(collate_results_dir):
1040 shutil.rmtree(collate_results_dir)
1041 os.makedirs(os.path.dirname(collate_results_dir), exist_ok=True)
1042
1043 # Copy from local cache results to strict results for collation
1044 shutil.copytree(local_results_dir, collate_results_dir)
1045
1046 # Run collation
1047 subprocess.run([
1048 "python", "-m", "evaluation_pipeline.collate_preds",
1049 "--model_path_or_name", model_name,
1050 "--backend", "causal",
1051 "--track", "strict-small",
1052 "--fast"
1053 ], cwd=strict_dir, check=True)
1054
1055 # Save results to local folder
1056 results_src = os.path.join(strict_dir, "results")
1057 results_dest = os.path.abspath("./results")
1058 if os.path.exists(results_dest):
1059 shutil.rmtree(results_dest)
1060 shutil.copytree(results_src, results_dest)
1061
1062 # Copy final collated json to current folder
1063 collated_json = os.path.join(strict_dir, "all_full_preds_and_fast_scores_causal.json")
1064 if os.path.exists(collated_json):
1065 shutil.copy(collated_json, "./all_full_preds_and_fast_scores_causal.json")
1066 print("\n[Eval] Success! Collation completed! Final file is at './all_full_preds_and_fast_scores_causal.json'")
1067
1068 print("\n[Eval] Pipeline evaluation run finished.")
1069
1070def upload_pipeline(model_name, repo_name, token=None):
1071 import os
1072 import shutil
1073 import json
1074 import hashlib
1075 from huggingface_hub import HfApi, create_repo
1076
1077 if not token:
1078 token = os.environ.get("HF_TOKEN")
1079
1080 api = HfApi(token=token)
1081 try:
1082 user_info = api.whoami()
1083 username = user_info["name"]
1084 print(f"[HF] Authenticated successfully as user: {username}")
1085 except Exception as e:
1086 print(f"[HF] Authentication failed. Error: {e}")
1087 return
1088
1089 repo_id = f"{username}/{repo_name}"
1090 print(f"[HF] Target Repository ID: {repo_id}")
1091
1092 # Create the repository if it doesn't exist
1093 try:
1094 create_repo(repo_id=repo_id, repo_type="model", token=token, exist_ok=True)
1095 print(f"[HF] Repository '{repo_id}' is ready.")
1096 except Exception as e:
1097 print(f"[HF] Failed to verify or create repository. Error: {e}")
1098 return
1099
1100 checkpoint_dir = os.path.abspath(f"./checkpoints/{model_name}")
1101
1102 # Resolve the main checkpoint directory using self-healing rules
1103 revisions = {}
1104 if os.path.exists("pytorch_model.bin") and os.path.exists("config.json"):
1105 print("[HF] Detected weight and config files in the current working directory. Using current folder as 'main' checkpoint.")
1106 revisions = {"main": os.getcwd()}
1107 elif os.path.exists(os.path.join(checkpoint_dir, "main")):
1108 revisions = {"main": os.path.join(checkpoint_dir, "main")}
1109 elif os.path.exists(os.path.abspath("./checkpoints/msit_gptbert_fresh/main")):
1110 print("[HF] Using fallback checkpoint folder './checkpoints/msit_gptbert_fresh/main'...")
1111 revisions = {"main": os.path.abspath("./checkpoints/msit_gptbert_fresh/main")}
1112 elif os.path.exists(os.path.abspath("./checkpoints/main")):
1113 revisions = {"main": os.path.abspath("./checkpoints/main")}
1114 else:
1115 print(f"[HF] Error: Could not locate the 'main' checkpoint weights. Checked: {checkpoint_dir}/main, current directory, and fallbacks.")
1116 return
1117
1118 # Check for intermediate checkpoints relative to the main checkpoint's parent folder
1119 main_dir = revisions["main"]
1120 parent_dir = os.path.dirname(main_dir)
1121
1122 for m in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100]:
1123 ckpt_name = f"chck_{m}M"
1124 ckpt_path = os.path.join(parent_dir, ckpt_name)
1125 if os.path.exists(ckpt_path):
1126 revisions[ckpt_name] = ckpt_path
1127 else:
1128 # Check if there is a .pt file in the parent folder
1129 pt_path = os.path.join(parent_dir, f"{ckpt_name}.pt")
1130 if os.path.exists(pt_path):
1131 print(f"[HF] Found legacy checkpoint file '{ckpt_name}.pt'. Converting to HF format for upload...")
1132 import torch
1133 from transformers import AutoTokenizer
1134 from tokenizers import Tokenizer
1135 try:
1136 from modeling_xpertgpt import XpertGPTForCausalLM, XpertGPTConfig
1137 cfg = XpertGPTConfig(d_model=256, d_thin=384, num_layers=6, num_blocks=4)
1138 model_to_save = XpertGPTForCausalLM(cfg)
1139 sd = torch.load(pt_path, map_location="cpu")
1140 clean_sd = {}
1141 for k, v in sd.items():
1142 new_k = k.replace("module.", "")
1143 clean_sd[new_k] = v
1144 model_to_save.load_state_dict(clean_sd)
1145
1146 vocab_path = os.path.join(parent_dir, "bpe_vocab_16k.json")
1147 if not os.path.exists(vocab_path):
1148 vocab_path = os.path.join(main_dir, "bpe_vocab_16k.json")
1149 if os.path.exists(vocab_path):
1150 tok = Tokenizer.from_file(vocab_path)
1151 else:
1152 tok = None
1153
1154 os.makedirs(ckpt_path, exist_ok=True)
1155 state_dict = model_to_save.state_dict()
1156 new_state_dict = {}
1157 for k, v in state_dict.items():
1158 name = k
1159 if name.startswith("_orig_mod."):
1160 name = name[10:]
1161 if name.startswith("model."):
1162 name = name[6:]
1163 if name == "lm_head.weight":
1164 new_state_dict["lm_head.weight"] = v
1165 else:
1166 new_state_dict[f"transformer.{name}"] = v
1167 torch.save(new_state_dict, os.path.join(ckpt_path, "pytorch_model.bin"))
1168
1169 config_dict = {
1170 "auto_map": {
1171 "AutoConfig": "configuration_xpertgpt.XpertGPTConfig",
1172 "AutoModel": "modeling_xpertgpt.XpertGPTModelWrapper",
1173 "AutoModelForCausalLM": "modeling_xpertgpt.XpertGPTForCausalLM"
1174 },
1175 "vocab_size": 16384,
1176 "block_size": 512,
1177 "d_model": 256,
1178 "hidden_size": 256,
1179 "d_thin": 384,
1180 "num_layers": 6,
1181 "num_blocks": 4,
1182 "capacity_factor": 2.0,
1183 "dropout": 0.1,
1184 "model_type": "xpertgpt",
1185 "num_hidden_layers": 6
1186 }
1187 with open(os.path.join(ckpt_path, "config.json"), "w") as f:
1188 json.dump(config_dict, f, indent=2)
1189
1190 if tok:
1191 from transformers import PreTrainedTokenizerFast
1192 fast_tokenizer = PreTrainedTokenizerFast(
1193 tokenizer_object=tok,
1194 bos_token="[CLS]",
1195 eos_token="[SEP]",
1196 unk_token="[UNK]",
1197 pad_token="[PAD]",
1198 mask_token="[MASK]"
1199 )
1200 fast_tokenizer.save_pretrained(ckpt_path)
