Files
hao-ai-lab-FastVideo/fastvideo/registry.py
T
Will LinandClaude Fable 5 d6ed0e54da [bugfix]: restore pipeline-class override short-circuit and dedup kandinsky5 detectors
get_model_info again skips model_index.json resolution entirely when
override_pipeline_cls_name is set, so overrides keep working for local
checkpoints without a diffusers model_index (and trigger no downloads).
Replace the nine copy-pasted kandinsky5 detectors with a parameterized
factory (identical matching semantics) and add a dedicated I2V Lite distilled
entry + preset so those checkpoints get distilled sampling defaults
(guidance 1.0 / 16 steps) instead of the sft fallback's 5.0 / 50.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 14:58:12 -07:00

1263 lines
46 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""
Central registry for FastVideo pipelines and model configuration discovery.
This module mirrors the organization of sglang's registry while keeping
FastVideo's legacy behavior and mappings intact.
"""
from __future__ import annotations
import dataclasses
import os
from collections.abc import Callable
from functools import lru_cache
from typing import TYPE_CHECKING, Any
from fastvideo.configs.pipelines.base import PipelineConfig
from fastvideo.configs.pipelines.cosmos import CosmosConfig
from fastvideo.configs.pipelines.cosmos2_5 import (
Cosmos25Config,
Cosmos25_14BConfig,
)
from fastvideo.configs.pipelines.hunyuan import FastHunyuanConfig, HunyuanConfig
from fastvideo.configs.pipelines.hunyuangamecraft import HunyuanGameCraftPipelineConfig
from fastvideo.configs.pipelines.gen3c import Gen3CConfig
from fastvideo.configs.pipelines.hunyuan15 import (Hunyuan15T2V480PConfig, Hunyuan15I2V480PStepDistilledConfig,
Hunyuan15T2V720PConfig, Hunyuan15I2V720PConfig,
Hunyuan15SR1080PConfig)
from fastvideo.configs.pipelines.hyworld import HYWorldConfig
from fastvideo.configs.pipelines.kandinsky5 import Kandinsky5I2VConfig, Kandinsky5T2VConfig
from fastvideo.configs.pipelines.lingbotworld import LingBotWorldI2V480PConfig
from fastvideo.configs.pipelines.longcat import LongCatT2V480PConfig
from fastvideo.pipelines.basic.ltx2.pipeline_configs import LTX2T2VConfig
from fastvideo.configs.pipelines.flux_2 import (
Flux2KleinPipelineConfig,
Flux2PipelineConfig,
)
from fastvideo.configs.pipelines.matrixgame2 import MatrixGame2I2V480PConfig
from fastvideo.configs.pipelines.matrixgame3 import MatrixGame3I2V720PConfig
from fastvideo.configs.pipelines.turbodiffusion import (
TurboDiffusionI2V_A14B_Config,
TurboDiffusionT2V_14B_Config,
TurboDiffusionT2V_1_3B_Config,
)
from fastvideo.configs.pipelines.wan import (
FastWan2_1_T2V_480P_Config,
FastWan2_2_TI2V_5B_Config,
LucyEditDevConfig,
SelfForcingWan2_2_T2V480PConfig,
SelfForcingWanT2V480PConfig,
WANV2VConfig,
Wan2_2_I2V_A14B_Config,
Wan2_2_T2V_A14B_Config,
Wan2_2_TI2V_5B_Config,
WanI2V480PConfig,
WanI2V720PConfig,
WanT2V480PConfig,
WanT2V720PConfig,
)
from fastvideo.configs.pipelines.sd35 import SD35Config
from fastvideo.configs.pipelines.stable_audio import (StableAudioOpenSmallConfig, StableAudioT2AConfig)
from fastvideo.api.sampling_param import SamplingParam
from fastvideo.api.matrixgame2 import MatrixGame2SamplingParam
from fastvideo.api.matrixgame3 import MatrixGame3SamplingParam
from fastvideo.fastvideo_args import WorkloadType
from fastvideo.logger import init_logger
from fastvideo.utils import (maybe_download_model_index, verify_model_config_and_directory)
logger = init_logger(__name__)
if TYPE_CHECKING:
from fastvideo.pipelines.composed_pipeline_base import ComposedPipelineBase
from fastvideo.pipelines.pipeline_registry import PipelineType
# --- Part 1: Pipeline Discovery ---
_PIPELINE_REGISTRY: dict[str, dict[str, type[ComposedPipelineBase]]] = {}
# Registry for pipeline configuration classes (for single-file weights without
# model_index.json). Maps pipeline_class_name -> (PipelineConfig, SamplingParam)
_PIPELINE_CONFIG_REGISTRY: dict[str, tuple[type[PipelineConfig], type[SamplingParam]]] = {}
def _discover_and_register_pipelines() -> None:
if _PIPELINE_REGISTRY:
return
from fastvideo.pipelines.pipeline_registry import import_pipeline_classes
pipeline_classes = import_pipeline_classes()
for pipeline_type, pipeline_dict in pipeline_classes.items():
_PIPELINE_REGISTRY[pipeline_type] = pipeline_dict
for pipeline_cls in pipeline_dict.values():
if pipeline_cls is None:
continue
if hasattr(pipeline_cls, "pipeline_config_cls") and hasattr(pipeline_cls, "sampling_params_cls"):
_PIPELINE_CONFIG_REGISTRY[pipeline_cls.__name__] = (
pipeline_cls.pipeline_config_cls,
pipeline_cls.sampling_params_cls,
)
def get_pipeline_config_classes(pipeline_class_name: str) -> tuple[type[PipelineConfig], type[SamplingParam]] | None:
_discover_and_register_pipelines()
return _PIPELINE_CONFIG_REGISTRY.get(pipeline_class_name)
# --- Part 2: Config Registration ---
@dataclasses.dataclass
class ConfigInfo:
"""Encapsulates sampling + pipeline config classes for a model family."""
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
# When set, overrides the model_index `_class_name` for pipeline resolution.
# Lets a model family map to a specific pipeline class by path/detector
# (e.g. a T2V and I2V checkpoint that share a `_class_name`).
pipeline_cls_name: str | None = None
# The central registry mapping a model name to its configuration information
_CONFIG_REGISTRY: dict[str, ConfigInfo] = {}
# Mappings from Hugging Face model paths to our internal model names
_MODEL_HF_PATH_TO_NAME: dict[str, str] = {}
# Detectors to identify model families from paths or class names
_MODEL_NAME_DETECTORS: list[tuple[str, Callable[[str], bool]]] = []
def register_configs(
sampling_param_cls: type[SamplingParam] | None,
pipeline_config_cls: type[PipelineConfig],
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,
pipeline_cls_name: str | None = None,
) -> None:
"""Register config classes for a model family.
workload_types declares which UI workload options this config supports.
Use () for configs not exposed as workload options.
"""
model_id = str(len(_CONFIG_REGISTRY))
_CONFIG_REGISTRY[model_id] = ConfigInfo(
sampling_param_cls=sampling_param_cls,
pipeline_config_cls=pipeline_config_cls,
workload_types=workload_types,
model_family=model_family,
default_preset=default_preset,
pipeline_cls_name=pipeline_cls_name,
)
if hf_model_paths:
for path in hf_model_paths:
if path in _MODEL_HF_PATH_TO_NAME:
logger.warning("Model path '%s' is already mapped to '%s' and will be overwritten by '%s'.", path,
_MODEL_HF_PATH_TO_NAME[path], model_id)
_MODEL_HF_PATH_TO_NAME[path] = model_id
if model_detectors:
for detector in model_detectors:
_MODEL_NAME_DETECTORS.append((model_id, detector))
def get_model_short_name(model_id: str) -> str:
if "/" in model_id:
return model_id.split("/")[-1]
return model_id
def _get_config_info(
model_path: str,
*,
raise_on_missing: bool = True,
) -> ConfigInfo | None:
# 1. Exact match
if model_path in _MODEL_HF_PATH_TO_NAME:
model_id = _MODEL_HF_PATH_TO_NAME[model_path]
logger.debug("Resolved model path '%s' from exact path match.", model_path)
return _CONFIG_REGISTRY.get(model_id)
# 2. Partial match: use short model name.
model_name = get_model_short_name(model_path.lower())
all_model_hf_paths = sorted(_MODEL_HF_PATH_TO_NAME.keys(), key=len, reverse=True)
for registered_model_hf_id in all_model_hf_paths:
registered_model_name = get_model_short_name(registered_model_hf_id.lower())
if registered_model_name == model_name:
logger.debug("Resolved model name '%s' from partial path match.", registered_model_hf_id)
model_id = _MODEL_HF_PATH_TO_NAME[registered_model_hf_id]
return _CONFIG_REGISTRY.get(model_id)
# 3. Use detectors (path or model_index pipeline name).
if os.path.exists(model_path):
config = verify_model_config_and_directory(model_path)
else:
config = maybe_download_model_index(model_path)
pipeline_name = config.get("_class_name", "").lower()
matched_model_names: list[str] = []
for model_id, detector in _MODEL_NAME_DETECTORS:
if detector(model_path.lower()) or detector(pipeline_name):
logger.debug("Matched model name '%s' using a registered detector.", model_id)
matched_model_names.append(model_id)
if matched_model_names:
if len(matched_model_names) > 1:
logger.warning(
"Multiple models matched for path '%s': %s. Using the first matched: '%s'.",
model_path,
matched_model_names,
matched_model_names[0],
)
model_id = matched_model_names[0]
return _CONFIG_REGISTRY.get(model_id)
if raise_on_missing:
raise RuntimeError(f"No model info found for model path: {model_path}")
return None
def _register_configs() -> None:
# LTX-2 (distilled) — registered FIRST so its detector wins over
# the base detector when both fire. The detector loop in
# ``get_model_name_for_path`` ORs the path-based check with a
# pipeline-name check (``ltx2pipeline``) which the base detector's
# "distilled not in path" predicate matches as True (the
# pipeline_name string contains no "distilled" marker), so the
# less-specific BASE detector would otherwise win when the
# input is the absolute path of the distilled checkpoint.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=LTX2T2VConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"FastVideo/LTX2-Distilled-Diffusers",
# LTX-2.3 distilled aliases share the distilled pipeline/preset.
"FastVideo/LTX2.3-Distilled-Diffusers",
"FastVideo/LTX-2.3-Distilled-Diffusers",
],
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",
)
# LTX-2.3 (base) — registered before the LTX-2.0 base entry so its more
# specific 2.3 detector wins. Uses the same pipeline config; the new
# arch flags are read from the checkpoint config.json.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=LTX2T2VConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"Lightricks/LTX-2.3",
"FastVideo/LTX2.3-base",
"FastVideo/LTX2.3-Diffusers",
],
model_detectors=[
lambda path: (any(token in path.lower() for token in (
"lightricks/ltx-2.3",
"ltx2.3-base",
"ltx2.3-diffusers",
"ltx-2.3-diffusers",
)) and "distilled" not in path.lower()),
],
model_family="ltx2",
default_preset="ltx2_3_base",
)
# LTX-2 (base) — excludes 2.3 so the dedicated 2.3 entry above wins.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=LTX2T2VConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"Lightricks/LTX-2",
"FastVideo/LTX2-base",
"FastVideo/LTX2-Diffusers",
],
model_detectors=[
lambda path: ("ltx2" in path.lower() or "ltx-2" in path.lower()) and "distilled" not in path.lower() and
"2.3" not in path.lower(),
],
model_family="ltx2",
default_preset="ltx2_base",
)
# Stable Audio Open (text-to-audio). Both variants must be loaded
# from the FastVideo-curated converted Diffusers-format repos —
# the upstream `stabilityai/stable-audio-open-{1.0,small}` repos
# ship `model.safetensors` as a single monolithic checkpoint with
# no per-component subfolders our standard loader can consume. See
# `scripts/checkpoint_conversion/stable_audio_to_diffusers.py`.
# NOTE: WorkloadType has no T2A variant yet; use T2V as the
# compatibility placeholder until the enum is extended.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=StableAudioT2AConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"FastVideo/stable-audio-open-1.0-Diffusers",
],
# Substring match against HF cache snapshot paths (the lookup
# runs on the resolved local directory, which uses `--` between
# org and repo: `models--FastVideo--stable-audio-open-1.0-Diffusers`).
model_detectors=[
lambda path: "stable-audio-open-1" in path.lower(),
],
model_family="stable_audio",
default_preset="stable_audio_open_1_0_base",
)
# Small variant uses its own `pipeline_config_cls` so it picks up
# the smaller (524288-sample) training window in `sample_size` /
# `max_audio_duration_s`.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=StableAudioOpenSmallConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"FastVideo/stable-audio-open-small-Diffusers",
],
model_detectors=[
lambda path: "stable-audio-open-small" in path.lower(),
],
model_family="stable_audio",
default_preset="stable_audio_open_small",
)
def _is_flux2_klein(path: str) -> bool:
path_lower = path.lower()
return "flux.2-klein" in path_lower or "flux2-klein" in path_lower or "flux2klein" in path_lower
def _is_flux2_full(path: str) -> bool:
path_lower = path.lower()
is_flux2 = "flux.2" in path_lower or "flux2" in path_lower or "flux_2" in path_lower or "flux-2" in path_lower
return is_flux2 and "klein" not in path_lower
# Flux2 Klein (distilled, 4-step, no guidance)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Flux2KleinPipelineConfig,
workload_types=(WorkloadType.T2I, ),
hf_model_paths=[
"black-forest-labs/FLUX.2-klein-4B",
"black-forest-labs/FLUX.2-klein-9B",
],
model_detectors=[
_is_flux2_klein,
],
model_family="flux2",
default_preset="flux2_klein_4b",
)
# Flux2 (full, Mistral3 text encoder, embedded guidance)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Flux2PipelineConfig,
workload_types=(WorkloadType.T2I, ),
hf_model_paths=[
"black-forest-labs/FLUX.2-dev",
],
model_detectors=[
_is_flux2_full,
],
model_family="flux2",
default_preset="flux2_dev",
)
# Hunyuan 1.5 (specific)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Hunyuan15T2V480PConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v",
],
model_detectors=[
lambda path: any(token in path.lower() for token in (
"hunyuan15",
"hunyuanvideo15",
"hunyuanvideo-1.5",
"hunyuanvideo_1.5",
)),
],
model_family="hunyuan15",
default_preset="hunyuan15_t2v_480p",
)
register_configs(
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=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=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=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=None,
pipeline_config_cls=HunyuanConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"hunyuanvideo-community/HunyuanVideo",
],
model_detectors=[
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=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=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=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=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",
)
def _kandinsky5_detector(require: tuple[str, ...] = (), exclude: tuple[str, ...] = ()) -> Callable[[str], bool]:
def detect(path: str) -> bool:
path_lower = path.lower()
if "kandinsky5" not in path_lower and "kandinsky-5" not in path_lower:
return False
return (all(token in path_lower for token in require) and not any(token in path_lower for token in exclude))
return detect
# t2v/i2v exclude each other so a checkpoint stored under a directory
# containing the other token (e.g. ~/i2v_experiments/kandinsky5-t2v-ft)
# falls through to the model_index _class_name fallback detectors below
# instead of being misrouted.
_is_kandinsky5_t2v = _kandinsky5_detector(require=("t2v", ), exclude=("i2v", ))
_is_kandinsky5_i2v = _kandinsky5_detector(require=("i2v", ), exclude=("t2v", ))
_is_kandinsky5_t2v_lite = _kandinsky5_detector(require=("t2v", "lite"), exclude=("i2v", "distilled"))
_is_kandinsky5_t2v_pro = _kandinsky5_detector(require=("t2v", "pro"), exclude=("i2v", "distilled"))
_is_kandinsky5_t2v_lite_distilled = _kandinsky5_detector(require=("t2v", "lite", "distilled"), exclude=("i2v", ))
_is_kandinsky5_t2v_pro_distilled = _kandinsky5_detector(require=("t2v", "pro", "distilled"), exclude=("i2v", ))
_is_kandinsky5_i2v_lite = _kandinsky5_detector(require=("i2v", "lite"), exclude=("t2v", "distilled"))
_is_kandinsky5_i2v_pro = _kandinsky5_detector(require=("i2v", "pro"), exclude=("t2v", "distilled"))
_is_kandinsky5_i2v_lite_distilled = _kandinsky5_detector(require=("i2v", "lite", "distilled"), exclude=("t2v", ))
_is_kandinsky5_i2v_pro_distilled = _kandinsky5_detector(require=("i2v", "pro", "distilled"), exclude=("t2v", ))
# Kandinsky5 Lite T2V
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky5T2VConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"kandinskylab/Kandinsky-5.0-T2V-Lite-sft-5s-Diffusers",
],
model_detectors=[
_is_kandinsky5_t2v_lite,
],
model_family="kandinsky5",
default_preset="kandinsky5_t2v_lite_5s",
pipeline_cls_name="Kandinsky5T2VPipeline",
)
# Kandinsky5 Pro T2V
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky5T2VConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"kandinskylab/Kandinsky-5.0-T2V-Pro-sft-5s-Diffusers",
],
model_detectors=[
_is_kandinsky5_t2v_pro,
],
model_family="kandinsky5",
default_preset="kandinsky5_t2v_pro_5s",
)
# Kandinsky5 Lite T2V Distilled
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky5T2VConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"kandinskylab/Kandinsky-5.0-T2V-Lite-distilled16steps-5s-Diffusers",
],
model_detectors=[
_is_kandinsky5_t2v_lite_distilled,
],
model_family="kandinsky5",
default_preset="kandinsky5_t2v_lite_distilled_5s",
)
# Kandinsky5 Pro T2V Distilled
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky5T2VConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"kandinskylab/Kandinsky-5.0-T2V-Pro-distilled-5s-Diffusers",
],
model_detectors=[
_is_kandinsky5_t2v_pro_distilled,
],
model_family="kandinsky5",
default_preset="kandinsky5_t2v_pro_distilled_5s",
)
# Kandinsky5 Lite I2V
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky5I2VConfig,
workload_types=(WorkloadType.I2V, ),
hf_model_paths=["kandinskylab/Kandinsky-5.0-I2V-Lite-5s-Diffusers"],
model_detectors=[
_is_kandinsky5_i2v_lite,
],
model_family="kandinsky5",
default_preset="kandinsky5_i2v_lite_5s",
pipeline_cls_name="Kandinsky5I2VPipeline",
)
# Kandinsky5 Pro I2V
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky5I2VConfig,
workload_types=(WorkloadType.I2V, ),
hf_model_paths=["kandinskylab/Kandinsky-5.0-I2V-Pro-sft-5s-Diffusers"],
model_detectors=[
_is_kandinsky5_i2v_pro,
],
model_family="kandinsky5",
default_preset="kandinsky5_i2v_pro_5s",
)
# Kandinsky5 Pro I2V Distilled
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky5I2VConfig,
workload_types=(WorkloadType.I2V, ),
hf_model_paths=["kandinskylab/Kandinsky-5.0-I2V-Pro-distilled-5s-Diffusers"],
model_detectors=[
_is_kandinsky5_i2v_pro_distilled,
],
model_family="kandinsky5",
default_preset="kandinsky5_i2v_pro_distilled_5s",
)
# Kandinsky5 Lite I2V Distilled (no official hub repo yet; local
# conversions get distilled sampling defaults instead of the sft ones the
# fallback would apply).
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky5I2VConfig,
workload_types=(),
model_detectors=[
_is_kandinsky5_i2v_lite_distilled,
],
model_family="kandinsky5",
default_preset="kandinsky5_i2v_lite_distilled_5s",
pipeline_cls_name="Kandinsky5I2VPipeline",
)
# Kandinsky5 fallbacks — registered AFTER the variant detectors so those
# win first-match. Catch checkpoints the variant detectors cannot resolve:
# token-less local paths matched via the model_index _class_name
# ("kandinsky5t2vpipeline" carries no lite/pro marker), variant combos
# without a dedicated entry (e.g. I2V Lite distilled), and t2v+i2v
# ambiguous paths resolved by the checkpoint's _class_name.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky5T2VConfig,
workload_types=(),
model_detectors=[
_is_kandinsky5_t2v,
],
model_family="kandinsky5",
default_preset="kandinsky5_t2v_lite_5s",
pipeline_cls_name="Kandinsky5T2VPipeline",
)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky5I2VConfig,
workload_types=(),
model_detectors=[
_is_kandinsky5_i2v,
],
model_family="kandinsky5",
default_preset="kandinsky5_i2v_lite_5s",
pipeline_cls_name="Kandinsky5I2VPipeline",
)
# LongCat (T2V, I2V, VC use same config; workload varies by path)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=LongCatT2V480PConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=["FastVideo/LongCat-Video-T2V-Diffusers"],
model_detectors=[
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,
pipeline_config_cls=LongCatT2V480PConfig,
workload_types=(WorkloadType.I2V, ),
hf_model_paths=["FastVideo/LongCat-Video-I2V-Diffusers"],
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,
pipeline_config_cls=LongCatT2V480PConfig,
workload_types=(),
hf_model_paths=["FastVideo/LongCat-Video-VC-Diffusers"],
model_detectors=[
lambda path: "longcatvideocontinuation" in path.lower() or
("longcat" in path.lower() and "vc" in path.lower()),
],
model_family="longcat",
default_preset="longcat_vc",
)
# MatrixGame 2.0 (I2V)
register_configs(
sampling_param_cls=MatrixGame2SamplingParam,
pipeline_config_cls=MatrixGame2I2V480PConfig,
workload_types=(WorkloadType.I2V, ),
hf_model_paths=[
"FastVideo/Matrix-Game-2.0-Base-Distilled-Diffusers",
"FastVideo/Matrix-Game-2.0-GTA-Distilled-Diffusers",
"FastVideo/Matrix-Game-2.0-TempleRun-Distilled-Diffusers",
# Legacy HF paths (kept for backward compat - pre-rename names):
"FastVideo/Matrix-Game-2.0-Base-Diffusers",
"FastVideo/Matrix-Game-2.0-GTA-Diffusers",
"FastVideo/Matrix-Game-2.0-TempleRun-Diffusers",
],
model_detectors=[
lambda path: any(token in path.lower() for token in (
"matrix-game-2",
"matrixgame2",
"matrix-game-2.0",
)),
],
model_family="matrixgame",
default_preset="matrixgame2_i2v",
)
# MatrixGame 3.0 (I2V)
register_configs(
sampling_param_cls=MatrixGame3SamplingParam,
pipeline_config_cls=MatrixGame3I2V720PConfig,
workload_types=(WorkloadType.I2V, ),
hf_model_paths=[
"FastVideo/Matrix-Game-3.0-Base-Distilled-Diffusers",
],
model_detectors=[
lambda path: any(token in path.lower() for token in (
"matrix-game-3",
"matrixgame3",
"matrix-game-3.0",
)),
],
model_family="matrixgame",
default_preset="matrixgame3_i2v",
)
# GEN3C (must register before generic Cosmos detector)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Gen3CConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"FastVideo/GEN3C-Cosmos-7B-Diffusers",
],
model_detectors=[
lambda path: "gen3c" in path.lower(),
],
model_family="gen3c",
default_preset="gen3c_cosmos_7b",
)
# Cosmos 2.5 (2B)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Cosmos25Config,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"KyleShao/Cosmos-Predict2.5-2B-Diffusers",
],
model_detectors=[
lambda path: any(token in path.lower() for token in (
"cosmos25",
"cosmos2_5",
"cosmos2.5",
"cosmos-predict2.5",
)) and "14b" not in path.lower(),
],
model_family="cosmos25",
default_preset="cosmos25_predict2_2b",
)
# Cosmos 2.5 (14B)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Cosmos25_14BConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"nvidia/Cosmos-Predict2.5-14B",
],
model_detectors=[
lambda path: any(token in path.lower() for token in (
"cosmos25",
"cosmos2_5",
"cosmos2.5",
)) and "14b" in path.lower(),
],
model_family="cosmos25",
default_preset="cosmos25_predict2_2b",
)
# Cosmos 2
register_configs(
sampling_param_cls=None,
pipeline_config_cls=CosmosConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"nvidia/Cosmos-Predict2-2B-Video2World",
],
model_detectors=[
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=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=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=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 — defaults provided by presets (no sampling_param_cls needed)
register_configs(
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=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=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=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=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=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=None,
pipeline_config_cls=FastWan2_1_T2V_480P_Config,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=[
"FastVideo/FastWan2.1-T2V-1.3B-Diffusers",
"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=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=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=None,
pipeline_config_cls=LucyEditDevConfig,
workload_types=(),
hf_model_paths=[
"decart-ai/Lucy-Edit-Dev",
"decart-ai/Lucy-Edit-1.1-Dev",
],
model_detectors=[lambda path: "lucy-edit" in path.lower()],
model_family="wan",
default_preset="lucy_edit_dev",
)
register_configs(
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=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=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=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=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=None,
pipeline_config_cls=SD35Config,
workload_types=(WorkloadType.T2I, ),
hf_model_paths=[
"stabilityai/stable-diffusion-3.5-medium",
],
model_detectors=[
lambda path: any(token in path.lower() for token in (
"sd35",
"stablediffusion3",
"stabilityai__stable-diffusion-3.5-medium",
)),
],
model_family="sd35",
default_preset="sd35_medium",
)
# --- Part 3: Main Resolver ---
@dataclasses.dataclass
class ModelInfo:
pipeline_cls: type[ComposedPipelineBase]
sampling_param_cls: type[SamplingParam]
pipeline_config_cls: type[PipelineConfig]
@lru_cache(maxsize=32)
def get_model_info(
model_path: str,
pipeline_type: PipelineType | str | None = None,
workload_type: WorkloadType | None = None,
override_pipeline_cls_name: str | None = None,
) -> ModelInfo:
from fastvideo.pipelines.pipeline_registry import (PipelineType, get_pipeline_registry)
if pipeline_type is None:
pipeline_type = PipelineType.BASIC
elif isinstance(pipeline_type, str):
pipeline_type = PipelineType.from_string(pipeline_type)
if workload_type is None:
workload_type = WorkloadType.T2V
config_info = _get_config_info(model_path, raise_on_missing=True)
assert config_info is not None, "config_info must be resolved"
if override_pipeline_cls_name:
# Explicit override: skip config resolution entirely so checkpoints
# without a diffusers model_index.json keep working (and no download
# is triggered just to log the replaced name).
pipeline_name: str | None = override_pipeline_cls_name
logger.info("Using override pipeline class name %s", pipeline_name)
else:
if os.path.exists(model_path):
config = verify_model_config_and_directory(model_path)
else:
config = maybe_download_model_index(model_path)
pipeline_name = config.get("_class_name")
if config_info.pipeline_cls_name is not None:
# The resolved (path/detector-based) config pins the pipeline class,
# e.g. an I2V checkpoint whose `_class_name` would otherwise resolve
# to the T2V pipeline.
logger.info("Pinning pipeline class name from %s to %s", pipeline_name, config_info.pipeline_cls_name)
pipeline_name = config_info.pipeline_cls_name
if pipeline_name is None:
raise ValueError("Model config does not contain a _class_name attribute. "
"Only diffusers format is supported.")
pipeline_registry = get_pipeline_registry(pipeline_type)
pipeline_cls = pipeline_registry.resolve_pipeline_cls(pipeline_name, pipeline_type, workload_type)
sampling_param_cls = config_info.sampling_param_cls or SamplingParam
return ModelInfo(
pipeline_cls=pipeline_cls,
sampling_param_cls=sampling_param_cls,
pipeline_config_cls=config_info.pipeline_config_cls,
)
def get_pipeline_config_cls_from_name(pipeline_name_or_path: str) -> type[PipelineConfig]:
config_info = _get_config_info(pipeline_name_or_path, raise_on_missing=False)
if config_info is None:
raise ValueError(
f"No match found for pipeline {pipeline_name_or_path}, please check the pipeline name or path.")
return config_info.pipeline_config_cls
def get_sampling_param_cls_for_name(pipeline_name_or_path: str) -> Any | None:
config_info = _get_config_info(pipeline_name_or_path, raise_on_missing=False)
if config_info is None:
logger.warning("No match found for pipeline %s, using default sampling param.", pipeline_name_or_path)
return None
return config_info.sampling_param_cls
_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.kandinsky5.presets import (
ALL_PRESETS as KANDINSKY5_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.matrixgame2.presets import (
ALL_PRESETS as MATRIXGAME2_PRESETS, )
from fastvideo.pipelines.basic.matrixgame3.presets import (
ALL_PRESETS as MATRIXGAME3_PRESETS, )
from fastvideo.pipelines.basic.sd35.presets import (
ALL_PRESETS as SD35_PRESETS, )
from fastvideo.pipelines.basic.stable_audio.presets import (
ALL_PRESETS as STABLE_AUDIO_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, )
from fastvideo.pipelines.basic.flux_2.presets import (
ALL_PRESETS as FLUX2_PRESETS, )
all_preset_groups = (
COSMOS_PRESETS,
FLUX2_PRESETS,
GAMECRAFT_PRESETS,
GEN3C_PRESETS,
HUNYUAN_PRESETS,
HUNYUAN15_PRESETS,
HYWORLD_PRESETS,
KANDINSKY5_PRESETS,
LINGBOTWORLD_PRESETS,
LONGCAT_PRESETS,
LTX2_PRESETS,
MATRIXGAME2_PRESETS,
MATRIXGAME3_PRESETS,
SD35_PRESETS,
STABLE_AUDIO_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.
Useful for UIs and tooling that need to enumerate supported models.
"""
return sorted(_MODEL_HF_PATH_TO_NAME.keys())
def get_registered_models_with_workloads(workload_type: str | None = None, ) -> list[dict[str, Any]]:
"""Return models with workload metadata, optionally filtered by workload.
Args:
workload_type: If set (e.g. "t2v", "i2v", "t2i"), only return models
that support this workload. If None, return all with workload_types.
Returns:
List of dicts with keys: id, label, workload_types.
"""
result: list[dict[str, Any]] = []
for path in sorted(_MODEL_HF_PATH_TO_NAME.keys()):
model_id = _MODEL_HF_PATH_TO_NAME[path]
config_info = _CONFIG_REGISTRY.get(model_id)
if config_info is None:
continue
workload_values = [w.value for w in config_info.workload_types]
if workload_type is not None and workload_type.lower() not in workload_values:
continue
label = path.split("/")[-1].replace("-", " ").replace("_", " ")
result.append({
"id": path,
"label": label,
"workload_types": workload_values,
})
return result
__all__ = [
"ConfigInfo",
"ModelInfo",
"get_default_preset",
"get_model_family",
"get_model_info",
"get_pipeline_config_cls_from_name",
"get_registered_model_paths",
"get_registered_models_with_workloads",
"get_sampling_param_cls_for_name",
"get_pipeline_config_classes",
]