5757CHECKPOINTS = modal .Volume .from_name ("rababa-checkpoints" )
5858
5959
60+
61+ VOLUME_MOUNTS = {
62+ "rababa" : "/checkpoints" ,
63+ "secryst" : "/secryst-checkpoints" ,
64+ "persian" : "/persian-checkpoints" ,
65+ }
66+ DATA_MOUNTS = {
67+ "secryst" : "/secryst-datasets" ,
68+ "persian" : "/persian-datasets" ,
69+ }
70+
71+
72+ def resolve_spec (spec : dict ) -> dict :
73+ """Volume-relative paths for a spec — the single owner of "which
74+ volume does this teacher/student/dataset live on" (previously four
75+ pasted vol_map blocks)."""
76+ teacher_vol = spec .get ("teacher_volume" , "rababa" )
77+ data_root = spec .get ("data_volume" , DATA_MOUNTS .get (teacher_vol , "/datasets" ))
78+ teacher = (
79+ spec ["teacher" ] if spec .get ("teacher_is_hub" )
80+ else str (Path (VOLUME_MOUNTS [teacher_vol ]) / spec ["teacher" ])
81+ )
82+ out_root = str (Path (VOLUME_MOUNTS [spec .get ("out_volume" , teacher_vol )]) / spec ["out" ])
83+ return {
84+ "teacher_vol" : teacher_vol ,
85+ "data_root" : data_root ,
86+ "teacher" : teacher ,
87+ "out_root" : out_root ,
88+ "best" : str (Path (out_root ) / "best" ),
89+ }
90+
6091def _ensure_src_path () -> None :
6192 # Modal copies the entry file to /root/<name>.py while the repo image
6293 # sits at /root/interscript-ml — cover both layouts before importing
@@ -398,19 +429,10 @@ def evaluate_per(spec_id: str, limit: int = 0) -> dict:
398429 from transformers import AutoModelForSeq2SeqLM , AutoTokenizer
399430
400431 spec = SPECS [spec_id ]
401- teacher_vol = spec .get ("teacher_volume" , "rababa" )
402- vol_map = {
403- "rababa" : "/checkpoints" ,
404- "secryst" : "/secryst-checkpoints" ,
405- "persian" : "/persian-checkpoints" ,
406- }
407- data_vol = {"secryst" : "/secryst-datasets" ,
408- "persian" : "/persian-datasets" }.get (teacher_vol , "/datasets" )
409- data_vol = spec .get ("data_volume" , data_vol )
410- teacher_path = (spec ["teacher" ] if spec .get ("teacher_is_hub" )
411- else str (Path (vol_map [teacher_vol ]) / spec ["teacher" ]))
412- student_vol = vol_map [spec .get ("out_volume" , teacher_vol )]
413- student_path = Path (student_vol ) / spec ["out" ] / "best"
432+ paths = resolve_spec (spec )
433+ data_vol = paths ["data_root" ]
434+ teacher_path = paths ["teacher" ]
435+ student_path = Path (paths ["best" ])
414436 test_rel = spec .get ("eval_test" ) or spec .get ("test" )
415437 if not test_rel :
416438 raise RuntimeError (f"{ spec_id } : no test path" )
@@ -510,20 +532,9 @@ def distill_sequence(spec_id: str, epochs: int = 3) -> dict:
510532 spec = SPECS [spec_id ]
511533 teacher_vol = spec .get ("teacher_volume" , "rababa" )
512534
513- vol_map = {
514- "rababa" : "/checkpoints" ,
515- "secryst" : "/secryst-checkpoints" ,
516- "persian" : "/persian-checkpoints" ,
517- }
518- teacher_root = vol_map [teacher_vol ]
519- out_root_vol = vol_map [spec .get ("out_volume" , teacher_vol )]
520- teacher_path = (spec ["teacher" ] if spec .get ("teacher_is_hub" )
521- else str (Path (teacher_root ) / spec ["teacher" ]))
522-
523- data_vol = {"secryst" : "/secryst-datasets" ,
524- "persian" : "/persian-datasets" }.get (teacher_vol , "/datasets" )
525- data_vol = spec .get ("data_volume" , data_vol )
526- train_path = Path (data_vol ) / spec ["train" ]
535+ paths = resolve_spec (spec )
536+ teacher_path = paths ["teacher" ]
537+ train_path = Path (paths ["data_root" ]) / spec ["train" ]
527538
528539 # Teacher: use its OWN tokenizer (sentencepiece for umt5)
529540 teacher_tok = AutoTokenizer .from_pretrained (str (teacher_path ))
@@ -644,15 +655,15 @@ def collate(batch):
644655 train_files = [(train_path , unit_limits [0 ] if unit_limits else 0 )]
645656 for i , p in enumerate (spec .get ("train_extra" , [])):
646657 lim = unit_limits [i + 1 ] if i + 1 < len (unit_limits ) else 0
647- train_files .append ((Path (data_vol ) / p , lim ))
658+ train_files .append ((Path (paths [ "data_root" ] ) / p , lim ))
648659 train_ds = Pairs (train_files )
649660 print (f"[{ spec_id } ] train pairs: { len (train_ds )} from { len (train_files )} files" , flush = True )
650661 label_beams = int (spec .get ("label_beams" , 4 ))
651662
652663 # Step 1: teacher generates labels (beam-4) for the full corpus.
653664 # Resumable: evictions mid-labeling are routine on long jobs —
654665 # already-labeled srcs are skipped, the rest are appended.
655- out_root = Path (out_root_vol ) / spec [ "out" ]
666+ out_root = Path (paths [ "out_root" ])
656667 out_root .mkdir (parents = True , exist_ok = True )
657668 labels_file = spec .get ("labels_file" , "teacher_labels.jsonl" )
658669 if labels_file .endswith (".b64" ):
@@ -999,17 +1010,9 @@ def evaluate_der(spec_id: str, window: int = 1400, limit: int = 0) -> dict:
9991010 )
10001011
10011012 spec = SPECS [spec_id ]
1002- vol_map = {
1003- "rababa" : "/checkpoints" ,
1004- "secryst" : "/secryst-checkpoints" ,
1005- "persian" : "/persian-checkpoints" ,
1006- }
1007- teacher_path = (spec ["teacher" ] if spec .get ("teacher_is_hub" )
1008- else str (Path (vol_map [spec .get ("teacher_volume" , "rababa" )]) / spec ["teacher" ]))
1009- student_path = (
1010- Path (vol_map [spec .get ("out_volume" , spec .get ("teacher_volume" , "rababa" ))])
1011- / spec ["out" ] / "best"
1012- )
1013+ paths = resolve_spec (spec )
1014+ teacher_path = paths ["teacher" ]
1015+ student_path = Path (paths ["best" ])
10131016
10141017 tok = AutoTokenizer .from_pretrained ("google/byt5-small" )
10151018 teacher = AutoModelForSeq2SeqLM .from_pretrained (teacher_path ).to ("cuda" ).eval ()
@@ -1051,9 +1054,7 @@ def der_ce(model) -> dict:
10511054 # what r7-style _init_choice probes read)
10521055 import json
10531056
1054- out_root = Path (
1055- vol_map [spec .get ("out_volume" , spec .get ("teacher_volume" , "rababa" ))]
1056- ) / spec ["out" ]
1057+ out_root = Path (paths ["out_root" ])
10571058 out_root .mkdir (parents = True , exist_ok = True )
10581059 (out_root / "final_eval.json" ).write_text (
10591060 json .dumps (result , indent = 2 ), encoding = "utf-8"
@@ -1103,20 +1104,10 @@ def distill_microkimi(spec_id: str, epochs: int = 3, calib_batches: int = 64,
11031104 )
11041105
11051106 spec = SPECS [spec_id ]
1106- teacher_vol = spec .get ("teacher_volume" , "rababa" )
1107- vol_map = {
1108- "rababa" : "/checkpoints" ,
1109- "secryst" : "/secryst-checkpoints" ,
1110- "persian" : "/persian-checkpoints" ,
1111- }
1112- data_vol = {"secryst" : "/secryst-datasets" ,
1113- "persian" : "/persian-datasets" }.get (teacher_vol , "/datasets" )
1114- data_vol = spec .get ("data_volume" , data_vol )
1115- out_root_vol = vol_map [spec .get ("out_volume" , teacher_vol )]
1116- teacher_path = (spec ["teacher" ] if spec .get ("teacher_is_hub" )
1117- else str (Path (vol_map [teacher_vol ]) / spec ["teacher" ]))
1118- out_root = Path (out_root_vol ) / spec ["out" ]
1107+ paths = resolve_spec (spec )
1108+ out_root = Path (paths ["out_root" ])
11191109 out_root .mkdir (parents = True , exist_ok = True )
1110+ teacher_path = paths ["teacher" ]
11201111
11211112 student_tok = AutoTokenizer .from_pretrained ("google/byt5-small" )
11221113 teacher = AutoModelForSeq2SeqLM .from_pretrained (teacher_path ).to ("cuda" ).eval ()
@@ -1404,7 +1395,6 @@ def qwen_next_chain() -> dict:
14041395
14051396 modal run --detach src/gpu/modal_distill.py::qwen_chain
14061397 """
1407- import json
14081398 import time
14091399 from pathlib import Path
14101400
@@ -1414,38 +1404,29 @@ def qwen_next_chain() -> dict:
14141404 ("ara-diac-small-muon" , "rababa_arabic_distill_small/run-005-muon" ),
14151405 ("ara-diac-small-2" , "rababa_arabic_distill_small/run-006-r7-muon" ),
14161406 ]
1417- ROOT = Path ("/checkpoints" )
1418-
1419- def log (run : str , event : str ) -> None :
1420- # mkdir: a fresh arm's run dir does not exist until its training
1421- # creates it — the first watch line must not crash on that
1422- run_dir = ROOT / run
1423- run_dir .mkdir (parents = True , exist_ok = True )
1424- with (run_dir / "chain_log.jsonl" ).open ("a" , encoding = "utf-8" ) as fh :
1425- fh .write (json .dumps ({"t" : round (time .time ()), "event" : event }) + "\n " )
1426- CHECKPOINTS .commit ()
14271407
1428- def latest_step (run : str ) -> int :
1429- steps = [int (p .name .split ("-" )[1 ]) for p in (ROOT / run ).glob ("step-*" )]
1430- return max (steps ) if steps else - 1
1408+ _ensure_src_path ()
1409+ from gpu .runstate import RunState
14311410
14321411 status = {}
14331412 for spec_id , run in ARMS :
1434- while not (ROOT / run / "best" / "config.json" ).exists ():
1413+ state = RunState (Path ("/checkpoints" ) / run )
1414+ while not state .training_done ():
14351415 CHECKPOINTS .reload ()
1436- before = latest_step (run )
1437- log (run , f"watch step={ before } " )
1416+ before = state . latest_step ()
1417+ state . log (f"watch step={ before } " , commit = CHECKPOINTS . commit )
14381418 time .sleep (1200 )
14391419 CHECKPOINTS .reload ()
1440- after = latest_step (run )
1441- if after == before and not (ROOT / run / "best" / "config.json" ).exists ():
1442- log (run , f"stalled at step={ after } ; respawning { spec_id } " )
1420+ after = state .latest_step ()
1421+ if after == before and not state .training_done ():
1422+ state .log (f"stalled at step={ after } ; respawning { spec_id } " ,
1423+ commit = CHECKPOINTS .commit )
14431424 distill_sequence .spawn (spec_id , epochs = 3 )
1444- log (run , "training complete (best present)" )
1445- if not ( ROOT / run / "final_eval.json" ). exists ():
1446- log (run , "evaluating" )
1425+ state . log ("training complete (best present)" , commit = CHECKPOINTS . commit )
1426+ if not state . eval_done ():
1427+ state . log ("evaluating" , commit = CHECKPOINTS . commit )
14471428 evaluate_der .remote (spec_id = spec_id )
1448- log (run , "eval done" )
1429+ state . log ("eval done" , commit = CHECKPOINTS . commit )
14491430 status [run ] = "complete"
14501431 return status
14511432
0 commit comments