From 145a3f166b691385372115140650be3f7de4415c Mon Sep 17 00:00:00 2001 From: William Lin Date: Thu, 16 Apr 2026 14:10:02 -0700 Subject: [PATCH] [feat] [4/n] Improve API: refactor sampling param and merge with presets (#1234) --- .github/mergify.yml | 2 +- docs/api/fastvideo.md | 4 +- docs/contributing/ci_architecture.md | 2 +- docs/contributing/coding_agents.md | 9 +- .../inference_schema_parity_inventory.yaml | 141 ++--- docs/design/overview.md | 11 +- docs/getting_started/v1_api.md | 2 +- docs/inference/architecture.md | 19 +- docs/inference/gen3c.md | 2 +- examples/inference/basic/basic.py | 2 +- .../inference/basic/basic_cosmos2_5_i2w.py | 2 +- .../inference/basic/basic_cosmos2_5_t2w.py | 2 +- .../inference/basic/basic_cosmos2_5_v2w.py | 2 +- examples/inference/basic/basic_dmd.py | 2 +- examples/inference/basic/basic_hy15.py | 2 +- examples/inference/basic/basic_hy15_1080p.py | 2 +- .../basic/basic_lingbotworld_base_cam.py | 2 +- examples/inference/basic/basic_mps.py | 2 +- examples/inference/basic/basic_ray.py | 2 +- .../basic_self_forcing_causal_wan2_2_i2v.py | 2 +- .../basic_self_forcing_causal_wan2_2_t2v.py | 2 +- examples/inference/basic/basic_wan2_2.py | 2 +- examples/inference/basic/basic_wan2_2_Fun.py | 2 +- examples/inference/basic/basic_wan2_2_i2v.py | 2 +- .../gradio/local/gradio_local_demo.py | 2 +- .../gradio/serving/gradio_frontend.py | 2 +- .../gradio/serving/ray_serve_backend.py | 2 +- examples/inference/lora/wan_lora_inference.py | 2 +- .../lora/wan_lora_inference_from_ckpt.py | 2 +- fastvideo/__init__.py | 2 +- fastvideo/api/__init__.py | 22 + fastvideo/api/compat.py | 12 +- fastvideo/api/presets.py | 261 +++++++++ .../sample/base.py => api/sampling_param.py} | 79 ++- fastvideo/api/schema.py | 6 +- fastvideo/configs/sample/__init__.py | 13 - fastvideo/configs/sample/cosmos.py | 18 - fastvideo/configs/sample/cosmos2_5.py | 23 - fastvideo/configs/sample/gen3c.py | 24 - fastvideo/configs/sample/hunyuan.py | 21 - fastvideo/configs/sample/hunyuan15.py | 55 -- fastvideo/configs/sample/hunyuangamecraft.py | 92 --- fastvideo/configs/sample/hyworld.py | 25 - fastvideo/configs/sample/lingbotworld.py | 20 - fastvideo/configs/sample/ltx2.py | 69 --- fastvideo/configs/sample/sd35.py | 25 - fastvideo/configs/sample/turbodiffusion.py | 73 --- fastvideo/configs/sample/wan.py | 154 ----- fastvideo/entrypoints/streaming_generator.py | 2 +- fastvideo/entrypoints/video_generator.py | 2 +- fastvideo/pipelines/basic/cosmos/presets.py | 84 +++ .../pipelines/basic/gamecraft/presets.py | 33 ++ fastvideo/pipelines/basic/gen3c/presets.py | 35 ++ fastvideo/pipelines/basic/hunyuan/presets.py | 49 ++ .../pipelines/basic/hunyuan15/presets.py | 152 +++++ fastvideo/pipelines/basic/hyworld/presets.py | 39 ++ .../pipelines/basic/lingbotworld/presets.py | 44 ++ fastvideo/pipelines/basic/longcat/presets.py | 65 +++ fastvideo/pipelines/basic/ltx2/presets.py | 80 +++ .../pipelines/basic/matrixgame/presets.py | 33 ++ fastvideo/pipelines/basic/sd35/presets.py | 34 ++ .../pipelines/basic/turbodiffusion/presets.py | 73 +++ fastvideo/pipelines/basic/wan/presets.py | 347 +++++++++++ .../preprocess_pipeline_ode_trajectory.py | 2 +- fastvideo/registry.py | 271 ++++++--- fastvideo/tests/api/test_cli_translation.py | 2 +- fastvideo/tests/api/test_parser.py | 10 +- fastvideo/tests/api/test_presets.py | 550 ++++++++++++++++++ .../tests/api/test_schema_parity_inventory.py | 12 +- .../tests/entrypoints/test_video_generator.py | 2 +- .../tests/ssim/test_causal_similarity.py | 7 +- .../tests/ssim/test_gamecraft_similarity.py | 7 +- .../tests/ssim/test_lingbot_similarity.py | 5 +- .../tests/ssim/test_longcat_similarity.py | 2 +- .../tests/ssim/test_matrixgame_similarity.py | 5 +- fastvideo/tests/ssim/test_sd35_similarity.py | 5 +- .../ssim/test_turbodiffusion_similarity.py | 11 +- .../tests/ssim/test_wan_i2v_similarity.py | 5 +- .../tests/ssim/test_wan_t2v_similarity.py | 5 +- fastvideo/train/callbacks/validation.py | 2 +- fastvideo/train/models/wan/wan.py | 2 +- fastvideo/training/distillation_pipeline.py | 2 +- .../training/matrixgame_training_pipeline.py | 2 +- fastvideo/training/training_pipeline.py | 2 +- .../training/wan_i2v_distillation_pipeline.py | 2 +- .../training/wan_i2v_training_pipeline.py | 2 +- tests/local_tests/test_ltx2_registry.py | 96 ++- 87 files changed, 2356 insertions(+), 953 deletions(-) create mode 100644 fastvideo/api/presets.py rename fastvideo/{configs/sample/base.py => api/sampling_param.py} (78%) delete mode 100644 fastvideo/configs/sample/__init__.py delete mode 100644 fastvideo/configs/sample/cosmos.py delete mode 100644 fastvideo/configs/sample/cosmos2_5.py delete mode 100644 fastvideo/configs/sample/gen3c.py delete mode 100644 fastvideo/configs/sample/hunyuan.py delete mode 100644 fastvideo/configs/sample/hunyuan15.py delete mode 100644 fastvideo/configs/sample/hunyuangamecraft.py delete mode 100644 fastvideo/configs/sample/hyworld.py delete mode 100644 fastvideo/configs/sample/lingbotworld.py delete mode 100644 fastvideo/configs/sample/ltx2.py delete mode 100644 fastvideo/configs/sample/sd35.py delete mode 100644 fastvideo/configs/sample/turbodiffusion.py delete mode 100644 fastvideo/configs/sample/wan.py create mode 100644 fastvideo/pipelines/basic/cosmos/presets.py create mode 100644 fastvideo/pipelines/basic/gamecraft/presets.py create mode 100644 fastvideo/pipelines/basic/gen3c/presets.py create mode 100644 fastvideo/pipelines/basic/hunyuan/presets.py create mode 100644 fastvideo/pipelines/basic/hunyuan15/presets.py create mode 100644 fastvideo/pipelines/basic/hyworld/presets.py create mode 100644 fastvideo/pipelines/basic/lingbotworld/presets.py create mode 100644 fastvideo/pipelines/basic/longcat/presets.py create mode 100644 fastvideo/pipelines/basic/ltx2/presets.py create mode 100644 fastvideo/pipelines/basic/matrixgame/presets.py create mode 100644 fastvideo/pipelines/basic/sd35/presets.py create mode 100644 fastvideo/pipelines/basic/turbodiffusion/presets.py create mode 100644 fastvideo/pipelines/basic/wan/presets.py create mode 100644 fastvideo/tests/api/test_presets.py diff --git a/.github/mergify.yml b/.github/mergify.yml index a58980c53..58b5ad700 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -105,7 +105,7 @@ pull_request_rules: - files~=^fastvideo/pipelines/samplers/ - files~=^fastvideo/entrypoints/ - files~=^fastvideo/worker/ - - files~=^fastvideo/configs/sample/ + - files~=^fastvideo/api/sampling_param - files~=^fastvideo/configs/pipelines/ - files~=^examples/inference/ - -closed diff --git a/docs/api/fastvideo.md b/docs/api/fastvideo.md index 85a17d13a..1ccaa6f45 100644 --- a/docs/api/fastvideo.md +++ b/docs/api/fastvideo.md @@ -62,9 +62,9 @@ This page contains the complete API reference for the FastVideo library. show_root_toc_entry: true heading_level: 4 -#### fastvideo.configs.sample +#### fastvideo.api.sampling_param -::: fastvideo.configs.sample +::: fastvideo.api.sampling_param options: show_source: true show_root_heading: true diff --git a/docs/contributing/ci_architecture.md b/docs/contributing/ci_architecture.md index c73d8f661..fe9c07c38 100644 --- a/docs/contributing/ci_architecture.md +++ b/docs/contributing/ci_architecture.md @@ -173,7 +173,7 @@ Applied by Mergify based on which paths you modified. Multiple scope labels can | Label | File paths that trigger it | |-------|---------------------------| | `scope: training` | `fastvideo/train/`, `fastvideo/training/`, `fastvideo/distillation/`, `examples/train/`, `examples/training/`, `examples/distill/` | -| `scope: inference` | `fastvideo/pipelines/basic/`, `fastvideo/pipelines/stages/`, `fastvideo/pipelines/samplers/`, `fastvideo/entrypoints/`, `fastvideo/worker/`, `fastvideo/configs/sample/`, `fastvideo/configs/pipelines/`, `examples/inference/` | +| `scope: inference` | `fastvideo/pipelines/basic/`, `fastvideo/pipelines/stages/`, `fastvideo/pipelines/samplers/`, `fastvideo/entrypoints/`, `fastvideo/worker/`, `fastvideo/api/sampling_param.py`, `fastvideo/configs/pipelines/`, `examples/inference/` | | `scope: attention` | `fastvideo/attention/` | | `scope: kernel` | `fastvideo-kernel/`, `csrc/` | | `scope: data` | `fastvideo/dataset/`, `fastvideo/pipelines/preprocess/`, `examples/preprocessing/` | diff --git a/docs/contributing/coding_agents.md b/docs/contributing/coding_agents.md index e6f8f8589..bd680d828 100644 --- a/docs/contributing/coding_agents.md +++ b/docs/contributing/coding_agents.md @@ -44,7 +44,7 @@ FastVideo maps a Diffusers-style repo into a pipeline like: - `fastvideo/configs/models/*`: arch configs and `param_names_mapping` for weight name translation. - `fastvideo/configs/pipelines/*`: pipeline wiring (component classes + names). -- `fastvideo/configs/sample/*`: default runtime sampling parameters. +- `fastvideo/api/sampling_param.py`: runtime sampling parameters. - `fastvideo/pipelines/basic/*`: end-to-end pipeline logic built from stages. - `model_index.json`: the HF repo entrypoint that maps component names to classes and weight files. @@ -55,7 +55,7 @@ Minimal usage example (based on `examples/inference/basic/basic.py`): ```python from fastvideo import VideoGenerator -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam model_id = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" # or official_weights// generator = VideoGenerator.from_pretrained(model_id, num_gpus=1) @@ -319,7 +319,8 @@ Purpose: - `fastvideo/configs/pipelines/` describes pipeline wiring and model module names. -- `fastvideo/configs/sample/` defines default runtime parameters. +- `fastvideo/api/sampling_param.py` defines runtime sampling parameters. + Defaults come from profiles in `fastvideo/pipelines/basic//profiles.py`. Action: @@ -474,7 +475,7 @@ FastVideo integration. 3. Pipeline wiring. - Pipeline: `fastvideo/pipelines/basic/wan/wan_pipeline.py` - Pipeline config: `fastvideo/configs/pipelines/wan.py` - - Sampling defaults: `fastvideo/configs/sample/wan.py` + - Sampling defaults: `fastvideo/pipelines/basic/wan/profiles.py` 4. Minimal example. - Script: `examples/inference/basic/basic.py` diff --git a/docs/design/inference_schema_parity_inventory.yaml b/docs/design/inference_schema_parity_inventory.yaml index 5f573a1fe..09ae0ff00 100644 --- a/docs/design/inference_schema_parity_inventory.yaml +++ b/docs/design/inference_schema_parity_inventory.yaml @@ -1,7 +1,7 @@ status_definitions: kept: "Public field remains on a public adapter surface with the same meaning." moved: "Public field remains supported but normalizes into a different nested path." - profile_owned: "Public field remains supported only through a model/profile-specific surface." + preset_owned: "Public field remains supported only through a model/preset-specific surface." compatibility_only: "Legacy public field remains adapter-only during migration and is not part of the canonical typed schema." private_only: "Field should only be handled by private adapters and is not a public FastVideo compatibility promise." internal_only: "Field is runtime/config plumbing and should not be part of the new public typed inference API." @@ -40,12 +40,12 @@ surfaces: init_weights_from_safetensors_2: generator.pipeline.components.transformer_2_weights override_pipeline_cls_name: generator.pipeline.components.override_pipeline_cls_name boundary_ratio: request.sampling.boundary_ratio - profile_owned: - ltx2_vae_tiling: generator.pipeline.profile_overrides.ltx2.vae_tiling - ltx2_vae_spatial_tile_size_in_pixels: generator.pipeline.profile_overrides.ltx2.vae.spatial_tile_size_in_pixels - ltx2_vae_spatial_tile_overlap_in_pixels: generator.pipeline.profile_overrides.ltx2.vae.spatial_tile_overlap_in_pixels - ltx2_vae_temporal_tile_size_in_frames: generator.pipeline.profile_overrides.ltx2.vae.temporal_tile_size_in_frames - ltx2_vae_temporal_tile_overlap_in_frames: generator.pipeline.profile_overrides.ltx2.vae.temporal_tile_overlap_in_frames + preset_owned: + ltx2_vae_tiling: generator.pipeline.preset_overrides.ltx2.vae_tiling + ltx2_vae_spatial_tile_size_in_pixels: generator.pipeline.preset_overrides.ltx2.vae.spatial_tile_size_in_pixels + ltx2_vae_spatial_tile_overlap_in_pixels: generator.pipeline.preset_overrides.ltx2.vae.spatial_tile_overlap_in_pixels + ltx2_vae_temporal_tile_size_in_frames: generator.pipeline.preset_overrides.ltx2.vae.temporal_tile_size_in_frames + ltx2_vae_temporal_tile_overlap_in_frames: generator.pipeline.preset_overrides.ltx2.vae.temporal_tile_overlap_in_frames ltx2_initial_latent_path: request.extensions.ltx2.initial_latent_path compatibility_only: mode: "Legacy multi-mode FastVideoArgs switch; typed inference config should not expose execution mode." @@ -69,16 +69,16 @@ surfaces: pipeline_config_base: moved: pipeline_config_path: generator.pipeline.components.pipeline_config_path - profile_owned: - embedded_cfg_scale: generator.pipeline.profile_overrides.embedded_cfg_scale - flow_shift: generator.pipeline.profile_overrides.flow_shift - flow_shift_sr: generator.pipeline.profile_overrides.flow_shift_sr - is_causal: generator.pipeline.profile_overrides.is_causal - vae_tiling: generator.pipeline.profile_overrides.vae_tiling - vae_sp: generator.pipeline.profile_overrides.vae_sp - dmd_denoising_steps: generator.pipeline.profile_overrides.dmd_denoising_steps - ti2v_task: generator.pipeline.profile_overrides.ti2v_task - boundary_ratio: generator.pipeline.profile_overrides.boundary_ratio + preset_owned: + embedded_cfg_scale: generator.pipeline.preset_overrides.embedded_cfg_scale + flow_shift: generator.pipeline.preset_overrides.flow_shift + flow_shift_sr: generator.pipeline.preset_overrides.flow_shift_sr + is_causal: generator.pipeline.preset_overrides.is_causal + vae_tiling: generator.pipeline.preset_overrides.vae_tiling + vae_sp: generator.pipeline.preset_overrides.vae_sp + dmd_denoising_steps: generator.pipeline.preset_overrides.dmd_denoising_steps + ti2v_task: generator.pipeline.preset_overrides.ti2v_task + boundary_ratio: generator.pipeline.preset_overrides.boundary_ratio compatibility_only: model_path: "Redundant with generator.model_path." disable_autocast: "Duplicated by generator.engine.disable_autocast during migration." @@ -97,7 +97,7 @@ surfaces: postprocess_text_funcs: "Internal text postprocessing hooks." pipeline_config_extensions: - profile_owned: + preset_owned: conditioning_strategy: sources: - fastvideo.configs.pipelines.cosmos.CosmosConfig @@ -309,8 +309,8 @@ surfaces: compatibility_only: batch_size: "Gen3C inference-only tuning field pending typed batching design." gradient_checkpointing: "Gen3C inference-only compatibility field pending typed batching design." - guidance_scale: "Gen3C pipeline-level default pending profile/default-request cleanup." - num_inference_steps: "Gen3C pipeline-level default pending profile/default-request cleanup." + guidance_scale: "Gen3C pipeline-level default pending preset/default-request cleanup." + num_inference_steps: "Gen3C pipeline-level default pending preset/default-request cleanup." internal_only: audio_decoder_config: "Legacy internal component config object." audio_decoder_precision: "Precision override pending dedicated component precision design." @@ -345,6 +345,7 @@ surfaces: num_inference_steps: request.sampling.num_inference_steps num_inference_steps_sr: request.sampling.num_inference_steps_sr guidance_scale: request.sampling.guidance_scale + guidance_scale_2: request.sampling.guidance_scale_2 guidance_rescale: request.sampling.guidance_rescale boundary_ratio: request.sampling.boundary_ratio sigmas: request.sampling.sigmas @@ -353,96 +354,34 @@ surfaces: return_frames: request.output.return_frames return_trajectory_latents: request.runtime.return_trajectory_latents return_trajectory_decoded: request.runtime.return_trajectory_decoded - profile_owned: + preset_owned: t_thresh: request.stage_overrides.refine.t_thresh spatial_refine_only: request.stage_overrides.refine.spatial_refine_only num_cond_frames: request.stage_overrides.refine.num_cond_frames trajectory_type: request.extensions.gen3c.trajectory_type movement_distance: request.extensions.gen3c.movement_distance camera_rotation: request.extensions.gen3c.camera_rotation + prompt_attention_mask: request.extensions.hyworld.prompt_attention_mask + negative_attention_mask: request.extensions.hyworld.negative_attention_mask + camera_states: request.extensions.hunyuangamecraft.camera_states + camera_trajectory: request.extensions.hunyuangamecraft.camera_trajectory + action_list: request.extensions.hunyuangamecraft.action_list + action_speed_list: request.extensions.hunyuangamecraft.action_speed_list + gt_latents: request.extensions.hunyuangamecraft.gt_latents + conditioning_mask: request.extensions.hunyuangamecraft.conditioning_mask + ltx2_cfg_scale_video: request.extensions.ltx2.cfg_scale_video + ltx2_cfg_scale_audio: request.extensions.ltx2.cfg_scale_audio + ltx2_modality_scale_video: request.extensions.ltx2.modality_scale_video + ltx2_modality_scale_audio: request.extensions.ltx2.modality_scale_audio + ltx2_rescale_scale: request.extensions.ltx2.rescale_scale + ltx2_stg_scale_video: request.extensions.ltx2.stg_scale_video + ltx2_stg_scale_audio: request.extensions.ltx2.stg_scale_audio + ltx2_stg_blocks_video: request.extensions.ltx2.stg_blocks_video + ltx2_stg_blocks_audio: request.extensions.ltx2.stg_blocks_audio internal_only: data_type: "Derived from the request shape and not a public input." - sampling_param_extensions: - moved: - guidance_scale_2: - target: request.sampling.guidance_scale_2 - sources: - - fastvideo.configs.sample.lingbotworld.LingBotWorld_SamplingParam - - fastvideo.configs.sample.lingbotworld.Wan2_2_I2V_A14B_SamplingParam - - fastvideo.configs.sample.wan.SelfForcingWan2_2_T2V_A14B_480P_SamplingParam - - fastvideo.configs.sample.wan.Wan2_2_I2V_A14B_SamplingParam - - fastvideo.configs.sample.wan.Wan2_2_T2V_A14B_SamplingParam - profile_owned: - action_list: - target: request.extensions.hunyuangamecraft.action_list - sources: - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraftSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft65FrameSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft129FrameSamplingParam - action_speed_list: - target: request.extensions.hunyuangamecraft.action_speed_list - sources: - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraftSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft65FrameSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft129FrameSamplingParam - camera_states: - target: request.extensions.hunyuangamecraft.camera_states - sources: - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraftSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft65FrameSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft129FrameSamplingParam - camera_trajectory: - target: request.extensions.hunyuangamecraft.camera_trajectory - sources: - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraftSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft65FrameSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft129FrameSamplingParam - conditioning_mask: - target: request.extensions.hunyuangamecraft.conditioning_mask - sources: - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraftSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft65FrameSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft129FrameSamplingParam - gt_latents: - target: request.extensions.hunyuangamecraft.gt_latents - sources: - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraftSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft65FrameSamplingParam - - fastvideo.configs.sample.hunyuangamecraft.HunyuanGameCraft129FrameSamplingParam - prompt_attention_mask: - target: request.extensions.hyworld.prompt_attention_mask - sources: [fastvideo.configs.sample.hyworld.HYWorld_SamplingParam] - negative_attention_mask: - target: request.extensions.hyworld.negative_attention_mask - sources: [fastvideo.configs.sample.hyworld.HYWorld_SamplingParam] - ltx2_cfg_scale_audio: - target: request.extensions.ltx2.cfg_scale_audio - sources: [fastvideo.configs.sample.ltx2.LTX2BaseSamplingParam] - ltx2_cfg_scale_video: - target: request.extensions.ltx2.cfg_scale_video - sources: [fastvideo.configs.sample.ltx2.LTX2BaseSamplingParam] - ltx2_modality_scale_audio: - target: request.extensions.ltx2.modality_scale_audio - sources: [fastvideo.configs.sample.ltx2.LTX2BaseSamplingParam] - ltx2_modality_scale_video: - target: request.extensions.ltx2.modality_scale_video - sources: [fastvideo.configs.sample.ltx2.LTX2BaseSamplingParam] - ltx2_rescale_scale: - target: request.extensions.ltx2.rescale_scale - sources: [fastvideo.configs.sample.ltx2.LTX2BaseSamplingParam] - ltx2_stg_blocks_audio: - target: request.extensions.ltx2.stg_blocks_audio - sources: [fastvideo.configs.sample.ltx2.LTX2BaseSamplingParam] - ltx2_stg_blocks_video: - target: request.extensions.ltx2.stg_blocks_video - sources: [fastvideo.configs.sample.ltx2.LTX2BaseSamplingParam] - ltx2_stg_scale_audio: - target: request.extensions.ltx2.stg_scale_audio - sources: [fastvideo.configs.sample.ltx2.LTX2BaseSamplingParam] - ltx2_stg_scale_video: - target: request.extensions.ltx2.stg_scale_video - sources: [fastvideo.configs.sample.ltx2.LTX2BaseSamplingParam] + sampling_param_extensions: {} openai_image_request: kept: diff --git a/docs/design/overview.md b/docs/design/overview.md index 8365bc3a0..29415fe5a 100644 --- a/docs/design/overview.md +++ b/docs/design/overview.md @@ -12,7 +12,7 @@ FastVideo maps a Diffusers-style repo into a pipeline like this: - `fastvideo/configs/models/*`: arch configs and `param_names_mapping` for weight name translation. - `fastvideo/configs/pipelines/*`: pipeline wiring (component classes + names). -- `fastvideo/configs/sample/*`: default runtime sampling parameters. +- `fastvideo/api/sampling_param.py`: runtime sampling parameters. - `fastvideo/pipelines/basic/*`: end-to-end pipelines. - `fastvideo/pipelines/stages/*`: reusable pipeline stages. - `fastvideo/models/loader/*`: component loaders for Diffusers-style repos. @@ -26,7 +26,7 @@ Minimal usage (from `examples/inference/basic/basic.py`): ```python from fastvideo import VideoGenerator -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam model_id = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" # or official_weights// generator = VideoGenerator.from_pretrained(model_id, num_gpus=1) @@ -49,8 +49,9 @@ runtime parameters consistent: - `fastvideo/configs/models/`: architecture definitions, layer shapes, and `param_names_mapping` rules for key renaming. - `fastvideo/configs/pipelines/`: pipeline wiring and required components. -- `fastvideo/configs/sample/`: default sampling parameters (steps, frames, - guidance scale, resolution, fps). +- `fastvideo/api/sampling_param.py`: sampling parameters (steps, frames, + guidance scale, resolution, fps). Defaults come from profiles in + `fastvideo/pipelines/basic//profiles.py`. - `fastvideo/registry.py`: unified registry for pipeline config + sampling defaults and model metadata resolution, defined via explicit `register_configs(...)` blocks (no separate dict registries). @@ -142,7 +143,7 @@ How this maps to FastVideo: - `T5TokenizerFast` -> loaded via HF in `fastvideo/models/loader/` - `UniPCMultistepScheduler` -> loaded via Diffusers scheduler utilities - Pipeline defaults -> `fastvideo/configs/pipelines/wan.py` -- Sampling defaults -> `fastvideo/configs/sample/wan.py` +- Sampling defaults -> `fastvideo/pipelines/basic/wan/profiles.py` ## Pipeline system diff --git a/docs/getting_started/v1_api.md b/docs/getting_started/v1_api.md index 52292fd66..872c39fa4 100644 --- a/docs/getting_started/v1_api.md +++ b/docs/getting_started/v1_api.md @@ -33,7 +33,7 @@ The following two classes `PipelineConfig` and `SamplingParam` are used to confi ### SamplingParam -::: fastvideo.configs.sample.base.SamplingParam +::: fastvideo.api.sampling_param.SamplingParam options: show_root_heading: true show_source: false diff --git a/docs/inference/architecture.md b/docs/inference/architecture.md index d36bf8412..b69f0c8bc 100644 --- a/docs/inference/architecture.md +++ b/docs/inference/architecture.md @@ -128,19 +128,14 @@ Concrete hierarchy: `DiTConfig` → `DiTArchConfig`, `VAEConfig` → - `dump_to_json()` / `load_from_json()` — JSON persistence. Callable fields and `arch_config` are excluded from dumps. -### SamplingParam (`fastvideo/configs/sample/`) +### SamplingParam (`fastvideo/api/sampling_param.py`) Generation parameters separate from pipeline config. Each model family -provides defaults: +provides defaults via a profile (see `fastvideo/pipelines/basic//profiles.py`): ```python -@dataclass -class WanT2V_1_3B_SamplingParam(SamplingParam): - height: int = 480 - width: int = 832 - num_frames: int = 81 - guidance_scale: float = 3.0 - num_inference_steps: int = 50 +sp = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers") +# sp.height == 480, sp.width == 832, sp.num_frames == 81, etc. ``` ## Component Loading @@ -430,9 +425,9 @@ User: generator.generate_video(prompt, ...) `fastvideo/configs/pipelines/.py`. Set DiT/VAE/encoder configs, flow_shift, precision defaults. -2. **Sampling param** — Create a `SamplingParam` subclass in - `fastvideo/configs/sample/.py`. Set default height, width, - num_frames, guidance_scale, num_inference_steps. +2. **Sampling param profile** — Create a profile in + `fastvideo/pipelines/basic//profiles.py` with default height, + width, num_frames, guidance_scale, num_inference_steps. 3. **Register configs** — In `fastvideo/registry.py`, add a `register_configs()` call inside `_register_configs()` with diff --git a/docs/inference/gen3c.md b/docs/inference/gen3c.md index 2d230fe16..bc887c4d2 100644 --- a/docs/inference/gen3c.md +++ b/docs/inference/gen3c.md @@ -89,7 +89,7 @@ GEN3C defaults in FastVideo: These values are defined in: -- `fastvideo/configs/sample/gen3c.py` +- `fastvideo/pipelines/basic/gen3c/profiles.py` - `fastvideo/configs/pipelines/gen3c.py` and align with the official GEN3C inference defaults in: diff --git a/examples/inference/basic/basic.py b/examples/inference/basic/basic.py index 3f00832b9..766b9bd79 100644 --- a/examples/inference/basic/basic.py +++ b/examples/inference/basic/basic.py @@ -1,6 +1,6 @@ from fastvideo import VideoGenerator -# from fastvideo.configs.sample import SamplingParam +# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples" def main(): diff --git a/examples/inference/basic/basic_cosmos2_5_i2w.py b/examples/inference/basic/basic_cosmos2_5_i2w.py index 001d02096..07c5f1316 100644 --- a/examples/inference/basic/basic_cosmos2_5_i2w.py +++ b/examples/inference/basic/basic_cosmos2_5_i2w.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 from fastvideo import VideoGenerator -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam def main(): diff --git a/examples/inference/basic/basic_cosmos2_5_t2w.py b/examples/inference/basic/basic_cosmos2_5_t2w.py index d3ed30c83..2296ca7bd 100644 --- a/examples/inference/basic/basic_cosmos2_5_t2w.py +++ b/examples/inference/basic/basic_cosmos2_5_t2w.py @@ -1,5 +1,5 @@ from fastvideo import VideoGenerator -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam def main(): diff --git a/examples/inference/basic/basic_cosmos2_5_v2w.py b/examples/inference/basic/basic_cosmos2_5_v2w.py index addac9b4e..0efefce0e 100644 --- a/examples/inference/basic/basic_cosmos2_5_v2w.py +++ b/examples/inference/basic/basic_cosmos2_5_v2w.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 from fastvideo import VideoGenerator -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam def main(): diff --git a/examples/inference/basic/basic_dmd.py b/examples/inference/basic/basic_dmd.py index b3b63b199..03231b857 100644 --- a/examples/inference/basic/basic_dmd.py +++ b/examples/inference/basic/basic_dmd.py @@ -2,7 +2,7 @@ import os import time from fastvideo import VideoGenerator -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_dmd2" def main(): diff --git a/examples/inference/basic/basic_hy15.py b/examples/inference/basic/basic_hy15.py index e0b782237..745fd7e64 100644 --- a/examples/inference/basic/basic_hy15.py +++ b/examples/inference/basic/basic_hy15.py @@ -1,6 +1,6 @@ from fastvideo import VideoGenerator import json -# from fastvideo.configs.sample import SamplingParam +# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_hy15" def main(): diff --git a/examples/inference/basic/basic_hy15_1080p.py b/examples/inference/basic/basic_hy15_1080p.py index 6b346e8f9..32dcc5780 100644 --- a/examples/inference/basic/basic_hy15_1080p.py +++ b/examples/inference/basic/basic_hy15_1080p.py @@ -1,6 +1,6 @@ from fastvideo import VideoGenerator import json -# from fastvideo.configs.sample import SamplingParam +# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_hy15_1080p" def main(): diff --git a/examples/inference/basic/basic_lingbotworld_base_cam.py b/examples/inference/basic/basic_lingbotworld_base_cam.py index db8ce10bb..8c9a95321 100644 --- a/examples/inference/basic/basic_lingbotworld_base_cam.py +++ b/examples/inference/basic/basic_lingbotworld_base_cam.py @@ -1,7 +1,7 @@ from fastvideo import VideoGenerator from fastvideo.models.dits.lingbotworld.cam_utils import prepare_camera_embedding -# from fastvideo.configs.sample import SamplingParam +# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_lingbotworld" def main(): # FastVideo will automatically use the optimal default arguments for the diff --git a/examples/inference/basic/basic_mps.py b/examples/inference/basic/basic_mps.py index 2300aa7c9..885f945d1 100644 --- a/examples/inference/basic/basic_mps.py +++ b/examples/inference/basic/basic_mps.py @@ -1,5 +1,5 @@ from fastvideo import VideoGenerator, PipelineConfig -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam def main(): config = PipelineConfig.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers") diff --git a/examples/inference/basic/basic_ray.py b/examples/inference/basic/basic_ray.py index 8dbcf909f..4837e7e01 100644 --- a/examples/inference/basic/basic_ray.py +++ b/examples/inference/basic/basic_ray.py @@ -1,6 +1,6 @@ from fastvideo import VideoGenerator -# from fastvideo.configs.sample import SamplingParam +# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples" def 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 d735d0540..32b60cd86 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 @@ -2,7 +2,7 @@ from fastvideo import VideoGenerator, SamplingParam import json -# from fastvideo.configs.sample import SamplingParam +# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_self_forcing_causal_wan2_2_14B_i2v" def 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 2d6032c5d..3abdb1b7e 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 @@ -2,7 +2,7 @@ from fastvideo import VideoGenerator -# from fastvideo.configs.sample import SamplingParam +# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_self_forcing_causal_wan2_2_14B_t2v" def main(): diff --git a/examples/inference/basic/basic_wan2_2.py b/examples/inference/basic/basic_wan2_2.py index bd2207b19..e6758347c 100644 --- a/examples/inference/basic/basic_wan2_2.py +++ b/examples/inference/basic/basic_wan2_2.py @@ -1,6 +1,6 @@ from fastvideo import VideoGenerator -# from fastvideo.configs.sample import SamplingParam +# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_wan2_2_14B_t2v" def main(): diff --git a/examples/inference/basic/basic_wan2_2_Fun.py b/examples/inference/basic/basic_wan2_2_Fun.py index 61c216074..b3d6f1229 100644 --- a/examples/inference/basic/basic_wan2_2_Fun.py +++ b/examples/inference/basic/basic_wan2_2_Fun.py @@ -1,6 +1,6 @@ from fastvideo import VideoGenerator -# from fastvideo.configs.sample import SamplingParam +# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_wan2_1_Fun" OUTPUT_NAME = "wan2.1_test" diff --git a/examples/inference/basic/basic_wan2_2_i2v.py b/examples/inference/basic/basic_wan2_2_i2v.py index 08eb2c21a..15babf2aa 100644 --- a/examples/inference/basic/basic_wan2_2_i2v.py +++ b/examples/inference/basic/basic_wan2_2_i2v.py @@ -1,6 +1,6 @@ from fastvideo import VideoGenerator -# from fastvideo.configs.sample import SamplingParam +# from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "video_samples_wan2_2_14B_i2v" def main(): diff --git a/examples/inference/gradio/local/gradio_local_demo.py b/examples/inference/gradio/local/gradio_local_demo.py index a59d1c123..d3683e23d 100644 --- a/examples/inference/gradio/local/gradio_local_demo.py +++ b/examples/inference/gradio/local/gradio_local_demo.py @@ -5,7 +5,7 @@ import time import gradio as gr from fastvideo.entrypoints.video_generator import VideoGenerator -from fastvideo.configs.sample.base import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from copy import deepcopy diff --git a/examples/inference/gradio/serving/gradio_frontend.py b/examples/inference/gradio/serving/gradio_frontend.py index 0843bc555..bb3f89a96 100644 --- a/examples/inference/gradio/serving/gradio_frontend.py +++ b/examples/inference/gradio/serving/gradio_frontend.py @@ -9,7 +9,7 @@ import tempfile import gradio as gr -from fastvideo.configs.sample.base import SamplingParam +from fastvideo.api.sampling_param import SamplingParam MODEL_PATH_MAPPING = { diff --git a/examples/inference/gradio/serving/ray_serve_backend.py b/examples/inference/gradio/serving/ray_serve_backend.py index 495aec3f6..2a783207b 100644 --- a/examples/inference/gradio/serving/ray_serve_backend.py +++ b/examples/inference/gradio/serving/ray_serve_backend.py @@ -185,7 +185,7 @@ class BaseModelDeployment: def _initialize_generator(self, config: Dict[str, Any]) -> None: from fastvideo.entrypoints.video_generator import VideoGenerator - from fastvideo.configs.sample.base import SamplingParam + from fastvideo.api.sampling_param import SamplingParam print(f"Initializing model: {self.model_path}") self.generator = VideoGenerator.from_pretrained( diff --git a/examples/inference/lora/wan_lora_inference.py b/examples/inference/lora/wan_lora_inference.py index bc3d264d9..79fe4300e 100644 --- a/examples/inference/lora/wan_lora_inference.py +++ b/examples/inference/lora/wan_lora_inference.py @@ -1,5 +1,5 @@ from fastvideo import VideoGenerator -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "./lora_out" def main(): diff --git a/examples/inference/lora/wan_lora_inference_from_ckpt.py b/examples/inference/lora/wan_lora_inference_from_ckpt.py index 14bba5e3b..ca7032133 100644 --- a/examples/inference/lora/wan_lora_inference_from_ckpt.py +++ b/examples/inference/lora/wan_lora_inference_from_ckpt.py @@ -2,7 +2,7 @@ Inference using a LoRA checkpoint from FastVideo trainer. """ from fastvideo import VideoGenerator -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam OUTPUT_PATH = "./lora_out" def main(): diff --git a/fastvideo/__init__.py b/fastvideo/__init__.py index 44c7938e9..cd2adcb0c 100644 --- a/fastvideo/__init__.py +++ b/fastvideo/__init__.py @@ -1,5 +1,5 @@ from fastvideo.configs.pipelines import PipelineConfig -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.entrypoints.video_generator import VideoGenerator from fastvideo.version import __version__ diff --git a/fastvideo/api/__init__.py b/fastvideo/api/__init__.py index be7d02978..7eff9911e 100644 --- a/fastvideo/api/__init__.py +++ b/fastvideo/api/__init__.py @@ -22,6 +22,17 @@ from fastvideo.api.schema import ( ) from fastvideo.api.errors import ConfigValidationError from fastvideo.api.overrides import apply_overrides, parse_cli_overrides +from fastvideo.api.presets import ( + InferencePreset, + PresetStageSpec, + get_all_preset_names, + get_preset, + get_presets_for_family, + register_preset, + validate_preset_selection, + validate_stage_names, + validate_stage_overrides, +) from fastvideo.api.parser import ( config_to_dict, load_config, @@ -31,6 +42,7 @@ from fastvideo.api.parser import ( parse_config, ) from fastvideo.api.results import GenerationResult +from fastvideo.api.sampling_param import SamplingParam __all__ = [ "CompileConfig", @@ -52,8 +64,11 @@ __all__ = [ "RequestRuntimeConfig", "RunConfig", "SamplingConfig", + "SamplingParam", "ServeConfig", "ServerConfig", + "InferencePreset", + "PresetStageSpec", "apply_overrides", "config_to_dict", "load_config", @@ -61,5 +76,12 @@ __all__ = [ "load_run_config", "load_serve_config", "parse_cli_overrides", + "get_all_preset_names", + "get_preset", + "get_presets_for_family", "parse_config", + "register_preset", + "validate_preset_selection", + "validate_stage_names", + "validate_stage_overrides", ] diff --git a/fastvideo/api/compat.py b/fastvideo/api/compat.py index 51ea86413..647b71934 100644 --- a/fastvideo/api/compat.py +++ b/fastvideo/api/compat.py @@ -22,7 +22,7 @@ from fastvideo.api.schema import ( RequestRuntimeConfig, SamplingConfig, ) -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.fastvideo_args import FastVideoArgs from fastvideo.utils import shallow_asdict @@ -157,10 +157,10 @@ def legacy_from_pretrained_to_config( def generator_config_to_fastvideo_args(config: GeneratorConfig | Mapping[str, Any], ) -> FastVideoArgs: normalized = normalize_generator_config(config) unsupported = [] - if normalized.pipeline.profile is not None: - unsupported.append("pipeline.profile") - if normalized.pipeline.profile_version is not None: - unsupported.append("pipeline.profile_version") + if normalized.pipeline.preset is not None: + unsupported.append("pipeline.preset") + if normalized.pipeline.preset_version is not None: + unsupported.append("pipeline.preset_version") if normalized.pipeline.components.config_root is not None: unsupported.append("pipeline.components.config_root") if normalized.pipeline.components.vae_weights is not None: @@ -220,7 +220,7 @@ def generator_config_to_fastvideo_args(config: GeneratorConfig | Mapping[str, An if components.transformer_2_weights is not None: kwargs["init_weights_from_safetensors_2"] = components.transformer_2_weights - kwargs.update(deepcopy(normalized.pipeline.profile_overrides)) + kwargs.update(deepcopy(normalized.pipeline.preset_overrides)) kwargs.update(deepcopy(normalized.pipeline.experimental)) return FastVideoArgs.from_kwargs(**kwargs) diff --git a/fastvideo/api/presets.py b/fastvideo/api/presets.py new file mode 100644 index 000000000..0c106e47d --- /dev/null +++ b/fastvideo/api/presets.py @@ -0,0 +1,261 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Pipeline preset registry. + +A *preset* is a named inference preset for a model family. It bundles: + + * ``defaults`` — sampling values applied when the user does not + override them (consumed at runtime via ``SamplingParam.from_pretrained``); + * ``stage_schemas`` — **validation-only** metadata describing which + user-facing stage names (``"denoise"``, ``"sr"``) the preset recognises + and which ``stage_overrides`` keys each stage accepts. + +The ``stage_schemas`` tuple does **not** drive pipeline execution. The +concrete execution DAG (text encoding, denoising, VAE decoding, …) is +hard-coded per-pipeline in ``create_pipeline_stages()``. Schemas exist +purely so that ``PipelineSelection.preset`` and +``GenerationRequest.stage_overrides`` can be type-checked up front +without touching the pipeline. + +Preset base types and the registry API live here (public API surface). +Preset *instances* are defined in pipeline-local ``presets.py`` files +(e.g. ``fastvideo/pipelines/basic/wan/presets.py``) and registered +explicitly from :func:`_register_presets` in ``fastvideo/registry.py``. +""" +from __future__ import annotations + +from collections.abc import Mapping +from dataclasses import dataclass, field +from typing import Any + +from fastvideo.api.errors import ConfigValidationError + +# ------------------------------------------------------------------- +# Types +# ------------------------------------------------------------------- + + +@dataclass(frozen=True) +class PresetStageSpec: + """A user-facing stage name within a preset, used only to validate + ``stage_overrides`` keys. Not read by pipeline execution — the real + execution DAG lives in each pipeline's ``create_pipeline_stages()``. + """ + + name: str + """Short user-facing name, e.g. ``"denoise"``, ``"sr"``.""" + + kind: str + """Semantic kind, e.g. ``"denoising"``, ``"super_resolution"``.""" + + description: str = "" + + allowed_overrides: frozenset[str] = field(default_factory=frozenset) + """Keys that may appear in ``stage_overrides[name]``.""" + + +@dataclass(frozen=True) +class InferencePreset: + """A named inference preset for a model family.""" + + name: str + """Preset name, e.g. ``"wan_t2v_1_3b"``.""" + + version: int + """Preset schema version; bump on breaking schema changes.""" + + model_family: str + """Model family key, e.g. ``"wan"``, ``"ltx2"``.""" + + description: str = "" + + workload_type: str | None = None + """Optional workload hint: ``"t2v"``, ``"i2v"``, etc.""" + + stage_schemas: tuple[PresetStageSpec, ...] = () + """User-facing stage names for ``stage_overrides`` validation. + + Validation-only: this tuple is consumed by + :func:`validate_stage_overrides` and is **not** used to drive + pipeline execution. Omit or leave empty if the preset exposes no + per-stage override surface. + """ + + defaults: dict[str, Any] = field(default_factory=dict) + """Preset-level default sampling/runtime values.""" + + stage_defaults: dict[str, dict[str, Any]] = field(default_factory=dict) + """Per-stage default overrides, keyed by stage name.""" + + +# ------------------------------------------------------------------- +# Registry +# ------------------------------------------------------------------- + +# Keyed by (model_family, name, version). +_PRESET_REGISTRY: dict[tuple[str, str, int], InferencePreset] = {} + + +def register_preset(preset: InferencePreset) -> None: + """Register a preset definition. + + Raises :class:`ValueError` on duplicate + ``(model_family, name, version)`` keys. + """ + key = (preset.model_family, preset.name, preset.version) + if key in _PRESET_REGISTRY: + raise ValueError(f"Duplicate preset registration: " + f"model_family={key[0]!r}, name={key[1]!r}, " + f"version={key[2]!r}") + _PRESET_REGISTRY[key] = preset + + +def get_preset( + name: str, + model_family: str, + version: int | None = None, +) -> InferencePreset: + """Look up a registered preset. + + When *version* is ``None`` the highest registered version for the + given *(model_family, name)* pair is returned. + + Raises :class:`~fastvideo.api.errors.ConfigValidationError` when the + preset cannot be found. + """ + if version is not None: + key = (model_family, name, version) + preset = _PRESET_REGISTRY.get(key) + if preset is not None: + return preset + raise ConfigValidationError( + "pipeline.preset", + f"unknown preset {name!r} version {version!r} " + f"for model family {model_family!r}; " + f"registered: {_format_registered(model_family)}", + ) + + # Find the highest version for (model_family, name). + candidates = [prof for (fam, n, _v), prof in _PRESET_REGISTRY.items() if fam == model_family and n == name] + if not candidates: + raise ConfigValidationError( + "pipeline.preset", + f"unknown preset {name!r} for model family " + f"{model_family!r}; " + f"registered: {_format_registered(model_family)}", + ) + return max(candidates, key=lambda p: p.version) + + +def get_presets_for_family(model_family: str, ) -> list[InferencePreset]: + """Return all presets registered for *model_family*.""" + return [prof for (fam, _n, _v), prof in _PRESET_REGISTRY.items() if fam == model_family] + + +def get_all_preset_names() -> list[str]: + """Return the sorted list of all registered preset names.""" + return sorted({prof.name for prof in _PRESET_REGISTRY.values()}) + + +# ------------------------------------------------------------------- +# Validation helpers +# ------------------------------------------------------------------- + + +def validate_stage_names( + preset: InferencePreset, + stage_overrides: Mapping[str, Any], +) -> None: + """Check that *stage_overrides* keys are valid stage names. + + Raises :class:`~fastvideo.api.errors.ConfigValidationError` with a + path-qualified message for unknown stage names. + """ + valid_names = {stage.name for stage in preset.stage_schemas} + for stage_name in stage_overrides: + if stage_name not in valid_names: + raise ConfigValidationError( + f"stage_overrides.{stage_name}", + f"unknown stage for preset {preset.name!r}; " + f"valid stages: {sorted(valid_names)}", + ) + + +def validate_stage_overrides( + preset: InferencePreset, + stage_overrides: Mapping[str, Any], +) -> None: + """Validate stage override keys against the preset. + + Calls :func:`validate_stage_names` first, then checks that each + override key is in the stage's ``allowed_overrides``. + """ + validate_stage_names(preset, stage_overrides) + stages_by_name = {stage.name: stage for stage in preset.stage_schemas} + for stage_name, overrides in stage_overrides.items(): + if not isinstance(overrides, Mapping): + raise ConfigValidationError( + f"stage_overrides.{stage_name}", + "must be a mapping", + ) + stage_spec = stages_by_name[stage_name] + if not stage_spec.allowed_overrides: + if overrides: + raise ConfigValidationError( + f"stage_overrides.{stage_name}", + f"stage {stage_name!r} does not accept " + f"overrides", + ) + continue + for key in overrides: + if key not in stage_spec.allowed_overrides: + raise ConfigValidationError( + f"stage_overrides.{stage_name}.{key}", + f"not an allowed override for stage " + f"{stage_name!r}; allowed: " + f"{sorted(stage_spec.allowed_overrides)}", + ) + + +def validate_preset_selection( + preset_name: str | None, + model_family: str, + *, + preset_version: int | None = None, + stage_overrides: Mapping[str, Any] | None = None, +) -> InferencePreset | None: + """Resolve and validate a preset selection end-to-end. + + Returns the resolved :class:`InferencePreset`, or ``None`` if + *preset_name* is ``None`` (no preset requested). + """ + if preset_name is None: + return None + preset = get_preset(preset_name, model_family, version=preset_version) + if stage_overrides: + validate_stage_overrides(preset, stage_overrides) + return preset + + +# ------------------------------------------------------------------- +# Internal helpers +# ------------------------------------------------------------------- + + +def _format_registered(model_family: str) -> str: + names = sorted({prof.name for (fam, _n, _v), prof in _PRESET_REGISTRY.items() if fam == model_family}) + if not names: + return "(none)" + return ", ".join(repr(n) for n in names) + + +__all__ = [ + "InferencePreset", + "PresetStageSpec", + "get_all_preset_names", + "get_preset", + "get_presets_for_family", + "register_preset", + "validate_preset_selection", + "validate_stage_names", + "validate_stage_overrides", +] diff --git a/fastvideo/configs/sample/base.py b/fastvideo/api/sampling_param.py similarity index 78% rename from fastvideo/configs/sample/base.py rename to fastvideo/api/sampling_param.py index e27eb5c97..452242baf 100644 --- a/fastvideo/configs/sample/base.py +++ b/fastvideo/api/sampling_param.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass +import copy +from dataclasses import dataclass, field, fields from typing import Any from fastvideo.logger import init_logger @@ -30,6 +31,16 @@ class SamplingParam: # Camera control inputs (HYWorld) pose: str | None = None # Camera trajectory: pose string (e.g., 'w-31') or JSON file path + prompt_attention_mask: list = field(default_factory=list) + negative_attention_mask: list = field(default_factory=list) + + # Camera/action control inputs (GameCraft) + camera_states: Any | None = None # Plücker coordinates [B, T_video, 6, H, W] + camera_trajectory: str | None = None + action_list: list[str] | None = None + action_speed_list: list[float] | None = None + gt_latents: Any | None = None # Ground truth latents [B, 16, T, H, W] + conditioning_mask: Any | None = None # Mask [B, 1, T, H, W] # Camera control inputs (LingBotWorld) c2ws_plucker_emb: Any | None = None # Plucker embedding: [B, C, F_lat, H_lat, W_lat] @@ -68,6 +79,7 @@ class SamplingParam: num_inference_steps: int = 50 num_inference_steps_sr: int = 50 guidance_scale: float = 1.0 + guidance_scale_2: float | None = None guidance_rescale: float = 0.0 boundary_ratio: float | None = None sigmas: list[float] | None = None @@ -80,6 +92,17 @@ class SamplingParam: movement_distance: float | None = None camera_rotation: str | None = None + # LTX2 multi-modal CFG and STG + ltx2_cfg_scale_video: float = 3.0 + ltx2_cfg_scale_audio: float = 7.0 + ltx2_modality_scale_video: float = 3.0 + ltx2_modality_scale_audio: float = 3.0 + ltx2_rescale_scale: float = 0.7 + ltx2_stg_scale_video: float = 1.0 + ltx2_stg_scale_audio: float = 1.0 + ltx2_stg_blocks_video: list[int] = field(default_factory=lambda: [29]) + ltx2_stg_blocks_audio: list[int] = field(default_factory=lambda: [29]) + # Misc save_video: bool = True return_frames: bool = True @@ -94,26 +117,58 @@ class SamplingParam: raise ValueError("prompt_path must be a txt file") def update(self, source_dict: dict[str, Any]) -> None: + valid_fields = {f.name for f in fields(self)} for key, value in source_dict.items(): - if hasattr(self, key): + if key in valid_fields: setattr(self, key, value) else: - logger.exception("%s has no attribute %s", type(self).__name__, key) + logger.error("%s has no field %s", type(self).__name__, key) self.__post_init__() @classmethod def from_pretrained(cls, model_path: str) -> "SamplingParam": - from fastvideo.registry import get_sampling_param_cls_for_name - sampling_cls = get_sampling_param_cls_for_name(model_path) - if sampling_cls is not None: - sampling_param: SamplingParam = sampling_cls() - else: - logger.warning("Couldn't find an optimal sampling param for %s. Using the default sampling param.", - model_path) - sampling_param = cls() + sampling_param = cls._from_preset(model_path) + if sampling_param is not None: + return sampling_param - return sampling_param + logger.warning( + "Couldn't find a preset for %s." + " Using the default sampling param.", + model_path, + ) + return cls() + + @classmethod + def _from_preset( + cls, + model_path: str, + ) -> "SamplingParam | None": + """Build a SamplingParam from preset defaults. + + Returns ``None`` when no preset is configured for + *model_path*, letting the caller fall back to the legacy + subclass lookup. + """ + from fastvideo.registry import get_preset_selection + + try: + preset_name, model_family = get_preset_selection(model_path) + except (ValueError, RuntimeError): + return None + if preset_name is None or model_family is None: + return None + + from fastvideo.api.presets import get_preset + + preset = get_preset(preset_name, model_family) + sp = cls() + valid_fields = {f.name for f in fields(cls)} + for key, value in preset.defaults.items(): + if key in valid_fields: + setattr(sp, key, copy.deepcopy(value)) + sp.__post_init__() + return sp @staticmethod def add_cli_args(parser: Any) -> Any: diff --git a/fastvideo/api/schema.py b/fastvideo/api/schema.py index 3ccf6a434..15dbf8c89 100644 --- a/fastvideo/api/schema.py +++ b/fastvideo/api/schema.py @@ -73,10 +73,10 @@ class ComponentConfig: @dataclass class PipelineSelection: workload_type: Literal["t2v", "i2v", "t2i", "i2i"] | None = None - profile: str | None = None - profile_version: str | None = None + preset: str | None = None + preset_version: int | None = None components: ComponentConfig = field(default_factory=ComponentConfig) - profile_overrides: dict[str, Any] = field(default_factory=dict) + preset_overrides: dict[str, Any] = field(default_factory=dict) experimental: dict[str, Any] = field(default_factory=dict) diff --git a/fastvideo/configs/sample/__init__.py b/fastvideo/configs/sample/__init__.py deleted file mode 100644 index 286db6da6..000000000 --- a/fastvideo/configs/sample/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastvideo.configs.sample.base import SamplingParam -from fastvideo.configs.sample.hunyuangamecraft import ( - HunyuanGameCraftSamplingParam, - HunyuanGameCraft65FrameSamplingParam, - HunyuanGameCraft129FrameSamplingParam, -) - -__all__ = [ - "SamplingParam", - "HunyuanGameCraftSamplingParam", - "HunyuanGameCraft65FrameSamplingParam", - "HunyuanGameCraft129FrameSamplingParam", -] diff --git a/fastvideo/configs/sample/cosmos.py b/fastvideo/configs/sample/cosmos.py deleted file mode 100644 index 32886151e..000000000 --- a/fastvideo/configs/sample/cosmos.py +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass - -from fastvideo.configs.sample.base import SamplingParam - - -@dataclass -class Cosmos_Predict2_2B_Video2World_SamplingParam(SamplingParam): - # Video parameters - height: int = 704 - width: int = 1280 - num_frames: int = 93 - fps: int = 16 - - # Denoising stage - guidance_scale: float = 7.0 - negative_prompt: str = "The video captures a series of frames showing ugly scenes, static with no motion, motion blur, over-saturation, shaky footage, low resolution, grainy texture, pixelated images, poorly lit areas, underexposed and overexposed scenes, poor color balance, washed out colors, choppy sequences, jerky movements, low frame rate, artifacting, color banding, unnatural transitions, outdated special effects, fake elements, unconvincing visuals, poorly edited content, jump cuts, visual noise, and flickering. Overall, the video is of poor quality." - num_inference_steps: int = 35 diff --git a/fastvideo/configs/sample/cosmos2_5.py b/fastvideo/configs/sample/cosmos2_5.py deleted file mode 100644 index 1bcb5573e..000000000 --- a/fastvideo/configs/sample/cosmos2_5.py +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass - -from fastvideo.configs.sample.base import SamplingParam - - -@dataclass -class Cosmos25SamplingParamBase(SamplingParam): - height: int = 704 - width: int = 1280 - num_frames: int = 77 - fps: int = 24 - seed: int = 0 - - guidance_scale: float = 7.0 - negative_prompt: str = ( - "The video captures a series of frames showing ugly scenes, static with no motion, motion blur, " - "over-saturation, shaky footage, low resolution, grainy texture, pixelated images, poorly lit areas, " - "underexposed and overexposed scenes, poor color balance, washed out colors, choppy sequences, jerky movements, " - "low frame rate, artifacting, color banding, unnatural transitions, outdated special effects, fake elements, " - "unconvincing visuals, poorly edited content, jump cuts, visual noise, and flickering. " - "Overall, the video is of poor quality.") - num_inference_steps: int = 35 diff --git a/fastvideo/configs/sample/gen3c.py b/fastvideo/configs/sample/gen3c.py deleted file mode 100644 index 80d8871e8..000000000 --- a/fastvideo/configs/sample/gen3c.py +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass - -from fastvideo.configs.sample.base import SamplingParam - - -@dataclass -class Gen3C_Cosmos_7B_SamplingParam(SamplingParam): - """Defaults for GEN3C (Cosmos-7B) camera-controlled video generation.""" - - # Video parameters (matching official GEN3C defaults) - height: int = 704 - width: int = 1280 - num_frames: int = 121 - fps: int = 24 - - # Denoising stage - guidance_scale: float = 1.0 - num_inference_steps: int = 35 - - # GEN3C camera control defaults - trajectory_type: str = "left" - movement_distance: float = 0.3 - camera_rotation: str = "center_facing" diff --git a/fastvideo/configs/sample/hunyuan.py b/fastvideo/configs/sample/hunyuan.py deleted file mode 100644 index 5441829af..000000000 --- a/fastvideo/configs/sample/hunyuan.py +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass - -from fastvideo.configs.sample.base import SamplingParam - - -@dataclass -class HunyuanSamplingParam(SamplingParam): - num_inference_steps: int = 50 - - num_frames: int = 125 - height: int = 720 - width: int = 1280 - fps: int = 24 - - guidance_scale: float = 1.0 - - -@dataclass -class FastHunyuanSamplingParam(HunyuanSamplingParam): - num_inference_steps: int = 6 diff --git a/fastvideo/configs/sample/hunyuan15.py b/fastvideo/configs/sample/hunyuan15.py deleted file mode 100644 index 61600fb87..000000000 --- a/fastvideo/configs/sample/hunyuan15.py +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -import numpy as np -from dataclasses import dataclass, field - -from fastvideo.configs.sample.base import SamplingParam - - -@dataclass -class Hunyuan15_480P_SamplingParam(SamplingParam): - num_inference_steps: int = 50 - - num_frames: int = 121 - height: int = 480 - width: int = 848 - fps: int = 24 - - guidance_scale: float = 6.0 - sigmas: list[float] | None = field(default_factory=lambda: list(np.linspace(1.0, 0.0, 50 + 1)[:-1])) - - negative_prompt: str = "" - - def __post_init__(self): - super().__post_init__() - self.sigmas = list(np.linspace(1.0, 0.0, self.num_inference_steps + 1)[:-1]) - - -@dataclass -class Hunyuan15_480P_StepDistilled_I2V_SamplingParam(Hunyuan15_480P_SamplingParam): - num_inference_steps: int = 12 - - height: int = 720 - width: int = 1280 - guidance_scale: float = 1.0 - - -@dataclass -class Hunyuan15_720P_SamplingParam(Hunyuan15_480P_SamplingParam): - height: int = 720 - width: int = 1280 - - -@dataclass -class Hunyuan15_720P_Distilled_I2V_SamplingParam(Hunyuan15_720P_SamplingParam): - guidance_scale: float = 1.0 - - -@dataclass -class Hunyuan15_SR_1080P_SamplingParam(Hunyuan15_480P_SamplingParam): - height_sr: int = 1072 - width_sr: int = 1920 - - num_inference_steps: int = 12 - num_inference_steps_sr: int = 8 - - guidance_scale: float = 1.0 diff --git a/fastvideo/configs/sample/hunyuangamecraft.py b/fastvideo/configs/sample/hunyuangamecraft.py deleted file mode 100644 index 6d8953ae5..000000000 --- a/fastvideo/configs/sample/hunyuangamecraft.py +++ /dev/null @@ -1,92 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -""" -Sampling parameters for HunyuanGameCraft video generation. - -GameCraft generates game-like videos with camera/action control. -Default parameters are based on the official implementation. -""" -from dataclasses import dataclass -from typing import Any - -from fastvideo.configs.sample.base import SamplingParam - - -@dataclass -class HunyuanGameCraftSamplingParam(SamplingParam): - """Sampling parameters for HunyuanGameCraft video generation. - - Supports camera/action conditioning via: - - camera_trajectory: Plücker coordinates for camera motion - - action_list: List of actions (e.g., ["forward", "left", "right"]) - - action_speed_list: Speed multipliers for each action - - Default resolution is 704x1280 (same as HunyuanVideo). - Default frame count is 33 video frames -> 9 latent frames. - """ - - # Number of denoising steps - num_inference_steps: int = 50 - - # Video dimensions - # 33 video frames -> 9 latent frames (4x temporal compression) - num_frames: int = 33 - height: int = 704 - width: int = 1280 - fps: int = 24 - - # Guidance scale - official GameCraft uses CFG with guidance_scale=6.0 - guidance_scale: float = 6.0 - - # Negative prompt for CFG (empty string = unconditional) - negative_prompt: str = "" - - # Camera/Action conditioning - # Camera states as Plücker coordinates [B, T_video, 6, H, W] - camera_states: Any | None = None - - # Camera trajectory file/identifier (alternative to camera_states) - camera_trajectory: str | None = None - - # Action list for camera motion (e.g., ["forward", "left"]) - action_list: list[str] | None = None - - # Speed multipliers for each action - action_speed_list: list[float] | None = None - - # History frame conditioning (for autoregressive generation) - # Ground truth latents for conditioning [B, 16, T, H, W] - gt_latents: Any | None = None - - # Mask for conditioning (1=use gt, 0=generate) [B, 1, T, H, W] - conditioning_mask: Any | None = None - - # Number of conditioning frames (for autoregressive) - maps to num_cond_frames - num_cond_frames: int = 0 - - def __post_init__(self) -> None: - super().__post_init__() - # Validate action lists - if (self.action_list is not None and self.action_speed_list is not None - and len(self.action_list) != len(self.action_speed_list)): - raise ValueError(f"action_list length ({len(self.action_list)}) must match " - f"action_speed_list length ({len(self.action_speed_list)})") - - -@dataclass -class HunyuanGameCraft65FrameSamplingParam(HunyuanGameCraftSamplingParam): - """Sampling parameters for 65-frame GameCraft generation. - - 65 video frames -> 17 latent frames (with first frame as key frame). - This is useful for longer video generation. - """ - num_frames: int = 65 - - -@dataclass -class HunyuanGameCraft129FrameSamplingParam(HunyuanGameCraftSamplingParam): - """Sampling parameters for 129-frame GameCraft generation. - - 129 video frames -> 33 latent frames. - This is the maximum supported by the official implementation. - """ - num_frames: int = 129 diff --git a/fastvideo/configs/sample/hyworld.py b/fastvideo/configs/sample/hyworld.py deleted file mode 100644 index a042150b8..000000000 --- a/fastvideo/configs/sample/hyworld.py +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass, field - -from fastvideo.configs.sample.base import SamplingParam -import numpy as np - - -@dataclass -class HYWorld_SamplingParam(SamplingParam): - num_inference_steps: int = 50 - - num_frames: int = 125 - height: int = 480 - width: int = 832 - fps: int = 24 - - # Camera trajectory: pose string (e.g., 'w-31' means generating [1 + 31] latents) or JSON file path - pose: str = 'w-31' - - guidance_scale: float = 6.0 - prompt_attention_mask: list = field(default_factory=list) - negative_attention_mask: list = field(default_factory=list) - sigmas: list[float] | None = field(default_factory=lambda: list(np.linspace(1.0, 0.0, 50 + 1)[:-1])) - - negative_prompt: str = "" diff --git a/fastvideo/configs/sample/lingbotworld.py b/fastvideo/configs/sample/lingbotworld.py deleted file mode 100644 index faf220960..000000000 --- a/fastvideo/configs/sample/lingbotworld.py +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass -from fastvideo.configs.sample.wan import Wan2_2_I2V_A14B_SamplingParam - - -@dataclass -class LingBotWorld_SamplingParam(Wan2_2_I2V_A14B_SamplingParam): - guidance_scale: float = 5.0 # high_noise - guidance_scale_2: float = 5.0 # low_noise - num_inference_steps: int = 70 - boundary_ratio: float | None = 0.947 - negative_prompt: str | None = ("画面突变,色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰," - "最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部," - "畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走," - "镜头晃动,画面闪烁,模糊,噪点,水印,签名,文字,变形,扭曲,液化,不合逻辑的结构,卡顿," - "PPT幻灯片感,过暗,欠曝,低对比度,霓虹灯光感,过度锐化,3D渲染感,人物,行人,游客,身体," - "皮肤,肢体,面部特征,汽车,电线") - fps: int = 16 - # NOTE(will): default boundary timestep is tracked by PipelineConfig, but - # can be overridden during sampling diff --git a/fastvideo/configs/sample/ltx2.py b/fastvideo/configs/sample/ltx2.py deleted file mode 100644 index 740687604..000000000 --- a/fastvideo/configs/sample/ltx2.py +++ /dev/null @@ -1,69 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass, field - -from fastvideo.configs.sample.base import SamplingParam - - -@dataclass -class LTX2BaseSamplingParam(SamplingParam): - """Default sampling parameters for LTX-2 base one-stage T2V. - - Values follow the official LTX-2 one-stage defaults. - Multi-modal CFG params are read by ``LTX2DenoisingStage``. - """ - - seed: int = 10 - num_frames: int = 121 - height: int = 512 - width: int = 768 - fps: int = 24 - num_inference_steps: int = 40 - guidance_scale: float = 3.0 - # Copied/following official LTX-2 DEFAULT_NEGATIVE_PROMPT. - negative_prompt: str = ("blurry, out of focus, overexposed, underexposed, low contrast, " - "washed out colors, excessive noise, grainy texture, poor lighting, " - "flickering, motion blur, distorted proportions, unnatural skin " - "tones, deformed facial features, asymmetrical face, missing facial " - "features, extra limbs, disfigured hands, wrong hand count, " - "artifacts around text, inconsistent perspective, camera shake, " - "incorrect depth of field, background too sharp, background clutter, " - "distracting reflections, harsh shadows, inconsistent lighting " - "direction, color banding, cartoonish rendering, 3D CGI look, " - "unrealistic materials, uncanny valley effect, incorrect ethnicity, " - "wrong gender, exaggerated expressions, wrong gaze direction, " - "mismatched lip sync, silent or muted audio, distorted voice, " - "robotic voice, echo, background noise, off-sync audio, incorrect " - "dialogue, added dialogue, repetitive speech, jittery movement, " - "awkward pauses, incorrect timing, unnatural transitions, " - "inconsistent framing, tilted camera, flat lighting, inconsistent " - "tone, cinematic oversaturation, stylized filters, or AI artifacts.") - # Official LTX-2 multi-modal CFG defaults. - ltx2_cfg_scale_video: float = 3.0 - ltx2_cfg_scale_audio: float = 7.0 - ltx2_modality_scale_video: float = 3.0 - ltx2_modality_scale_audio: float = 3.0 - ltx2_rescale_scale: float = 0.7 - # STG (Spatio-Temporal Guidance) defaults from official LTX-2. - ltx2_stg_scale_video: float = 1.0 - ltx2_stg_scale_audio: float = 1.0 - ltx2_stg_blocks_video: list[int] = field(default_factory=lambda: [29]) - ltx2_stg_blocks_audio: list[int] = field(default_factory=lambda: [29]) - - -@dataclass -class LTX2DistilledSamplingParam(SamplingParam): - """Default sampling parameters for LTX-2 distilled one-stage T2V.""" - - seed: int = 10 - num_frames: int = 121 - height: int = 1024 - width: int = 1536 - fps: int = 24 - num_inference_steps: int = 8 - guidance_scale: float = 1.0 - # No default negative_prompt for distilled models - negative_prompt: str = "" - - -# Backward compatibility alias. -LTX2SamplingParam = LTX2DistilledSamplingParam diff --git a/fastvideo/configs/sample/sd35.py b/fastvideo/configs/sample/sd35.py deleted file mode 100644 index fadcec853..000000000 --- a/fastvideo/configs/sample/sd35.py +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations - -from dataclasses import dataclass - -from fastvideo.configs.sample.base import SamplingParam - - -@dataclass -class SD35SamplingParam(SamplingParam): - - prompt: str | None = "a photo of a cat" - negative_prompt: str = "" - - num_videos_per_prompt: int = 1 - seed: int = 0 - - num_frames: int = 1 - height: int = 512 - width: int = 512 - fps: int = 1 - - num_inference_steps: int = 28 - guidance_scale: float = 6.0 diff --git a/fastvideo/configs/sample/turbodiffusion.py b/fastvideo/configs/sample/turbodiffusion.py deleted file mode 100644 index 009bce201..000000000 --- a/fastvideo/configs/sample/turbodiffusion.py +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -""" -TurboDiffusion sampling parameters. - -TurboDiffusion uses RCM (recurrent Consistency Model) scheduler for -1-4 step video generation with no classifier-free guidance. -""" -from dataclasses import dataclass - -from fastvideo.configs.sample.base import SamplingParam - - -@dataclass -class TurboDiffusionT2V_1_3B_SamplingParam(SamplingParam): - """Sampling parameters for TurboDiffusion T2V 1.3B model. - - Uses 4-step RCM sampling with guidance_scale=1.0 (no CFG). - """ - # Video parameters - height: int = 480 - width: int = 832 - num_frames: int = 81 - fps: int = 16 - - # Denoising stage - TurboDiffusion uses 1-4 steps with no CFG - guidance_scale: float = 1.0 - num_inference_steps: int = 4 - - # No negative prompt needed for TurboDiffusion (no CFG) - negative_prompt: str | None = None - - -@dataclass -class TurboDiffusionT2V_14B_SamplingParam(SamplingParam): - """Sampling parameters for TurboDiffusion T2V 14B model. - - Uses 4-step RCM sampling with guidance_scale=1.0 (no CFG). - """ - # Video parameters (720p for 14B) - height: int = 720 - width: int = 1280 - num_frames: int = 81 - fps: int = 16 - - # Denoising stage - TurboDiffusion uses 1-4 steps with no CFG - guidance_scale: float = 1.0 - num_inference_steps: int = 4 - - # No negative prompt needed for TurboDiffusion (no CFG) - negative_prompt: str | None = None - - -@dataclass -class TurboDiffusionI2V_A14B_SamplingParam(SamplingParam): - """Sampling parameters for TurboDiffusion I2V A14B model. - - Uses 4-step RCM sampling with dual-model switching (high/low noise). - """ - # Video parameters (720p for A14B I2V) - height: int = 720 - width: int = 1280 - num_frames: int = 81 - fps: int = 16 - - # Denoising stage - TurboDiffusion uses 1-4 steps with no CFG - guidance_scale: float = 1.0 - num_inference_steps: int = 4 - - # Note: boundary_ratio is set in the pipeline config (TurboDiffusionI2VConfig), - # not here. This keeps sampling params and pipeline config separate. - - # No negative prompt needed for TurboDiffusion (no CFG) - negative_prompt: str | None = None diff --git a/fastvideo/configs/sample/wan.py b/fastvideo/configs/sample/wan.py deleted file mode 100644 index f24e67ebc..000000000 --- a/fastvideo/configs/sample/wan.py +++ /dev/null @@ -1,154 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -from dataclasses import dataclass - -from fastvideo.configs.sample.base import SamplingParam - - -@dataclass -class WanT2V_1_3B_SamplingParam(SamplingParam): - # Video parameters - height: int = 480 - width: int = 832 - num_frames: int = 81 - fps: int = 16 - - # Denoising stage - guidance_scale: float = 3.0 - negative_prompt: str = "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards" - num_inference_steps: int = 50 - - -@dataclass -class WanT2V_14B_SamplingParam(SamplingParam): - # Video parameters - height: int = 720 - width: int = 1280 - num_frames: int = 81 - fps: int = 16 - - # Denoising stage - guidance_scale: float = 5.0 - negative_prompt: str = "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards" - num_inference_steps: int = 50 - - -@dataclass -class WanI2V_14B_480P_SamplingParam(WanT2V_1_3B_SamplingParam): - # Denoising stage - guidance_scale: float = 5.0 - num_inference_steps: int = 40 - - -@dataclass -class WanI2V_14B_720P_SamplingParam(WanT2V_14B_SamplingParam): - # Denoising stage - guidance_scale: float = 5.0 - num_inference_steps: int = 40 - - -@dataclass -class FastWanT2V480P_SamplingParam(WanT2V_1_3B_SamplingParam): - # DMD parameters - # dmd_denoising_steps: list[int] | None = field(default_factory=lambda: [1000, 757, 522]) - num_inference_steps: int = 3 - num_frames: int = 61 - height: int = 448 - width: int = 832 - fps: int = 16 - - -# ============================================= -# ============= Wan2.1 Fun Models ============= -# ============================================= -@dataclass -class Wan2_1_Fun_1_3B_InP_SamplingParam(SamplingParam): - """Sampling parameters for Wan2.1 Fun 1.3B InP model.""" - height: int = 480 - width: int = 832 - num_frames: int = 81 - fps: int = 16 - negative_prompt: str | None = "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" - guidance_scale: float = 6.0 - num_inference_steps: int = 50 - - -@dataclass -class Wan2_1_Fun_1_3B_Control_SamplingParam(SamplingParam): - fps: int = 16 - num_frames: int = 49 - height: int = 832 - width: int = 480 - guidance_scale: float = 6.0 - - -# ============================================= -# ============= Wan2.2 TI2V Models ============= -# ============================================= -@dataclass -class Wan2_2_Base_SamplingParam(SamplingParam): - """Sampling parameters for Wan2.2 TI2V 5B model.""" - negative_prompt: str | None = "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" - - -@dataclass -class Wan2_2_TI2V_5B_SamplingParam(Wan2_2_Base_SamplingParam): - """Sampling parameters for Wan2.2 TI2V 5B model.""" - height: int = 704 - width: int = 1280 - num_frames: int = 121 - fps: int = 24 - guidance_scale: float = 5.0 - num_inference_steps: int = 50 - - -@dataclass -class Wan2_2_T2V_A14B_SamplingParam(Wan2_2_Base_SamplingParam): - guidance_scale: float = 4.0 # high_noise - guidance_scale_2: float = 3.0 # low_noise - num_inference_steps: int = 40 - fps: int = 16 - # NOTE(will): default boundary timestep is tracked by PipelineConfig, but - # can be overridden during sampling - - -@dataclass -class Wan2_2_I2V_A14B_SamplingParam(Wan2_2_Base_SamplingParam): - guidance_scale: float = 3.5 # high_noise - guidance_scale_2: float = 3.5 # low_noise - num_inference_steps: int = 40 - fps: int = 16 - # NOTE(will): default boundary timestep is tracked by PipelineConfig, but - # can be overridden during sampling - - -@dataclass -class Wan2_2_Fun_A14B_Control_SamplingParam(Wan2_1_Fun_1_3B_Control_SamplingParam): - num_frames: int = 81 - - -# ============================================= -# ============= Causal Self-Forcing ============= -# ============================================= -@dataclass -class SelfForcingWan2_1_T2V_1_3B_480P_SamplingParam(Wan2_1_Fun_1_3B_InP_SamplingParam): - pass - - -@dataclass -class SelfForcingWan2_2_T2V_A14B_480P_SamplingParam(Wan2_2_T2V_A14B_SamplingParam): - num_inference_steps: int = 8 - num_frames: int = 81 - height: int = 448 - width: int = 832 - fps: int = 16 - - -@dataclass -class MatrixGame2_SamplingParam(SamplingParam): - height: int = 352 - width: int = 640 - num_frames: int = 57 - fps: int = 25 - guidance_scale: float = 1.0 - num_inference_steps: int = 3 - negative_prompt: str | None = None diff --git a/fastvideo/entrypoints/streaming_generator.py b/fastvideo/entrypoints/streaming_generator.py index b0f8aec1c..64f1a08b8 100644 --- a/fastvideo/entrypoints/streaming_generator.py +++ b/fastvideo/entrypoints/streaming_generator.py @@ -8,7 +8,7 @@ import torch import torchvision from einops import rearrange -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.entrypoints.video_generator import VideoGenerator from fastvideo.fastvideo_args import FastVideoArgs from fastvideo.logger import init_logger diff --git a/fastvideo/entrypoints/video_generator.py b/fastvideo/entrypoints/video_generator.py index 7047e10bf..91ba1837a 100644 --- a/fastvideo/entrypoints/video_generator.py +++ b/fastvideo/entrypoints/video_generator.py @@ -35,7 +35,7 @@ from fastvideo.api.compat import ( ) from fastvideo.api.results import GenerationResult from fastvideo.api.schema import GenerationRequest, GeneratorConfig -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.fastvideo_args import FastVideoArgs from fastvideo.logger import init_logger from fastvideo.pipelines import ForwardBatch diff --git a/fastvideo/pipelines/basic/cosmos/presets.py b/fastvideo/pipelines/basic/cosmos/presets.py new file mode 100644 index 000000000..09f1b748f --- /dev/null +++ b/fastvideo/pipelines/basic/cosmos/presets.py @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Cosmos model family pipeline presets. + +Covers both Cosmos Predict2 and Cosmos Predict2.5, which share the +same pipeline directory but have distinct model families. +""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Main denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +# ------------------------------------------------------------------- +# Cosmos Predict2 +# ------------------------------------------------------------------- + +_COSMOS_NEGATIVE_PROMPT = ("The video captures a series of frames showing ugly scenes, " + "static with no motion, motion blur, over-saturation, shaky " + "footage, low resolution, grainy texture, pixelated images, " + "poorly lit areas, underexposed and overexposed scenes, poor " + "color balance, washed out colors, choppy sequences, jerky " + "movements, low frame rate, artifacting, color banding, " + "unnatural transitions, outdated special effects, fake elements, " + "unconvincing visuals, poorly edited content, jump cuts, visual " + "noise, and flickering. Overall, the video is of poor quality.") + +COSMOS_PREDICT2_2B = InferencePreset( + name="cosmos_predict2_2b", + version=1, + model_family="cosmos", + description="Cosmos Predict2 2B Video2World", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 704, + "width": 1280, + "num_frames": 93, + "fps": 16, + "guidance_scale": 7.0, + "num_inference_steps": 35, + "negative_prompt": _COSMOS_NEGATIVE_PROMPT, + }, +) + +# ------------------------------------------------------------------- +# Cosmos Predict2.5 +# ------------------------------------------------------------------- + +_COSMOS25_NEGATIVE_PROMPT = ("The video captures a series of frames showing ugly scenes, " + "static with no motion, motion blur, over-saturation, shaky " + "footage, low resolution, grainy texture, pixelated images, " + "poorly lit areas, underexposed and overexposed scenes, poor " + "color balance, washed out colors, choppy sequences, jerky " + "movements, low frame rate, artifacting, color banding, " + "unnatural transitions, outdated special effects, fake elements, " + "unconvincing visuals, poorly edited content, jump cuts, visual " + "noise, and flickering. Overall, the video is of poor quality.") + +COSMOS25_PREDICT2_2B = InferencePreset( + name="cosmos25_predict2_2b", + version=1, + model_family="cosmos25", + description="Cosmos Predict2.5 2B", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "seed": 0, + "height": 704, + "width": 1280, + "num_frames": 77, + "fps": 24, + "guidance_scale": 7.0, + "num_inference_steps": 35, + "negative_prompt": _COSMOS25_NEGATIVE_PROMPT, + }, +) + +ALL_PRESETS = (COSMOS_PREDICT2_2B, COSMOS25_PREDICT2_2B) diff --git a/fastvideo/pipelines/basic/gamecraft/presets.py b/fastvideo/pipelines/basic/gamecraft/presets.py new file mode 100644 index 000000000..1d2261ac0 --- /dev/null +++ b/fastvideo/pipelines/basic/gamecraft/presets.py @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: Apache-2.0 +"""HunyuanGameCraft model family pipeline presets.""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Action-controlled denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +GAMECRAFT_I2V = InferencePreset( + name="gamecraft_i2v", + version=1, + model_family="gamecraft", + description="HunyuanGameCraft I2V at 704x1280", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 704, + "width": 1280, + "num_frames": 33, + "fps": 24, + "guidance_scale": 6.0, + "num_inference_steps": 50, + "negative_prompt": "", + }, +) + +ALL_PRESETS = (GAMECRAFT_I2V, ) diff --git a/fastvideo/pipelines/basic/gen3c/presets.py b/fastvideo/pipelines/basic/gen3c/presets.py new file mode 100644 index 000000000..117f35b30 --- /dev/null +++ b/fastvideo/pipelines/basic/gen3c/presets.py @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: Apache-2.0 +"""GEN3C model family pipeline presets.""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Camera-controlled denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +GEN3C_COSMOS_7B = InferencePreset( + name="gen3c_cosmos_7b", + version=1, + model_family="gen3c", + description="GEN3C Cosmos 7B", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 704, + "width": 1280, + "num_frames": 121, + "fps": 24, + "guidance_scale": 1.0, + "num_inference_steps": 35, + "trajectory_type": "left", + "movement_distance": 0.3, + "camera_rotation": "center_facing", + }, +) + +ALL_PRESETS = (GEN3C_COSMOS_7B, ) diff --git a/fastvideo/pipelines/basic/hunyuan/presets.py b/fastvideo/pipelines/basic/hunyuan/presets.py new file mode 100644 index 000000000..28ceed462 --- /dev/null +++ b/fastvideo/pipelines/basic/hunyuan/presets.py @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Hunyuan model family pipeline presets.""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Main denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +HUNYUAN_T2V = InferencePreset( + name="hunyuan_t2v", + version=1, + model_family="hunyuan", + description="HunyuanVideo T2V at 720p", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 720, + "width": 1280, + "num_frames": 125, + "fps": 24, + "guidance_scale": 1.0, + "num_inference_steps": 50, + }, +) + +FAST_HUNYUAN_T2V = InferencePreset( + name="fast_hunyuan_t2v", + version=1, + model_family="hunyuan", + description="FastHunyuan T2V at 720p", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 720, + "width": 1280, + "num_frames": 125, + "fps": 24, + "guidance_scale": 1.0, + "num_inference_steps": 6, + }, +) + +ALL_PRESETS = (HUNYUAN_T2V, FAST_HUNYUAN_T2V) diff --git a/fastvideo/pipelines/basic/hunyuan15/presets.py b/fastvideo/pipelines/basic/hunyuan15/presets.py new file mode 100644 index 000000000..a760653da --- /dev/null +++ b/fastvideo/pipelines/basic/hunyuan15/presets.py @@ -0,0 +1,152 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Hunyuan 1.5 model family pipeline presets.""" +import numpy as np + +from fastvideo.api.presets import InferencePreset, PresetStageSpec + + +def _sigmas(n: int) -> list[float]: + """Precompute sigmas schedule for *n* inference steps.""" + return np.linspace(1.0, 0.0, n + 1).tolist()[:-1] + + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Main denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +_SR_STAGE = PresetStageSpec( + name="sr", + kind="super_resolution", + description="Super-resolution upscaling pass", + allowed_overrides=frozenset({ + "num_inference_steps", + }), +) + +# ------------------------------------------------------------------- +# Hunyuan 1.5 T2V presets +# ------------------------------------------------------------------- + +HUNYUAN15_T2V_480P = InferencePreset( + name="hunyuan15_t2v_480p", + version=1, + model_family="hunyuan15", + description="HunyuanVideo 1.5 T2V at 480p", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 480, + "width": 848, + "num_frames": 121, + "fps": 24, + "guidance_scale": 6.0, + "num_inference_steps": 50, + "negative_prompt": "", + "sigmas": _sigmas(50), + }, +) + +HUNYUAN15_T2V_720P = InferencePreset( + name="hunyuan15_t2v_720p", + version=1, + model_family="hunyuan15", + description="HunyuanVideo 1.5 T2V at 720p", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 720, + "width": 1280, + "num_frames": 121, + "fps": 24, + "guidance_scale": 6.0, + "num_inference_steps": 50, + "negative_prompt": "", + "sigmas": _sigmas(50), + }, +) + +# ------------------------------------------------------------------- +# Hunyuan 1.5 I2V presets +# ------------------------------------------------------------------- + +HUNYUAN15_I2V_480P_DISTILLED = InferencePreset( + name="hunyuan15_i2v_480p_distilled", + version=1, + model_family="hunyuan15", + description="HunyuanVideo 1.5 I2V 480p step-distilled", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 720, + "width": 1280, + "num_frames": 121, + "fps": 24, + "guidance_scale": 1.0, + "num_inference_steps": 12, + "negative_prompt": "", + "sigmas": _sigmas(12), + }, +) + +HUNYUAN15_I2V_720P_DISTILLED = InferencePreset( + name="hunyuan15_i2v_720p_distilled", + version=1, + model_family="hunyuan15", + description="HunyuanVideo 1.5 I2V 720p distilled", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 720, + "width": 1280, + "num_frames": 121, + "fps": 24, + "guidance_scale": 1.0, + "num_inference_steps": 50, + "negative_prompt": "", + "sigmas": _sigmas(50), + }, +) + +# ------------------------------------------------------------------- +# Hunyuan 1.5 SR preset (two-stage) +# ------------------------------------------------------------------- + +HUNYUAN15_SR_1080P = InferencePreset( + name="hunyuan15_sr_1080p", + version=1, + model_family="hunyuan15", + description="HunyuanVideo 1.5 SR to 1080p (two-stage)", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, _SR_STAGE), + defaults={ + "height": 480, + "width": 848, + "num_frames": 121, + "fps": 24, + "guidance_scale": 1.0, + "num_inference_steps": 12, + "negative_prompt": "", + "sigmas": _sigmas(12), + }, + stage_defaults={ + "sr": { + "height_sr": 1072, + "width_sr": 1920, + "num_inference_steps": 8, + }, + }, +) + +ALL_PRESETS = ( + HUNYUAN15_T2V_480P, + HUNYUAN15_T2V_720P, + HUNYUAN15_I2V_480P_DISTILLED, + HUNYUAN15_I2V_720P_DISTILLED, + HUNYUAN15_SR_1080P, +) diff --git a/fastvideo/pipelines/basic/hyworld/presets.py b/fastvideo/pipelines/basic/hyworld/presets.py new file mode 100644 index 000000000..f5ce21ab0 --- /dev/null +++ b/fastvideo/pipelines/basic/hyworld/presets.py @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: Apache-2.0 +"""HYWorld model family pipeline presets.""" +import numpy as np + +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_HYWORLD_SIGMAS = list(np.linspace(1.0, 0.0, 51).tolist()[:-1]) + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Camera-controlled denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +HYWORLD_T2V = InferencePreset( + name="hyworld_t2v", + version=1, + model_family="hyworld", + description="HY-WorldPlay bidirectional at 480p", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 480, + "width": 832, + "num_frames": 125, + "fps": 24, + "guidance_scale": 6.0, + "num_inference_steps": 50, + "negative_prompt": "", + "pose": "w-31", + "sigmas": _HYWORLD_SIGMAS, + }, +) + +ALL_PRESETS = (HYWORLD_T2V, ) diff --git a/fastvideo/pipelines/basic/lingbotworld/presets.py b/fastvideo/pipelines/basic/lingbotworld/presets.py new file mode 100644 index 000000000..6f8f6c80c --- /dev/null +++ b/fastvideo/pipelines/basic/lingbotworld/presets.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: Apache-2.0 +"""LingBotWorld model family pipeline presets.""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Dual-guidance denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + "guidance_scale_2", + "boundary_ratio", + }), +) + +LINGBOTWORLD_I2V = InferencePreset( + name="lingbotworld_i2v", + version=1, + model_family="lingbotworld", + description="LingBot-World I2V with dual guidance", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "guidance_scale": + 5.0, + "guidance_scale_2": + 5.0, + "num_inference_steps": + 70, + "fps": + 16, + "boundary_ratio": + 0.947, + "negative_prompt": ("画面突变,色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰," + "最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部," + "畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走," + "镜头晃动,画面闪烁,模糊,噪点,水印,签名,文字,变形,扭曲,液化,不合逻辑的结构,卡顿," + "PPT幻灯片感,过暗,欠曝,低对比度,霓虹灯光感,过度锐化,3D渲染感,人物,行人,游客,身体," + "皮肤,肢体,面部特征,汽车,电线"), + }, +) + +ALL_PRESETS = (LINGBOTWORLD_I2V, ) diff --git a/fastvideo/pipelines/basic/longcat/presets.py b/fastvideo/pipelines/basic/longcat/presets.py new file mode 100644 index 000000000..39a07652e --- /dev/null +++ b/fastvideo/pipelines/basic/longcat/presets.py @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: Apache-2.0 +"""LongCat model family pipeline presets.""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Main denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +LONGCAT_T2V = InferencePreset( + name="longcat_t2v", + version=1, + model_family="longcat", + description="LongCat-Video T2V at 480p", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 480, + "width": 848, + "num_frames": 121, + "fps": 24, + "guidance_scale": 1.0, + "num_inference_steps": 50, + }, +) + +LONGCAT_I2V = InferencePreset( + name="longcat_i2v", + version=1, + model_family="longcat", + description="LongCat-Video I2V at 480p", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 480, + "width": 848, + "num_frames": 121, + "fps": 24, + "guidance_scale": 1.0, + "num_inference_steps": 50, + }, +) + +LONGCAT_VC = InferencePreset( + name="longcat_vc", + version=1, + model_family="longcat", + description="LongCat-Video continuation at 480p", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 480, + "width": 848, + "num_frames": 121, + "fps": 24, + "guidance_scale": 1.0, + "num_inference_steps": 50, + }, +) + +ALL_PRESETS = (LONGCAT_T2V, LONGCAT_I2V, LONGCAT_VC) diff --git a/fastvideo/pipelines/basic/ltx2/presets.py b/fastvideo/pipelines/basic/ltx2/presets.py new file mode 100644 index 000000000..2ea8ffe6d --- /dev/null +++ b/fastvideo/pipelines/basic/ltx2/presets.py @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: Apache-2.0 +"""LTX2 model family pipeline presets.""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_LTX2_NEGATIVE_PROMPT = ("blurry, out of focus, overexposed, underexposed, low contrast, " + "washed out colors, excessive noise, grainy texture, poor lighting, " + "flickering, motion blur, distorted proportions, unnatural skin " + "tones, deformed facial features, asymmetrical face, missing facial " + "features, extra limbs, disfigured hands, wrong hand count, " + "artifacts around text, inconsistent perspective, camera shake, " + "incorrect depth of field, background too sharp, background clutter, " + "distracting reflections, harsh shadows, inconsistent lighting " + "direction, color banding, cartoonish rendering, 3D CGI look, " + "unrealistic materials, uncanny valley effect, incorrect ethnicity, " + "wrong gender, exaggerated expressions, wrong gaze direction, " + "mismatched lip sync, silent or muted audio, distorted voice, " + "robotic voice, echo, background noise, off-sync audio, incorrect " + "dialogue, added dialogue, repetitive speech, jittery movement, " + "awkward pauses, incorrect timing, unnatural transitions, " + "inconsistent framing, tilted camera, flat lighting, inconsistent " + "tone, cinematic oversaturation, stylized filters, or AI artifacts.") + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Main denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +LTX2_BASE = InferencePreset( + name="ltx2_base", + version=1, + model_family="ltx2", + description="LTX-2 base at 512x768", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "seed": 10, + "height": 512, + "width": 768, + "num_frames": 121, + "fps": 24, + "guidance_scale": 3.0, + "num_inference_steps": 40, + "negative_prompt": _LTX2_NEGATIVE_PROMPT, + "ltx2_cfg_scale_video": 3.0, + "ltx2_cfg_scale_audio": 7.0, + "ltx2_modality_scale_video": 3.0, + "ltx2_modality_scale_audio": 3.0, + "ltx2_rescale_scale": 0.7, + "ltx2_stg_scale_video": 1.0, + "ltx2_stg_scale_audio": 1.0, + "ltx2_stg_blocks_video": [29], + "ltx2_stg_blocks_audio": [29], + }, +) + +LTX2_DISTILLED = InferencePreset( + name="ltx2_distilled", + version=1, + model_family="ltx2", + description="LTX-2 distilled at 1024x1536", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "seed": 10, + "height": 1024, + "width": 1536, + "num_frames": 121, + "fps": 24, + "guidance_scale": 1.0, + "num_inference_steps": 8, + "negative_prompt": "", + }, +) + +ALL_PRESETS = (LTX2_BASE, LTX2_DISTILLED) diff --git a/fastvideo/pipelines/basic/matrixgame/presets.py b/fastvideo/pipelines/basic/matrixgame/presets.py new file mode 100644 index 000000000..cf70a341a --- /dev/null +++ b/fastvideo/pipelines/basic/matrixgame/presets.py @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: Apache-2.0 +"""MatrixGame model family pipeline presets.""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Causal denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +MATRIXGAME_I2V = InferencePreset( + name="matrixgame_i2v", + version=1, + model_family="matrixgame", + description="Matrix-Game 2.0 I2V", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 352, + "width": 640, + "num_frames": 57, + "fps": 25, + "guidance_scale": 1.0, + "num_inference_steps": 3, + "negative_prompt": None, + }, +) + +ALL_PRESETS = (MATRIXGAME_I2V, ) diff --git a/fastvideo/pipelines/basic/sd35/presets.py b/fastvideo/pipelines/basic/sd35/presets.py new file mode 100644 index 000000000..1c274dcfb --- /dev/null +++ b/fastvideo/pipelines/basic/sd35/presets.py @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Stable Diffusion 3.5 model family pipeline presets.""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Main denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +SD35_MEDIUM = InferencePreset( + name="sd35_medium", + version=1, + model_family="sd35", + description="Stable Diffusion 3.5 Medium (text-to-image)", + workload_type="t2i", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 512, + "width": 512, + "num_frames": 1, + "fps": 1, + "seed": 0, + "guidance_scale": 6.0, + "num_inference_steps": 28, + "negative_prompt": "", + }, +) + +ALL_PRESETS = (SD35_MEDIUM, ) diff --git a/fastvideo/pipelines/basic/turbodiffusion/presets.py b/fastvideo/pipelines/basic/turbodiffusion/presets.py new file mode 100644 index 000000000..2d0a5bec0 --- /dev/null +++ b/fastvideo/pipelines/basic/turbodiffusion/presets.py @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: Apache-2.0 +"""TurboDiffusion model family pipeline presets.""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Fast few-step denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +TURBO_T2V_1_3B = InferencePreset( + name="turbo_t2v_1_3b", + version=1, + model_family="turbodiffusion", + description="TurboWan 2.1 T2V 1.3B (4-step)", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 480, + "width": 832, + "num_frames": 81, + "fps": 16, + "guidance_scale": 1.0, + "num_inference_steps": 4, + "negative_prompt": None, + }, +) + +TURBO_T2V_14B = InferencePreset( + name="turbo_t2v_14b", + version=1, + model_family="turbodiffusion", + description="TurboWan 2.1 T2V 14B (4-step)", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 720, + "width": 1280, + "num_frames": 81, + "fps": 16, + "guidance_scale": 1.0, + "num_inference_steps": 4, + "negative_prompt": None, + }, +) + +TURBO_I2V_A14B = InferencePreset( + name="turbo_i2v_a14b", + version=1, + model_family="turbodiffusion", + description="TurboWan 2.2 I2V A14B (4-step)", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 720, + "width": 1280, + "num_frames": 81, + "fps": 16, + "guidance_scale": 1.0, + "num_inference_steps": 4, + "negative_prompt": None, + }, +) + +ALL_PRESETS = ( + TURBO_T2V_1_3B, + TURBO_T2V_14B, + TURBO_I2V_A14B, +) diff --git a/fastvideo/pipelines/basic/wan/presets.py b/fastvideo/pipelines/basic/wan/presets.py new file mode 100644 index 000000000..0c4f48165 --- /dev/null +++ b/fastvideo/pipelines/basic/wan/presets.py @@ -0,0 +1,347 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Wan model family pipeline presets. + +Each preset is a named inference preset that declares the user-facing +stage topology, default sampling values, and which per-stage overrides +are allowed. Presets are registered explicitly from +:func:`fastvideo.registry._register_presets`. +""" +from fastvideo.api.presets import InferencePreset, PresetStageSpec + +# ------------------------------------------------------------------- +# Shared negative prompts +# ------------------------------------------------------------------- + +_NEGATIVE_PROMPT_EN = ("Bright tones, overexposed, static, blurred details, subtitles," + " style, works, paintings, images, static, overall gray, worst" + " quality, low quality, JPEG compression residue, ugly," + " incomplete, extra fingers, poorly drawn hands, poorly drawn" + " faces, deformed, disfigured, misshapen limbs, fused fingers," + " still picture, messy background, three legs, many people in" + " the background, walking backwards") + +_NEGATIVE_PROMPT_CN = ("色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面," + "静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的," + "多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的," + "形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿," + "背景人很多,倒着走") + +# ------------------------------------------------------------------- +# Shared stage specs +# ------------------------------------------------------------------- + +_DENOISE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + description="Main denoising pass", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + }), +) + +# ------------------------------------------------------------------- +# Wan 2.1 T2V presets +# ------------------------------------------------------------------- + +WAN_T2V_1_3B = InferencePreset( + name="wan_t2v_1_3b", + version=1, + model_family="wan", + description="Wan 2.1 T2V 1.3B at 480p", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 480, + "width": 832, + "num_frames": 81, + "fps": 16, + "guidance_scale": 3.0, + "num_inference_steps": 50, + "negative_prompt": _NEGATIVE_PROMPT_EN, + }, +) + +WAN_T2V_14B = InferencePreset( + name="wan_t2v_14b", + version=1, + model_family="wan", + description="Wan 2.1 T2V 14B at 720p", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 720, + "width": 1280, + "num_frames": 81, + "fps": 16, + "guidance_scale": 5.0, + "num_inference_steps": 50, + "negative_prompt": _NEGATIVE_PROMPT_EN, + }, +) + +# ------------------------------------------------------------------- +# Wan 2.1 I2V presets +# ------------------------------------------------------------------- + +WAN_I2V_14B_480P = InferencePreset( + name="wan_i2v_14b_480p", + version=1, + model_family="wan", + description="Wan 2.1 I2V 14B at 480p", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 480, + "width": 832, + "num_frames": 81, + "fps": 16, + "guidance_scale": 5.0, + "num_inference_steps": 40, + "negative_prompt": _NEGATIVE_PROMPT_EN, + }, +) + +WAN_I2V_14B_720P = InferencePreset( + name="wan_i2v_14b_720p", + version=1, + model_family="wan", + description="Wan 2.1 I2V 14B at 720p", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 720, + "width": 1280, + "num_frames": 81, + "fps": 16, + "guidance_scale": 5.0, + "num_inference_steps": 40, + "negative_prompt": _NEGATIVE_PROMPT_EN, + }, +) + +# ------------------------------------------------------------------- +# Wan 2.2 presets +# ------------------------------------------------------------------- + +_DENOISE_STAGE_WAN22 = PresetStageSpec( + name="denoise", + kind="denoising", + description="Wan 2.2 two-guidance-scale denoising", + allowed_overrides=frozenset({ + "num_inference_steps", + "guidance_scale", + "guidance_scale_2", + "boundary_ratio", + }), +) + +WAN_2_2_T2V_A14B = InferencePreset( + name="wan_2_2_t2v_a14b", + version=1, + model_family="wan", + description="Wan 2.2 T2V A14B with dual guidance scales", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE_WAN22, ), + defaults={ + "guidance_scale": 4.0, + "guidance_scale_2": 3.0, + "num_inference_steps": 40, + "fps": 16, + "negative_prompt": _NEGATIVE_PROMPT_CN, + }, +) + +WAN_2_2_I2V_A14B = InferencePreset( + name="wan_2_2_i2v_a14b", + version=1, + model_family="wan", + description="Wan 2.2 I2V A14B with dual guidance scales", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE_WAN22, ), + defaults={ + "guidance_scale": 3.5, + "guidance_scale_2": 3.5, + "num_inference_steps": 40, + "fps": 16, + "negative_prompt": _NEGATIVE_PROMPT_CN, + }, +) + +# ------------------------------------------------------------------- +# Wan 2.1 Fun / Control presets +# ------------------------------------------------------------------- + +WAN_FUN_1_3B_INP = InferencePreset( + name="wan_fun_1_3b_inp", + version=1, + model_family="wan", + description="Wan 2.1 Fun 1.3B InP (image-to-video inpainting)", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 480, + "width": 832, + "num_frames": 81, + "fps": 16, + "guidance_scale": 6.0, + "num_inference_steps": 50, + "negative_prompt": _NEGATIVE_PROMPT_CN, + }, +) + +WAN_FUN_1_3B_CONTROL = InferencePreset( + name="wan_fun_1_3b_control", + version=1, + model_family="wan", + description="Wan 2.1 Fun 1.3B Control (V2V)", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 832, + "width": 480, + "num_frames": 49, + "fps": 16, + "guidance_scale": 6.0, + "negative_prompt": _NEGATIVE_PROMPT_EN, + }, +) + +# ------------------------------------------------------------------- +# FastWan (DMD) presets +# ------------------------------------------------------------------- + +FAST_WAN_T2V_480P = InferencePreset( + name="fast_wan_t2v_480p", + version=1, + model_family="wan", + description="FastWan 2.1 T2V DMD at 480p (3-step)", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 448, + "width": 832, + "num_frames": 61, + "fps": 16, + "guidance_scale": 3.0, + "num_inference_steps": 3, + "negative_prompt": _NEGATIVE_PROMPT_EN, + }, +) + +# ------------------------------------------------------------------- +# Wan 2.2 TI2V 5B presets +# ------------------------------------------------------------------- + +WAN_2_2_TI2V_5B = InferencePreset( + name="wan_2_2_ti2v_5b", + version=1, + model_family="wan", + description="Wan 2.2 TI2V 5B", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 704, + "width": 1280, + "num_frames": 121, + "fps": 24, + "guidance_scale": 5.0, + "num_inference_steps": 50, + "negative_prompt": _NEGATIVE_PROMPT_CN, + }, +) + +FAST_WAN_2_2_TI2V_5B = InferencePreset( + name="fast_wan_2_2_ti2v_5b", + version=1, + model_family="wan", + description="FastWan 2.2 TI2V 5B DMD", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 704, + "width": 1280, + "num_frames": 121, + "fps": 24, + "guidance_scale": 5.0, + "num_inference_steps": 50, + "negative_prompt": _NEGATIVE_PROMPT_CN, + }, +) + +# ------------------------------------------------------------------- +# Self-Forcing (causal) presets +# ------------------------------------------------------------------- + +SF_WAN_T2V_1_3B = InferencePreset( + name="sf_wan_t2v_1_3b", + version=1, + model_family="wan", + description="Self-Forcing Wan 2.1 T2V 1.3B (causal)", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE, ), + defaults={ + "height": 480, + "width": 832, + "num_frames": 81, + "fps": 16, + "guidance_scale": 6.0, + "num_inference_steps": 50, + "negative_prompt": _NEGATIVE_PROMPT_CN, + }, +) + +SF_WAN_2_2_T2V_A14B = InferencePreset( + name="sf_wan_2_2_t2v_a14b", + version=1, + model_family="wan", + description="Self-Forcing Wan 2.2 T2V A14B (causal)", + workload_type="t2v", + stage_schemas=(_DENOISE_STAGE_WAN22, ), + defaults={ + "height": 448, + "width": 832, + "num_frames": 81, + "fps": 16, + "guidance_scale": 4.0, + "guidance_scale_2": 3.0, + "num_inference_steps": 8, + "negative_prompt": _NEGATIVE_PROMPT_CN, + }, +) + +SF_WAN_2_2_I2V_A14B = InferencePreset( + name="sf_wan_2_2_i2v_a14b", + version=1, + model_family="wan", + description="Self-Forcing Wan 2.2 I2V A14B (causal)", + workload_type="i2v", + stage_schemas=(_DENOISE_STAGE_WAN22, ), + defaults={ + "height": 448, + "width": 832, + "num_frames": 81, + "fps": 16, + "guidance_scale": 4.0, + "guidance_scale_2": 3.0, + "num_inference_steps": 8, + "negative_prompt": _NEGATIVE_PROMPT_CN, + }, +) + +# Collect all presets for bulk registration. +ALL_PRESETS = ( + WAN_T2V_1_3B, + WAN_T2V_14B, + WAN_I2V_14B_480P, + WAN_I2V_14B_720P, + WAN_2_2_T2V_A14B, + WAN_2_2_I2V_A14B, + WAN_FUN_1_3B_INP, + WAN_FUN_1_3B_CONTROL, + FAST_WAN_T2V_480P, + WAN_2_2_TI2V_5B, + FAST_WAN_2_2_TI2V_5B, + SF_WAN_T2V_1_3B, + SF_WAN_2_2_T2V_A14B, + SF_WAN_2_2_I2V_A14B, +) diff --git a/fastvideo/pipelines/preprocess/preprocess_pipeline_ode_trajectory.py b/fastvideo/pipelines/preprocess/preprocess_pipeline_ode_trajectory.py index 9c11b2818..a88ae5539 100644 --- a/fastvideo/pipelines/preprocess/preprocess_pipeline_ode_trajectory.py +++ b/fastvideo/pipelines/preprocess/preprocess_pipeline_ode_trajectory.py @@ -18,7 +18,7 @@ from torch.utils.data import DataLoader from torchdata.stateful_dataloader import StatefulDataLoader from tqdm import tqdm -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.dataset import gettextdataset from fastvideo.dataset.dataloader.parquet_io import (ParquetDatasetWriter, records_to_table) from fastvideo.dataset.dataloader.record_schema import (ode_text_only_record_creator) diff --git a/fastvideo/registry.py b/fastvideo/registry.py index d37a4d639..348746f44 100644 --- a/fastvideo/registry.py +++ b/fastvideo/registry.py @@ -48,42 +48,7 @@ from fastvideo.configs.pipelines.wan import ( WanT2V720PConfig, ) from fastvideo.configs.pipelines.sd35 import SD35Config -from fastvideo.configs.sample.base import SamplingParam -from fastvideo.configs.sample.cosmos import ( - Cosmos_Predict2_2B_Video2World_SamplingParam, ) -from fastvideo.configs.sample.cosmos2_5 import Cosmos25SamplingParamBase -from fastvideo.configs.sample.gen3c import Gen3C_Cosmos_7B_SamplingParam -from fastvideo.configs.sample.hunyuan import (FastHunyuanSamplingParam, HunyuanSamplingParam) -from fastvideo.configs.sample.hunyuan15 import (Hunyuan15_480P_SamplingParam, - Hunyuan15_480P_StepDistilled_I2V_SamplingParam, - Hunyuan15_720P_SamplingParam, - Hunyuan15_720P_Distilled_I2V_SamplingParam, - Hunyuan15_SR_1080P_SamplingParam) -from fastvideo.configs.sample.hyworld import HYWorld_SamplingParam -from fastvideo.configs.sample.hunyuangamecraft import HunyuanGameCraftSamplingParam -from fastvideo.configs.sample.lingbotworld import LingBotWorld_SamplingParam -from fastvideo.configs.sample.ltx2 import (LTX2BaseSamplingParam, LTX2DistilledSamplingParam) -from fastvideo.configs.sample.turbodiffusion import ( - TurboDiffusionI2V_A14B_SamplingParam, - TurboDiffusionT2V_14B_SamplingParam, - TurboDiffusionT2V_1_3B_SamplingParam, -) -from fastvideo.configs.sample.wan import ( - FastWanT2V480P_SamplingParam, - MatrixGame2_SamplingParam, - SelfForcingWan2_1_T2V_1_3B_480P_SamplingParam, - SelfForcingWan2_2_T2V_A14B_480P_SamplingParam, - Wan2_1_Fun_1_3B_Control_SamplingParam, - Wan2_1_Fun_1_3B_InP_SamplingParam, - Wan2_2_I2V_A14B_SamplingParam, - Wan2_2_T2V_A14B_SamplingParam, - Wan2_2_TI2V_5B_SamplingParam, - WanI2V_14B_480P_SamplingParam, - WanI2V_14B_720P_SamplingParam, - WanT2V_14B_SamplingParam, - WanT2V_1_3B_SamplingParam, -) -from fastvideo.configs.sample.sd35 import SD35SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.fastvideo_args import WorkloadType from fastvideo.logger import init_logger @@ -138,6 +103,8 @@ class ConfigInfo: sampling_param_cls: type[SamplingParam] | None pipeline_config_cls: type[PipelineConfig] workload_types: tuple[WorkloadType, ...] + model_family: str | None = None + default_preset: str | None = None # The central registry mapping a model name to its configuration information @@ -156,6 +123,8 @@ def register_configs( workload_types: tuple[WorkloadType, ...], hf_model_paths: list[str] | None = None, model_detectors: list[Callable[[str], bool]] | None = None, + model_family: str | None = None, + default_preset: str | None = None, ) -> None: """Register config classes for a model family. @@ -168,6 +137,8 @@ def register_configs( sampling_param_cls=sampling_param_cls, pipeline_config_cls=pipeline_config_cls, workload_types=workload_types, + model_family=model_family, + default_preset=default_preset, ) if hf_model_paths: @@ -242,7 +213,7 @@ def _get_config_info( def _register_configs() -> None: # LTX-2 (base) register_configs( - sampling_param_cls=LTX2BaseSamplingParam, + sampling_param_cls=None, pipeline_config_cls=LTX2T2VConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ @@ -253,10 +224,12 @@ def _register_configs() -> None: model_detectors=[ lambda path: ("ltx2" in path.lower() or "ltx-2" in path.lower()) and "distilled" not in path.lower(), ], + model_family="ltx2", + default_preset="ltx2_base", ) # LTX-2 (distilled) register_configs( - sampling_param_cls=LTX2DistilledSamplingParam, + sampling_param_cls=None, pipeline_config_cls=LTX2T2VConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ @@ -265,11 +238,13 @@ def _register_configs() -> None: model_detectors=[ lambda path: ("ltx2" in path.lower() or "ltx-2" in path.lower()) and "distilled" in path.lower(), ], + model_family="ltx2", + default_preset="ltx2_distilled", ) # Hunyuan 1.5 (specific) register_configs( - sampling_param_cls=Hunyuan15_480P_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=Hunyuan15T2V480PConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ @@ -283,41 +258,51 @@ def _register_configs() -> None: "hunyuanvideo_1.5", )), ], + model_family="hunyuan15", + default_preset="hunyuan15_t2v_480p", ) register_configs( - sampling_param_cls=Hunyuan15_480P_StepDistilled_I2V_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=Hunyuan15I2V480PStepDistilledConfig, workload_types=(WorkloadType.I2V, ), hf_model_paths=[ "hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_i2v_step_distilled", ], + model_family="hunyuan15", + default_preset="hunyuan15_i2v_480p_distilled", ) register_configs( - sampling_param_cls=Hunyuan15_720P_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=Hunyuan15T2V720PConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ "hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_t2v", ], + model_family="hunyuan15", + default_preset="hunyuan15_t2v_720p", ) register_configs( - sampling_param_cls=Hunyuan15_720P_Distilled_I2V_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=Hunyuan15I2V720PConfig, workload_types=(WorkloadType.I2V, ), hf_model_paths=[ "hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_i2v_distilled", ], + model_family="hunyuan15", + default_preset="hunyuan15_i2v_720p_distilled", ) register_configs( - sampling_param_cls=Hunyuan15_SR_1080P_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=Hunyuan15SR1080PConfig, workload_types=(), hf_model_paths=["weizhou03/HunyuanVideo-1.5-Diffusers-1080p", "weizhou03/HunyuanVideo-1.5-Diffusers-1080p-2SR"], + model_family="hunyuan15", + default_preset="hunyuan15_sr_1080p", ) # Hunyuan (excludes gamecraft, hyworld, and versioned models) register_configs( - sampling_param_cls=HunyuanSamplingParam, + sampling_param_cls=None, pipeline_config_cls=HunyuanConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ @@ -327,46 +312,56 @@ def _register_configs() -> None: lambda path: "hunyuan" in path.lower() and "gamecraft" not in path.lower() and "hyworld" not in path.lower( ) and "1.5" not in path.lower() and "1-5" not in path.lower() ], + model_family="hunyuan", + default_preset="hunyuan_t2v", ) register_configs( - sampling_param_cls=FastHunyuanSamplingParam, + sampling_param_cls=None, pipeline_config_cls=FastHunyuanConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ "FastVideo/FastHunyuan-diffusers", ], + model_family="hunyuan", + default_preset="fast_hunyuan_t2v", ) # HYWorld register_configs( - sampling_param_cls=HYWorld_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=HYWorldConfig, workload_types=(), hf_model_paths=[ "FastVideo/HY-WorldPlay-Bidirectional-Diffusers", ], model_detectors=[lambda path: "hyworld" in path.lower()], + model_family="hyworld", + default_preset="hyworld_t2v", ) # HunyuanGameCraft register_configs( - sampling_param_cls=HunyuanGameCraftSamplingParam, + sampling_param_cls=None, pipeline_config_cls=HunyuanGameCraftPipelineConfig, workload_types=(WorkloadType.I2V, ), hf_model_paths=[ "FastVideo/HunyuanGameCraft-Diffusers", ], model_detectors=[lambda path: "gamecraft" in path.lower()], + model_family="gamecraft", + default_preset="gamecraft_i2v", ) # LingBotWorld register_configs( - sampling_param_cls=LingBotWorld_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=LingBotWorldI2V480PConfig, workload_types=(WorkloadType.I2V, ), hf_model_paths=[ "FastVideo/LingBot-World-Base-Cam-Diffusers", ], model_detectors=[lambda path: ("lingbotworld" in path.lower() or "lingbot-world" in path.lower())], + model_family="lingbotworld", + default_preset="lingbotworld_i2v", ) # Kandinsky5 Lite T2V @@ -380,6 +375,7 @@ def _register_configs() -> None: model_detectors=[ lambda path: any(token in path.lower() for token in ("kandinsky5", "kandinsky-5")), ], + model_family="kandinsky5", ) # LongCat (T2V, I2V, VC use same config; workload varies by path) @@ -392,6 +388,8 @@ def _register_configs() -> None: lambda path: "longcat" in path.lower() and "i2v" not in path.lower() and "imagetovideo" not in path.lower() and "vc" not in path.lower() and "videocontinuation" not in path.lower(), ], + model_family="longcat", + default_preset="longcat_t2v", ) register_configs( sampling_param_cls=None, @@ -401,6 +399,8 @@ def _register_configs() -> None: model_detectors=[ lambda path: "longcatimagetovideo" in path.lower() or ("longcat" in path.lower() and "i2v" in path.lower()), ], + model_family="longcat", + default_preset="longcat_i2v", ) register_configs( sampling_param_cls=None, @@ -411,11 +411,13 @@ def _register_configs() -> None: lambda path: "longcatvideocontinuation" in path.lower() or ("longcat" in path.lower() and "vc" in path.lower()), ], + model_family="longcat", + default_preset="longcat_vc", ) # MatrixGame register_configs( - sampling_param_cls=MatrixGame2_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=MatrixGameI2V480PConfig, workload_types=(WorkloadType.I2V, ), hf_model_paths=[ @@ -426,11 +428,13 @@ def _register_configs() -> None: model_detectors=[ lambda path: "matrix-game" in path.lower() or "matrixgame" in path.lower(), ], + model_family="matrixgame", + default_preset="matrixgame_i2v", ) # GEN3C (must register before generic Cosmos detector) register_configs( - sampling_param_cls=Gen3C_Cosmos_7B_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=Gen3CConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ @@ -439,11 +443,13 @@ def _register_configs() -> None: model_detectors=[ lambda path: "gen3c" in path.lower(), ], + model_family="gen3c", + default_preset="gen3c_cosmos_7b", ) # Cosmos 2.5 register_configs( - sampling_param_cls=Cosmos25SamplingParamBase, + sampling_param_cls=None, pipeline_config_cls=Cosmos25Config, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ @@ -456,11 +462,13 @@ def _register_configs() -> None: "cosmos2.5", )), ], + model_family="cosmos25", + default_preset="cosmos25_predict2_2b", ) # Cosmos 2 register_configs( - sampling_param_cls=Cosmos_Predict2_2B_Video2World_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=CosmosConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ @@ -470,89 +478,109 @@ def _register_configs() -> None: lambda path: "cosmos" in path.lower() and ("2.5" not in path.lower() and "2_5" not in path.lower() and "25" not in path.lower() and "gen3c" not in path.lower()), ], + model_family="cosmos", + default_preset="cosmos_predict2_2b", ) # TurboDiffusion register_configs( - sampling_param_cls=TurboDiffusionT2V_1_3B_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=TurboDiffusionT2V_1_3B_Config, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ "loayrashid/TurboWan2.1-T2V-1.3B-Diffusers", ], model_detectors=[lambda path: "turbodiffusion" in path.lower() or "turbowan" in path.lower()], + model_family="turbodiffusion", + default_preset="turbo_t2v_1_3b", ) register_configs( - sampling_param_cls=TurboDiffusionT2V_14B_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=TurboDiffusionT2V_14B_Config, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ "loayrashid/TurboWan2.1-T2V-14B-Diffusers", ], + model_family="turbodiffusion", + default_preset="turbo_t2v_14b", ) register_configs( - sampling_param_cls=TurboDiffusionI2V_A14B_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=TurboDiffusionI2V_A14B_Config, workload_types=(WorkloadType.I2V, ), hf_model_paths=[ "loayrashid/TurboWan2.2-I2V-A14B-Diffusers", ], + model_family="turbodiffusion", + default_preset="turbo_i2v_a14b", ) - # Wan + # Wan — defaults provided by presets (no sampling_param_cls needed) register_configs( - sampling_param_cls=WanT2V_1_3B_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=WanT2V480PConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", ], model_detectors=[lambda path: "wanpipeline" in path.lower()], + model_family="wan", + default_preset="wan_t2v_1_3b", ) register_configs( - sampling_param_cls=WanT2V_14B_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=WanT2V720PConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ "Wan-AI/Wan2.1-T2V-14B-Diffusers", "FastVideo/Wan2.1-VSA-T2V-14B-720P-Diffusers", ], + model_family="wan", + default_preset="wan_t2v_14b", ) register_configs( - sampling_param_cls=WanI2V_14B_480P_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=WanI2V480PConfig, workload_types=(WorkloadType.I2V, ), hf_model_paths=[ "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers", ], model_detectors=[lambda path: "wanimagetovideo" in path.lower()], + model_family="wan", + default_preset="wan_i2v_14b_480p", ) register_configs( - sampling_param_cls=WanI2V_14B_720P_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=WanI2V720PConfig, workload_types=(WorkloadType.I2V, ), hf_model_paths=[ "Wan-AI/Wan2.1-I2V-14B-720P-Diffusers", ], + model_family="wan", + default_preset="wan_i2v_14b_720p", ) register_configs( - sampling_param_cls=Wan2_1_Fun_1_3B_InP_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=WanI2V480PConfig, workload_types=(WorkloadType.I2V, ), hf_model_paths=[ "weizhou03/Wan2.1-Fun-1.3B-InP-Diffusers", ], + model_family="wan", + default_preset="wan_fun_1_3b_inp", ) register_configs( - sampling_param_cls=Wan2_1_Fun_1_3B_Control_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=WANV2VConfig, workload_types=(), hf_model_paths=[ "IRMChen/Wan2.1-Fun-1.3B-Control-Diffusers", ], + model_family="wan", + default_preset="wan_fun_1_3b_control", ) register_configs( - sampling_param_cls=FastWanT2V480P_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=FastWan2_1_T2V_480P_Config, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ @@ -560,72 +588,88 @@ def _register_configs() -> None: "FastVideo/FastWan2.1-T2V-14B-480P-Diffusers", ], model_detectors=[lambda path: "wandmdpipeline" in path.lower()], + model_family="wan", + default_preset="fast_wan_t2v_480p", ) register_configs( - sampling_param_cls=Wan2_2_TI2V_5B_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=Wan2_2_TI2V_5B_Config, workload_types=(WorkloadType.T2V, WorkloadType.I2V), hf_model_paths=[ "Wan-AI/Wan2.2-TI2V-5B-Diffusers", ], + model_family="wan", + default_preset="wan_2_2_ti2v_5b", ) register_configs( - sampling_param_cls=Wan2_2_TI2V_5B_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=FastWan2_2_TI2V_5B_Config, workload_types=(WorkloadType.T2V, WorkloadType.I2V), hf_model_paths=[ "FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers", "FastVideo/FastWan2.2-TI2V-5B-Diffusers", ], + model_family="wan", + default_preset="fast_wan_2_2_ti2v_5b", ) register_configs( - sampling_param_cls=Wan2_2_T2V_A14B_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=Wan2_2_T2V_A14B_Config, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ "Wan-AI/Wan2.2-T2V-A14B-Diffusers", ], + model_family="wan", + default_preset="wan_2_2_t2v_a14b", ) register_configs( - sampling_param_cls=Wan2_2_I2V_A14B_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=Wan2_2_I2V_A14B_Config, workload_types=(WorkloadType.I2V, ), hf_model_paths=[ "Wan-AI/Wan2.2-I2V-A14B-Diffusers", ], + model_family="wan", + default_preset="wan_2_2_i2v_a14b", ) register_configs( - sampling_param_cls=SelfForcingWan2_1_T2V_1_3B_480P_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=SelfForcingWanT2V480PConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=[ "wlsaidhi/SFWan2.1-T2V-1.3B-Diffusers", ], model_detectors=[lambda path: "wancausaldmdpipeline" in path.lower()], + model_family="wan", + default_preset="sf_wan_t2v_1_3b", ) # SFWan2.2: T2V and I2V variants by path register_configs( - sampling_param_cls=SelfForcingWan2_2_T2V_A14B_480P_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=SelfForcingWan2_2_T2V480PConfig, workload_types=(WorkloadType.T2V, ), hf_model_paths=["rand0nmr/SFWan2.2-T2V-A14B-Diffusers"], model_detectors=[ lambda path: ("sfwan2.2" in path.lower() or "sfwan2_2" in path.lower()) and "i2v" not in path.lower(), ], + model_family="wan", + default_preset="sf_wan_2_2_t2v_a14b", ) register_configs( - sampling_param_cls=SelfForcingWan2_2_T2V_A14B_480P_SamplingParam, + sampling_param_cls=None, pipeline_config_cls=SelfForcingWan2_2_T2V480PConfig, workload_types=(WorkloadType.I2V, ), hf_model_paths=["FastVideo/SFWan2.2-I2V-A14B-Preview-Diffusers"], model_detectors=[ lambda path: ("sfwan2.2" in path.lower() or "sfwan2_2" in path.lower()) and "i2v" in path.lower(), ], + model_family="wan", + default_preset="sf_wan_2_2_i2v_a14b", ) # SD3.5 register_configs( - sampling_param_cls=SD35SamplingParam, + sampling_param_cls=None, pipeline_config_cls=SD35Config, workload_types=(WorkloadType.T2I, ), hf_model_paths=[ @@ -638,6 +682,8 @@ def _register_configs() -> None: "stabilityai__stable-diffusion-3.5-medium", )), ], + model_family="sd35", + default_preset="sd35_medium", ) @@ -716,6 +762,87 @@ def get_sampling_param_cls_for_name(pipeline_name_or_path: str) -> Any | None: _register_configs() +def _register_presets() -> None: + from fastvideo.api.presets import register_preset + from fastvideo.pipelines.basic.cosmos.presets import ( + ALL_PRESETS as COSMOS_PRESETS, ) + from fastvideo.pipelines.basic.gamecraft.presets import ( + ALL_PRESETS as GAMECRAFT_PRESETS, ) + from fastvideo.pipelines.basic.gen3c.presets import ( + ALL_PRESETS as GEN3C_PRESETS, ) + from fastvideo.pipelines.basic.hunyuan.presets import ( + ALL_PRESETS as HUNYUAN_PRESETS, ) + from fastvideo.pipelines.basic.hunyuan15.presets import ( + ALL_PRESETS as HUNYUAN15_PRESETS, ) + from fastvideo.pipelines.basic.hyworld.presets import ( + ALL_PRESETS as HYWORLD_PRESETS, ) + from fastvideo.pipelines.basic.lingbotworld.presets import ( + ALL_PRESETS as LINGBOTWORLD_PRESETS, ) + from fastvideo.pipelines.basic.longcat.presets import ( + ALL_PRESETS as LONGCAT_PRESETS, ) + from fastvideo.pipelines.basic.ltx2.presets import ( + ALL_PRESETS as LTX2_PRESETS, ) + from fastvideo.pipelines.basic.matrixgame.presets import ( + ALL_PRESETS as MATRIXGAME_PRESETS, ) + from fastvideo.pipelines.basic.sd35.presets import ( + ALL_PRESETS as SD35_PRESETS, ) + from fastvideo.pipelines.basic.turbodiffusion.presets import ( + ALL_PRESETS as TURBODIFFUSION_PRESETS, ) + from fastvideo.pipelines.basic.wan.presets import ( + ALL_PRESETS as WAN_PRESETS, ) + + all_preset_groups = ( + COSMOS_PRESETS, + GAMECRAFT_PRESETS, + GEN3C_PRESETS, + HUNYUAN_PRESETS, + HUNYUAN15_PRESETS, + HYWORLD_PRESETS, + LINGBOTWORLD_PRESETS, + LONGCAT_PRESETS, + LTX2_PRESETS, + MATRIXGAME_PRESETS, + SD35_PRESETS, + TURBODIFFUSION_PRESETS, + WAN_PRESETS, + ) + for group in all_preset_groups: + for preset in group: + register_preset(preset) + + +_register_presets() + + +def get_model_family(model_path: str) -> str | None: + """Return the ``model_family`` string for a model path, or ``None``.""" + config_info = _get_config_info(model_path, raise_on_missing=False) + if config_info is None: + return None + return config_info.model_family + + +def get_default_preset(model_path: str) -> str | None: + """Return the ``default_preset`` name for a model path.""" + config_info = _get_config_info(model_path, raise_on_missing=False) + if config_info is None: + return None + return config_info.default_preset + + +def get_preset_selection(model_path: str) -> tuple[str | None, str | None]: + """Return ``(default_preset, model_family)`` for a model path. + + Single-lookup variant of :func:`get_default_preset` + + :func:`get_model_family`; callers that need both should prefer this + to avoid walking the registry twice. + """ + config_info = _get_config_info(model_path, raise_on_missing=False) + if config_info is None: + return None, None + return config_info.default_preset, config_info.model_family + + def get_registered_model_paths() -> list[str]: """Return all registered HuggingFace model paths. @@ -755,6 +882,8 @@ def get_registered_models_with_workloads(workload_type: str | None = None, ) -> __all__ = [ "ConfigInfo", "ModelInfo", + "get_default_preset", + "get_model_family", "get_model_info", "get_pipeline_config_cls_from_name", "get_registered_model_paths", diff --git a/fastvideo/tests/api/test_cli_translation.py b/fastvideo/tests/api/test_cli_translation.py index 1aec79097..3ae996554 100644 --- a/fastvideo/tests/api/test_cli_translation.py +++ b/fastvideo/tests/api/test_cli_translation.py @@ -12,7 +12,7 @@ from fastvideo.entrypoints.cli.inference_config import ( build_generate_run_config, build_serve_config, ) -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.entrypoints.cli.serve import ServeSubcommand from fastvideo.entrypoints.openai import api_server from fastvideo.entrypoints.video_generator import VideoGenerator diff --git a/fastvideo/tests/api/test_parser.py b/fastvideo/tests/api/test_parser.py index 241a0141f..9784cfe33 100644 --- a/fastvideo/tests/api/test_parser.py +++ b/fastvideo/tests/api/test_parser.py @@ -23,7 +23,7 @@ def test_parse_config_builds_nested_typed_config() -> None: "model_path": "/models/ltx2", "pipeline": { "workload_type": "t2v", - "profile": "ltx2_two_stage", + "preset": "ltx2_two_stage", }, }, "request": { @@ -51,7 +51,7 @@ def test_parse_config_builds_nested_typed_config() -> None: config = parse_config(RunConfig, raw) - assert config.generator.pipeline.profile == "ltx2_two_stage" + assert config.generator.pipeline.preset == "ltx2_two_stage" assert config.request.prompt == ["a fox", "a wolf"] assert config.request.state == ContinuationState( kind="ltx2_continuation", @@ -114,8 +114,8 @@ def test_load_run_config_supports_yaml_roundtrip(tmp_path) -> None: }, "pipeline": { "workload_type": None, - "profile": None, - "profile_version": None, + "preset": None, + "preset_version": None, "components": { "config_root": None, "pipeline_config_path": None, @@ -128,7 +128,7 @@ def test_load_run_config_supports_yaml_roundtrip(tmp_path) -> None: "override_pipeline_cls_name": None, "override_transformer_cls_name": None, }, - "profile_overrides": {}, + "preset_overrides": {}, "experimental": {}, }, }, diff --git a/fastvideo/tests/api/test_presets.py b/fastvideo/tests/api/test_presets.py new file mode 100644 index 000000000..cfe76063c --- /dev/null +++ b/fastvideo/tests/api/test_presets.py @@ -0,0 +1,550 @@ +# SPDX-License-Identifier: Apache-2.0 +import pytest + +from fastvideo.api.errors import ConfigValidationError +from fastvideo.api.presets import ( + InferencePreset, + PresetStageSpec, + get_all_preset_names, + get_preset, + get_presets_for_family, + register_preset, + validate_preset_selection, + validate_stage_names, + validate_stage_overrides, +) + + +# ------------------------------------------------------------------- +# Fixtures +# ------------------------------------------------------------------- + +@pytest.fixture() +def _isolated_registry(): + """Run each test with an empty preset registry, restoring after.""" + from fastvideo.api.presets import _PRESET_REGISTRY + saved = dict(_PRESET_REGISTRY) + _PRESET_REGISTRY.clear() + yield + _PRESET_REGISTRY.clear() + _PRESET_REGISTRY.update(saved) + + +_SIMPLE_STAGE = PresetStageSpec( + name="denoise", + kind="denoising", + allowed_overrides=frozenset({"num_inference_steps", "guidance_scale"}), +) + +_SR_STAGE = PresetStageSpec( + name="sr", + kind="super_resolution", + allowed_overrides=frozenset({"height_sr", "width_sr"}), +) + +_NO_OVERRIDES_STAGE = PresetStageSpec( + name="encode", + kind="text_encoding", +) + + +def _make_preset( + name: str = "test_preset", + version: int = 1, + model_family: str = "test", + stage_schemas: tuple[PresetStageSpec, ...] = (_SIMPLE_STAGE, ), + **kwargs, +) -> InferencePreset: + return InferencePreset( + name=name, + version=version, + model_family=model_family, + stage_schemas=stage_schemas, + **kwargs, + ) + + +# ------------------------------------------------------------------- +# Registration and lookup +# ------------------------------------------------------------------- + + +class TestRegistration: + + @pytest.mark.usefixtures("_isolated_registry") + def test_register_and_get(self) -> None: + p = _make_preset() + register_preset(p) + assert get_preset("test_preset", "test") is p + + @pytest.mark.usefixtures("_isolated_registry") + def test_get_with_explicit_version(self) -> None: + p = _make_preset(version=2) + register_preset(p) + assert get_preset("test_preset", "test", version=2) is p + + @pytest.mark.usefixtures("_isolated_registry") + def test_get_latest_version(self) -> None: + p1 = _make_preset(version=1) + p2 = _make_preset(version=2) + register_preset(p1) + register_preset(p2) + assert get_preset("test_preset", "test") is p2 + + @pytest.mark.usefixtures("_isolated_registry") + def test_get_missing_raises(self) -> None: + with pytest.raises(ConfigValidationError, match="unknown preset"): + get_preset("nope", "test") + + @pytest.mark.usefixtures("_isolated_registry") + def test_get_wrong_version_raises(self) -> None: + register_preset(_make_preset(version=1)) + with pytest.raises(ConfigValidationError, match="version"): + get_preset("test_preset", "test", version=99) + + @pytest.mark.usefixtures("_isolated_registry") + def test_duplicate_raises(self) -> None: + register_preset(_make_preset()) + with pytest.raises(ValueError, match="Duplicate"): + register_preset(_make_preset()) + + @pytest.mark.usefixtures("_isolated_registry") + def test_get_presets_for_family(self) -> None: + register_preset(_make_preset(name="a")) + register_preset(_make_preset(name="b")) + register_preset(_make_preset( + name="c", model_family="other")) + result = get_presets_for_family("test") + assert {p.name for p in result} == {"a", "b"} + + @pytest.mark.usefixtures("_isolated_registry") + def test_get_all_preset_names(self) -> None: + register_preset(_make_preset(name="beta")) + register_preset(_make_preset(name="alpha")) + assert get_all_preset_names() == ["alpha", "beta"] + + +# ------------------------------------------------------------------- +# Stage-name validation +# ------------------------------------------------------------------- + + +class TestStageNameValidation: + + def test_valid_stage_name_passes(self) -> None: + preset = _make_preset(stage_schemas=(_SIMPLE_STAGE, _SR_STAGE)) + validate_stage_names( + preset, {"denoise": {}, "sr": {}}) + + def test_unknown_stage_name_raises(self) -> None: + preset = _make_preset(stage_schemas=(_SIMPLE_STAGE, )) + with pytest.raises( + ConfigValidationError, match="stage_overrides.bogus" + ): + validate_stage_names(preset, {"bogus": {}}) + + def test_empty_overrides_passes(self) -> None: + preset = _make_preset(stage_schemas=(_SIMPLE_STAGE, )) + validate_stage_names(preset, {}) + + def test_error_lists_valid_stages(self) -> None: + preset = _make_preset( + stage_schemas=(_SIMPLE_STAGE, _SR_STAGE)) + with pytest.raises( + ConfigValidationError, match="'denoise'" + ): + validate_stage_names(preset, {"nope": {}}) + + +# ------------------------------------------------------------------- +# Stage-override validation +# ------------------------------------------------------------------- + + +class TestStageOverrideValidation: + + def test_allowed_override_passes(self) -> None: + preset = _make_preset(stage_schemas=(_SIMPLE_STAGE, )) + validate_stage_overrides( + preset, + {"denoise": {"num_inference_steps": 25}}, + ) + + def test_disallowed_override_raises(self) -> None: + preset = _make_preset(stage_schemas=(_SIMPLE_STAGE, )) + with pytest.raises( + ConfigValidationError, + match="stage_overrides.denoise.height", + ): + validate_stage_overrides( + preset, + {"denoise": {"height": 720}}, + ) + + def test_override_on_stage_with_no_allowed_raises(self) -> None: + preset = _make_preset(stage_schemas=(_NO_OVERRIDES_STAGE, )) + with pytest.raises( + ConfigValidationError, + match="does not accept overrides", + ): + validate_stage_overrides( + preset, + {"encode": {"some_key": 1}}, + ) + + def test_empty_override_on_no_allowed_passes(self) -> None: + preset = _make_preset(stage_schemas=(_NO_OVERRIDES_STAGE, )) + validate_stage_overrides(preset, {"encode": {}}) + + def test_non_mapping_override_raises(self) -> None: + preset = _make_preset(stage_schemas=(_SIMPLE_STAGE, )) + with pytest.raises(ConfigValidationError, match="mapping"): + validate_stage_overrides( + preset, {"denoise": "not a dict"}) + + def test_unknown_stage_still_caught(self) -> None: + preset = _make_preset(stage_schemas=(_SIMPLE_STAGE, )) + with pytest.raises(ConfigValidationError, match="unknown"): + validate_stage_overrides( + preset, {"missing_stage": {"a": 1}}) + + def test_error_lists_allowed_overrides(self) -> None: + preset = _make_preset(stage_schemas=(_SIMPLE_STAGE, )) + with pytest.raises( + ConfigValidationError, match="guidance_scale" + ): + validate_stage_overrides( + preset, + {"denoise": {"bad_key": 1}}, + ) + + +# ------------------------------------------------------------------- +# validate_preset_selection end-to-end +# ------------------------------------------------------------------- + + +class TestValidatePresetSelection: + + @pytest.mark.usefixtures("_isolated_registry") + def test_none_preset_returns_none(self) -> None: + assert validate_preset_selection( + None, "test") is None + + @pytest.mark.usefixtures("_isolated_registry") + def test_valid_preset_resolves(self) -> None: + p = _make_preset() + register_preset(p) + result = validate_preset_selection( + "test_preset", "test") + assert result is p + + @pytest.mark.usefixtures("_isolated_registry") + def test_valid_preset_with_overrides(self) -> None: + p = _make_preset() + register_preset(p) + result = validate_preset_selection( + "test_preset", + "test", + stage_overrides={"denoise": {"guidance_scale": 2.0}}, + ) + assert result is p + + @pytest.mark.usefixtures("_isolated_registry") + def test_invalid_preset_raises(self) -> None: + with pytest.raises(ConfigValidationError, match="unknown"): + validate_preset_selection("nope", "test") + + @pytest.mark.usefixtures("_isolated_registry") + def test_bad_stage_override_raises(self) -> None: + register_preset(_make_preset()) + with pytest.raises(ConfigValidationError): + validate_preset_selection( + "test_preset", + "test", + stage_overrides={"denoise": {"bad": 1}}, + ) + + +# ------------------------------------------------------------------- +# Wan preset integration (uses real registry) +# ------------------------------------------------------------------- + + +class TestWanPresets: + """Verify the Wan presets registered from registry.py.""" + + def test_wan_presets_are_registered(self) -> None: + # Force registration by importing registry. + import fastvideo.registry # noqa: F401 + presets = get_presets_for_family("wan") + names = {p.name for p in presets} + assert "wan_t2v_1_3b" in names + assert "wan_t2v_14b" in names + assert "wan_i2v_14b_480p" in names + assert "wan_2_2_t2v_a14b" in names + + def test_wan_t2v_1_3b_lookup(self) -> None: + import fastvideo.registry # noqa: F401 + preset = get_preset("wan_t2v_1_3b", "wan") + assert preset.model_family == "wan" + assert preset.workload_type == "t2v" + assert len(preset.stage_schemas) == 1 + assert preset.stage_schemas[0].name == "denoise" + assert preset.defaults["height"] == 480 + assert preset.defaults["width"] == 832 + + def test_wan_2_2_allows_dual_guidance(self) -> None: + import fastvideo.registry # noqa: F401 + preset = get_preset("wan_2_2_t2v_a14b", "wan") + stage = preset.stage_schemas[0] + assert "guidance_scale_2" in stage.allowed_overrides + assert "boundary_ratio" in stage.allowed_overrides + + def test_wan_stage_override_validation(self) -> None: + import fastvideo.registry # noqa: F401 + preset = get_preset("wan_t2v_14b", "wan") + # Valid override. + validate_stage_overrides( + preset, + {"denoise": {"num_inference_steps": 25}}, + ) + # Invalid override key. + with pytest.raises(ConfigValidationError): + validate_stage_overrides( + preset, + {"denoise": {"height": 1080}}, + ) + + def test_wan_model_family_in_registry(self) -> None: + from fastvideo.registry import get_model_family + family = get_model_family( + "Wan-AI/Wan2.1-T2V-1.3B-Diffusers") + assert family == "wan" + + +# ------------------------------------------------------------------- +# LTX2 preset integration +# ------------------------------------------------------------------- + + +class TestLtx2Presets: + + def test_ltx2_presets_registered(self) -> None: + import fastvideo.registry # noqa: F401 + presets = get_presets_for_family("ltx2") + names = {p.name for p in presets} + assert names == {"ltx2_base", "ltx2_distilled"} + + def test_ltx2_base_lookup(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("ltx2_base", "ltx2") + assert p.workload_type == "t2v" + assert p.defaults["height"] == 512 + assert p.defaults["width"] == 768 + + def test_ltx2_distilled_fewer_steps(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("ltx2_distilled", "ltx2") + assert p.defaults["num_inference_steps"] == 8 + assert p.defaults["guidance_scale"] == 1.0 + + +# ------------------------------------------------------------------- +# Hunyuan preset integration +# ------------------------------------------------------------------- + + +class TestHunyuanPresets: + + def test_hunyuan_presets_registered(self) -> None: + import fastvideo.registry # noqa: F401 + presets = get_presets_for_family("hunyuan") + names = {p.name for p in presets} + assert names == {"hunyuan_t2v", "fast_hunyuan_t2v"} + + def test_fast_hunyuan_fewer_steps(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("fast_hunyuan_t2v", "hunyuan") + assert p.defaults["num_inference_steps"] == 6 + + +# ------------------------------------------------------------------- +# Hunyuan15 preset integration (includes two-stage SR) +# ------------------------------------------------------------------- + + +class TestHunyuan15Presets: + + def test_hunyuan15_presets_registered(self) -> None: + import fastvideo.registry # noqa: F401 + presets = get_presets_for_family("hunyuan15") + assert len(presets) == 5 + + def test_hunyuan15_sr_is_two_stage(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("hunyuan15_sr_1080p", "hunyuan15") + assert len(p.stage_schemas) == 2 + assert p.stage_schemas[0].name == "denoise" + assert p.stage_schemas[1].name == "sr" + assert p.stage_schemas[1].kind == "super_resolution" + + def test_hunyuan15_sr_stage_defaults(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("hunyuan15_sr_1080p", "hunyuan15") + sr = p.stage_defaults["sr"] + assert sr["height_sr"] == 1072 + assert sr["width_sr"] == 1920 + + def test_hunyuan15_sr_stage_override_validation(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("hunyuan15_sr_1080p", "hunyuan15") + # Valid: override sr num_inference_steps. + validate_stage_overrides( + p, {"sr": {"num_inference_steps": 12}}) + # Invalid: height not in sr allowed_overrides. + with pytest.raises(ConfigValidationError): + validate_stage_overrides( + p, {"sr": {"height": 1080}}) + + +# ------------------------------------------------------------------- +# Cosmos / Cosmos25 preset integration +# ------------------------------------------------------------------- + + +class TestCosmosPresets: + + def test_cosmos_preset_registered(self) -> None: + import fastvideo.registry # noqa: F401 + presets = get_presets_for_family("cosmos") + assert len(presets) == 1 + assert presets[0].name == "cosmos_predict2_2b" + + def test_cosmos25_separate_family(self) -> None: + import fastvideo.registry # noqa: F401 + presets = get_presets_for_family("cosmos25") + assert len(presets) == 1 + assert presets[0].name == "cosmos25_predict2_2b" + + def test_cosmos_and_cosmos25_different_fps(self) -> None: + import fastvideo.registry # noqa: F401 + c = get_preset("cosmos_predict2_2b", "cosmos") + c25 = get_preset("cosmos25_predict2_2b", "cosmos25") + assert c.defaults["fps"] == 16 + assert c25.defaults["fps"] == 24 + + +# ------------------------------------------------------------------- +# TurboDiffusion preset integration +# ------------------------------------------------------------------- + + +class TestTurboDiffusionPresets: + + def test_turbo_presets_registered(self) -> None: + import fastvideo.registry # noqa: F401 + presets = get_presets_for_family("turbodiffusion") + names = {p.name for p in presets} + assert names == { + "turbo_t2v_1_3b", + "turbo_t2v_14b", + "turbo_i2v_a14b", + } + + def test_turbo_4_step(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("turbo_t2v_14b", "turbodiffusion") + assert p.defaults["num_inference_steps"] == 4 + assert p.defaults["guidance_scale"] == 1.0 + + +# ------------------------------------------------------------------- +# SD35 preset integration +# ------------------------------------------------------------------- + + +class TestSD35Presets: + + def test_sd35_preset_registered(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("sd35_medium", "sd35") + assert p.workload_type == "t2i" + assert p.defaults["height"] == 512 + assert p.defaults["num_frames"] == 1 + + +# ------------------------------------------------------------------- +# LingBotWorld preset integration (dual guidance) +# ------------------------------------------------------------------- + + +class TestLingBotWorldPresets: + + def test_lingbotworld_dual_guidance(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("lingbotworld_i2v", "lingbotworld") + stage = p.stage_schemas[0] + assert "guidance_scale_2" in stage.allowed_overrides + assert "boundary_ratio" in stage.allowed_overrides + + def test_lingbotworld_override_validation(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("lingbotworld_i2v", "lingbotworld") + validate_stage_overrides( + p, {"denoise": {"boundary_ratio": 0.95}}) + with pytest.raises(ConfigValidationError): + validate_stage_overrides( + p, {"denoise": {"height": 720}}) + + +# ------------------------------------------------------------------- +# Remaining single-preset families +# ------------------------------------------------------------------- + + +class TestSinglePresetFamilies: + + def test_hyworld_registered(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("hyworld_t2v", "hyworld") + assert p.workload_type == "t2v" + + def test_gamecraft_registered(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("gamecraft_i2v", "gamecraft") + assert p.workload_type == "i2v" + assert p.defaults["num_frames"] == 33 + + def test_gen3c_registered(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("gen3c_cosmos_7b", "gen3c") + assert p.defaults["num_inference_steps"] == 35 + + def test_matrixgame_registered(self) -> None: + import fastvideo.registry # noqa: F401 + p = get_preset("matrixgame_i2v", "matrixgame") + assert p.defaults["num_inference_steps"] == 3 + assert p.defaults["fps"] == 25 + + def test_longcat_presets_registered(self) -> None: + import fastvideo.registry # noqa: F401 + presets = get_presets_for_family("longcat") + names = {p.name for p in presets} + assert names == { + "longcat_t2v", "longcat_i2v", "longcat_vc" + } + + +# ------------------------------------------------------------------- +# Cross-family: total preset count +# ------------------------------------------------------------------- + + +class TestPresetCountIntegrity: + + def test_total_preset_count(self) -> None: + """At least the baseline 37 presets from 13 families are registered.""" + import fastvideo.registry # noqa: F401 + names = get_all_preset_names() + assert len(names) >= 37 diff --git a/fastvideo/tests/api/test_schema_parity_inventory.py b/fastvideo/tests/api/test_schema_parity_inventory.py index a0c161088..6bda66ad3 100644 --- a/fastvideo/tests/api/test_schema_parity_inventory.py +++ b/fastvideo/tests/api/test_schema_parity_inventory.py @@ -12,7 +12,7 @@ import yaml from fastvideo.api import RunConfig, ServeConfig from fastvideo.configs.pipelines.base import PipelineConfig -from fastvideo.configs.sample.base import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.entrypoints.cli.generate import GenerateSubcommand from fastvideo.entrypoints.cli.serve import ServeSubcommand from fastvideo.entrypoints.openai import image_api, video_api @@ -50,6 +50,8 @@ def _get_extra_dataclass_fields(package_name: str, base_cls: type) -> set[str]: package = importlib.import_module(package_name) base_fields = {f.name for f in dataclasses.fields(base_cls)} extras: set[str] = set() + if not hasattr(package, "__path__"): + return extras for _, modname, _ in pkgutil.iter_modules(package.__path__): if modname == "__pycache__": continue @@ -195,7 +197,7 @@ def test_sampling_param_base_fields_are_classified() -> None: def test_sampling_param_extension_fields_are_classified() -> None: inventory = _load_inventory() - expected = _get_extra_dataclass_fields("fastvideo.configs.sample", SamplingParam) + expected = _get_extra_dataclass_fields("fastvideo.api.sampling_param", SamplingParam) actual = _flatten_status_section( inventory["surfaces"]["sampling_param_extensions"], set(inventory["status_definitions"]), @@ -235,8 +237,8 @@ def test_cli_dest_inventory_matches_live_parsers() -> None: def test_review_gap_fields_are_explicitly_inventory_tracked() -> None: inventory = _load_inventory() - sampling_extensions = inventory["surfaces"]["sampling_param_extensions"] - assert "guidance_scale_2" in sampling_extensions["moved"] + sampling_base = inventory["surfaces"]["sampling_param_base"] + assert "guidance_scale_2" in sampling_base["moved"] image_request = inventory["surfaces"]["openai_image_request"] video_request = inventory["surfaces"]["openai_video_request"] @@ -247,7 +249,7 @@ def test_review_gap_fields_are_explicitly_inventory_tracked() -> None: def test_inventory_targets_exist_in_typed_schema() -> None: inventory = _load_inventory() - target_statuses = {"moved", "profile_owned"} + target_statuses = {"moved", "preset_owned"} for surface in inventory["surfaces"].values(): for status, entries in surface.items(): diff --git a/fastvideo/tests/entrypoints/test_video_generator.py b/fastvideo/tests/entrypoints/test_video_generator.py index 5f4fcf3c5..70bf16613 100644 --- a/fastvideo/tests/entrypoints/test_video_generator.py +++ b/fastvideo/tests/entrypoints/test_video_generator.py @@ -12,7 +12,7 @@ from fastvideo.api import ( SamplingConfig, load_run_config, ) -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.entrypoints.video_generator import VideoGenerator from fastvideo.fastvideo_args import WorkloadType diff --git a/fastvideo/tests/ssim/test_causal_similarity.py b/fastvideo/tests/ssim/test_causal_similarity.py index 18b104854..ed04bf3fd 100644 --- a/fastvideo/tests/ssim/test_causal_similarity.py +++ b/fastvideo/tests/ssim/test_causal_similarity.py @@ -3,9 +3,7 @@ import os import pytest -from fastvideo.configs.sample.wan import ( - SelfForcingWan2_1_T2V_1_3B_480P_SamplingParam, -) +from fastvideo.api.sampling_param import SamplingParam from fastvideo.logger import init_logger from fastvideo.tests.ssim.inference_similarity_utils import ( resolve_inference_device_reference_folder, @@ -30,7 +28,8 @@ SF_WAN_T2V_PARAMS = { "tp_size": 1, } -_SF_WAN_T2V_FULL_QUALITY_DEFAULTS = SelfForcingWan2_1_T2V_1_3B_480P_SamplingParam() +_SF_WAN_T2V_FULL_QUALITY_DEFAULTS = SamplingParam.from_pretrained( + "wlsaidhi/SFWan2.1-T2V-1.3B-Diffusers") SF_WAN_T2V_FULL_QUALITY_PARAMS = { "num_gpus": SF_WAN_T2V_PARAMS["num_gpus"], "model_path": SF_WAN_T2V_PARAMS["model_path"], diff --git a/fastvideo/tests/ssim/test_gamecraft_similarity.py b/fastvideo/tests/ssim/test_gamecraft_similarity.py index 4eabed58d..dabb9978e 100644 --- a/fastvideo/tests/ssim/test_gamecraft_similarity.py +++ b/fastvideo/tests/ssim/test_gamecraft_similarity.py @@ -18,9 +18,7 @@ import torch import pytest from fastvideo import VideoGenerator -from fastvideo.configs.sample.hunyuangamecraft import ( - HunyuanGameCraftSamplingParam, -) +from fastvideo.api.sampling_param import SamplingParam from fastvideo.logger import init_logger from fastvideo.tests.ssim.reference_utils import ( build_generated_output_dir, @@ -91,7 +89,8 @@ GAMECRAFT_T2V_PARAMS = { "negative_prompt": "", } -_GAMECRAFT_FULL_QUALITY_DEFAULTS = HunyuanGameCraftSamplingParam() +_GAMECRAFT_FULL_QUALITY_DEFAULTS = SamplingParam.from_pretrained( + _GAMECRAFT_MODEL_PATH) GAMECRAFT_T2V_FULL_QUALITY_PARAMS = { "num_gpus": GAMECRAFT_T2V_PARAMS["num_gpus"], "model_path": GAMECRAFT_T2V_PARAMS["model_path"], diff --git a/fastvideo/tests/ssim/test_lingbot_similarity.py b/fastvideo/tests/ssim/test_lingbot_similarity.py index 20fc6140e..af56c388c 100644 --- a/fastvideo/tests/ssim/test_lingbot_similarity.py +++ b/fastvideo/tests/ssim/test_lingbot_similarity.py @@ -14,7 +14,7 @@ import pytest import torch from fastvideo import VideoGenerator -from fastvideo.configs.sample.lingbotworld import LingBotWorld_SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.logger import init_logger from fastvideo.models.dits.lingbotworld.cam_utils import prepare_camera_embedding from fastvideo.tests.ssim.reference_utils import ( @@ -90,7 +90,8 @@ LINGBOT_PARAMS = { "皮肤,肢体,面部特征,汽车,电线" ), } -_LINGBOT_FULL_QUALITY_DEFAULTS = LingBotWorld_SamplingParam() +_LINGBOT_FULL_QUALITY_DEFAULTS = SamplingParam.from_pretrained( + LINGBOT_PARAMS["model_path"]) LINGBOT_FULL_QUALITY_PARAMS = { "model_path": LINGBOT_PARAMS["model_path"], "num_gpus": LINGBOT_PARAMS["num_gpus"], diff --git a/fastvideo/tests/ssim/test_longcat_similarity.py b/fastvideo/tests/ssim/test_longcat_similarity.py index 7266f0482..f187d1f9c 100644 --- a/fastvideo/tests/ssim/test_longcat_similarity.py +++ b/fastvideo/tests/ssim/test_longcat_similarity.py @@ -20,7 +20,7 @@ import pytest import torch from fastvideo import VideoGenerator -from fastvideo.configs.sample.base import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.logger import init_logger from fastvideo.tests.ssim.reference_utils import ( build_generated_output_dir, diff --git a/fastvideo/tests/ssim/test_matrixgame_similarity.py b/fastvideo/tests/ssim/test_matrixgame_similarity.py index 533e80295..89d7aa61b 100644 --- a/fastvideo/tests/ssim/test_matrixgame_similarity.py +++ b/fastvideo/tests/ssim/test_matrixgame_similarity.py @@ -5,7 +5,7 @@ import torch import pytest from fastvideo import VideoGenerator -from fastvideo.configs.sample.wan import MatrixGame2_SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.logger import init_logger from fastvideo.models.dits.matrixgame.utils import create_action_presets from fastvideo.tests.ssim.reference_utils import ( @@ -47,7 +47,8 @@ MATRIXGAME_PARAMS = { "seed": 1024, "keyboard_dim": 4, } -_MATRIXGAME_FULL_QUALITY_DEFAULTS = MatrixGame2_SamplingParam() +_MATRIXGAME_FULL_QUALITY_DEFAULTS = SamplingParam.from_pretrained( + MATRIXGAME_PARAMS["model_path"]) MATRIXGAME_FULL_QUALITY_PARAMS = { "num_gpus": MATRIXGAME_PARAMS["num_gpus"], "model_path": MATRIXGAME_PARAMS["model_path"], diff --git a/fastvideo/tests/ssim/test_sd35_similarity.py b/fastvideo/tests/ssim/test_sd35_similarity.py index 98ce90e59..fcbaa4a0c 100644 --- a/fastvideo/tests/ssim/test_sd35_similarity.py +++ b/fastvideo/tests/ssim/test_sd35_similarity.py @@ -6,7 +6,7 @@ import os import pytest import torch -from fastvideo.configs.sample.sd35 import SD35SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.logger import init_logger from fastvideo.tests.ssim.inference_similarity_utils import ( run_text_to_video_similarity_test, @@ -60,7 +60,8 @@ SD35_PARAMS = { "neg_prompt": "lowres, blurry", } -_SD35_FULL_QUALITY_DEFAULTS = SD35SamplingParam() +_SD35_FULL_QUALITY_DEFAULTS = SamplingParam.from_pretrained( + SD35_MODEL_PATH) SD35_FULL_QUALITY_PARAMS = { "num_gpus": 1, "model_path": SD35_MODEL_PATH, diff --git a/fastvideo/tests/ssim/test_turbodiffusion_similarity.py b/fastvideo/tests/ssim/test_turbodiffusion_similarity.py index 08f6c603c..16f0ef232 100644 --- a/fastvideo/tests/ssim/test_turbodiffusion_similarity.py +++ b/fastvideo/tests/ssim/test_turbodiffusion_similarity.py @@ -3,10 +3,7 @@ import os import pytest -from fastvideo.configs.sample.turbodiffusion import ( - TurboDiffusionI2V_A14B_SamplingParam, - TurboDiffusionT2V_1_3B_SamplingParam, -) +from fastvideo.api.sampling_param import SamplingParam from fastvideo.logger import init_logger from fastvideo.tests.ssim.inference_similarity_utils import ( DEVICE_MAPPINGS, @@ -44,7 +41,8 @@ TURBODIFFUSION_PARAMS = { "tp_size": 1, "fps": 24, } -_TURBODIFFUSION_FULL_QUALITY_DEFAULTS = TurboDiffusionT2V_1_3B_SamplingParam() +_TURBODIFFUSION_FULL_QUALITY_DEFAULTS = SamplingParam.from_pretrained( + TURBODIFFUSION_PARAMS["model_path"]) TURBODIFFUSION_FULL_QUALITY_PARAMS = { "num_gpus": TURBODIFFUSION_PARAMS["num_gpus"], "model_path": TURBODIFFUSION_PARAMS["model_path"], @@ -112,7 +110,8 @@ TURBODIFFUSION_I2V_PARAMS = { "tp_size": 1, "fps": 24, } -_TURBODIFFUSION_I2V_FULL_QUALITY_DEFAULTS = TurboDiffusionI2V_A14B_SamplingParam() +_TURBODIFFUSION_I2V_FULL_QUALITY_DEFAULTS = SamplingParam.from_pretrained( + TURBODIFFUSION_I2V_PARAMS["model_path"]) TURBODIFFUSION_I2V_FULL_QUALITY_PARAMS = { "num_gpus": TURBODIFFUSION_I2V_PARAMS["num_gpus"], "model_path": TURBODIFFUSION_I2V_PARAMS["model_path"], diff --git a/fastvideo/tests/ssim/test_wan_i2v_similarity.py b/fastvideo/tests/ssim/test_wan_i2v_similarity.py index b6eb97b0d..0f24b9add 100644 --- a/fastvideo/tests/ssim/test_wan_i2v_similarity.py +++ b/fastvideo/tests/ssim/test_wan_i2v_similarity.py @@ -3,7 +3,7 @@ import os import pytest -from fastvideo.configs.sample.wan import WanI2V_14B_480P_SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.logger import init_logger from fastvideo.tests.ssim.inference_similarity_utils import ( resolve_inference_device_reference_folder, @@ -41,7 +41,8 @@ WAN_I2V_PARAMS = { ), "text-encoder-precision": ("fp32",), } -_WAN_I2V_FULL_QUALITY_DEFAULTS = WanI2V_14B_480P_SamplingParam() +_WAN_I2V_FULL_QUALITY_DEFAULTS = SamplingParam.from_pretrained( + "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers") WAN_I2V_FULL_QUALITY_PARAMS = { "num_gpus": WAN_I2V_PARAMS["num_gpus"], "model_path": WAN_I2V_PARAMS["model_path"], diff --git a/fastvideo/tests/ssim/test_wan_t2v_similarity.py b/fastvideo/tests/ssim/test_wan_t2v_similarity.py index 2f4f0bd57..51ec7baf2 100644 --- a/fastvideo/tests/ssim/test_wan_t2v_similarity.py +++ b/fastvideo/tests/ssim/test_wan_t2v_similarity.py @@ -3,7 +3,7 @@ import os import pytest -from fastvideo.configs.sample.wan import WanT2V_1_3B_SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.logger import init_logger from fastvideo.tests.ssim.inference_similarity_utils import ( resolve_inference_device_reference_folder, @@ -41,7 +41,8 @@ WAN_T2V_PARAMS = { ), "text-encoder-precision": ("fp32",), } -_WAN_T2V_FULL_QUALITY_DEFAULTS = WanT2V_1_3B_SamplingParam() +_WAN_T2V_FULL_QUALITY_DEFAULTS = SamplingParam.from_pretrained( + "Wan-AI/Wan2.1-T2V-1.3B-Diffusers") WAN_T2V_FULL_QUALITY_PARAMS = { "num_gpus": WAN_T2V_PARAMS["num_gpus"], "model_path": WAN_T2V_PARAMS["model_path"], diff --git a/fastvideo/train/callbacks/validation.py b/fastvideo/train/callbacks/validation.py index 41697336d..5f09636b6 100644 --- a/fastvideo/train/callbacks/validation.py +++ b/fastvideo/train/callbacks/validation.py @@ -20,7 +20,7 @@ import torchvision from einops import rearrange from torch.utils.data import DataLoader -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.dataset.validation_dataset import ( ValidationDataset, ) from fastvideo.distributed import ( diff --git a/fastvideo/train/models/wan/wan.py b/fastvideo/train/models/wan/wan.py index c485f30a1..5f3d6e668 100644 --- a/fastvideo/train/models/wan/wan.py +++ b/fastvideo/train/models/wan/wan.py @@ -10,7 +10,7 @@ from typing import Any, Literal, TYPE_CHECKING import torch import fastvideo.envs as envs -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.distributed import ( get_sp_group, get_world_group, diff --git a/fastvideo/training/distillation_pipeline.py b/fastvideo/training/distillation_pipeline.py index 319f89e4f..0c7a2d0bd 100644 --- a/fastvideo/training/distillation_pipeline.py +++ b/fastvideo/training/distillation_pipeline.py @@ -20,7 +20,7 @@ from torchdata.stateful_dataloader import StatefulDataLoader from tqdm.auto import tqdm import fastvideo.envs as envs -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.dataset.validation_dataset import ValidationDataset from fastvideo.distributed import (cleanup_dist_env_and_memory, get_local_torch_device, get_sp_group, get_world_group) from fastvideo.fastvideo_args import FastVideoArgs, TrainingArgs diff --git a/fastvideo/training/matrixgame_training_pipeline.py b/fastvideo/training/matrixgame_training_pipeline.py index cc7d1948d..177e4ad27 100644 --- a/fastvideo/training/matrixgame_training_pipeline.py +++ b/fastvideo/training/matrixgame_training_pipeline.py @@ -5,7 +5,7 @@ from typing import Any import torch -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.dataset.dataloader.schema import pyarrow_schema_matrixgame from fastvideo.distributed import get_local_torch_device from fastvideo.fastvideo_args import FastVideoArgs, TrainingArgs diff --git a/fastvideo/training/training_pipeline.py b/fastvideo/training/training_pipeline.py index cd8f7e0b0..4b805a1b3 100644 --- a/fastvideo/training/training_pipeline.py +++ b/fastvideo/training/training_pipeline.py @@ -27,7 +27,7 @@ try: from fastvideo.attention.backends.vmoba import VideoMobaAttentionMetadataBuilder except Exception: pass -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.dataset import build_parquet_map_style_dataloader from fastvideo.dataset.dataloader.schema import pyarrow_schema_t2v from fastvideo.dataset.validation_dataset import ValidationDataset diff --git a/fastvideo/training/wan_i2v_distillation_pipeline.py b/fastvideo/training/wan_i2v_distillation_pipeline.py index eb6a9bb3c..c3a0594ac 100644 --- a/fastvideo/training/wan_i2v_distillation_pipeline.py +++ b/fastvideo/training/wan_i2v_distillation_pipeline.py @@ -6,7 +6,7 @@ from typing import Any import torch from einops import rearrange -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.dataset.dataloader.schema import pyarrow_schema_i2v from fastvideo.distributed import get_local_torch_device from fastvideo.fastvideo_args import FastVideoArgs, TrainingArgs diff --git a/fastvideo/training/wan_i2v_training_pipeline.py b/fastvideo/training/wan_i2v_training_pipeline.py index 5d746fef6..cd95a8020 100644 --- a/fastvideo/training/wan_i2v_training_pipeline.py +++ b/fastvideo/training/wan_i2v_training_pipeline.py @@ -5,7 +5,7 @@ from typing import Any import torch -from fastvideo.configs.sample import SamplingParam +from fastvideo.api.sampling_param import SamplingParam from fastvideo.dataset.dataloader.schema import pyarrow_schema_i2v from fastvideo.distributed import get_local_torch_device from fastvideo.fastvideo_args import FastVideoArgs, TrainingArgs diff --git a/tests/local_tests/test_ltx2_registry.py b/tests/local_tests/test_ltx2_registry.py index 85acbab0a..95f1a2985 100644 --- a/tests/local_tests/test_ltx2_registry.py +++ b/tests/local_tests/test_ltx2_registry.py @@ -1,8 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 import json -import importlib -import sys -import types from pathlib import Path import pytest @@ -10,33 +7,12 @@ import pytest pytest.importorskip("torchvision") -def _bootstrap_fastvideo_namespace() -> None: - """Avoid importing fastvideo/__init__.py during local registry tests.""" - if "fastvideo" in sys.modules: - return - - repo_root = Path(__file__).resolve().parents[2] - package_dir = repo_root / "fastvideo" - fastvideo_pkg = types.ModuleType("fastvideo") - fastvideo_pkg.__path__ = [str(package_dir)] # type: ignore[attr-defined] - fastvideo_pkg.__file__ = str(package_dir / "__init__.py") - sys.modules["fastvideo"] = fastvideo_pkg - - -def _get_registry_test_symbols() -> tuple[type, type, type, object, object]: - _bootstrap_fastvideo_namespace() - - pipeline_module = importlib.import_module("fastvideo.configs.pipelines.ltx2") - sample_module = importlib.import_module("fastvideo.configs.sample.ltx2") - registry_module = importlib.import_module("fastvideo.registry") - - return ( - pipeline_module.LTX2T2VConfig, - sample_module.LTX2BaseSamplingParam, - sample_module.LTX2DistilledSamplingParam, - registry_module.get_pipeline_config_cls_from_name, - registry_module.get_sampling_param_cls_for_name, - ) +from fastvideo.api.sampling_param import SamplingParam +from fastvideo.registry import ( + get_pipeline_config_cls_from_name, + get_sampling_param_cls_for_name, +) +from fastvideo.configs.pipelines.ltx2 import LTX2T2VConfig @pytest.mark.parametrize( @@ -47,12 +23,23 @@ def _get_registry_test_symbols() -> tuple[type, type, type, object, object]: ("FastVideo/LTX2-Distilled-Diffusers", "distilled"), ], ) -def test_ltx2_sampling_registry_exact_ids(model_id: str, - expected_variant: str) -> None: - _, base_cls, distilled_cls, _, get_sampling_param_cls_for_name = _get_registry_test_symbols() - expected_cls = base_cls if expected_variant == "base" else distilled_cls - resolved_cls = get_sampling_param_cls_for_name(model_id) - assert resolved_cls is expected_cls +def test_ltx2_sampling_registry_exact_ids( + model_id: str, + expected_variant: str, +) -> None: + # All sampling_param_cls are None after profile migration. + assert get_sampling_param_cls_for_name(model_id) is None + + # Profile-based path should return correct defaults. + sp = SamplingParam.from_pretrained(model_id) + if expected_variant == "base": + assert sp.num_inference_steps == 40 + assert sp.height == 512 + assert sp.width == 768 + else: + assert sp.num_inference_steps == 8 + assert sp.height == 1024 + assert sp.width == 1536 @pytest.mark.parametrize( @@ -64,21 +51,27 @@ def test_ltx2_sampling_registry_exact_ids(model_id: str, ], ) def test_ltx2_pipeline_registry_exact_ids(model_id: str) -> None: - pipeline_cls, _, _, get_pipeline_config_cls_from_name, _ = _get_registry_test_symbols() resolved_cls = get_pipeline_config_cls_from_name(model_id) - assert resolved_cls is pipeline_cls + assert resolved_cls is LTX2T2VConfig -def _write_minimal_diffusers_repo(model_dir: Path, class_name: str) -> None: +def _write_minimal_diffusers_repo( + model_dir: Path, class_name: str +) -> None: model_dir.mkdir(parents=True, exist_ok=True) (model_dir / "transformer").mkdir(exist_ok=True) (model_dir / "vae").mkdir(exist_ok=True) - with (model_dir / "model_index.json").open("w", encoding="utf-8") as f: + with (model_dir / "model_index.json").open( + "w", encoding="utf-8" + ) as f: json.dump( { "_class_name": class_name, "_diffusers_version": "0.33.0.dev0", - "transformer": ["diffusers", "LTX2Transformer3DModel"], + "transformer": [ + "diffusers", + "LTX2Transformer3DModel", + ], "vae": ["diffusers", "CausalVideoAutoencoder"], }, f, @@ -86,23 +79,20 @@ def _write_minimal_diffusers_repo(model_dir: Path, class_name: str) -> None: def test_ltx2_ambiguous_local_path_has_no_sampling_fallback( - tmp_path: Path) -> None: - _, _, _, _, get_sampling_param_cls_for_name = _get_registry_test_symbols() - # Simulate a user-local converted LTX2 path that might be either base or - # distilled. Registry must not assume a variant for local converted paths. + tmp_path: Path, +) -> None: model_dir = tmp_path / "converted" / "ltx2_diffusers" _write_minimal_diffusers_repo(model_dir, "LTX2Pipeline") - resolved_cls = get_sampling_param_cls_for_name(str(model_dir)) assert resolved_cls is None -def test_ltx2_ambiguous_local_path_has_no_pipeline_mapping( - tmp_path: Path) -> None: - _, _, _, get_pipeline_config_cls_from_name, _ = _get_registry_test_symbols() +def test_ltx2_ambiguous_local_path_resolves_via_detector( + tmp_path: Path, +) -> None: + # Both base and distilled share LTX2T2VConfig, so the + # "ltx2" detector correctly resolves ambiguous local paths. model_dir = tmp_path / "converted" / "ltx2_diffusers" _write_minimal_diffusers_repo(model_dir, "LTX2Pipeline") - - with pytest.raises(ValueError, - match="No match found for pipeline .*check the pipeline name or path"): - get_pipeline_config_cls_from_name(str(model_dir)) + resolved_cls = get_pipeline_config_cls_from_name(str(model_dir)) + assert resolved_cls is LTX2T2VConfig