171 lines
5.8 KiB
Python
171 lines
5.8 KiB
Python
"""
|
||
Dispatch a 10×5 rep-CV of logit_mlp_head with --save-checkpoints.
|
||
|
||
Results land in v3/results/phase5/logit_mlp_head_ckpt/{rep00..rep09}/binary/ensemble/
|
||
|
||
Usage:
|
||
# Dry run
|
||
python -m v3.scripts.main.phase5.dispatch_logit_mlp_ckpt --dry-run
|
||
|
||
# Submit to server
|
||
python -m v3.scripts.main.phase5.dispatch_logit_mlp_ckpt \
|
||
--server http://hades:8765 --token hypertower
|
||
|
||
# Skip reps already done, re-queue only missing ones:
|
||
python -m v3.scripts.main.phase5.dispatch_logit_mlp_ckpt \
|
||
--server http://hades:8765 --token hypertower
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import os
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import requests
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[4]))
|
||
|
||
RUN_NAME = "phase5/logit_mlp_head_ckpt"
|
||
MODULE = "v3.scripts.main.run_cv"
|
||
OUTPUT_DIR = "v3/results"
|
||
RESULTS_ROOT = Path(__file__).resolve().parents[4] / "v3" / "results"
|
||
REP_SEED_START = 100
|
||
REP_SEED_STEP = 100
|
||
N_REPS = 10
|
||
|
||
RUN_ARGS = [
|
||
"--eval-mode", "binary",
|
||
"--bridge-mode", "fused",
|
||
"--tower-mode", "ensemble",
|
||
"--fused-head",
|
||
"--head-type", "logit_mlp",
|
||
"--epochs", "30",
|
||
"--in-memory-cache",
|
||
"--augment",
|
||
"--tune-binary-threshold",
|
||
"--backbone", "refugelike",
|
||
"--iop-corr-method", "ratio",
|
||
"--iop-drop-raw",
|
||
"--exclude-cols", "Axial_Length",
|
||
"--output-root", "v3/results",
|
||
"--save-checkpoints",
|
||
]
|
||
|
||
|
||
# ── Completion check ──────────────────────────────────────────────────────────
|
||
|
||
def _completed_reps(reps: int) -> list[int]:
|
||
done = []
|
||
for i in range(reps):
|
||
rep_dir = RESULTS_ROOT / RUN_NAME / f"rep{i:02d}" / "binary" / "ensemble"
|
||
if (rep_dir / "summary.json").exists():
|
||
# Also verify at least one checkpoint exists
|
||
if any(rep_dir.glob("fold*/best_single.pt")):
|
||
done.append(i)
|
||
else:
|
||
print(f" [warn] rep{i:02d} has summary.json but no checkpoints — will re-queue")
|
||
return done
|
||
|
||
|
||
# ── Server API ────────────────────────────────────────────────────────────────
|
||
|
||
class _API:
|
||
def __init__(self, base_url: str, token: str):
|
||
self.base_url = base_url.rstrip("/")
|
||
self._h = {"x-token": token}
|
||
|
||
def get(self, path: str, **params) -> object:
|
||
r = requests.get(f"{self.base_url}{path}", headers=self._h, params=params, timeout=10)
|
||
r.raise_for_status()
|
||
return r.json()
|
||
|
||
def post(self, path: str, body: dict) -> dict:
|
||
r = requests.post(f"{self.base_url}{path}", headers=self._h, json=body, timeout=10)
|
||
r.raise_for_status()
|
||
return r.json()
|
||
|
||
|
||
def _queued_reps(jobs: list[dict]) -> set[int]:
|
||
active = set()
|
||
for job in jobs:
|
||
if job.get("run_name") != RUN_NAME:
|
||
continue
|
||
if job["state"] not in ("pending", "running"):
|
||
continue
|
||
try:
|
||
args = job["args"] if isinstance(job["args"], list) else json.loads(job["args"])
|
||
if "--rep-index" in args:
|
||
active.add(int(args[args.index("--rep-index") + 1]))
|
||
except Exception:
|
||
pass
|
||
return active
|
||
|
||
|
||
# ── Main ──────────────────────────────────────────────────────────────────────
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser(description=__doc__,
|
||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||
ap.add_argument("--server", default=os.environ.get("HT_SERVER", ""))
|
||
ap.add_argument("--token", default=os.environ.get("HT_TOKEN", ""))
|
||
ap.add_argument("--reps", type=int, default=N_REPS)
|
||
ap.add_argument("--dry-run", action="store_true")
|
||
args = ap.parse_args()
|
||
|
||
if not args.dry_run:
|
||
if not args.server:
|
||
ap.error("--server required (or set HT_SERVER)")
|
||
if not args.token:
|
||
ap.error("--token required (or set HT_TOKEN)")
|
||
|
||
api = _API(args.server, args.token) if (args.server and args.token) else None
|
||
|
||
server_jobs: list[dict] = []
|
||
if api:
|
||
try:
|
||
all_jobs = api.get("/jobs")
|
||
server_jobs = [j for j in all_jobs if j["state"] in ("pending", "running")]
|
||
print(f"[server] {len(server_jobs)} job(s) pending/running")
|
||
except Exception as e:
|
||
print(f"[warn] could not fetch queue: {e}")
|
||
|
||
done = set(_completed_reps(args.reps))
|
||
queued = _queued_reps(server_jobs)
|
||
missing = [i for i in range(args.reps) if i not in (done | queued)]
|
||
|
||
print(f"\nRun: {RUN_NAME}")
|
||
print(f" Done: {sorted(done)}")
|
||
print(f" Queued: {sorted(queued - done)}")
|
||
print(f" Missing: {missing}")
|
||
|
||
if not missing:
|
||
print("Nothing to submit.")
|
||
return
|
||
|
||
for i in missing:
|
||
seed = REP_SEED_START + i * REP_SEED_STEP
|
||
rep_args = RUN_ARGS + [
|
||
"--run-name", RUN_NAME,
|
||
"--reps", "1",
|
||
"--rep-seed-start", str(seed),
|
||
"--rep-index", str(i),
|
||
]
|
||
body = {
|
||
"run_name": RUN_NAME,
|
||
"module": MODULE,
|
||
"args": rep_args,
|
||
"output_dir": OUTPUT_DIR,
|
||
"priority": 0,
|
||
}
|
||
if args.dry_run:
|
||
print(f" [dry-run] rep{i:02d} seed={seed}")
|
||
else:
|
||
resp = api.post("/jobs", body)
|
||
print(f" queued rep{i:02d} seed={seed} job_id={resp['job_id']}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|