Files
hao-ai-lab-FastVideo/examples/inference/basic/basic_fasth3_to_regen2k.py
T

141 lines
7.1 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""Draft with a FastH3 checkpoint at 768P, then regenerate the draft at 2560x1440 with Open-H3-Regenerate-2K.
The draft's native latent goes straight to the regen's upscaler: the draft is never decoded to pixels and re-encoded.
The regen stack is unchanged (base MiniMax-H3, the released merged LoRA and Sharp branches, three Euler steps).
python examples/inference/basic/basic_fasth3_to_regen2k.py \
--draft-model FastVideo/FastVideo-FastH3-8-Step-V2 --regen-model Efficient-Large-Model/Open-H3-Regenerate-2K \
--prompt "A red fox trots through fresh snow" --output outputs/fasth3_regen2k
A packed NVFP4 draft checkpoint (FastH3 QAD, FastH3 Trim NVFP4) needs ``--draft-quant h3_dit_vsa``; ``--vae-decoder``
selects an NVFP4 decoder for the 2K decode.
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import torch
from fastvideo import VideoGenerator
from fastvideo.api import (
ComponentConfig,
EngineConfig,
GenerationRequest,
GeneratorConfig,
InputConfig,
OffloadConfig,
OutputConfig,
PipelineSelection,
QuantizationConfig,
SamplingConfig,
)
from fastvideo.utils import maybe_download_model
DRAFT_SIZE = (768, 1344)
NUM_FRAMES = 124
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--draft-model", required=True, help="FastH3 checkpoint id or local path")
parser.add_argument("--draft-quant", default=None, help="NVFP4 layer profile of a packed draft, e.g. h3_dit_vsa")
parser.add_argument("--regen-model", default="Efficient-Large-Model/Open-H3-Regenerate-2K")
parser.add_argument("--prompt", required=True)
parser.add_argument("--draft-steps", type=int, default=9, help="sigma-grid points of the draft (9 = 8 forwards)")
parser.add_argument("--draft-seed", type=int, default=1000)
parser.add_argument("--seed", type=int, default=1766580932, help="regen refine noise seed")
parser.add_argument("--output", type=Path, default=Path("outputs/fasth3_regen2k"))
parser.add_argument("--vae-decoder",
default=None,
help="NVFP4 H3 video decoder for the regen decode (Blackwell), e.g. "
"quality-36L/decoder-nvfp4.safetensors from FastVideo/FastH3-NVFP4-VAE-Decoder")
return parser.parse_args()
def normalize_h3_latents(latents: torch.Tensor, regen_model: str) -> torch.Tensor:
"""Re-apply the H3 VAE's latent statistics: a draft run returns denormalized latents, the regen takes normalized."""
vae_config = os.path.join(maybe_download_model(regen_model, allow_patterns=["base_model/vae/config.json"]),
"base_model", "vae", "config.json")
with open(vae_config, encoding="utf-8") as handle:
config = json.load(handle)
mean = torch.tensor(config["latents_mean"], dtype=torch.float32).view(-1, 1, 1, 1)
std = torch.tensor(config["latents_std"], dtype=torch.float32).view(-1, 1, 1, 1)
return (latents.float().cpu().reshape(-1, *latents.shape[-3:]) - mean) / std
def draft_latent(args: argparse.Namespace) -> torch.Tensor:
engine = EngineConfig(num_gpus=1, offload=OffloadConfig(dit=False, dit_layerwise=False, vae=False))
if args.draft_quant:
engine.quantization = QuantizationConfig(transformer_quant="NVFP4", layer_profile=args.draft_quant)
generator = VideoGenerator.from_config(
GeneratorConfig(model_path=args.draft_model,
engine=engine,
pipeline=PipelineSelection(components=ComponentConfig(),
experimental={
"attention_backend": "VIDEO_SPARSE_ATTN_H3",
"VSA_sparsity": 0.8,
"VSA_tile_size": 64,
"output_type": "latent",
})))
try:
result = generator.generate(
GenerationRequest(prompt=args.prompt,
sampling=SamplingConfig(height=DRAFT_SIZE[0],
width=DRAFT_SIZE[1],
num_frames=NUM_FRAMES,
fps=24,
num_inference_steps=args.draft_steps,
guidance_scale=1.0,
seed=args.draft_seed),
output=OutputConfig(save_video=False, return_frames=True)))
finally:
generator.shutdown()
return normalize_h3_latents(torch.as_tensor(result.samples), args.regen_model)
def main() -> None:
args = parse_args()
args.output.mkdir(parents=True, exist_ok=True)
latent_path = args.output / "draft_latent.pt"
torch.save(draft_latent(args), latent_path)
generator = VideoGenerator.from_config(
GeneratorConfig(model_path=args.regen_model,
engine=EngineConfig(num_gpus=1,
offload=OffloadConfig(dit=False, dit_layerwise=False, text_encoder=True,
vae=False)),
pipeline=PipelineSelection(components=ComponentConfig(),
experimental={
"attention_backend": "TORCH_SDPA",
"draft_size": "768p",
**({
"h3_vae_nvfp4_decoder": args.vae_decoder
} if args.vae_decoder else {}),
})))
try:
result = generator.generate(
GenerationRequest(prompt=args.prompt,
inputs=InputConfig(video_path=str(latent_path)),
sampling=SamplingConfig(height=1440,
width=2560,
num_frames=NUM_FRAMES,
fps=24,
num_inference_steps=3,
guidance_scale=1.0,
seed=args.seed),
output=OutputConfig(output_path=str(args.output / "regen2k.mp4"),
save_video=True,
return_frames=False)))
print(f"wrote {result.video_path}")
finally:
generator.shutdown()
if __name__ == "__main__":
main()