164 lines
5.8 KiB
Python
164 lines
5.8 KiB
Python
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
# You may not use this file except in compliance with the License.
|
|
# To view a copy of this license, visit http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# No warranties are given. The work is provided "AS IS", without warranty of any kind, express or implied.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""Compute SP chunk-halo VAE frame windows for a training config."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from omegaconf import OmegaConf
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
if str(REPO_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|
|
|
from utils.config import normalize_config, wan_default_config
|
|
|
|
DEFAULT_SP_VAE_HALO_LATENTS = 28
|
|
|
|
|
|
def latent_range_to_raw_window(
|
|
latent_start: int,
|
|
latent_end: int,
|
|
*,
|
|
temporal_compression_ratio: int,
|
|
) -> tuple[int, int, int]:
|
|
"""Map a latent range to the raw-frame window needed by Wan VAE encode."""
|
|
if latent_end <= latent_start:
|
|
raise ValueError(f"latent_end must be > latent_start, got {latent_start}, {latent_end}")
|
|
ratio = int(temporal_compression_ratio)
|
|
if latent_start == 0:
|
|
return 0, 1 + ratio * (latent_end - 1), 0
|
|
return ratio * (latent_start - 1), 1 + ratio * (latent_end - 1), 1
|
|
|
|
|
|
def compute_chunk_halo_metas(
|
|
*,
|
|
total_latent_frames: int,
|
|
total_raw_frames: int,
|
|
sp_size: int,
|
|
halo_latents: int,
|
|
temporal_compression_ratio: int,
|
|
) -> list[dict[str, int]]:
|
|
if sp_size <= 0:
|
|
raise ValueError("sp_size must be positive")
|
|
if halo_latents < 0:
|
|
raise ValueError("halo_latents must be non-negative")
|
|
if total_latent_frames % sp_size != 0:
|
|
raise ValueError(
|
|
f"total_latent_frames={total_latent_frames} must be divisible by sp_size={sp_size}"
|
|
)
|
|
|
|
local_latent_frames = total_latent_frames // sp_size
|
|
metas = []
|
|
for sp_rank in range(sp_size):
|
|
keep_start = sp_rank * local_latent_frames
|
|
keep_end = keep_start + local_latent_frames
|
|
halo_start = max(0, keep_start - halo_latents)
|
|
raw_start, raw_end, pseudo_prefix_latents = latent_range_to_raw_window(
|
|
halo_start,
|
|
keep_end,
|
|
temporal_compression_ratio=temporal_compression_ratio,
|
|
)
|
|
raw_start = max(0, raw_start)
|
|
raw_end = min(total_raw_frames, raw_end)
|
|
drop_latents = pseudo_prefix_latents + (keep_start - halo_start)
|
|
metas.append(
|
|
{
|
|
"sp_rank": sp_rank,
|
|
"keep_start": keep_start,
|
|
"keep_end": keep_end,
|
|
"halo_start": halo_start,
|
|
"raw_start": raw_start,
|
|
"raw_end": raw_end,
|
|
"raw_frames": raw_end - raw_start,
|
|
"drop_latents": drop_latents,
|
|
"local_latent_frames": local_latent_frames,
|
|
}
|
|
)
|
|
return metas
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--config", default="configs/train_ar.yaml", help="Training config path")
|
|
parser.add_argument("--sp-size", type=int, default=None, help="Override sequence_parallel_size")
|
|
parser.add_argument("--halo-latents", type=int, default=None, help="Override vae_halo_latents")
|
|
parser.add_argument("--latent-frames", type=int, default=None, help="Override image_or_video_shape[1]")
|
|
parser.add_argument("--raw-frames", type=int, default=None, help="Override total raw frame count")
|
|
parser.add_argument("--json", action="store_true", help="Print JSON instead of a table")
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
cfg = normalize_config(OmegaConf.load(args.config))
|
|
model_name = cfg.model_kwargs.model_name
|
|
temporal_ratio = int(wan_default_config[model_name]["temporal_compression_ratio"])
|
|
total_latent_frames = int(
|
|
args.latent_frames if args.latent_frames is not None else cfg.image_or_video_shape[1]
|
|
)
|
|
total_raw_frames = int(
|
|
args.raw_frames
|
|
if args.raw_frames is not None
|
|
else ((total_latent_frames - 1) * temporal_ratio + 1)
|
|
)
|
|
sp_size = int(
|
|
args.sp_size if args.sp_size is not None else getattr(cfg, "sequence_parallel_size", 1)
|
|
)
|
|
halo_latents = int(
|
|
args.halo_latents
|
|
if args.halo_latents is not None
|
|
else getattr(cfg, "vae_halo_latents", DEFAULT_SP_VAE_HALO_LATENTS)
|
|
)
|
|
|
|
metas = compute_chunk_halo_metas(
|
|
total_latent_frames=total_latent_frames,
|
|
total_raw_frames=total_raw_frames,
|
|
sp_size=sp_size,
|
|
halo_latents=halo_latents,
|
|
temporal_compression_ratio=temporal_ratio,
|
|
)
|
|
payload = {
|
|
"config": str(Path(args.config)),
|
|
"model_name": model_name,
|
|
"temporal_compression_ratio": temporal_ratio,
|
|
"total_latent_frames": total_latent_frames,
|
|
"total_raw_frames": total_raw_frames,
|
|
"sp_size": sp_size,
|
|
"halo_latents": halo_latents,
|
|
"rank_metas": metas,
|
|
}
|
|
if args.json:
|
|
print(json.dumps(payload, indent=2))
|
|
return
|
|
|
|
print(
|
|
f"config={payload['config']} model={model_name} "
|
|
f"latent_frames={total_latent_frames} raw_frames={total_raw_frames} "
|
|
f"sp_size={sp_size} halo_latents={halo_latents}"
|
|
)
|
|
print("rank keep_latents halo_start raw_window raw_frames drop_latents")
|
|
for meta in metas:
|
|
keep_latents = f"[{meta['keep_start']},{meta['keep_end']})".ljust(12)
|
|
raw_window = f"[{meta['raw_start']},{meta['raw_end']})".ljust(11)
|
|
print(
|
|
f"{meta['sp_rank']:>4} {keep_latents} {meta['halo_start']:>10} "
|
|
f"{raw_window} {meta['raw_frames']:>10} {meta['drop_latents']:>12}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|