AbstractPhil's picture
Deploy: 12-task vision extraction + fusion ZeroGPU showcase
fed954e verified
Raw
History Blame Contribute Delete
7.66 kB
"""
report.py — Leaderboards + the no-finetune labeler verdict.
Aggregates per-(model, reasoning, category, mode) metrics into:
* a QUALITY leaderboard ranked by labeler_score (native json_mode columns),
* a FLEET leaderboard folding throughput, and
* a verdict naming the best NO-FINETUNE model — or, if none is natively
sufficient, the best finetune-candidate to feed the SFT/LoRA pipeline.
Native columns use json_mode (no grammar crutch) because that is the signal for
whether a model emits robust JSON on its own.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Optional
from .metrics import labeler_score
from .throughput import fleet_score
# Bucketing thresholds (config-surfaceable later).
ACC_FLOOR = 0.55 # below this the vision itself is too weak
NATIVE_ROBUST = 0.90 # native json_mode robustness to count as "ships as-is"
def _bucket(accuracy: Optional[float], native_robust: float) -> str:
if accuracy is None:
return "no_task_gt"
if accuracy < ACC_FLOOR:
return "insufficient"
if native_robust >= NATIVE_ROBUST:
return "native_capable"
return "finetune_candidate"
def _mean(xs):
xs = [x for x in xs if x is not None]
return sum(xs) / len(xs) if xs else None
def summarize(metric_rows: list[dict]) -> list[dict]:
"""Collapse per-category metric rows into one summary per (model, reasoning).
Native (json_mode) columns drive the no-finetune decision; the constrained
rows are kept only to compute the native-vs-constrained validity gap.
"""
by_model: dict[tuple[str, str], dict] = {}
for r in metric_rows:
key = (r["model"], r["reasoning"])
by_model.setdefault(key, {"json_mode": [], "constrained": []})
bucket = r["mode"] if r["mode"] in ("json_mode", "constrained") else "json_mode"
by_model[key][bucket].append(r)
summaries = []
for (model, reasoning), modes in by_model.items():
jm = modes["json_mode"] or modes["constrained"]
acc = _mean([r["primary_score_mean"] for r in jm if r["has_task_score"]])
valid = _mean([r["schema_valid_rate"] for r in jm]) or 0.0
robust = _mean([r["json_robustness"] for r in jm]) or 0.0
constrained_valid = _mean([r["schema_valid_rate"] for r in modes["constrained"]])
gap = (constrained_valid - valid) if constrained_valid is not None else None
tok_s = _mean([r["tokens_per_sec"] for r in jm]) or 0.0
mean_out = _mean([r["mean_output_tokens"] for r in jm]) or 0.0
lab = labeler_score(acc, valid, robust)
# samples/hour from tok/s + mean output tokens (prefill folded in elsewhere)
sph = (3600.0 * tok_s / mean_out) if (tok_s > 0 and mean_out > 0) else 0.0
summaries.append({
"model": model, "reasoning": reasoning,
"accuracy": acc, "native_valid": valid, "native_robust": robust,
"constrained_valid": constrained_valid, "native_gap": gap,
"labeler_score": lab, "tokens_per_sec": tok_s, "samples_per_hour": sph,
"fleet_score": fleet_score(lab, sph) if lab is not None else None,
"bucket": _bucket(acc, robust),
})
summaries.sort(key=lambda s: (s["labeler_score"] is not None, s["labeler_score"] or -1), reverse=True)
return summaries
def _fmt(x, pct=False):
if x is None:
return "n/a"
return f"{x:.1%}" if pct else f"{x:.3f}"
def quality_table(summaries: list[dict]) -> str:
lines = [
"| rank | model | reason | acc | native_valid | native_robust | gap | labeler | tok/s | bucket |",
"|------|-------|--------|-----|--------------|---------------|-----|---------|-------|--------|",
]
for i, s in enumerate(summaries, 1):
lines.append(
f"| {i} | {s['model']} | {s['reasoning']} | {_fmt(s['accuracy'])} | "
f"{_fmt(s['native_valid'], True)} | {_fmt(s['native_robust'], True)} | "
f"{_fmt(s['native_gap'], True)} | {_fmt(s['labeler_score'])} | "
f"{s['tokens_per_sec']:.0f} | {s['bucket']} |"
)
return "\n".join(lines)
def fleet_table(summaries: list[dict]) -> str:
fs = sorted([s for s in summaries if s["fleet_score"] is not None],
key=lambda s: s["fleet_score"], reverse=True)
lines = [
"| rank | model | reason | labeler | samples/hr | fleet |",
"|------|-------|--------|---------|------------|-------|",
]
for i, s in enumerate(fs, 1):
lines.append(
f"| {i} | {s['model']} | {s['reasoning']} | {_fmt(s['labeler_score'])} | "
f"{s['samples_per_hour']:.0f} | {_fmt(s['fleet_score'])} |"
)
return "\n".join(lines)
def verdict(summaries: list[dict]) -> str:
native = [s for s in summaries if s["bucket"] == "native_capable"]
ft = [s for s in summaries if s["bucket"] == "finetune_candidate"]
out = ["## Headline recommendation", ""]
if native:
b = native[0]
out.append(
f"> **Best no-finetune labeler:** `{b['model']}` ({b['reasoning']}) — "
f"{_fmt(b['native_valid'], True)} native-valid, {_fmt(b['accuracy'])} accuracy, "
f"{b['tokens_per_sec']:.0f} tok/s. Ships as-is."
)
elif ft:
b = ft[0]
out.append(
f"> **No natively-sufficient model.** Best **finetune-candidate:** `{b['model']}` "
f"({b['reasoning']}) — robust vision ({_fmt(b['accuracy'])} acc) but native JSON gap = "
f"{_fmt(b['native_gap'], True)}. Close it with the existing data-gen → SFT/LoRA pipeline."
)
else:
out.append("> No model cleared the accuracy floor on this run. Re-check inputs / categories.")
fleet = sorted([s for s in summaries if s["fleet_score"] is not None],
key=lambda s: s["fleet_score"], reverse=True)
if fleet:
f = fleet[0]
out.append("")
out.append(
f"> **Best fleet labeler (1M+ images):** `{f['model']}` ({f['reasoning']}) — "
f"{f['samples_per_hour']:.0f} samples/hr at labeler {_fmt(f['labeler_score'])}."
)
return "\n".join(out)
def write_reports(run_dir: Path, metric_rows: list[dict], config: dict) -> dict:
summaries = summarize(metric_rows)
md = [
f"# Qwen VLM Labeler Selection — {run_dir.name}",
"",
f"models={config.get('models')} categories={len(config.get('categories', []))} "
f"reasoning={config.get('reasonings')} modes={config.get('modes')} "
f"n={config.get('n')} dataset={config.get('dataset')} runner={config.get('runner')}",
"",
"## Quality leaderboard (labeler_score, native json_mode)",
"",
quality_table(summaries),
"",
"## Fleet leaderboard (accuracy × throughput)",
"",
fleet_table(summaries),
"",
verdict(summaries),
"",
]
(run_dir / "leaderboard.md").write_text("\n".join(md), encoding="utf-8")
(run_dir / "summary.json").write_text(
json.dumps({"config": config, "summaries": summaries}, indent=2), encoding="utf-8")
# CSV
cols = ["model", "reasoning", "accuracy", "native_valid", "native_robust", "native_gap",
"labeler_score", "tokens_per_sec", "samples_per_hour", "fleet_score", "bucket"]
csv_lines = [",".join(cols)]
for s in summaries:
csv_lines.append(",".join("" if s.get(c) is None else str(s.get(c)) for c in cols))
(run_dir / "leaderboard.csv").write_text("\n".join(csv_lines), encoding="utf-8")
return {"summaries": summaries}