From bbbb7ab0215d65ef8b8976acac77cf36adb51569 Mon Sep 17 00:00:00 2001 From: SolitaryThinker Date: Fri, 26 Jun 2026 12:38:20 -0700 Subject: [PATCH] [refactor]: migrate examples/scripts to typed VideoGenerator API; rename api/compat.py to api/translation.py Move all examples/inference and scripts off the legacy VideoGenerator.from_pretrained(model, **kwargs) + generate_video(prompt, **kwargs) surface onto the typed from_config(GeneratorConfig(...)) + generate(GenerationRequest(...)) convention, including the Kandinsky-5 and DreamX-World examples added on main after the original migration. git-mv api/compat.py to api/translation.py ('compat' implied a temporary shim; the forward translation is its honest permanent role) and repoint all importers, tests, and doc links. --- docs/design/server_contracts/dynamo.md | 2 +- docs/design/server_contracts/openai.md | 2 +- examples/inference/basic/README.md | 11 +- examples/inference/basic/basic.py | 41 +-- .../inference/basic/basic_cosmos2_5_i2w.py | 46 ++-- .../inference/basic/basic_cosmos2_5_t2w.py | 45 ++-- .../inference/basic/basic_cosmos2_5_v2w.py | 49 ++-- examples/inference/basic/basic_dmd.py | 51 ++-- .../inference/basic/basic_dreamx_world.py | 69 +++-- examples/inference/basic/basic_gamecraft.py | 61 +++-- examples/inference/basic/basic_gen3c.py | 70 +++-- examples/inference/basic/basic_hy15.py | 49 +++- examples/inference/basic/basic_hy15_1080p.py | 52 +++- examples/inference/basic/basic_hyworld.py | 60 +++-- .../inference/basic/basic_kandinsky5_i2v.py | 49 ++-- .../inference/basic/basic_kandinsky5_t2v.py | 50 ++-- .../basic/basic_lingbotworld_base_cam.py | 57 ++-- examples/inference/basic/basic_longcat_i2v.py | 235 ++++++++++------- examples/inference/basic/basic_longcat_t2v.py | 246 +++++++++++------- examples/inference/basic/basic_longcat_vc.py | 228 ++++++++++------ examples/inference/basic/basic_ltx2.py | 39 ++- .../basic/basic_ltx2_3_distilled_i2v.py | 153 +++++++---- .../inference/basic/basic_ltx2_distilled.py | 18 +- .../basic_ltx2_distilled_fast_profile.py | 114 +++++--- examples/inference/basic/basic_lucy_edit.py | 53 ++-- examples/inference/basic/basic_matrixgame2.py | 61 +++-- .../basic/basic_matrixgame2_streaming.py | 29 ++- examples/inference/basic/basic_matrixgame3.py | 55 ++-- examples/inference/basic/basic_mps.py | 56 ++-- examples/inference/basic/basic_ray.py | 40 +-- examples/inference/basic/basic_sd35_t2i.py | 74 +++--- .../basic/basic_self_forcing_causal.py | 38 ++- .../basic_self_forcing_causal_wan2_2_i2v.py | 69 +++-- .../basic_self_forcing_causal_wan2_2_t2v.py | 61 +++-- .../inference/basic/basic_stable_audio.py | 32 ++- .../inference/basic/basic_stable_audio_a2a.py | 36 ++- .../basic/basic_stable_audio_inpaint.py | 35 ++- .../basic/basic_stable_audio_small.py | 27 +- .../inference/basic/basic_turbodiffusion.py | 54 ++-- .../basic/basic_turbodiffusion_14b.py | 35 ++- .../basic/basic_turbodiffusion_i2v.py | 25 +- examples/inference/basic/basic_wan2_2.py | 55 ++-- examples/inference/basic/basic_wan2_2_Fun.py | 51 ++-- examples/inference/basic/basic_wan2_2_i2v.py | 45 ++-- examples/inference/basic/basic_wan2_2_ti2v.py | 46 +++- examples/inference/eval/all_metrics_demo.py | 32 ++- .../inference/eval/basic_ltx2_audio_eval.py | 34 ++- examples/inference/eval/basic_ltx2_eval.py | 25 +- examples/inference/eval/bench_physics_iq.py | 16 +- examples/inference/eval/bench_vbench.py | 14 +- examples/inference/eval/eval_ltx2_vbench.py | 34 ++- examples/inference/gradio/local/README.md | 2 +- .../gradio/local/gradio_local_demo.py | 27 +- .../local/gradio_local_demo_ltx2_3/README.md | 9 +- .../local/gradio_local_demo_ltx2_3/app.py | 70 +++-- .../local/gradio_local_demo_ltx2_3/config.py | 2 +- .../local/gradio_local_demo_ltx2_3/ui.py | 64 ++--- .../local/gradio_local_demo_matrixgame2.py | 23 +- .../gradio/serving/ray_serve_backend.py | 112 ++++---- examples/inference/lora/wan_lora_inference.py | 70 +++-- .../lora/wan_lora_inference_from_ckpt.py | 79 +++--- .../optimizations/FastWan_QAD_TAEHV.py | 97 ++++--- .../optimizations/attention_example.py | 40 +-- .../optimizations/fp4_attn_wan2_1_1_3b.py | 53 ++-- .../optimizations/fp8_wan2_1_1_3b.py | 86 +++--- .../optimizations/nvfp4_qat_wan2_1_1_3b.py | 49 ++-- .../text_encoder_quant_example.py | 50 ++-- .../optimizations/torch_compile_example.py | 37 ++- fastvideo/api/schema.py | 2 +- fastvideo/api/{compat.py => translation.py} | 20 +- fastvideo/entrypoints/cli/serve.py | 2 +- fastvideo/entrypoints/openai/video_api.py | 2 +- fastvideo/entrypoints/video_generator.py | 2 +- .../pipelines/basic/ltx2/continuation.py | 2 +- fastvideo/tests/api/test_cli_translation.py | 2 +- .../tests/api/test_compat_translation.py | 4 +- fastvideo/tests/api/test_ltx2_continuation.py | 8 +- .../api/test_ltx2_gpu_pool_translation.py | 4 +- fastvideo/tests/api/test_typed_quant_flow.py | 2 +- .../tests/contract/test_dreamverse_shape.py | 4 +- fastvideo/tests/contract/test_dynamo_shape.py | 2 +- .../tests/contract/test_generate_async.py | 2 +- .../tests/entrypoints/test_video_generator.py | 2 +- .../convert_magi_human_to_diffusers.py | 3 +- .../longcat_to_fastvideo.py | 22 +- .../push_magi_human_to_hf.py | 3 +- .../lora_inference_comparison.py | 67 +++-- 87 files changed, 2493 insertions(+), 1461 deletions(-) rename fastvideo/api/{compat.py => translation.py} (95%) diff --git a/docs/design/server_contracts/dynamo.md b/docs/design/server_contracts/dynamo.md index 70f70e612..301da6d42 100644 --- a/docs/design/server_contracts/dynamo.md +++ b/docs/design/server_contracts/dynamo.md @@ -330,7 +330,7 @@ at FastVideo's CI — before the Dynamo-side integration even knows. internal; presets identify them by name on `PipelineSelection.preset`). * `fastvideo.fastvideo_args.FastVideoArgs` (legacy compat type). -* `fastvideo.api.compat.*` private helpers +* `fastvideo.api.translation.*` private helpers (`_validate_continuation_state` etc.) — the public boundary is `VideoGenerator` + `fastvideo.api`. * Any flat legacy LTX-2 kwarg (`ltx2_refine_upsampler_path`, diff --git a/docs/design/server_contracts/openai.md b/docs/design/server_contracts/openai.md index caae24321..5cd5812ed 100644 --- a/docs/design/server_contracts/openai.md +++ b/docs/design/server_contracts/openai.md @@ -48,7 +48,7 @@ highest first: `request.model_fields_set` (Pydantic v2). Unset fields do not count, even if the Pydantic model has a schema default for them. 2. **`ServeConfig.default_request` (operator-explicit)** — projected via - [`explicit_request_updates()`](https://github.com/hao-ai-lab/FastVideo/blob/main/fastvideo/api/compat.py); + [`explicit_request_updates()`](https://github.com/hao-ai-lab/FastVideo/blob/main/fastvideo/api/translation.py); only fields the operator actually wrote into the YAML count as defaults. Every other field inherits the schema default rather than being pinned. diff --git a/examples/inference/basic/README.md b/examples/inference/basic/README.md index dcb235be1..bab68a4c7 100644 --- a/examples/inference/basic/README.md +++ b/examples/inference/basic/README.md @@ -39,17 +39,20 @@ All you need to generate videos using multi-gpus from state-of-the-art diffusion ```python from fastvideo import VideoGenerator +from fastvideo.api import EngineConfig, GenerationRequest, GeneratorConfig def main(): - generator = VideoGenerator.from_pretrained( - "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", - num_gpus=1, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + engine=EngineConfig(num_gpus=1), + ) ) 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) + result = generator.generate(GenerationRequest(prompt=prompt)) if __name__ == "__main__": main() diff --git a/examples/inference/basic/basic.py b/examples/inference/basic/basic.py index 766b9bd79..d4569a951 100644 --- a/examples/inference/basic/basic.py +++ b/examples/inference/basic/basic.py @@ -1,6 +1,7 @@ from fastvideo import VideoGenerator - -# from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig, OutputConfig, +) OUTPUT_PATH = "video_samples" def main(): @@ -8,29 +9,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( - "Wan-AI/Wan2.1-T2V-1.3B-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=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" - # image_encoder_cpu_offload=False, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + # FastVideo will automatically handle distributed setup + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + 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" + # image_encoder=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( + GenerationRequest(prompt=prompt, output=OutputConfig(output_path=OUTPUT_PATH, save_video=True))) # Generate another video with a different prompt, without reloading the # model! @@ -40,7 +44,8 @@ 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( + GenerationRequest(prompt=prompt2, output=OutputConfig(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 07c5f1316..95f27624c 100644 --- a/examples/inference/basic/basic_cosmos2_5_i2w.py +++ b/examples/inference/basic/basic_cosmos2_5_i2w.py @@ -1,24 +1,30 @@ # SPDX-License-Identifier: Apache-2.0 from fastvideo import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig, +) def main(): # Point this to your local diffusers model dir (or replace with a HF model ID). model_path = "KyleShao/Cosmos-Predict2.5-2B-Diffusers" - generator = VideoGenerator.from_pretrained( - model_path, - num_gpus=1, - use_fsdp_inference=False, # set True if GPU is out of memory - dit_cpu_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=model_path, + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set True if GPU is out of memory + offload=OffloadConfig( + dit=False, + vae=False, + text_encoder=True, + pin_cpu_memory=True, + ), + ), + ) ) - sampling_param = SamplingParam.from_pretrained(model_path) - # image2world example from official repo image_path = "assets/images/bus_terminal.jpg" @@ -33,13 +39,16 @@ def main(): "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( + GenerationRequest( + prompt=prompt, + inputs=InputConfig(image_path=str(image_path)), + output=OutputConfig( + output_path="outputs_video/cosmos2_5_i2w.mp4", + save_video=True, + ), + extensions={"num_cond_frames": 1}, + ) ) generator.shutdown() @@ -47,4 +56,3 @@ def main(): if __name__ == "__main__": main() - diff --git a/examples/inference/basic/basic_cosmos2_5_t2w.py b/examples/inference/basic/basic_cosmos2_5_t2w.py index 2296ca7bd..2ce827cc2 100644 --- a/examples/inference/basic/basic_cosmos2_5_t2w.py +++ b/examples/inference/basic/basic_cosmos2_5_t2w.py @@ -1,24 +1,29 @@ from fastvideo import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig, OutputConfig, +) def main(): # Point this to your local diffusers model dir (or replace with a HF model ID). model_path = "KyleShao/Cosmos-Predict2.5-2B-Diffusers" - generator = VideoGenerator.from_pretrained( - model_path, - num_gpus=1, - use_fsdp_inference=False, # set True if GPU is out of memory - dit_cpu_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=model_path, + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set True if GPU is out of memory + offload=OffloadConfig( + dit=False, + vae=False, + text_encoder=True, + 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. " @@ -34,11 +39,14 @@ def main(): "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( + GenerationRequest( + prompt=prompt, + output=OutputConfig( + output_path="outputs_video/cosmos2_5_t2w.mp4", + save_video=True, + ), + ) ) generator.shutdown() @@ -46,6 +54,3 @@ def main(): if __name__ == "__main__": main() - - - diff --git a/examples/inference/basic/basic_cosmos2_5_v2w.py b/examples/inference/basic/basic_cosmos2_5_v2w.py index 0efefce0e..d58fe339c 100644 --- a/examples/inference/basic/basic_cosmos2_5_v2w.py +++ b/examples/inference/basic/basic_cosmos2_5_v2w.py @@ -1,23 +1,29 @@ # SPDX-License-Identifier: Apache-2.0 from fastvideo import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, + OffloadConfig, OutputConfig, +) def main(): # Point this to your local diffusers model dir (or replace with a HF model ID). model_path = "KyleShao/Cosmos-Predict2.5-2B-Diffusers" - generator = VideoGenerator.from_pretrained( - model_path, - num_gpus=1, - use_fsdp_inference=False, # set True if GPU is out of memory - dit_cpu_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, - ) - - sampling_param = SamplingParam.from_pretrained(model_path) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=model_path, + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set True if GPU is out of memory + offload=OffloadConfig( + dit=False, + vae=False, + text_encoder=True, + pin_cpu_memory=True, + ), + ), + )) # video2world example from official repo video_path = "assets/videos/robot_pouring.mp4" @@ -36,18 +42,19 @@ 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( + GenerationRequest( + prompt=prompt, + inputs=InputConfig(video_path=str(video_path)), + output=OutputConfig( + output_path="outputs_video/cosmos2_5_v2w.mp4", + save_video=True, + ), + extensions={"num_cond_frames": 1}, + )) generator.shutdown() if __name__ == "__main__": main() - diff --git a/examples/inference/basic/basic_dmd.py b/examples/inference/basic/basic_dmd.py index 348a403e8..16cf718be 100644 --- a/examples/inference/basic/basic_dmd.py +++ b/examples/inference/basic/basic_dmd.py @@ -2,7 +2,9 @@ import os import time from fastvideo import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, + OffloadConfig, OutputConfig, PipelineSelection, + SamplingConfig) OUTPUT_PATH = "video_samples_dmd2" def main(): @@ -10,30 +12,36 @@ 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( + GeneratorConfig( + model_path=model_name, + engine=EngineConfig( + # 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=OffloadConfig( + 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, + ), + ), + pipeline=PipelineSelection(experimental={"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( + GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(num_frames=81), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + )) end_time = time.perf_counter() gen_time = end_time - start_time @@ -46,7 +54,12 @@ 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( + GenerationRequest( + prompt=prompt2, + sampling=SamplingConfig(num_frames=81), + output=OutputConfig(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 e07f1e1a6..7902e2e62 100644 --- a/examples/inference/basic/basic_dreamx_world.py +++ b/examples/inference/basic/basic_dreamx_world.py @@ -1,6 +1,9 @@ import os from fastvideo import VideoGenerator +from fastvideo.api import (ComponentConfig, EngineConfig, GenerationRequest, + GeneratorConfig, InputConfig, OffloadConfig, + OutputConfig, PipelineSelection, SamplingConfig) OUTPUT_PATH = os.getenv("DREAMX_WORLD_OUTPUT_PATH", "video_samples_dreamx_world") @@ -16,16 +19,22 @@ 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( + GeneratorConfig( + model_path=model_name, + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, + offload=OffloadConfig( + dit=False, + vae=True, + text_encoder=True, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection( + components=ComponentConfig(override_pipeline_cls_name="DreamXWorldPipeline"), ), + )) prompt = os.getenv( "DREAMX_WORLD_PROMPT", @@ -37,25 +46,31 @@ 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(",") - ], - } - if image_path: - kwargs["image_path"] = image_path + request = GenerationRequest( + prompt=prompt, + inputs=InputConfig(image_path=image_path or None), + sampling=SamplingConfig( + 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=OutputConfig( + 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(",") + ], + }, + ) try: - generator.generate_video(prompt, **kwargs) + generator.generate(request) finally: generator.shutdown() diff --git a/examples/inference/basic/basic_gamecraft.py b/examples/inference/basic/basic_gamecraft.py index 17d21fc29..602e04362 100644 --- a/examples/inference/basic/basic_gamecraft.py +++ b/examples/inference/basic/basic_gamecraft.py @@ -26,6 +26,15 @@ import os import torch from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + InputConfig, + OffloadConfig, + OutputConfig, + SamplingConfig, +) from fastvideo.models.camera import create_camera_trajectory # Model configuration (use GAMECRAFT_MODEL_PATH for local weights) @@ -55,14 +64,20 @@ OUTPUT_PATH = "video_samples_gamecraft" def main(): # Initialize generator # FastVideo will automatically download weights from HuggingFace - generator = VideoGenerator.from_pretrained( - MODEL_PATH, - num_gpus=1, - use_fsdp_inference=True, - dit_cpu_offload=True, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=MODEL_PATH, + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + offload=OffloadConfig( + dit=True, + vae=True, + text_encoder=True, + pin_cpu_memory=True, + ), + ), + ) ) # Video parameters @@ -96,23 +111,27 @@ 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( + request = GenerationRequest( 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, + sampling=SamplingConfig( + height=height, + width=width, + num_frames=num_frames, + num_inference_steps=50, + guidance_scale=6.0, + seed=42, + fps=24, + ), + output=OutputConfig( + 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 = InputConfig(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 a91ad301f..2997d138d 100644 --- a/examples/inference/basic/basic_gen3c.py +++ b/examples/inference/basic/basic_gen3c.py @@ -22,6 +22,10 @@ Requirements: import argparse from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, + OffloadConfig, OutputConfig, SamplingConfig, +) def main(): @@ -74,33 +78,47 @@ def main(): parser.add_argument("--seed", type=int, default=42) args = parser.parse_args() - generator = VideoGenerator.from_pretrained( - args.model_path, - num_gpus=1, - use_fsdp_inference=False, - dit_cpu_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, - ) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=args.model_path, + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, + offload=OffloadConfig( + dit=False, + vae=True, + text_encoder=True, + 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( + GenerationRequest( + prompt=args.prompt, + negative_prompt=args.negative_prompt, + inputs=InputConfig( + image_path=args.image_path, + ), + sampling=SamplingConfig( + 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=OutputConfig( + 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 745fd7e64..453c5a74e 100644 --- a/examples/inference/basic/basic_hy15.py +++ b/examples/inference/basic/basic_hy15.py @@ -1,6 +1,13 @@ from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + OffloadConfig, + OutputConfig, + SamplingConfig, +) import json -# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_hy15" def main(): @@ -8,17 +15,21 @@ 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( - "hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v", + generator = VideoGenerator.from_config(GeneratorConfig( + model_path="hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v", # 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, - 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" - # image_encoder_cpu_offload=False, - ) + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + dit=True, + vae=True, + text_encoder=True, + pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" + # image_encoder=False, + ), + ), + )) prompt = ( "A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " @@ -26,7 +37,12 @@ def main(): "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) + generator.generate(GenerationRequest( + prompt=prompt, + negative_prompt="", + sampling=SamplingConfig(num_frames=81, fps=16), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + )) prompt2 = ( "A majestic lion strides across the golden savanna, its powerful frame " @@ -35,8 +51,13 @@ 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) + generator.generate(GenerationRequest( + prompt=prompt2, + negative_prompt="", + sampling=SamplingConfig(num_frames=81, fps=16), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + )) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/inference/basic/basic_hy15_1080p.py b/examples/inference/basic/basic_hy15_1080p.py index 32dcc5780..b93905f43 100644 --- a/examples/inference/basic/basic_hy15_1080p.py +++ b/examples/inference/basic/basic_hy15_1080p.py @@ -1,6 +1,12 @@ from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + OffloadConfig, + OutputConfig, +) import json -# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_hy15_1080p" def main(): @@ -8,17 +14,23 @@ 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( - "weizhou03/HunyuanVideo-1.5-Diffusers-1080p-2SR", # 480p -> 720p -> 1080p - # or "weizhou03/HunyuanVideo-1.5-Diffusers-1080p" # 720p -> 1080p - # 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, - 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" - # image_encoder_cpu_offload=False, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="weizhou03/HunyuanVideo-1.5-Diffusers-1080p-2SR", # 480p -> 720p -> 1080p + # or "weizhou03/HunyuanVideo-1.5-Diffusers-1080p" # 720p -> 1080p + engine=EngineConfig( + # FastVideo will automatically handle distributed setup + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + dit=True, + vae=True, + text_encoder=True, + pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" + # image_encoder=False, + ), + ), + ) ) prompt = ( @@ -27,7 +39,13 @@ def main(): "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( + GenerationRequest( + prompt=prompt, + negative_prompt="", + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + ) + ) prompt2 = ( "A majestic lion strides across the golden savanna, its powerful frame " @@ -36,7 +54,13 @@ 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( + GenerationRequest( + prompt=prompt2, + negative_prompt="", + output=OutputConfig(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 4caae8656..245ea9f57 100644 --- a/examples/inference/basic/basic_hyworld.py +++ b/examples/inference/basic/basic_hyworld.py @@ -1,4 +1,6 @@ from fastvideo import VideoGenerator +from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig, + SamplingConfig) from fastvideo.models.dits.hyworld.resolution_utils import get_resolution_from_image # Default prompt from HY-WorldPlay run.sh @@ -31,33 +33,45 @@ def main(): # Initialize generator print("\nInitializing VideoGenerator for HYWorld...") - generator = VideoGenerator.from_pretrained( - "FastVideo/HY-WorldPlay-Bidirectional-Diffusers", - num_gpus=1, - use_fsdp_inference=True, - dit_cpu_offload=True, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, - image_encoder_cpu_offload=True, - ) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="FastVideo/HY-WorldPlay-Bidirectional-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + offload=OffloadConfig( + dit=True, + vae=True, + text_encoder=True, + pin_cpu_memory=True, + image_encoder=True, + ), + ), + )) # 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( + GenerationRequest( + prompt=args.prompt, + negative_prompt="", + inputs=InputConfig( + image_path=args.image, + pose=args.pose, # Camera trajectory control + ), + sampling=SamplingConfig( + num_frames=args.num_frames, + fps=24, + height=HEIGHT, + width=WIDTH, + seed=args.seed, + ), + output=OutputConfig( + 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 c30ca2f48..b3c1b1898 100644 --- a/examples/inference/basic/basic_kandinsky5_i2v.py +++ b/examples/inference/basic/basic_kandinsky5_i2v.py @@ -1,36 +1,43 @@ from fastvideo import VideoGenerator +from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, + InputConfig, OffloadConfig, OutputConfig, + SamplingConfig) + OUTPUT_PATH = "video_samples_kandinsky5_i2v" IMAGE_PATH = "assets/girl.png" def main(): - generator = VideoGenerator.from_pretrained( - "kandinskylab/Kandinsky-5.0-I2V-Pro-distilled-5s-Diffusers", - # "kandinskylab/Kandinsky-5.0-I2V-Pro-sft-5s-Diffusers" - # "kandinskylab/Kandinsky-5.0-I2V-Lite-5s-Diffusers" - num_gpus=1, - use_fsdp_inference=False, - dit_cpu_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, - # image_encoder_cpu_offload=False, - ) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="kandinskylab/Kandinsky-5.0-I2V-Pro-distilled-5s-Diffusers", + # "kandinskylab/Kandinsky-5.0-I2V-Pro-sft-5s-Diffusers" + # "kandinskylab/Kandinsky-5.0-I2V-Lite-5s-Diffusers" + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, + offload=OffloadConfig( + dit=False, + vae=False, + text_encoder=True, + pin_cpu_memory=True, + # image_encoder=False, + ), + ), + )) 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( + GenerationRequest( + prompt=prompt, + inputs=InputConfig(image_path=IMAGE_PATH), + sampling=SamplingConfig(height=1024, width=1024, num_frames=121), + output=OutputConfig(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 8042f87fd..6bf01bf6c 100644 --- a/examples/inference/basic/basic_kandinsky5_t2v.py +++ b/examples/inference/basic/basic_kandinsky5_t2v.py @@ -1,28 +1,41 @@ from fastvideo import VideoGenerator +from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, + OffloadConfig, OutputConfig, SamplingConfig) + OUTPUT_PATH = "video_samples_kandinsky5_t2v" def main(): - generator = VideoGenerator.from_pretrained( - "kandinskylab/Kandinsky-5.0-T2V-Lite-sft-5s-Diffusers", - # "kandinskylab/Kandinsky-5.0-T2V-Pro-sft-5s-Diffusers" - # "kandinskylab/Kandinsky-5.0-T2V-Lite-distilled16steps-5s-Diffusers" - # "kandinskylab/Kandinsky-5.0-T2V-Pro-distilled-5s-Diffusers" - num_gpus=1, - use_fsdp_inference=False, - dit_cpu_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, - # image_encoder_cpu_offload=False, - ) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="kandinskylab/Kandinsky-5.0-T2V-Lite-sft-5s-Diffusers", + # "kandinskylab/Kandinsky-5.0-T2V-Pro-sft-5s-Diffusers" + # "kandinskylab/Kandinsky-5.0-T2V-Lite-distilled16steps-5s-Diffusers" + # "kandinskylab/Kandinsky-5.0-T2V-Pro-distilled-5s-Diffusers" + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, + offload=OffloadConfig( + dit=False, + vae=False, + text_encoder=True, + pin_cpu_memory=True, + # image_encoder=False, + ), + ), + )) 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( + GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(height=512, width=768, num_frames=121), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + )) prompt2 = ( "A majestic lion strides across the golden savanna, its powerful frame " @@ -30,8 +43,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.") - _ = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True, height=512, width=768, num_frames=121) + _ = generator.generate( + GenerationRequest( + prompt=prompt2, + sampling=SamplingConfig(height=512, width=768, num_frames=121), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + )) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/inference/basic/basic_lingbotworld_base_cam.py b/examples/inference/basic/basic_lingbotworld_base_cam.py index 8c9a95321..4a99f8324 100644 --- a/examples/inference/basic/basic_lingbotworld_base_cam.py +++ b/examples/inference/basic/basic_lingbotworld_base_cam.py @@ -1,24 +1,31 @@ from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig, SamplingConfig, +) from fastvideo.models.dits.lingbotworld.cam_utils import prepare_camera_embedding -# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_lingbotworld" def main(): # FastVideo will automatically use the optimal default arguments for the # model. # If a local path is provided, FastVideo will make a best effort # attempt to identify the optimal arguments. - generator = VideoGenerator.from_pretrained( - "FastVideo/LingBot-World-Base-Cam-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, - # 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, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="FastVideo/LingBot-World-Base-Cam-Diffusers", + engine=EngineConfig( + # FastVideo will automatically handle distributed setup + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + 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, + ), + ), + ) ) num_frames = 81 @@ -33,15 +40,23 @@ 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( + GenerationRequest( + prompt=prompt, + inputs=InputConfig( + image_path=image_path, + c2ws_plucker_emb=c2ws_plucker_emb, + ), + sampling=SamplingConfig( + num_frames=num_frames, + height=480, + width=832, + ), + output=OutputConfig( + output_path=OUTPUT_PATH, + save_video=True, + ), + ) ) diff --git a/examples/inference/basic/basic_longcat_i2v.py b/examples/inference/basic/basic_longcat_i2v.py index f170dc4a3..1a61bab67 100644 --- a/examples/inference/basic/basic_longcat_i2v.py +++ b/examples/inference/basic/basic_longcat_i2v.py @@ -19,6 +19,10 @@ import glob import os from fastvideo import VideoGenerator +from fastvideo.api import ( + ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig, + InputConfig, OffloadConfig, OutputConfig, PipelineSelection, SamplingConfig, +) # Common prompts and settings matching the shell script examples PROMPT = ( @@ -45,41 +49,50 @@ SEED = 42 def basic_generation(): """ Run basic LongCat I2V generation (50 steps at 480p). - + This uses the full 50-step denoising process for highest quality. """ print("=" * 60) 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( + GeneratorConfig( + model_path="FastVideo/LongCat-Video-I2V-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + dit=False, + vae=True, + text_encoder=True, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection(experimental={"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( + GenerationRequest( + prompt=PROMPT, + negative_prompt=NEGATIVE_PROMPT, + inputs=InputConfig(image_path=IMAGE_PATH), + sampling=SamplingConfig( + height=480, + width=480, # Square + num_frames=93, + num_inference_steps=50, + fps=15, + guidance_scale=4.0, + seed=SEED, + ), + output=OutputConfig(output_path=output_path, save_video=True), + ) ) - + print(f"\nBasic generation complete! Video saved to: {output_path}") generator.shutdown() @@ -87,55 +100,70 @@ def basic_generation(): def distill_refine_generation(): """ Run LongCat I2V with distill+refine pipeline (16 steps + refinement to 768p). - + This uses the distilled LoRA for fast 480p generation (16 steps), then refines to 768p using the refinement LoRA with BSA enabled. """ print("\n" + "=" * 60) print("LongCat I2V: Distill + Refine Pipeline") print("=" * 60) - + # Stage 1: Distilled generation (16 steps at 480p) 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( + GeneratorConfig( + model_path="FastVideo/LongCat-Video-I2V-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + offload=OffloadConfig( + dit=False, + vae=True, + text_encoder=True, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection( + components=ComponentConfig( + lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA", + ), + experimental={ + "enable_bsa": False, + "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( + GenerationRequest( + prompt=PROMPT, + negative_prompt=NEGATIVE_PROMPT, + inputs=InputConfig(image_path=IMAGE_PATH), + sampling=SamplingConfig( + height=480, + width=480, # Square + num_frames=93, + num_inference_steps=16, + fps=15, + guidance_scale=1.0, + seed=SEED, + ), + output=OutputConfig(output_path=distill_output_path, save_video=True), + ) ) - + print(f"Distilled generation complete! Video saved to: {distill_output_path}") generator.shutdown() - + # Stage 2: Refinement (480p -> 768p) print("\n[Stage 2] Refinement (480p -> 768p with BSA)") print("-" * 40) - + # Find the actual saved video file from stage 1 video_files = glob.glob(os.path.join(distill_output_path, "*.mp4")) if not video_files: @@ -143,46 +171,63 @@ def distill_refine_generation(): # Use the most recently created video file distill_video_path = max(video_files, key=os.path.getmtime) print(f"Using stage 1 video: {distill_video_path}") - + # Create a new generator with refinement LoRA and BSA enabled # 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( + GeneratorConfig( + model_path="FastVideo/LongCat-Video-T2V-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + offload=OffloadConfig( + dit=True, + vae=True, + text_encoder=True, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection( + components=ComponentConfig( + lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA", + ), + experimental={ + "enable_bsa": True, + "bsa_sparsity": 0.875, + "bsa_chunk_q": [4, 4, 4], + "bsa_chunk_k": [4, 4, 4], + "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( + GenerationRequest( + prompt=PROMPT, + negative_prompt=NEGATIVE_PROMPT, + inputs=InputConfig(refine_from=distill_video_path), + sampling=SamplingConfig( + height=720, + width=720, + num_inference_steps=50, + fps=30, + guidance_scale=1.0, + seed=SEED, + ), + output=OutputConfig(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() @@ -192,13 +237,13 @@ def main(): print("\n" + "=" * 60) print("LongCat Image-to-Video Example") print("=" * 60 + "\n") - + # Run basic generation basic_generation() - + # Run distill+refine pipeline distill_refine_generation() - + print("\n" + "=" * 60) print("All generations complete!") print("=" * 60) diff --git a/examples/inference/basic/basic_longcat_t2v.py b/examples/inference/basic/basic_longcat_t2v.py index 354228de2..cce5cd03c 100644 --- a/examples/inference/basic/basic_longcat_t2v.py +++ b/examples/inference/basic/basic_longcat_t2v.py @@ -13,6 +13,10 @@ import glob import os from fastvideo import VideoGenerator +from fastvideo.api import ( + ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig, + InputConfig, OffloadConfig, OutputConfig, PipelineSelection, SamplingConfig, +) # Common prompts and settings matching the shell script examples PROMPT = ( @@ -38,40 +42,54 @@ SEED = 42 def basic_generation(): """ Run basic LongCat T2V generation (50 steps at 480p). - + This uses the full 50-step denoising process for highest quality. """ print("=" * 60) 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( + GeneratorConfig( + model_path="FastVideo/LongCat-Video-T2V-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + dit=False, + vae=True, + text_encoder=True, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection( + experimental={"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( + GenerationRequest( + prompt=PROMPT, + negative_prompt=NEGATIVE_PROMPT, + output=OutputConfig( + output_path=output_path, + save_video=True, + ), + sampling=SamplingConfig( + height=480, + width=832, + num_frames=93, + num_inference_steps=50, + fps=15, + guidance_scale=4.0, + seed=SEED, + ), + ) ) - + print(f"\nBasic generation complete! Video saved to: {output_path}") generator.shutdown() @@ -79,54 +97,72 @@ def basic_generation(): def distill_refine_generation(): """ Run LongCat T2V with distill+refine pipeline (16 steps + refinement to 720p). - + This uses the distilled LoRA for fast 480p generation (16 steps), then refines to 720p using the refinement LoRA with BSA enabled. """ print("\n" + "=" * 60) print("LongCat T2V: Distill + Refine Pipeline") print("=" * 60) - + # Stage 1: Distilled generation (16 steps at 480p) 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( + GeneratorConfig( + model_path="FastVideo/LongCat-Video-T2V-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + offload=OffloadConfig( + dit=False, + vae=True, + text_encoder=True, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection( + components=ComponentConfig( + lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA", + ), + experimental={ + "enable_bsa": False, + "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( + GenerationRequest( + prompt=PROMPT, + negative_prompt=NEGATIVE_PROMPT, + output=OutputConfig( + output_path=distill_output_path, + save_video=True, + ), + sampling=SamplingConfig( + height=480, + width=832, + num_frames=93, + num_inference_steps=16, + fps=15, + guidance_scale=1.0, + seed=SEED, + ), + ) ) - + print(f"Distilled generation complete! Video saved to: {distill_output_path}") generator.shutdown() - + # Stage 2: Refinement (480p -> 720p) print("\n[Stage 2] Refinement (480p -> 720p with BSA)") print("-" * 40) - + # Find the actual saved video file from stage 1 video_files = glob.glob(os.path.join(distill_output_path, "*.mp4")) if not video_files: @@ -134,43 +170,65 @@ def distill_refine_generation(): # Use the most recently created video file distill_video_path = max(video_files, key=os.path.getmtime) 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( + GeneratorConfig( + model_path="FastVideo/LongCat-Video-T2V-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + offload=OffloadConfig( + dit=True, + vae=True, + text_encoder=True, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection( + components=ComponentConfig( + lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA", + ), + experimental={ + "enable_bsa": True, + "bsa_sparsity": 0.875, + "bsa_chunk_q": [4, 4, 8], + "bsa_chunk_k": [4, 4, 8], + "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( + GenerationRequest( + prompt=PROMPT, + negative_prompt=NEGATIVE_PROMPT, + output=OutputConfig( + output_path=refine_output_path, + save_video=True, + ), + inputs=InputConfig( + refine_from=distill_video_path, + ), + sampling=SamplingConfig( + height=720, + width=1280, + num_inference_steps=50, + fps=30, + guidance_scale=1.0, + seed=SEED, + ), + 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() @@ -180,13 +238,13 @@ def main(): print("\n" + "=" * 60) print("LongCat Text-to-Video Example") print("=" * 60 + "\n") - + # Run basic generation basic_generation() - + # Run distill+refine pipeline distill_refine_generation() - + print("\n" + "=" * 60) print("All generations complete!") print("=" * 60) @@ -194,5 +252,3 @@ def main(): if __name__ == "__main__": main() - - diff --git a/examples/inference/basic/basic_longcat_vc.py b/examples/inference/basic/basic_longcat_vc.py index 313cbb72c..612733dca 100644 --- a/examples/inference/basic/basic_longcat_vc.py +++ b/examples/inference/basic/basic_longcat_vc.py @@ -19,6 +19,10 @@ import glob import os from fastvideo import VideoGenerator +from fastvideo.api import ( + ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig, + PipelineSelection, SamplingConfig, +) # Common prompts and settings matching the shell script examples PROMPT = ( @@ -63,35 +67,49 @@ def basic_generation(): "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( + GeneratorConfig( + model_path="FastVideo/LongCat-Video-VC-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + dit=False, + vae=True, + text_encoder=True, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection( + experimental={"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( + GenerationRequest( + prompt=PROMPT, + negative_prompt=NEGATIVE_PROMPT, + inputs=InputConfig(video_path=VIDEO_PATH), + sampling=SamplingConfig( + height=480, + width=832, + num_frames=93, + num_inference_steps=50, + fps=15, + guidance_scale=4.0, + seed=SEED, + ), + output=OutputConfig( + 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() @@ -118,37 +136,55 @@ 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( + GeneratorConfig( + model_path="FastVideo/LongCat-Video-VC-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + offload=OffloadConfig( + dit=False, + vae=True, + text_encoder=True, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection( + components=ComponentConfig( + lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA", + ), + experimental={ + "enable_bsa": False, + "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( + GenerationRequest( + prompt=PROMPT, + negative_prompt=NEGATIVE_PROMPT, + inputs=InputConfig(video_path=VIDEO_PATH), + sampling=SamplingConfig( + height=480, + width=832, + num_frames=93, + num_inference_steps=16, + fps=15, + guidance_scale=1.0, + seed=SEED, + ), + output=OutputConfig( + 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() @@ -166,41 +202,61 @@ 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( + GeneratorConfig( + model_path="FastVideo/LongCat-Video-T2V-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + offload=OffloadConfig( + dit=True, + vae=True, + text_encoder=True, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection( + components=ComponentConfig( + lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA", + ), + experimental={ + "enable_bsa": True, + "bsa_sparsity": 0.875, + "bsa_chunk_q": [4, 4, 8], + "bsa_chunk_k": [4, 4, 8], + "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( + GenerationRequest( + prompt=PROMPT, + negative_prompt=NEGATIVE_PROMPT, + inputs=InputConfig(refine_from=distill_video_path), + sampling=SamplingConfig( + height=720, + width=1280, + num_inference_steps=50, + fps=30, + guidance_scale=1.0, + seed=SEED, + ), + output=OutputConfig( + 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 c7196e291..f2a608174 100644 --- a/examples/inference/basic/basic_ltx2.py +++ b/examples/inference/basic/basic_ltx2.py @@ -1,4 +1,11 @@ from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + OutputConfig, + SamplingConfig, +) PROMPT = ( @@ -18,22 +25,32 @@ PROMPT = ( def main() -> None: # Uses FastVideo default sampling settings for LTX2 base. - generator = VideoGenerator.from_pretrained( - "Davids048/LTX2-Base-Diffusers", - num_gpus=1, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="Davids048/LTX2-Base-Diffusers", + engine=EngineConfig( + num_gpus=1, + ), + ) ) 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( + GenerationRequest( + prompt=PROMPT, + output=OutputConfig( + output_path=output_path, + save_video=True, + ), + sampling=SamplingConfig( + num_frames=121, + height=1088, + width=1920, + ), + ) ) generator.shutdown() if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/inference/basic/basic_ltx2_3_distilled_i2v.py b/examples/inference/basic/basic_ltx2_3_distilled_i2v.py index 7e680d032..2a775260f 100644 --- a/examples/inference/basic/basic_ltx2_3_distilled_i2v.py +++ b/examples/inference/basic/basic_ltx2_3_distilled_i2v.py @@ -49,6 +49,11 @@ from pathlib import Path import torch._inductor.config as _inductor from fastvideo import VideoGenerator +from fastvideo.api import ( + CompileConfig, ComponentConfig, EngineConfig, GenerationRequest, + GeneratorConfig, OffloadConfig, OutputConfig, PipelineSelection, + SamplingConfig, +) from fastvideo.configs.pipelines.base import PipelineConfig from fastvideo.utils import maybe_download_model @@ -86,9 +91,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, 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") @@ -104,11 +109,11 @@ def _print_stage_breakdown(result: dict, label: str) -> float | None: def _collect_stage_times( - result: dict, + result, 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 @@ -169,34 +174,54 @@ 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( + GeneratorConfig( + model_path=model_root, + engine=EngineConfig( + num_gpus=1, + compile=CompileConfig( + 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, + backend=torch_compile_kwargs["backend"], + fullgraph=torch_compile_kwargs["fullgraph"], + mode=torch_compile_kwargs["mode"], + dynamic=torch_compile_kwargs["dynamic"], + vae_kwargs=torch_compile_kwargs, + ), + # Keep everything resident — no CPU offload for serving runs. + offload=OffloadConfig( + dit=False, + text_encoder=False, + vae=False, + ), + ), + pipeline=PipelineSelection( + vae_tiling=False, + # LTX-2.3 distilled uses the two-stage refine pipeline; the + # refine LoRA is intentionally empty for the distilled + # student. + components=ComponentConfig( + upsampler_weights=str(refine_upsampler_path), + ), + preset_overrides={ + "refine": { + "enabled": True, + "num_inference_steps": 3, + "guidance_scale": 1.0, + "add_noise": True, + } + }, + experimental={"pipeline_config": pipeline_config}, + ), + ) ) common_kwargs = dict( @@ -206,12 +231,15 @@ def main() -> None: height=1280, width=832, # portrait runway aspect num_frames=121, fps=24, # ~5s clip num_inference_steps=8, # distilled denoise steps - # 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. + ) + + # 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. These are model-specific knobs routed through + # the request extensions escape hatch. + common_extensions = dict( ltx2_images=[(I2V_IMAGE, 0, 1.0)], ltx2_image_crf=0.0, - save_video=True, ) warmup_runs = 2 @@ -227,10 +255,25 @@ 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( + GenerationRequest( + prompt=common_kwargs["prompt"], + negative_prompt=common_kwargs["negative_prompt"], + sampling=SamplingConfig( + guidance_scale=common_kwargs["guidance_scale"], + height=common_kwargs["height"], + width=common_kwargs["width"], + num_frames=common_kwargs["num_frames"], + fps=common_kwargs["fps"], + num_inference_steps=common_kwargs["num_inference_steps"], + seed=7, + ), + output=OutputConfig( + output_path=str(OUTPUT_DIR / f"_warmup_{w + 1}.mp4"), + save_video=True, + ), + extensions=common_extensions, + ) ) dt = time.perf_counter() - t0 warmup_secs.append(dt) @@ -245,19 +288,31 @@ 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( + GenerationRequest( + prompt=common_kwargs["prompt"], + negative_prompt=common_kwargs["negative_prompt"], + sampling=SamplingConfig( + guidance_scale=common_kwargs["guidance_scale"], + height=common_kwargs["height"], + width=common_kwargs["width"], + num_frames=common_kwargs["num_frames"], + fps=common_kwargs["fps"], + num_inference_steps=common_kwargs["num_inference_steps"], + seed=2002 + m, + ), + output=OutputConfig( + output_path=str(out_path), + save_video=True, + ), + extensions=common_extensions, + ) ) 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 result is not None 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 result is not None: _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 1ac732eaf..298c8371f 100644 --- a/examples/inference/basic/basic_ltx2_distilled.py +++ b/examples/inference/basic/basic_ltx2_distilled.py @@ -1,4 +1,5 @@ from fastvideo import VideoGenerator +from fastvideo.api import EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig PROMPT = ( "A warm sunny backyard. The camera starts in a tight cinematic close-up " @@ -17,16 +18,19 @@ import os os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "FLASH_ATTN" def main() -> None: - generator = VideoGenerator.from_pretrained( - "FastVideo/LTX2-Distilled-Diffusers", - num_gpus=4, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="FastVideo/LTX2-Distilled-Diffusers", + engine=EngineConfig(num_gpus=4), + ) ) 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( + GenerationRequest( + prompt=PROMPT, + output=OutputConfig(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 e5f284746..7dc6204b3 100644 --- a/examples/inference/basic/basic_ltx2_distilled_fast_profile.py +++ b/examples/inference/basic/basic_ltx2_distilled_fast_profile.py @@ -8,6 +8,11 @@ from pathlib import Path import torch import torch._inductor.config from fastvideo import VideoGenerator +from fastvideo.api import ( + CompileConfig, ComponentConfig, EngineConfig, GenerationRequest, + GenerationResult, GeneratorConfig, OffloadConfig, OutputConfig, + PipelineSelection, SamplingConfig, +) from fastvideo.configs.pipelines.base import PipelineConfig from fastvideo.layers.quantization.nvfp4_config import NVFP4Config from fastvideo.utils import maybe_download_model @@ -45,11 +50,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 @@ -70,9 +75,9 @@ def print_stage_breakdown( def extract_sr_forward_latency( - result: dict, + result: GenerationResult, ) -> tuple[float | None, list[tuple[str, float]], list[str]]: - logging_info = result.get("logging_info") + logging_info = result.logging_info if logging_info is None: return None, [], [] @@ -106,11 +111,11 @@ def extract_sr_forward_latency( 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) @@ -202,26 +207,45 @@ 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( + GeneratorConfig( + model_path=model_root, + engine=EngineConfig( + num_gpus=1, + offload=OffloadConfig( + dit=False, + text_encoder=False, + vae=False, + ), + compile=CompileConfig( + enabled=True, + text_encoder_enabled=True, + vae_enabled=True, + backend="inductor", + fullgraph=True, + dynamic=False, + vae_kwargs=torch_compile_kwargs, + ), + ), + pipeline=PipelineSelection( + vae_tiling=False, + components=ComponentConfig( + upsampler_weights=str(refine_upsampler_path), + ), + preset_overrides={ + "refine": { + "enabled": True, + "num_inference_steps": 2, + "guidance_scale": 1.0, + "add_noise": True, + } + }, + experimental={ + "refine_lora_path": "", # keep refine LoRA disabled in this repo's typed adapter + "pipeline_config": pipeline_config, + }, + ), + ) ) run_times: list[float] = [] @@ -243,25 +267,31 @@ 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( + GenerationRequest( + prompt=prompt, + sampling=SamplingConfig( + 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=OutputConfig( + output_path=str(output_path), + save_video=True, + ), + # inputs=InputConfig(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: @@ -272,7 +302,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..cd5d71a0d 100644 --- a/examples/inference/basic/basic_lucy_edit.py +++ b/examples/inference/basic/basic_lucy_edit.py @@ -1,18 +1,27 @@ from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, + OffloadConfig, OutputConfig, SamplingConfig, +) OUTPUT_PATH = "video_samples_lucy_edit" def main(): - generator = VideoGenerator.from_pretrained( - "decart-ai/Lucy-Edit-Dev", - num_gpus=1, - use_fsdp_inference=False, - dit_cpu_offload=True, - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, - ) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="decart-ai/Lucy-Edit-Dev", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, + offload=OffloadConfig( + dit=True, + vae=False, + text_encoder=True, + pin_cpu_memory=True, + ), + ), + )) prompt = ("Change the apron and blouse to a classic clown costume: satin " "polka-dot jumpsuit in bright primary colors, ruffled white collar, " @@ -20,18 +29,20 @@ 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( + GenerationRequest( + prompt=prompt, + negative_prompt="", + inputs=InputConfig(video_path=video_path), + sampling=SamplingConfig( + height=480, + width=832, + num_frames=81, + fps=24, + guidance_scale=5.0, + ), + output=OutputConfig(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 70763ecfb..61da36f75 100644 --- a/examples/inference/basic/basic_matrixgame2.py +++ b/examples/inference/basic/basic_matrixgame2.py @@ -1,4 +1,6 @@ from fastvideo import VideoGenerator +from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig, + SamplingConfig) from fastvideo.models.dits.matrixgame2.utils import create_action_presets import torch @@ -38,35 +40,48 @@ def main(): # attempt to identify the optimal arguments. config = VARIANT_CONFIG[MODEL_VARIANT] - generator = VideoGenerator.from_pretrained( - config["model_path"], - # 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, - # 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, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=config["model_path"], + # FastVideo will automatically handle distributed setup + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + 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, + ), + ), + ) ) num_frames = 597 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( + GenerationRequest( + prompt="", + inputs=InputConfig( + image_path=config["image_url"], + mouse_cond=actions["mouse"].unsqueeze(0), + keyboard_cond=actions["keyboard"].unsqueeze(0), + grid_sizes=grid_sizes, + ), + sampling=SamplingConfig( + num_frames=num_frames, + height=352, + width=640, + num_inference_steps=50, + ), + output=OutputConfig( + output_path=OUTPUT_PATH, + save_video=True, + ), + ) ) diff --git a/examples/inference/basic/basic_matrixgame2_streaming.py b/examples/inference/basic/basic_matrixgame2_streaming.py index 0361d5611..dfbe26f27 100644 --- a/examples/inference/basic/basic_matrixgame2_streaming.py +++ b/examples/inference/basic/basic_matrixgame2_streaming.py @@ -1,5 +1,6 @@ from fastvideo.entrypoints.streaming_generator import StreamingVideoGenerator from fastvideo.models.dits.matrixgame2.utils import get_current_action_async, expand_action_to_frames +from fastvideo.api import EngineConfig, GeneratorConfig, OffloadConfig import torch import asyncio @@ -42,17 +43,23 @@ async def main(): # attempt to identify the optimal arguments. config = VARIANT_CONFIG[MODEL_VARIANT] - generator = StreamingVideoGenerator.from_pretrained( - config["model_path"], - # 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, - # 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, + generator = StreamingVideoGenerator.from_config( + GeneratorConfig( + model_path=config["model_path"], + # FastVideo will automatically handle distributed setup + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + 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, + ), + ), + ) ) max_blocks = 50 diff --git a/examples/inference/basic/basic_matrixgame3.py b/examples/inference/basic/basic_matrixgame3.py index 37783fdab..e788c8153 100644 --- a/examples/inference/basic/basic_matrixgame3.py +++ b/examples/inference/basic/basic_matrixgame3.py @@ -1,4 +1,7 @@ from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig, SamplingConfig, +) MODEL_PATH = "FastVideo/Matrix-Game-3.0-Base-Distilled-Diffusers" IMAGE_URL = "https://raw.githubusercontent.com/SkyworkAI/Matrix-Game/main/Matrix-Game-3/demo_images/001/image.png" @@ -7,28 +10,38 @@ OUTPUT_PATH = "video_samples_matrixgame3" def main(): - generator = VideoGenerator.from_pretrained( - MODEL_PATH, - num_gpus=1, - use_fsdp_inference=False, - dit_cpu_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, - ) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=MODEL_PATH, + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, + offload=OffloadConfig( + dit=False, + vae=False, + text_encoder=True, + 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( + GenerationRequest( + prompt=PROMPT, + inputs=InputConfig(image_path=IMAGE_URL), + sampling=SamplingConfig( + height=720, + width=1280, + num_frames=57, + num_inference_steps=3, + guidance_scale=1.0, + seed=42, + ), + output=OutputConfig( + output_path=OUTPUT_PATH, + save_video=True, + ), + )) if __name__ == "__main__": diff --git a/examples/inference/basic/basic_mps.py b/examples/inference/basic/basic_mps.py index 885f945d1..9bb6f4d61 100644 --- a/examples/inference/basic/basic_mps.py +++ b/examples/inference/basic/basic_mps.py @@ -1,40 +1,56 @@ from fastvideo import VideoGenerator, PipelineConfig -from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + OffloadConfig, + PipelineSelection, + SamplingConfig, +) 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( + GeneratorConfig( + model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # Disable FSDP for MPS + disable_autocast=False, + offload=OffloadConfig( + dit=True, + text_encoder=True, + pin_cpu_memory=True, + ), + ), + pipeline=PipelineSelection( + 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 + # Reduce from default 81 to 25 frames bc we have to use the SDPA attn backend for mps + sampling = SamplingConfig( + num_frames=25, + 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(GenerationRequest(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 " "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, sampling_param=sampling_param) + + video2 = generator.generate(GenerationRequest(prompt=prompt2, sampling=sampling)) if __name__ == "__main__": main() diff --git a/examples/inference/basic/basic_ray.py b/examples/inference/basic/basic_ray.py index 4837e7e01..031344b39 100644 --- a/examples/inference/basic/basic_ray.py +++ b/examples/inference/basic/basic_ray.py @@ -1,6 +1,8 @@ from fastvideo import VideoGenerator - -# from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig, + OutputConfig, +) OUTPUT_PATH = "video_samples" def main(): @@ -8,17 +10,23 @@ 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( - "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", - # FastVideo will automatically handle distributed setup - num_gpus=2, - use_fsdp_inference=True, - 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" - distributed_executor_backend="ray", - # image_encoder_cpu_offload=False, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + # FastVideo will automatically handle distributed setup + engine=EngineConfig( + num_gpus=2, + use_fsdp_inference=True, + execution_backend="ray", + offload=OffloadConfig( + 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" + # image_encoder=False, + ), + ), + ) ) # Generate videos with the same simple API, regardless of GPU count @@ -27,7 +35,8 @@ 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) + video = generator.generate( + GenerationRequest(prompt=prompt, output=OutputConfig(output_path=OUTPUT_PATH, save_video=True))) # Generate another video with a different prompt, without reloading the # model! @@ -37,7 +46,8 @@ 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( + GenerationRequest(prompt=prompt2, output=OutputConfig(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 6351948c5..d02486de5 100644 --- a/examples/inference/basic/basic_sd35_t2i.py +++ b/examples/inference/basic/basic_sd35_t2i.py @@ -85,24 +85,35 @@ def main() -> None: os.environ["FASTVIDEO_ATTENTION_BACKEND"] = args.backend from fastvideo import VideoGenerator + from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig, OutputConfig, + ParallelismConfig, PipelineSelection, SamplingConfig, + ) 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( + GeneratorConfig( + model_path=args.model_path, + engine=EngineConfig( + num_gpus=args.num_gpus, + use_fsdp_inference=False, + parallelism=ParallelismConfig( + sp_size=1, + tp_size=1, + ), + offload=OffloadConfig( + dit=False, + dit_layerwise=False, + text_encoder=False, + vae=False, + image_encoder=False, + pin_cpu_memory=False, + ), + ), + pipeline=PipelineSelection(workload_type="t2i"), + ) + ) try: for i, prompt in enumerate(prompts): seed = args.seed + i @@ -113,20 +124,25 @@ 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, - "negative_prompt": args.negative, - "save_video": True, - } - - generator.generate_video(prompt, **generation_kwargs) + generator.generate( + GenerationRequest( + prompt=prompt, + negative_prompt=args.negative, + sampling=SamplingConfig( + height=args.height, + width=args.width, + num_frames=1, + fps=1, + num_inference_steps=args.steps, + guidance_scale=args.guidance, + seed=seed, + ), + output=OutputConfig( + output_path=output_path, + save_video=True, + ), + ) + ) 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 0e6d46e9e..63c401434 100644 --- a/examples/inference/basic/basic_self_forcing_causal.py +++ b/examples/inference/basic/basic_self_forcing_causal.py @@ -1,6 +1,14 @@ import os import time -from fastvideo import VideoGenerator, SamplingParam + +from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + OffloadConfig, + OutputConfig, +) OUTPUT_PATH = "video_samples_causal" def main(): @@ -9,23 +17,33 @@ def main(): # If a local path is provided, FastVideo will make a best effort # attempt to identify the optimal arguments. model_name = "wlsaidhi/SFWan2.1-T2V-1.3B-Diffusers" - generator = VideoGenerator.from_pretrained( - model_name, + generator_config = GeneratorConfig( + model_path=model_name, # FastVideo will automatically handle distributed setup - num_gpus=1, - use_fsdp_inference=False, # set to True if GPU is out of memory - text_encoder_cpu_offload=False, - dit_cpu_offload=False, + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + text_encoder=False, + dit=False, + ), + ), ) - - sampling_param = SamplingParam.from_pretrained(model_name) + generator = VideoGenerator.from_config(generator_config) 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) + request = GenerationRequest( + prompt=prompt, + output=OutputConfig( + output_path=OUTPUT_PATH, + save_video=True, + ), + ) + video = generator.generate(request) if __name__ == "__main__": 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 32b60cd86..83e163aa3 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,8 +1,17 @@ # NOTE: This is still a work in progress, and the checkpoints are not released yet. -from fastvideo import VideoGenerator, SamplingParam +from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + InputConfig, + OffloadConfig, + OutputConfig, + PipelineSelection, + SamplingConfig, +) import json -# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_self_forcing_causal_wan2_2_14B_i2v" def main(): @@ -10,26 +19,37 @@ 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, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="FastVideo/SFWan2.2-I2V-A14B-Preview-Diffusers", + # FastVideo will automatically handle distributed setup + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + 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=PipelineSelection( + experimental={ + "dit_precision": "fp32", + "dmd_denoising_steps": [1000, 850, 700, 550, 350, 275, 200, 125], + }, + ), + ) ) - 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 + sampling = SamplingConfig( + num_frames=81, + width=832, + height=480, + seed=1000, + ) with open("assets/prompts/mixkit_i2v.jsonl", "r") as f: prompt_image_pairs = json.load(f) @@ -37,7 +57,14 @@ 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( + GenerationRequest( + prompt=prompt, + inputs=InputConfig(image_path=image_path), + sampling=sampling, + output=OutputConfig(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 3abdb1b7e..26e4e276f 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 @@ -1,8 +1,10 @@ # NOTE: This is still a work in progress, and the checkpoints are not released yet. from fastvideo import VideoGenerator - -# from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig, + OffloadConfig, OutputConfig, PipelineSelection, SamplingConfig, +) OUTPUT_PATH = "video_samples_self_forcing_causal_wan2_2_14B_t2v" def main(): @@ -10,34 +12,49 @@ 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( + GeneratorConfig( + model_path="rand0nmr/SFWan2.2-T2V-A14B-Diffusers", + engine=EngineConfig( + # FastVideo will automatically handle distributed setup + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + 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, + ), + ), + pipeline=PipelineSelection( + components=ComponentConfig( + 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={ + "dmd_denoising_steps": [1000, 850, 700, 550, 350, 275, 200, 125], + "num_frame_per_block": 7, + }, + ), + # 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, num_frames=81) + _ = generator.generate( + GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(num_frames=81), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + ) + ) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/inference/basic/basic_stable_audio.py b/examples/inference/basic/basic_stable_audio.py index 23008b51b..d654d2982 100644 --- a/examples/inference/basic/basic_stable_audio.py +++ b/examples/inference/basic/basic_stable_audio.py @@ -51,25 +51,31 @@ Prerequisites: uv pip install k_diffusion einops_exts alias_free_torch torchsde """ from fastvideo import VideoGenerator +from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, + OutputConfig) PROMPT = "Lo-fi hip hop instrumental with vinyl crackle and gentle piano." def main() -> None: - generator = VideoGenerator.from_pretrained( - "FastVideo/stable-audio-open-1.0-Diffusers", - num_gpus=1, - ) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="FastVideo/stable-audio-open-1.0-Diffusers", + engine=EngineConfig(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, - # The registered preset gives 100 steps + CFG=7.0 by default; - # override num_inference_steps / guidance_scale here for QA. - ) + generator.generate( + GenerationRequest( + prompt=PROMPT, + output=OutputConfig( + output_path=output_path, + save_video=True, + ), + # 6-second clip; the model max is ~47.5s. + extensions={"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..d9ef2975e 100644 --- a/examples/inference/basic/basic_stable_audio_a2a.py +++ b/examples/inference/basic/basic_stable_audio_a2a.py @@ -48,6 +48,12 @@ Picking `init_audio_strength` (0.0 to 1.0): Prerequisites: same as `basic_stable_audio.py`. """ from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + OutputConfig, +) PROMPT = "Change the piano to a cello playing the same notes" # Path to any audio-bearing file (wav, mp3, mp4, m4a, flac, ...). @@ -58,18 +64,24 @@ INIT_AUDIO_STRENGTH = 0.6 def main() -> None: - generator = VideoGenerator.from_pretrained( - "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 = VideoGenerator.from_config( + GeneratorConfig( + model_path="FastVideo/stable-audio-open-1.0-Diffusers", + engine=EngineConfig(num_gpus=1), + )) + generator.generate( + GenerationRequest( + prompt=PROMPT, + output=OutputConfig( + 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 126bbc54a..4b74006cc 100644 --- a/examples/inference/basic/basic_stable_audio_inpaint.py +++ b/examples/inference/basic/basic_stable_audio_inpaint.py @@ -48,6 +48,9 @@ Prerequisites: same as `basic_stable_audio.py`. import os from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GeneratorConfig, GenerationRequest, OutputConfig, +) PROMPT = "Steady lo-fi hip hop drum loop with vinyl crackle." # Required: path to the reference audio file (wav, mp3, mp4, m4a, flac, @@ -64,19 +67,25 @@ def main() -> None: f"REFERENCE_AUDIO_PATH={REFERENCE_AUDIO_PATH!r} does not exist. " "Edit this script to point at a real audio file (wav/mp3/mp4/" "m4a/flac) before running.") - generator = VideoGenerator.from_pretrained( - "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 = VideoGenerator.from_config( + GeneratorConfig( + model_path="FastVideo/stable-audio-open-1.0-Diffusers", + engine=EngineConfig(num_gpus=1), + )) + generator.generate( + GenerationRequest( + prompt=PROMPT, + output=OutputConfig( + 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..f86aa3366 100644 --- a/examples/inference/basic/basic_stable_audio_small.py +++ b/examples/inference/basic/basic_stable_audio_small.py @@ -28,24 +28,27 @@ Prerequisites: same as `basic_stable_audio.py`. The converted repo is public so no gated-access flow is required. """ from fastvideo import VideoGenerator +from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, + OutputConfig) PROMPT = "Lo-fi hip hop instrumental with vinyl crackle and gentle piano." def main() -> None: - generator = VideoGenerator.from_pretrained( - "FastVideo/stable-audio-open-small-Diffusers", - num_gpus=1, - ) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="FastVideo/stable-audio-open-small-Diffusers", + engine=EngineConfig(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( + GenerationRequest( + prompt=PROMPT, + output=OutputConfig(output_path=output_path, save_video=True), + # Small variant trains on a ~11.9s window — keep `audio_end_in_s` + # at or below that. + extensions={"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 b3e314869..c788b5857 100644 --- a/examples/inference/basic/basic_turbodiffusion.py +++ b/examples/inference/basic/basic_turbodiffusion.py @@ -4,6 +4,9 @@ import os os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "SLA_ATTN" from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig, SamplingConfig, +) OUTPUT_PATH = "video_samples_turbodiffusion" @@ -11,14 +14,17 @@ OUTPUT_PATH = "video_samples_turbodiffusion" def main() -> None: # TurboDiffusion: 1-4 step video generation using RCM scheduler + SLA attention # FastVideo will automatically use TurboDiffusionPipeline when specified - generator = VideoGenerator.from_pretrained( - "loayrashid/TurboWan2.1-T2V-1.3B-Diffusers", - # FastVideo will automatically handle distributed setup - num_gpus=1, - use_fsdp_inference=False, # set to True if GPU is out of memory - - # set to false if using RTX 4090 - # pin_cpu_memory=False, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="loayrashid/TurboWan2.1-T2V-1.3B-Diffusers", + # FastVideo will automatically handle distributed setup + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + ), + # set to false if using RTX 4090 + # pin_cpu_memory=False, + ) ) # Generate videos with the same simple API, regardless of GPU count @@ -28,11 +34,17 @@ def main() -> None: "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( + GenerationRequest( + prompt=prompt, + output=OutputConfig( + output_path=OUTPUT_PATH, + save_video=True, + ), + sampling=SamplingConfig( + seed=42, + ), + ) ) # Generate another video with a different prompt, without reloading the model! @@ -43,11 +55,17 @@ def main() -> None: "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( + GenerationRequest( + prompt=prompt2, + output=OutputConfig( + output_path=OUTPUT_PATH, + save_video=True, + ), + sampling=SamplingConfig( + seed=42, + ), + ) ) diff --git a/examples/inference/basic/basic_turbodiffusion_14b.py b/examples/inference/basic/basic_turbodiffusion_14b.py index 50cfd337b..0ef494f63 100644 --- a/examples/inference/basic/basic_turbodiffusion_14b.py +++ b/examples/inference/basic/basic_turbodiffusion_14b.py @@ -4,6 +4,9 @@ import os os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "SLA_ATTN" from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig, SamplingConfig, +) OUTPUT_PATH = "video_samples_turbodiffusion_14B" @@ -11,10 +14,12 @@ OUTPUT_PATH = "video_samples_turbodiffusion_14B" def main() -> None: # TurboDiffusion 14B: 1-4 step video generation using RCM scheduler + SLA attention # FastVideo will automatically use TurboDiffusionPipeline when specified - generator = VideoGenerator.from_pretrained( - "loayrashid/TurboWan2.1-T2V-14B-Diffusers", - # 14B model needs more GPUs - num_gpus=2, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="loayrashid/TurboWan2.1-T2V-14B-Diffusers", + # 14B model needs more GPUs + engine=EngineConfig(num_gpus=2), + ) ) prompt = ( @@ -22,11 +27,12 @@ def main() -> None: "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( + GenerationRequest( + prompt=prompt, + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + sampling=SamplingConfig(seed=42), + ) ) # Generate another video with a different prompt, without reloading the model! @@ -37,11 +43,12 @@ def main() -> None: "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( + GenerationRequest( + prompt=prompt2, + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + sampling=SamplingConfig(seed=42), + ) ) diff --git a/examples/inference/basic/basic_turbodiffusion_i2v.py b/examples/inference/basic/basic_turbodiffusion_i2v.py index 655dbbbdd..126d8c3ac 100644 --- a/examples/inference/basic/basic_turbodiffusion_i2v.py +++ b/examples/inference/basic/basic_turbodiffusion_i2v.py @@ -4,6 +4,10 @@ import os os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "SLA_ATTN" from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, + OutputConfig, SamplingConfig, +) # Use local model path MODEL_PATH = "loayrashid/TurboWan2.2-I2V-A14B-Diffusers" @@ -12,9 +16,11 @@ OUTPUT_PATH = "video_samples_turbodiffusion_i2v" def main() -> None: # TurboDiffusion I2V: 1-4 step image-to-video generation - generator = VideoGenerator.from_pretrained( - MODEL_PATH, - num_gpus=2, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=MODEL_PATH, + engine=EngineConfig(num_gpus=2), + ) ) # Example prompt and image for I2V @@ -24,12 +30,13 @@ def main() -> None: 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( + GenerationRequest( + prompt=prompt, + inputs=InputConfig(image_path=image_path), + sampling=SamplingConfig(seed=42), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + ) ) diff --git a/examples/inference/basic/basic_wan2_2.py b/examples/inference/basic/basic_wan2_2.py index e6758347c..0c288e7df 100644 --- a/examples/inference/basic/basic_wan2_2.py +++ b/examples/inference/basic/basic_wan2_2.py @@ -1,6 +1,8 @@ from fastvideo import VideoGenerator - -# from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig, + OutputConfig, SamplingConfig, +) OUTPUT_PATH = "video_samples_wan2_2_14B_t2v" def main(): @@ -8,30 +10,37 @@ 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( - "Wan-AI/Wan2.2-T2V-A14B-Diffusers", - # FastVideo will automatically handle distributed setup - num_gpus=2, - 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, - # 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, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="Wan-AI/Wan2.2-T2V-A14B-Diffusers", + engine=EngineConfig( + # FastVideo will automatically handle distributed setup + num_gpus=2, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + 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, + ), + ), + ) ) - # 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( + GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(height=720, width=1280, num_frames=81), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + ) + ) # Generate another video with a different prompt, without reloading the # model! @@ -41,8 +50,14 @@ 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( + GenerationRequest( + prompt=prompt2, + sampling=SamplingConfig(height=720, width=1280, num_frames=81), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + ) + ) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/inference/basic/basic_wan2_2_Fun.py b/examples/inference/basic/basic_wan2_2_Fun.py index b3d6f1229..63a8aa5da 100644 --- a/examples/inference/basic/basic_wan2_2_Fun.py +++ b/examples/inference/basic/basic_wan2_2_Fun.py @@ -1,6 +1,12 @@ from fastvideo import VideoGenerator - -# from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + InputConfig, + OffloadConfig, + OutputConfig, +) OUTPUT_PATH = "video_samples_wan2_1_Fun" OUTPUT_NAME = "wan2.1_test" @@ -9,18 +15,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( - "IRMChen/Wan2.1-Fun-1.3B-Control-Diffusers", - # "alibaba-pai/Wan2.2-Fun-A14B-Control", - # 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, - # 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, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="IRMChen/Wan2.1-Fun-1.3B-Control-Diffusers", + # "alibaba-pai/Wan2.2-Fun-A14B-Control", + engine=EngineConfig( + # FastVideo will automatically handle distributed setup + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + 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, + ), + ), + ) ) prompt = "一位年轻女性穿着一件粉色的连衣裙,裙子上有白色的装饰和粉色的纽扣。她的头发是紫色的,头上戴着一个红色的大蝴蝶结,显得非常可爱和精致。她还戴着一个红色的领结,整体造型充满了少女感和活力。她的表情温柔,双手轻轻交叉放在身前,姿态优雅。背景是简单的灰色,没有任何多余的装饰,使得人物更加突出。她的妆容清淡自然,突显了她的清新气质。整体画面给人一种甜美、梦幻的感觉,仿佛置身于童话世界中。" @@ -30,7 +42,14 @@ 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( + GenerationRequest( + prompt=prompt, + negative_prompt=negative_prompt, + inputs=InputConfig(image_path=image_path, video_path=control_video_path), + output=OutputConfig(output_path=OUTPUT_PATH, output_video_name=OUTPUT_NAME, save_video=True), + ) + ) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/inference/basic/basic_wan2_2_i2v.py b/examples/inference/basic/basic_wan2_2_i2v.py index 15babf2aa..124174c9d 100644 --- a/examples/inference/basic/basic_wan2_2_i2v.py +++ b/examples/inference/basic/basic_wan2_2_i2v.py @@ -1,6 +1,8 @@ from fastvideo import VideoGenerator - -# from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, + OffloadConfig, OutputConfig, SamplingConfig, +) OUTPUT_PATH = "video_samples_wan2_2_14B_i2v" def main(): @@ -8,23 +10,36 @@ 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( - "Wan-AI/Wan2.2-I2V-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, - # 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, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="Wan-AI/Wan2.2-I2V-A14B-Diffusers", + # FastVideo will automatically handle distributed setup + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + 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, + ), + ), + ) ) 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( + GenerationRequest( + prompt=prompt, + inputs=InputConfig(image_path=image_path), + sampling=SamplingConfig(height=832, width=480, num_frames=81), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + ) + ) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/inference/basic/basic_wan2_2_ti2v.py b/examples/inference/basic/basic_wan2_2_ti2v.py index df0c18848..bb1202926 100644 --- a/examples/inference/basic/basic_wan2_2_ti2v.py +++ b/examples/inference/basic/basic_wan2_2_ti2v.py @@ -1,4 +1,7 @@ from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig, +) OUTPUT_PATH = "video_samples_wan2_2_5B_ti2v" def main(): @@ -7,22 +10,34 @@ def main(): # 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=False, # set to True if GPU is out of memory - dit_cpu_offload=True, - 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" - # image_encoder_cpu_offload=False, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=model_name, + engine=EngineConfig( + # FastVideo will automatically handle distributed setup + num_gpus=1, + use_fsdp_inference=False, # set to True if GPU is out of memory + offload=OffloadConfig( + dit=True, + vae=False, + text_encoder=True, + pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument" + # image_encoder=False, + ), + ), + ) ) # 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( + GenerationRequest( + prompt=prompt, + inputs=InputConfig(image_path=image_path), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + ) + ) # Generate another video with a different prompt, without reloading the # model! @@ -34,8 +49,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( + GenerationRequest( + prompt=prompt2, + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + ) + ) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/inference/eval/all_metrics_demo.py b/examples/inference/eval/all_metrics_demo.py index 471ff489a..61214e1f0 100644 --- a/examples/inference/eval/all_metrics_demo.py +++ b/examples/inference/eval/all_metrics_demo.py @@ -50,23 +50,33 @@ N_DUP = 4 # how many times to duplicate the video for the gen/ref corpora def generate_one_ltx2_video() -> str: os.environ.setdefault("FASTVIDEO_ATTENTION_BACKEND", "FLASH_ATTN") from fastvideo import VideoGenerator + from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, + OutputConfig, SamplingConfig) Path(OUTPUT_PATH).parent.mkdir(parents=True, exist_ok=True) # Davids048/LTX2-Base-Diffusers is the audio-capable LTX-2 checkpoint # (the Distilled variant ships without the audio VAE, so its mp4 # audio track is silence/noise — unusable for audio.* metrics). - generator = VideoGenerator.from_pretrained( - "Davids048/LTX2-Base-Diffusers", - num_gpus=1, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="Davids048/LTX2-Base-Diffusers", + engine=EngineConfig(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( + GenerationRequest( + prompt=PROMPT, + sampling=SamplingConfig( + num_frames=121, # ~5s @ 24 fps — long enough for audio.desync (Synchformer ≥14 segments) + height=480, + width=832, + fps=24, + ), + output=OutputConfig( + output_path=OUTPUT_PATH, + save_video=True, + ), + ) ) generator.shutdown() torch.cuda.empty_cache() diff --git a/examples/inference/eval/basic_ltx2_audio_eval.py b/examples/inference/eval/basic_ltx2_audio_eval.py index 986ebea22..ad75fd3f7 100644 --- a/examples/inference/eval/basic_ltx2_audio_eval.py +++ b/examples/inference/eval/basic_ltx2_audio_eval.py @@ -21,6 +21,10 @@ Install: ``uv pip install -e .[eval-audio]`` covers both metrics here import torch from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig, + SamplingConfig, +) from fastvideo.eval import create_evaluator PROMPT = ( @@ -39,20 +43,26 @@ METRICS = [ def main() -> None: - generator = VideoGenerator.from_pretrained( - "Davids048/LTX2-Base-Diffusers", - num_gpus=1, - ) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="Davids048/LTX2-Base-Diffusers", + engine=EngineConfig(num_gpus=1), + )) 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( + GenerationRequest( + prompt=PROMPT, + sampling=SamplingConfig( + num_frames=121, + height=1088, + width=1920, + ), + output=OutputConfig( + 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 caeb2293c..0335233f9 100644 --- a/examples/inference/eval/basic_ltx2_eval.py +++ b/examples/inference/eval/basic_ltx2_eval.py @@ -22,6 +22,10 @@ sharing, or run on a smaller-resolution generation. import torch from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig, + SamplingConfig, +) from fastvideo.eval import Evaluator from fastvideo.eval.io import build_eval_kwargs @@ -58,19 +62,20 @@ METRICS = [ def main() -> None: # ----- generation (matches examples/inference/basic/basic_ltx2.py) ----- - generator = VideoGenerator.from_pretrained( - "Davids048/LTX2-Base-Diffusers", - num_gpus=1, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path="Davids048/LTX2-Base-Diffusers", + engine=EngineConfig(num_gpus=1), + ) ) 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( + GenerationRequest( + prompt=PROMPT, + output=OutputConfig(output_path=output_path, save_video=True), + sampling=SamplingConfig(num_frames=121, height=1088, width=1920), + ) ) generator.shutdown() # Free residual CUDA memory the generator left behind so the diff --git a/examples/inference/eval/bench_physics_iq.py b/examples/inference/eval/bench_physics_iq.py index a33298e67..9dcc213b7 100644 --- a/examples/inference/eval/bench_physics_iq.py +++ b/examples/inference/eval/bench_physics_iq.py @@ -45,6 +45,9 @@ def _generate_videos(rows: list[dict], videos_dir: Path, model: str, num_gpus: int, num_frames: int, height: int, width: int) -> None: from fastvideo import VideoGenerator + from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig, SamplingConfig, + ) videos_dir.mkdir(parents=True, exist_ok=True) todo = [(row, videos_dir / _expected_filename(row)) for row in rows] @@ -55,13 +58,16 @@ def _generate_videos(rows: list[dict], videos_dir: Path, print(f"[gen] {len(todo)}/{len(rows)} scenarios to render with {model} " f"({num_frames}x{height}x{width})...") - gen = VideoGenerator.from_pretrained(model, num_gpus=num_gpus) + gen = VideoGenerator.from_config(GeneratorConfig( + model_path=model, engine=EngineConfig(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(GenerationRequest( + prompt=row["prompt"], + sampling=SamplingConfig(num_frames=num_frames, height=height, width=width), + output=OutputConfig(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 d2a5d8e43..5073efc39 100644 --- a/examples/inference/eval/bench_vbench.py +++ b/examples/inference/eval/bench_vbench.py @@ -43,6 +43,8 @@ def _generate_videos(prompts: list[str], videos_dir: Path, model: str, num_gpus: int, num_frames: int, height: int, width: int) -> None: from fastvideo import VideoGenerator + from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, + OutputConfig, SamplingConfig) videos_dir.mkdir(parents=True, exist_ok=True) todo = [(p, videos_dir / f"{_slugify(p)}.mp4") for p in prompts] @@ -53,13 +55,15 @@ def _generate_videos(prompts: list[str], videos_dir: Path, print(f"[gen] {len(todo)}/{len(prompts)} prompts to render with {model} " f"({num_frames}x{height}x{width})...") - gen = VideoGenerator.from_pretrained(model, num_gpus=num_gpus) + gen = VideoGenerator.from_config(GeneratorConfig( + model_path=model, engine=EngineConfig(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(GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(num_frames=num_frames, height=height, width=width), + output=OutputConfig(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 b2d7760e6..773428425 100644 --- a/examples/inference/eval/eval_ltx2_vbench.py +++ b/examples/inference/eval/eval_ltx2_vbench.py @@ -33,6 +33,13 @@ import json from pathlib import Path from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + OutputConfig, + SamplingConfig, +) from fastvideo.eval import create_evaluator from fastvideo.eval.io import load_video @@ -99,16 +106,27 @@ def generate(args: argparse.Namespace) -> Path: out.parent.mkdir(parents=True, exist_ok=True) print(f"[gen] loading {args.model} ({args.num_gpus} GPU)...") - generator = VideoGenerator.from_pretrained(args.model, num_gpus=args.num_gpus) + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=args.model, + engine=EngineConfig(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( + GenerationRequest( + prompt=args.prompt, + sampling=SamplingConfig( + num_frames=args.num_frames, + height=args.height, + width=args.width, + ), + output=OutputConfig( + output_path=str(out), + save_video=True, + ), + ) ) finally: generator.shutdown() diff --git a/examples/inference/gradio/local/README.md b/examples/inference/gradio/local/README.md index f744dbf46..de693e3a7 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(GenerationRequest(...))`) ## Gradio Interface diff --git a/examples/inference/gradio/local/gradio_local_demo.py b/examples/inference/gradio/local/gradio_local_demo.py index d3683e23d..ee11f1209 100644 --- a/examples/inference/gradio/local/gradio_local_demo.py +++ b/examples/inference/gradio/local/gradio_local_demo.py @@ -5,7 +5,13 @@ import time import gradio as gr from fastvideo.entrypoints.video_generator import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + GenerationRequest, + GeneratorConfig, + OutputConfig, + SamplingConfig, + SamplingParam, +) from copy import deepcopy @@ -129,9 +135,22 @@ 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( + GenerationRequest( + prompt=prompt, + negative_prompt=params.negative_prompt, + sampling=SamplingConfig( + seed=int(params.seed), + guidance_scale=params.guidance_scale, + num_frames=int(params.num_frames), + height=int(params.height), + width=int(params.width), + ), + output=OutputConfig(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 = [ @@ -550,7 +569,7 @@ def main(): for model_path in model_paths: print(f"Loading model: {model_path}") setup_model_environment(model_path) - generators[model_path] = VideoGenerator.from_pretrained(model_path) + generators[model_path] = VideoGenerator.from_config(GeneratorConfig(model_path=model_path)) default_params[model_path] = SamplingParam.from_pretrained(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/README.md b/examples/inference/gradio/local/gradio_local_demo_ltx2_3/README.md index 3f7bd7a9e..95379bbda 100644 --- a/examples/inference/gradio/local/gradio_local_demo_ltx2_3/README.md +++ b/examples/inference/gradio/local/gradio_local_demo_ltx2_3/README.md @@ -55,10 +55,11 @@ demo can actually boot: `fastvideo/fastvideo_args.py` currently wires only `ltx2_vae_tiling`. The backing stages (`ltx2_refine.py`, `ltx2_i2v_conditioning.py`) are also missing from `fastvideo/pipelines/stages/`. -3. **`fastvideo.configs.sample.base.SamplingParam`** — the import path used - by this demo. Upstream moved sampling params to - `fastvideo.api.sampling_param`. A re-export shim at the old path, or an - import update here once the other two prereqs land, will resolve it. +3. **`SamplingParam`** — now imported from `fastvideo.api` (the public + re-export of `fastvideo.api.sampling_param`); the old + `fastvideo.configs.sample.base` path was removed upstream. `SamplingParam` + here only sources model-default slider values — generation itself runs + through the typed `GenerationRequest` / `generator.generate(...)` path. ## Environment variables 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 4b53d698f..60b835b3a 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 @@ -4,8 +4,16 @@ from pathlib import Path import gradio as gr +from fastvideo.api import ( + CompileConfig, + ComponentConfig, + EngineConfig, + GeneratorConfig, + OffloadConfig, + PipelineSelection, + SamplingParam, +) from fastvideo.configs.pipelines.base import PipelineConfig -from fastvideo.configs.sample.base import SamplingParam from fastvideo.entrypoints.video_generator import VideoGenerator from fastvideo.layers.quantization.fp4_config import FP4Config from fastvideo.utils import maybe_download_model @@ -48,28 +56,44 @@ 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, - }, - dit_cpu_offload=False, - vae_cpu_offload=False, - text_encoder_cpu_offload=False, - ltx2_vae_tiling=False, + generators[model_path] = VideoGenerator.from_config( + GeneratorConfig( + model_path=str(resolved_model_path), + engine=EngineConfig( + num_gpus=1, + offload=OffloadConfig( + dit=False, + vae=False, + text_encoder=False, + ), + compile=CompileConfig( + enabled=True, + text_encoder_enabled=True, + backend="inductor", + fullgraph=True, + mode="max-autotune-no-cudagraphs", + dynamic=False, + ), + ), + pipeline=PipelineSelection( + components=ComponentConfig( + upsampler_weights=str(refine_upsampler_path), + # Empty refine LoRA path (distilled needs none) -> omit. + ), + vae_tiling=False, + preset_overrides={ + "refine": { + "enabled": True, + "num_inference_steps": 2, + "guidance_scale": 1.0, + "add_noise": True, + }, + }, + # PipelineConfig object (with FP4 quant wired on above) has + # no first-class typed field; route via experimental. + experimental={"pipeline_config": pipeline_config}, + ), + ) ) default_params[model_path] = apply_ltx2_defaults( SamplingParam.from_pretrained(str(resolved_model_path)) diff --git a/examples/inference/gradio/local/gradio_local_demo_ltx2_3/config.py b/examples/inference/gradio/local/gradio_local_demo_ltx2_3/config.py index d661d365a..61d0b8e92 100644 --- a/examples/inference/gradio/local/gradio_local_demo_ltx2_3/config.py +++ b/examples/inference/gradio/local/gradio_local_demo_ltx2_3/config.py @@ -4,7 +4,7 @@ from pathlib import Path import torch import torch._inductor.config -from fastvideo.configs.sample.base import SamplingParam +from fastvideo.api import SamplingParam LOCAL_DEMO_DIR = Path(__file__).resolve().parent CLASSIFIER_DIR = Path( 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 1c658391f..210d287f0 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 @@ -5,8 +5,14 @@ from copy import deepcopy import gradio as gr -from fastvideo.configs.sample.base import SamplingParam -from fastvideo.entrypoints.video_generator import VideoGenerator +from fastvideo import VideoGenerator +from fastvideo.api import ( + GenerationRequest, + InputConfig, + OutputConfig, + SamplingConfig, + SamplingParam, +) from .config import ( DEFAULT_FPS, @@ -69,40 +75,38 @@ 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( + GenerationRequest( + prompt=prompt, + negative_prompt=params.negative_prompt, + inputs=InputConfig(image_path=params.image_path), + sampling=SamplingConfig( + seed=int(params.seed), + fps=DEFAULT_FPS, + 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=OutputConfig( + output_path=output_path, + save_video=True, + return_frames=False, + ), + # LTX-2 i2v knob without a first-class typed field yet. + 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.video_path or 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/local/gradio_local_demo_matrixgame2.py b/examples/inference/gradio/local/gradio_local_demo_matrixgame2.py index dc88f434f..55f9f80d3 100644 --- a/examples/inference/gradio/local/gradio_local_demo_matrixgame2.py +++ b/examples/inference/gradio/local/gradio_local_demo_matrixgame2.py @@ -9,6 +9,7 @@ import uvicorn from fastapi import FastAPI, Request, HTTPException from fastapi.responses import HTMLResponse, FileResponse +from fastvideo.api import EngineConfig, GeneratorConfig, OffloadConfig from fastvideo.entrypoints.streaming_generator import StreamingVideoGenerator from fastvideo.models.dits.matrixgame2.utils import expand_action_to_frames @@ -572,14 +573,20 @@ def main(): print(f"Loading model: {model_path}") setup_model_environment(model_path) - generator = StreamingVideoGenerator.from_pretrained( - model_path, - num_gpus=1, - use_fsdp_inference=True, - dit_cpu_offload=True, - vae_cpu_offload=False, - text_encoder_cpu_offload=True, - pin_cpu_memory=True, + generator = StreamingVideoGenerator.from_config( + GeneratorConfig( + model_path=model_path, + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + offload=OffloadConfig( + dit=True, + vae=False, + text_encoder=True, + pin_cpu_memory=True, + ), + ), + ) ) generators = {model_path: generator} diff --git a/examples/inference/gradio/serving/ray_serve_backend.py b/examples/inference/gradio/serving/ray_serve_backend.py index 2a783207b..3dfbd2980 100644 --- a/examples/inference/gradio/serving/ray_serve_backend.py +++ b/examples/inference/gradio/serving/ray_serve_backend.py @@ -3,7 +3,6 @@ import os import torch import base64 import io -from copy import deepcopy from typing import Dict, Any, Optional, List import signal import sys @@ -20,6 +19,17 @@ import imageio from ray.serve.handle import DeploymentHandle from prometheus_client import Counter, Histogram, generate_latest +from fastvideo.api import ( + EngineConfig, + GenerationRequest, + GeneratorConfig, + InputConfig, + OffloadConfig, + OutputConfig, + PipelineSelection, + SamplingConfig, +) + NUM_GPUS = 16 DEFAULT_FPS = 16 SEED_RANGE_MAX = 1_000_000 @@ -136,10 +146,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 - - logging_info = result.get("logging_info", None) + frames = result.frames or [] + generation_time = result.generation_time or 0.0 + + logging_info = result.logging_info if logging_info: stage_names = logging_info.get_execution_order() stage_execution_times = [ @@ -153,24 +163,27 @@ def process_generation_result(result: Any) -> tuple[List[np.ndarray], float, Lis return frames, generation_time, stage_names, stage_execution_times -def prepare_sampling_params(video_request: VideoGenerationRequest, default_params: Any) -> Any: - params = deepcopy(default_params) - params.prompt = video_request.prompt - - if video_request.use_negative_prompt: - params.negative_prompt = video_request.negative_prompt +def prepare_generation_request(video_request: VideoGenerationRequest, image_path: Optional[str] = None) -> Any: + seed = (video_request.seed if not video_request.randomize_seed + else torch.randint(0, SEED_RANGE_MAX, (1,)).item()) - params.seed = (video_request.seed if not video_request.randomize_seed - else torch.randint(0, SEED_RANGE_MAX, (1,)).item()) - params.randomize_seed = video_request.randomize_seed - params.guidance_scale = video_request.guidance_scale - params.num_frames = video_request.num_frames - params.height = video_request.height - params.width = video_request.width - params.save_video = False - params.return_frames = True - - return params + negative_prompt = video_request.negative_prompt if video_request.use_negative_prompt else None + + request = GenerationRequest( + prompt=video_request.prompt, + negative_prompt=negative_prompt, + inputs=InputConfig(image_path=image_path), + sampling=SamplingConfig( + seed=seed, + guidance_scale=video_request.guidance_scale, + num_frames=video_request.num_frames, + height=video_request.height, + width=video_request.width, + ), + output=OutputConfig(save_video=False, return_frames=True), + ) + + return request, seed class BaseModelDeployment: @@ -185,31 +198,34 @@ class BaseModelDeployment: def _initialize_generator(self, config: Dict[str, Any]) -> None: from fastvideo.entrypoints.video_generator import VideoGenerator - 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( + GeneratorConfig( + model_path=self.model_path, + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + enable_stage_verification=False, + offload=OffloadConfig( + text_encoder=config["text_encoder_cpu_offload"], + dit=config["dit_cpu_offload"], + vae=config["vae_cpu_offload"], + ), + ), + pipeline=PipelineSelection( + # I2V knobs without first-class typed fields yet. + experimental={ + "dmd_denoising_steps": [1000, 850, 700, 550, 350, 275, 200, 125], + "dit_precision": "fp32", + "VSA_sparsity": config["VSA_sparsity"], + }, + ), + ) ) - self.default_params = SamplingParam.from_pretrained(self.model_path) - self.default_params.seed = 1000 - self.default_params.num_frames = 73 - self.default_params.width = 832 - self.default_params.height = 480 def generate_video(self, video_request: VideoGenerationRequest) -> VideoGenerationResponse: total_start_time = time.time() - - params = prepare_sampling_params(video_request, self.default_params) # Save image if provided (for I2V) image_path = None @@ -218,19 +234,15 @@ class BaseModelDeployment: if image_path is None: return VideoGenerationResponse( video_data=None, - seed=params.seed, + seed=video_request.seed, success=False, error_message="Failed to save input image", ) + request, seed = prepare_generation_request(video_request, image_path) + 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(request) inference_time = time.time() - inference_start_time frames, generation_time, stage_names, stage_execution_times = process_generation_result(result) @@ -250,7 +262,7 @@ class BaseModelDeployment: return VideoGenerationResponse( video_data=video_data, - seed=params.seed, + seed=seed, success=True, generation_time=generation_time, inference_time=inference_time, diff --git a/examples/inference/lora/wan_lora_inference.py b/examples/inference/lora/wan_lora_inference.py index 79fe4300e..0bd55b2e1 100644 --- a/examples/inference/lora/wan_lora_inference.py +++ b/examples/inference/lora/wan_lora_inference.py @@ -1,18 +1,31 @@ from fastvideo import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import ( + ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig, + OffloadConfig, OutputConfig, PipelineSelection, SamplingConfig, +) 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( + GeneratorConfig( + model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + engine=EngineConfig( + num_gpus=1, + offload=OffloadConfig( + 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=PipelineSelection( + components=ComponentConfig( + lora_path="benjamin-paine/steamboat-willie-1.3b", + ), + experimental={"lora_nickname": "steamboat"}, + ), + ) ) kwargs = { "height": 480, @@ -26,25 +39,32 @@ 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( + GenerationRequest( + prompt=prompt, + negative_prompt=negative_prompt, + sampling=SamplingConfig(**kwargs), + output=OutputConfig( + 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( + GenerationRequest( + prompt=prompt, + negative_prompt=negative_prompt, + sampling=SamplingConfig(**kwargs), + output=OutputConfig( + output_path=OUTPUT_PATH, + save_video=True, + ), + ) ) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/inference/lora/wan_lora_inference_from_ckpt.py b/examples/inference/lora/wan_lora_inference_from_ckpt.py index ca7032133..19b22283f 100644 --- a/examples/inference/lora/wan_lora_inference_from_ckpt.py +++ b/examples/inference/lora/wan_lora_inference_from_ckpt.py @@ -2,45 +2,60 @@ Inference using a LoRA checkpoint from FastVideo trainer. """ from fastvideo import VideoGenerator -from fastvideo.api.sampling_param import SamplingParam +from fastvideo.api import (ComponentConfig, EngineConfig, GenerationRequest, + GeneratorConfig, OffloadConfig, OutputConfig, + PipelineSelection, SamplingConfig) 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( + GeneratorConfig( + model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + engine=EngineConfig( + num_gpus=1, + offload=OffloadConfig( + 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=PipelineSelection( + components=ComponentConfig( + lora_path="checkpoints/wan_t2v_finetune_lora/checkpoint-160/transformer", + ), + experimental={"lora_nickname": "crush_smol"}, + ), + )) generator.unmerge_lora_weights() - kwargs = { - "height": 480, - "width": 832, - "num_frames": 77, - "guidance_scale": 6.0, - "num_inference_steps": 50, - "seed": 42, - } + sampling = SamplingConfig( + height=480, + width=832, + num_frames=77, + guidance_scale=6.0, + num_inference_steps=50, + seed=42, + ) + output = OutputConfig( + output_path=OUTPUT_PATH, + save_video=True, + ) # 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( + GenerationRequest( + prompt=prompt, + sampling=sampling, + output=output, + )) 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( + GenerationRequest( + prompt=prompt, + sampling=sampling, + output=output, + )) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/inference/optimizations/FastWan_QAD_TAEHV.py b/examples/inference/optimizations/FastWan_QAD_TAEHV.py index aa4b6840d..3eeec5f33 100644 --- a/examples/inference/optimizations/FastWan_QAD_TAEHV.py +++ b/examples/inference/optimizations/FastWan_QAD_TAEHV.py @@ -37,6 +37,17 @@ import imageio import torch from fastvideo import VideoGenerator +from fastvideo.api import ( + CompileConfig, + ComponentConfig, + EngineConfig, + GenerationRequest, + GeneratorConfig, + OffloadConfig, + OutputConfig, + PipelineSelection, + SamplingConfig, +) from fastvideo.configs.pipelines.base import PipelineConfig from fastvideo.layers.quantization.nvfp4_qat_config import NVFP4QATConfig @@ -148,35 +159,49 @@ def build_generator(args: argparse.Namespace) -> VideoGenerator: compile_enabled = not args.no_compile - extra_kwargs = {} + # ``pipeline_config`` is a PipelineConfig object (not a string path) and + # ``output_type`` has no first-class typed field, so both are routed through + # the pipeline experimental escape hatch. + experimental = {"pipeline_config": pipeline_config} + + components = ComponentConfig() 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_config = GeneratorConfig( + model_path=model_id, + engine=EngineConfig( + 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=OffloadConfig( + dit=False, + dit_layerwise=False, + vae=args.taehv, + text_encoder=False, + pin_cpu_memory=False, + ), + compile=CompileConfig( + enabled=compile_enabled, + text_encoder_enabled=compile_enabled, + vae_enabled=compile_enabled and not args.taehv, + ), + ), + pipeline=PipelineSelection( + components=components, + experimental=experimental, + ), ) + generator = VideoGenerator.from_config(generator_config) return generator @@ -237,11 +262,11 @@ def main() -> None: # runs below measure steady-state latency only. with silence_request_log(): for _ in range(args.warmups): - warm = generator.generate(request={ - "prompt": PROMPT, - "sampling": {"num_inference_steps": 2, "guidance_scale": args.guidance_scale}, - "output": {"save_video": False, "return_frames": args.taehv}, - }) + warm = generator.generate(GenerationRequest( + prompt=PROMPT, + sampling=SamplingConfig(num_inference_steps=2, guidance_scale=args.guidance_scale), + output=OutputConfig(save_video=False, return_frames=args.taehv), + )) if args.taehv: taehv.decode(warm.samples) @@ -257,18 +282,18 @@ def main() -> None: frames = None with silence_request_log(): for i in range(args.benchmark_runs): - result = generator.generate(request={ - "prompt": PROMPT, - "sampling": { - "num_inference_steps": args.infer_steps, - "guidance_scale": args.guidance_scale, - }, - "output": { - "save_video": False, - "return_frames": args.taehv, - "output_path": output_path, - }, - }) + result = generator.generate(GenerationRequest( + prompt=PROMPT, + sampling=SamplingConfig( + num_inference_steps=args.infer_steps, + guidance_scale=args.guidance_scale, + ), + output=OutputConfig( + save_video=False, + return_frames=args.taehv, + output_path=output_path, + ), + )) denoise_elapsed = result.generation_time denoise_times.append(denoise_elapsed) diff --git a/examples/inference/optimizations/attention_example.py b/examples/inference/optimizations/attention_example.py index 558a001d2..77bccc457 100644 --- a/examples/inference/optimizations/attention_example.py +++ b/examples/inference/optimizations/attention_example.py @@ -2,31 +2,41 @@ import os import time from fastvideo import VideoGenerator +from fastvideo.api import ( + EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig, + OutputConfig, SamplingConfig, +) def main(): - # set the attention backend + # set the attention backend os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "FLASH_ATTN" start_time = time.perf_counter() - gen = VideoGenerator.from_pretrained( - model_path="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" - ) + gen = VideoGenerator.from_config( + GeneratorConfig( + model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + engine=EngineConfig( + num_gpus=1, + offload=OffloadConfig( + 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" + ), + ), + )) load_time = time.perf_counter() - start_time print(f"Model loading time: {load_time:.2f} seconds") gen_start_time = time.perf_counter() - gen.generate_video( - 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/") - + gen.generate( + GenerationRequest( + 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.", + sampling=SamplingConfig(seed=1024), + output=OutputConfig(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/fp4_attn_wan2_1_1_3b.py b/examples/inference/optimizations/fp4_attn_wan2_1_1_3b.py index 8e7dfa87c..3f5c71bfd 100644 --- a/examples/inference/optimizations/fp4_attn_wan2_1_1_3b.py +++ b/examples/inference/optimizations/fp4_attn_wan2_1_1_3b.py @@ -18,6 +18,10 @@ import os import time from fastvideo import VideoGenerator +from fastvideo.api import ( + CompileConfig, EngineConfig, GenerationRequest, GeneratorConfig, + OffloadConfig, OutputConfig, SamplingConfig, +) OUTPUT_PATH = "video_samples" @@ -39,17 +43,24 @@ def main(): mode += "_compile" print(f"Mode: {mode.upper()}") - generator = VideoGenerator.from_pretrained( - args.model, - num_gpus=args.num_gpus, - nvfp4_fa4=args.nvfp4_fa4, - use_fsdp_inference=not args.nvfp4_fa4, - dit_cpu_offload=False, - dit_layerwise_offload=False, - vae_cpu_offload=True, - text_encoder_cpu_offload=True, - enable_torch_compile=args.compile, - ) + if args.nvfp4_fa4: + os.environ["FASTVIDEO_NVFP4_FA4"] = "1" + os.environ.setdefault("CUTE_DSL_ENABLE_TVM_FFI", "1") + + generator = VideoGenerator.from_config(GeneratorConfig( + model_path=args.model, + engine=EngineConfig( + num_gpus=args.num_gpus, + use_fsdp_inference=not args.nvfp4_fa4, + offload=OffloadConfig( + dit=False, + dit_layerwise=False, + vae=True, + text_encoder=True, + ), + compile=CompileConfig(enabled=args.compile), + ), + )) prompt = ( "A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " @@ -59,16 +70,22 @@ def main(): n_warmup = 2 if args.compile else 1 for i in range(n_warmup): - generator.generate(request={"prompt": prompt, "sampling": {"num_inference_steps": 2}, - "output": {"save_video": False}}) + generator.generate(GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(num_inference_steps=2), + output=OutputConfig(save_video=False), + )) os.makedirs(OUTPUT_PATH, exist_ok=True) start = time.time() - generator.generate(request={ - "prompt": prompt, - "sampling": {"num_inference_steps": args.infer_steps}, - "output": {"save_video": True, "output_path": os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4")}, - }) + generator.generate(GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(num_inference_steps=args.infer_steps), + output=OutputConfig( + save_video=True, + output_path=os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4"), + ), + )) elapsed = time.time() - start print(f"[{mode.upper()}] {args.infer_steps} steps in {elapsed:.2f}s " f"({args.infer_steps / elapsed:.2f} it/s)") diff --git a/examples/inference/optimizations/fp8_wan2_1_1_3b.py b/examples/inference/optimizations/fp8_wan2_1_1_3b.py index e48d85cc8..6054b75c6 100644 --- a/examples/inference/optimizations/fp8_wan2_1_1_3b.py +++ b/examples/inference/optimizations/fp8_wan2_1_1_3b.py @@ -69,6 +69,16 @@ def main(): os.environ.setdefault("FASTVIDEO_ATTENTION_BACKEND", "SAGE_ATTN") from fastvideo import VideoGenerator + from fastvideo.api import ( + CompileConfig, + EngineConfig, + GenerationRequest, + GeneratorConfig, + OffloadConfig, + OutputConfig, + PipelineSelection, + SamplingConfig, + ) from fastvideo.layers.quantization import get_quantization_config mode = "bf16" if args.bf16 else f"fp8_{args.granularity}" @@ -79,25 +89,33 @@ 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. - 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, - ) + # ``output_type`` and ``transformer_quant`` have no first-class typed + # fields yet, so they ride the pipeline.experimental escape hatch (same + # place the legacy from_pretrained shim routed them). The typed + # QuantizationConfig only accepts a quant-name string, so it can't carry + # FP8's ``granularity`` arg — pass the resolved config instance instead. + experimental = {"output_type": "latent" if use_taehv else "pil"} + if not args.bf16: + experimental["transformer_quant"] = get_quantization_config("FP8")(granularity=args.granularity) + generator = VideoGenerator.from_config(GeneratorConfig( + model_path=args.model, + engine=EngineConfig( + num_gpus=args.num_gpus, + use_fsdp_inference=False, + offload=OffloadConfig( + dit=False, + dit_layerwise=False, + vae=use_taehv, + text_encoder=False, + pin_cpu_memory=False, + ), + compile=CompileConfig( + enabled=not args.no_compile, + vae_enabled=not args.no_compile and not use_taehv, + ), + ), + pipeline=PipelineSelection(experimental=experimental), + )) prompt = ( "A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " @@ -107,28 +125,34 @@ def main(): n_warmup = 1 if not args.no_compile else 0 for _ in range(n_warmup): - generator.generate(request={"prompt": prompt, "sampling": {"num_inference_steps": 3, "guidance_scale": 1.0}, - "output": {"save_video": False}}) + generator.generate(GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(num_inference_steps=3, guidance_scale=1.0), + output=OutputConfig(save_video=False), + )) os.makedirs(OUTPUT_PATH, exist_ok=True) start = time.time() if use_taehv: - result = generator.generate(request={ - "prompt": prompt, - "sampling": {"num_inference_steps": args.infer_steps, "guidance_scale": 1.0}, - "output": {"save_video": False}, - }) + result = generator.generate(GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(num_inference_steps=args.infer_steps, guidance_scale=1.0), + output=OutputConfig(save_video=False), + )) import imageio frames = decode_with_taehv(taehv_model, result.samples) video_path = os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4") imageio.mimsave(video_path, frames, fps=16, format="mp4") print(f"Saved TAEHV-decoded video to: {video_path}") else: - generator.generate(request={ - "prompt": prompt, - "sampling": {"num_inference_steps": args.infer_steps, "guidance_scale": 1.0}, - "output": {"save_video": True, "output_path": os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4")}, - }) + generator.generate(GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(num_inference_steps=args.infer_steps, guidance_scale=1.0), + output=OutputConfig( + save_video=True, + output_path=os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4"), + ), + )) elapsed = time.time() - start print(f"[{mode.upper()}] {args.infer_steps} steps in {elapsed:.2f}s " f"({args.infer_steps / elapsed:.2f} it/s)") 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 095c8bba4..68aff4c4f 100644 --- a/examples/inference/optimizations/nvfp4_qat_wan2_1_1_3b.py +++ b/examples/inference/optimizations/nvfp4_qat_wan2_1_1_3b.py @@ -45,26 +45,29 @@ def main(): # Import after the env var so the platform picks up the selection. from fastvideo import VideoGenerator - from fastvideo.layers.quantization import get_quantization_config + from fastvideo.api import ( + CompileConfig, EngineConfig, GenerationRequest, GeneratorConfig, + OffloadConfig, OutputConfig, QuantizationConfig, SamplingConfig, + ) mode = "bf16" if args.bf16 else args.quant_method if args.compile: mode += "_compile" print(f"Mode: {mode.upper()}") - # transformer_quant needs a QuantizationConfig *instance* — the bare string - # is not resolved on the from_pretrained kwarg 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, - ) + # transformer_quant takes the config name (string); the typed path resolves + # the QuantizationConfig class. + quantization = None if args.bf16 else QuantizationConfig(transformer_quant=args.quant_method) + generator = VideoGenerator.from_config(GeneratorConfig( + model_path=args.model, + engine=EngineConfig( + num_gpus=args.num_gpus, + use_fsdp_inference=args.bf16, + offload=OffloadConfig(dit=False, vae=True, text_encoder=True), + compile=CompileConfig(enabled=args.compile), + quantization=quantization, + ), + )) prompt = ( "A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes " @@ -74,16 +77,20 @@ def main(): n_warmup = 2 if args.compile else 1 for _ in range(n_warmup): - generator.generate(request={"prompt": prompt, "sampling": {"num_inference_steps": 2}, - "output": {"save_video": False}}) + generator.generate(GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(num_inference_steps=2), + output=OutputConfig(save_video=False), + )) os.makedirs(OUTPUT_PATH, exist_ok=True) start = time.time() - generator.generate(request={ - "prompt": prompt, - "sampling": {"num_inference_steps": args.infer_steps}, - "output": {"save_video": True, "output_path": os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4")}, - }) + generator.generate(GenerationRequest( + prompt=prompt, + sampling=SamplingConfig(num_inference_steps=args.infer_steps), + output=OutputConfig(save_video=True, + output_path=os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4")), + )) elapsed = time.time() - start print(f"[{mode.upper()}] {args.infer_steps} steps in {elapsed:.2f}s " f"({args.infer_steps / elapsed:.2f} it/s)") diff --git a/examples/inference/optimizations/text_encoder_quant_example.py b/examples/inference/optimizations/text_encoder_quant_example.py index 8b3cca506..e763c81eb 100644 --- a/examples/inference/optimizations/text_encoder_quant_example.py +++ b/examples/inference/optimizations/text_encoder_quant_example.py @@ -1,4 +1,7 @@ from fastvideo import VideoGenerator +from fastvideo.api import (ComponentConfig, EngineConfig, GenerationRequest, + GeneratorConfig, InputConfig, OffloadConfig, + OutputConfig, PipelineSelection, QuantizationConfig) import argparse OUTPUT_PATH = "video_samples_wan2_2_5B_ti2v" @@ -10,28 +13,39 @@ 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( + GeneratorConfig( + model_path=model_name, + # FastVideo will automatically handle distributed setup + engine=EngineConfig( + num_gpus=1, + use_fsdp_inference=True, + offload=OffloadConfig( + 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=QuantizationConfig(text_encoder_quant="AbsMaxFP8"), + ), + pipeline=PipelineSelection( + components=ComponentConfig( + # 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( + GenerationRequest( + prompt=prompt, + inputs=InputConfig(image_path=image_path), + output=OutputConfig(output_path=OUTPUT_PATH, save_video=True), + )) if __name__ == "__main__": diff --git a/examples/inference/optimizations/torch_compile_example.py b/examples/inference/optimizations/torch_compile_example.py index 2a7cfbf13..a2c2df548 100644 --- a/examples/inference/optimizations/torch_compile_example.py +++ b/examples/inference/optimizations/torch_compile_example.py @@ -22,6 +22,14 @@ import os import time from fastvideo import VideoGenerator +from fastvideo.api import ( + CompileConfig, + EngineConfig, + GenerationRequest, + GeneratorConfig, + OutputConfig, + SamplingConfig, +) PROMPT = ( "A high-definition video of a robotic arm welding a metal structure, " @@ -42,24 +50,27 @@ def main() -> None: os.makedirs("video_samples", exist_ok=True) - generator = VideoGenerator.from_pretrained( - args.model, - num_gpus=args.num_gpus, - enable_torch_compile=args.compile, + generator = VideoGenerator.from_config( + GeneratorConfig( + model_path=args.model, + engine=EngineConfig( + num_gpus=args.num_gpus, + compile=CompileConfig(enabled=args.compile), + ), + ) ) def _run(tag: str) -> float: save = tag == "measured" - # Modern typed-request API (generate_video is deprecated). Same - # prompt/seed/shapes both runs so the compiled graph is reused. - request: dict = { - "prompt": PROMPT, - "sampling": {"seed": 1024}, - "output": {"save_video": save}, - } + # Same prompt/seed/shapes both runs so the compiled graph is reused. + output = OutputConfig(save_video=save) if save: - request["output"]["output_path"] = ( - f"video_samples/torch_compile_{tag}.mp4") + output.output_path = f"video_samples/torch_compile_{tag}.mp4" + request = GenerationRequest( + prompt=PROMPT, + sampling=SamplingConfig(seed=1024), + output=output, + ) t0 = time.perf_counter() generator.generate(request) return time.perf_counter() - t0 diff --git a/fastvideo/api/schema.py b/fastvideo/api/schema.py index c4fba6e17..5931bb7a7 100644 --- a/fastvideo/api/schema.py +++ b/fastvideo/api/schema.py @@ -269,7 +269,7 @@ class ServeConfig: body (client-explicit) > default_request (operator-explicit) > hardcoded fallback (e.g. ``fps=24``) - See :func:`fastvideo.api.compat.explicit_request_updates` for the + See :func:`fastvideo.api.translation.explicit_request_updates` for the projection and ``entrypoints/openai/video_api.py::_build_generation_kwargs`` for the merge. """ diff --git a/fastvideo/api/compat.py b/fastvideo/api/translation.py similarity index 95% rename from fastvideo/api/compat.py rename to fastvideo/api/translation.py index ea3ff10ac..a3913a4bd 100644 --- a/fastvideo/api/compat.py +++ b/fastvideo/api/translation.py @@ -1,4 +1,22 @@ # SPDX-License-Identifier: Apache-2.0 +"""Translation layer between the public typed API and the legacy internals. + +This module (formerly ``fastvideo/api/compat.py``) is the single bridge that +converts typed public config objects (:class:`GeneratorConfig`, +:class:`GenerationRequest`) into the internal :class:`FastVideoArgs` / +:class:`SamplingParam` the runtime still consumes, plus the inbound +normalization helpers used by every public entrypoint. + +It is *internal* and not part of the stable public surface. The legacy +forward-translation helpers (``legacy_from_pretrained_to_config``, +``legacy_generate_call_to_request`` and friends) exist only to support the +deprecated ``VideoGenerator.from_pretrained(model, **kwargs)`` / +``generate_video(...)`` entry points. The reverse-translation +(``generator_config_to_fastvideo_args``, ``request_to_sampling_param``) is +slated for elimination once ``FastVideoArgs`` becomes a view over +``GeneratorConfig`` and ``ForwardBatch`` reads ``GenerationRequest`` directly +(see ``.agents/memory/dreamverse-integration/pr-roadmap.md``, PRs 15-17). +""" from __future__ import annotations from collections.abc import Mapping @@ -480,7 +498,7 @@ def explicit_request_updates(request: GenerationRequest) -> dict[str, Any]: Precondition: the request must carry ``_fastvideo_explicit_paths`` — populated by :func:`fastvideo.api.parser.parse_config` or - :func:`fastvideo.api.compat.normalize_generation_request`. Calling on + :func:`fastvideo.api.translation.normalize_generation_request`. Calling on a raw ``GenerationRequest()`` asserts. """ assert hasattr(request, diff --git a/fastvideo/entrypoints/cli/serve.py b/fastvideo/entrypoints/cli/serve.py index 6d5d13ce9..4ad503f5f 100644 --- a/fastvideo/entrypoints/cli/serve.py +++ b/fastvideo/entrypoints/cli/serve.py @@ -5,7 +5,7 @@ import argparse import os from typing import cast -from fastvideo.api.compat import generator_config_to_fastvideo_args +from fastvideo.api.translation import generator_config_to_fastvideo_args from fastvideo.entrypoints.cli.cli_types import CLISubcommand from fastvideo.entrypoints.cli.inference_config import build_serve_config from fastvideo.logger import init_logger diff --git a/fastvideo/entrypoints/openai/video_api.py b/fastvideo/entrypoints/openai/video_api.py index 4134175f3..7b2ce645a 100644 --- a/fastvideo/entrypoints/openai/video_api.py +++ b/fastvideo/entrypoints/openai/video_api.py @@ -19,7 +19,7 @@ from fastapi import ( ) from fastapi.responses import FileResponse -from fastvideo.api.compat import explicit_request_updates +from fastvideo.api.translation import explicit_request_updates from fastvideo.api.schema import GenerationRequest from fastvideo.entrypoints.openai.state import ( get_default_request, diff --git a/fastvideo/entrypoints/video_generator.py b/fastvideo/entrypoints/video_generator.py index bfc163353..d4f83e425 100644 --- a/fastvideo/entrypoints/video_generator.py +++ b/fastvideo/entrypoints/video_generator.py @@ -26,7 +26,7 @@ import torch import torchvision from einops import rearrange -from fastvideo.api.compat import ( +from fastvideo.api.translation import ( expand_request_prompt_batch, generator_config_to_fastvideo_args, legacy_from_pretrained_to_config, diff --git a/fastvideo/pipelines/basic/ltx2/continuation.py b/fastvideo/pipelines/basic/ltx2/continuation.py index 785b0484f..299735155 100644 --- a/fastvideo/pipelines/basic/ltx2/continuation.py +++ b/fastvideo/pipelines/basic/ltx2/continuation.py @@ -23,7 +23,7 @@ from collections.abc import Mapping from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any -from fastvideo.api.compat import register_continuation_kind +from fastvideo.api.translation import register_continuation_kind from fastvideo.api.schema import ContinuationState if TYPE_CHECKING: diff --git a/fastvideo/tests/api/test_cli_translation.py b/fastvideo/tests/api/test_cli_translation.py index a204daa7c..07ff5ee4d 100644 --- a/fastvideo/tests/api/test_cli_translation.py +++ b/fastvideo/tests/api/test_cli_translation.py @@ -5,7 +5,7 @@ from types import SimpleNamespace import pytest -from fastvideo.api.compat import request_to_sampling_param +from fastvideo.api.translation import request_to_sampling_param from fastvideo.entrypoints.cli import main as cli_main from fastvideo.entrypoints.cli.generate import GenerateSubcommand from fastvideo.entrypoints.cli.inference_config import ( diff --git a/fastvideo/tests/api/test_compat_translation.py b/fastvideo/tests/api/test_compat_translation.py index 6100588ef..d9924912d 100644 --- a/fastvideo/tests/api/test_compat_translation.py +++ b/fastvideo/tests/api/test_compat_translation.py @@ -1,11 +1,11 @@ # SPDX-License-Identifier: Apache-2.0 -"""Tests for ``fastvideo.api.compat`` translation helpers covering the +"""Tests for ``fastvideo.api.translation`` translation helpers covering the typed CompileConfig + PipelineSelection.vae_tiling surfaces promoted in PR 6. """ from __future__ import annotations -from fastvideo.api.compat import ( +from fastvideo.api.translation import ( generator_config_to_fastvideo_args, legacy_from_pretrained_to_config, ) diff --git a/fastvideo/tests/api/test_ltx2_continuation.py b/fastvideo/tests/api/test_ltx2_continuation.py index 9bb9dd860..10c318606 100644 --- a/fastvideo/tests/api/test_ltx2_continuation.py +++ b/fastvideo/tests/api/test_ltx2_continuation.py @@ -21,7 +21,7 @@ import torch # Importing compat first, then the LTX-2 module, exercises the # self-registration side effect on import (important for the API # test suite where the pipeline package isn't otherwise imported). -from fastvideo.api import compat as api_compat # noqa: F401 +from fastvideo.api import translation as api_compat # noqa: F401 from fastvideo.api.schema import ( ContinuationState, GenerationRequest, @@ -217,7 +217,7 @@ class TestCompatLayerWireUp: # PR 7 removes the NotImplementedError for request.state; build a # minimal GenerationRequest carrying an LTX-2 state and make sure # the public boundary accepts it. - from fastvideo.api.compat import ( + from fastvideo.api.translation import ( normalize_generation_request, _validate_continuation_state, ) @@ -230,13 +230,13 @@ class TestCompatLayerWireUp: _validate_continuation_state(normalized.state) def test_unknown_kind_rejected_at_boundary(self): - from fastvideo.api.compat import _validate_continuation_state + from fastvideo.api.translation import _validate_continuation_state with pytest.raises(ValueError, match="Unknown ContinuationState kind"): _validate_continuation_state( ContinuationState(kind="mystery.v1", payload={})) def test_empty_kind_rejected_at_boundary(self): - from fastvideo.api.compat import _validate_continuation_state + from fastvideo.api.translation import _validate_continuation_state with pytest.raises(ValueError, match="non-empty string"): _validate_continuation_state( ContinuationState(kind="", payload={})) diff --git a/fastvideo/tests/api/test_ltx2_gpu_pool_translation.py b/fastvideo/tests/api/test_ltx2_gpu_pool_translation.py index 610777f6e..f1b3aeead 100644 --- a/fastvideo/tests/api/test_ltx2_gpu_pool_translation.py +++ b/fastvideo/tests/api/test_ltx2_gpu_pool_translation.py @@ -19,7 +19,7 @@ from copy import deepcopy import pytest -from fastvideo.api.compat import ( +from fastvideo.api.translation import ( generator_config_to_fastvideo_args, legacy_from_pretrained_to_config, ) @@ -208,7 +208,7 @@ class TestRefineFlattenCoversAllTypedFields: def test_all_fields_reemitted(self, monkeypatch) -> None: from fastvideo import fastvideo_args as fva - from fastvideo.api.compat import ( + from fastvideo.api.translation import ( generator_config_to_fastvideo_args, ) from fastvideo.api.schema import GeneratorConfig, PipelineSelection diff --git a/fastvideo/tests/api/test_typed_quant_flow.py b/fastvideo/tests/api/test_typed_quant_flow.py index 69e9ebeff..4574e3beb 100644 --- a/fastvideo/tests/api/test_typed_quant_flow.py +++ b/fastvideo/tests/api/test_typed_quant_flow.py @@ -15,7 +15,7 @@ from __future__ import annotations import pytest -from fastvideo.api.compat import generator_config_to_fastvideo_args +from fastvideo.api.translation import generator_config_to_fastvideo_args from fastvideo.api.schema import ( EngineConfig, GeneratorConfig, diff --git a/fastvideo/tests/contract/test_dreamverse_shape.py b/fastvideo/tests/contract/test_dreamverse_shape.py index b418b09a5..6ac9f5479 100644 --- a/fastvideo/tests/contract/test_dreamverse_shape.py +++ b/fastvideo/tests/contract/test_dreamverse_shape.py @@ -34,7 +34,7 @@ from fastvideo.api import ( GeneratorConfig, GenerationRequest, ) -from fastvideo.api.compat import ( +from fastvideo.api.translation import ( legacy_from_pretrained_to_config, legacy_generate_call_to_request, normalize_generation_request, @@ -205,7 +205,7 @@ class TestDreamverseNoPrivateImports: [ "fastvideo", "fastvideo.api", - "fastvideo.api.compat", # public in that it's re-exported + "fastvideo.api.translation", # public in that it's re-exported ], ) def test_public_imports_resolve(self, import_path): diff --git a/fastvideo/tests/contract/test_dynamo_shape.py b/fastvideo/tests/contract/test_dynamo_shape.py index 9432449cb..1127e8f5f 100644 --- a/fastvideo/tests/contract/test_dynamo_shape.py +++ b/fastvideo/tests/contract/test_dynamo_shape.py @@ -269,7 +269,7 @@ class TestNoInternalImports: "fastvideo.pipelines.", "fastvideo.configs.", "fastvideo.fastvideo_args", - "fastvideo.api.compat", + "fastvideo.api.translation", "fastvideo.api.parser", "fastvideo.api.overrides", "fastvideo.api.errors", diff --git a/fastvideo/tests/contract/test_generate_async.py b/fastvideo/tests/contract/test_generate_async.py index 3d2c35e7f..0b685925e 100644 --- a/fastvideo/tests/contract/test_generate_async.py +++ b/fastvideo/tests/contract/test_generate_async.py @@ -199,7 +199,7 @@ class TestHealthCheckRequest: assert req.output.return_frames is False def test_round_trips_through_normalization(self): - from fastvideo.api.compat import normalize_generation_request + from fastvideo.api.translation import normalize_generation_request from fastvideo.entrypoints.video_generator import VideoGenerator req = VideoGenerator.default_health_check_request() diff --git a/fastvideo/tests/entrypoints/test_video_generator.py b/fastvideo/tests/entrypoints/test_video_generator.py index 2789f071d..dae6151fa 100644 --- a/fastvideo/tests/entrypoints/test_video_generator.py +++ b/fastvideo/tests/entrypoints/test_video_generator.py @@ -69,7 +69,7 @@ def _patch_fastvideo_args_from_kwargs(monkeypatch): ) monkeypatch.setattr( - "fastvideo.api.compat.FastVideoArgs.from_kwargs", + "fastvideo.api.translation.FastVideoArgs.from_kwargs", classmethod(fake_from_kwargs), ) return captured diff --git a/scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py b/scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py index 888781cc2..477541f47 100644 --- a/scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py +++ b/scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py @@ -13,7 +13,8 @@ The base DiT uses Wan-AI/Wan2.2-TI2V-5B's VAE and google/t5gemma-9b-9b-ul2's encoder at inference time; neither is bundled upstream. This converter takes the raw MagiHuman base DiT and emits a Diffusers-style -directory so `VideoGenerator.from_pretrained(...)` can load it standalone: +directory so `VideoGenerator.from_config(GeneratorConfig(model_path=...))` can +load it standalone: / model_index.json diff --git a/scripts/checkpoint_conversion/longcat_to_fastvideo.py b/scripts/checkpoint_conversion/longcat_to_fastvideo.py index 7a9996724..59cfd6953 100644 --- a/scripts/checkpoint_conversion/longcat_to_fastvideo.py +++ b/scripts/checkpoint_conversion/longcat_to_fastvideo.py @@ -683,21 +683,23 @@ def main(): print() print(" 1. Test basic generation:") print(" from fastvideo import VideoGenerator") - print(f" generator = VideoGenerator.from_pretrained('{output_dir}')") - print(" video = generator.generate_video(") + print(" from fastvideo.api import GeneratorConfig, GenerationRequest, SamplingConfig") + print(f" generator = VideoGenerator.from_config(GeneratorConfig(model_path='{output_dir}'))") + print(" result = generator.generate(GenerationRequest(") print(" prompt='A cat playing piano',") - print(" num_inference_steps=50") - print(" )") + print(" sampling=SamplingConfig(num_inference_steps=50),") + print(" ))") print() if (output_dir / "lora" / "distilled").exists(): print(" 2. Test distilled generation (16 steps with LoRA):") - print(f" generator = VideoGenerator.from_pretrained('{output_dir}',") - print(f" lora_path='{output_dir}/lora/distilled',") - print(" lora_nickname='distilled')") - print(" video = generator.generate_video(") + print(" from fastvideo.api import ComponentConfig, PipelineSelection") + print(f" generator = VideoGenerator.from_config(GeneratorConfig(model_path='{output_dir}',") + print(f" pipeline=PipelineSelection(") + print(f" components=ComponentConfig(lora_path='{output_dir}/lora/distilled'),") + print(" experimental={'lora_nickname': 'distilled'})))") + print(" result = generator.generate(GenerationRequest(") print(" prompt='A cat playing piano',") - print(" num_inference_steps=16,") - print(" guidance_scale=1.0)") + print(" sampling=SamplingConfig(num_inference_steps=16, guidance_scale=1.0)))") print() print() diff --git a/scripts/checkpoint_conversion/push_magi_human_to_hf.py b/scripts/checkpoint_conversion/push_magi_human_to_hf.py index f35211f12..527629b7f 100644 --- a/scripts/checkpoint_conversion/push_magi_human_to_hf.py +++ b/scripts/checkpoint_conversion/push_magi_human_to_hf.py @@ -18,7 +18,8 @@ Example (one-shot per variant): --public After upload, the local directory can be deleted — the HF repo is the -source of truth. `VideoGenerator.from_pretrained("FastVideo/...")` pulls +source of truth. +`VideoGenerator.from_config(GeneratorConfig(model_path="FastVideo/..."))` pulls shards on demand. """ from __future__ import annotations diff --git a/scripts/lora_extraction/lora_inference_comparison.py b/scripts/lora_extraction/lora_inference_comparison.py index bee3c83a9..af64c597b 100644 --- a/scripts/lora_extraction/lora_inference_comparison.py +++ b/scripts/lora_extraction/lora_inference_comparison.py @@ -97,42 +97,53 @@ def generate_with_model( flow_shift: Optional[float] = None, embedded_guidance_scale: Optional[float] = None, ) -> str: - """Produce a video with VideoGenerator.from_pretrained; returns video path.""" + """Produce a video with VideoGenerator.from_config; returns video path.""" try: from fastvideo import VideoGenerator # lazy import + from fastvideo.api import ( + ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig, + OffloadConfig, OutputConfig, PipelineSelection, SamplingConfig, + ) except Exception as exc: raise RuntimeError(f"Failed to import fastvideo.VideoGenerator: {exc}") from exc - init_kwargs: Dict[str, Any] = { - "num_gpus": 1, - "dit_cpu_offload": True, - "vae_cpu_offload": True, - "text_encoder_cpu_offload": True, - "pin_cpu_memory": True, - } + pipeline_kwargs: Dict[str, Any] = {} if lora_path: - init_kwargs["lora_path"] = lora_path - init_kwargs["lora_nickname"] = "extracted" + pipeline_kwargs["components"] = ComponentConfig(lora_path=lora_path) + pipeline_kwargs["experimental"] = {"lora_nickname": "extracted"} - generator = VideoGenerator.from_pretrained(model_path, **init_kwargs) + generator = VideoGenerator.from_config(GeneratorConfig( + model_path=model_path, + engine=EngineConfig( + num_gpus=1, + offload=OffloadConfig(dit=True, vae=True, text_encoder=True, pin_cpu_memory=True), + ), + pipeline=PipelineSelection(**pipeline_kwargs), + )) - gen_kwargs = { - "height": height, - "width": width, - "num_frames": num_frames, - "num_inference_steps": num_inference_steps, - "guidance_scale": guidance_scale, - "seed": seed, - "output_path": output_dir, - "output_video_name": output_name, - "save_video": True, - } + extensions: Dict[str, Any] = {} if flow_shift is not None: - gen_kwargs["flow_shift"] = flow_shift + extensions["flow_shift"] = flow_shift if embedded_guidance_scale is not None: - gen_kwargs["embedded_guidance_scale"] = embedded_guidance_scale + extensions["embedded_guidance_scale"] = embedded_guidance_scale - result = generator.generate_video(prompt, **gen_kwargs) + result = generator.generate(GenerationRequest( + prompt=prompt, + sampling=SamplingConfig( + height=height, + width=width, + num_frames=num_frames, + num_inference_steps=num_inference_steps, + guidance_scale=guidance_scale, + seed=seed, + ), + output=OutputConfig( + output_path=output_dir, + output_video_name=output_name, + save_video=True, + ), + extensions=extensions, + )) # best-effort cleanup of internal executors try: @@ -145,9 +156,9 @@ def generate_with_model( expected = Path(output_dir) / f"{output_name}.mp4" if expected.exists(): return str(expected) - # fallback: check result dict - if isinstance(result, dict) and "video_path" in result: - return str(result["video_path"]) + # fallback: check result video path + if getattr(result, "video_path", None): + return str(result.video_path) raise FileNotFoundError(f"Video not found at expected path: {expected}")