@@ -1287,6 +1287,18 @@ def der_ce(model) -> dict:
12871287 result = {"teacher" : der_ce (teacher ), "student" : der_ce (student )}
12881288 result ["gate_delta" ] = round (result ["student" ]["der_ce" ] - result ["teacher" ]["der_ce" ], 4 )
12891289 result ["gate_pass" ] = result ["gate_delta" ] <= 0.5
1290+ # durable verdict marker: the run dir is the provenance record (also
1291+ # what r7-style _init_choice probes read)
1292+ import json
1293+
1294+ out_root = Path (
1295+ vol_map [spec .get ("out_volume" , spec .get ("teacher_volume" , "rababa" ))]
1296+ ) / spec ["out" ]
1297+ out_root .mkdir (parents = True , exist_ok = True )
1298+ (out_root / "final_eval.json" ).write_text (
1299+ json .dumps (result , indent = 2 ), encoding = "utf-8"
1300+ )
1301+ CHECKPOINTS .commit ()
12901302 return result
12911303
12921304
@@ -1602,3 +1614,81 @@ def probe_pkm_gates(spec_id: str = "ara-diac-small-pkm") -> dict:
16021614@app .local_entrypoint ()
16031615def pkm_gates (spec : str = "ara-diac-small-pkm" ) -> None :
16041616 print (probe_pkm_gates .remote (spec ))
1617+
1618+
1619+ @app .function (
1620+ cpu = 1 ,
1621+ memory = 1024 ,
1622+ timeout = 24 * 3600 ,
1623+ volumes = {"/checkpoints" : CHECKPOINTS , "/secryst-checkpoints" : SECRYST_CHECKPOINTS },
1624+ )
1625+ def qwen_next_chain () -> dict :
1626+ """Server-side orchestrator for the qwen-next experiments (E2/E3):
1627+ workstation-independent, self-healing, idempotent — the replacement
1628+ for local shell chains that die with the workstation or break when
1629+ the repo's checked-out branch changes.
1630+
1631+ State machine per arm, driven by durable volume markers only:
1632+ best/config.json absent -> watch step-* checkpoints; respawn
1633+ training if no progress for 20 min
1634+ (distill_sequence resumes from the
1635+ latest checkpoint; a redundant spawn
1636+ is benign — a finished run saves
1637+ best again and exits)
1638+ best present, final_eval.json absent -> evaluate_der (which now
1639+ writes final_eval.json itself)
1640+ final_eval.json present -> arm done
1641+
1642+ Audit trail: chain_log.jsonl in each run dir. If this function times
1643+ out (24h) or is evicted, relaunching continues from the markers:
1644+
1645+ modal run --detach src/gpu/modal_distill.py::qwen_chain
1646+ """
1647+ import json
1648+ import time
1649+ from pathlib import Path
1650+
1651+ ARMS = [
1652+ ("ara-diac-small-pkm" , "rababa_arabic_distill_small/run-003-pkm" ),
1653+ ("ara-diac-small-pkm-muon" , "rababa_arabic_distill_small/run-004-pkm-muon" ),
1654+ ]
1655+ ROOT = Path ("/checkpoints" )
1656+
1657+ def log (run : str , event : str ) -> None :
1658+ with (ROOT / run / "chain_log.jsonl" ).open ("a" , encoding = "utf-8" ) as fh :
1659+ fh .write (json .dumps ({"t" : round (time .time ()), "event" : event }) + "\n " )
1660+ CHECKPOINTS .commit ()
1661+
1662+ def latest_step (run : str ) -> int :
1663+ steps = [int (p .name .split ("-" )[1 ]) for p in (ROOT / run ).glob ("step-*" )]
1664+ return max (steps ) if steps else - 1
1665+
1666+ status = {}
1667+ for spec_id , run in ARMS :
1668+ while not (ROOT / run / "best" / "config.json" ).exists ():
1669+ CHECKPOINTS .reload ()
1670+ before = latest_step (run )
1671+ log (run , f"watch step={ before } " )
1672+ time .sleep (1200 )
1673+ CHECKPOINTS .reload ()
1674+ after = latest_step (run )
1675+ if after == before and not (ROOT / run / "best" / "config.json" ).exists ():
1676+ log (run , f"stalled at step={ after } ; respawning { spec_id } " )
1677+ distill_sequence .spawn (spec_id , epochs = 3 )
1678+ log (run , "training complete (best present)" )
1679+ if not (ROOT / run / "final_eval.json" ).exists ():
1680+ log (run , "evaluating" )
1681+ evaluate_der .remote (spec_id = spec_id )
1682+ log (run , "eval done" )
1683+ status [run ] = "complete"
1684+ return status
1685+
1686+
1687+ @app .local_entrypoint ()
1688+ def qwen_chain () -> None :
1689+ handle = qwen_next_chain .spawn ()
1690+ print (
1691+ f"spawned { handle .object_id } ; durable markers: chain_log.jsonl, "
1692+ f"final_eval.json in each run dir; relaunch is idempotent" ,
1693+ flush = True ,
1694+ )
0 commit comments