Self-Forcing / scripts /generate_vbench8_extended_atc.py
Cccccz's picture
Upload Python scripts
bc29ee3 verified
Raw History Blame Contribute Delete
10.8 kB
#!/usr/bin/env python3
"""Generate ATC FPPF/FPPP videos against the existing Extended-251 FFFF run."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any
REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
from scripts import generate_vbench8_extended_strategies as extended
import lpips
import torch
from omegaconf import OmegaConf
from safetensors import safe_open
from scripts import evaluate_single_block_fppf as base
from scripts.generate_vbench8_extended_disca import artifact_paths, read_u8_video
from utils.wan_wrapper import WanTextEncoder, WanVAEWrapper
MAPPING_DEFAULT = REPO_ROOT / "assets/vbench8_extended_subset_mapping.json"
REFERENCE_DEFAULT = (
REPO_ROOT / "evaluation_runs/vbench8_extended_stage1_step2000_20260901"
)
OUTPUT_DEFAULT = (
REPO_ROOT / "evaluation_runs/vbench8_extended_atc_stage1_step2000_20260901"
)
PATTERN_TO_STEPS = {"FPPF": [1, 2], "FPPP": [1, 2, 3]}
def read_predictor_config(weights: Path) -> dict[str, Any]:
with safe_open(weights, framework="pt", device="cpu") as handle:
metadata = handle.metadata() or {}
raw = metadata.get("predictor_config")
if raw is None:
raise ValueError(f"ATC checkpoint has no predictor_config metadata: {weights}")
config = json.loads(raw)
if config.get("input_variant") != "atc":
raise ValueError(f"Checkpoint is not ATC: {config}")
return config
def strategy_configs(
scope: str, patterns: list[str] | tuple[str, ...]
) -> list[dict[str, Any]]:
return [
{
"name": f"atc_{scope}_{pattern.lower()}",
"policy": "fppf",
"pattern": pattern,
"candidate_steps": PATTERN_TO_STEPS[pattern],
"allow_chunk0_predictor": False,
"beta": None,
"threshold": None,
"target_accepts": 6 * len(PATTERN_TO_STEPS[pattern]),
"head": None,
"input_variant": "atc",
"atc_previous_scope": scope,
}
for pattern in patterns
]
def load_models(
weights: Path, scope: str, device: torch.device
) -> tuple[Any, Any, Any, Any, None, None, Any]:
predictor_config = read_predictor_config(weights)
if predictor_config.get("atc_previous_scope") != scope:
raise ValueError(
f"Checkpoint scope={predictor_config.get('atc_previous_scope')} "
f"does not match requested scope={scope}"
)
print("[setup] loading VAE", flush=True)
vae = WanVAEWrapper().to(device=device, dtype=torch.bfloat16).eval()
config = OmegaConf.merge(
OmegaConf.load(REPO_ROOT / "configs/default_config.yaml"),
OmegaConf.load(REPO_ROOT / "configs/self_forcing_sid.yaml"),
)
print(f"[setup] loading Teacher and ATC scope={scope}", flush=True)
pipeline = base.build_pipeline(
config, REPO_ROOT / "checkpoints/self_forcing_dmd.pt", vae, device
)
predictor = base.load_predictor(
pipeline.generator.model,
{
**predictor_config,
"weights": weights,
"atc_collect_diagnostics": False,
},
device,
)
text_encoder = WanTextEncoder().to(device=device, dtype=torch.bfloat16).eval()
text_encoder.requires_grad_(False)
print("[setup] loading LPIPS", flush=True)
lpips_model = lpips.LPIPS(net="alex", verbose=False).to(device).eval()
lpips_model.requires_grad_(False)
return vae, pipeline, predictor, text_encoder, None, None, lpips_model
def is_complete(
output_root: Path,
row: dict[str, Any],
strategies: list[dict[str, Any]],
) -> bool:
return all(
all(path.is_file() for path in artifact_paths(output_root, row, config["name"]))
for config in strategies
)
def run_prompt(
*,
row: dict[str, Any],
strategies: list[dict[str, Any]],
reference_root: Path,
output_root: Path,
seed: int,
models: tuple[Any, ...],
device: torch.device,
) -> None:
global_index = int(row["global_index"])
suite = str(row["prompt_suite"])
suite_index = int(row["suite_index"])
prompt = str(row["extended_prompt"])
reference_path = (
reference_root / "generated_videos/ffff" / suite / f"{suite_index:03d}.mp4"
)
print(f"[prompt] global={global_index} suite={suite}/{suite_index}", flush=True)
conditional = models[3](text_prompts=[prompt])
for strategy in strategies:
name = str(strategy["name"])
video_path, record_path = artifact_paths(output_root, row, name)
if video_path.is_file() and record_path.is_file():
print(f"[cached-strategy] global={global_index} strategy={name}", flush=True)
continue
latent, diagnostic = extended.generate_rollout(
pipeline=models[1],
conditional_dict=conditional,
seed=seed,
device=device,
predictor=models[2],
head=None,
config=strategy,
)
with torch.autocast(device_type="cuda", dtype=torch.bfloat16):
pixels = models[0].decode_to_pixel(latent, use_cache=False)
prediction_u8 = base.pixels_to_u8(pixels)
extended.atomic_video(prediction_u8, video_path)
reference_decoded = read_u8_video(reference_path)
prediction_decoded = read_u8_video(video_path)
metrics = base.frame_metrics(
reference_u8=reference_decoded,
prediction_u8=prediction_decoded,
lpips_model=models[6],
batch_size=4,
device=device,
)
extended.atomic_json(
record_path,
{
"status": "complete",
"strategy": name,
"policy": "fppf",
"pattern": strategy["pattern"],
"input_variant": "atc",
"atc_previous_scope": strategy["atc_previous_scope"],
"candidate_steps": strategy["candidate_steps"],
"allow_chunk0_predictor": False,
"global_index": global_index,
"prompt_suite": suite,
"suite_index": suite_index,
"prompt": prompt,
"seed": seed,
"generation": {
key: value
for key, value in diagnostic.items()
if key != "decisions"
},
"decisions": diagnostic["decisions"],
"pixel_metrics_vs_ffff": extended.compact_metrics(metrics),
"pixel_metric_input": (
"MP4-decoded uint8 RGB, all 81 frames on both sides"
),
"reference_video": str(reference_path),
"video": str(video_path.relative_to(output_root)),
},
)
print(
f"[result] global={global_index} strategy={name} "
f"accept={diagnostic['accepted_predictor_calls']} "
f"psnr={metrics['psnr']:.4f} ssim={metrics['ssim']:.6f} "
f"lpips={metrics['lpips']:.6f}",
flush=True,
)
if hasattr(models[0].model, "clear_cache"):
models[0].model.clear_cache()
del latent, pixels, prediction_u8, reference_decoded, prediction_decoded
torch.cuda.empty_cache()
del conditional
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--gpu", default=extended.PHYSICAL_GPU)
parser.add_argument("--scope", choices=("chunk", "last_frame"), required=True)
parser.add_argument("--predictor-weights", type=Path, required=True)
parser.add_argument("--mapping", type=Path, default=MAPPING_DEFAULT)
parser.add_argument("--reference-root", type=Path, default=REFERENCE_DEFAULT)
parser.add_argument("--output-root", type=Path, default=OUTPUT_DEFAULT)
parser.add_argument("--pattern", action="append", choices=tuple(PATTERN_TO_STEPS))
parser.add_argument("--shard-index", type=int, default=0)
parser.add_argument("--num-shards", type=int, default=1)
parser.add_argument("--global-index", action="append", type=int, default=None)
parser.add_argument("--seed", type=int, default=0)
return parser.parse_args()
def main() -> None:
args = parse_args()
if args.num_shards < 1 or not 0 <= args.shard_index < args.num_shards:
raise ValueError("Invalid shard index/number")
weights = args.predictor_weights.resolve()
mapping = extended.read_mapping(args.mapping.resolve())
if args.global_index is None:
rows = [
row for index, row in enumerate(mapping)
if index % args.num_shards == args.shard_index
]
else:
requested = set(args.global_index)
rows = [row for row in mapping if int(row["global_index"]) in requested]
if len(rows) != len(requested):
raise ValueError("Unknown global index")
patterns = args.pattern or ["FPPF", "FPPP"]
strategies = strategy_configs(args.scope, patterns)
reference_root = args.reference_root.resolve()
if len(list((reference_root / "generated_videos/ffff").rglob("*.mp4"))) != 251:
raise ValueError("Incomplete FFFF reference")
output_root = args.output_root.resolve()
output_root.mkdir(parents=True, exist_ok=True)
extended.write_shard_manifest(
output_root,
str(args.gpu),
args.shard_index,
args.num_shards,
rows,
strategies,
"running",
)
device = torch.device("cuda")
torch.set_grad_enabled(False)
models = load_models(weights, args.scope, device)
completed = 0
try:
for row in rows:
if is_complete(output_root, row, strategies):
completed += 1
print(f"[cached] {completed}/{len(rows)} global={row['global_index']}", flush=True)
continue
run_prompt(
row=row,
strategies=strategies,
reference_root=reference_root,
output_root=output_root,
seed=args.seed,
models=models,
device=device,
)
completed += 1
print(f"[progress] {completed}/{len(rows)} prompts", flush=True)
finally:
extended.write_shard_manifest(
output_root,
str(args.gpu),
args.shard_index,
args.num_shards,
rows,
strategies,
"complete" if completed == len(rows) else "failed",
)
print(f"[complete] gpu={args.gpu} scope={args.scope} prompts={completed}", flush=True)
if __name__ == "__main__":
main()