diff --git a/fastvideo2/registry.py b/fastvideo2/registry.py index 263ccb6ee..75eef88c7 100644 --- a/fastvideo2/registry.py +++ b/fastvideo2/registry.py @@ -1,16 +1,13 @@ """The catalog — one place that maps names to (card, pipeline builder). -Identity policy (three levels): +Identity policy (two levels, nothing else resolves): -* ``model_id`` is the PRIMARY identity — it names a *servable* (weights plus - loop semantics, precision regime, conventions, defaults). One HF repo can - back many cards (base / distilled / precision variants) and one card can - draw from many repos (the aligned Wan card loads the official-layout DiT - and the diffusers-layout encoder/VAE), so repo strings cannot be identities. -* HF repo strings are *ingredients* (``card.weights``, ``ComponentSpec.source``) - accepted here as a convenience alias — but only while unambiguous. As soon - as two cards reference the same repo, resolving by that repo fails with the - candidate list; it never picks silently. +* ``model_id`` is THE identity — it names a *servable* (weights plus loop + semantics, precision regime, conventions, defaults). HF repo strings are + ingredients on the card (``card.weights``, ``ComponentSpec.source``), never + load keys: one repo can back many cards (base / distilled / precision + variants) and one card can draw from many repos (the aligned Wan card loads + the official-layout DiT and the diffusers-layout encoder/VAE). * ``card.digest()`` is the machine identity: evidence records, T1 baselines, and environment manifests key on it, never on names. @@ -33,32 +30,10 @@ PIPELINES: dict[str, Callable[[], Any]] = { } -def _build_aliases(cards: dict[str, Any]) -> dict[str, list[str]]: - """HF repo string -> model_ids referencing it (as card weights or as a - per-component source). Values with several ids are ambiguous aliases.""" - out: dict[str, list[str]] = {} - for card in cards.values(): - refs = {card.weights} | {s.source for s in card.components.values() if s.source} - for ref in sorted(refs): - ids = out.setdefault(ref, []) - if card.model_id not in ids: - ids.append(card.model_id) - return out - - -_ALIASES = _build_aliases(CARDS) - - -def resolve(name: str) -> tuple[ModelCard, Callable[[], Any]]: - """model_id (primary), or an unambiguous HF repo alias -> (card, pipeline - builder).""" - if name in CARDS: - return CARDS[name], PIPELINES[name] - hits = _ALIASES.get(name, []) - if len(hits) == 1: - return CARDS[hits[0]], PIPELINES[hits[0]] - if hits: - raise KeyError(f"{name!r} is a weights repo referenced by several cards " - f"{hits} — load by model_id") - raise KeyError(f"unknown model {name!r}; known ids: {sorted(CARDS)}; " - f"known repo aliases: {sorted(_ALIASES)}") +def resolve(model_id: str) -> tuple[ModelCard, Callable[[], Any]]: + """model_id -> (card, pipeline builder). Only model ids resolve — pass an + HF repo string and you get the catalog, not a guess.""" + if model_id not in CARDS: + raise KeyError(f"unknown model {model_id!r}; known ids: {sorted(CARDS)} " + f"(HF repo strings are card ingredients, not load keys)") + return CARDS[model_id], PIPELINES[model_id] diff --git a/fastvideo2/sdk.py b/fastvideo2/sdk.py index a07b745bb..4e1b79b52 100644 --- a/fastvideo2/sdk.py +++ b/fastvideo2/sdk.py @@ -117,8 +117,9 @@ class Model: f"digest={self.card.digest()})") -def load(model: str, *, root: str | None = None, device: str | None = None) -> Model: - """Resolve a model id or HF repo id, load it resident, return the handle.""" +def load(model_id: str, *, root: str | None = None, device: str | None = None) -> Model: + """Resolve a model id from the catalog, load it resident, return the + handle. HF repo strings are card ingredients, not load keys.""" from fastvideo2.registry import resolve - card, build_pipeline = resolve(model) + card, build_pipeline = resolve(model_id) return Model(card, build_pipeline(), _load_instance(card, root=root, device=device)) diff --git a/fastvideo2/tests/test_registry.py b/fastvideo2/tests/test_registry.py index f009c8132..81786a0a4 100644 --- a/fastvideo2/tests/test_registry.py +++ b/fastvideo2/tests/test_registry.py @@ -1,9 +1,7 @@ -"""T0: the identity policy — model_id primary, HF repos as unambiguous aliases.""" -from types import SimpleNamespace - +"""T0: the identity policy — model_id is the only load key.""" import pytest -from fastvideo2.registry import CARDS, _build_aliases, resolve +from fastvideo2.registry import CARDS, PIPELINES, resolve from fastvideo2.wan21 import WAN21_T2V_1_3B @@ -12,31 +10,16 @@ def test_resolve_by_model_id(): assert card is WAN21_T2V_1_3B and callable(build) -def test_resolve_by_weights_repo_alias(): - card, _ = resolve("Wan-AI/Wan2.1-T2V-1.3B-Diffusers") - assert card.model_id == "wan2.1-t2v-1.3b" +def test_hf_repo_strings_do_not_resolve(): + for repo in ("Wan-AI/Wan2.1-T2V-1.3B-Diffusers", "Wan-AI/Wan2.1-T2V-1.3B"): + with pytest.raises(KeyError, match="not load keys"): + resolve(repo) -def test_resolve_by_component_source_alias(): - # the official-layout DiT repo is an ingredient of the same card - card, _ = resolve("Wan-AI/Wan2.1-T2V-1.3B") - assert card.model_id == "wan2.1-t2v-1.3b" - - -def test_unknown_name_lists_ids_and_aliases(): +def test_unknown_id_lists_catalog(): with pytest.raises(KeyError, match="known ids"): - resolve("no/such-model") - - -def test_ambiguous_repo_alias_fails_closed(): - def fake(mid): - spec = SimpleNamespace(source="") - return SimpleNamespace(model_id=mid, weights="acme/shared-repo", - components={"c": spec}) - aliases = _build_aliases({"a": fake("card-a"), "b": fake("card-b")}) - assert aliases["acme/shared-repo"] == ["card-a", "card-b"] # resolve() would error + resolve("no-such-model") def test_every_card_id_has_a_pipeline(): - from fastvideo2.registry import PIPELINES assert set(CARDS) == set(PIPELINES)