Two new entrypoints showcasing the post-refactor eval surface: - examples/inference/eval/basic_ltx2_eval.py — generate one LTX2 video using the same parameters as basic_ltx2.py (same prompt, model, 1088×1920×121), then score it with the prompt-aware VBench subset. Builds Evaluator directly, calls evaluate(**kwargs) per the single-sample API; no runner, no argparse. - scripts/eval/score_folder.py — bulk-score every video in a folder with prompt-free VBench metrics. Optional --prompts-json maps filenames to prompts to enable prompt-aware metrics. Uses Evaluator.evaluate(samples=[...]) for multi-GPU fan-out. Tested on fs-mbz-gpu-085: - score_folder.py: 3 duplicate mp4s × 4 metrics → 3 identical score sets, summary aggregated, scores.json written. Clean run. - basic_ltx2_eval.py: generation succeeded; scoring path verified for 7 of 8 metrics on the LTX2 output (aesthetic_quality, subject_consistency, background_consistency, imaging_quality, temporal_flickering, dynamic_degree, overall_consistency). vbench.motion_smoothness OOMs at 1088×1920 on a shared GPU because VBench's AMT memory autoscale reads total_memory rather than mem_get_info() free memory, so it underestimates the required resolution scale-down when another process holds 45 GB. Documented in the script docstring; not refactor-related (same behavior at HEAD pre-refactor). Drop motion_smoothness from METRICS or run on a dedicated GPU to score it. Added a torch.cuda.empty_cache() between generator.shutdown() and evaluator construction to free residual generation memory.
132 lines
4.6 KiB
Python
132 lines
4.6 KiB
Python
"""Score every video in a folder with VBench metrics (no prompts).
|
|
|
|
Multi-GPU bulk eval over a flat folder of mp4s. The default metric
|
|
set is the prompt-free VBench subset — anything that needs a source
|
|
prompt or aux info is excluded by default.
|
|
|
|
python scripts/eval/score_folder.py \\
|
|
--videos-dir runs/my_outputs \\
|
|
--num-gpus 4 \\
|
|
--fps 24 \\
|
|
--results-json runs/my_outputs/scores.json
|
|
|
|
Pass ``--metrics`` to override the default set; pass a custom JSON
|
|
mapping ``filename → prompt`` via ``--prompts-json`` to enable
|
|
prompt-aware metrics.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
logging.basicConfig(level=logging.INFO,
|
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
log = logging.getLogger("score-folder")
|
|
|
|
# Metrics that need only the generated frames (and optionally fps).
|
|
DEFAULT_METRICS = [
|
|
"vbench.aesthetic_quality",
|
|
"vbench.subject_consistency",
|
|
"vbench.background_consistency",
|
|
"vbench.imaging_quality",
|
|
"vbench.temporal_flickering",
|
|
"vbench.motion_smoothness",
|
|
"vbench.dynamic_degree",
|
|
]
|
|
|
|
VIDEO_EXTS = {".mp4", ".mov", ".avi", ".webm", ".mkv"}
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
ap = argparse.ArgumentParser(
|
|
__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--videos-dir", required=True, type=Path)
|
|
ap.add_argument("--metrics", default=",".join(DEFAULT_METRICS),
|
|
help="Comma-separated metric names, or 'all', or a "
|
|
"group prefix like 'vbench'.")
|
|
ap.add_argument("--num-gpus", type=int, default=1)
|
|
ap.add_argument("--fps", type=float, default=24.0)
|
|
ap.add_argument("--prompts-json", type=Path, default=None,
|
|
help="Optional path to a JSON {filename: prompt} mapping. "
|
|
"If given, prompts attach to each sample as "
|
|
"text_prompt (enables prompt-aware metrics).")
|
|
ap.add_argument("--results-json", type=Path, default=None)
|
|
return ap.parse_args()
|
|
|
|
|
|
def parse_metrics(spec: str) -> str | list[str]:
|
|
spec = spec.strip()
|
|
if spec == "all":
|
|
return "all"
|
|
if "," in spec:
|
|
return [m.strip() for m in spec.split(",") if m.strip()]
|
|
return spec
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
from fastvideo.eval import Evaluator
|
|
from fastvideo.eval.io import build_eval_kwargs
|
|
from fastvideo.eval.types import EvalResult
|
|
|
|
videos_dir: Path = args.videos_dir
|
|
if not videos_dir.is_dir():
|
|
raise SystemExit(f"--videos-dir {videos_dir} is not a directory")
|
|
|
|
paths = sorted(p for p in videos_dir.iterdir()
|
|
if p.suffix.lower() in VIDEO_EXTS)
|
|
if not paths:
|
|
raise SystemExit(f"no videos found in {videos_dir} "
|
|
f"(looked for {sorted(VIDEO_EXTS)})")
|
|
log.info("[score] %d videos found in %s", len(paths), videos_dir)
|
|
|
|
prompts: dict[str, str] = {}
|
|
if args.prompts_json:
|
|
prompts = json.loads(args.prompts_json.read_text())
|
|
log.info("[score] loaded %d prompts from %s",
|
|
len(prompts), args.prompts_json)
|
|
|
|
samples: list[dict] = []
|
|
metas: list[dict] = []
|
|
for vp in paths:
|
|
row: dict = {}
|
|
if vp.name in prompts:
|
|
row["prompt"] = prompts[vp.name]
|
|
samples.append(build_eval_kwargs(row, vp, fps=args.fps))
|
|
metas.append({
|
|
"video": str(vp),
|
|
"prompt": row.get("prompt"),
|
|
})
|
|
|
|
log.info("[score] building evaluator: %s (num_gpus=%d)",
|
|
args.metrics, args.num_gpus)
|
|
evaluator = Evaluator(metrics=parse_metrics(args.metrics),
|
|
num_gpus=args.num_gpus)
|
|
log.info("[score] dispatching %d samples...", len(samples))
|
|
results = evaluator.evaluate(samples)
|
|
|
|
by_metric: dict[str, list[float]] = {}
|
|
per_video: list[dict] = []
|
|
for meta, res in zip(metas, results, strict=True):
|
|
scores: dict[str, float] = {}
|
|
for name, mr in res.items():
|
|
if mr is not None and mr.score is not None:
|
|
scores[name] = float(mr.score)
|
|
by_metric.setdefault(name, []).append(scores[name])
|
|
per_video.append({**meta, "scores": scores})
|
|
log.info("[score] %s -> %s", meta["video"],
|
|
{k: round(v, 4) for k, v in scores.items()})
|
|
log.info("[score] done. videos_scored=%d", len(per_video))
|
|
|
|
result = EvalResult.from_raw(by_metric, per_video)
|
|
result.print()
|
|
if args.results_json:
|
|
result.save(args.results_json)
|
|
log.info("[score] wrote %s", args.results_json)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|