Files
hao-ai-lab-FastVideo/fastvideo/registry.py
T
aryan5v dcbaed504f [feat]: add Pika H3 Flex inference support
Add Pika H3 Flex (MiniMax-H3 plus the released fl2va/ref2va rank-128 adapters) as a FastVideo model family: registry/config/presets, bucket geometry with tiered reference sizing, automatic adapter selection and manifest validation, the fixed six-forward re-noise sampler, T2VA/I2VA/L2VA/FL2VA/Ref2VA pipeline support, an example, docs, and CPU unit tests.

GPU end-to-end validation is still pending. This change does not add PDD, VSA, or new kernel paths.
2026-10-29 17:37:52 -07:00

1502 lines
60 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.gen3c import Gen3CConfig
from fastvideo.configs.pipelines.helios import HeliosPipelineConfig
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.kandinsky6 import Kandinsky6TI2VAConfig
from fastvideo.configs.pipelines.kandinsky6_sr import Kandinsky6SRPipelineConfig
from fastvideo.configs.pipelines.lingbot_video import LingBotVideoT2VConfig
from fastvideo.configs.pipelines.lingbotworld import LingBotWorldI2V480PConfig
from fastvideo.configs.pipelines.lingbotworld2 import LingBotWorld2CausalFastI2V480PConfig
from fastvideo.configs.pipelines.lingbotworld_fast import LingBotWorldFastI2V480PConfig
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.minimax_h3 import MiniMaxH3PipelineConfig
from fastvideo.configs.pipelines.pika_h3_flex import PikaH3FlexPipelineConfig
from fastvideo.configs.pipelines.mmaudio import (
MMAudioLarge44kV2PipelineConfig,
MMAudioLarge44kV2AConfig,
MMAudioMedium44kV2AConfig,
MMAudioSmall16kV2AConfig,
MMAudioSmall44kV2AConfig,
MMAudioV2AConfig,
)
from fastvideo.models.wan import pipeline_config as wan_pipeline_config
from fastvideo.models.wan.definition import WAN_MODEL_DEFINITIONS, WanModelDefinition
from fastvideo.configs.pipelines.flux import FluxPipelineConfig
from fastvideo.configs.pipelines.sd35 import SD35Config
from fastvideo.configs.pipelines.stable_audio import (StableAudioOpenSmallConfig, StableAudioT2AConfig)
from fastvideo.configs.pipelines.qwen_image21 import QwenImage21PipelineConfig
from fastvideo.api.sampling_param import SamplingParam
from fastvideo.api.matrixgame2 import MatrixGame2SamplingParam
from fastvideo.api.matrixgame3 import MatrixGame3SamplingParam
from fastvideo.api.flux import FluxSamplingParam
from fastvideo.api.pika_h3_flex import PikaH3FlexSamplingParam
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 _longest_common_substring_length(left: str, right: str) -> int:
"""Length of the longest substring shared by ``left`` and ``right``."""
if not left or not right:
return 0
previous = [0] * (len(right) + 1)
longest = 0
for left_char in left:
current = [0] * (len(right) + 1)
for index, right_char in enumerate(right, start=1):
if left_char == right_char:
current[index] = previous[index - 1] + 1
longest = max(longest, current[index])
previous = current
return longest
def _pattern_specificity(model_id: str, path_lower: str) -> int:
"""How specifically ``path_lower`` points at ``model_id``: the longest
substring it shares with any of the model's registered HF paths (short
names). Unlike plain containment this still scores truncated or
re-hyphenated local directory names (e.g. ``ltx-2.3-distilled``). An
entry registered only through detectors scores 0."""
return max((_longest_common_substring_length(path_lower, get_model_short_name(registered_path.lower()))
for registered_path, mapped_id in _MODEL_HF_PATH_TO_NAME.items() if mapped_id == model_id),
default=0)
def _resolve_class_name(config: dict[str, Any]) -> str | None:
"""Read `_class_name` from a model_index.json/component config dict.
Usually a plain string, but some exports write it in the diffusers
`[library, class]` 2-element form instead -- unwrap that form to the bare
class name.
"""
class_name = config.get("_class_name")
if isinstance(class_name, list | tuple):
return class_name[-1] if class_name else None
return class_name
def _get_config_info(
model_path: str,
*,
raise_on_missing: bool = True,
revision: str | None = None,
allow_remote: bool = True,
) -> ConfigInfo | None:
# Normalize only the lookup key; keep the real path for manifest IO and detectors.
lookup_path = model_path.rstrip("/") or model_path
parts = lookup_path.split("/")
# Standard Hugging Face cache layout: models--{org}--{name}/snapshots/{revision}.
if len(parts) >= 3 and parts[-2] == "snapshots" and parts[-3].startswith("models--"):
lookup_path = parts[-3][len("models--"):].replace("--", "/", 1)
# 1. Exact match
if lookup_path in _MODEL_HF_PATH_TO_NAME:
model_id = _MODEL_HF_PATH_TO_NAME[lookup_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(lookup_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, required_component_dirs=[])
elif allow_remote:
config = maybe_download_model_index(model_path, revision=revision)
else:
return None
pipeline_name = (_resolve_class_name(config) or "").lower()
scheduler = config.get("scheduler")
if (pipeline_name == "heliospyramidpipeline" and config.get("is_distilled") is True and isinstance(scheduler, list)
and len(scheduler) >= 2 and scheduler[1] == "HeliosDMDScheduler"):
helios_model_id = _MODEL_HF_PATH_TO_NAME.get("BestWishYsh/Helios-Distilled")
if helios_model_id is not None:
logger.debug("Resolved Helios-Distilled from authoritative model index metadata.")
return _CONFIG_REGISTRY.get(helios_model_id)
variant = config.get("fastvideo_ltx2_variant", "").lower()
matched_model_names: list[str] = []
for model_id, detector in _MODEL_NAME_DETECTORS:
if detector(model_path.lower()) or detector(pipeline_name) or detector(variant):
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:
# The first detector match always wins: registration order is the
# supported precedence (dedicated entries register before broad
# family ones, e.g. LTX-2 distilled before LTX-2 base), even when
# a path detector and a class-name detector disagree.
# Specificity only decides whether that choice is ambiguous. When
# the first match shares more of the query path with its
# registered HF paths than every other match does (e.g. an
# LTX-2.3 distilled directory that the LTX-2 base detector also
# claims through the shared pipeline class name), resolve
# silently. Otherwise warn.
path_lower = model_path.lower()
first_score, *other_scores = [_pattern_specificity(name, path_lower) for name in matched_model_names]
if first_score <= max(other_scores):
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 _mmaudio_variant_detector(expected: str) -> Callable[[str], bool]:
def detector(path: str) -> bool:
normalized = path.lower().replace("-", "_")
return expected in normalized and (expected == "large_44k_v2" or "large_44k_v2" not in normalized)
return detector
def _unversioned_mmaudio_detector(*, mode: str) -> Callable[[str], bool]:
variants = ("small_16k", "small_44k", "medium_44k", "large_44k")
def detector(path: str) -> bool:
normalized = path.lower().replace("-", "_")
# This fallback is path-only. Matching ``mmaudiopipeline`` would also
# fire for every variant-specific model_index and create ambiguity.
if "mmaudio" not in normalized or "mmaudiopipeline" in normalized:
return False
if any(variant in normalized for variant in variants):
return False
return ("16k" in normalized) if mode == "16k" else ("16k" not in normalized)
return detector
def _register_wan_configs(definitions: tuple[WanModelDefinition, ...]) -> None:
for definition in definitions:
register_configs(
sampling_param_cls=None,
pipeline_config_cls=getattr(wan_pipeline_config, definition.pipeline_config),
workload_types=tuple(WorkloadType(value) for value in definition.workload_types),
hf_model_paths=list(definition.hf_model_paths),
model_detectors=[definition.matches] if definition.match_any else None,
model_family="wan",
default_preset=definition.preset,
)
def _register_configs() -> None:
register_configs(
sampling_param_cls=None,
pipeline_config_cls=HeliosPipelineConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=["BestWishYsh/Helios-Distilled"],
model_detectors=[],
model_family="helios",
default_preset="helios_distilled_t2v",
pipeline_cls_name="HeliosPyramidPipeline",
)
# Official MMAudio video/text-to-audio variants. Converted directories keep
# the variant in their path, allowing the registry to select the matching
# 16 kHz or 44.1 kHz component configuration before any modules are loaded.
mmaudio_variants = (
("small_16k", MMAudioSmall16kV2AConfig),
("small_44k", MMAudioSmall44kV2AConfig),
("medium_44k", MMAudioMedium44kV2AConfig),
("large_44k_v2", MMAudioLarge44kV2PipelineConfig),
("large_44k", MMAudioLarge44kV2AConfig),
)
for variant, pipeline_config_cls in mmaudio_variants:
register_configs(
sampling_param_cls=None,
pipeline_config_cls=pipeline_config_cls,
workload_types=(WorkloadType.V2A, WorkloadType.T2A),
hf_model_paths=[
f"FastVideo/MMAudio-{variant.replace('_', '-')}-Diffusers",
],
model_detectors=[_mmaudio_variant_detector(variant)],
model_family="mmaudio",
default_preset=f"mmaudio_{variant}",
pipeline_cls_name="MMAudioPipeline",
)
# Preserve converted preprocessing trees and older local checkpoint names
# that predate explicit variant tags. These detectors intentionally do not
# inspect ``_class_name=MMAudioPipeline`` so they cannot overlap the five
# exact variant entries above.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=MMAudioSmall16kV2AConfig,
workload_types=(WorkloadType.V2A, WorkloadType.T2A),
model_detectors=[_unversioned_mmaudio_detector(mode="16k")],
model_family="mmaudio",
default_preset="mmaudio_small_16k",
pipeline_cls_name="MMAudioPipeline",
)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=MMAudioV2AConfig,
workload_types=(WorkloadType.V2A, WorkloadType.T2A),
model_detectors=[_unversioned_mmaudio_detector(mode="44k")],
model_family="mmaudio",
default_preset="mmaudio_large_44k_v2",
pipeline_cls_name="MMAudioPipeline",
)
# LTX-2.5 distilled — registered before every older/generic LTX entry so
# its ancestral-sampling preset and architecture flags cannot be shadowed.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=LTX2T2VConfig,
workload_types=(WorkloadType.T2V, WorkloadType.I2V),
hf_model_paths=[
"FastVideo/LTX-2.5-Distilled-Diffusers",
"FastVideo/LTX2.5-Distilled-Diffusers",
],
model_detectors=[
lambda path: ("ltx-2.5" in path.lower() or "ltx2.5" in path.lower()) and "distilled" in path.lower(),
],
model_family="ltx2",
default_preset="ltx2_5_distilled_two_stage",
)
# LTX-2.5 dev — raw Lightricks split checkpoints must first be converted
# into the standard component layout consumed by FastVideo loaders.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=LTX2T2VConfig,
workload_types=(WorkloadType.T2V, WorkloadType.I2V),
hf_model_paths=[
"FastVideo/LTX-2.5-Dev-Diffusers",
"FastVideo/LTX2.5-Dev-Diffusers",
],
model_detectors=[
lambda path: ("ltx-2.5" in path.lower() or "ltx2.5" in path.lower()) and "distilled" not in path.lower(),
],
model_family="ltx2",
default_preset="ltx2_5_dev",
)
# LTX-2 (distilled) — registered before the generic base detector 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`.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=StableAudioT2AConfig,
# Keep T2V as a backward-compatible alias for existing callers while
# exposing the semantically correct audio workload to new clients.
workload_types=(WorkloadType.T2A, 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.T2A, 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 hyworld, versioned models, and removed GameCraft checkpoints)
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",
)
# LingBotWorld2 causal-fast
register_configs(
sampling_param_cls=None,
pipeline_config_cls=LingBotWorld2CausalFastI2V480PConfig,
workload_types=(WorkloadType.I2V, ),
hf_model_paths=[
"robbyant/lingbot-world-v2-14b-causal-fast",
],
model_detectors=[
lambda path:
("lingbot-world-v2-14b-causal-fast" in path.lower() or "lingbotworld2causalfastpipeline" in path.lower())
],
model_family="lingbotworld2",
default_preset="lingbotworld2_causal_fast_i2v",
)
# LingBotWorld-Fast — registered BEFORE the LingBotWorld base entry so its
# detector wins when both fire. The base detector only excludes the
# "causal-fast" spelling, so it also matches this checkpoint's path and its
# `lingbotworldcausaldmdpipeline` model_index name; the detector loop keeps
# the first match, which must be this more specific entry.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=LingBotWorldFastI2V480PConfig,
workload_types=(WorkloadType.I2V, ),
hf_model_paths=[
"FastVideo/LingBot-World-Fast-Diffusers",
],
model_detectors=[
lambda path: ("lingbot-world-fast" in path.lower() or "lingbotworldfast" in path.lower() or
"lingbotworldcausaldmdpipeline" in path.lower())
],
model_family="lingbotworld_fast",
default_preset="lingbotworld_fast_i2v",
pipeline_cls_name="LingBotWorldFastPipeline",
)
# 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()) and "causal-fast" not in path.lower()
and "causalfast" not in path.lower() and "-fast" not in path.lower() and "causaldmd" not in path.lower())
],
model_family="lingbotworld",
default_preset="lingbotworld_i2v",
)
# LingBot-Video MoE T2V with the released second-stage refiner.
register_configs(
sampling_param_cls=None,
pipeline_config_cls=LingBotVideoT2VConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=["FastVideo/LingBot-Video-MoE-30B-A3B-Diffusers"],
model_detectors=[lambda path: "lingbotvideomoepipeline" in path.lower()],
model_family="lingbot_video",
default_preset="lingbot_video_moe_refiner_t2v",
pipeline_cls_name="LingBotVideoPipeline",
)
# LingBot-Video Dense T2V
register_configs(
sampling_param_cls=None,
pipeline_config_cls=LingBotVideoT2VConfig,
workload_types=(WorkloadType.T2V, ),
hf_model_paths=["FastVideo/LingBot-Video-Dense-1.3B-Diffusers"],
model_detectors=[lambda path: "lingbotvideodensepipeline" in path.lower()],
model_family="lingbot_video",
default_preset="lingbot_video_dense_t2v",
pipeline_cls_name="LingBotVideoPipeline",
)
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",
)
# Kandinsky6 video super-resolution (video -> video). Registered before the Kandinsky6 TI2VA entries: VSR bundle
# names also contain the Kandinsky6 family markers and the first matching detector wins (the TI2VA detector excludes
# VSR bundles too). There is no V2V WorkloadType, so, like the Hunyuan15 SR entry, it declares no workload. Both
# bundles share the pipeline; the distilled one (PiflowScheduler, 2 steps) gets its own preset.
def _is_kandinsky6_sr(path: str) -> bool:
path_lower = path.lower()
if "kandinsky6" not in path_lower and "kandinsky-6" not in path_lower:
return False
# Only the last path component counts, so a parent directory called "sr" does not make a TI2VA checkpoint SR.
name = path_lower.rstrip("/").rsplit("/", 1)[-1].replace("_", "-").replace(".", "-")
return "6sr" in name or "superres" in name or "super-res" in name or any(token in ("sr", "vsr")
for token in name.split("-"))
def _is_kandinsky6_sr_distilled(path: str) -> bool:
return _is_kandinsky6_sr(path) and "distill" in path.lower().rstrip("/").rsplit("/", 1)[-1]
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky6SRPipelineConfig,
workload_types=(),
hf_model_paths=["kandinskylab/Kandinsky-6.0-VSR-distilled2steps-5s-Diffusers"],
model_detectors=[_is_kandinsky6_sr_distilled],
model_family="kandinsky6_sr",
default_preset="kandinsky6_sr_distilled",
pipeline_cls_name="Kandinsky6SRPipeline",
)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky6SRPipelineConfig,
workload_types=(),
hf_model_paths=["kandinskylab/Kandinsky-6.0-VSR-5s-Diffusers"],
model_detectors=[lambda path: _is_kandinsky6_sr(path) and not _is_kandinsky6_sr_distilled(path)],
model_family="kandinsky6_sr",
default_preset="kandinsky6_sr",
pipeline_cls_name="Kandinsky6SRPipeline",
)
# Kandinsky6 (TI2VA) -- a single pipeline handles text-only and
# image+text calls (see Kandinsky6TI2VAPipeline's docstring), so unlike
# Kandinsky5 there is no t2v/i2v detector split: any path/class-name
# containing "kandinsky6" or "kandinsky-6" resolves here (except VSR
# bundles, see above). The only split is base vs pi-Flow distilled, laid
# out like the LTX-2 distilled/base pair: the distilled entry is registered
# FIRST and the base detector excludes distilled names, so neither can
# swallow the other. Both bundles declare the same model_index
# `_class_name`, so a local copy counts as distilled only when its
# directory name is a Kandinsky-6 name containing "distill"
# ("Kandinsky-6.0-Pro-distill-5s-Diffusers"); any other directory resolves
# to the base entry.
def _is_kandinsky6_ti2va(path: str) -> bool:
path_lower = path.lower()
return ("kandinsky6" in path_lower or "kandinsky-6" in path_lower) and not _is_kandinsky6_sr(path_lower)
def _is_kandinsky6_distilled(path: str) -> bool:
# Looked up in the last path component only, like the VSR markers above.
name = path.lower().rstrip("/").rsplit("/", 1)[-1]
return _is_kandinsky6_ti2va(path) and "distill" in name
def _is_kandinsky6(path: str) -> bool:
return _is_kandinsky6_ti2va(path) and not _is_kandinsky6_distilled(path)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky6TI2VAConfig,
workload_types=(WorkloadType.T2V, WorkloadType.I2V),
hf_model_paths=[
"kandinskylab/Kandinsky-6.0-Pro-distill-5s-Diffusers",
"kandinskylab/Kandinsky-6.0-Lite-distill-5s-Diffusers",
],
model_detectors=[
_is_kandinsky6_distilled,
],
model_family="kandinsky6",
default_preset="kandinsky6_ti2va_distilled",
pipeline_cls_name="Kandinsky6TI2VAPipeline",
)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Kandinsky6TI2VAConfig,
workload_types=(WorkloadType.T2V, WorkloadType.I2V),
hf_model_paths=[
"kandinskylab/Kandinsky-6.0-Pro-5s-Diffusers",
"kandinskylab/Kandinsky-6.0-Pro-sft-5s-Diffusers",
"kandinskylab/Kandinsky-6.0-Lite-5s-Diffusers",
],
model_detectors=[
_is_kandinsky6,
],
model_family="kandinsky6",
default_preset="kandinsky6_ti2va",
pipeline_cls_name="Kandinsky6TI2VAPipeline",
)
# 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",
# Zelda World Model
"mignonjia/mg_longtuning_distilled_zelda", # distilled using streaming long tuning which init from mg_sf_distilled_zelda_1k_steps
"mignonjia/mg_sf_distilled_zelda_1k_steps", # distilled using self forcing for 1k steps
"mignonjia/mg_sf_distilled_zelda", # distilled using self forcing for 3k steps
"mignonjia/mg_causal_zelda",
"mignonjia/mg_bidirectional_zelda",
],
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 DFD V2W (2B). Register before the generic Cosmos 2.5
# detector so converted directories whose names contain DFD select the
# fixed four-step I2V preset.
def _cosmos25_dfd_detector(path: str) -> bool:
# "dfd" is matched in the last path component only (like the
# Kandinsky-6 distilled marker above) so a parent directory that
# merely contains "dfd" cannot re-label a base Predict2.5 package.
path_lower = path.lower()
name = path_lower.rstrip("/").rsplit("/", 1)[-1]
tokens = ("cosmos25", "cosmos2_5", "cosmos2.5", "cosmos-predict2.5")
return "dfd" in name and any(token in path_lower for token in tokens)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=Cosmos25Config,
workload_types=(WorkloadType.I2V, ),
model_detectors=[_cosmos25_dfd_detector],
model_family="cosmos25",
default_preset="cosmos25_dfd_v2w_2b",
)
# 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",
)
_register_wan_configs(WAN_MODEL_DEFINITIONS)
# Pika H3 Flex. It reuses MiniMax-H3's components, but its six-forward
# re-noise sampler and bucket geometry are a separate family contract.
register_configs(
sampling_param_cls=PikaH3FlexSamplingParam,
pipeline_config_cls=PikaH3FlexPipelineConfig,
workload_types=(WorkloadType.T2V, WorkloadType.I2V),
hf_model_paths=["Pika-Labs/Pika-H3-Flex"],
model_detectors=[
lambda path: any(token in path.lower() for token in (
"pika-h3-flex",
"pika_h3_flex",
"pikah3flexmodularpipeline",
"pikah3flexref2vamodularpipeline",
)),
],
model_family="pika_h3_flex",
default_preset="pika_h3_flex_t2va",
pipeline_cls_name="PikaH3FlexModularPipeline",
)
# MiniMax H3
register_configs(
sampling_param_cls=None,
pipeline_config_cls=MiniMaxH3PipelineConfig,
workload_types=(WorkloadType.T2V, WorkloadType.I2V),
hf_model_paths=[
"MiniMaxAI/MiniMax-H3",
"FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2",
"FastVideo/FastVideo-FastH3-8-Step-V2",
],
model_detectors=[
lambda path: any(token in path.lower() for token in (
"minimax-h3",
"minimax_h3",
"fasth3",
"minimaxh3modularpipeline",
"minimaxh3ref2vamodularpipeline",
)),
],
model_family="minimax_h3",
default_preset="minimax_h3_t2va",
# FastH3 full checkpoints need not carry a Diffusers model_index.json;
# the native full-checkpoint loader still uses the standard H3 graph.
pipeline_cls_name="MiniMaxH3ModularPipeline",
)
# 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",
)
# FLUX.1-dev (Diffusers)
register_configs(
sampling_param_cls=FluxSamplingParam,
pipeline_config_cls=FluxPipelineConfig,
workload_types=(WorkloadType.T2I, ),
hf_model_paths=[
"black-forest-labs/FLUX.1-dev",
],
model_detectors=[
lambda path: "fluxpipeline" in path,
lambda path: "flux.1-dev" in path or "flux_1_dev" in path,
lambda path: "/flux/" in path or path.endswith("/flux"),
],
)
register_configs(
sampling_param_cls=None,
pipeline_config_cls=QwenImage21PipelineConfig,
workload_types=(WorkloadType.T2I, WorkloadType.I2I),
hf_model_paths=["Qwen/Qwen-Image-2.1"],
model_detectors=[
lambda path: "qwenimage21pipeline" in path or "qwen-image-2.1" in path or "qwen_image21" in path,
],
model_family="qwen_image21",
default_preset="qwen_image21",
pipeline_cls_name="QwenImage21Pipeline",
)
# --- 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,
revision: 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, revision=revision)
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, required_component_dirs=[])
else:
config = maybe_download_model_index(model_path, revision=revision)
pipeline_name = _resolve_class_name(config)
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, *, allow_remote: bool = True) -> Any | None:
config_info = _get_config_info(pipeline_name_or_path, raise_on_missing=False, allow_remote=allow_remote)
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.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.helios.presets import (
ALL_PRESETS as HELIOS_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.kandinsky6.presets import (
ALL_PRESETS as KANDINSKY6_PRESETS, )
from fastvideo.pipelines.basic.kandinsky6_sr.presets import (
ALL_PRESETS as KANDINSKY6_SR_PRESETS, )
from fastvideo.pipelines.basic.lingbotworld.presets import (
ALL_PRESETS as LINGBOTWORLD_PRESETS, )
from fastvideo.pipelines.basic.lingbotworld2.presets import (
ALL_PRESETS as LINGBOTWORLD2_PRESETS, )
from fastvideo.pipelines.basic.lingbotworld_fast.presets import (
ALL_PRESETS as LINGBOTWORLD_FAST_PRESETS, )
from fastvideo.pipelines.basic.lingbot_video.presets import (
ALL_PRESETS as LINGBOT_VIDEO_PRESETS, )
from fastvideo.pipelines.basic.ltx2.presets import (
ALL_PRESETS as LTX2_PRESETS, )
from fastvideo.pipelines.basic.magi_human.presets import (
ALL_PRESETS as MAGI_HUMAN_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.minimax_h3.presets import (
ALL_PRESETS as MINIMAX_H3_PRESETS, )
from fastvideo.pipelines.basic.pika_h3_flex.presets import (
ALL_PRESETS as PIKA_H3_FLEX_PRESETS, )
from fastvideo.pipelines.basic.mmaudio.presets import (
ALL_PRESETS as MMAUDIO_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.wan.presets import (
ALL_PRESETS as WAN_PRESETS, )
from fastvideo.pipelines.basic.qwen_image21.presets import (
ALL_PRESETS as QWEN_IMAGE21_PRESETS, )
from fastvideo.pipelines.basic.flux_2.presets import (
ALL_PRESETS as FLUX2_PRESETS, )
all_preset_groups = (
COSMOS_PRESETS,
FLUX2_PRESETS,
GEN3C_PRESETS,
HUNYUAN_PRESETS,
HUNYUAN15_PRESETS,
HELIOS_PRESETS,
HYWORLD_PRESETS,
KANDINSKY5_PRESETS,
KANDINSKY6_PRESETS,
KANDINSKY6_SR_PRESETS,
LINGBOT_VIDEO_PRESETS,
LINGBOTWORLD_PRESETS,
LINGBOTWORLD2_PRESETS,
LINGBOTWORLD_FAST_PRESETS,
LTX2_PRESETS,
MAGI_HUMAN_PRESETS,
MATRIXGAME2_PRESETS,
MATRIXGAME3_PRESETS,
MINIMAX_H3_PRESETS,
PIKA_H3_FLEX_PRESETS,
MMAUDIO_PRESETS,
SD35_PRESETS,
STABLE_AUDIO_PRESETS,
WAN_PRESETS,
QWEN_IMAGE21_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",
]