mae-cross-objective / scripts /probe_all.py
quinnlue's picture
MAE cross-objective mixture-ranking campaign
ffdcfe7 verified
Raw
History Blame Contribute Delete
4.33 kB
"""Probe every final export under a run root, in parallel across GPUs.
Reports AS-20K probe mAP against the patching's own random-init floor. That
ratio -- not reconstruction loss -- is the gate. This project has twice been
burned by treating MAE val_loss as a quality signal: cluster-weight-channels.md
measures it at rho=0.72 with the train/eval distribution-match channel, and
regmix-regression-256.md found it uncorrelated with downstream score at
rho=-0.034 over 256 mixtures.
"""
import argparse
import json
import queue
import subprocess
import threading
from pathlib import Path
REPO = "/workspace/code/eat-map-regmix"
FLOORS = {"p16": 0.03861, "p4x64": 0.04867}
def latest_export(run: Path) -> Path | None:
exports = sorted((run / "exports").glob("step_*")) if (run / "exports").exists() else []
return exports[-1] if exports else None
def main():
ap = argparse.ArgumentParser()
ap.add_argument("root")
ap.add_argument("--gpus", default="0,1,2,3")
ap.add_argument("--all-steps", action="store_true", help="probe every export, not just the last")
# The probe is CPU-bound, not GPU-bound: feature extraction is ~15 s but the
# ridge solve and the 527-class mAP run on BLAS, which by default grabs one
# thread per core. Four concurrent probes then ask for 512 threads on 128
# cores and spend 90% of wall time descheduling. Capping threads and raising
# concurrency is worth ~8x here.
ap.add_argument("--per-gpu", type=int, default=2, help="concurrent probes per GPU")
ap.add_argument("--threads", type=int, default=12, help="BLAS threads per probe")
args = ap.parse_args()
jobs: queue.Queue = queue.Queue()
for run in sorted(Path(args.root).iterdir()):
if not run.is_dir():
continue
exports = (sorted((run / "exports").glob("step_*")) if args.all_steps
else [latest_export(run)])
for exp in [e for e in exports if e]:
if not (exp / "probe.json").exists():
jobs.put((run.name, exp))
total = jobs.qsize()
print(f"{total} exports to probe")
lock = threading.Lock()
def worker(gpu):
while True:
try:
name, exp = jobs.get_nowait()
except queue.Empty:
return
threads = str(args.threads)
proc = subprocess.run(
["python", "-m", "eatmap.cli.probe", "--export", str(exp)],
cwd=REPO, capture_output=True,
env={**__import__("os").environ, "CUDA_VISIBLE_DEVICES": str(gpu),
"PYTHONPATH": REPO,
"OMP_NUM_THREADS": threads, "OPENBLAS_NUM_THREADS": threads,
"MKL_NUM_THREADS": threads, "NUMEXPR_NUM_THREADS": threads},
)
with lock:
if proc.returncode:
print(f" FAIL {name} {exp.name}: {proc.stderr.decode()[-200:]}")
jobs.task_done()
threads = [threading.Thread(target=worker, args=(int(g),))
for g in args.gpus.split(",") for _ in range(args.per_gpu)]
for t in threads:
t.start()
for t in threads:
t.join()
rows = []
for run in sorted(Path(args.root).iterdir()):
if not run.is_dir():
continue
for exp in sorted((run / "exports").glob("step_*")):
pj = exp / "probe.json"
if not pj.exists():
continue
d = json.loads(pj.read_text())
tag = "p4x64" if "p4x64" in run.name else "p16"
floor = FLOORS[tag]
status = json.loads((run / "status.json").read_text()) if (run / "status.json").exists() else {}
rows.append((run.name, int(exp.name.split("_")[1]), d["probe/map"],
d["probe/map"] / floor, status.get("val/loss", float("nan"))))
print(f"\n{'run':<24}{'step':>8}{'probe mAP':>12}{'x floor':>10}{'val loss':>10}")
for name, step, m, ratio, vl in sorted(rows, key=lambda r: -r[2]):
print(f"{name:<24}{step:>8}{m:>12.5f}{ratio:>10.2f}{vl:>10.4f}")
Path(args.root, "probe_summary.json").write_text(json.dumps(
[{"run": n, "step": s, "probe_map": m, "x_floor": r, "val_loss": v}
for n, s, m, r, v in rows], indent=1))
if __name__ == "__main__":
main()