Files
hao-ai-lab-FastVideo/v2/registry.py
T
SolitaryThinker 4e331eb7ff [refactor] v2: use absolute imports (v2.*) everywhere instead of relative
Mechanical conversion of every relative import under v2/ to an absolute v2.* path
(from .x / ..x / ...x -> from v2.<pkg>.x) so imports are unambiguous, grep-able, and
stable when code is copied/moved between entrypoints (VideoGenerator, CLI, server).

Surgical prefix-only rewrite: only the 'from <dots><module>' prefix changed — import
names, parentheses, multi-line formatting, comments, and ordering are byte-for-byte
preserved (no collapsing, no reorder, no unrelated reformatting).

- 471 imports across 125 files; v2/tests/ was already absolute (untouched).
- Validated: all 125 files compile, every 'from v2.* import' target resolves to a real
  module/package, zero relative imports remain (full sweep), CPU mini suite green
  (202 passed, 2 skipped).
2026-07-04 17:15:34 +00:00

157 lines
8.1 KiB
Python

"""v2 model registry — the SHARED resolution of a model (HF repo id or local checkpoint dir) to its
``(build_card, build_program)`` builders, so every v2 entrypoint (the typed ``VideoGenerator``, a CLI,
the server) uses one source of truth instead of each re-deriving the mapping.
Mirrors fastvideo's ``fastvideo/registry.py`` hybrid resolution:
1. exact HF repo id in the explicit registry — PRIMARY: correct per-model card + capabilities, and
the only way to split same-architecture *capability*
variants (e.g. a Wan2.1 T2V base vs the i2v "InP"
1.3B) that pure architecture inference cannot tell apart.
2. short repo-name match — a renamed / forked copy of a known repo.
3. architecture inference from the checkpoint — FALLBACK: local paths / unregistered repos, keyed on
the pipeline / transformer / VAE class names.
Adding a model = one ``ModelEntry`` below (its HF ids -> builders). New architectures (for the fallback)
are added in ``select_by_architecture``. All builders are CPU-clean v2 submodules imported lazily, so
``import v2.registry`` stays cheap and torch-free.
"""
from __future__ import annotations
import json
import os
from dataclasses import dataclass
from typing import Any, Callable
@dataclass(frozen=True)
class ModelEntry:
"""A registered model family: the exact HF repo ids it serves + its card/program builders."""
hf_ids: tuple[str, ...]
build_card: Callable[[], Any]
build_program: Callable[[], Any]
def _entries() -> list[ModelEntry]:
"""The explicit registry (PRIMARY). Builders imported lazily to avoid import cycles with the model
packages and keep importing this module cheap."""
from v2.models.ltx2 import (
build_ltx2_3_card,
build_ltx2_3_program,
build_ltx2_base_card,
build_ltx2_base_program,
build_ltx2_card,
build_ltx2_program,
)
from v2.models.wan_causal import build_wan_causal_card, build_wan_causal_program
from v2.models.wan21 import (
build_wan21_card,
build_wan22_a14b_card,
build_wan22_ti2v_card,
build_wan_t2v_program,
)
return [
ModelEntry(("Wan-AI/Wan2.1-T2V-1.3B-Diffusers",), build_wan21_card, build_wan_t2v_program),
ModelEntry(("Wan-AI/Wan2.2-TI2V-5B-Diffusers",), build_wan22_ti2v_card, build_wan_t2v_program),
ModelEntry(("Wan-AI/Wan2.2-T2V-A14B-Diffusers",), build_wan22_a14b_card, build_wan_t2v_program),
ModelEntry(("wlsaidhi/SFWan2.1-T2V-1.3B-Diffusers",), build_wan_causal_card, build_wan_causal_program),
# LTX-2 two-stage distilled (ships a spatial_upsampler: base -> upsample -> refine):
ModelEntry(("FastVideo/LTX2-Distilled-Diffusers",), build_ltx2_card, build_ltx2_program),
# LTX-2 single-stage, non-distilled base (request-driven many-step, video-only):
ModelEntry(("Davids048/LTX2-Base-Diffusers",), build_ltx2_base_card, build_ltx2_base_program),
# LTX-2.3 distilled — single-stage, JOINT text->video+audio (separate connectors + gated attn):
ModelEntry(("FastVideo/LTX-2.3-Distilled-Diffusers",), build_ltx2_3_card, build_ltx2_3_program),
]
def _short(p: str) -> str:
return p.rstrip("/").split("/")[-1].lower()
def read_arch_signature(root: str) -> dict:
"""Read a diffusers checkpoint's configs into an architecture signature (the FALLBACK dispatch
keys): pipeline / transformer / VAE class names + the fields that split same-class variants
(VAE ``z_dim``, a second transformer / ``boundary_ratio`` for MoE, a ``spatial_upsampler``).
``root`` is a local diffusers dir or a snapshot of just the ``*.json`` configs."""
def _load(*parts: str) -> dict:
p = os.path.join(root, *parts)
if os.path.exists(p):
with open(p) as f:
return json.load(f)
return {}
mi = _load("model_index.json")
tcfg = _load("transformer", "config.json")
vcfg = _load("vae", "config.json")
return {
"pipeline": mi.get("_class_name"),
"boundary_ratio": mi.get("boundary_ratio"),
"has_transformer_2": os.path.isdir(os.path.join(root, "transformer_2")),
"has_spatial_upsampler": os.path.isdir(os.path.join(root, "spatial_upsampler")) or "spatial_upsampler" in mi,
"transformer_cls": tcfg.get("_class_name"),
"in_channels": tcfg.get("in_channels"),
"vae_z_dim": vcfg.get("z_dim", vcfg.get("latent_channels")),
}
def select_by_architecture(sig: dict):
"""FALLBACK: map an architecture signature -> (build_card, build_program) by class names, so a local
path / renamed repo / new distilled variant of a known arch still resolves with no registry entry."""
tr, pipe = sig.get("transformer_cls"), sig.get("pipeline")
if tr == "LTX2Transformer3DModel":
from v2.models.ltx2 import (
build_ltx2_base_card,
build_ltx2_base_program,
build_ltx2_card,
build_ltx2_program,
)
if sig.get("has_spatial_upsampler"):
return build_ltx2_card, build_ltx2_program # two-stage (base -> upsample -> refine)
return build_ltx2_base_card, build_ltx2_base_program # single-stage
if tr == "CausalWanTransformer3DModel":
from v2.models.wan_causal import build_wan_causal_card, build_wan_causal_program
return build_wan_causal_card, build_wan_causal_program
if tr == "WanTransformer3DModel":
from v2.models.wan21 import (
build_wan21_card,
build_wan22_a14b_card,
build_wan22_ti2v_card,
build_wan_t2v_program,
)
if pipe == "WanDMDPipeline": # FastWan: detected by pipeline class -> precise, not a load crash
raise ValueError(
"v2 registry: WanDMD/FastWan is not supported via the generic Wan path — its checkpoint's "
"to_gate_compress param mapping differs from the generic WanTransformer3DModel load. See "
"examples/inference/basic/V2_PORTING_STATUS.md.")
if sig.get("has_transformer_2") or sig.get("boundary_ratio"):
return build_wan22_a14b_card, build_wan_t2v_program # Wan2.2 MoE (two experts)
if sig.get("vae_z_dim") == 48:
return build_wan22_ti2v_card, build_wan_t2v_program # Wan2.2-TI2V-5B (z_dim=48 VAE)
return build_wan21_card, build_wan_t2v_program # Wan2.1
raise ValueError(
f"v2 registry: unsupported architecture (transformer={tr!r}, pipeline={pipe!r}). Supported "
f"transformers: WanTransformer3DModel / CausalWanTransformer3DModel / LTX2Transformer3DModel. "
f"See examples/inference/basic/V2_PORTING_STATUS.md.")
def resolve(model_path: str, root: str | None = None):
"""Resolve ``model_path`` (an HF repo id or a local checkpoint dir) -> ``(build_card, build_program)``.
PRIMARY: an exact HF-id, then a short repo-name match, in the explicit registry. FALLBACK: when
``model_path`` isn't registered, architecture inference from ``root`` (a local dir or a downloaded
``*.json`` config snapshot). Raises ``ValueError`` if unregistered with ``root=None`` (so the caller
knows to fetch the configs), or if the architecture is unsupported. Shared by every v2 entrypoint."""
entries = _entries()
for e in entries: # 1. exact HF id
if model_path in e.hf_ids:
return e.build_card, e.build_program
short = _short(model_path) # 2. short repo name (renamed / forked copy)
for e in entries:
if any(_short(h) == short for h in e.hf_ids):
return e.build_card, e.build_program
if root is None: # 3. architecture inference (needs the configs)
raise ValueError(
f"v2 registry: {model_path!r} is not registered; pass the checkpoint root for architecture "
f"inference, or add a ModelEntry in v2/registry.py.")
return select_by_architecture(read_arch_signature(root))