Spaces:
Running on Zero
Running on Zero
File size: 5,826 Bytes
1811aab | 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 | """Stream held-out clips chunk by chunk and compare with the CARLA ground truth.
python -m rtcwm.eval.evaluate --lora LORA --out DIR --clips clip_0244 [...] [--chunks N]
[--steps 35] [--rule euler|x0] [--shift S] (distilled students: --steps 4 --rule x0 --shift 5)
Each clip is generated from nothing (no keyframe) as one stream of --chunks 17-frame chunks (default: all
54, 918 frames / 38 s), every chunk in its window of the 5 chunks before it (rtcwm.model.sampler).
Writes DIR/<clip>_stream_gen_vs_gt.mp4 (generated | GT, full resolution), DIR/<clip>_latents.pt and DIR/<clip>_stream.json:
pixel MSE overall and per chunk, the luma jump at chunk boundaries vs inside chunks, seconds per chunk.
"""
from __future__ import annotations
import argparse
import json
import time
from pathlib import Path
import numpy as np
import torch
from cwm_h3_inference.constants import VIDEO_SHIFT
from .. import paths
from ..model.layout import CLIP_FRAMES
from ..model.loading import load_for_inference
from ..model.sampler import generate_stream
from ..train.dataset import load_window, silence, stream_chunks
from .video import decode, load_vae, read_mp4, write_mp4
def luma(frames: np.ndarray) -> np.ndarray:
f = frames.astype(np.float32)
return (0.299 * f[..., 0] + 0.587 * f[..., 1] + 0.114 * f[..., 2]).mean(axis=(1, 2))
def report(gen: np.ndarray, gt: np.ndarray) -> dict:
err = ((gen.astype(np.float32) - gt.astype(np.float32)) ** 2).mean(axis=(1, 2, 3))
jumps, gt_jumps = np.abs(np.diff(luma(gen))), np.abs(np.diff(luma(gt)))
boundary = np.zeros(len(jumps), bool)
boundary[CLIP_FRAMES - 1::CLIP_FRAMES] = True # frame 17k-1 -> 17k
n = len(gen) // CLIP_FRAMES
return dict(frames=int(len(gen)), pixel_mse=float(err.mean()),
chunk_mse=[float(err[CLIP_FRAMES * k:CLIP_FRAMES * (k + 1)].mean()) for k in range(n)],
boundary_luma_jump=float(jumps[boundary].mean()), inner_luma_jump=float(jumps[~boundary].mean()),
gt_boundary_luma_jump=float(gt_jumps[boundary].mean()), max_luma_jump=float(jumps.max()))
def check_span(lora):
"""The LoRA's training span (its metadata; 6 if it predates the setting) must be the sampler's."""
from safetensors import safe_open
from ..model.layout import SPAN_CHUNKS
with safe_open(str(lora), "pt") as f:
trained = int((f.metadata() or {}).get("span_chunks", "6"))
assert trained == SPAN_CHUNKS, f"{lora} was trained with span {trained}; set RTCWM_SPAN_CHUNKS={trained}"
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--lora", type=Path, default=paths.CWM_LORA)
ap.add_argument("--out", type=Path, required=True)
ap.add_argument("--clips", nargs="+", required=True)
ap.add_argument("--chunks", type=int, default=0, help="0 = the whole stream")
ap.add_argument("--steps", type=int, default=35)
ap.add_argument("--rule", choices=["euler", "x0"], default="euler", help="x0: few-step (distilled) sampling")
ap.add_argument("--shift", type=float, default=VIDEO_SHIFT, help="flow shift of the sampling schedule")
ap.add_argument("--history-noise", type=float, default=0.0, help="noise level of the history chunks")
ap.add_argument("--picture1", choices=["native", "2048"], default="native", help="Picture 1 resolution the LoRA was trained with")
ap.add_argument("--kv-reuse", action="store_true", help="carry the history's keys/values across chunks")
ap.add_argument("--seed", type=int, default=0)
ap.add_argument("--fp8", action="store_true", help="the blocks' large linears in FP8 (Hopper)")
ap.add_argument("--history-from-last-step", action=argparse.BooleanOptionalAction, default=True,
help="kv-reuse: cache each chunk from its last denoising step (one forward fewer per chunk; "
"--no-history-from-last-step: one more clean forward)")
a = ap.parse_args()
dev = torch.device("cuda", 0)
a.out.mkdir(parents=True, exist_ok=True)
check_span(a.lora)
from ..model import sampler
sampler.HISTORY_FROM_LAST_STEP = a.history_from_last_step
model = load_for_inference(a.lora, dev, fp8=a.fp8)
audio, results = silence(), {}
for clip in a.clips:
n = a.chunks or stream_chunks(clip)
secs = []
t0 = time.time()
lat = generate_stream(model, lambda j: load_window(clip, j, audio, start=0, with_target=False, res=a.picture1), n, a.steps,
torch.Generator().manual_seed(a.seed), dev, a.rule, a.shift, a.history_noise, a.kv_reuse,
on_chunk=lambda k, s: (secs.append(round(s, 1)),
print(f"{clip} chunk {k + 1}/{n} {s:.1f}s", flush=True)))
results[clip] = (lat.cpu(), secs, time.time() - t0)
del model
torch.cuda.empty_cache()
vae = load_vae(dev)
for clip, (lat, secs, total) in results.items():
gen = decode(vae, lat, dev)
gt = read_mp4(paths.DATASET / clip / "rgb.mp4")[:len(gen)]
rep = dict(clip=clip, lora=str(a.lora), steps=a.steps, rule=a.rule, history_noise=a.history_noise, history_from_last_step=a.history_from_last_step, kv_reuse=a.kv_reuse, chunks=len(secs), **report(gen, gt),
seconds_per_chunk=secs, gen_seconds=round(total, 1))
(a.out / f"{clip}_stream.json").write_text(json.dumps(rep, indent=1))
torch.save(lat, a.out / f"{clip}_latents.pt")
write_mp4(a.out / f"{clip}_stream_gen_vs_gt.mp4", np.concatenate([gen, gt], 2), crf=20)
print(json.dumps({k: v for k, v in rep.items() if k not in ("chunk_mse", "seconds_per_chunk")}), flush=True)
if __name__ == "__main__":
main()
|