Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions invokeai/backend/architectures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
resolve_latent_space,
)
from invokeai.backend.architectures.facets.unet import UNetDownscaleFacet, get_max_unet_downscale
from invokeai.backend.architectures.facets.variant import (
VariantFacet,
declared_variant_enums,
get_variant_enum,
)
from invokeai.backend.architectures.registry import (
ArchitectureError,
defs_module_path,
Expand All @@ -56,12 +61,15 @@
"LatentSpace",
"LatentSpaceFacet",
"UNetDownscaleFacet",
"VariantFacet",
"declared_variant_enums",
"defs_module_path",
"facets_of",
"generative_bases",
"get",
"get_latent_space",
"get_max_unet_downscale",
"get_variant_enum",
"register",
"require",
"resolve_latent_space",
Expand Down
14 changes: 13 additions & 1 deletion invokeai/backend/architectures/defs/flux.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
from invokeai.backend.architectures.facets.latent_space import FLUX_16, LatentSpaceFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import (
BaseModelType,
FluxVariantType,
ModelType,
PiDDecoderVariantType,
)

register(
BaseModelType.Flux,
LatentSpaceFacet(FLUX_16),
VariantFacet(
{
ModelType.Main: FluxVariantType,
ModelType.PiDDecoder: PiDDecoderVariantType,
}
),
)
16 changes: 15 additions & 1 deletion invokeai/backend/architectures/defs/flux2.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
from invokeai.backend.architectures.facets.latent_space import FLUX2_32, LatentSpaceFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import (
BaseModelType,
Flux2VariantType,
ModelType,
PiDDecoderVariantType,
)

register(
BaseModelType.Flux2,
LatentSpaceFacet(FLUX2_32),
VariantFacet(
{
# FLUX.2 LoRAs are labelled with the same variant enum as the mains they target.
ModelType.Main: Flux2VariantType,
ModelType.LoRA: Flux2VariantType,
ModelType.PiDDecoder: PiDDecoderVariantType,
}
),
)
6 changes: 5 additions & 1 deletion invokeai/backend/architectures/defs/krea_2.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from invokeai.backend.architectures.facets.latent_space import WAN21_16, LatentSpaceFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import BaseModelType, Krea2VariantType, ModelType

register(
BaseModelType.Krea2,
# Krea-2 decodes with the Qwen-Image VAE, which is the Wan 2.1 VAE (16 latent channels), so it
# shares the preview factors.
LatentSpaceFacet(WAN21_16),
# The values are krea2_turbo / krea2_base rather than turbo / base, because variant strings are
# resolved without base context in configs/factory.py and so must be globally unique.
VariantFacet({ModelType.Main: Krea2VariantType}),
)
14 changes: 13 additions & 1 deletion invokeai/backend/architectures/defs/qwen_image.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
from invokeai.backend.architectures.facets.latent_space import WAN21_16, LatentSpaceFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import (
BaseModelType,
ModelType,
PiDDecoderVariantType,
QwenImageVariantType,
)

register(
BaseModelType.QwenImage,
# Qwen-Image decodes with the 16-channel Wan 2.1 VAE.
LatentSpaceFacet(WAN21_16),
VariantFacet(
{
ModelType.Main: QwenImageVariantType,
ModelType.PiDDecoder: PiDDecoderVariantType,
}
),
)
4 changes: 3 additions & 1 deletion invokeai/backend/architectures/defs/sd_1.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from invokeai.backend.architectures.facets.latent_space import SD15_4, LatentSpaceFacet
from invokeai.backend.architectures.facets.unet import UNetDownscaleFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelType, ModelVariantType

register(
BaseModelType.StableDiffusion1,
LatentSpaceFacet(SD15_4),
UNetDownscaleFacet(max_unet_downscale=8),
VariantFacet({ModelType.Main: ModelVariantType}),
)
4 changes: 3 additions & 1 deletion invokeai/backend/architectures/defs/sd_2.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from invokeai.backend.architectures.facets.latent_space import SD15_4, LatentSpaceFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelType, ModelVariantType

register(
BaseModelType.StableDiffusion2,
# SD2 shares SD1's 4-channel latent space and preview factors.
LatentSpaceFacet(SD15_4),
VariantFacet({ModelType.Main: ModelVariantType}),
)
5 changes: 4 additions & 1 deletion invokeai/backend/architectures/defs/sd_3.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from invokeai.backend.architectures.facets.latent_space import SD3_16, LatentSpaceFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelType, PiDDecoderVariantType

register(
BaseModelType.StableDiffusion3,
LatentSpaceFacet(SD3_16),
# SD3 mains carry no variant; only its PiD decoder checkpoints do.
VariantFacet({ModelType.PiDDecoder: PiDDecoderVariantType}),
)
14 changes: 13 additions & 1 deletion invokeai/backend/architectures/defs/sdxl.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
from invokeai.backend.architectures.facets.latent_space import SDXL_4, LatentSpaceFacet
from invokeai.backend.architectures.facets.unet import UNetDownscaleFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import (
BaseModelType,
ModelType,
ModelVariantType,
PiDDecoderVariantType,
)

register(
BaseModelType.StableDiffusionXL,
LatentSpaceFacet(SDXL_4),
UNetDownscaleFacet(max_unet_downscale=4),
VariantFacet(
{
ModelType.Main: ModelVariantType,
ModelType.PiDDecoder: PiDDecoderVariantType,
}
),
)
4 changes: 3 additions & 1 deletion invokeai/backend/architectures/defs/sdxl_refiner.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from invokeai.backend.architectures.facets.latent_space import SDXL_4, LatentSpaceFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelType, ModelVariantType

register(
BaseModelType.StableDiffusionXLRefiner,
# The refiner shares SDXL's latent space, smooth matrix included.
LatentSpaceFacet(SDXL_4),
VariantFacet({ModelType.Main: ModelVariantType}),
)
17 changes: 16 additions & 1 deletion invokeai/backend/architectures/defs/wan.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
from invokeai.backend.architectures.facets.latent_space import WAN21_16, WAN22_48, LatentSpaceFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import (
BaseModelType,
ModelType,
WanLoRAVariantType,
WanVariantType,
)

register(
BaseModelType.Wan,
# The only architecture with more than one latent space. A14B uses the standard 16-channel Wan
# VAE at 8x spatial; TI2V-5B uses the 48-channel Wan2.2-VAE at 16x. The latent channel count
# uniquely identifies the variant, which is how `LatentSpaceFacet.resolve()` tells them apart.
LatentSpaceFacet(WAN21_16, alternates=(WAN22_48,)),
# The one architecture whose LoRAs carry a different variant enum from its mains. They are not
# interchangeable: an A14B LoRA (inner_dim=5120) against a TI2V-5B main (3072) crashes in the
# layer patcher, which is why the LoRA enum exists separately at all.
VariantFacet(
{
ModelType.Main: WanVariantType,
ModelType.LoRA: WanLoRAVariantType,
}
),
)
9 changes: 8 additions & 1 deletion invokeai/backend/architectures/defs/z_image.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
from invokeai.backend.architectures.facets.latent_space import FLUX_16, LatentSpaceFacet
from invokeai.backend.architectures.facets.variant import VariantFacet
from invokeai.backend.architectures.registry import register
from invokeai.backend.model_manager.taxonomy import BaseModelType
from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelType, ZImageVariantType

register(
BaseModelType.ZImage,
# Z-Image uses a FLUX-compatible VAE with 16 latent channels.
LatentSpaceFacet(FLUX_16),
VariantFacet(
{
ModelType.Main: ZImageVariantType,
ModelType.LoRA: ZImageVariantType,
}
),
)
69 changes: 69 additions & 0 deletions invokeai/backend/architectures/facets/variant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Which variant enum labels an architecture's models, keyed by model type.

Base alone does not determine it, which is why this facet carries a mapping rather than a single
enum:

- Wan mains carry `WanVariantType`, Wan LoRAs carry `WanLoRAVariantType`. Same base, different
enum, and they are not interchangeable -- applying an A14B LoRA to a TI2V-5B main crashes in the
layer patcher.
- The PiD decoder's resolution presets are one enum shared across five bases.

Two variant enums cannot live here at all: `ClipVariantType` and `Qwen3VariantType` sit on
`base=Any` configs, and `Any` is a sentinel the registry refuses to register. They are named
explicitly in `tests/backend/architectures/test_variants.py` so the completeness check against
`AnyVariant` stays total rather than quietly partial.

This facet is deliberately *not* wired into `configs/factory.py`. That module validates a bare
variant string against `variant_type_adapter` without passing the base (`build_common_fields`), so
variant values have to be globally unique -- which is why `Krea2VariantType.Turbo` is
``krea2_turbo`` rather than ``turbo``. Making resolution base-aware would still need a fallback for
the `base=Any` models, so it would move the trap rather than close it, at the cost of touching the
identification path. The invariant is pinned by a test instead.
"""

from collections.abc import Mapping
from dataclasses import dataclass
from enum import Enum

from invokeai.backend.architectures.facet import Facet
from invokeai.backend.architectures.registry import generative_bases, get
from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelType


@dataclass(frozen=True)
class VariantFacet(Facet):
"""The variant enums an architecture's models are labelled with, by model type.

Optional: four registered architectures (CogView4, ERNIE-Image, Ideogram 4, Anima) model no
variants at all, and declaring an empty facet would be indistinguishable from declaring nothing.
"""

by_model_type: Mapping[ModelType, type[Enum]]

def __post_init__(self) -> None:
if not self.by_model_type:
raise ValueError(
"VariantFacet declares no model types. An architecture without variants omits the facet entirely."
)


def get_variant_enum(base: BaseModelType, model_type: ModelType) -> type[Enum] | None:
"""The variant enum for this base and model type, or None if that combination has no variants."""
facet = get(base, VariantFacet)
if facet is None:
return None
return facet.by_model_type.get(model_type)


def declared_variant_enums() -> frozenset[type[Enum]]:
"""Every variant enum declared by any registered architecture.

The registry side of the CI guards that keep `AnyVariant`, `variant_type_adapter` and
`ModelRecordChanges.variant` -- four hand-maintained copies of one list -- from drifting apart.
"""
enums: set[type[Enum]] = set()
for base in generative_bases():
facet = get(base, VariantFacet)
if facet is not None:
enums.update(facet.by_model_type.values())
return frozenset(enums)
Loading
Loading