diff --git a/examples/inference/basic/README.md b/examples/inference/basic/README.md index 7c948bd9b..2544fe48f 100644 --- a/examples/inference/basic/README.md +++ b/examples/inference/basic/README.md @@ -183,7 +183,7 @@ def main(): prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - video = generator.generate_video(prompt) + video = generator.generate({"prompt": prompt}) if __name__ == "__main__": main() diff --git a/examples/inference/basic/basic.py b/examples/inference/basic/basic.py index 90a702041..428c38f17 100644 --- a/examples/inference/basic/basic.py +++ b/examples/inference/basic/basic.py @@ -1,7 +1,5 @@ from fastvideo import VideoGenerator -# from fastvideo.api.sampling_param import SamplingParam - OUTPUT_PATH = "video_samples" @@ -22,15 +20,20 @@ def main(): # image_encoder_cpu_offload=False, ) - # sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers") - # sampling_param.num_frames = 45 - # sampling_param.image_path = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg" # Generate videos with the same simple API, regardless of GPU count prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True) - # video = generator.generate_video(prompt, sampling_param=sampling_param, output_path="wan_t2v_videos/") + video = generator.generate({ + "prompt": prompt, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) + # video = generator.generate({ + # "prompt": prompt, + # "inputs": {"image_path": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg"}, + # "sampling": {"num_frames": 45}, + # "output": {"output_path": "wan_t2v_videos/"}, + # }) # Generate another video with a different prompt, without reloading the # model! @@ -39,7 +42,10 @@ def main(): "the breeze, enhancing the lion's commanding presence. The tone is vibrant, " "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") - video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True) + video2 = generator.generate({ + "prompt": prompt2, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_cosmos2_5_i2w.py b/examples/inference/basic/basic_cosmos2_5_i2w.py index 667bb64fc..366f2b862 100644 --- a/examples/inference/basic/basic_cosmos2_5_i2w.py +++ b/examples/inference/basic/basic_cosmos2_5_i2w.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 from fastvideo import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam def main(): @@ -17,8 +16,6 @@ def main(): pin_cpu_memory=True, ) - sampling_param = SamplingParam.from_pretrained(model_path) - # image2world example from official repo image_path = "assets/images/bus_terminal.jpg" @@ -32,14 +29,12 @@ def main(): "It then comes to a smooth stop, resuming its position in line. " "Overhead signage in Chinese characters remains illuminated, enhancing the vibrant, urban night scene.") - generator.generate_video( - prompt, - sampling_param=sampling_param, - image_path=str(image_path), - num_cond_frames=1, - output_path="outputs_video/cosmos2_5_i2w.mp4", - save_video=True, - ) + generator.generate({ + "prompt": prompt, + "inputs": {"image_path": str(image_path)}, + "output": {"output_path": "outputs_video/cosmos2_5_i2w.mp4", "save_video": True}, + "extensions": {"num_cond_frames": 1}, + }) generator.shutdown() diff --git a/examples/inference/basic/basic_cosmos2_5_t2w.py b/examples/inference/basic/basic_cosmos2_5_t2w.py index 13590dfdf..84e9b178f 100644 --- a/examples/inference/basic/basic_cosmos2_5_t2w.py +++ b/examples/inference/basic/basic_cosmos2_5_t2w.py @@ -1,5 +1,4 @@ from fastvideo import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam def main(): @@ -16,9 +15,6 @@ def main(): pin_cpu_memory=True, ) - # Load default sampling parameters (negative_prompt, resolution, steps, etc.) - sampling_param = SamplingParam.from_pretrained(model_path) - prompt = ( "A high-definition video captures the precision of robotic welding in an industrial setting. " "The first frame showcases a robotic arm, equipped with a welding torch, positioned over a large metal structure. " @@ -33,12 +29,10 @@ def main(): "The scene retains its industrial ambiance, with the welding sparks and smoke dominating the visual field, " "underscoring the ongoing nature of the welding operation.") - generator.generate_video( - prompt, - sampling_param=sampling_param, - output_path="outputs_video/cosmos2_5_t2w.mp4", - save_video=True, - ) + generator.generate({ + "prompt": prompt, + "output": {"output_path": "outputs_video/cosmos2_5_t2w.mp4", "save_video": True}, + }) generator.shutdown() diff --git a/examples/inference/basic/basic_cosmos2_5_v2w.py b/examples/inference/basic/basic_cosmos2_5_v2w.py index c174aced9..45a689c3e 100644 --- a/examples/inference/basic/basic_cosmos2_5_v2w.py +++ b/examples/inference/basic/basic_cosmos2_5_v2w.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 from fastvideo import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam def main(): @@ -17,8 +16,6 @@ def main(): pin_cpu_memory=True, ) - sampling_param = SamplingParam.from_pretrained(model_path) - # video2world example from official repo video_path = "assets/videos/robot_pouring.mp4" @@ -36,14 +33,12 @@ def main(): "The final frame captures the robotic arm with the pitcher finishing the pour, with the glass now filled to a higher level, while the pitcher is slightly tilted but still held securely by the gripper." ) - generator.generate_video( - prompt, - sampling_param=sampling_param, - video_path=str(video_path), - num_cond_frames=1, - output_path="outputs_video/cosmos2_5_v2w.mp4", - save_video=True, - ) + generator.generate({ + "prompt": prompt, + "inputs": {"video_path": str(video_path)}, + "output": {"output_path": "outputs_video/cosmos2_5_v2w.mp4", "save_video": True}, + "extensions": {"num_cond_frames": 1}, + }) generator.shutdown() diff --git a/examples/inference/basic/basic_dmd.py b/examples/inference/basic/basic_dmd.py index b0340e1c1..bb3408572 100644 --- a/examples/inference/basic/basic_dmd.py +++ b/examples/inference/basic/basic_dmd.py @@ -2,8 +2,6 @@ import os import time from fastvideo import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam - OUTPUT_PATH = "video_samples_dmd2" @@ -12,29 +10,34 @@ def main(): load_start_time = time.perf_counter() model_name = "FastVideo/FastWan2.1-T2V-1.3B-Diffusers" - generator = VideoGenerator.from_pretrained( - model_name, - # FastVideo will automatically handle distributed setup - num_gpus=1, - use_fsdp_inference=False, # set to True if GPU is out of memory - # Adjust these offload parameters if you have < 32GB of VRAM - text_encoder_cpu_offload=True, - pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" - dit_cpu_offload=False, - vae_cpu_offload=False, - VSA_sparsity=0.8, - ) + generator = VideoGenerator.from_config({ + "model_path": model_name, + "engine": { + # FastVideo will automatically handle distributed setup + "num_gpus": 1, + "use_fsdp_inference": False, # set to True if GPU is out of memory + # Adjust these offload parameters if you have < 32GB of VRAM + "offload": { + "text_encoder": True, + "pin_cpu_memory": True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" + "dit": False, + "vae": False, + }, + "attention": {"vsa_sparsity": 0.8}, + }, + }) load_end_time = time.perf_counter() load_time = load_end_time - load_start_time - sampling_param = SamplingParam.from_pretrained(model_name) - sampling_param.num_frames = 81 - prompt = ( "A neon-lit alley in futuristic Tokyo during a heavy rainstorm at night. The puddles reflect glowing signs in kanji, advertising ramen, karaoke, and VR arcades. A woman in a translucent raincoat walks briskly with an LED umbrella. Steam rises from a street food cart, and a cat darts across the screen. Raindrops are visible on the camera lens, creating a cinematic bokeh effect." ) start_time = time.perf_counter() - video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, sampling_param=sampling_param) + video = generator.generate({ + "prompt": prompt, + "sampling": {"num_frames": 81}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) end_time = time.perf_counter() gen_time = end_time - start_time @@ -46,7 +49,11 @@ def main(): "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") start_time = time.perf_counter() - video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True, num_frames=81) + video2 = generator.generate({ + "prompt": prompt2, + "sampling": {"num_frames": 81}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) end_time = time.perf_counter() gen_time2 = end_time - start_time diff --git a/examples/inference/basic/basic_dreamx_world.py b/examples/inference/basic/basic_dreamx_world.py index 9af4a02ed..5beff58b0 100644 --- a/examples/inference/basic/basic_dreamx_world.py +++ b/examples/inference/basic/basic_dreamx_world.py @@ -15,16 +15,20 @@ def _env_float(name: str, default: float) -> float: def main(): model_name = os.getenv("DREAMX_WORLD_MODEL_DIR", "FastVideo/DreamX-World-5B-Cam-Diffusers") - generator = VideoGenerator.from_pretrained( - model_name, - num_gpus=1, - use_fsdp_inference=False, - dit_cpu_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=False, - override_pipeline_cls_name="DreamXWorldPipeline", - ) + generator = VideoGenerator.from_config({ + "model_path": model_name, + "engine": { + "num_gpus": 1, + "use_fsdp_inference": False, + "offload": { + "dit": False, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": False, + }, + }, + "pipeline": {"components": {"override_pipeline_cls_name": "DreamXWorldPipeline"}}, + }) prompt = os.getenv( "DREAMX_WORLD_PROMPT", @@ -36,23 +40,30 @@ def main(): "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG", ) - kwargs = { - "output_path": OUTPUT_PATH, - "save_video": os.getenv("DREAMX_WORLD_SAVE_VIDEO", "1") != "0", - "height": _env_int("DREAMX_WORLD_HEIGHT", 480), - "width": _env_int("DREAMX_WORLD_WIDTH", 832), - "num_frames": _env_int("DREAMX_WORLD_NUM_FRAMES", 161), - "num_inference_steps": _env_int("DREAMX_WORLD_STEPS", 30), - "guidance_scale": _env_float("DREAMX_WORLD_GUIDANCE", 5.0), - "action_list": os.getenv("DREAMX_WORLD_ACTIONS", "w,d,w").split(","), - "action_speed_list": - [float(value) for value in os.getenv("DREAMX_WORLD_ACTION_SPEEDS", "4.0,2.0,4.0").split(",")], + request = { + "prompt": prompt, + "sampling": { + "height": _env_int("DREAMX_WORLD_HEIGHT", 480), + "width": _env_int("DREAMX_WORLD_WIDTH", 832), + "num_frames": _env_int("DREAMX_WORLD_NUM_FRAMES", 161), + "num_inference_steps": _env_int("DREAMX_WORLD_STEPS", 30), + "guidance_scale": _env_float("DREAMX_WORLD_GUIDANCE", 5.0), + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": os.getenv("DREAMX_WORLD_SAVE_VIDEO", "1") != "0", + }, + "extensions": { + "action_list": os.getenv("DREAMX_WORLD_ACTIONS", "w,d,w").split(","), + "action_speed_list": + [float(value) for value in os.getenv("DREAMX_WORLD_ACTION_SPEEDS", "4.0,2.0,4.0").split(",")], + }, } if image_path: - kwargs["image_path"] = image_path + request["inputs"] = {"image_path": image_path} try: - generator.generate_video(prompt, **kwargs) + generator.generate(request) finally: generator.shutdown() diff --git a/examples/inference/basic/basic_flux_dev.py b/examples/inference/basic/basic_flux_dev.py index 930f0f0fb..2033d7c2f 100644 --- a/examples/inference/basic/basic_flux_dev.py +++ b/examples/inference/basic/basic_flux_dev.py @@ -83,24 +83,23 @@ def main() -> None: os.makedirs(args.out_dir, exist_ok=True) - init_kwargs = { - "num_gpus": args.num_gpus, - "workload_type": "t2i", - "sp_size": 1, - "tp_size": 1, - "dit_cpu_offload": False, - "dit_layerwise_offload": False, - "text_encoder_cpu_offload": False, - "vae_cpu_offload": False, - "image_encoder_cpu_offload": False, - "pin_cpu_memory": False, - "use_fsdp_inference": False, - } - - generator = VideoGenerator.from_pretrained( - model_path=args.model_path, - **init_kwargs, - ) + generator = VideoGenerator.from_config({ + "model_path": args.model_path, + "engine": { + "num_gpus": args.num_gpus, + "parallelism": {"sp_size": 1, "tp_size": 1}, + "offload": { + "dit": False, + "dit_layerwise": False, + "text_encoder": False, + "vae": False, + "image_encoder": False, + "pin_cpu_memory": False, + }, + "use_fsdp_inference": False, + }, + "pipeline": {"workload_type": "t2i"}, + }) try: for i, prompt in enumerate(prompts): seed = args.seed + i @@ -109,21 +108,23 @@ def main() -> None: output_path = os.path.join(args.out_dir, f"{filename_base}.png") print(f"[flux] prompt_idx={i} seed={seed} output_path={output_path}") - generation_kwargs = { - "output_path": output_path, - "height": args.height, - "width": args.width, - "num_frames": 1, - "fps": 1, - "num_inference_steps": args.steps, - "guidance_scale": args.guidance, - "use_embedded_guidance": True, - "true_cfg_scale": 1.0, - "seed": seed, - "save_video": True, + request = { + "prompt": prompt, + "sampling": { + "height": args.height, + "width": args.width, + "num_frames": 1, + "fps": 1, + "num_inference_steps": args.steps, + "guidance_scale": args.guidance, + "use_embedded_guidance": True, + "true_cfg_scale": 1.0, + "seed": seed, + }, + "output": {"output_path": output_path, "save_video": True}, } - generator.generate_video(prompt, **generation_kwargs) + generator.generate(request) print(f"[flux] done. outputs written to: {args.out_dir}") finally: diff --git a/examples/inference/basic/basic_gamecraft.py b/examples/inference/basic/basic_gamecraft.py index c086d86e4..2782827ea 100644 --- a/examples/inference/basic/basic_gamecraft.py +++ b/examples/inference/basic/basic_gamecraft.py @@ -95,23 +95,24 @@ def main(): prompt = DEFAULT_I2V_PROMPT if is_i2v else DEFAULT_PROMPTS["temple"] print(f"Mode: {'I2V' if is_i2v else 'T2V'}, prompt: {prompt[:60]}...") - gen_kw = dict( - prompt=prompt, - negative_prompt="", - camera_states=camera_states, - height=height, - width=width, - num_frames=num_frames, - num_inference_steps=50, - guidance_scale=6.0, - seed=42, - fps=24, - output_path=OUTPUT_PATH, - save_video=True, - ) + request = { + "prompt": prompt, + "negative_prompt": "", + "sampling": { + "height": height, + "width": width, + "num_frames": num_frames, + "num_inference_steps": 50, + "guidance_scale": 6.0, + "seed": 42, + "fps": 24, + }, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + "extensions": {"camera_states": camera_states}, + } if is_i2v: - gen_kw["image_path"] = image_path - generator.generate_video(**gen_kw) + request["inputs"] = {"image_path": image_path} + generator.generate(request) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_gen3c.py b/examples/inference/basic/basic_gen3c.py index d98364b00..05b1ea57b 100644 --- a/examples/inference/basic/basic_gen3c.py +++ b/examples/inference/basic/basic_gen3c.py @@ -68,23 +68,26 @@ def main(): pin_cpu_memory=True, ) - video = generator.generate_video( - args.prompt, - negative_prompt=args.negative_prompt, - image_path=args.image_path, - trajectory_type=args.trajectory, - movement_distance=args.movement_distance, - camera_rotation=args.camera_rotation, - height=args.height, - width=args.width, - num_frames=args.num_frames, - num_inference_steps=args.num_inference_steps, - guidance_scale=args.guidance_scale, - fps=24, - seed=args.seed, - output_path=args.output_path, - save_video=True, - ) + video = generator.generate({ + "prompt": args.prompt, + "negative_prompt": args.negative_prompt, + "inputs": {"image_path": args.image_path}, + "sampling": { + "height": args.height, + "width": args.width, + "num_frames": args.num_frames, + "num_inference_steps": args.num_inference_steps, + "guidance_scale": args.guidance_scale, + "fps": 24, + "seed": args.seed, + }, + "output": {"output_path": args.output_path, "save_video": True}, + "extensions": { + "trajectory_type": args.trajectory, + "movement_distance": args.movement_distance, + "camera_rotation": args.camera_rotation, + }, + }) generator.shutdown() diff --git a/examples/inference/basic/basic_hy15.py b/examples/inference/basic/basic_hy15.py index a9dc3d141..eda138126 100644 --- a/examples/inference/basic/basic_hy15.py +++ b/examples/inference/basic/basic_hy15.py @@ -26,12 +26,12 @@ def main(): "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - video = generator.generate_video(prompt, - output_path=OUTPUT_PATH, - save_video=True, - negative_prompt="", - num_frames=81, - fps=16) + video = generator.generate({ + "prompt": prompt, + "negative_prompt": "", + "sampling": {"num_frames": 81, "fps": 16}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) prompt2 = ("A majestic lion strides across the golden savanna, its powerful frame " "glistening under the warm afternoon sun. The tall grass ripples gently in " @@ -39,12 +39,12 @@ def main(): "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") - video2 = generator.generate_video(prompt2, - output_path=OUTPUT_PATH, - save_video=True, - negative_prompt="", - num_frames=81, - fps=16) + video2 = generator.generate({ + "prompt": prompt2, + "negative_prompt": "", + "sampling": {"num_frames": 81, "fps": 16}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_hy15_1080p.py b/examples/inference/basic/basic_hy15_1080p.py index 29ce70152..439da4a98 100644 --- a/examples/inference/basic/basic_hy15_1080p.py +++ b/examples/inference/basic/basic_hy15_1080p.py @@ -27,7 +27,11 @@ def main(): "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, negative_prompt="") + video = generator.generate({ + "prompt": prompt, + "negative_prompt": "", + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) prompt2 = ("A majestic lion strides across the golden savanna, its powerful frame " "glistening under the warm afternoon sun. The tall grass ripples gently in " @@ -35,7 +39,11 @@ def main(): "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") - video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True, negative_prompt="") + video2 = generator.generate({ + "prompt": prompt2, + "negative_prompt": "", + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_hyworld.py b/examples/inference/basic/basic_hyworld.py index 72bcf6593..9a99ed6f5 100644 --- a/examples/inference/basic/basic_hyworld.py +++ b/examples/inference/basic/basic_hyworld.py @@ -47,19 +47,22 @@ def main(): # Generate video # The pose string is automatically converted to camera matrices by the pipeline print("\nGenerating video...") - generator.generate_video( - prompt=args.prompt, - image_path=args.image, - pose=args.pose, # Camera trajectory control - output_path=args.output_path, - save_video=True, - negative_prompt="", - num_frames=args.num_frames, - fps=24, - height=HEIGHT, - width=WIDTH, - seed=args.seed, - ) + generator.generate({ + "prompt": args.prompt, + "negative_prompt": "", + "inputs": { + "image_path": args.image, + "pose": args.pose, # Camera trajectory control + }, + "sampling": { + "num_frames": args.num_frames, + "fps": 24, + "height": HEIGHT, + "width": WIDTH, + "seed": args.seed, + }, + "output": {"output_path": args.output_path, "save_video": True}, + }) print(f"\nVideo saved to: {args.output_path}") diff --git a/examples/inference/basic/basic_kandinsky5_i2v.py b/examples/inference/basic/basic_kandinsky5_i2v.py index a11dde536..86579913c 100644 --- a/examples/inference/basic/basic_kandinsky5_i2v.py +++ b/examples/inference/basic/basic_kandinsky5_i2v.py @@ -20,15 +20,12 @@ def main(): ) prompt = ("A woman stands up and walks away") - _ = generator.generate_video( - prompt, - image_path=IMAGE_PATH, - output_path=OUTPUT_PATH, - save_video=True, - height=1024, - width=1024, - num_frames=121, - ) + _ = generator.generate({ + "prompt": prompt, + "inputs": {"image_path": IMAGE_PATH}, + "sampling": {"height": 1024, "width": 1024, "num_frames": 121}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_kandinsky5_t2v.py b/examples/inference/basic/basic_kandinsky5_t2v.py index 37ee149a0..a4dcc175a 100644 --- a/examples/inference/basic/basic_kandinsky5_t2v.py +++ b/examples/inference/basic/basic_kandinsky5_t2v.py @@ -21,24 +21,22 @@ def main(): prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - _ = generator.generate_video(prompt, - output_path=OUTPUT_PATH, - save_video=True, - height=512, - width=768, - num_frames=121) + _ = generator.generate({ + "prompt": prompt, + "sampling": {"height": 512, "width": 768, "num_frames": 121}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) prompt2 = ("A majestic lion strides across the golden savanna, its powerful frame " "glistening under the warm afternoon sun. The tall grass ripples gently in " "the breeze, enhancing the lion's commanding presence. The tone is vibrant, " "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") - _ = generator.generate_video(prompt2, - output_path=OUTPUT_PATH, - save_video=True, - height=512, - width=768, - num_frames=121) + _ = generator.generate({ + "prompt": prompt2, + "sampling": {"height": 512, "width": 768, "num_frames": 121}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_lingbotworld2_causal_fast.py b/examples/inference/basic/basic_lingbotworld2_causal_fast.py index 4c08764a7..5ff801d17 100644 --- a/examples/inference/basic/basic_lingbotworld2_causal_fast.py +++ b/examples/inference/basic/basic_lingbotworld2_causal_fast.py @@ -13,36 +13,43 @@ OUTPUT_PATH = REPO_ROOT / "outputs" / "lingbotworld2_causal_fast.mp4" def main() -> None: """Load the native FastVideo LingBot World 2 causal-fast pipeline and generate one video.""" - generator = VideoGenerator.from_pretrained( - os.environ["LINGBOTWORLD2_MODEL_PATH"], - num_gpus=8, - sp_size=8, - hsdp_shard_dim=8, - use_fsdp_inference=True, - dit_layerwise_offload=False, - dit_cpu_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=False, - pin_cpu_memory=True, - override_pipeline_cls_name="LingBotWorld2CausalFastPipeline", - ) + generator = VideoGenerator.from_config({ + "model_path": os.environ["LINGBOTWORLD2_MODEL_PATH"], + "engine": { + "num_gpus": 8, + "parallelism": {"sp_size": 8, "hsdp_shard_dim": 8}, + "use_fsdp_inference": True, + "offload": { + "dit_layerwise": False, + "dit": False, + "vae": False, + "text_encoder": False, + "pin_cpu_memory": True, + }, + }, + "pipeline": {"components": {"override_pipeline_cls_name": "LingBotWorld2CausalFastPipeline"}}, + }) try: - generator.generate_video( + generator.generate({ + "prompt": "A serene lakeside scene with a lone tree standing in calm water, surrounded by distant snow-capped mountains under a bright blue sky with drifting white clouds; gentle ripples reflect the tree and sky, creating a tranquil, meditative atmosphere.", - image_path=str(DATASET_DIR / "image.jpg"), - action_path=str(DATASET_DIR), - output_path=str(OUTPUT_PATH), - save_video=True, - height=480, - width=832, - num_frames=65, - num_inference_steps=4, - guidance_scale=1.0, - negative_prompt="", - fps=16, - seed=42, - ) + "negative_prompt": "", + "inputs": { + "image_path": str(DATASET_DIR / "image.jpg"), + "action_path": str(DATASET_DIR), + }, + "sampling": { + "height": 480, + "width": 832, + "num_frames": 65, + "num_inference_steps": 4, + "guidance_scale": 1.0, + "fps": 16, + "seed": 42, + }, + "output": {"output_path": str(OUTPUT_PATH), "save_video": True}, + }) finally: generator.shutdown() diff --git a/examples/inference/basic/basic_lingbotworld_base_cam.py b/examples/inference/basic/basic_lingbotworld_base_cam.py index 2be36e022..7f586ab79 100644 --- a/examples/inference/basic/basic_lingbotworld_base_cam.py +++ b/examples/inference/basic/basic_lingbotworld_base_cam.py @@ -35,16 +35,12 @@ def main(): spatial_scale=8, ) - generator.generate_video( - prompt, - image_path=image_path, - output_path=OUTPUT_PATH, - save_video=True, - num_frames=num_frames, - height=480, - width=832, - c2ws_plucker_emb=c2ws_plucker_emb, - ) + generator.generate({ + "prompt": prompt, + "inputs": {"image_path": image_path, "c2ws_plucker_emb": c2ws_plucker_emb}, + "sampling": {"num_frames": num_frames, "height": 480, "width": 832}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_longcat_i2v.py b/examples/inference/basic/basic_longcat_i2v.py index 001885247..11715df8a 100644 --- a/examples/inference/basic/basic_longcat_i2v.py +++ b/examples/inference/basic/basic_longcat_i2v.py @@ -48,33 +48,38 @@ def basic_generation(): print("LongCat I2V: Basic Generation (50 steps, 480p)") print("=" * 60) - generator = VideoGenerator.from_pretrained( - "FastVideo/LongCat-Video-I2V-Diffusers", - num_gpus=1, - use_fsdp_inference=False, # set to True if GPU is out of memory - dit_cpu_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=False, - enable_bsa=False, - ) + generator = VideoGenerator.from_config({ + "model_path": "FastVideo/LongCat-Video-I2V-Diffusers", + "engine": { + "num_gpus": 1, + "use_fsdp_inference": False, # set to True if GPU is out of memory + "offload": { + "dit": False, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": False, + }, + }, + "pipeline": {"longcat": {"enable_bsa": False}}, + }) output_path = "outputs_video/longcat_i2v_basic" - generator.generate_video( - prompt=PROMPT, - negative_prompt=NEGATIVE_PROMPT, - image_path=IMAGE_PATH, - output_path=output_path, - save_video=True, - height=480, - width=480, # Square - num_frames=93, - num_inference_steps=50, - fps=15, - guidance_scale=4.0, - seed=SEED, - ) + generator.generate({ + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "inputs": {"image_path": IMAGE_PATH}, + "sampling": { + "height": 480, + "width": 480, # Square + "num_frames": 93, + "num_inference_steps": 50, + "fps": 15, + "guidance_scale": 4.0, + "seed": SEED, + }, + "output": {"output_path": output_path, "save_video": True}, + }) print(f"\nBasic generation complete! Video saved to: {output_path}") generator.shutdown() @@ -95,35 +100,44 @@ def distill_refine_generation(): print("\n[Stage 1] Distilled generation (16 steps, 480p)") print("-" * 40) - generator = VideoGenerator.from_pretrained( - "FastVideo/LongCat-Video-I2V-Diffusers", - num_gpus=1, - use_fsdp_inference=True, - dit_cpu_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=False, - enable_bsa=False, - lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA", - lora_nickname="distilled", - ) + generator = VideoGenerator.from_config({ + "model_path": "FastVideo/LongCat-Video-I2V-Diffusers", + "engine": { + "num_gpus": 1, + "use_fsdp_inference": True, + "offload": { + "dit": False, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": False, + }, + }, + "pipeline": { + "longcat": {"enable_bsa": False}, + "components": { + "lora_path": "FastVideo/LongCat-Video-T2V-Distilled-LoRA", + "lora_nickname": "distilled", + }, + }, + }) distill_output_path = "outputs_video/longcat_i2v_distill" - generator.generate_video( - prompt=PROMPT, - negative_prompt=NEGATIVE_PROMPT, - image_path=IMAGE_PATH, - output_path=distill_output_path, - save_video=True, - height=480, - width=480, # Square - num_frames=93, - num_inference_steps=16, - fps=15, - guidance_scale=1.0, - seed=SEED, - ) + generator.generate({ + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "inputs": {"image_path": IMAGE_PATH}, + "sampling": { + "height": 480, + "width": 480, # Square + "num_frames": 93, + "num_inference_steps": 16, + "fps": 15, + "guidance_scale": 1.0, + "seed": SEED, + }, + "output": {"output_path": distill_output_path, "save_video": True}, + }) print(f"Distilled generation complete! Video saved to: {distill_output_path}") generator.shutdown() @@ -144,40 +158,53 @@ def distill_refine_generation(): # Note: Refinement uses the T2V model (not I2V) since it's upscaling the generated video # For BSA [4, 4, 8]: latent must be divisible by 8 # 768x768: latent 48x48, 48%8=0 ✓ - refine_generator = VideoGenerator.from_pretrained( - "FastVideo/LongCat-Video-T2V-Diffusers", - num_gpus=1, - use_fsdp_inference=True, - dit_cpu_offload=True, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=False, - enable_bsa=True, - bsa_sparsity=0.875, - bsa_chunk_q=[4, 4, 4], - bsa_chunk_k=[4, 4, 4], - lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA", - lora_nickname="refinement", - ) + refine_generator = VideoGenerator.from_config({ + "model_path": "FastVideo/LongCat-Video-T2V-Diffusers", + "engine": { + "num_gpus": 1, + "use_fsdp_inference": True, + "offload": { + "dit": True, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": False, + }, + }, + "pipeline": { + "longcat": { + "enable_bsa": True, + "bsa_sparsity": 0.875, + "bsa_chunk_q": [4, 4, 4], + "bsa_chunk_k": [4, 4, 4], + }, + "components": { + "lora_path": "FastVideo/LongCat-Video-T2V-Refinement-LoRA", + "lora_nickname": "refinement", + }, + }, + }) refine_output_path = "outputs_video/longcat_i2v_refine_720p" - refine_generator.generate_video( - prompt=PROMPT, - negative_prompt=NEGATIVE_PROMPT, - output_path=refine_output_path, - save_video=True, - refine_from=distill_video_path, - t_thresh=0.5, - spatial_refine_only=False, - num_cond_frames=0, - height=720, - width=720, - num_inference_steps=50, - fps=30, - guidance_scale=1.0, - seed=SEED, - ) + refine_generator.generate({ + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "inputs": {"refine_from": distill_video_path}, + "sampling": { + "height": 720, + "width": 720, + "num_inference_steps": 50, + "fps": 30, + "guidance_scale": 1.0, + "seed": SEED, + }, + "output": {"output_path": refine_output_path, "save_video": True}, + "extensions": { + "t_thresh": 0.5, + "spatial_refine_only": False, + "num_cond_frames": 0, + }, + }) print(f"Refinement complete! Video saved to: {refine_output_path}") refine_generator.shutdown() diff --git a/examples/inference/basic/basic_longcat_t2v.py b/examples/inference/basic/basic_longcat_t2v.py index 58c87dc3a..669a13a3e 100644 --- a/examples/inference/basic/basic_longcat_t2v.py +++ b/examples/inference/basic/basic_longcat_t2v.py @@ -41,32 +41,37 @@ def basic_generation(): print("LongCat T2V: Basic Generation (50 steps, 480p)") print("=" * 60) - generator = VideoGenerator.from_pretrained( - "FastVideo/LongCat-Video-T2V-Diffusers", - num_gpus=1, - use_fsdp_inference=False, # set to True if GPU is out of memory - dit_cpu_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=False, - enable_bsa=False, - ) + generator = VideoGenerator.from_config({ + "model_path": "FastVideo/LongCat-Video-T2V-Diffusers", + "engine": { + "num_gpus": 1, + "use_fsdp_inference": False, # set to True if GPU is out of memory + "offload": { + "dit": False, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": False, + }, + }, + "pipeline": {"longcat": {"enable_bsa": False}}, + }) output_path = "outputs_video/longcat_t2v_basic" - generator.generate_video( - prompt=PROMPT, - negative_prompt=NEGATIVE_PROMPT, - output_path=output_path, - save_video=True, - height=480, - width=832, - num_frames=93, - num_inference_steps=50, - fps=15, - guidance_scale=4.0, - seed=SEED, - ) + generator.generate({ + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "sampling": { + "height": 480, + "width": 832, + "num_frames": 93, + "num_inference_steps": 50, + "fps": 15, + "guidance_scale": 4.0, + "seed": SEED, + }, + "output": {"output_path": output_path, "save_video": True}, + }) print(f"\nBasic generation complete! Video saved to: {output_path}") generator.shutdown() @@ -87,34 +92,43 @@ def distill_refine_generation(): print("\n[Stage 1] Distilled generation (16 steps, 480p)") print("-" * 40) - generator = VideoGenerator.from_pretrained( - "FastVideo/LongCat-Video-T2V-Diffusers", - num_gpus=1, - use_fsdp_inference=True, - dit_cpu_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=False, - enable_bsa=False, - lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA", - lora_nickname="distilled", - ) + generator = VideoGenerator.from_config({ + "model_path": "FastVideo/LongCat-Video-T2V-Diffusers", + "engine": { + "num_gpus": 1, + "use_fsdp_inference": True, + "offload": { + "dit": False, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": False, + }, + }, + "pipeline": { + "longcat": {"enable_bsa": False}, + "components": { + "lora_path": "FastVideo/LongCat-Video-T2V-Distilled-LoRA", + "lora_nickname": "distilled", + }, + }, + }) distill_output_path = "outputs_video/longcat_t2v_distill" - generator.generate_video( - prompt=PROMPT, - negative_prompt=NEGATIVE_PROMPT, - output_path=distill_output_path, - save_video=True, - height=480, - width=832, - num_frames=93, - num_inference_steps=16, - fps=15, - guidance_scale=1.0, - seed=SEED, - ) + generator.generate({ + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "sampling": { + "height": 480, + "width": 832, + "num_frames": 93, + "num_inference_steps": 16, + "fps": 15, + "guidance_scale": 1.0, + "seed": SEED, + }, + "output": {"output_path": distill_output_path, "save_video": True}, + }) print(f"Distilled generation complete! Video saved to: {distill_output_path}") generator.shutdown() @@ -132,40 +146,53 @@ def distill_refine_generation(): print(f"Using stage 1 video: {distill_video_path}") # Create a new generator with refinement LoRA and BSA enabled - refine_generator = VideoGenerator.from_pretrained( - "FastVideo/LongCat-Video-T2V-Diffusers", - num_gpus=1, - use_fsdp_inference=True, - dit_cpu_offload=True, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=False, - enable_bsa=True, - bsa_sparsity=0.875, - bsa_chunk_q=[4, 4, 8], - bsa_chunk_k=[4, 4, 8], - lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA", - lora_nickname="refinement", - ) + refine_generator = VideoGenerator.from_config({ + "model_path": "FastVideo/LongCat-Video-T2V-Diffusers", + "engine": { + "num_gpus": 1, + "use_fsdp_inference": True, + "offload": { + "dit": True, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": False, + }, + }, + "pipeline": { + "longcat": { + "enable_bsa": True, + "bsa_sparsity": 0.875, + "bsa_chunk_q": [4, 4, 8], + "bsa_chunk_k": [4, 4, 8], + }, + "components": { + "lora_path": "FastVideo/LongCat-Video-T2V-Refinement-LoRA", + "lora_nickname": "refinement", + }, + }, + }) refine_output_path = "outputs_video/longcat_t2v_refine_720p" - refine_generator.generate_video( - prompt=PROMPT, - negative_prompt=NEGATIVE_PROMPT, - output_path=refine_output_path, - save_video=True, - refine_from=distill_video_path, - t_thresh=0.5, - spatial_refine_only=False, - num_cond_frames=0, - height=720, - width=1280, - num_inference_steps=50, - fps=30, - guidance_scale=1.0, - seed=SEED, - ) + refine_generator.generate({ + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "inputs": {"refine_from": distill_video_path}, + "sampling": { + "height": 720, + "width": 1280, + "num_inference_steps": 50, + "fps": 30, + "guidance_scale": 1.0, + "seed": SEED, + }, + "output": {"output_path": refine_output_path, "save_video": True}, + "extensions": { + "t_thresh": 0.5, + "spatial_refine_only": False, + "num_cond_frames": 0, + }, + }) print(f"Refinement complete! Video saved to: {refine_output_path}") refine_generator.shutdown() diff --git a/examples/inference/basic/basic_longcat_vc.py b/examples/inference/basic/basic_longcat_vc.py index 36d5f0935..0d92d9e71 100644 --- a/examples/inference/basic/basic_longcat_vc.py +++ b/examples/inference/basic/basic_longcat_vc.py @@ -57,34 +57,39 @@ def basic_generation(): raise FileNotFoundError(f"Video not found at {VIDEO_PATH}. " "Please provide a valid video path.") - generator = VideoGenerator.from_pretrained( - "FastVideo/LongCat-Video-VC-Diffusers", - num_gpus=1, - use_fsdp_inference=False, # set to True if GPU is out of memory - dit_cpu_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=False, - enable_bsa=False, - ) + generator = VideoGenerator.from_config({ + "model_path": "FastVideo/LongCat-Video-VC-Diffusers", + "engine": { + "num_gpus": 1, + "use_fsdp_inference": False, # set to True if GPU is out of memory + "offload": { + "dit": False, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": False, + }, + }, + "pipeline": {"longcat": {"enable_bsa": False}}, + }) output_path = "outputs_video/longcat_vc_basic" - generator.generate_video( - prompt=PROMPT, - negative_prompt=NEGATIVE_PROMPT, - video_path=VIDEO_PATH, - num_cond_frames=NUM_COND_FRAMES, - output_path=output_path, - save_video=True, - height=480, - width=832, - num_frames=93, - num_inference_steps=50, - fps=15, - guidance_scale=4.0, - seed=SEED, - ) + generator.generate({ + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "inputs": {"video_path": VIDEO_PATH}, + "sampling": { + "height": 480, + "width": 832, + "num_frames": 93, + "num_inference_steps": 50, + "fps": 15, + "guidance_scale": 4.0, + "seed": SEED, + }, + "output": {"output_path": output_path, "save_video": True}, + "extensions": {"num_cond_frames": NUM_COND_FRAMES}, + }) print(f"\nBasic generation complete! Video saved to: {output_path}") generator.shutdown() @@ -110,36 +115,45 @@ def distill_refine_generation(): print("\n[Stage 1] Distilled generation (16 steps, 480p)") print("-" * 40) - generator = VideoGenerator.from_pretrained( - "FastVideo/LongCat-Video-VC-Diffusers", - num_gpus=1, - use_fsdp_inference=True, - dit_cpu_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=False, - enable_bsa=False, - lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA", - lora_nickname="distilled", - ) + generator = VideoGenerator.from_config({ + "model_path": "FastVideo/LongCat-Video-VC-Diffusers", + "engine": { + "num_gpus": 1, + "use_fsdp_inference": True, + "offload": { + "dit": False, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": False, + }, + }, + "pipeline": { + "longcat": {"enable_bsa": False}, + "components": { + "lora_path": "FastVideo/LongCat-Video-T2V-Distilled-LoRA", + "lora_nickname": "distilled", + }, + }, + }) distill_output_path = "outputs_video/longcat_vc_distill" - generator.generate_video( - prompt=PROMPT, - negative_prompt=NEGATIVE_PROMPT, - video_path=VIDEO_PATH, - num_cond_frames=NUM_COND_FRAMES, - output_path=distill_output_path, - save_video=True, - height=480, - width=832, - num_frames=93, - num_inference_steps=16, - fps=15, - guidance_scale=1.0, - seed=SEED, - ) + generator.generate({ + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "inputs": {"video_path": VIDEO_PATH}, + "sampling": { + "height": 480, + "width": 832, + "num_frames": 93, + "num_inference_steps": 16, + "fps": 15, + "guidance_scale": 1.0, + "seed": SEED, + }, + "output": {"output_path": distill_output_path, "save_video": True}, + "extensions": {"num_cond_frames": NUM_COND_FRAMES}, + }) print(f"Distilled generation complete! Video saved to: {distill_output_path}") generator.shutdown() @@ -158,40 +172,53 @@ def distill_refine_generation(): # Create a new generator with refinement LoRA and BSA enabled # Note: Refinement uses the T2V model (not VC) since it's upscaling the generated video - refine_generator = VideoGenerator.from_pretrained( - "FastVideo/LongCat-Video-T2V-Diffusers", - num_gpus=1, - use_fsdp_inference=True, - dit_cpu_offload=True, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=False, - enable_bsa=True, - bsa_sparsity=0.875, - bsa_chunk_q=[4, 4, 8], - bsa_chunk_k=[4, 4, 8], - lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA", - lora_nickname="refinement", - ) + refine_generator = VideoGenerator.from_config({ + "model_path": "FastVideo/LongCat-Video-T2V-Diffusers", + "engine": { + "num_gpus": 1, + "use_fsdp_inference": True, + "offload": { + "dit": True, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": False, + }, + }, + "pipeline": { + "longcat": { + "enable_bsa": True, + "bsa_sparsity": 0.875, + "bsa_chunk_q": [4, 4, 8], + "bsa_chunk_k": [4, 4, 8], + }, + "components": { + "lora_path": "FastVideo/LongCat-Video-T2V-Refinement-LoRA", + "lora_nickname": "refinement", + }, + }, + }) refine_output_path = "outputs_video/longcat_vc_refine_720p" - refine_generator.generate_video( - prompt=PROMPT, - negative_prompt=NEGATIVE_PROMPT, - output_path=refine_output_path, - save_video=True, - refine_from=distill_video_path, - t_thresh=0.5, - spatial_refine_only=False, - num_cond_frames=0, # For refinement, no conditioning frames - height=720, - width=1280, - num_inference_steps=50, - fps=30, - guidance_scale=1.0, - seed=SEED, - ) + refine_generator.generate({ + "prompt": PROMPT, + "negative_prompt": NEGATIVE_PROMPT, + "inputs": {"refine_from": distill_video_path}, + "sampling": { + "height": 720, + "width": 1280, + "num_inference_steps": 50, + "fps": 30, + "guidance_scale": 1.0, + "seed": SEED, + }, + "output": {"output_path": refine_output_path, "save_video": True}, + "extensions": { + "t_thresh": 0.5, + "spatial_refine_only": False, + "num_cond_frames": 0, # For refinement, no conditioning frames + }, + }) print(f"Refinement complete! Video saved to: {refine_output_path}") refine_generator.shutdown() diff --git a/examples/inference/basic/basic_ltx2.py b/examples/inference/basic/basic_ltx2.py index e7e1dea00..b1da6b0ec 100644 --- a/examples/inference/basic/basic_ltx2.py +++ b/examples/inference/basic/basic_ltx2.py @@ -21,14 +21,11 @@ def main() -> None: ) output_path = "outputs_video/ltx2_basic/output_ltx2_base_t2v_1088_1920_1.1.mp4" - generator.generate_video( - prompt=PROMPT, - output_path=output_path, - save_video=True, - num_frames=121, - height=1088, - width=1920, - ) + generator.generate({ + "prompt": PROMPT, + "sampling": {"num_frames": 121, "height": 1088, "width": 1920}, + "output": {"output_path": output_path, "save_video": True}, + }) generator.shutdown() diff --git a/examples/inference/basic/basic_ltx2_3_distilled_i2v.py b/examples/inference/basic/basic_ltx2_3_distilled_i2v.py index 8026bf2cb..8d3058dd0 100644 --- a/examples/inference/basic/basic_ltx2_3_distilled_i2v.py +++ b/examples/inference/basic/basic_ltx2_3_distilled_i2v.py @@ -49,6 +49,7 @@ from pathlib import Path import torch._inductor.config as _inductor from fastvideo import VideoGenerator +from fastvideo.api import GenerationResult from fastvideo.configs.pipelines.base import PipelineConfig from fastvideo.utils import maybe_download_model @@ -80,9 +81,9 @@ PROMPT = os.getenv("LTX23_I2V_PROMPT", DEFAULT_PROMPT) # Per-stage timing helpers -------------------------------------------------- -def _print_stage_breakdown(result: dict, label: str) -> float | None: +def _print_stage_breakdown(result: GenerationResult, label: str) -> float | None: """Print stage execution times and return the sum, or None if missing.""" - logging_info = result.get("logging_info") + logging_info = result.logging_info stages = getattr(logging_info, "stages", None) if logging_info else None if not stages: print(f" [{label}] stage breakdown unavailable") @@ -98,11 +99,11 @@ def _print_stage_breakdown(result: dict, label: str) -> float | None: def _collect_stage_times( - result: dict, + result: GenerationResult, stage_times: dict[str, list[float]], stage_order: OrderedDict[str, None], ) -> None: - logging_info = result.get("logging_info") + logging_info = result.logging_info stages = getattr(logging_info, "stages", None) if logging_info else None if not stages: return @@ -158,52 +159,68 @@ def main() -> None: pipeline_config = PipelineConfig.from_pretrained(model_root) pipeline_config.dit_config.quant_config = None - generator = VideoGenerator.from_pretrained( - model_root, - num_gpus=1, - # LTX-2.3 distilled uses the two-stage refine pipeline; the refine - # LoRA is intentionally empty for the distilled student. - ltx2_refine_enabled=True, - ltx2_refine_upsampler_path=str(refine_upsampler_path), - ltx2_refine_lora_path="", - ltx2_refine_num_inference_steps=3, - ltx2_refine_guidance_scale=1.0, - ltx2_refine_add_noise=True, - pipeline_config=pipeline_config, - enable_torch_compile=True, - enable_torch_compile_text_encoder=True, - # Compile the VAE codec submodules (encoder / decoder) too. The - # `LTX2CausalVideoAutoencoder` declares `_compile_conditions` so - # `_compile_with_conditions` targets just those submodules and - # leaves the surrounding tiling control flow eager — needed for - # fullgraph + dynamic=False to succeed. VAE eager decode is - # ~1.0s; compiling it brings the stage to ~0.3s. - enable_torch_compile_vae=True, - torch_compile_kwargs=torch_compile_kwargs, - torch_compile_kwargs_vae=torch_compile_kwargs, - # Keep everything resident — no CPU offload for serving-style runs. - dit_cpu_offload=False, - text_encoder_cpu_offload=False, - vae_cpu_offload=False, - ltx2_vae_tiling=False, - ) + generator = VideoGenerator.from_config({ + "model_path": model_root, + "engine": { + "num_gpus": 1, + "compile": { + "enabled": True, + "text_encoder_enabled": True, + # Compile the VAE codec submodules (encoder / decoder) too. The + # `LTX2CausalVideoAutoencoder` declares `_compile_conditions` so + # `_compile_with_conditions` targets just those submodules and + # leaves the surrounding tiling control flow eager — needed for + # fullgraph + dynamic=False to succeed. VAE eager decode is + # ~1.0s; compiling it brings the stage to ~0.3s. + "vae_enabled": True, + **torch_compile_kwargs, + "vae_kwargs": torch_compile_kwargs, + }, + # Keep everything resident — no CPU offload for serving-style runs. + "offload": { + "dit": False, + "text_encoder": False, + "vae": False, + }, + }, + "pipeline": { + # LTX-2.3 distilled uses the two-stage refine pipeline; the refine + # LoRA is intentionally empty for the distilled student. + "preset_overrides": { + "refine": { + "enabled": True, + "num_inference_steps": 3, + "guidance_scale": 1.0, + "add_noise": True, + }, + }, + "components": {"upsampler_weights": str(refine_upsampler_path)}, + "ltx2": {"refine": {"lora_path": ""}}, + "experimental": {"pipeline_config": pipeline_config}, + "vae_tiling": False, + }, + }) - common_kwargs = dict( - prompt=PROMPT, - negative_prompt="", # distilled is CFG-free; no negative needed - guidance_scale=1.0, # CFG=1 for distilled - height=1280, - width=832, # portrait runway aspect - num_frames=121, - fps=24, # ~5s clip - num_inference_steps=8, # distilled denoise steps + common_request = { + "prompt": PROMPT, + "negative_prompt": "", # distilled is CFG-free; no negative needed + "sampling": { + "guidance_scale": 1.0, # CFG=1 for distilled + "height": 1280, + "width": 832, # portrait runway aspect + "num_frames": 121, + "fps": 24, # ~5s clip + "num_inference_steps": 8, # distilled denoise steps + }, + "output": {"save_video": True}, # i2v: anchor the input image at frame 0 with full strength. # `ltx2_image_crf=0.0` skips an extra JPEG re-encode of an already # JPEG conditioning image. - ltx2_images=[(I2V_IMAGE, 0, 1.0)], - ltx2_image_crf=0.0, - save_video=True, - ) + "extensions": { + "ltx2_images": [(I2V_IMAGE, 0, 1.0)], + "ltx2_image_crf": 0.0, + }, + } warmup_runs = 2 measured_runs = 2 @@ -218,11 +235,11 @@ def main() -> None: for w in range(warmup_runs): t0 = time.perf_counter() print(f"\n[warmup {w + 1}/{warmup_runs}] compiling + generating…") - generator.generate_video( - output_path=str(OUTPUT_DIR / f"_warmup_{w + 1}.mp4"), - seed=7, - **common_kwargs, - ) + generator.generate({ + **common_request, + "sampling": {**common_request["sampling"], "seed": 7}, + "output": {**common_request["output"], "output_path": str(OUTPUT_DIR / f"_warmup_{w + 1}.mp4")}, + }) dt = time.perf_counter() - t0 warmup_secs.append(dt) print(f"[warmup {w + 1}/{warmup_runs}] wall={dt:.1f}s") @@ -236,16 +253,16 @@ def main() -> None: out_path = OUTPUT_DIR / f"output_ltx2_3_distilled_i2v_run_{m + 1}.mp4" print(f"\n[measured {m + 1}/{measured_runs}] generating: {out_path}") t0 = time.perf_counter() - result = generator.generate_video( - output_path=str(out_path), - seed=2002 + m, - **common_kwargs, - ) + result = generator.generate({ + **common_request, + "sampling": {**common_request["sampling"], "seed": 2002 + m}, + "output": {**common_request["output"], "output_path": str(out_path)}, + }) wall = time.perf_counter() - t0 - e2e = (result.get("e2e_latency") if isinstance(result, dict) else None) or wall + e2e = (result.extra.get("e2e_latency") if isinstance(result, GenerationResult) else None) or wall measured_secs.append(e2e) print(f"[measured {m + 1}/{measured_runs}] e2e={e2e:.2f}s wall={wall:.2f}s") - if isinstance(result, dict): + if isinstance(result, GenerationResult): _print_stage_breakdown(result, f"measured {m + 1}") _collect_stage_times(result, stage_times, stage_order) diff --git a/examples/inference/basic/basic_ltx2_distilled.py b/examples/inference/basic/basic_ltx2_distilled.py index 1eb1f3c1d..817522b14 100644 --- a/examples/inference/basic/basic_ltx2_distilled.py +++ b/examples/inference/basic/basic_ltx2_distilled.py @@ -23,11 +23,10 @@ def main() -> None: ) output_path = "outputs_video/ltx2_basic/output_ltx2_distilled_t2v.mp4" - generator.generate_video( - prompt=PROMPT, - output_path=output_path, - save_video=True, - ) + generator.generate({ + "prompt": PROMPT, + "output": {"output_path": output_path, "save_video": True}, + }) generator.shutdown() diff --git a/examples/inference/basic/basic_ltx2_distilled_fast_profile.py b/examples/inference/basic/basic_ltx2_distilled_fast_profile.py index b4e6c42b2..961cf80bc 100644 --- a/examples/inference/basic/basic_ltx2_distilled_fast_profile.py +++ b/examples/inference/basic/basic_ltx2_distilled_fast_profile.py @@ -8,6 +8,7 @@ from pathlib import Path import torch import torch._inductor.config from fastvideo import VideoGenerator +from fastvideo.api import GenerationResult from fastvideo.configs.pipelines.base import PipelineConfig from fastvideo.layers.quantization.nvfp4_config import NVFP4Config from fastvideo.utils import maybe_download_model @@ -41,11 +42,11 @@ def load_validation_entries(path: Path) -> list[dict]: def print_stage_breakdown( - result: dict, + result: GenerationResult, run_idx: int, num_runs: int, ) -> float | None: - logging_info = result.get("logging_info") + logging_info = result.logging_info if logging_info is None: print(f"[{run_idx}/{num_runs}] Stage breakdown unavailable: no logging_info") return None @@ -65,8 +66,8 @@ def print_stage_breakdown( return total -def extract_sr_forward_latency(result: dict, ) -> tuple[float | None, list[tuple[str, float]], list[str]]: - logging_info = result.get("logging_info") +def extract_sr_forward_latency(result: GenerationResult, ) -> tuple[float | None, list[tuple[str, float]], list[str]]: + logging_info = result.logging_info if logging_info is None: return None, [], [] @@ -96,11 +97,11 @@ def extract_sr_forward_latency(result: dict, ) -> tuple[float | None, list[tuple def collect_stage_times( - result: dict, + result: GenerationResult, stage_times: dict[str, list[float]], stage_order: OrderedDict[str, None], ) -> None: - logging_info = result.get("logging_info") + logging_info = result.logging_info if logging_info is None: return stages = getattr(logging_info, "stages", None) @@ -206,27 +207,41 @@ def main() -> None: "dynamic": False, } - generator = VideoGenerator.from_pretrained( - model_root, - num_gpus=1, - ltx2_refine_enabled=True, - ltx2_refine_upsampler_path=str(refine_upsampler_path), - refine_lora_path="", # keep refine LoRA disabled in this repo's typed adapter - ltx2_refine_lora_path="", # keep refine LoRA disabled for distilled model - ltx2_refine_num_inference_steps=2, - ltx2_refine_guidance_scale=1.0, - ltx2_refine_add_noise=True, - pipeline_config=pipeline_config, - enable_torch_compile=True, - enable_torch_compile_text_encoder=True, - enable_torch_compile_vae=True, - torch_compile_kwargs=torch_compile_kwargs, - torch_compile_kwargs_vae=torch_compile_kwargs, - dit_cpu_offload=False, - text_encoder_cpu_offload=False, - vae_cpu_offload=False, - ltx2_vae_tiling=False, - ) + generator = VideoGenerator.from_config({ + "model_path": model_root, + "engine": { + "num_gpus": 1, + "compile": { + "enabled": True, + "text_encoder_enabled": True, + "vae_enabled": True, + **torch_compile_kwargs, + "vae_kwargs": torch_compile_kwargs, + }, + "offload": { + "dit": False, + "text_encoder": False, + "vae": False, + }, + }, + "pipeline": { + "preset_overrides": { + "refine": { + "enabled": True, + "num_inference_steps": 2, + "guidance_scale": 1.0, + "add_noise": True, + }, + }, + "components": {"upsampler_weights": str(refine_upsampler_path)}, + "ltx2": {"refine": {"lora_path": ""}}, # keep refine LoRA disabled for distilled model + "experimental": { + "refine_lora_path": "", # keep refine LoRA disabled in this repo's typed adapter + "pipeline_config": pipeline_config, + }, + "vae_tiling": False, + }, + }) run_times: list[float] = [] e2e_times: list[float] = [] @@ -247,25 +262,26 @@ def main() -> None: torch.cuda.synchronize() start = time.perf_counter() - result = generator.generate_video( - prompt=prompt, - output_path=str(output_path), - fps=24, - seed=10, - save_video=True, - guidance_scale=1.0, - height=benchmark_entry.get("height", 1088), - width=benchmark_entry.get("width", 1920), - num_frames=121, - num_inference_steps=5, - # image_path="examples/inference/basic/prompt1.png", - # ltx2_image_crf=0.0 - ) + result = generator.generate({ + "prompt": prompt, + "sampling": { + "fps": 24, + "seed": 10, + "guidance_scale": 1.0, + "height": benchmark_entry.get("height", 1088), + "width": benchmark_entry.get("width", 1920), + "num_frames": 121, + "num_inference_steps": 5, + }, + "output": {"output_path": str(output_path), "save_video": True}, + # "inputs": {"image_path": "examples/inference/basic/prompt1.png"}, + # "extensions": {"ltx2_image_crf": 0.0}, + }) if os.environ.get("FASTVIDEO_STAGE_LOGGING") == "0": torch.cuda.synchronize() - elapsed = result.get("generation_time") if isinstance(result, dict) else None - e2e_elapsed = result.get("e2e_latency") if isinstance(result, dict) else None + elapsed = result.generation_time if isinstance(result, GenerationResult) else None + e2e_elapsed = result.extra.get("e2e_latency") if isinstance(result, GenerationResult) else None if elapsed is None: elapsed = time.perf_counter() - start if e2e_elapsed is None: @@ -276,7 +292,7 @@ def main() -> None: print(f"[{i + 1}/{num_runs}] Generation time: {elapsed:.2f}s") print(f"[{i + 1}/{num_runs}] End-to-end latency: {e2e_elapsed:.2f}s") - if isinstance(result, dict): + if isinstance(result, GenerationResult): stage_sum = print_stage_breakdown(result, i + 1, num_runs) if stage_sum is not None: non_stage_overhead = e2e_elapsed - stage_sum diff --git a/examples/inference/basic/basic_lucy_edit.py b/examples/inference/basic/basic_lucy_edit.py index b90db752a..6329c4846 100644 --- a/examples/inference/basic/basic_lucy_edit.py +++ b/examples/inference/basic/basic_lucy_edit.py @@ -20,18 +20,19 @@ def main(): "foam nose; soft window light from left, eye-level medium shot.") video_path = "https://d2drjpuinn46lb.cloudfront.net/painter_original_edit.mp4" - generator.generate_video( - prompt, - negative_prompt="", - video_path=video_path, - output_path=OUTPUT_PATH, - save_video=True, - height=480, - width=832, - num_frames=81, - fps=24, - guidance_scale=5.0, - ) + generator.generate({ + "prompt": prompt, + "negative_prompt": "", + "inputs": {"video_path": video_path}, + "sampling": { + "height": 480, + "width": 832, + "num_frames": 81, + "fps": 24, + "guidance_scale": 5.0, + }, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_matrixgame2.py b/examples/inference/basic/basic_matrixgame2.py index 3ffd39011..bab669d80 100644 --- a/examples/inference/basic/basic_matrixgame2.py +++ b/examples/inference/basic/basic_matrixgame2.py @@ -65,19 +65,22 @@ def main(): actions = create_action_presets(num_frames, keyboard_dim=config["keyboard_dim"]) grid_sizes = torch.tensor([150, 44, 80]) - generator.generate_video( - prompt="", - image_path=config["image_url"], - mouse_cond=actions["mouse"].unsqueeze(0), - keyboard_cond=actions["keyboard"].unsqueeze(0), - grid_sizes=grid_sizes, - num_frames=num_frames, - height=352, - width=640, - num_inference_steps=50, - output_path=OUTPUT_PATH, - save_video=True, - ) + generator.generate({ + "prompt": "", + "inputs": { + "image_path": config["image_url"], + "mouse_cond": actions["mouse"].unsqueeze(0), + "keyboard_cond": actions["keyboard"].unsqueeze(0), + "grid_sizes": grid_sizes, + }, + "sampling": { + "num_frames": num_frames, + "height": 352, + "width": 640, + "num_inference_steps": 50, + }, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_matrixgame3.py b/examples/inference/basic/basic_matrixgame3.py index 37783fdab..e3d8fa2f7 100644 --- a/examples/inference/basic/basic_matrixgame3.py +++ b/examples/inference/basic/basic_matrixgame3.py @@ -17,18 +17,19 @@ def main(): pin_cpu_memory=True, ) - generator.generate_video( - prompt=PROMPT, - image_path=IMAGE_URL, - height=720, - width=1280, - num_frames=57, - num_inference_steps=3, - guidance_scale=1.0, - seed=42, - output_path=OUTPUT_PATH, - save_video=True, - ) + generator.generate({ + "prompt": PROMPT, + "inputs": {"image_path": IMAGE_URL}, + "sampling": { + "height": 720, + "width": 1280, + "num_frames": 57, + "num_inference_steps": 3, + "guidance_scale": 1.0, + "seed": 42, + }, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_mmaudio.py b/examples/inference/basic/basic_mmaudio.py index 11804102d..a5a278aec 100644 --- a/examples/inference/basic/basic_mmaudio.py +++ b/examples/inference/basic/basic_mmaudio.py @@ -19,24 +19,22 @@ def parse_args() -> argparse.Namespace: def main() -> None: args = parse_args() - generator = VideoGenerator.from_pretrained( - os.environ.get( + generator = VideoGenerator.from_config({ + "model_path": os.environ.get( "MMAUDIO_MODEL_PATH", "converted_weights/mmaudio/large_44k_v2", ), - workload_type="v2a", - num_gpus=1, - ) - result = generator.generate_video( - prompt=args.prompt, - negative_prompt=args.negative_prompt, - video_path=args.video_path, - audio_end_in_s=args.duration_seconds, - output_path=args.output_path, - save_video=True, - return_frames=False, - ) - print(result["video_path"]) + "engine": {"num_gpus": 1}, + "pipeline": {"workload_type": "v2a"}, + }) + result = generator.generate({ + "prompt": args.prompt, + "negative_prompt": args.negative_prompt, + "inputs": {"video_path": args.video_path}, + "output": {"output_path": args.output_path, "save_video": True, "return_frames": False}, + "extensions": {"audio_end_in_s": args.duration_seconds}, + }) + print(result.video_path) generator.shutdown() diff --git a/examples/inference/basic/basic_mps.py b/examples/inference/basic/basic_mps.py index f1d9c214d..a39238fc6 100644 --- a/examples/inference/basic/basic_mps.py +++ b/examples/inference/basic/basic_mps.py @@ -1,33 +1,37 @@ from fastvideo import VideoGenerator, PipelineConfig -from fastvideo.api.sampling_param import SamplingParam def main(): config = PipelineConfig.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers") config.text_encoder_precisions = ["fp16"] - generator = VideoGenerator.from_pretrained( - "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", - pipeline_config=config, - use_fsdp_inference=False, # Disable FSDP for MPS - dit_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, - disable_autocast=False, - num_gpus=1, - ) + generator = VideoGenerator.from_config({ + "model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + "engine": { + "use_fsdp_inference": False, # Disable FSDP for MPS + "offload": { + "dit": True, + "text_encoder": True, + "pin_cpu_memory": True, + }, + "disable_autocast": False, + "num_gpus": 1, + }, + "pipeline": {"experimental": {"pipeline_config": config}}, + }) # Create sampling parameters with reduced number of frames - sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers") - sampling_param.num_frames = 25 # Reduce from default 81 to 25 frames bc we have to use the SDPA attn backend for mps - sampling_param.height = 256 - sampling_param.width = 256 + sampling = { + "num_frames": 25, # Reduce from default 81 to 25 frames bc we have to use the SDPA attn backend for mps + "height": 256, + "width": 256, + } prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - video = generator.generate_video(prompt, sampling_param=sampling_param) + video = generator.generate({"prompt": prompt, "sampling": sampling}) prompt2 = ("A majestic lion strides across the golden savanna, its powerful frame " "glistening under the warm afternoon sun. The tall grass ripples gently in " @@ -35,7 +39,7 @@ def main(): "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") - video2 = generator.generate_video(prompt2, sampling_param=sampling_param) + video2 = generator.generate({"prompt": prompt2, "sampling": sampling}) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_ray.py b/examples/inference/basic/basic_ray.py index 6d72b430c..05f2a558f 100644 --- a/examples/inference/basic/basic_ray.py +++ b/examples/inference/basic/basic_ray.py @@ -27,7 +27,10 @@ def main(): prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True) + video = generator.generate({ + "prompt": prompt, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) # Generate another video with a different prompt, without reloading the # model! @@ -36,7 +39,10 @@ def main(): "the breeze, enhancing the lion's commanding presence. The tone is vibrant, " "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") - video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True) + video2 = generator.generate({ + "prompt": prompt2, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_sd35_t2i.py b/examples/inference/basic/basic_sd35_t2i.py index 0124fc5c2..6cc60eb1b 100644 --- a/examples/inference/basic/basic_sd35_t2i.py +++ b/examples/inference/basic/basic_sd35_t2i.py @@ -89,21 +89,23 @@ def main() -> None: os.makedirs(args.out_dir, exist_ok=True) - init_kwargs = { - "num_gpus": args.num_gpus, - "workload_type": "t2i", - "sp_size": 1, - "tp_size": 1, - "dit_cpu_offload": False, - "dit_layerwise_offload": False, - "text_encoder_cpu_offload": False, - "vae_cpu_offload": False, - "image_encoder_cpu_offload": False, - "pin_cpu_memory": False, - "use_fsdp_inference": False, - } - - generator = VideoGenerator.from_pretrained(model_path=args.model_path, **init_kwargs) + generator = VideoGenerator.from_config({ + "model_path": args.model_path, + "engine": { + "num_gpus": args.num_gpus, + "parallelism": {"sp_size": 1, "tp_size": 1}, + "offload": { + "dit": False, + "dit_layerwise": False, + "text_encoder": False, + "vae": False, + "image_encoder": False, + "pin_cpu_memory": False, + }, + "use_fsdp_inference": False, + }, + "pipeline": {"workload_type": "t2i"}, + }) try: for i, prompt in enumerate(prompts): seed = args.seed + i @@ -114,20 +116,22 @@ def main() -> None: output_path = os.path.join(args.out_dir, f"{filename_base}.png") print(f"[sd35] prompt_idx={i} seed={seed} output_path={output_path}") - generation_kwargs = { - "output_path": output_path, - "height": args.height, - "width": args.width, - "num_frames": 1, - "fps": 1, - "num_inference_steps": args.steps, - "guidance_scale": args.guidance, - "seed": seed, + request = { + "prompt": prompt, "negative_prompt": args.negative, - "save_video": True, + "sampling": { + "height": args.height, + "width": args.width, + "num_frames": 1, + "fps": 1, + "num_inference_steps": args.steps, + "guidance_scale": args.guidance, + "seed": seed, + }, + "output": {"output_path": output_path, "save_video": True}, } - generator.generate_video(prompt, **generation_kwargs) + generator.generate(request) print(f"[sd35] done. outputs written to: {args.out_dir}") finally: diff --git a/examples/inference/basic/basic_self_forcing_causal.py b/examples/inference/basic/basic_self_forcing_causal.py index 3937226ec..1ea839882 100644 --- a/examples/inference/basic/basic_self_forcing_causal.py +++ b/examples/inference/basic/basic_self_forcing_causal.py @@ -1,6 +1,6 @@ import os import time -from fastvideo import VideoGenerator, SamplingParam +from fastvideo import VideoGenerator OUTPUT_PATH = "video_samples_causal" @@ -20,12 +20,13 @@ def main(): dit_cpu_offload=False, ) - sampling_param = SamplingParam.from_pretrained(model_name) - prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, sampling_param=sampling_param) + video = generator.generate({ + "prompt": prompt, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_self_forcing_causal_wan2_2_i2v.py b/examples/inference/basic/basic_self_forcing_causal_wan2_2_i2v.py index e75373b54..0a14ddf84 100644 --- a/examples/inference/basic/basic_self_forcing_causal_wan2_2_i2v.py +++ b/examples/inference/basic/basic_self_forcing_causal_wan2_2_i2v.py @@ -1,6 +1,6 @@ # NOTE: This is still a work in progress, and the checkpoints are not released yet. -from fastvideo import VideoGenerator, SamplingParam +from fastvideo import VideoGenerator import json # from fastvideo.api.sampling_param import SamplingParam @@ -12,26 +12,24 @@ def main(): # model. # If a local path is provided, FastVideo will make a best effort # attempt to identify the optimal arguments. - generator = VideoGenerator.from_pretrained( - "FastVideo/SFWan2.2-I2V-A14B-Preview-Diffusers", - # FastVideo will automatically handle distributed setup - num_gpus=1, - use_fsdp_inference=False, # set to True if GPU is out of memory - dit_cpu_offload=True, # DiT need to be offloaded for MoE - dit_precision="fp32", - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - dmd_denoising_steps=[1000, 850, 700, 550, 350, 275, 200, 125], - # Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer - pin_cpu_memory=True, - # image_encoder_cpu_offload=False, - ) - - sampling_param = SamplingParam.from_pretrained("FastVideo/SFWan2.2-I2V-A14B-Preview-Diffusers") - sampling_param.num_frames = 81 - sampling_param.width = 832 - sampling_param.height = 480 - sampling_param.seed = 1000 + generator = VideoGenerator.from_config({ + "model_path": "FastVideo/SFWan2.2-I2V-A14B-Preview-Diffusers", + "engine": { + # FastVideo will automatically handle distributed setup + "num_gpus": 1, + "use_fsdp_inference": False, # set to True if GPU is out of memory + "offload": { + "dit": True, # DiT need to be offloaded for MoE + "vae": False, + "text_encoder": True, + # Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer + "pin_cpu_memory": True, + # "image_encoder": False, + }, + "precision": {"dit": "fp32"}, + }, + "pipeline": {"dmd_denoising_steps": [1000, 850, 700, 550, 350, 275, 200, 125]}, + }) with open("assets/prompts/mixkit_i2v.jsonl", "r") as f: prompt_image_pairs = json.load(f) @@ -39,11 +37,12 @@ def main(): for prompt_image_pair in prompt_image_pairs: prompt = prompt_image_pair["prompt"] image_path = prompt_image_pair["image_path"] - _ = generator.generate_video(prompt, - image_path=image_path, - output_path=OUTPUT_PATH, - save_video=True, - sampling_param=sampling_param) + _ = generator.generate({ + "prompt": prompt, + "inputs": {"image_path": image_path}, + "sampling": {"num_frames": 81, "width": 832, "height": 480, "seed": 1000}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_self_forcing_causal_wan2_2_t2v.py b/examples/inference/basic/basic_self_forcing_causal_wan2_2_t2v.py index 508f84ec2..29fd4567a 100644 --- a/examples/inference/basic/basic_self_forcing_causal_wan2_2_t2v.py +++ b/examples/inference/basic/basic_self_forcing_causal_wan2_2_t2v.py @@ -12,24 +12,32 @@ def main(): # model. # If a local path is provided, FastVideo will make a best effort # attempt to identify the optimal arguments. - generator = VideoGenerator.from_pretrained( - "rand0nmr/SFWan2.2-T2V-A14B-Diffusers", - # FastVideo will automatically handle distributed setup - num_gpus=1, - use_fsdp_inference=False, # set to True if GPU is out of memory - dit_cpu_offload=True, # DiT need to be offloaded for MoE - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - dmd_denoising_steps=[1000, 850, 700, 550, 350, 275, 200, 125], - # Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer - pin_cpu_memory=True, - init_weights_from_safetensors= - "/mnt/sharefs/users/hao.zhang/wei/SFwan2.2_distill_self_forcing_release_cfg2/checkpoint-246_weight_only/generator_inference_transformer/", - init_weights_from_safetensors_2= - "/mnt/sharefs/users/hao.zhang/wei/SFwan2.2_distill_self_forcing_release_cfg2/checkpoint-246_weight_only/generator_2_inference_transformer/", - num_frame_per_block=7, - # image_encoder_cpu_offload=False, - ) + generator = VideoGenerator.from_config({ + "model_path": "rand0nmr/SFWan2.2-T2V-A14B-Diffusers", + "engine": { + # FastVideo will automatically handle distributed setup + "num_gpus": 1, + "use_fsdp_inference": False, # set to True if GPU is out of memory + "offload": { + "dit": True, # DiT need to be offloaded for MoE + "vae": False, + "text_encoder": True, + # Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer + "pin_cpu_memory": True, + # "image_encoder": False, + }, + }, + "pipeline": { + "dmd_denoising_steps": [1000, 850, 700, 550, 350, 275, 200, 125], + "components": { + "transformer_weights": + "/mnt/sharefs/users/hao.zhang/wei/SFwan2.2_distill_self_forcing_release_cfg2/checkpoint-246_weight_only/generator_inference_transformer/", + "transformer_2_weights": + "/mnt/sharefs/users/hao.zhang/wei/SFwan2.2_distill_self_forcing_release_cfg2/checkpoint-246_weight_only/generator_2_inference_transformer/", + }, + "experimental": {"num_frame_per_block": 7}, + }, + }) # sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers") # sampling_param.num_frames = 45 @@ -38,7 +46,11 @@ def main(): prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - _ = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, num_frames=81) + _ = generator.generate({ + "prompt": prompt, + "sampling": {"num_frames": 81}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_stable_audio.py b/examples/inference/basic/basic_stable_audio.py index 23008b51b..5142c49e9 100644 --- a/examples/inference/basic/basic_stable_audio.py +++ b/examples/inference/basic/basic_stable_audio.py @@ -61,15 +61,19 @@ def main() -> None: num_gpus=1, ) output_path = "outputs_audio/stable_audio_basic/output_stable_audio.wav" - generator.generate_video( - prompt=PROMPT, - output_path=output_path, - save_video=True, - # 6-second clip; the model max is ~47.5s. - audio_end_in_s=6.0, + generator.generate({ + "prompt": PROMPT, + "output": { + "output_path": output_path, + "save_video": True, + }, + "extensions": { + # 6-second clip; the model max is ~47.5s. + "audio_end_in_s": 6.0, + }, # The registered preset gives 100 steps + CFG=7.0 by default; # override num_inference_steps / guidance_scale here for QA. - ) + }) generator.shutdown() diff --git a/examples/inference/basic/basic_stable_audio_a2a.py b/examples/inference/basic/basic_stable_audio_a2a.py index 7b7dd00e7..fc2e0096c 100644 --- a/examples/inference/basic/basic_stable_audio_a2a.py +++ b/examples/inference/basic/basic_stable_audio_a2a.py @@ -62,14 +62,18 @@ def main() -> None: "FastVideo/stable-audio-open-1.0-Diffusers", num_gpus=1, ) - generator.generate_video( - prompt=PROMPT, - output_path="outputs_audio/stable_audio_a2a/output_a2a.wav", - save_video=True, - audio_end_in_s=6.0, - init_audio=INIT_AUDIO_PATH, - init_audio_strength=INIT_AUDIO_STRENGTH, - ) + generator.generate({ + "prompt": PROMPT, + "output": { + "output_path": "outputs_audio/stable_audio_a2a/output_a2a.wav", + "save_video": True, + }, + "extensions": { + "audio_end_in_s": 6.0, + "init_audio": INIT_AUDIO_PATH, + "init_audio_strength": INIT_AUDIO_STRENGTH, + }, + }) generator.shutdown() diff --git a/examples/inference/basic/basic_stable_audio_inpaint.py b/examples/inference/basic/basic_stable_audio_inpaint.py index 0424edbb1..459192cd1 100644 --- a/examples/inference/basic/basic_stable_audio_inpaint.py +++ b/examples/inference/basic/basic_stable_audio_inpaint.py @@ -67,15 +67,19 @@ def main() -> None: "FastVideo/stable-audio-open-1.0-Diffusers", num_gpus=1, ) - generator.generate_video( - prompt=PROMPT, - output_path="outputs_audio/stable_audio_inpaint/output_inpaint.wav", - save_video=True, - audio_end_in_s=TOTAL_SECONDS, - inpaint_audio=REFERENCE_AUDIO_PATH, - # Tuple form: keep first KEEP_SECONDS, regenerate the rest. - inpaint_mask=(KEEP_SECONDS, TOTAL_SECONDS), - ) + generator.generate({ + "prompt": PROMPT, + "output": { + "output_path": "outputs_audio/stable_audio_inpaint/output_inpaint.wav", + "save_video": True, + }, + "extensions": { + "audio_end_in_s": TOTAL_SECONDS, + "inpaint_audio": REFERENCE_AUDIO_PATH, + # Tuple form: keep first KEEP_SECONDS, regenerate the rest. + "inpaint_mask": (KEEP_SECONDS, TOTAL_SECONDS), + }, + }) generator.shutdown() diff --git a/examples/inference/basic/basic_stable_audio_small.py b/examples/inference/basic/basic_stable_audio_small.py index 6a4306872..94bbd97f8 100644 --- a/examples/inference/basic/basic_stable_audio_small.py +++ b/examples/inference/basic/basic_stable_audio_small.py @@ -38,14 +38,18 @@ def main() -> None: num_gpus=1, ) output_path = "outputs_audio/stable_audio_small/output_stable_audio_small.wav" - generator.generate_video( - prompt=PROMPT, - output_path=output_path, - save_video=True, - # Small variant trains on a ~11.9s window — keep `audio_end_in_s` - # at or below that. - audio_end_in_s=6.0, - ) + generator.generate({ + "prompt": PROMPT, + "output": { + "output_path": output_path, + "save_video": True, + }, + "extensions": { + # Small variant trains on a ~11.9s window — keep `audio_end_in_s` + # at or below that. + "audio_end_in_s": 6.0, + }, + }) generator.shutdown() diff --git a/examples/inference/basic/basic_turbodiffusion.py b/examples/inference/basic/basic_turbodiffusion.py index 6e85a734e..608ad91e4 100644 --- a/examples/inference/basic/basic_turbodiffusion.py +++ b/examples/inference/basic/basic_turbodiffusion.py @@ -26,12 +26,16 @@ def main() -> None: prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - video = generator.generate_video( - prompt, - output_path=OUTPUT_PATH, - save_video=True, - seed=42, - ) + video = generator.generate({ + "prompt": prompt, + "sampling": { + "seed": 42, + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) # Generate another video with a different prompt, without reloading the model! prompt2 = ("A majestic lion strides across the golden savanna, its powerful frame " @@ -39,12 +43,16 @@ def main() -> None: "the breeze, enhancing the lion's commanding presence. The tone is vibrant, " "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") - video2 = generator.generate_video( - prompt2, - output_path=OUTPUT_PATH, - save_video=True, - seed=42, - ) + video2 = generator.generate({ + "prompt": prompt2, + "sampling": { + "seed": 42, + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_turbodiffusion_14b.py b/examples/inference/basic/basic_turbodiffusion_14b.py index ebaa39c3b..0daaaf262 100644 --- a/examples/inference/basic/basic_turbodiffusion_14b.py +++ b/examples/inference/basic/basic_turbodiffusion_14b.py @@ -20,12 +20,16 @@ def main() -> None: prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - video = generator.generate_video( - prompt, - output_path=OUTPUT_PATH, - save_video=True, - seed=42, - ) + video = generator.generate({ + "prompt": prompt, + "sampling": { + "seed": 42, + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) # Generate another video with a different prompt, without reloading the model! prompt2 = ("A majestic lion strides across the golden savanna, its powerful frame " @@ -33,12 +37,16 @@ def main() -> None: "the breeze, enhancing the lion's commanding presence. The tone is vibrant, " "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") - video2 = generator.generate_video( - prompt2, - output_path=OUTPUT_PATH, - save_video=True, - seed=42, - ) + video2 = generator.generate({ + "prompt": prompt2, + "sampling": { + "seed": 42, + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_turbodiffusion_i2v.py b/examples/inference/basic/basic_turbodiffusion_i2v.py index b28092f21..0a874a614 100644 --- a/examples/inference/basic/basic_turbodiffusion_i2v.py +++ b/examples/inference/basic/basic_turbodiffusion_i2v.py @@ -25,13 +25,19 @@ def main() -> None: # Use an example image path image_path = "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG" - video = generator.generate_video( - prompt, - image_path=image_path, - output_path=OUTPUT_PATH, - save_video=True, - seed=42, - ) + video = generator.generate({ + "prompt": prompt, + "inputs": { + "image_path": image_path, + }, + "sampling": { + "seed": 42, + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_wan2_2.py b/examples/inference/basic/basic_wan2_2.py index 02d8d8d38..26366700c 100644 --- a/examples/inference/basic/basic_wan2_2.py +++ b/examples/inference/basic/basic_wan2_2.py @@ -1,7 +1,5 @@ from fastvideo import VideoGenerator -# from fastvideo.api.sampling_param import SamplingParam - OUTPUT_PATH = "video_samples_wan2_2_14B_t2v" @@ -23,20 +21,22 @@ def main(): # image_encoder_cpu_offload=False, ) - # sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers") - # sampling_param.num_frames = 45 - # sampling_param.image_path = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg" # Generate videos with the same simple API, regardless of GPU count prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " "natural light filtering through the petals. Mid-shot, warm and cheerful tones.") - _ = generator.generate_video(prompt, - output_path=OUTPUT_PATH, - save_video=True, - height=720, - width=1280, - num_frames=81) - # video = generator.generate_video(prompt, sampling_param=sampling_param, output_path="wan_t2v_videos/") + _ = generator.generate({ + "prompt": prompt, + "sampling": { + "height": 720, + "width": 1280, + "num_frames": 81, + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) # Generate another video with a different prompt, without reloading the # model! @@ -45,12 +45,18 @@ def main(): "the breeze, enhancing the lion's commanding presence. The tone is vibrant, " "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") - _ = generator.generate_video(prompt2, - output_path=OUTPUT_PATH, - save_video=True, - height=720, - width=1280, - num_frames=81) + _ = generator.generate({ + "prompt": prompt2, + "sampling": { + "height": 720, + "width": 1280, + "num_frames": 81, + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_wan2_2_Fun.py b/examples/inference/basic/basic_wan2_2_Fun.py index c41699a46..9185e4766 100644 --- a/examples/inference/basic/basic_wan2_2_Fun.py +++ b/examples/inference/basic/basic_wan2_2_Fun.py @@ -32,13 +32,19 @@ def main(): image_path = "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset_Wan2_2/v1.0/8.png" control_video_path = "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset_Wan2_2/v1.0/pose.mp4" - video = generator.generate_video(prompt, - negative_prompt=negative_prompt, - image_path=image_path, - video_path=control_video_path, - output_path=OUTPUT_PATH, - output_video_name=OUTPUT_NAME, - save_video=True) + video = generator.generate({ + "prompt": prompt, + "negative_prompt": negative_prompt, + "inputs": { + "image_path": image_path, + "video_path": control_video_path, + }, + "output": { + "output_path": OUTPUT_PATH, + "output_video_name": OUTPUT_NAME, + "save_video": True, + }, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_wan2_2_i2v.py b/examples/inference/basic/basic_wan2_2_i2v.py index a67d57b84..fe7f653ad 100644 --- a/examples/inference/basic/basic_wan2_2_i2v.py +++ b/examples/inference/basic/basic_wan2_2_i2v.py @@ -26,13 +26,21 @@ def main(): prompt = "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds. The cat assumes a naturally relaxed posture, as if savoring the sea breeze and warm sunlight. A close-up shot highlights the feline's intricate details and the refreshing atmosphere of the seaside." image_path = "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG" - video = generator.generate_video(prompt, - image_path=image_path, - output_path=OUTPUT_PATH, - save_video=True, - height=832, - width=480, - num_frames=81) + video = generator.generate({ + "prompt": prompt, + "inputs": { + "image_path": image_path, + }, + "sampling": { + "height": 832, + "width": 480, + "num_frames": 81, + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_wan2_2_ti2v.py b/examples/inference/basic/basic_wan2_2_ti2v.py index c9ca208e4..40b82eaad 100644 --- a/examples/inference/basic/basic_wan2_2_ti2v.py +++ b/examples/inference/basic/basic_wan2_2_ti2v.py @@ -24,7 +24,16 @@ def main(): # I2V is triggered just by passing in an image_path argument prompt = "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds. The cat assumes a naturally relaxed posture, as if savoring the sea breeze and warm sunlight. A close-up shot highlights the feline's intricate details and the refreshing atmosphere of the seaside." image_path = "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG" - video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, image_path=image_path) + video = generator.generate({ + "prompt": prompt, + "inputs": { + "image_path": image_path, + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) # Generate another video with a different prompt, without reloading the # model! @@ -35,7 +44,13 @@ def main(): "the breeze, enhancing the lion's commanding presence. The tone is vibrant, " "embodying the raw energy of the wild. Low angle, steady tracking shot, " "cinematic.") - video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True) + video2 = generator.generate({ + "prompt": prompt2, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) if __name__ == "__main__": diff --git a/examples/inference/eval/all_metrics_demo.py b/examples/inference/eval/all_metrics_demo.py index 0b6eda9c2..d7c7a6c4b 100644 --- a/examples/inference/eval/all_metrics_demo.py +++ b/examples/inference/eval/all_metrics_demo.py @@ -57,15 +57,19 @@ def generate_one_ltx2_video() -> str: "Davids048/LTX2-Base-Diffusers", num_gpus=1, ) - generator.generate_video( - prompt=PROMPT, - output_path=OUTPUT_PATH, - save_video=True, - num_frames=121, # ~5s @ 24 fps — long enough for audio.desync (Synchformer ≥14 segments) - height=480, - width=832, - fps=24, - ) + generator.generate({ + "prompt": PROMPT, + "sampling": { + "num_frames": 121, # ~5s @ 24 fps — long enough for audio.desync (Synchformer ≥14 segments) + "height": 480, + "width": 832, + "fps": 24, + }, + "output": { + "output_path": OUTPUT_PATH, + "save_video": True, + }, + }) generator.shutdown() torch.cuda.empty_cache() return OUTPUT_PATH diff --git a/examples/inference/eval/basic_ltx2_audio_eval.py b/examples/inference/eval/basic_ltx2_audio_eval.py index 3bf314c07..ec9aacd0e 100644 --- a/examples/inference/eval/basic_ltx2_audio_eval.py +++ b/examples/inference/eval/basic_ltx2_audio_eval.py @@ -43,14 +43,18 @@ def main() -> None: ) output_path = "outputs_video/ltx2_audio_eval/output.mp4" - generator.generate_video( - prompt=PROMPT, - output_path=output_path, - save_video=True, - num_frames=121, - height=1088, - width=1920, - ) + generator.generate({ + "prompt": PROMPT, + "sampling": { + "num_frames": 121, + "height": 1088, + "width": 1920, + }, + "output": { + "output_path": output_path, + "save_video": True, + }, + }) generator.shutdown() torch.cuda.empty_cache() diff --git a/examples/inference/eval/basic_ltx2_eval.py b/examples/inference/eval/basic_ltx2_eval.py index 55c2b6503..43cf58774 100644 --- a/examples/inference/eval/basic_ltx2_eval.py +++ b/examples/inference/eval/basic_ltx2_eval.py @@ -62,14 +62,18 @@ def main() -> None: ) output_path = "outputs_video/ltx2_basic/output_ltx2_base_t2v_1088_1920_1.1.mp4" - generator.generate_video( - prompt=PROMPT, - output_path=output_path, - save_video=True, - num_frames=121, - height=1088, - width=1920, - ) + generator.generate({ + "prompt": PROMPT, + "sampling": { + "num_frames": 121, + "height": 1088, + "width": 1920, + }, + "output": { + "output_path": output_path, + "save_video": True, + }, + }) generator.shutdown() # Free residual CUDA memory the generator left behind so the # evaluator can grab the largest possible workspace for AMT/RAFT. diff --git a/examples/inference/eval/bench_physics_iq.py b/examples/inference/eval/bench_physics_iq.py index 1feb2e771..018c198b9 100644 --- a/examples/inference/eval/bench_physics_iq.py +++ b/examples/inference/eval/bench_physics_iq.py @@ -57,14 +57,18 @@ def _generate_videos(rows: list[dict], videos_dir: Path, model: str, num_gpus: i gen = VideoGenerator.from_pretrained(model, num_gpus=num_gpus) try: for row, out_path in todo: - gen.generate_video( - prompt=row["prompt"], - output_path=str(out_path), - save_video=True, - num_frames=num_frames, - height=height, - width=width, - ) + gen.generate({ + "prompt": row["prompt"], + "sampling": { + "num_frames": num_frames, + "height": height, + "width": width, + }, + "output": { + "output_path": str(out_path), + "save_video": True, + }, + }) finally: gen.shutdown() diff --git a/examples/inference/eval/bench_vbench.py b/examples/inference/eval/bench_vbench.py index 777d1c346..c87095cad 100644 --- a/examples/inference/eval/bench_vbench.py +++ b/examples/inference/eval/bench_vbench.py @@ -55,14 +55,18 @@ def _generate_videos(prompts: list[str], videos_dir: Path, model: str, num_gpus: gen = VideoGenerator.from_pretrained(model, num_gpus=num_gpus) try: for prompt, out_path in todo: - gen.generate_video( - prompt=prompt, - output_path=str(out_path), - save_video=True, - num_frames=num_frames, - height=height, - width=width, - ) + gen.generate({ + "prompt": prompt, + "sampling": { + "num_frames": num_frames, + "height": height, + "width": width, + }, + "output": { + "output_path": str(out_path), + "save_video": True, + }, + }) finally: gen.shutdown() diff --git a/examples/inference/eval/eval_ltx2_vbench.py b/examples/inference/eval/eval_ltx2_vbench.py index 8d72e04a2..0a1af5f76 100644 --- a/examples/inference/eval/eval_ltx2_vbench.py +++ b/examples/inference/eval/eval_ltx2_vbench.py @@ -100,14 +100,18 @@ def generate(args: argparse.Namespace) -> Path: generator = VideoGenerator.from_pretrained(args.model, num_gpus=args.num_gpus) try: print(f"[gen] generating to {out}...") - generator.generate_video( - prompt=args.prompt, - output_path=str(out), - save_video=True, - num_frames=args.num_frames, - height=args.height, - width=args.width, - ) + generator.generate({ + "prompt": args.prompt, + "sampling": { + "num_frames": args.num_frames, + "height": args.height, + "width": args.width, + }, + "output": { + "output_path": str(out), + "save_video": True, + }, + }) finally: generator.shutdown() return out diff --git a/examples/inference/gradio/local/README.md b/examples/inference/gradio/local/README.md index f744dbf46..a7e07cc0b 100644 --- a/examples/inference/gradio/local/README.md +++ b/examples/inference/gradio/local/README.md @@ -33,7 +33,7 @@ This demo initializes a `VideoGenerator` with the minimum required arguments for The core functionality is in the `generate_video` function, which: 1. Processes user inputs -2. Uses the FastVideo VideoGenerator from earlier to run inference (`generator.generate_video()`) +2. Uses the FastVideo VideoGenerator from earlier to run inference (`generator.generate()`) ## Gradio Interface diff --git a/examples/inference/gradio/local/gradio_local_demo.py b/examples/inference/gradio/local/gradio_local_demo.py index 3a76aabd5..9dca1044a 100644 --- a/examples/inference/gradio/local/gradio_local_demo.py +++ b/examples/inference/gradio/local/gradio_local_demo.py @@ -134,12 +134,20 @@ def create_gradio_interface(default_params: dict[str, SamplingParam], generators output_dir = "outputs/" os.makedirs(output_dir, exist_ok=True) start_time = time.time() - result = generator.generate_video(prompt=prompt, - sampling_param=params, - save_video=True, - return_frames=False) + result = generator.generate({ + "prompt": prompt, + "negative_prompt": params.negative_prompt, + "sampling": { + "seed": params.seed, + "guidance_scale": params.guidance_scale, + "num_frames": params.num_frames, + "height": params.height, + "width": params.width, + }, + "output": {"save_video": True, "return_frames": False}, + }) inference_time = time.time() - start_time - logging_info = result.get("logging_info", None) + logging_info = result.logging_info if logging_info: stage_names = logging_info.get_execution_order() stage_execution_times = [ diff --git a/examples/inference/gradio/local/gradio_local_demo_ltx2_3/app.py b/examples/inference/gradio/local/gradio_local_demo_ltx2_3/app.py index 32c1242bd..f0a7566d8 100644 --- a/examples/inference/gradio/local/gradio_local_demo_ltx2_3/app.py +++ b/examples/inference/gradio/local/gradio_local_demo_ltx2_3/app.py @@ -48,29 +48,47 @@ def main(): refine_upsampler_path = resolve_refine_upsampler_path(resolved_model_path) print(f"Using refine upsampler: {refine_upsampler_path}") - generators[model_path] = VideoGenerator.from_pretrained( - str(resolved_model_path), - num_gpus=1, - ltx2_refine_enabled=True, - ltx2_refine_upsampler_path=str(refine_upsampler_path), - ltx2_refine_lora_path="", # disable refine LoRA for distilled model - ltx2_refine_num_inference_steps=2, - ltx2_refine_guidance_scale=1.0, - ltx2_refine_add_noise=True, - pipeline_config=pipeline_config, - enable_torch_compile=True, - enable_torch_compile_text_encoder=True, - torch_compile_kwargs={ - "backend": "inductor", - "fullgraph": True, - "mode": "max-autotune-no-cudagraphs", - "dynamic": False, + generators[model_path] = VideoGenerator.from_config({ + "model_path": str(resolved_model_path), + "engine": { + "num_gpus": 1, + "compile": { + "enabled": True, + "text_encoder_enabled": True, + "backend": "inductor", + "fullgraph": True, + "mode": "max-autotune-no-cudagraphs", + "dynamic": False, + }, + "offload": { + "dit": False, + "vae": False, + "text_encoder": False, + }, }, - dit_cpu_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=False, - ltx2_vae_tiling=False, - ) + "pipeline": { + "components": { + "upsampler_weights": str(refine_upsampler_path), + }, + "ltx2": { + "refine": { + "lora_path": "", # disable refine LoRA for distilled model + }, + }, + "preset_overrides": { + "refine": { + "enabled": True, + "num_inference_steps": 2, + "guidance_scale": 1.0, + "add_noise": True, + }, + }, + "vae_tiling": False, + "experimental": { + "pipeline_config": pipeline_config, + }, + }, + }) default_params[model_path] = apply_ltx2_defaults(SamplingParam.from_pretrained(str(resolved_model_path))) demo = create_gradio_interface(default_params, generators) print(f"Starting Gradio frontend at http://{args.host}:{args.port}") diff --git a/examples/inference/gradio/local/gradio_local_demo_ltx2_3/ui.py b/examples/inference/gradio/local/gradio_local_demo_ltx2_3/ui.py index 6b25744ae..7c39a9bde 100644 --- a/examples/inference/gradio/local/gradio_local_demo_ltx2_3/ui.py +++ b/examples/inference/gradio/local/gradio_local_demo_ltx2_3/ui.py @@ -69,29 +69,31 @@ def create_gradio_interface(default_params: dict[str, SamplingParam], generators output_path = str(OUTPUT_DIR / video_filename) params.output_path = output_path start_time = time.perf_counter() - result = generator.generate_video(prompt=prompt, - output_path=output_path, - fps=DEFAULT_FPS, - seed=int(params.seed), - save_video=True, - return_frames=False, - guidance_scale=float(params.guidance_scale), - height=int(params.height), - width=int(params.width), - num_frames=int(params.num_frames), - num_inference_steps=DEFAULT_NUM_INFERENCE_STEPS, - negative_prompt=params.negative_prompt, - image_path=params.image_path, - ltx2_image_crf=0.0) + result = generator.generate({ + "prompt": prompt, + "negative_prompt": params.negative_prompt, + "inputs": {"image_path": params.image_path}, + "sampling": { + "fps": DEFAULT_FPS, + "seed": int(params.seed), + "guidance_scale": float(params.guidance_scale), + "height": int(params.height), + "width": int(params.width), + "num_frames": int(params.num_frames), + "num_inference_steps": DEFAULT_NUM_INFERENCE_STEPS, + }, + "output": {"output_path": output_path, "save_video": True, "return_frames": False}, + "extensions": {"ltx2_image_crf": 0.0}, + }) wall_time = time.perf_counter() - start_time - generation_time = (result.get("generation_time") if isinstance(result, dict) else None) - e2e_latency = (result.get("e2e_latency") if isinstance(result, dict) else None) + generation_time = result.generation_time + e2e_latency = result.extra.get("e2e_latency") if generation_time is None: generation_time = wall_time if e2e_latency is None: e2e_latency = wall_time - resolved_output_path = (result.get("output_path", output_path) if isinstance(result, dict) else output_path) - logging_info = result.get("logging_info", None) if isinstance(result, dict) else None + resolved_output_path = result.extra.get("output_path", output_path) + logging_info = result.logging_info if logging_info: stage_names = logging_info.get_execution_order() stage_execution_times = [ diff --git a/examples/inference/gradio/serving/ray_serve_backend.py b/examples/inference/gradio/serving/ray_serve_backend.py index 4be1518b4..0eec6c986 100644 --- a/examples/inference/gradio/serving/ray_serve_backend.py +++ b/examples/inference/gradio/serving/ray_serve_backend.py @@ -136,10 +136,10 @@ def setup_model_environment(model_path: str) -> None: def process_generation_result(result: Any) -> tuple[List[np.ndarray], float, List[str], List[float]]: - frames = result if isinstance(result, list) else result.get("frames", []) - generation_time = result.get("generation_time", 0.0) if isinstance(result, dict) else 0.0 + frames = result if isinstance(result, list) else result.frames + generation_time = result.generation_time - logging_info = result.get("logging_info", None) + logging_info = result.logging_info if logging_info: stage_names = logging_info.get_execution_order() stage_execution_times = [ @@ -188,18 +188,28 @@ class BaseModelDeployment: from fastvideo.api.sampling_param import SamplingParam print(f"Initializing model: {self.model_path}") - self.generator = VideoGenerator.from_pretrained( - model_path=self.model_path, - num_gpus=1, - use_fsdp_inference=True, - text_encoder_cpu_offload=config["text_encoder_cpu_offload"], - dmd_denoising_steps=[1000, 850, 700, 550, 350, 275, 200, 125], # TODO: hardocde for I2V - dit_precision="fp32", # TODO: hardocde for I2V - dit_cpu_offload=config["dit_cpu_offload"], - vae_cpu_offload=config["vae_cpu_offload"], - VSA_sparsity=config["VSA_sparsity"], - enable_stage_verification=False, - ) + self.generator = VideoGenerator.from_config({ + "model_path": self.model_path, + "engine": { + "num_gpus": 1, + "use_fsdp_inference": True, + "offload": { + "text_encoder": config["text_encoder_cpu_offload"], + "dit": config["dit_cpu_offload"], + "vae": config["vae_cpu_offload"], + }, + "precision": { + "dit": "fp32", # TODO: hardocde for I2V + }, + "attention": { + "vsa_sparsity": config["VSA_sparsity"], + }, + "enable_stage_verification": False, + }, + "pipeline": { + "dmd_denoising_steps": [1000, 850, 700, 550, 350, 275, 200, 125], # TODO: hardocde for I2V + }, + }) self.default_params = SamplingParam.from_pretrained(self.model_path) self.default_params.seed = 1000 self.default_params.num_frames = 73 @@ -224,13 +234,19 @@ class BaseModelDeployment: ) inference_start_time = time.time() - result = self.generator.generate_video( - prompt=video_request.prompt, - sampling_param=params, - image_path=image_path, - save_video=False, - return_frames=True, - ) + result = self.generator.generate({ + "prompt": video_request.prompt, + "negative_prompt": params.negative_prompt, + "inputs": {"image_path": image_path}, + "sampling": { + "seed": params.seed, + "guidance_scale": params.guidance_scale, + "num_frames": params.num_frames, + "height": params.height, + "width": params.width, + }, + "output": {"save_video": False, "return_frames": True}, + }) inference_time = time.time() - inference_start_time frames, generation_time, stage_names, stage_execution_times = process_generation_result(result) diff --git a/examples/inference/lora/wan_lora_inference.py b/examples/inference/lora/wan_lora_inference.py index 1b42be28b..6c6c36dd5 100644 --- a/examples/inference/lora/wan_lora_inference.py +++ b/examples/inference/lora/wan_lora_inference.py @@ -6,15 +6,24 @@ OUTPUT_PATH = "./lora_out" def main(): # Initialize VideoGenerator with the Wan model - generator = VideoGenerator.from_pretrained( - "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", - num_gpus=1, - dit_cpu_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" - lora_path="benjamin-paine/steamboat-willie-1.3b", - lora_nickname="steamboat") + generator = VideoGenerator.from_config({ + "model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + "engine": { + "num_gpus": 1, + "offload": { + "dit": False, + "vae": False, + "text_encoder": True, + "pin_cpu_memory": True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" + }, + }, + "pipeline": { + "components": { + "lora_path": "benjamin-paine/steamboat-willie-1.3b", + "lora_nickname": "steamboat", + }, + }, + }) kwargs = { "height": 480, "width": 832, @@ -27,22 +36,22 @@ def main(): prompt = "steamboat willie style, golden era animation, close-up of a short fluffy monster kneeling beside a melting red candle. the mood is one of wonder and curiosity, as the monster gazes at the flame with wide eyes and open mouth. Its pose and expression convey a sense of innocence and playfulness, as if it is exploring the world around it for the first time. The use of warm colors and dramatic lighting further enhances the cozy atmosphere of the image." negative_prompt = "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" - video = generator.generate_video( - prompt, - # sampling_param=sampling_param, - output_path=OUTPUT_PATH, - save_video=True, - negative_prompt=negative_prompt, - **kwargs) + video = generator.generate({ + "prompt": prompt, + "negative_prompt": negative_prompt, + "sampling": kwargs, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) generator.set_lora_adapter(lora_nickname="flat_color", lora_path="motimalu/wan-flat-color-1.3b-v2") prompt = "flat color, no lineart, blending, negative space, artist:[john kafka|ponsuke kaikai|hara id 21|yoneyama mai|fuzichoco], 1girl, sakura miko, pink hair, cowboy shot, white shirt, floral print, off shoulder, outdoors, cherry blossom, tree shade, wariza, looking up, falling petals, half-closed eyes, white sky, clouds, live2d animation, upper body, high quality cinematic video of a woman sitting under a sakura tree. Dreamy and lonely, the camera close-ups on the face of the woman as she turns towards the viewer. The Camera is steady, This is a cowboy shot. The animation is smooth and fluid." negative_prompt = "bad quality video,色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" - video = generator.generate_video(prompt, - output_path=OUTPUT_PATH, - save_video=True, - negative_prompt=negative_prompt, - **kwargs) + video = generator.generate({ + "prompt": prompt, + "negative_prompt": negative_prompt, + "sampling": kwargs, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/lora/wan_lora_inference_from_ckpt.py b/examples/inference/lora/wan_lora_inference_from_ckpt.py index 99f6bb1c3..a36cf4521 100644 --- a/examples/inference/lora/wan_lora_inference_from_ckpt.py +++ b/examples/inference/lora/wan_lora_inference_from_ckpt.py @@ -9,15 +9,24 @@ OUTPUT_PATH = "./lora_out" def main(): # Initialize VideoGenerator with the Wan model - generator = VideoGenerator.from_pretrained( - "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", - num_gpus=1, - dit_cpu_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" - lora_path="checkpoints/wan_t2v_finetune_lora/checkpoint-160/transformer", - lora_nickname="crush_smol") + generator = VideoGenerator.from_config({ + "model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + "engine": { + "num_gpus": 1, + "offload": { + "dit": False, + "vae": True, + "text_encoder": True, + "pin_cpu_memory": True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" + }, + }, + "pipeline": { + "components": { + "lora_path": "checkpoints/wan_t2v_finetune_lora/checkpoint-160/transformer", + "lora_nickname": "crush_smol", + }, + }, + }) generator.unmerge_lora_weights() kwargs = { "height": 480, @@ -30,9 +39,17 @@ def main(): # Generate video with LoRA style prompt = "A large metal cylinder is seen pressing down on a pile of Oreo cookies, flattening them as if they were under a hydraulic press." - video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, **kwargs) + video = generator.generate({ + "prompt": prompt, + "sampling": kwargs, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) prompt = "A large metal cylinder is seen compressing colorful clay into a compact shape, demonstrating the power of a hydraulic press." - video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, **kwargs) + video = generator.generate({ + "prompt": prompt, + "sampling": kwargs, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__": diff --git a/examples/inference/ltx2_3/optimized_nvfp4_t2v.py b/examples/inference/ltx2_3/optimized_nvfp4_t2v.py index ed5161f42..dca0dd0f9 100644 --- a/examples/inference/ltx2_3/optimized_nvfp4_t2v.py +++ b/examples/inference/ltx2_3/optimized_nvfp4_t2v.py @@ -95,60 +95,70 @@ def main() -> None: "dynamic": False, } - generator = VideoGenerator.from_pretrained( - model_root, - num_gpus=1, - pipeline_config=pipeline_config, - # Compile the DiT, text encoder, and VAE — all three stages benefit, - # and the VAE's codec submodules compile cleanly under fullgraph. - enable_torch_compile=True, - enable_torch_compile_text_encoder=True, - enable_torch_compile_vae=True, - torch_compile_kwargs=torch_compile_kwargs, - torch_compile_kwargs_vae=torch_compile_kwargs, - # Keep everything resident — no CPU offload for serving-style runs. - dit_cpu_offload=False, - text_encoder_cpu_offload=False, - vae_cpu_offload=False, - ltx2_vae_tiling=False, - ) + generator = VideoGenerator.from_config({ + "model_path": model_root, + "engine": { + "num_gpus": 1, + # Compile the DiT, text encoder, and VAE — all three stages benefit, + # and the VAE's codec submodules compile cleanly under fullgraph. + "compile": { + "enabled": True, + "text_encoder_enabled": True, + "vae_enabled": True, + **torch_compile_kwargs, + "vae_kwargs": torch_compile_kwargs, + }, + # Keep everything resident — no CPU offload for serving-style runs. + "offload": { + "dit": False, + "text_encoder": False, + "vae": False, + }, + }, + "pipeline": { + "vae_tiling": False, + "experimental": {"pipeline_config": pipeline_config}, + }, + }) - common_kwargs = dict( - prompt=PROMPT, - negative_prompt="", # distilled is CFG-free; no negative needed - guidance_scale=1.0, # CFG=1 for distilled - height=1280, - width=832, # portrait runway aspect - num_frames=121, - fps=24, # ~5s clip - # Single-stage 8-step distilled sampling — the validated preset for - # this checkpoint (no two-stage refine; the NVFP4 deploy contract - # runs the distilled single-stage recipe). - num_inference_steps=8, - save_video=True, - ) + common_request = { + "prompt": PROMPT, + "negative_prompt": "", # distilled is CFG-free; no negative needed + "sampling": { + "guidance_scale": 1.0, # CFG=1 for distilled + "height": 1280, + "width": 832, # portrait runway aspect + "num_frames": 121, + "fps": 24, # ~5s clip + # Single-stage 8-step distilled sampling — the validated preset for + # this checkpoint (no two-stage refine; the NVFP4 deploy contract + # runs the distilled single-stage recipe). + "num_inference_steps": 8, + }, + "output": {"save_video": True}, + } try: # Warmup: pays cold compile + first-shape guard work, untimed. print("\n[warmup] compiling + generating…") - generator.generate_video( - output_path=str(OUTPUT_DIR / "_warmup.mp4"), - seed=7, - **common_kwargs, - ) + generator.generate({ + **common_request, + "sampling": {**common_request["sampling"], "seed": 7}, + "output": {**common_request["output"], "output_path": str(OUTPUT_DIR / "_warmup.mp4")}, + }) (OUTPUT_DIR / "_warmup.mp4").unlink(missing_ok=True) # Measured run. out_path = OUTPUT_DIR / "output_ltx2_3_nvfp4_t2v.mp4" print(f"\n[measured] generating: {out_path}") t0 = time.perf_counter() - result = generator.generate_video( - output_path=str(out_path), - seed=2002, - **common_kwargs, - ) + result = generator.generate({ + **common_request, + "sampling": {**common_request["sampling"], "seed": 2002}, + "output": {**common_request["output"], "output_path": str(out_path)}, + }) wall = time.perf_counter() - t0 - e2e = (result.get("e2e_latency") if isinstance(result, dict) else None) or wall + e2e = result.extra.get("e2e_latency") or wall print(f"[measured] e2e={e2e:.2f}s wall={wall:.2f}s") finally: generator.shutdown() diff --git a/examples/inference/optimizations/FastWan_QAD_TAEHV.py b/examples/inference/optimizations/FastWan_QAD_TAEHV.py index f7ada6e44..8ac98a886 100644 --- a/examples/inference/optimizations/FastWan_QAD_TAEHV.py +++ b/examples/inference/optimizations/FastWan_QAD_TAEHV.py @@ -141,35 +141,44 @@ def build_generator(args: argparse.Namespace) -> VideoGenerator: compile_enabled = not args.no_compile - extra_kwargs = {} + components = {} + experimental = {"pipeline_config": pipeline_config} if args.distilled_model: weights_path = resolve_distilled_weights(args.distilled_model) print(f"Using distilled weights: {args.distilled_model} -> {weights_path}") - extra_kwargs["init_weights_from_safetensors"] = weights_path + components["transformer_weights"] = weights_path if args.taehv: # Skip the in-pipeline VAE decode entirely: the pipeline returns raw # latents, the Wan VAE is offloaded to CPU (and not compiled) since we # decode with TAEHV in this script instead. - extra_kwargs["output_type"] = "latent" + experimental["output_type"] = "latent" - generator = VideoGenerator.from_pretrained( - model_id, - pipeline_config=pipeline_config, - num_gpus=args.num_gpus, - # Keep everything resident on the GPU -- no offloading, except the - # unused Wan VAE when TAEHV handles decoding. - use_fsdp_inference=False, - dit_cpu_offload=False, - dit_layerwise_offload=False, - vae_cpu_offload=args.taehv, - text_encoder_cpu_offload=False, - pin_cpu_memory=False, - enable_torch_compile=compile_enabled, - enable_torch_compile_text_encoder=compile_enabled, - enable_torch_compile_vae=compile_enabled and not args.taehv, - **extra_kwargs, - ) + generator = VideoGenerator.from_config({ + "model_path": model_id, + "engine": { + "num_gpus": args.num_gpus, + # Keep everything resident on the GPU -- no offloading, except the + # unused Wan VAE when TAEHV handles decoding. + "use_fsdp_inference": False, + "offload": { + "dit": False, + "dit_layerwise": False, + "vae": args.taehv, + "text_encoder": False, + "pin_cpu_memory": False, + }, + "compile": { + "enabled": compile_enabled, + "text_encoder_enabled": compile_enabled, + "vae_enabled": compile_enabled and not args.taehv, + }, + }, + "pipeline": { + "components": components, + "experimental": experimental, + }, + }) return generator diff --git a/examples/inference/optimizations/attention_example.py b/examples/inference/optimizations/attention_example.py index ba8e830a5..ea2a23601 100644 --- a/examples/inference/optimizations/attention_example.py +++ b/examples/inference/optimizations/attention_example.py @@ -22,11 +22,12 @@ def main(): gen_start_time = time.perf_counter() - gen.generate_video( - prompt= + gen.generate({ + "prompt": "Will Smith casually eats noodles, his relaxed demeanor contrasting with the energetic background of a bustling street food market. The scene captures a mix of humor and authenticity. Mid-shot framing, vibrant lighting.", - seed=1024, - output_path="example_outputs/") + "sampling": {"seed": 1024}, + "output": {"output_path": "example_outputs/"}, + }) generation_time = time.perf_counter() - gen_start_time print(f"Video generation time: {generation_time:.2f} seconds") diff --git a/examples/inference/optimizations/fp8_wan2_1_1_3b.py b/examples/inference/optimizations/fp8_wan2_1_1_3b.py index 735aba6de..f664334e9 100644 --- a/examples/inference/optimizations/fp8_wan2_1_1_3b.py +++ b/examples/inference/optimizations/fp8_wan2_1_1_3b.py @@ -81,22 +81,32 @@ def main(): taehv_model = load_taehv(args.taehv_checkpoint) if use_taehv else None # transformer_quant needs a QuantizationConfig *instance* — the bare string - # is not resolved on the from_pretrained kwarg path. + # is not resolved on the pipeline.experimental path. extra = {} if args.bf16 else {"transformer_quant": get_quantization_config("FP8")(granularity=args.granularity)} - generator = VideoGenerator.from_pretrained( - args.model, - num_gpus=args.num_gpus, - use_fsdp_inference=False, - dit_cpu_offload=False, - dit_layerwise_offload=False, - vae_cpu_offload=use_taehv, - text_encoder_cpu_offload=False, - pin_cpu_memory=False, - enable_torch_compile=not args.no_compile, - enable_torch_compile_vae=not args.no_compile and not use_taehv, - output_type="latent" if use_taehv else "pil", - **extra, - ) + generator = VideoGenerator.from_config({ + "model_path": args.model, + "engine": { + "num_gpus": args.num_gpus, + "use_fsdp_inference": False, + "offload": { + "dit": False, + "dit_layerwise": False, + "vae": use_taehv, + "text_encoder": False, + "pin_cpu_memory": False, + }, + "compile": { + "enabled": not args.no_compile, + "vae_enabled": not args.no_compile and not use_taehv, + }, + }, + "pipeline": { + "experimental": { + "output_type": "latent" if use_taehv else "pil", + **extra, + }, + }, + }) prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " diff --git a/examples/inference/optimizations/nvfp4_qat_wan2_1_1_3b.py b/examples/inference/optimizations/nvfp4_qat_wan2_1_1_3b.py index c4bf75061..7b8681550 100644 --- a/examples/inference/optimizations/nvfp4_qat_wan2_1_1_3b.py +++ b/examples/inference/optimizations/nvfp4_qat_wan2_1_1_3b.py @@ -53,18 +53,22 @@ def main(): print(f"Mode: {mode.upper()}") # transformer_quant needs a QuantizationConfig *instance* — the bare string - # is not resolved on the from_pretrained kwarg path. + # is not resolved on the pipeline.experimental path. extra = {} if args.bf16 else {"transformer_quant": get_quantization_config(args.quant_method)()} - generator = VideoGenerator.from_pretrained( - args.model, - num_gpus=args.num_gpus, - use_fsdp_inference=args.bf16, - dit_cpu_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - enable_torch_compile=args.compile, - **extra, - ) + generator = VideoGenerator.from_config({ + "model_path": args.model, + "engine": { + "num_gpus": args.num_gpus, + "use_fsdp_inference": args.bf16, + "offload": { + "dit": False, + "vae": True, + "text_encoder": True, + }, + "compile": {"enabled": args.compile}, + }, + "pipeline": {"experimental": extra}, + }) prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " "wide with interest. The playful yet serene atmosphere is complemented by soft " diff --git a/examples/inference/optimizations/qad_fp4_ab.py b/examples/inference/optimizations/qad_fp4_ab.py index 4d2457609..a28406242 100644 --- a/examples/inference/optimizations/qad_fp4_ab.py +++ b/examples/inference/optimizations/qad_fp4_ab.py @@ -101,27 +101,33 @@ def build_generator(fp4_linear: bool): from fastvideo.layers.quantization.nvfp4_qat_config import NVFP4QATConfig pipeline_config.dit_config.quant_config = NVFP4QATConfig() - extra_kwargs = {} + components = {} distilled = resolve_distilled_weights(_env("QAD_DISTILLED", DEFAULT_DISTILLED)) if distilled: print(f"[qad] distilled weights: {distilled}") - extra_kwargs["init_weights_from_safetensors"] = distilled + components["transformer_weights"] = distilled # Keep everything resident (the 1.3B QAD model + FP4 fits the GB10's unified # memory); real Wan VAE decode for a faithful quality read (no TAEHV). - return VideoGenerator.from_pretrained( - model_id, - pipeline_config=pipeline_config, - num_gpus=1, - use_fsdp_inference=False, - dit_cpu_offload=False, - dit_layerwise_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=False, - pin_cpu_memory=False, - enable_torch_compile=False, # eager: isolate the FP4 effect, no compile noise - **extra_kwargs, - ) + return VideoGenerator.from_config({ + "model_path": model_id, + "engine": { + "num_gpus": 1, + "use_fsdp_inference": False, + "offload": { + "dit": False, + "dit_layerwise": False, + "vae": False, + "text_encoder": False, + "pin_cpu_memory": False, + }, + "compile": {"enabled": False}, # eager: isolate the FP4 effect, no compile noise + }, + "pipeline": { + "components": components, + "experimental": {"pipeline_config": pipeline_config}, + }, + }) def main() -> None: diff --git a/examples/inference/optimizations/spark_benchmark.py b/examples/inference/optimizations/spark_benchmark.py index e048b5e8f..83ebeab19 100644 --- a/examples/inference/optimizations/spark_benchmark.py +++ b/examples/inference/optimizations/spark_benchmark.py @@ -51,7 +51,6 @@ def _p(msg: str) -> None: def bench_generation(args) -> None: """Part A: median few-step generation time on a distilled model.""" from fastvideo import VideoGenerator - from fastvideo.api.sampling_param import SamplingParam # VSA auto-routes to the Triton kernel on sm_121; do NOT force TORCH_SDPA on a # VSA checkpoint (the SDPA path builds a model without the gate weights the @@ -60,27 +59,24 @@ def bench_generation(args) -> None: _p(f"loading {args.model} ...") load_t0 = time.perf_counter() - generator = VideoGenerator.from_pretrained( - args.model, - num_gpus=1, - use_fsdp_inference=False, - # Leave offload at these defaults: "CPU" offload is the same unified RAM - # on the GB10, so the win is tiling + sane resolution, not offloading. - text_encoder_cpu_offload=True, - pin_cpu_memory=True, - dit_cpu_offload=False, - vae_cpu_offload=False, - VSA_sparsity=0.8, - ) + generator = VideoGenerator.from_config({ + "model_path": args.model, + "engine": { + "num_gpus": 1, + "use_fsdp_inference": False, + # Leave offload at these defaults: "CPU" offload is the same unified RAM + # on the GB10, so the win is tiling + sane resolution, not offloading. + "offload": { + "text_encoder": True, + "pin_cpu_memory": True, + "dit": False, + "vae": False, + }, + "attention": {"vsa_sparsity": 0.8}, + }, + }) _p(f"model loaded in {time.perf_counter() - load_t0:.1f}s") - sampling_param = SamplingParam.from_pretrained(args.model) - sampling_param.num_frames = args.frames - sampling_param.height = args.height - sampling_param.width = args.width - sampling_param.num_inference_steps = args.steps - sampling_param.seed = args.seed - prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, " "its eyes wide with interest. Soft natural light, warm cheerful tones, " "mid-shot, cinematic.") @@ -88,17 +84,25 @@ def bench_generation(args) -> None: def _gen(): torch.cuda.synchronize() t0 = time.perf_counter() - video = generator.generate_video(prompt, output_path=args.out, save_video=True, sampling_param=sampling_param) + video = generator.generate({ + "prompt": prompt, + "sampling": { + "num_frames": args.frames, + "height": args.height, + "width": args.width, + "num_inference_steps": args.steps, + "seed": args.seed, + }, + "output": {"output_path": args.out, "save_video": True}, + }) torch.cuda.synchronize() - # generate_video returns a plain dict (legacy result), not an object — - # attribute access would silently fall back to wall time / None. - dt = video.get("generation_time") if isinstance(video, dict) else None + dt = video.generation_time if dt is None: dt = time.perf_counter() - t0 # Peak memory is measured *inside the worker process* that runs the # pipeline and surfaced on the result; reading torch's allocator in this # (main) process would report ~0 because the allocations aren't here. - peak = video.get("peak_memory_mb") if isinstance(video, dict) else None + peak = video.peak_memory_mb return dt, peak for _ in range(args.warmup): diff --git a/examples/inference/optimizations/text_encoder_quant_example.py b/examples/inference/optimizations/text_encoder_quant_example.py index b0e22f233..31e6d59bb 100644 --- a/examples/inference/optimizations/text_encoder_quant_example.py +++ b/examples/inference/optimizations/text_encoder_quant_example.py @@ -10,26 +10,38 @@ def main(text_encoder_path: str): # If a local path is provided, FastVideo will make a best effort # attempt to identify the optimal arguments. model_name = "Wan-AI/Wan2.2-TI2V-5B-Diffusers" - generator = VideoGenerator.from_pretrained( - model_name, - # FastVideo will automatically handle distributed setup - num_gpus=1, - use_fsdp_inference=True, - dit_cpu_offload=True, - vae_cpu_offload=False, - text_encoder_cpu_offload=False, - # AbsMaxFP8 is the quantization method used by ComfyUI; - # check fastvideo/layers/quantization/* for more quantization methods - override_text_encoder_quant="AbsMaxFP8", - # for Wan 2.2, this is the path to "umt5_xxl_fp8_e4m3fn_scaled.safetensors" - override_text_encoder_safetensors=text_encoder_path, - pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" - ) + generator = VideoGenerator.from_config({ + "model_path": model_name, + "engine": { + # FastVideo will automatically handle distributed setup + "num_gpus": 1, + "use_fsdp_inference": True, + "offload": { + "dit": True, + "vae": False, + "text_encoder": False, + "pin_cpu_memory": True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" + }, + # AbsMaxFP8 is the quantization method used by ComfyUI; + # check fastvideo/layers/quantization/* for more quantization methods + "quantization": {"text_encoder_quant": "AbsMaxFP8"}, + }, + "pipeline": { + "components": { + # for Wan 2.2, this is the path to "umt5_xxl_fp8_e4m3fn_scaled.safetensors" + "text_encoder_weights": text_encoder_path, + }, + }, + }) # I2V is triggered just by passing in an image_path argument prompt = "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds. The cat assumes a naturally relaxed posture, as if savoring the sea breeze and warm sunlight. A close-up shot highlights the feline's intricate details and the refreshing atmosphere of the seaside." image_path = "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG" - video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, image_path=image_path) + video = generator.generate({ + "prompt": prompt, + "inputs": {"image_path": image_path}, + "output": {"output_path": OUTPUT_PATH, "save_video": True}, + }) if __name__ == "__main__":