"""Build analysis/quant_report.html from analysis/quant_*.json files given on argv.""" import sys, json, html, datetime runs=[json.load(open(p)) for p in sys.argv[1:]] ORDER=["fp32","fp32_onnx","int8_ort","int8_w","int4_w"] LABEL={"fp32":"FP32 (PyTorch)","fp32_onnx":"FP32 (ONNX Runtime)","int8_ort":"INT8 static (ONNX Runtime)","int8_w":"INT8 weight-only (simulated)","int4_w":"INT4 weight-only (simulated)"} def pct(v): return "" if v is None else f"{v*100:.1f}%" def delta(v,ref): return "" if v is None else f"{(v-ref)*100:+.1f}" def cls(v,ref): if v is None: return "" d=(v-ref)*100; return "good" if d>=-0.5 else ("warn" if d>=-3 else "bad") css="""body{font-family:Segoe UI,system-ui,sans-serif;max-width:1100px;margin:2rem auto;padding:0 1rem;color:#222;line-height:1.45} h1{font-size:1.6rem}h2{font-size:1.2rem;margin-top:2rem;border-bottom:1px solid #ddd;padding-bottom:.3rem} table{border-collapse:collapse;width:100%;margin:1rem 0;font-size:.92rem}th,td{border:1px solid #ddd;padding:.45rem .6rem;text-align:right} th:first-child,td:first-child{text-align:left}th{background:#f4f4f4}tr:nth-child(even){background:#fafafa} .good{color:#1a7f37}.warn{color:#9a6700}.bad{color:#c0392b;font-weight:600}.muted{color:#666;font-size:.88rem} .bar{display:inline-block;height:10px;background:#4a7bd0;vertical-align:middle;margin-right:.4rem}code{background:#f2f2f2;padding:0 .25rem} .note{background:#fff8e1;border-left:4px solid #e0a800;padding:.6rem 1rem;margin:1rem 0}""" out=[f"TinyFaceNet quantization report", f"

TinyFaceNet quantization report

Generated {datetime.date.today()} · CPU i7-1360P · torch 2.12 / onnxruntime 1.26

", "
How to read: Family acc = centroid argmax on the date-held-out test faces (small set, so ±1 face ≈ ±2%). " "Open-set acc = same but with unknown threshold 0.45 and 'other' included. " "Agreement = share of 1500 faces from the full dataset where the quantized model picks the same person as FP32 — statistically the more reliable number. " "Cos = mean cosine similarity of quantized vs FP32 embeddings. INT4 rows are weight-only simulations (CPU/Pi have no int4 conv kernels; the gain is file size only). " "Latency here is noisy (measured while a training job was running) and is NOT Pi latency.
"] for r in runs: R=r["results"]; ref=R["fp32"] out.append(f"

{html.escape(r['tag'])} — {html.escape(r['ckpt'])}

") out.append(f"

test faces: {r['n_test']} ({r['n_test_family']} family) · agreement subset: {r['n_agree']} · THR={r['thr']}

") out.append("") mx=max(R[k]["size_kb"] for k in ORDER if k in R) for k in ORDER: if k not in R: continue v=R[k]; lat="—" if v["lat_ms"] is None else f"{v['lat_ms']:.1f} ms" out.append(f"" f"" f"" f"") out.append("
VariantFamily accΔ ptsOpen-set accΔ ptsAgreement vs FP32Cos vs FP32SizeLatency/face (this PC)Note
{LABEL[k]}{pct(v['fam_acc'])}{delta(v['fam_acc'],ref['fam_acc'])}{pct(v['openset_acc'])}{delta(v['openset_acc'],ref['openset_acc'])}{pct(v['argmax_agree_vs_fp32'])}{v['cos_vs_fp32']:.4f}{v['size_kb']/1024:.2f} MB{lat}{html.escape(v['note'])}
") names=[n for n in r["names"] if n in ref["per_class"]] out.append("
Per-person accuracy (test set)"+"".join(f"" for n in names)+"") for k in ORDER: if k in R: out.append(f""+"".join(f"" for n in names)+"") out.append("
Variant{n}
{LABEL[k]}{pct(R[k]['per_class'].get(n))}
") out.append("

Recommendation

") out.append(""); open("analysis/quant_report.html","w",encoding="utf-8").write("\n".join(out)); print("wrote analysis/quant_report.html")