Spaces:
Running on Zero
Running on Zero
File size: 7,663 Bytes
fed954e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | """
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}
|