"""Tiny GPU-slot scheduler: runs a list of training configs across the local T4s, N concurrent processes per GPU, skipping configs whose result file already exists.""" import argparse import json import os import subprocess import sys import threading import time from queue import Queue PY = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".venvr", "bin", "python") TRAIN = os.path.join(os.path.dirname(os.path.abspath(__file__)), "train.py") def worker(slot, gpu, q, logdir): while True: try: name, args = q.get_nowait() except Exception: return out = os.path.join(logdir, f"{name}.json") if os.path.exists(out) and os.path.getsize(out) > 0: print(f"[slot{slot}] skip {name}", flush=True) q.task_done() continue env = dict(os.environ, CUDA_VISIBLE_DEVICES=str(gpu), OMP_NUM_THREADS="4") cmd = [PY, TRAIN] + args + ["--out", out] log = os.path.join(logdir, f"{name}.log") t0 = time.time() print(f"[slot{slot}/gpu{gpu}] START {name}", flush=True) with open(log, "w") as fh: rc = subprocess.call(cmd, stdout=fh, stderr=subprocess.STDOUT, env=env) print(f"[slot{slot}/gpu{gpu}] DONE {name} rc={rc} " f"({time.time()-t0:.0f}s)", flush=True) q.task_done() def main(): ap = argparse.ArgumentParser() ap.add_argument("--jobs", required=True, help="json file: {name: [args...]}") ap.add_argument("--logdir", required=True) ap.add_argument("--gpus", default="0,1") ap.add_argument("--per-gpu", type=int, default=2) a = ap.parse_args() os.makedirs(a.logdir, exist_ok=True) jobs = json.load(open(a.jobs)) q = Queue() for name, args in jobs.items(): q.put((name, args)) threads = [] gpus = [g for g in a.gpus.split(",") if g] for gi, gpu in enumerate(gpus): for s in range(a.per_gpu): t = threading.Thread(target=worker, args=(f"{gi}.{s}", gpu, q, a.logdir)) t.start() threads.append(t) time.sleep(3) for t in threads: t.join() print("ALL DONE", flush=True) if __name__ == "__main__": main()