inferencekit-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- inferencekit/__init__.py +9 -0
- inferencekit/__main__.py +5 -0
- inferencekit/analysis/__init__.py +1 -0
- inferencekit/analysis/ncu_csv.py +284 -0
- inferencekit/analysis/nsys_sqlite.py +518 -0
- inferencekit/canonical.py +104 -0
- inferencekit/checkpoint.py +396 -0
- inferencekit/cli.py +274 -0
- inferencekit/cuda/bw_peak.cu +252 -0
- inferencekit/cuda/torch_bw.py +60 -0
- inferencekit/engines/__init__.py +7 -0
- inferencekit/engines/adapters.py +190 -0
- inferencekit/engines/clip/__init__.py +13 -0
- inferencekit/engines/clip/comfyui.py +749 -0
- inferencekit/engines/clip/diffusers_ltx.py +601 -0
- inferencekit/engines/clip/noise.py +46 -0
- inferencekit/engines/clip/official_ltx.py +316 -0
- inferencekit/engines/clip/server.py +542 -0
- inferencekit/engines/clip/stepdump.py +182 -0
- inferencekit/engines/gates.py +94 -0
- inferencekit/engines/sglang_worker.py +86 -0
- inferencekit/engines/vllm_worker.py +129 -0
- inferencekit/evidence.py +427 -0
- inferencekit/gpuinfo.py +93 -0
- inferencekit/hardware.py +135 -0
- inferencekit/mcp_server.py +178 -0
- inferencekit/ops/__init__.py +30 -0
- inferencekit/ops/bench.py +184 -0
- inferencekit/ops/bench_audio.py +384 -0
- inferencekit/ops/bench_clip.py +819 -0
- inferencekit/ops/bench_image.py +393 -0
- inferencekit/ops/bench_rounds.py +297 -0
- inferencekit/ops/bench_serve.py +866 -0
- inferencekit/ops/ceiling.py +135 -0
- inferencekit/ops/compare.py +378 -0
- inferencekit/ops/correctness.py +153 -0
- inferencekit/ops/doctor.py +486 -0
- inferencekit/ops/gates.py +1772 -0
- inferencekit/ops/gates_clip.py +930 -0
- inferencekit/ops/intake.py +1321 -0
- inferencekit/ops/ledger.py +749 -0
- inferencekit/ops/manifest.py +309 -0
- inferencekit/ops/ncu.py +183 -0
- inferencekit/ops/nsys.py +224 -0
- inferencekit/ops/prompts.py +316 -0
- inferencekit/ops/quality.py +2161 -0
- inferencekit/ops/quality_clip.py +690 -0
- inferencekit/ops/quality_clip_steps.py +341 -0
- inferencekit/ops/report.py +317 -0
- inferencekit/ops/review.py +119 -0
- inferencekit/ops/rocprof.py +89 -0
- inferencekit/ops/roofline.py +392 -0
- inferencekit/ops/store.py +61 -0
- inferencekit/proc.py +172 -0
- inferencekit/prompts/ATTRIBUTION.md +56 -0
- inferencekit/prompts/__init__.py +167 -0
- inferencekit/prompts/build.py +518 -0
- inferencekit/prompts/dev-load.jsonl.gz +0 -0
- inferencekit/prompts/dev-load.manifest.json +212 -0
- inferencekit/prompts/dev.jsonl.gz +0 -0
- inferencekit/prompts/dev.manifest.json +214 -0
- inferencekit/prompts/exclusions.json +54 -0
- inferencekit/quality/__init__.py +23 -0
- inferencekit/quality/client.py +66 -0
- inferencekit/quality/data/gsm8k_LICENSE.txt +21 -0
- inferencekit/quality/fake.py +329 -0
- inferencekit/quality/gsm8k.py +224 -0
- inferencekit/quality/logprobs.py +106 -0
- inferencekit/quality/metrics.py +235 -0
- inferencekit/quality/niah.py +63 -0
- inferencekit/quality/parquet.py +296 -0
- inferencekit/quality/sampling.py +262 -0
- inferencekit/quality/step_replay.py +217 -0
- inferencekit/quality/video_metrics.py +304 -0
- inferencekit/registry.py +206 -0
- inferencekit/review/__init__.py +6 -0
- inferencekit/review/diff.py +163 -0
- inferencekit/review/media.py +181 -0
- inferencekit/review/page.py +920 -0
- inferencekit/review/rundir.py +844 -0
- inferencekit/roofline_headers.py +504 -0
- inferencekit/schemas/evidence.v1.json +130 -0
- inferencekit/schemas/evidence.v2.json +499 -0
- inferencekit/schemas/intake.v1.json +276 -0
- inferencekit/schemas/run-manifest.v1.json +118 -0
- inferencekit/serve/__init__.py +8 -0
- inferencekit/serve/audio_client.py +330 -0
- inferencekit/serve/client.py +446 -0
- inferencekit/serve/clip_client.py +351 -0
- inferencekit/serve/clip_rounds.py +429 -0
- inferencekit/serve/image_client.py +373 -0
- inferencekit/serve/loadgen.py +136 -0
- inferencekit/serve/metrics.py +203 -0
- inferencekit/serve/profiles.py +105 -0
- inferencekit/serve/prompts_io.py +125 -0
- inferencekit/serve/rounds.py +1111 -0
- inferencekit/serve/servers.py +897 -0
- inferencekit/stats.py +249 -0
- inferencekit_cli-0.1.0.dist-info/METADATA +233 -0
- inferencekit_cli-0.1.0.dist-info/RECORD +104 -0
- inferencekit_cli-0.1.0.dist-info/WHEEL +4 -0
- inferencekit_cli-0.1.0.dist-info/entry_points.txt +2 -0
- inferencekit_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
- inferencekit_cli-0.1.0.dist-info/licenses/NOTICE +9 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
"""quality clip: does a faster video engine still make the reference pipeline's clips?
|
|
2
|
+
|
|
3
|
+
Inputs are directories of clips: a ``bench clip`` outputs directory (``outputs/<label>/r<round>/<id>.mp4``)
|
|
4
|
+
or any directory of ``<id>.<ext>`` files. Clips pair up by their path relative to the directory, so the
|
|
5
|
+
engine and the reference must have been asked for the same clips in the same rounds:
|
|
6
|
+
|
|
7
|
+
--reference the BF16 reference pipeline's clips
|
|
8
|
+
--reference-repeat the reference run a second time with the same requests: the run-to-run A/A floor
|
|
9
|
+
--reference-alt the reference with another exact attention backend (e.g. SDPA math instead of flash):
|
|
10
|
+
the kernel A/A floor, how far an exact but different kernel already moves the clip
|
|
11
|
+
--outputs each engine's clips (one directory per engine)
|
|
12
|
+
|
|
13
|
+
Every clip pair is scored frame by frame by ``quality/video_metrics.py``, which runs as a worker inside the
|
|
14
|
+
video pipeline's own Python (``--python``; PyAV and numpy, and torch + lpips for LPIPS), because the harness
|
|
15
|
+
carries none of those packages. Metrics per frame: PSNR (dB, RGB, capped at 100 dB for identical frames),
|
|
16
|
+
SSIM (luma, Gaussian 11x11) and LPIPS (v0.1, AlexNet); each clip's value is its mean over frames, and the
|
|
17
|
+
request is the bootstrap unit: frames inside a clip are not independent, and neither are the clips of one
|
|
18
|
+
request made again in later rounds (bench clip's ``--seed-policy fixed`` asks for the same clips every
|
|
19
|
+
round; a deterministic pipeline makes them byte for byte). Clips whose kept request bodies match on every
|
|
20
|
+
REQUEST_KEYS field are one unit; without request bodies, every round of a clip id is one unit.
|
|
21
|
+
|
|
22
|
+
Same inputs, or the comparison means nothing: the engine's clip and the reference's must come from the same
|
|
23
|
+
prompt, image, shape, steps, guidance, seed and initial noise. When the directories are bench clip outputs,
|
|
24
|
+
the request bodies it kept (``inputs/<label>/<key>.json``) are compared field by field and a clip whose
|
|
25
|
+
request differs is left out (the verdict is then at best UNPROVEN). Identical initial noise is the
|
|
26
|
+
engines' job (the clip contract's ``noise_seed``); a pipeline that draws its noise differently shows up as
|
|
27
|
+
an engine-vs-reference distance far above the floor.
|
|
28
|
+
|
|
29
|
+
Verdicts (``--fidelity``):
|
|
30
|
+
lossless (default) per metric, a paired bootstrap CI over clips of "engine vs reference" minus "floor vs
|
|
31
|
+
reference" (oriented so positive = worse), against pre-registered margins
|
|
32
|
+
(``--margin-psnr-db``, ``--margin-ssim``, ``--margin-lpips``): PASS when the whole CI is
|
|
33
|
+
within the margin, FAIL when all of it is beyond, else UNPROVEN
|
|
34
|
+
(``quality.metrics.margin_verdict``). The floor is the alternative-backend run when
|
|
35
|
+
given, else the repeat. A deterministic reference repeats itself exactly; an exact floor
|
|
36
|
+
cannot tell a different-but-exact kernel from a lossy one, so whenever the floor used is
|
|
37
|
+
bit-identical to the reference (the repeat, or an alternative-backend run whose switch
|
|
38
|
+
was not applied) an engine that is not bit-exact gets UNPROVEN, never PASS or FAIL.
|
|
39
|
+
lossy a declared quality budget (``--budget``, the full-reference bound: psnr_db_min, ssim_min,
|
|
40
|
+
lpips_max on the engine's mean vs the reference, judged on the CI over clips) and a
|
|
41
|
+
human spot-check record (``--spot-check``): a lossy PASS needs both; "not_acceptable"
|
|
42
|
+
in the spot check is a FAIL. Lossy results are a separate track, reported as
|
|
43
|
+
"N x at the stated quality cost", never ranked against lossless ones.
|
|
44
|
+
gross a loose gross-failure gate beside the per-step check (quality clip-steps), which judges
|
|
45
|
+
losslessness: the lossless comparison with the same margins (set them loose), but only a
|
|
46
|
+
CI entirely beyond the margin FAILs, anything else PASSes (an exact floor still gives
|
|
47
|
+
UNPROVEN: it needs --reference-alt).
|
|
48
|
+
Any clip whose frame size or frame count differs from the reference's is a FAIL (the engine did not make
|
|
49
|
+
the clip it was asked for; gates clip checks the decoded shape against the request), and so is an engine
|
|
50
|
+
clip whose sha256 differs from the one its directory's ``index.json`` (bench clip's outputs index, when
|
|
51
|
+
present) recorded: it is not the clip that was timed. A requested metric that could not be computed (e.g.
|
|
52
|
+
LPIPS without its weights) counts as UNPROVEN.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
|
|
57
|
+
import json
|
|
58
|
+
import re
|
|
59
|
+
import sys
|
|
60
|
+
from pathlib import Path
|
|
61
|
+
|
|
62
|
+
from inferencekit import evidence as ev
|
|
63
|
+
from inferencekit import stats
|
|
64
|
+
from inferencekit.canonical import file_sha256
|
|
65
|
+
from inferencekit.ops.quality import _kind
|
|
66
|
+
from inferencekit.proc import run, which
|
|
67
|
+
from inferencekit.quality import metrics as qm
|
|
68
|
+
from inferencekit.registry import Arg, OpError, operation
|
|
69
|
+
|
|
70
|
+
WORKER = Path(__file__).resolve().parents[1] / "quality" / "video_metrics.py"
|
|
71
|
+
MARK = "INFERKIT_RESULT "
|
|
72
|
+
VIDEO_EXT = (".mp4", ".webm", ".mkv", ".mov", ".avi")
|
|
73
|
+
METRICS = ("psnr", "ssim", "lpips")
|
|
74
|
+
HIGHER_IS_BETTER = {"psnr": True, "ssim": True, "lpips": False}
|
|
75
|
+
UNITS = {"psnr": "dB", "ssim": "index", "lpips": "distance"}
|
|
76
|
+
# Evidence metric names (the review page and a re-run read psnr_db; the flag is --margin-psnr-db).
|
|
77
|
+
NAMES = {"psnr": "psnr_db", "ssim": "ssim", "lpips": "lpips"}
|
|
78
|
+
ROUND_KEY = re.compile(r"^r(\d+)/(.+)$") # bench clip: r<round>/<id>
|
|
79
|
+
BUDGET = {"psnr_db_min": ("psnr", "min"), "ssim_min": ("ssim", "min"), "lpips_max": ("lpips", "max")}
|
|
80
|
+
SPOT_VERDICTS = ("acceptable", "not_acceptable")
|
|
81
|
+
# Request fields the engine's clip and the reference's must share (bench clip's inputs/<label>/<key>.json).
|
|
82
|
+
REQUEST_KEYS = ("task", "prompt", "image", "width", "height", "num_frames", "fps", "steps", "guidance_scale",
|
|
83
|
+
"seed", "noise_seed", "audio")
|
|
84
|
+
FORMAT = "inferencekit.quality.clip.v1"
|
|
85
|
+
PSNR_CAP_DB = 100.0
|
|
86
|
+
FFPROBE_ENTRIES = ("stream=index,codec_type,codec_name,width,height,avg_frame_rate,nb_frames,sample_rate,"
|
|
87
|
+
"channels,duration:frame=stream_index,media_type,width,height,best_effort_timestamp_time,"
|
|
88
|
+
"nb_samples:format=format_name,duration")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# --------------------------------------------------------------------------- the worker and decoding
|
|
92
|
+
|
|
93
|
+
def run_worker(python: str | None, cfg: dict, work: Path, name: str, timeout_s: float) -> dict:
|
|
94
|
+
"""Run video_metrics.py under ``python`` (the video pipeline's interpreter); return its results file.
|
|
95
|
+
|
|
96
|
+
Paths in ``cfg`` are only in the config file, which is deleted afterwards; the results carry keys."""
|
|
97
|
+
python = python or sys.executable
|
|
98
|
+
work.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
cfg_path, out_path = work / f"{name}.config.json", work / f"{name}.results.json"
|
|
100
|
+
log_path, stdout_path = work / f"{name}.log", work / f"{name}.stdout.txt"
|
|
101
|
+
cfg_path.write_text(json.dumps({**cfg, "out": str(out_path)}))
|
|
102
|
+
try:
|
|
103
|
+
r = run([python, str(WORKER), str(cfg_path)], timeout=timeout_s, stdout_path=stdout_path,
|
|
104
|
+
stderr_path=log_path)
|
|
105
|
+
finally:
|
|
106
|
+
cfg_path.unlink(missing_ok=True)
|
|
107
|
+
if log_path.is_file():
|
|
108
|
+
log_path.write_text(ev.redact_text(log_path.read_text(errors="replace")))
|
|
109
|
+
lines = [ln for ln in r.stdout.splitlines() if ln.startswith(MARK)]
|
|
110
|
+
stdout_path.unlink(missing_ok=True)
|
|
111
|
+
head = json.loads(lines[-1][len(MARK):]) if lines else None
|
|
112
|
+
if not head or not head.get("ok") or not out_path.is_file():
|
|
113
|
+
tail = log_path.read_text(errors="replace")[-1200:] if log_path.is_file() else r.stderr[-1200:]
|
|
114
|
+
why = (head or {}).get("error") or f"exit {r.returncode}"
|
|
115
|
+
raise OpError(f"video metrics worker failed under {python} ({why}). It needs PyAV and numpy (and torch + "
|
|
116
|
+
"lpips for LPIPS): pass the video pipeline's interpreter with --python." +
|
|
117
|
+
(f"\nlog tail:\n{tail}" if tail.strip() else ""))
|
|
118
|
+
doc = json.loads(out_path.read_text())
|
|
119
|
+
doc["head"] = head
|
|
120
|
+
return doc
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _rate(text) -> float | None:
|
|
124
|
+
try:
|
|
125
|
+
num, _, den = str(text).partition("/")
|
|
126
|
+
v = float(num) / float(den or 1)
|
|
127
|
+
return v if v > 0 else None
|
|
128
|
+
except (ValueError, ZeroDivisionError):
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _num(x) -> float | None:
|
|
133
|
+
try:
|
|
134
|
+
return float(x)
|
|
135
|
+
except (TypeError, ValueError):
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def probe_ffprobe(path: Path, timeout_s: float = 900) -> dict:
|
|
140
|
+
"""Decode ``path`` with ffprobe (``-show_entries frame=...`` decodes every frame); same shape as the
|
|
141
|
+
worker's probe: what decoding gives, beside what the header claims."""
|
|
142
|
+
exe = which("ffprobe")
|
|
143
|
+
if not exe:
|
|
144
|
+
return {"ok": False, "error": "ffprobe not found"}
|
|
145
|
+
r = run([exe, "-v", "error", "-show_entries", FFPROBE_ENTRIES, "-of", "json", str(path)], timeout=timeout_s)
|
|
146
|
+
if r.returncode != 0:
|
|
147
|
+
return {"ok": False, "error": f"ffprobe exit {r.returncode}: {r.stderr.strip()[:300]}"}
|
|
148
|
+
try:
|
|
149
|
+
doc = json.loads(r.stdout)
|
|
150
|
+
except ValueError:
|
|
151
|
+
return {"ok": False, "error": "ffprobe printed no JSON"}
|
|
152
|
+
streams, frames = doc.get("streams") or [], doc.get("frames") or []
|
|
153
|
+
vs = next((s for s in streams if s.get("codec_type") == "video"), None)
|
|
154
|
+
au = next((s for s in streams if s.get("codec_type") == "audio"), None)
|
|
155
|
+
fmt = doc.get("format") or {}
|
|
156
|
+
out = {"ok": True, "decoder": "ffprobe", "video": None, "audio": None,
|
|
157
|
+
"header": {"format": fmt.get("format_name"), "duration_s": _num(fmt.get("duration"))}}
|
|
158
|
+
if vs is not None:
|
|
159
|
+
out["header"]["video"] = {"codec": vs.get("codec_name"), "width": vs.get("width"),
|
|
160
|
+
"height": vs.get("height"), "frames": int(vs["nb_frames"])
|
|
161
|
+
if str(vs.get("nb_frames", "")).isdigit() else None,
|
|
162
|
+
"fps": _rate(vs.get("avg_frame_rate"))}
|
|
163
|
+
vf = [f for f in frames if f.get("stream_index") == vs.get("index")]
|
|
164
|
+
sizes = sorted({(f.get("width"), f.get("height")) for f in vf})
|
|
165
|
+
times = sorted(t for t in (_num(f.get("best_effort_timestamp_time")) for f in vf) if t is not None)
|
|
166
|
+
fps = (len(times) - 1) / (times[-1] - times[0]) if len(times) >= 2 and times[-1] > times[0] else None
|
|
167
|
+
w, h = sizes[0] if sizes else (None, None)
|
|
168
|
+
out["video"] = {"frames": len(vf), "width": w, "height": h, "sizes": [list(s) for s in sizes], "fps": fps,
|
|
169
|
+
"first_s": times[0] if times else None, "last_s": times[-1] if times else None,
|
|
170
|
+
"duration_s": len(vf) / fps if fps else None}
|
|
171
|
+
if au is not None:
|
|
172
|
+
rate = int(au["sample_rate"]) if str(au.get("sample_rate", "")).isdigit() else None
|
|
173
|
+
out["header"]["audio"] = {"codec": au.get("codec_name"), "sample_rate": rate, "channels": au.get("channels"),
|
|
174
|
+
"duration_s": _num(au.get("duration"))}
|
|
175
|
+
samples = sum(int(f.get("nb_samples") or 0) for f in frames if f.get("stream_index") == au.get("index"))
|
|
176
|
+
out["audio"] = {"samples": samples, "sample_rate": rate, "duration_s": samples / rate if rate else None}
|
|
177
|
+
return out
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def probe_clips(files: list[tuple[str, Path]], python: str | None, work: Path, timeout_s: float) -> tuple[dict, str]:
|
|
181
|
+
"""Decode each clip: with the worker (PyAV) when ``python`` is given, else ffprobe. ({key: probe}, decoder)."""
|
|
182
|
+
if python:
|
|
183
|
+
doc = run_worker(python, {"task": "probe", "files": [{"key": k, "path": str(p)} for k, p in files]},
|
|
184
|
+
work, "probe", timeout_s)
|
|
185
|
+
return {it["key"]: it for it in doc["items"]}, "pyav"
|
|
186
|
+
if not which("ffprobe"):
|
|
187
|
+
raise OpError("nothing to decode clips with: install ffprobe (ffmpeg) or pass --python (a venv with PyAV)")
|
|
188
|
+
return {k: probe_ffprobe(p, timeout_s) for k, p in files}, "ffprobe"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# --------------------------------------------------------------------------- clip directories
|
|
192
|
+
|
|
193
|
+
def clip_files(d: Path) -> dict[str, Path]:
|
|
194
|
+
"""{key: path} for every clip under ``d``; key = the path relative to ``d`` without its extension."""
|
|
195
|
+
if not d.is_dir():
|
|
196
|
+
raise OpError(f"{d}: not a directory of clips")
|
|
197
|
+
out: dict[str, Path] = {}
|
|
198
|
+
for p in sorted(d.rglob("*")):
|
|
199
|
+
if p.is_file() and p.suffix.lower() in VIDEO_EXT:
|
|
200
|
+
key = p.relative_to(d).with_suffix("").as_posix()
|
|
201
|
+
if key in out:
|
|
202
|
+
raise OpError(f"{d}: two clips share the key {key!r} ({out[key].name}, {p.name})")
|
|
203
|
+
out[key] = p
|
|
204
|
+
if not out:
|
|
205
|
+
raise OpError(f"{d}: no clips ({', '.join(VIDEO_EXT)})")
|
|
206
|
+
return out
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def requests_of(d: Path) -> dict[str, dict]:
|
|
210
|
+
"""bench clip's request bodies for an ``outputs/<label>`` directory: ``inputs/<label>/<key>.json``."""
|
|
211
|
+
if d.parent.name != "outputs":
|
|
212
|
+
return {}
|
|
213
|
+
root = d.parent.parent / "inputs" / d.name
|
|
214
|
+
out = {}
|
|
215
|
+
for p in sorted(root.rglob("*.json")) if root.is_dir() else []:
|
|
216
|
+
try:
|
|
217
|
+
doc = json.loads(p.read_text())
|
|
218
|
+
except (OSError, ValueError):
|
|
219
|
+
continue
|
|
220
|
+
if isinstance(doc, dict):
|
|
221
|
+
out[p.relative_to(root).with_suffix("").as_posix()] = doc
|
|
222
|
+
return out
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def request_diff(a: dict | None, b: dict | None) -> list[str] | None:
|
|
226
|
+
"""Request fields that differ (None when either side has no request body to compare)."""
|
|
227
|
+
if a is None or b is None:
|
|
228
|
+
return None
|
|
229
|
+
return [k for k in REQUEST_KEYS if a.get(k) != b.get(k)]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def split_key(key: str) -> tuple[int | None, str]:
|
|
233
|
+
"""(round, clip id) of a clip key: bench clip's ``r<round>/<id>``, else (None, the key)."""
|
|
234
|
+
m = ROUND_KEY.match(key)
|
|
235
|
+
return (int(m.group(1)), m.group(2)) if m else (None, key)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def request_groups(keys, bodies: list[dict[str, dict]]) -> dict[str, str]:
|
|
239
|
+
"""{key: request identity}; clips of one identity are one bootstrap unit, on every side alike.
|
|
240
|
+
|
|
241
|
+
The identity is the REQUEST_KEYS values of the first request body found for the key (the reference's
|
|
242
|
+
first: the sides' bodies must match it to be scored). Without any body it is the clip id, so every
|
|
243
|
+
round of a clip counts once: rounds may have repeated the same request, and a unit too many narrows the
|
|
244
|
+
CI (by about sqrt(rounds) when every round repeats), a unit too few only widens it."""
|
|
245
|
+
out = {}
|
|
246
|
+
for k in keys:
|
|
247
|
+
body = next((b[k] for b in bodies if k in b), None)
|
|
248
|
+
out[k] = ("request " + json.dumps({f: body.get(f) for f in REQUEST_KEYS}, sort_keys=True)
|
|
249
|
+
if body is not None else "clip " + split_key(k)[1])
|
|
250
|
+
return out
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def index_mismatch(d: Path, files: dict[str, Path]) -> dict[str, str]:
|
|
254
|
+
"""{key: why} for clips whose sha256 differs from ``d/index.json`` (bench clip's outputs index, when the
|
|
255
|
+
directory has one): the file is not the clip that was timed."""
|
|
256
|
+
idx = d / "index.json"
|
|
257
|
+
if not idx.is_file():
|
|
258
|
+
return {}
|
|
259
|
+
try:
|
|
260
|
+
listed = (json.loads(idx.read_text()) or {}).get("files") or {}
|
|
261
|
+
except (OSError, ValueError, AttributeError):
|
|
262
|
+
return {}
|
|
263
|
+
out = {}
|
|
264
|
+
for key, p in files.items():
|
|
265
|
+
want = listed.get(p.relative_to(d).as_posix()) if isinstance(listed, dict) else None
|
|
266
|
+
if isinstance(want, str) and file_sha256(p)[0] != want:
|
|
267
|
+
out[key] = "sha256 differs from index.json: not the clip that was timed"
|
|
268
|
+
return out
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# --------------------------------------------------------------------------- scoring
|
|
272
|
+
|
|
273
|
+
def _units(items: dict[str, dict], metric: str, groups: dict[str, str], keys=None) -> list[tuple[float, int]]:
|
|
274
|
+
"""Per request (sum over its clips' frames, frames): the request is the bootstrap unit. Units come in the
|
|
275
|
+
order of ``keys`` (default: the items'), so two sides scored over the same keys pair up unit by unit."""
|
|
276
|
+
acc: dict[str, list] = {}
|
|
277
|
+
for k in items if keys is None else keys:
|
|
278
|
+
vs = [v for v in (items[k].get("per_frame") or {}).get(metric) or [] if v is not None]
|
|
279
|
+
u = acc.setdefault(groups.get(k, k), [0.0, 0])
|
|
280
|
+
u[0] += sum(vs)
|
|
281
|
+
u[1] += len(vs)
|
|
282
|
+
return [(s, n) for s, n in acc.values()]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _scored(items: dict[str, dict], metric: str) -> int:
|
|
286
|
+
return sum(1 for it in items.values() if any(v is not None for v in (it.get("per_frame") or {}).get(metric) or []))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _exact(it: dict) -> bool:
|
|
290
|
+
return bool(it.get("frames_compared")) and it.get("identical_frames") == it.get("frames_compared")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def lossless_check(eng: dict[str, dict], flo: dict[str, dict], keys: list[str], groups: dict[str, str], metric: str,
|
|
294
|
+
margin: float, resamples: int, seed: int) -> dict:
|
|
295
|
+
d = qm.paired_delta_ci(_units(eng, metric, groups, keys), _units(flo, metric, groups, keys), resamples, seed)
|
|
296
|
+
lo, hi = d["ci95"]
|
|
297
|
+
if HIGHER_IS_BETTER[metric]: # worse = floor - engine
|
|
298
|
+
worse = (-d["delta"] if d["delta"] is not None else None, -hi if hi is not None else None,
|
|
299
|
+
-lo if lo is not None else None)
|
|
300
|
+
else:
|
|
301
|
+
worse = (d["delta"], lo, hi)
|
|
302
|
+
v, why = qm.margin_verdict(worse[1], worse[2], margin)
|
|
303
|
+
both = [k for k in keys if _scored({k: eng[k]}, metric) and _scored({k: flo[k]}, metric)]
|
|
304
|
+
return {"verdict": v, "reason": why, "worse_than_floor": worse[0], "ci95": [worse[1], worse[2]],
|
|
305
|
+
"margin": margin, "n_clips": len(both), "n_requests": d["n_prompts"]}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def budget_check(eng: dict[str, dict], groups: dict[str, str], metric: str, side: str, bound: float, resamples: int,
|
|
309
|
+
seed: int) -> dict:
|
|
310
|
+
units = _units(eng, metric, groups)
|
|
311
|
+
value = qm.pooled(units)
|
|
312
|
+
lo, hi = qm.cluster_ci(units, resamples, seed)
|
|
313
|
+
if lo is None or hi is None:
|
|
314
|
+
v, why = "UNPROVEN", "no confidence interval (need >= 2 distinct requests scored)"
|
|
315
|
+
elif side == "min":
|
|
316
|
+
v = "PASS" if lo >= bound else "FAIL" if hi < bound else "UNPROVEN"
|
|
317
|
+
why = f"CI [{lo:.4g}, {hi:.4g}] vs minimum {bound:g}"
|
|
318
|
+
else:
|
|
319
|
+
v = "PASS" if hi <= bound else "FAIL" if lo > bound else "UNPROVEN"
|
|
320
|
+
why = f"CI [{lo:.4g}, {hi:.4g}] vs maximum {bound:g}"
|
|
321
|
+
return {"verdict": v, "reason": why, "value": value, "ci95": [lo, hi], "bound": bound, "side": side,
|
|
322
|
+
"n_clips": _scored(eng, metric), "n_requests": len([u for u in units if u[1]])}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def summary_of(items: dict[str, dict], groups: dict[str, str], metric: str, resamples: int, seed: int) -> dict:
|
|
326
|
+
units = _units(items, metric, groups)
|
|
327
|
+
return {"value": qm.pooled(units), "ci95": list(qm.cluster_ci(units, resamples, seed)),
|
|
328
|
+
"n_clips": _scored(items, metric), "n_requests": len([u for u in units if u[1]]),
|
|
329
|
+
"n_frames": sum(u[1] for u in units)}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def load_spot_check(path: str | None) -> dict | None:
|
|
333
|
+
"""The human spot-check record: {reviewer, date, clips: [...], verdict: acceptable|not_acceptable, notes?}."""
|
|
334
|
+
if not path:
|
|
335
|
+
return None
|
|
336
|
+
p = Path(path)
|
|
337
|
+
try:
|
|
338
|
+
doc = json.loads(p.read_text())
|
|
339
|
+
except (OSError, ValueError) as e:
|
|
340
|
+
raise OpError(f"--spot-check {path}: not a readable JSON file ({type(e).__name__})") from None
|
|
341
|
+
problems = []
|
|
342
|
+
if not isinstance(doc, dict):
|
|
343
|
+
raise OpError(f"--spot-check {path}: must be a JSON object")
|
|
344
|
+
for k in ("reviewer", "date"):
|
|
345
|
+
if not isinstance(doc.get(k), str) or not doc[k].strip():
|
|
346
|
+
problems.append(f"{k} must be non-empty text")
|
|
347
|
+
if doc.get("verdict") not in SPOT_VERDICTS:
|
|
348
|
+
problems.append(f"verdict must be one of {list(SPOT_VERDICTS)}")
|
|
349
|
+
if not isinstance(doc.get("clips"), list) or not doc["clips"] or not all(isinstance(c, str) for c in doc["clips"]):
|
|
350
|
+
problems.append("clips must list the clip keys the reviewer watched")
|
|
351
|
+
if problems:
|
|
352
|
+
raise OpError(f"--spot-check {path}: " + "; ".join(problems))
|
|
353
|
+
sha = ev.artifact(p, None, "spot_check")["sha256"]
|
|
354
|
+
return {"sha256": sha, "reviewer": doc["reviewer"], "date": doc["date"], "verdict": doc["verdict"],
|
|
355
|
+
"clips": doc["clips"], "notes": str(doc.get("notes") or "")[:2000]}
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _budget_arg(text: str | None) -> dict | None:
|
|
359
|
+
if not text:
|
|
360
|
+
return None
|
|
361
|
+
try:
|
|
362
|
+
doc = json.loads(text)
|
|
363
|
+
except ValueError:
|
|
364
|
+
raise OpError("--budget must be a JSON object, e.g. {\"lpips_max\": 0.05, \"psnr_db_min\": 30}") from None
|
|
365
|
+
if not isinstance(doc, dict) or not doc:
|
|
366
|
+
raise OpError("--budget must be a non-empty JSON object")
|
|
367
|
+
bad = [k for k in doc if k not in BUDGET]
|
|
368
|
+
if bad:
|
|
369
|
+
raise OpError(f"--budget: unknown key(s) {bad}; known: {sorted(BUDGET)}")
|
|
370
|
+
for k, v in doc.items():
|
|
371
|
+
if not isinstance(v, (int, float)) or isinstance(v, bool):
|
|
372
|
+
raise OpError(f"--budget {k} must be a number")
|
|
373
|
+
return doc
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# --------------------------------------------------------------------------- the operation
|
|
377
|
+
|
|
378
|
+
@operation("quality_clip", ("quality", "clip"),
|
|
379
|
+
"Video clip quality vs the BF16 reference pipeline: per-frame PSNR, SSIM and LPIPS of each engine's "
|
|
380
|
+
"clips (same prompt, steps, seed and initial noise), against an A/A floor (the reference repeated, or "
|
|
381
|
+
"run with another exact attention backend) with bootstrap CIs over clips; lossless margins or a lossy "
|
|
382
|
+
"budget plus a human spot check.",
|
|
383
|
+
[Arg("reference", "string", "The reference pipeline's clips: a bench clip outputs/<label> directory or "
|
|
384
|
+
"any directory of <id>.<ext> clips.", required=True),
|
|
385
|
+
Arg("outputs", "array", "Each engine's clip directory, laid out like --reference.", required=True),
|
|
386
|
+
Arg("reference_repeat", "string", "The reference run a second time with the same requests (the "
|
|
387
|
+
"run-to-run A/A floor)."),
|
|
388
|
+
Arg("reference_alt", "string", "The reference with another exact attention backend (the kernel A/A "
|
|
389
|
+
"floor; used as the floor when given)."),
|
|
390
|
+
Arg("python", "string", "Python of the video pipeline's venv (PyAV, numpy; torch + lpips for LPIPS): "
|
|
391
|
+
"the metrics worker runs there. Default: this interpreter."),
|
|
392
|
+
Arg("metrics", "array", "Metrics to compute and judge: psnr, ssim, lpips.", default=list(METRICS),
|
|
393
|
+
items="string"),
|
|
394
|
+
Arg("fidelity", "string", "lossless: judged against the A/A floor with margins; lossy: judged against "
|
|
395
|
+
"--budget, plus a human --spot-check; gross: the lossless comparison as a loose gate (only a CI "
|
|
396
|
+
"entirely beyond the margin fails).", default="lossless", choices=("lossless", "lossy", "gross")),
|
|
397
|
+
Arg("margin_psnr_db", "number", "Pre-registered margin: max PSNR drop vs the floor (dB).", default=1.0),
|
|
398
|
+
Arg("margin_ssim", "number", "Pre-registered margin: max SSIM drop vs the floor.", default=0.01),
|
|
399
|
+
Arg("margin_lpips", "number", "Pre-registered margin: max LPIPS increase vs the floor.", default=0.01),
|
|
400
|
+
Arg("budget", "string", "Lossy: the declared full-reference bound, JSON with any of psnr_db_min, "
|
|
401
|
+
"ssim_min, lpips_max (on the engine's mean vs the reference)."),
|
|
402
|
+
Arg("spot_check", "string", "Lossy: the human spot-check record, JSON {reviewer, date, clips: [keys], "
|
|
403
|
+
"verdict: acceptable|not_acceptable, notes?}."),
|
|
404
|
+
Arg("device", "string", "Where LPIPS runs (auto = the GPU when the worker sees one).", default="auto",
|
|
405
|
+
choices=("auto", "cpu", "cuda")),
|
|
406
|
+
Arg("frame_stride", "integer", "Score every n-th frame (1 = every frame).", default=1),
|
|
407
|
+
Arg("model", "string", "Model id for the record's identity."),
|
|
408
|
+
Arg("run_dir", "string", "Run directory: the score is also written to <run-dir>/quality/clip_score.json "
|
|
409
|
+
"and the record to <run-dir>/evidence/."),
|
|
410
|
+
Arg("resamples", "integer", "Bootstrap resamples.", default=stats.DEFAULT_RESAMPLES),
|
|
411
|
+
Arg("seed", "integer", "Bootstrap seed.", default=0),
|
|
412
|
+
Arg("timeout_s", "number", "Seconds the metrics worker may take in all.", default=14400)])
|
|
413
|
+
def quality_clip(call, reference, outputs, reference_repeat, reference_alt, python, metrics, fidelity,
|
|
414
|
+
margin_psnr_db, margin_ssim, margin_lpips, budget, spot_check, device, frame_stride, model,
|
|
415
|
+
run_dir, resamples, seed, timeout_s):
|
|
416
|
+
metrics = [m.lower() for m in (metrics or [])]
|
|
417
|
+
if not metrics or any(m not in METRICS for m in metrics):
|
|
418
|
+
raise OpError(f"--metrics must be a non-empty subset of {list(METRICS)}")
|
|
419
|
+
if frame_stride < 1:
|
|
420
|
+
raise OpError("frame_stride must be >= 1")
|
|
421
|
+
budget_doc = _budget_arg(budget)
|
|
422
|
+
if fidelity == "lossy" and not budget_doc:
|
|
423
|
+
raise OpError("--fidelity lossy needs --budget (the declared full-reference bound)")
|
|
424
|
+
if fidelity in ("lossless", "gross") and (budget_doc or spot_check):
|
|
425
|
+
raise OpError("--budget and --spot-check are for --fidelity lossy")
|
|
426
|
+
for k in budget_doc or {}:
|
|
427
|
+
if BUDGET[k][0] not in metrics:
|
|
428
|
+
raise OpError(f"--budget {k} needs {BUDGET[k][0]} in --metrics")
|
|
429
|
+
spot = load_spot_check(spot_check)
|
|
430
|
+
margins = {"psnr": margin_psnr_db, "ssim": margin_ssim, "lpips": margin_lpips}
|
|
431
|
+
|
|
432
|
+
ref_dir = Path(reference)
|
|
433
|
+
ref = clip_files(ref_dir)
|
|
434
|
+
ref_req = requests_of(ref_dir)
|
|
435
|
+
floors = {name: Path(p) for name, p in (("alt", reference_alt), ("repeat", reference_repeat)) if p}
|
|
436
|
+
resolved = {ref_dir.resolve()}
|
|
437
|
+
for name, p in floors.items():
|
|
438
|
+
if p.resolve() in resolved:
|
|
439
|
+
raise OpError(f"--reference-{name} must be another run of the reference, not the same directory")
|
|
440
|
+
resolved.add(p.resolve())
|
|
441
|
+
engines: dict[str, Path] = {}
|
|
442
|
+
for p in map(Path, outputs or []):
|
|
443
|
+
if p.resolve() in resolved:
|
|
444
|
+
raise OpError(f"{p}: the same directory as a reference run")
|
|
445
|
+
label = p.name
|
|
446
|
+
if label in engines:
|
|
447
|
+
raise OpError(f"two --outputs directories are both named {label!r}; rename one")
|
|
448
|
+
engines[label] = p
|
|
449
|
+
if not engines:
|
|
450
|
+
raise OpError("--outputs: give at least one engine's clip directory")
|
|
451
|
+
|
|
452
|
+
sides = {f"floor:{n}": d for n, d in floors.items()} | {f"engine:{lab}": d for lab, d in engines.items()}
|
|
453
|
+
files = {s: clip_files(d) for s, d in sides.items()}
|
|
454
|
+
reqs = {s: requests_of(d) for s, d in sides.items()}
|
|
455
|
+
groups = request_groups(ref, [ref_req] + list(reqs.values()))
|
|
456
|
+
not_timed = {s: index_mismatch(sides[s], files[s]) for s in sides if s.startswith("engine:")}
|
|
457
|
+
pairs, plan = [], {} # plan[side] = {key: {"pair": id} | {"missing": True} | {"request_diff": [...]}}
|
|
458
|
+
for side, have in files.items():
|
|
459
|
+
plan[side] = {}
|
|
460
|
+
for key, ref_path in ref.items():
|
|
461
|
+
if key not in have:
|
|
462
|
+
plan[side][key] = {"missing": True}
|
|
463
|
+
continue
|
|
464
|
+
diff = request_diff(ref_req.get(key), reqs[side].get(key))
|
|
465
|
+
if diff:
|
|
466
|
+
plan[side][key] = {"request_diff": diff}
|
|
467
|
+
continue
|
|
468
|
+
pid = f"{side}|{key}"
|
|
469
|
+
pairs.append({"key": pid, "a": str(ref_path), "b": str(have[key])})
|
|
470
|
+
plan[side][key] = {"pair": pid, "request_checked": diff is not None}
|
|
471
|
+
extra = sorted(set(have) - set(ref))
|
|
472
|
+
if extra:
|
|
473
|
+
plan[side]["__extra__"] = extra
|
|
474
|
+
|
|
475
|
+
store = ev.Store()
|
|
476
|
+
with store.staging_dir() as staging:
|
|
477
|
+
doc = run_worker(python, {"task": "compare", "pairs": pairs, "metrics": metrics, "device": device,
|
|
478
|
+
"frame_stride": frame_stride, "psnr_cap_db": PSNR_CAP_DB, "lpips_net": "alex"},
|
|
479
|
+
staging / "worker", "compare", timeout_s)
|
|
480
|
+
by_pair = {it["key"]: it for it in doc["items"]}
|
|
481
|
+
computed = [m for m in metrics if not (m == "lpips" and doc.get("lpips_error"))]
|
|
482
|
+
result = _score(plan, by_pair, ref, floors, engines, metrics, computed, margins, fidelity, budget_doc, spot,
|
|
483
|
+
resamples, seed, doc.get("lpips_error"), groups, not_timed)
|
|
484
|
+
rows = staging / "clip_metrics.jsonl"
|
|
485
|
+
rows.write_text("".join(json.dumps(it) + "\n" for it in doc["items"]))
|
|
486
|
+
artifacts = [ev.artifact(rows, staging, "clip_metrics")]
|
|
487
|
+
log = staging / "worker" / "compare.log"
|
|
488
|
+
if log.is_file() and log.stat().st_size:
|
|
489
|
+
artifacts.append(ev.artifact(log, staging, "worker_log"))
|
|
490
|
+
for leftover in (staging / "worker").glob("*.results.json"):
|
|
491
|
+
leftover.unlink()
|
|
492
|
+
return _save(call, store, staging, result, artifacts, doc, model, ref_req, run_dir)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _side_items(plan_side: dict, by_pair: dict) -> dict[str, dict]:
|
|
496
|
+
return {k: by_pair[v["pair"]] for k, v in plan_side.items()
|
|
497
|
+
if k != "__extra__" and "pair" in v and v["pair"] in by_pair}
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _per_clip(items: dict[str, dict]) -> list[dict]:
|
|
501
|
+
"""[{id, round, psnr_db, ssim, lpips}] per scored clip (its mean over frames), in key order."""
|
|
502
|
+
out = []
|
|
503
|
+
for k, it in sorted(items.items()):
|
|
504
|
+
r, cid = split_key(k)
|
|
505
|
+
mean = it.get("mean") or {}
|
|
506
|
+
out.append({"id": cid, "round": r, **{NAMES[m]: mean.get(m) for m in METRICS}})
|
|
507
|
+
return out
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _score(plan, by_pair, ref, floors, engines, metrics, computed, margins, fidelity, budget, spot, resamples, seed,
|
|
511
|
+
lpips_error, groups, not_timed) -> dict:
|
|
512
|
+
floor_side = "floor:alt" if "alt" in floors else "floor:repeat" if "repeat" in floors else None
|
|
513
|
+
floor_items = {s: _side_items(plan[s], by_pair) for s in plan if s.startswith("floor:")}
|
|
514
|
+
floor_info = {}
|
|
515
|
+
for s, items in floor_items.items():
|
|
516
|
+
ok = {k: it for k, it in items.items() if it.get("ok")}
|
|
517
|
+
floor_info[s.split(":", 1)[1]] = {
|
|
518
|
+
"n_clips": len(ok), "exact_clips": sum(1 for it in ok.values() if _exact(it)),
|
|
519
|
+
"missing": sorted(k for k, v in plan[s].items() if k != "__extra__" and v.get("missing")),
|
|
520
|
+
"request_mismatch": {k: v["request_diff"] for k, v in plan[s].items() if k != "__extra__"
|
|
521
|
+
and v.get("request_diff")},
|
|
522
|
+
"metrics": {m: summary_of(ok, groups, m, resamples, seed) for m in computed}}
|
|
523
|
+
out_engines = {}
|
|
524
|
+
for label in engines:
|
|
525
|
+
side = f"engine:{label}"
|
|
526
|
+
items = _side_items(plan[side], by_pair)
|
|
527
|
+
missing = sorted(k for k, v in plan[side].items() if k != "__extra__" and v.get("missing"))
|
|
528
|
+
mismatch = {k: v["request_diff"] for k, v in plan[side].items() if k != "__extra__" and v.get("request_diff")}
|
|
529
|
+
unchecked = sum(1 for k, v in plan[side].items() if k != "__extra__" and "pair" in v
|
|
530
|
+
and not v.get("request_checked"))
|
|
531
|
+
changed = {k: why for k, why in not_timed.get(side, {}).items() if k in items}
|
|
532
|
+
shape_bad = {k: (it.get("error") or f"{it.get('frames_b')} frames vs the reference's {it.get('frames_a')}")
|
|
533
|
+
for k, it in items.items() if k not in changed and (not it.get("ok")
|
|
534
|
+
or it.get("frame_count_differs"))}
|
|
535
|
+
good = {k: it for k, it in items.items() if k not in shape_bad and k not in changed}
|
|
536
|
+
checks, caps = {}, []
|
|
537
|
+
if changed:
|
|
538
|
+
k0 = next(iter(changed))
|
|
539
|
+
checks["outputs_index"] = {"verdict": "FAIL", "reason": f"{len(changed)} clip file(s) are not the clips "
|
|
540
|
+
f"bench clip timed (e.g. {k0}: {changed[k0]})"}
|
|
541
|
+
if shape_bad:
|
|
542
|
+
k0 = next(iter(shape_bad))
|
|
543
|
+
checks["shape"] = {"verdict": "FAIL", "reason": f"{len(shape_bad)} clip(s) differ from the reference in "
|
|
544
|
+
f"frame size or frame count (e.g. {k0}: {shape_bad[k0]})"}
|
|
545
|
+
if missing:
|
|
546
|
+
caps.append(f"{len(missing)} reference clip(s) have no engine clip (e.g. {missing[0]})")
|
|
547
|
+
if mismatch:
|
|
548
|
+
k0 = next(iter(mismatch))
|
|
549
|
+
caps.append(f"{len(mismatch)} clip(s) were made from a different request than the reference's (e.g. {k0}: "
|
|
550
|
+
f"{', '.join(mismatch[k0])}) and were not scored")
|
|
551
|
+
block = {"n_clips_scored": len(good), "n_requests_scored": len({groups.get(k, k) for k in good}),
|
|
552
|
+
"missing": missing, "request_mismatch": mismatch, "requests_unchecked": unchecked,
|
|
553
|
+
"shape_mismatch": shape_bad, "not_timed": changed, "extra_clips": plan[side].get("__extra__", []),
|
|
554
|
+
"metrics": {m: summary_of(good, groups, m, resamples, seed) for m in computed},
|
|
555
|
+
"exact_clips": sum(1 for it in good.values() if _exact(it)), "per_clip": _per_clip(good)}
|
|
556
|
+
if fidelity in ("lossless", "gross"):
|
|
557
|
+
if floor_side is None:
|
|
558
|
+
caps.append("no A/A floor (pass --reference-repeat and/or --reference-alt)")
|
|
559
|
+
for m in metrics:
|
|
560
|
+
checks[m] = {"verdict": "NOT RUN", "reason": "no A/A floor"}
|
|
561
|
+
else:
|
|
562
|
+
fl = floor_items[floor_side]
|
|
563
|
+
keys = [k for k in good if k in fl and fl[k].get("ok") and not fl[k].get("frame_count_differs")]
|
|
564
|
+
for m in metrics:
|
|
565
|
+
if m not in computed:
|
|
566
|
+
checks[m] = {"verdict": "NOT RUN", "reason": f"not computed ({lpips_error})"}
|
|
567
|
+
continue
|
|
568
|
+
checks[m] = lossless_check(good, fl, keys, groups, m, margins[m], resamples, seed)
|
|
569
|
+
exact_floor = keys and all(_exact(fl[k]) for k in keys)
|
|
570
|
+
exact_engine = keys and all(_exact(good[k]) for k in keys)
|
|
571
|
+
if fidelity == "gross" and not (exact_floor and not exact_engine):
|
|
572
|
+
for m, c in checks.items():
|
|
573
|
+
if m in METRICS and c["verdict"] == "UNPROVEN" and None not in (c.get("ci95") or [None]):
|
|
574
|
+
c["verdict"] = "PASS"
|
|
575
|
+
c["reason"] = f"gross gate, not entirely beyond the margin: {c['reason']}"
|
|
576
|
+
if exact_floor and not exact_engine: # whichever floor: exact cannot judge a drifting kernel
|
|
577
|
+
for m, c in checks.items():
|
|
578
|
+
if m in METRICS and c["verdict"] in ("PASS", "FAIL"):
|
|
579
|
+
c["verdict_before_exact_floor_rule"] = c["verdict"]
|
|
580
|
+
c["verdict"] = "UNPROVEN"
|
|
581
|
+
if floor_side == "floor:alt":
|
|
582
|
+
caps.append("the --reference-alt run is bit-identical to the reference (was the attention "
|
|
583
|
+
"backend switch applied?), so the A/A floor is exact and cannot separate an "
|
|
584
|
+
"exact-but-different kernel from a lossy one")
|
|
585
|
+
else:
|
|
586
|
+
caps.append("the A/A floor is exact (the reference repeats itself bit for bit), so it "
|
|
587
|
+
"cannot separate an exact-but-different kernel from a lossy one: pass "
|
|
588
|
+
"--reference-alt (the reference with another exact attention backend)")
|
|
589
|
+
block["floor"] = floor_side.split(":", 1)[1]
|
|
590
|
+
block["n_clips_judged"] = len(keys)
|
|
591
|
+
if len(keys) < len(good):
|
|
592
|
+
caps.append(f"{len(good) - len(keys)} scored clip(s) have no usable floor clip (missing, made "
|
|
593
|
+
"from another request, or a different frame count) and were not judged")
|
|
594
|
+
else:
|
|
595
|
+
for k, (m, sidek) in BUDGET.items():
|
|
596
|
+
if k in (budget or {}):
|
|
597
|
+
if m not in computed:
|
|
598
|
+
checks[f"budget.{k}"] = {"verdict": "NOT RUN", "reason": f"{m} not computed ({lpips_error})"}
|
|
599
|
+
else:
|
|
600
|
+
checks[f"budget.{k}"] = budget_check(good, groups, m, sidek, budget[k], resamples, seed)
|
|
601
|
+
if spot is None:
|
|
602
|
+
checks["spot_check"] = {"verdict": "UNPROVEN", "reason": "lossy: no human spot-check record "
|
|
603
|
+
"(--spot-check)"}
|
|
604
|
+
else:
|
|
605
|
+
checks["spot_check"] = {"verdict": "PASS" if spot["verdict"] == "acceptable" else "FAIL",
|
|
606
|
+
"reason": f"{spot['reviewer']} ({spot['date']}): {spot['verdict']}, "
|
|
607
|
+
f"{len(spot['clips'])} clip(s) watched"}
|
|
608
|
+
if floor_side is not None: # for the reader: how far the floor already is, no verdict
|
|
609
|
+
fl = floor_items[floor_side]
|
|
610
|
+
block["vs_floor"] = {m: summary_of({k: fl[k] for k in good if k in fl and fl[k].get("ok")}, groups,
|
|
611
|
+
m, resamples, seed) for m in computed}
|
|
612
|
+
verdict = stats.combine_claims("UNPROVEN" if c["verdict"] == "NOT RUN" else c["verdict"]
|
|
613
|
+
for c in checks.values())
|
|
614
|
+
if verdict in ("PASS", "NOT RUN") and caps:
|
|
615
|
+
verdict = "UNPROVEN"
|
|
616
|
+
block.update({"verdict": verdict, "checks": checks, "caps": caps,
|
|
617
|
+
"reason": "; ".join([f"{k}: {c['reason']}" for k, c in checks.items()] + caps)})
|
|
618
|
+
out_engines[label] = block
|
|
619
|
+
overall = stats.combine_claims(b["verdict"] for b in out_engines.values())
|
|
620
|
+
return {"verdict": overall, "fidelity": fidelity, "engines": out_engines, "floors": floor_info,
|
|
621
|
+
"floor_used": floor_side.split(":", 1)[1] if floor_side else None, "reference_clips": len(ref),
|
|
622
|
+
"reference_requests": len(set(groups.values())),
|
|
623
|
+
"margins": {m: margins[m] for m in metrics}, "budget": budget, "spot_check": spot, "metrics": metrics,
|
|
624
|
+
"computed": computed, "lpips_error": lpips_error}
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def _uniform(reqs: dict[str, dict], key: str):
|
|
628
|
+
vals = {json.dumps(r.get(key), sort_keys=True) for r in reqs.values()}
|
|
629
|
+
return next(iter(reqs.values())).get(key) if reqs and len(vals) == 1 else None
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def _save(call, store, staging, result, artifacts, doc, model, ref_req, run_dir) -> dict:
|
|
633
|
+
mets = []
|
|
634
|
+
for label, b in result["engines"].items(): # n = distinct requests, the bootstrap unit of the CI
|
|
635
|
+
for m, s in b["metrics"].items():
|
|
636
|
+
mets.append(ev.metric(NAMES[m], s["value"], UNITS[m], "mean_over_frames", n=s["n_requests"],
|
|
637
|
+
ci95=s["ci95"], subject=label))
|
|
638
|
+
for m, c in b["checks"].items():
|
|
639
|
+
if c.get("worse_than_floor") is not None:
|
|
640
|
+
mets.append(ev.metric(f"{NAMES[m]}_worse_than_floor", c["worse_than_floor"], UNITS[m],
|
|
641
|
+
"paired_delta", n=c["n_requests"], ci95=c["ci95"], subject=label))
|
|
642
|
+
for name, f in result["floors"].items():
|
|
643
|
+
for m, s in f["metrics"].items():
|
|
644
|
+
mets.append(ev.metric(NAMES[m], s["value"], UNITS[m], "mean_over_frames", n=s["n_requests"],
|
|
645
|
+
ci95=s["ci95"], subject=f"floor:{name}"))
|
|
646
|
+
lim = [f"{label}: {c}" for label, b in result["engines"].items() for c in b["caps"]]
|
|
647
|
+
if result["lpips_error"]:
|
|
648
|
+
lim.append(f"LPIPS not computed: {result['lpips_error'][:200]}")
|
|
649
|
+
if result["fidelity"] == "lossy":
|
|
650
|
+
lim.append("lossy track: report as 'N x at the stated quality cost'; never ranked against lossless results")
|
|
651
|
+
unchecked = sum(b["requests_unchecked"] for b in result["engines"].values())
|
|
652
|
+
if unchecked:
|
|
653
|
+
lim.append(f"{unchecked} clip pair(s) had no bench clip request body on one side: same prompt, seed and "
|
|
654
|
+
"steps were not checked for them")
|
|
655
|
+
workload = {k: _uniform(ref_req, k) for k in ("task", "width", "height", "num_frames", "fps", "steps",
|
|
656
|
+
"guidance_scale")} if ref_req else {}
|
|
657
|
+
labels = list(result["engines"])
|
|
658
|
+
summary = f"quality clip {result['verdict']} ({result['fidelity']}, floor {result['floor_used'] or 'none'}): " + \
|
|
659
|
+
", ".join(f"{lab} {b['verdict']}" + (f" (PSNR {b['metrics']['psnr']['value']:.2f} dB)"
|
|
660
|
+
if b["metrics"].get("psnr", {}).get("value") is not None else "")
|
|
661
|
+
for lab, b in result["engines"].items())
|
|
662
|
+
kind, extra = _kind("clip_quality")
|
|
663
|
+
worker = {k: doc.get(k) for k in ("versions", "device", "ssim_backend", "frame_stride", "psnr_cap_db")}
|
|
664
|
+
data = {**extra, **result, "format": FORMAT, "worker": worker,
|
|
665
|
+
"definitions": {"psnr_db": "10 log10(255^2 / MSE) per frame over RGB24, capped at 100 dB; clip = mean "
|
|
666
|
+
"over frames",
|
|
667
|
+
"ssim": "luma (BT.601), 11x11 Gaussian sigma 1.5, K1 0.01, K2 0.03",
|
|
668
|
+
"lpips": "LPIPS v0.1, AlexNet backbone, RGB in [-1, 1]",
|
|
669
|
+
"worse_than_floor": "engine-vs-reference minus floor-vs-reference, oriented so positive "
|
|
670
|
+
"is worse; paired bootstrap 95% CI over requests",
|
|
671
|
+
"request": "the bootstrap unit: clips whose request bodies match on every REQUEST_KEYS "
|
|
672
|
+
"field (or, without bodies, every round of one clip id) count once; metric n "
|
|
673
|
+
"is the number of requests",
|
|
674
|
+
"per_clip": "each scored clip's mean over frames; id and round from its r<round>/<id> "
|
|
675
|
+
"key (round null for other layouts)"}}
|
|
676
|
+
if len(labels) == 1: # the review page and a re-run read the engine and its clips from here
|
|
677
|
+
data["label"] = labels[0]
|
|
678
|
+
data["per_clip"] = result["engines"][labels[0]]["per_clip"]
|
|
679
|
+
rec = ev.build(call, kind=kind, claim_class="observed", summary=summary,
|
|
680
|
+
identity_=ev.identity(engine={"name": labels[0] if len(labels) == 1 else None},
|
|
681
|
+
model={"id": model}, workload=workload),
|
|
682
|
+
metrics=mets, artifacts=artifacts, missing=[] if any(
|
|
683
|
+
b["n_clips_scored"] for b in result["engines"].values()) else ["no clip pair was scored"],
|
|
684
|
+
limitations=lim, data=data)
|
|
685
|
+
if run_dir: # the store also copies the record to <run-dir>/evidence/
|
|
686
|
+
out = Path(run_dir) / "quality"
|
|
687
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
688
|
+
(out / "clip_score.json").write_text(json.dumps(result, indent=1) + "\n")
|
|
689
|
+
return store.save(rec, staging)
|
|
690
|
+
|