igneum/tools/fleet/collect.py

68 lines
6.5 KiB
Python
Executable file

#!/usr/bin/env python3
"""Phase 1 collector: reads ~/Desktop/fleet/<instance>/matrix.json (and ember.json when present) for every phase-1 box,
writes ~/Desktop/fleet/results.json (the fleet page's rows, keyed by card) and prints the markdown table for
docs/analysis/prover-tiers-real-cards.md. The verdict rule, per card (the 9.0 GB mine-and-prove line and the tier
gates of docs/analysis/prover-floor.md), on the card's OWN numbers:
proves alone = a compressed proof of the v1 shard at 2^26 or 2^27 verified with the server's own working set
plus the idle under the card's memory, but mine-and-prove over the line
mines and proves = the compressed v1 shard beside the miner verified (peak under the card's memory)
mines and proves core-only = only the core-only point beside the miner fits
mining only = no proof point verified on the card (every point refused or out of memory)
"""
import json, os, sys, glob
ROOT = os.path.expanduser("~/Desktop/fleet")
reg = json.load(open(f"{ROOT}/boxes.json"))
def gb(mib): return round(mib / 1024, 1)
rows = {}; table = []
for iid, b in reg.items():
mp = f"{ROOT}/{iid}/matrix.json" # every box with a matrix (a box moves to phase 2 when its Ember ladder ends)
rj = f"{ROOT}/{iid}/rows.jsonl"
if os.path.exists(mp): m = json.load(open(mp))
elif os.path.exists(rj): # the box's final step failed (the 4060 reported power_w=[N/A]): rebuild from the raw rows and the start line
ml = f"{ROOT}/{iid}/matrix.log"; start = [l for l in open(ml, errors="replace") if l.startswith("RESULT start")][-1] if os.path.exists(ml) else ""
g = lambda k, d="": (start.split(k + "=")[1].split()[0] if k + "=" in start else d)
idl = [l for l in open(ml, errors="replace") if l.startswith("RESULT idle")][-1] if os.path.exists(ml) else ""
m = {"label": b["label"], "card": g("card", b["card"]), "total_mib": int(g("total_mib", b.get("vram_mb") or 0)), "driver": g("driver"), "idle_mib": int(idl.split("mem_mib=")[1].split()[0]) if "mem_mib=" in idl else 0, "idle_w": 0, "rows": [json.loads(l) for l in open(rj) if l.strip()]}
else: continue
pts = {r["name"]: r for r in m["rows"] if r.get("row") == "point"}; miner = next((r for r in m["rows"] if r.get("row") == "miner"), {})
total = m["total_mib"]; idle = m["idle_mib"]
# the miner's rate from the raw STATUS lines (the first matrices parsed a second now= field): the mean of the last
# 12 STATUS lines before the miner was stopped for the stock point, i.e. the sampled window
ml = f"{ROOT}/{iid}/miner.log"
if os.path.exists(ml):
vals = [float(l.split(" now=")[1].split()[0]) for l in open(ml, errors="replace") if "STATUS" in l and " now=" in l]
# only when the script's own row is broken (the first matrices parsed a second now= field): the miner-alone
# window is the first 21 STATUS lines (60 s warm + 150 s sample at 10 s), minus the warm-up's first 6
if vals and (not miner.get("mhs") or miner.get("mhs", 0) > 10000): w = vals[6:21]; miner = dict(miner, mhs=round(sum(w) / len(w), 2), mhs_recomputed=True)
def ok(n): p = pts.get(n); return p and p.get("verified") == "yes"
def own(n): p = pts.get(n); return p.get("own_mib") if p else None
def secs(n): p = pts.get(n); return p.get("prove_s") if p else None
alone = next((n for n in ("alone-comp-26-v1", "alone-comp-27-v1", "alone-comp-25-v1") if ok(n)), None)
beside = next((n for n in ("miner-comp-26-v1", "miner-comp-27-v1") if ok(n)), None)
core_b = next((n for n in ("miner-core-25-v1", "miner-core-26-v1", "miner-core-24-v1") if ok(n)), None)
stock = pts.get("stock-comp-v1", {})
if beside: verdict = "mines and proves"
elif alone and core_b: verdict = "mines and proves core-only"
elif alone: verdict = "proves alone"
else: verdict = "mining only"
card = b.get("card") or m["card"]
card = card if "4060 Ti" not in card else f"RTX 4060 Ti {round(total / 1024)} GB"
r = {"card": card, "vram_gb": round(total / 1024), "mhs": miner.get("mhs"), "watts": miner.get("watts"), "miner_gb": gb(miner.get("own_mib", 0)),
"stock": ("not measured: the SDK's server download stalled (killed at 553 s)" if stock.get("rc") == 137 else "refused: " + (stock.get("err") or "")[:60]) if stock.get("verified") != "yes" else f"proved {stock.get('prove_s')} s at {gb(stock.get('peak_mib', 0))} GB",
"prove_alone_gb": gb(own(alone)) if alone else None, "shard_s": secs(alone), "prove_alone_point": alone,
"mine_prove_gb": gb(pts[beside]["peak_mib"]) if beside else None, "mine_prove_s": secs(beside), "core_only_gb": gb(own(core_b)) if core_b else None, "core_only_s": secs(core_b),
"verdict": verdict, "points": {n: {"v": p.get("verified"), "own": p.get("own_mib"), "peak": p.get("peak_mib"), "s": p.get("prove_s"), "err": (p.get("err") or "")[:80]} for n, p in pts.items()}}
r["tier_line"] = {"mines and proves": f"{card} ({r['vram_gb']} GB): mines and proves; the v1 shard beside the miner {r['mine_prove_s']} s at {r['mine_prove_gb']} GB peak",
"mines and proves core-only": f"{card} ({r['vram_gb']} GB): proves alone ({r['shard_s']} s) and mines beside a core-only prover ({r['core_only_gb']} GB own)",
"proves alone": f"{card} ({r['vram_gb']} GB): proves alone, {r['shard_s']} s a v1 shard at {r['prove_alone_gb']} GB own; not beside the miner",
"mining only": f"{card} ({r['vram_gb']} GB): mines only ({r['mhs']} MH/s); no proof point fits"}[verdict]
ep = f"{ROOT}/{iid}/ember.json"
if os.path.exists(ep):
e = json.load(open(ep)); c = e["chosen"]
r.update({"tune_w": c["watts"], "tune_clock_mhz": c["clock_mhz"], "tune_mhs": c["mhs"], "tune_mhw": c["eff"], "ladder": [{"w": s["watts"], "mhs": s["mhs"], "mhw": s["eff"], "step": s["label"]} for s in e["steps"]], "tune_plan": e["plan"]})
rows[f"{card} {r['vram_gb']} GB"] = r
table.append(f"| {card} | {r['vram_gb']} | {idle} | {r['mhs']} MH/s at {r['watts']} W, {r['miner_gb']} GB | {r['stock']} | " +
f"{r['prove_alone_gb'] or 'no'} GB, {r['shard_s'] or ''} s ({alone or 'none'}) | {r['mine_prove_gb'] or 'no'} GB peak, {r['mine_prove_s'] or ''} s | {r['core_only_gb'] or 'no'} GB, {r['core_only_s'] or ''} s | {verdict} |")
json.dump(rows, open(f"{ROOT}/results.json", "w"), indent=1)
print("| Card | VRAM GB | Idle MiB | Miner | Stock SP1 6.8.1 | Patched, proves alone (own) | Beside the miner (peak) | Core-only beside the miner (own) | Verdict |\n|---|---|---|---|---|---|---|---|---|")
print("\n".join(sorted(table)))