Download code/gc25d/verify_prep.py from teawhite/maliang: direct link, hf CLI and curl.
- Browser
- Download file 27.6 kB
-
https://huggingface.co/teawhite/maliang/resolve/main/code/gc25d/verify_prep.py
- Command line
-
hf download hf://teawhite/maliang/code/gc25d/verify_prep.py
-
curl -L -o verify_prep.py https://huggingface.co/teawhite/maliang/resolve/main/code/gc25d/verify_prep.py
27.6 kB
| #!/usr/bin/env python3 | |
| """prep_v10 产物校验(data_v10.md §8.5)。 | |
| python verify_prep.py --root $M/data/gc25d_v10 [--spot 16] [--device cuda:0] [--workers 32] | |
| 分辨率 = geometry(env GC_RES,默认 832x480):index/prep_args.json 的 "res" 与每行的 "res"(缺失 = 832x480)必须等于 GC_RES, | |
| 下面的 [48,21,30,52] / [81,480,832] 是 832×480 的例子(1280×704:[48,21,44,80] / [81,704,1280])。 | |
| A. index:res 一致、clips.jsonl 字段齐、clip_id 唯一、split = SPEC §5 规则、drop = buried 名单、runs.json 一致、每 run 50 条、 | |
| text_table_gc25d.pt 与源文件逐字节相同且 19 个 key = text.all_prompts() + NULL_PROMPT、没有残留 .tmp 文件。 | |
| B. 全部 clip(多进程):文件齐(cond_ext 当且仅当 has_ext);latent bf16 [48,21,30,52] 有限;actions_block uint8 [21,5], | |
| cell 0 = cell 1,[1:] = 原始 actions_block[s//4 : s//4+20];prompts == text.cell_prompts(actions_block[1:]);cond ids | |
| uint8 [81,480,832] ⊆ parts 词表(无 255);cond_ext ids [28,480,832] / blocks [7,5] = 原始块 / prompts;has_ext 用 | |
| frames.jsonl 独立重判;meta 形状 / dtype(F = 81 或 109)、frames 连续、player_px / boss_px == cond 里数出来的像素数。 | |
| C. GPU 抽查 N 条: | |
| * VAE 解码(fp32)PSNR vs resample_rgb(GT)(典型 23–26 dB,简单内容 30+;< 18 dB ⇒ FAIL,[PSNR_WARN_LO, 40] 外 ⇒ WARN; | |
| PSNR_WARN_LO = 21(832×480 上定的),其它分辨率 20:GT 高频更多,繁杂 clip 低 ≈ 1 dB,704p mini 实测最低 20.26); | |
| * 官方 vae.encode([x], tiled=False) 重编整段 == 存的 latent(逐位);latent cell 0 == encode(帧 0)(到 bf16 舍入); | |
| * 源 ids.png ⊆ {0..6,10,11};collapse_parts(cond ids) == resample_ids(ids.png)(全部帧,含扩展帧);meta 与 tar json 逐位相同; | |
| 从 meta 重跑 label_parts == cond; | |
| * 从 meta 用 capsules.render_capsules 重画角色:v10 无遮挡帧 IoU ≥ 0.98(及 GT ⊆ 重画的覆盖率)。 | |
| 退出码:有 FAIL ⇒ 1。报告写 <root>/index/verify_report.json。 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import collections | |
| import glob | |
| import hashlib | |
| import itertools | |
| import json | |
| import os | |
| import sys | |
| import time | |
| from multiprocessing import Pool | |
| import prep_v10 as P # 先 import:设单线程环境变量 | |
| import cv2 | |
| import numpy as np | |
| import torch | |
| import capsules as CAP | |
| import geometry as GEO | |
| import model as GM | |
| import text as TX | |
| ROW_KEYS = ("clip_id", "subset", "scene", "run", "start", "end", "split", "drop", "drop_reason", "episode", | |
| "camera_variant", "look_name", "centered", "n_events", "dead_frac", "has_ext", "ortho") | |
| PT_KEYS = ("latent", "prompts", "actions_block", "clip_id", "subset", "scene", "run", "start") | |
| def meta_spec(F: int) -> dict: | |
| return { | |
| "world_to_clip": ("f8", (F, 16)), "world_to_view": ("f8", (F, 16)), "projection": ("f8", (F, 16)), | |
| "orthographic": ("b1", ()), "fov_x_deg": ("f4", (F,)), "ortho_width_cm": ("f4", (F,)), | |
| "frame_offset_cm": ("f4", (F, 3)), "capsules": ("f4", (F, 2, 12, 7)), "actor_pos": ("f4", (F, 2, 3)), | |
| "ground_z": ("f4", (F,)), "player_state": ("U", (F,)), "boss_state": ("U", (F,)), | |
| "player_px": ("i4", (F,)), "boss_px": ("i4", (F,)), | |
| } | |
| def _dtype_ok(arr, code): | |
| return arr.dtype.kind == "U" if code == "U" else arr.dtype == np.dtype(code) | |
| def _px(ids): | |
| """[F,H,W] parts ids → (player_px, boss_px) int [F]。""" | |
| c = GM.collapse_parts(ids) | |
| return (c == GEO.PLAYER_ID).sum((1, 2)), (c == GEO.BOSS_ID).sum((1, 2)) | |
| # ---------------------------------------------------------------------------- | |
| # B. 逐 clip 校验(CPU,多进程) | |
| # ---------------------------------------------------------------------------- | |
| _TABLE_KEYS: frozenset = frozenset() | |
| _SRC = "" | |
| def _pool_init(table_keys, src): | |
| global _TABLE_KEYS, _SRC | |
| _TABLE_KEYS, _SRC = frozenset(table_keys), src | |
| cv2.setNumThreads(1) | |
| torch.set_num_threads(1) | |
| def check_clip(args) -> tuple[str, list[str], dict]: | |
| row, root = args | |
| cid, s = row["clip_id"], row["start"] | |
| err = [] | |
| paths = P.clip_paths(root, cid) | |
| for k in ("latent", "cond", "meta") + (("cond_ext",) if row["has_ext"] else ()): | |
| if not os.path.isfile(paths[k]): | |
| err.append(f"缺 {k}") | |
| if not row["has_ext"] and os.path.isfile(paths["cond_ext"]): | |
| err.append("has_ext=False 却有 cond_ext") | |
| if err: | |
| return cid, err, {} | |
| run_dir = os.path.join(_SRC, row["subset"], row["scene"], row["run"]) | |
| AB = np.load(os.path.join(run_dir, "index", "actions_block_0.25s.npy")) | |
| # latent .pt | |
| d = torch.load(paths["latent"], map_location="cpu", weights_only=True) | |
| miss = [k for k in PT_KEYS if k not in d] | |
| if miss: | |
| err.append(f"latent 缺 key {miss}") | |
| else: | |
| lat, ab = d["latent"], d["actions_block"] | |
| if lat.dtype != torch.bfloat16 or tuple(lat.shape) != P.LAT_SHAPE: | |
| err.append(f"latent {lat.dtype} {tuple(lat.shape)}") | |
| elif not torch.isfinite(lat.float()).all(): | |
| err.append("latent 有非有限值") | |
| ab = ab.numpy() if torch.is_tensor(ab) else np.asarray(ab) | |
| if ab.dtype != np.uint8 or ab.shape != (21, 5) or not np.isin(ab, (0, 1)).all(): | |
| err.append(f"actions_block {ab.dtype} {ab.shape}") | |
| else: | |
| if not (ab[0] == ab[1]).all(): | |
| err.append("actions_block cell 0 ≠ cell 1") | |
| if not (ab[1:] == P.clip_blocks(AB, s)).all(): | |
| err.append("actions_block[1:] ≠ 原始 actions_block[s//4:s//4+20]") | |
| if list(d["prompts"]) != TX.cell_prompts(ab[1:]): | |
| err.append("prompts ≠ text.cell_prompts(actions_block[1:])") | |
| if not set(d["prompts"]) <= _TABLE_KEYS: | |
| err.append(f"prompt 不在 text table:{set(d['prompts']) - _TABLE_KEYS}") | |
| for k in ("clip_id", "subset", "scene", "run", "start"): | |
| if d[k] != row[k]: | |
| err.append(f"latent[{k}]={d[k]!r} ≠ row {row[k]!r}") | |
| # has_ext 独立重判(index/frames.jsonl,不是 prep 用的 tar json) | |
| with open(os.path.join(run_dir, "index", "frames.jsonl")) as fh: | |
| fr = [json.loads(line) for line in itertools.islice(fh, s, s + P.F_SPAN)] | |
| he = P.ext_valid([j["episode"] for j in fr], [j["camera_variant"] for j in fr], s, len(AB)) | |
| if he != row["has_ext"]: | |
| err.append(f"has_ext={row['has_ext']},frames.jsonl 判为 {he}") | |
| # cond / cond_ext | |
| with np.load(paths["cond"], allow_pickle=False) as z: | |
| ids = z["ids"] | |
| if ids.dtype != np.uint8 or ids.shape != (P.F_CLIP, P.DST_H, P.DST_W): | |
| err.append(f"cond ids {ids.dtype} {ids.shape}") | |
| return cid, err, {} | |
| bad = np.nonzero(np.bincount(ids.ravel(), minlength=256) * ~P._VALID_PARTS)[0] | |
| if len(bad): | |
| err.append(f"cond ids 有词表外值 {bad.tolist()}") | |
| if row["has_ext"]: | |
| with np.load(paths["cond_ext"], allow_pickle=False) as z: | |
| eids, eb, ep = z["ids"], z["blocks"], z["prompts"] | |
| if eids.dtype != np.uint8 or eids.shape != (P.F_EXT, P.DST_H, P.DST_W): | |
| err.append(f"cond_ext ids {eids.dtype} {eids.shape}") | |
| else: | |
| bad = np.nonzero(np.bincount(eids.ravel(), minlength=256) * ~P._VALID_PARTS)[0] | |
| if len(bad): | |
| err.append(f"cond_ext ids 有词表外值 {bad.tolist()}") | |
| ids = np.concatenate([ids, eids]) | |
| if eb.dtype != np.uint8 or eb.shape != (P.N_EXT_CELLS, 5) or not (eb == P.ext_blocks(AB, s)).all(): | |
| err.append("cond_ext blocks ≠ 原始 actions_block[s//4+20:s//4+27]") | |
| elif ep.shape != (P.N_EXT_CELLS,) or ep.tolist() != [TX.block_to_prompt(*r.tolist()) for r in eb]: | |
| err.append("cond_ext prompts ≠ block_to_prompt(blocks)") | |
| # meta | |
| F = P.F_SPAN if row["has_ext"] else P.F_CLIP | |
| with np.load(paths["meta"], allow_pickle=False) as z: | |
| m = {k: z[k] for k in z.files} | |
| for k, (code, shape) in meta_spec(F).items(): | |
| if k not in m: | |
| err.append(f"meta 缺 {k}") | |
| elif not _dtype_ok(m[k], code) or m[k].shape != shape: | |
| err.append(f"meta {k}: {m[k].dtype} {m[k].shape},应为 {code} {shape}") | |
| if err: | |
| return cid, err, {} | |
| if bool(m["orthographic"]) != row["ortho"]: | |
| err.append("meta orthographic ≠ row ortho") | |
| if "frames" in m and not (m["frames"] == s + np.arange(F)).all(): | |
| err.append("meta frames 不连续") | |
| for k in ("world_to_clip", "world_to_view", "projection", "fov_x_deg", "ortho_width_cm", "frame_offset_cm"): | |
| if not np.isfinite(m[k]).all(): | |
| err.append(f"meta {k} 有非有限值") | |
| pp, bp = _px(ids) | |
| if not (pp == m["player_px"]).all() or not (bp == m["boss_px"]).all(): | |
| err.append("meta player_px / boss_px ≠ cond 里的像素数") | |
| has_player_caps = ~np.isnan(m["capsules"][:, 0, 0, 0]) | |
| if not np.isfinite(m["ground_z"][has_player_caps]).all(): | |
| err.append("ground_z 在有玩家胶囊的帧上非有限") | |
| info = {"subset": row["subset"], "player_px_mean": float(pp[:P.F_CLIP].mean()), "boss_px_mean": float(bp[:P.F_CLIP].mean()), | |
| "bytes": {k: os.path.getsize(paths[k]) for k in paths if os.path.isfile(paths[k])}} | |
| return cid, err, info | |
| # ---------------------------------------------------------------------------- | |
| # C. GPU 抽查 | |
| # ---------------------------------------------------------------------------- | |
| def psnr(a: np.ndarray, b: np.ndarray) -> float: | |
| mse = ((a.astype(np.float64) - b.astype(np.float64)) ** 2).mean() | |
| return float(10 * np.log10(255.0 ** 2 / max(mse, 1e-12))) | |
| # 832×480 上定的阈值按分辨率换算(832×480 时与原值相同) | |
| PSNR_WARN_LO = 21.0 if GEO.RES_TAG == GEO.LEGACY_RES else 20.0 | |
| VIS_MIN_PX = 50 * GEO.AREA_VS_480 # 「可见帧」的 GT 像素下限(像素数 ∝ 面积) | |
| def render_stats(cond_ids, meta, frames) -> list[dict]: | |
| """从 meta 重画胶囊(不看 ids)→ 每帧每角色的 IoU / 覆盖率 / 遮挡比例 / 部位一致率。""" | |
| out = [] | |
| for i in frames: | |
| caps = CAP.capsules_from_array(meta["capsules"][i]) | |
| with np.errstate(invalid="ignore"): # cap_hit 里 inf·0(没命中的射线),无害 | |
| rend = CAP.render_capsules(np.zeros((P.DST_H, P.DST_W), np.uint8), meta["world_to_clip"][i], meta["world_to_view"][i], caps) | |
| g, r = GM.collapse_parts(cond_ids[i]), GM.collapse_parts(rend) | |
| for cls in (GEO.PLAYER_ID, GEO.BOSS_ID): | |
| G, Rm = g == cls, r == cls | |
| inter = G & Rm | |
| out.append({"frame": int(i), "cls": cls, "g": int(G.sum()), "r": int(Rm.sum()), "i": int(inter.sum()), | |
| "u": int((G | Rm).sum()), "occl": int((Rm & (cond_ids[i] < GEO.PLAYER_ID)).sum()), | |
| "part_eq": int((rend[inter] == cond_ids[i][inter]).sum())}) | |
| return out | |
| def spot_check(row, root, src, pipe, dev, log) -> tuple[list[str], dict]: | |
| cid, s = row["clip_id"], row["start"] | |
| err = [] | |
| paths = P.clip_paths(root, cid) | |
| F = P.F_SPAN if row["has_ext"] else P.F_CLIP | |
| R = P.RunTars(os.path.join(src, row["subset"], row["scene"], row["run"])) | |
| Js = [R.json(s + i) for i in range(F)] | |
| gt = np.stack([GEO.resample_rgb(cv2.cvtColor(cv2.imdecode(np.frombuffer(R.get(s + i, "jpg"), np.uint8), cv2.IMREAD_COLOR), | |
| cv2.COLOR_BGR2RGB)) for i in range(P.F_CLIP)]) | |
| raws = [cv2.imdecode(np.frombuffer(R.get(s + i, "ids.png"), np.uint8), cv2.IMREAD_UNCHANGED) for i in range(F)] | |
| R.close() | |
| for i, raw in enumerate(raws): # 源 ids ⊆ {0..6,10,11}(全分辨率) | |
| try: | |
| P.check_raw_ids(raw, f"帧 {s + i}") | |
| except AssertionError as e: | |
| err.append(str(e)) | |
| break | |
| ids_src = np.stack([GEO.resample_ids(raw) for raw in raws]) | |
| del raws | |
| with np.load(paths["cond"]) as z: | |
| cond = z["ids"] | |
| if row["has_ext"]: | |
| with np.load(paths["cond_ext"]) as z: | |
| cond = np.concatenate([cond, z["ids"]]) | |
| with np.load(paths["meta"]) as z: | |
| meta = {k: z[k] for k in z.files} | |
| info = {"clip_id": cid, "subset": row["subset"], "drop": row["drop"], "F": F} | |
| # label_parts 一致性 | |
| n_bad = int((GM.collapse_parts(cond) != ids_src).sum()) | |
| info["collapse_mismatch_px"] = n_bad | |
| if n_bad: | |
| err.append(f"collapse_parts(cond) ≠ resample_ids(ids.png):{n_bad} px") | |
| for k in ("world_to_clip", "world_to_view", "projection"): | |
| if not (meta[k] == np.array([J[k] for J in Js], np.float64)).all(): | |
| err.append(f"meta {k} ≠ tar json") | |
| caps_j = np.stack([CAP.capsules_to_array(CAP.capsules_from_json(J)) for J in Js]) | |
| if not np.array_equal(meta["capsules"], caps_j, equal_nan=True): | |
| err.append("meta capsules ≠ capsules_to_array(tar json)") | |
| sub = list(range(0, F, 4)) | |
| eq_json = eq_meta = tot = 0 | |
| for i in sub: | |
| J = Js[i] | |
| a = CAP.label_parts(ids_src[i], J["world_to_clip"], J["world_to_view"], CAP.capsules_from_json(J)) | |
| b = CAP.label_parts(ids_src[i], meta["world_to_clip"][i], meta["world_to_view"][i], CAP.capsules_from_array(meta["capsules"][i])) | |
| ch = ids_src[i] >= GEO.PLAYER_ID | |
| eq_json += int((a == cond[i]).all()) | |
| eq_meta += int((b[ch] == cond[i][ch]).sum()) | |
| tot += int(ch.sum()) | |
| info["relabel_json_exact_frames"] = f"{eq_json}/{len(sub)}" | |
| info["relabel_meta_char_px_agree"] = eq_meta / max(tot, 1) | |
| if eq_json != len(sub): | |
| err.append(f"label_parts(tar json) ≠ cond 在 {len(sub) - eq_json}/{len(sub)} 帧") | |
| if info["relabel_meta_char_px_agree"] < 0.999: | |
| err.append(f"label_parts(meta) 与 cond 角色像素一致率 {info['relabel_meta_char_px_agree']:.4f} < 0.999") | |
| # VAE | |
| d = torch.load(paths["latent"], map_location="cpu", weights_only=True) | |
| lat = d["latent"] | |
| with torch.no_grad(): | |
| rec = pipe.vae.decode([lat.float()], device=dev, tiled=False)[0] # [3,81,H,W] ∈ [-1,1] | |
| rec = rec.float().add_(1.0).mul_(127.5).round_().clamp_(0, 255).to(torch.uint8).permute(1, 2, 3, 0).cpu().numpy() | |
| info["psnr_db"] = psnr(rec, gt) | |
| info["psnr_frame0_db"] = psnr(rec[:1], gt[:1]) | |
| x = torch.from_numpy(gt).permute(3, 0, 1, 2).float().div_(127.5).sub_(1.0) # infer.encode_video 原样(CPU) | |
| z = pipe.vae.encode([x], device=dev, tiled=False)[0].to(torch.bfloat16).cpu() | |
| z0 = pipe.vae.encode([x[:, :1]], device=dev, tiled=False)[0].float().cpu() # [48,1,H/16,W/16] fp32 | |
| info["reencode_exact_frac"] = float((z == lat).float().mean()) | |
| info["reencode_max_abs"] = float((z.float() - lat.float()).abs().max()) | |
| c0 = lat[:, :1].float() | |
| info["cell0_bf16_exact_frac"] = float((z0.to(torch.bfloat16).float() == c0).float().mean()) | |
| info["cell0_rel_l2"] = float((z0 - c0).norm() / z0.norm()) | |
| if info["psnr_db"] < 18.0: # 错位 / 串 clip 才会这么低;内容简单的 clip 可到 35 dB 以上 | |
| err.append(f"VAE 重建 PSNR {info['psnr_db']:.2f} dB < 18") | |
| elif not PSNR_WARN_LO <= info["psnr_db"] <= 40.0: | |
| info["warn"] = f"VAE 重建 PSNR {info['psnr_db']:.2f} dB 不在 [{PSNR_WARN_LO:g}, 40]" | |
| if info["reencode_exact_frac"] < 0.999: | |
| err.append(f"官方 encode 重编 latent 一致率 {info['reencode_exact_frac']:.5f}") | |
| if info["cell0_bf16_exact_frac"] < 0.999 or info["cell0_rel_l2"] > 4e-3: | |
| err.append(f"latent cell 0 ≠ encode(帧 0):bf16 一致率 {info['cell0_bf16_exact_frac']:.5f},rel L2 {info['cell0_rel_l2']:.2e}") | |
| # 胶囊重画(只用 meta) | |
| info["render"] = render_stats(cond, meta, list(range(0, F, 4))) | |
| log(f" {cid} F={F}: PSNR {info['psnr_db']:.2f} dB (f0 {info['psnr_frame0_db']:.2f}); re-encode exact {info['reencode_exact_frac']:.5f} " | |
| f"(max {info['reencode_max_abs']:.2e}); cell0 bf16 exact {info['cell0_bf16_exact_frac']:.5f} rel {info['cell0_rel_l2']:.1e}; " | |
| f"collapse mismatch {n_bad}; relabel json {info['relabel_json_exact_frames']} meta {info['relabel_meta_char_px_agree']:.5f}" | |
| + (f" ERR {err}" if err else "")) | |
| return err, info | |
| def summarize_render(spots) -> dict: | |
| """v10 未 drop 的抽查帧:pooled IoU / 覆盖率;无遮挡帧(occl < 1 %,GT ≥ 50 px · AREA_VS_480)的逐帧 IoU。""" | |
| out = {} | |
| for subset in P.SUBSETS: | |
| recs = [r for sp in spots if sp["subset"] == subset and not sp["drop"] for r in sp.get("render", [])] | |
| if not recs: | |
| continue | |
| o = {} | |
| for cls, name in ((GEO.PLAYER_ID, "player"), (GEO.BOSS_ID, "boss")): | |
| rs = [r for r in recs if r["cls"] == cls] | |
| vis = [r for r in rs if r["g"] >= VIS_MIN_PX] | |
| un = [r for r in vis if r["occl"] < 0.01 * max(r["r"], 1)] | |
| ious = [r["i"] / r["u"] for r in un] | |
| o[name] = { | |
| "frames": len(rs), "visible_frames": len(vis), "unoccluded_frames": len(un), | |
| "pooled_iou": sum(r["i"] for r in rs) / max(1, sum(r["u"] for r in rs)), | |
| "coverage_gt_in_render": sum(r["i"] for r in rs) / max(1, sum(r["g"] for r in rs)), | |
| "occluded_frac_of_render": sum(r["occl"] for r in rs) / max(1, sum(r["r"] for r in rs)), | |
| "unoccluded_iou_median": float(np.median(ious)) if ious else None, | |
| "unoccluded_iou_p10": float(np.percentile(ious, 10)) if ious else None, | |
| "unoccluded_pooled_iou": sum(r["i"] for r in un) / max(1, sum(r["u"] for r in un)) if un else None, | |
| "part_agree_in_intersection": sum(r["part_eq"] for r in rs) / max(1, sum(r["i"] for r in rs)), | |
| } | |
| out[subset] = o | |
| return out | |
| # ---------------------------------------------------------------------------- | |
| def main(argv=None): | |
| ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) | |
| ap.add_argument("--root", required=True) | |
| ap.add_argument("--src", default=P.SRC_DEFAULT) | |
| ap.add_argument("--spot", type=int, default=8, help="GPU 抽查的 clip 数(0 = 不抽查)") | |
| ap.add_argument("--device", default="cuda:0") | |
| ap.add_argument("--workers", type=int, default=32) | |
| ap.add_argument("--buried", default=P.BURIED_LIST) | |
| ap.add_argument("--text_table", default=P.TEXT_TABLE_SRC) | |
| ap.add_argument("--clips_per_run", type=int, default=50, help="每 run 应有的 clip 数(0 = 不查)") | |
| args = ap.parse_args(argv) | |
| root = os.path.abspath(args.root) | |
| T0 = time.time() | |
| fails, warns = [], [] | |
| log = lambda *x: print(*x, flush=True) # noqa: E731 | |
| GEO.check_root_res(root, "--root") # prep_args.json 的 res ≠ GC_RES ⇒ 直接报错(形状全对不上,没法往下验) | |
| # ---------------- A. index | |
| rows = [json.loads(line) for line in open(os.path.join(root, "index", "clips.jsonl")) if line.strip()] | |
| ids = [r["clip_id"] for r in rows] | |
| if len(set(ids)) != len(ids): | |
| fails.append("clips.jsonl 有重复 clip_id") | |
| buried = {x.strip() for x in open(args.buried) if x.strip()} | |
| for r in rows: | |
| miss = [k for k in ROW_KEYS if k not in r] | |
| if miss: | |
| fails.append(f"{r.get('clip_id')}: 行缺字段 {miss}") | |
| continue | |
| if r["clip_id"] != P.clip_id_of(r["scene"], r["run"], r["start"]) or r["end"] != r["start"] + P.F_CLIP: | |
| fails.append(f"{r['clip_id']}: clip_id / end 不对") | |
| if r["split"] != P.split_of(r["run"]): | |
| fails.append(f"{r['clip_id']}: split {r['split']} ≠ 规则 {P.split_of(r['run'])}") | |
| if r["drop"] != (r["clip_id"] in buried) or (r["drop_reason"] == P.DROP_REASON) != r["drop"]: | |
| fails.append(f"{r['clip_id']}: drop / drop_reason 与 buried 名单不符") | |
| bad_res = collections.Counter(r.get("res", GEO.LEGACY_RES) for r in rows if r.get("res", GEO.LEGACY_RES) != GEO.RES_TAG) | |
| if bad_res: | |
| fails.append(f"index 行的 res {dict(bad_res)} ≠ {GEO.RES_ENV}={GEO.RES_TAG}") | |
| per_run = collections.Counter(r["run"] for r in rows) | |
| if args.clips_per_run: | |
| badrun = {k: v for k, v in per_run.items() if v != args.clips_per_run} | |
| if badrun: | |
| fails.append(f"这些 run 的 clip 数 ≠ {args.clips_per_run}:{badrun}") | |
| runs = json.load(open(os.path.join(root, "index", "runs.json"))) | |
| if "_rule" not in runs or {k: v for k, v in runs.items() if k != "_rule"} != {r["run"]: r["split"] for r in rows}: | |
| fails.append("runs.json 与 clips.jsonl 不一致") | |
| tt = os.path.join(root, "text_table_gc25d.pt") | |
| table_keys = [] | |
| if not os.path.isfile(tt): | |
| fails.append("缺 text_table_gc25d.pt") | |
| else: | |
| h = [hashlib.sha256(open(p, "rb").read()).hexdigest() for p in (tt, args.text_table)] | |
| if h[0] != h[1]: | |
| fails.append("text_table_gc25d.pt 与源文件不同") | |
| table = torch.load(tt, map_location="cpu", weights_only=True) | |
| table_keys = sorted(table) | |
| if set(table_keys) != set(TX.all_prompts()) | {TX.NULL_PROMPT}: | |
| fails.append("text table 的 key ≠ 18 条 prompt + NULL_PROMPT") | |
| for k, v in table.items(): | |
| if v.dtype != torch.bfloat16 or tuple(v.shape) != (512, 4096): | |
| fails.append(f"text table[{k!r}] {v.dtype} {tuple(v.shape)}") | |
| stray = [p for d in ("latents", "cond", "cond_ext", "meta") for p in glob.glob(os.path.join(root, d, "*.tmp*"))] | |
| if stray: | |
| warns.append(f"{len(stray)} 个残留 .tmp 文件(中断的运行),例如 {stray[:3]}") | |
| on_disk = {os.path.basename(p)[:-3] for p in glob.glob(os.path.join(root, "latents", "*.pt"))} | |
| orphan = on_disk - set(ids) | |
| if orphan: | |
| warns.append(f"{len(orphan)} 个 latent 不在 clips.jsonl 里") | |
| sp = collections.Counter((r["subset"], r["split"], r["drop"], r["has_ext"]) for r in rows) | |
| log(f"A. index: res {GEO.RES_TAG}, {len(rows)} clips, {len(per_run)} runs; " + "; ".join(f"{k}: {v}" for k, v in sorted(sp.items()))) | |
| # has_ext 里有多少在 cond_ext 内换 look(v10_real 帧 s+81 的语义图跳变;stage3 是否过滤见 docs/impl/prep.md) | |
| ext = {s: {"has_ext": sum(r["has_ext"] for r in rows if r["subset"] == s), | |
| "ext_look_change": sum(bool(r.get("ext_look_change")) for r in rows if r["subset"] == s), | |
| "dmd_pool_train": sum(r["has_ext"] and not r["drop"] and r["split"] == "train" for r in rows if r["subset"] == s), | |
| "dmd_pool_train_same_look": sum(r["has_ext"] and not r["drop"] and r["split"] == "train" and not r.get("ext_look_change") | |
| for r in rows if r["subset"] == s)} | |
| for s in P.SUBSETS} | |
| log(" ext: " + "; ".join(f"{s} " + " ".join(f"{k}={v}" for k, v in o.items()) for s, o in ext.items())) | |
| # ---------------- B. 逐 clip | |
| t = time.time() | |
| nbad = 0 | |
| infos = [] | |
| with Pool(min(args.workers, max(1, len(rows))), initializer=_pool_init, initargs=(table_keys, args.src)) as pool: | |
| for cid, err, info in pool.imap_unordered(check_clip, [(r, root) for r in rows], chunksize=1): | |
| if err: | |
| nbad += 1 | |
| if nbad <= 50: | |
| fails.append(f"{cid}: {'; '.join(err)}") | |
| if info: | |
| infos.append(info) | |
| if nbad > 50: | |
| fails.append(f"…… 共 {nbad} 个 clip 有错") | |
| pxs = {s: (np.mean([i["player_px_mean"] for i in infos if i["subset"] == s]), | |
| np.mean([i["boss_px_mean"] for i in infos if i["subset"] == s])) for s in P.SUBSETS if any(i["subset"] == s for i in infos)} | |
| size = collections.defaultdict(list) | |
| for i in infos: | |
| for k, v in i["bytes"].items(): | |
| size[k].append(v) | |
| sizes = {k: {"mean_MB": round(float(np.mean(v)) / 1e6, 3), "total_GB": round(sum(v) / 1e9, 3)} for k, v in size.items()} | |
| log(f"B. per-clip: {len(rows) - nbad}/{len(rows)} ok ({time.time() - t:.0f}s); player/boss px per frame ({GEO.RES_TAG}) " | |
| + ", ".join(f"{s} {a:.0f}/{b:.0f}" for s, (a, b) in pxs.items()) + f"; sizes {sizes}") | |
| # ---------------- C. GPU 抽查 | |
| spots = [] | |
| if args.spot and rows: | |
| order = {s: i for i, s in enumerate(P.SUBSETS)} | |
| cand = sorted(rows, key=lambda r: (order[r["subset"]], r["scene"], r["run"], r["start"])) | |
| pick = sorted(set(np.linspace(0, len(cand) - 1, min(args.spot, len(cand))).round().astype(int).tolist())) | |
| log(f"C. spot check {len(pick)} clips on {args.device}") | |
| pipe = P.load_vae(args.device) | |
| for k in pick: | |
| try: | |
| err, info = spot_check(cand[k], root, args.src, pipe, args.device, log) | |
| except Exception as e: # noqa: BLE001 | |
| err, info = [f"抽查异常 {type(e).__name__}: {e}"], {"clip_id": cand[k]["clip_id"], "subset": cand[k]["subset"], "drop": True} | |
| spots.append(info) | |
| if info.get("warn"): | |
| warns.append(f"spot {cand[k]['clip_id']}: {info['warn']}") | |
| fails += [f"spot {cand[k]['clip_id']}: {e}" for e in err] | |
| rs = summarize_render(spots) | |
| for subset, o in rs.items(): | |
| for name, v in o.items(): | |
| log(f" render {subset:<11} {name:<6} " + " ".join(f"{k}={v[k]:.4f}" if isinstance(v[k], float) else f"{k}={v[k]}" for k in v)) | |
| v10 = rs.get("v10_real") | |
| if v10 is None: | |
| warns.append("抽查里没有 v10_real 的未 drop clip,跳过胶囊重画 IoU 判据") | |
| else: | |
| for name, v in v10.items(): | |
| if v["visible_frames"] and v["unoccluded_frames"] < 0.5 * v["visible_frames"]: | |
| fails.append(f"render v10 {name}: 无遮挡帧只占 {v['unoccluded_frames']}/{v['visible_frames']}(meta 几何可能错位)") | |
| if v["coverage_gt_in_render"] < 0.995: | |
| fails.append(f"render v10 {name}: GT ⊆ 重画覆盖率 {v['coverage_gt_in_render']:.4f} < 0.995") | |
| if v["unoccluded_iou_median"] is not None and (v["unoccluded_iou_median"] < 0.98 or v["unoccluded_pooled_iou"] < 0.98): | |
| fails.append(f"render v10 {name}: 无遮挡帧 IoU median {v['unoccluded_iou_median']:.4f} / pooled {v['unoccluded_pooled_iou']:.4f} < 0.98") | |
| ps = [s["psnr_db"] for s in spots if "psnr_db" in s] | |
| if ps: | |
| log(f" VAE recon PSNR: mean {np.mean(ps):.2f} dB, min {np.min(ps):.2f}, max {np.max(ps):.2f}") | |
| else: | |
| rs = {} | |
| report = {"root": root, "res": GEO.RES_TAG, "time": time.strftime("%Y-%m-%d %H:%M:%S"), "seconds": round(time.time() - T0, 1), | |
| "n_clips": len(rows), "n_runs": len(per_run), "fails": fails, "warns": warns, "ext": ext, | |
| "px_per_frame": {s: {"player": a, "boss": b} for s, (a, b) in pxs.items()}, "sizes": sizes, | |
| "spots": [{k: v for k, v in s.items() if k != "render"} for s in spots], "render": rs} | |
| with open(os.path.join(root, "index", "verify_report.json"), "w") as fh: | |
| json.dump(report, fh, indent=1, ensure_ascii=False, default=float) | |
| for w in warns: | |
| log("WARN", w) | |
| for f in fails[:80]: | |
| log("FAIL", f) | |
| log(f"verify_prep: {'FAIL' if fails else 'OK'} — {len(rows)} clips, {len(fails)} fails, {len(warns)} warns, {time.time() - T0:.0f}s") | |
| return 1 if fails else 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |