Files
cloud-worker/bench/summarize.py
T
2026-08-06 19:21:16 -04:00

116 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""Summarize benchmark results across GPUs and weight families.
Reads bench/results.csv (B200) plus bench/4090/results.csv and
bench/4000/results.csv when present, groups repeated configs, and prints
mean +/- spread tables (markdown, padded columns) for docs/BENCHMARKS.md.
"""
import csv
import json
import statistics
from collections import defaultdict
from pathlib import Path
HERE = Path(__file__).parent
SOURCES = [
("B200", HERE / "results.csv"),
("RTX 4090", HERE / "4090" / "results.csv"),
("RTX 4090 (patched)", HERE / "4090c" / "results.csv"),
("RTX 5090", HERE / "5090" / "results.csv"),
("RTX PRO 4000", HERE / "4000" / "results.csv"),
]
FAMILY_PREFIXES = [
("pi8", "pruned int8"), ("i8", "int8"), ("b16", "bf16"),
("prunedint8", "pruned int8"), ("int8", "int8"), ("bf16", "bf16"),
("g4090_pi8", "pruned int8"), ("g4000_pi8", "pruned int8"),
]
def family_of(row):
m = row["model_file"]
if "pruned" in m:
return "pruned int8"
if "int8" in m:
return "int8"
if "bf16" in m:
return "bf16"
return m
def load(path):
jl = path.with_suffix(".jsonl")
if jl.exists():
rows = [json.loads(l) for l in jl.open() if l.strip()]
return [{k: str(v) if v is not None else "" for k, v in r.items()}
for r in rows if r.get("ok") is True]
if not path.exists():
return []
return [r for r in csv.DictReader(path.open()) if r.get("ok") == "True"]
def key_of(row):
return (row["task"], f'{row["width"]}x{row["height"]}', row["seconds"],
row.get("ref_count") or "1", row.get("ref_image_size") or "match",
row["steps"])
def fmt_stats(vals):
if len(vals) == 1:
return f"{vals[0]:.0f} s"
return f"{statistics.mean(vals):.0f} ± {statistics.stdev(vals):.0f} s (n={len(vals)})"
def pad_table(rows):
widths = [max(len(r[c]) for r in rows) for c in range(len(rows[0]))]
out = []
for i, r in enumerate(rows):
out.append("| " + " | ".join(v.ljust(w) for v, w in zip(r, widths)) + " |")
if i == 0:
out.append("|" + "|".join("-" * (w + 2) for w in widths) + "|")
return "\n".join(out)
def main():
for gpu, path in SOURCES:
rows = load(path)
if not rows:
continue
# group warm runs only (exclude first-load runs marked warm=False)
groups = defaultdict(lambda: defaultdict(list))
vram = defaultdict(lambda: defaultdict(list))
for r in rows:
if r.get("warm") == "False":
continue
fam = family_of(r)
try:
t = float(r["exec_s"])
except (ValueError, TypeError):
continue
groups[key_of(r)][fam].append(t)
try:
vram[key_of(r)][fam].append(float(r["peak_vram_gb"]))
except (ValueError, TypeError):
pass
fams = sorted({f for g in groups.values() for f in g},
key=lambda f: ["pruned int8", "int8", "bf16"].index(f)
if f in ["pruned int8", "int8", "bf16"] else 9)
print(f"\n## {gpu}\n")
header = ["task", "canvas", "sec", "refs", "sizing"] + \
[f"{f} (exec)" for f in fams] + [f"{f} (peak GB)" for f in fams]
table = [header]
for k in sorted(groups):
task, wh, sec, refs, sizing, steps = k
row = [task, wh, sec, refs, sizing]
for f in fams:
row.append(fmt_stats(groups[k][f]) if groups[k][f] else "—")
for f in fams:
row.append(f"{max(vram[k][f]):.0f}" if vram[k][f] else "—")
table.append(row)
print(pad_table(table))
if __name__ == "__main__":
main()