pipeline.model is a tagged union: a mapping with exactly one key, which names the model family (ltx2, minimax_h3, longcat, or generic) and selects that family's options class. The former sibling sections pipeline.ltx2, pipeline.minimax_h3, and pipeline.longcat, and the DiT/VAE architecture override dicts pipeline.dit and pipeline.vae, live inside the family block. The parser implements tagged unions as a general mechanism (a dataclass with a TAGS table); the family is readable as pipeline.model.family. Resolution validates the tag against the family that the registry chose for model_path (validate_model_family raises and names the registry class and the right block) and fills the family's empty block when the input has none (fill_model_family), so readers never see None. Family-only fills and derivations (the MiniMax-H3 parallel-VAE environment variables, the LTX-2 tile-size derivation and refine preset copy, the LongCat BSA mirrors) run only for their family. Against the3f6893a0goldens every PipelineConfig is identical and no other field differs beyond the allowed categories; family-only settings are no longer decided on models of another family. LingBot-Video reads its refiner switch from pipeline.preset_overrides.refine.enabled and loads the refiner by default when the checkpoint has one, as at3f6893a0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CbSuTKA9oX4YsECdcBPAsX
145 lines
4.9 KiB
Python
145 lines
4.9 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""Generate 5-second FastH3 videos with a supported Preview adapter."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import statistics
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
from huggingface_hub import hf_hub_download
|
|
|
|
FASTVIDEO_ROOT = Path(__file__).resolve().parents[3]
|
|
sys.path.insert(0, str(FASTVIDEO_ROOT))
|
|
|
|
from fastvideo import VideoGenerator # noqa: E402
|
|
from fastvideo.api import ( # noqa: E402
|
|
AttentionConfig,
|
|
CompileConfig,
|
|
ComponentConfig,
|
|
EngineConfig,
|
|
GenerationRequest,
|
|
GeneratorConfig,
|
|
MiniMaxH3Options,
|
|
OffloadConfig,
|
|
OutputConfig,
|
|
ParallelismConfig,
|
|
PipelineSelection,
|
|
SamplingConfig,
|
|
)
|
|
|
|
# FastVideo exposes these three backend switches only through environment variables.
|
|
os.environ.update({
|
|
"FASTVIDEO_FA4": "1",
|
|
"FASTVIDEO_MINIMAX_H3_FUSIONS": "all",
|
|
"FASTVIDEO_VSA_SM100A": "0",
|
|
})
|
|
os.environ.pop("FASTVIDEO_INFERENCE_TORCH_COMPILE", None)
|
|
|
|
VARIANT_BACKENDS = {
|
|
"dense-datafree": "FLASH_ATTN",
|
|
"vsa-datafree": "VIDEO_SPARSE_ATTN_H3",
|
|
"vsa-synthetic-step1300": "VIDEO_SPARSE_ATTN_H3",
|
|
"vsa-synthetic-step1900": "VIDEO_SPARSE_ATTN_H3",
|
|
}
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("variant", choices=VARIANT_BACKENDS)
|
|
parser.add_argument("--prompt", required=True)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
"""Run one compile warmup and three measured generations with one fixed recipe."""
|
|
args = parse_args()
|
|
|
|
attention_backend = VARIANT_BACKENDS[args.variant]
|
|
use_vsa = attention_backend == "VIDEO_SPARSE_ATTN_H3"
|
|
|
|
adapter_path = hf_hub_download(
|
|
repo_id="FastVideo/FastVideo-FastH3-4-step-Preview-v1-LoRA",
|
|
filename=f"{args.variant}/adapter_model.safetensors",
|
|
)
|
|
output_dir = FASTVIDEO_ROOT / "outputs/fasth3_lora_preview" / args.variant
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
generator = VideoGenerator.from_config(
|
|
GeneratorConfig(
|
|
model_path="MiniMaxAI/MiniMax-H3",
|
|
pipeline=PipelineSelection(
|
|
components=ComponentConfig(lora_path=adapter_path, lora_strength=1.0),
|
|
model=MiniMaxH3Options(vae_parallel_decode=True, vae_parallel_decode_strategy="gather"),
|
|
),
|
|
engine=EngineConfig(
|
|
num_gpus=4,
|
|
parallelism=ParallelismConfig(tp_size=1, sp_size=4),
|
|
offload=OffloadConfig(dit=False, dit_layerwise=False),
|
|
compile=CompileConfig(vae_enabled=True, regional=attention_backend == "FLASH_ATTN"),
|
|
attention=AttentionConfig(
|
|
backend=attention_backend,
|
|
vsa_sparsity=0.9 if use_vsa else None,
|
|
vsa_tile_size=64 if use_vsa else None,
|
|
),
|
|
),
|
|
)
|
|
)
|
|
|
|
generation_count = 4
|
|
warmup_count = 1
|
|
measured_count = generation_count - warmup_count
|
|
measured_seconds: list[float] = []
|
|
|
|
print(f"Variant: {args.variant} ({attention_backend})")
|
|
print(f"Output directory: {output_dir}")
|
|
try:
|
|
for generation_index in range(generation_count):
|
|
measured = generation_index >= warmup_count
|
|
if measured:
|
|
measured_index = generation_index - warmup_count + 1
|
|
label = f"measured {measured_index}/{measured_count}"
|
|
output_path = output_dir / f"fasth3_all_run_{measured_index:02d}.mp4"
|
|
else:
|
|
warmup_index = generation_index + 1
|
|
label = f"warmup {warmup_index}/{warmup_count}"
|
|
output_path = output_dir / f"_fasth3_warmup_{warmup_index:02d}.mp4"
|
|
|
|
request = GenerationRequest(
|
|
prompt=args.prompt,
|
|
negative_prompt="",
|
|
sampling=SamplingConfig(
|
|
height=768,
|
|
width=1344,
|
|
num_frames=345, # <-- Change video length: 5sec: 124; 10sec: 243; 15sec: 345.
|
|
fps=24,
|
|
num_inference_steps=5,
|
|
guidance_scale=1.0,
|
|
batch_cfg=False,
|
|
seed=1000,
|
|
),
|
|
output=OutputConfig(
|
|
output_path=str(output_path),
|
|
save_video=True,
|
|
return_frames=False,
|
|
),
|
|
)
|
|
started = time.perf_counter()
|
|
result = generator.generate(request)
|
|
elapsed = time.perf_counter() - started
|
|
if measured:
|
|
measured_seconds.append(elapsed)
|
|
suffix = "" if measured else " (excluded from median)"
|
|
print(f"[{label}] {result.video_path or output_path}: {elapsed:.3f}s{suffix}")
|
|
finally:
|
|
generator.shutdown()
|
|
|
|
print(f"Median E2E wall time: {statistics.median(measured_seconds):.3f}s")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|