Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Model Format Support
description: Which file formats and quantizations InvokeAI loads for every model family, text encoder, VAE and adapter.
lastUpdated: 2026-10-10
lastUpdated: 2026-10-11
sidebar:
order: 3
---
Expand All @@ -20,7 +20,7 @@ speeds are on each family's own page.
- – no such build exists

How an unsupported file fails depends on the file. Some are not recognized at all and install as an unknown model,
with the reason only in the server log: the Diffusers layout of LTX-2 and the GGUF builds of LTX-2, MiniMax H3 and the
with the reason only in the server log: the Diffusers layout of LTX-2 and the GGUF builds of MiniMax H3 and the
Ministral 3B encoder. Others are recognized by their layer names and fail, or misbehave, only when they load.

## Main models
Expand All @@ -37,18 +37,18 @@ Ministral 3B encoder. Others are recognized by their layer names and fail, or mi
| [Anima](/users-guide/models/local-models/anima/) | – | ✓ | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ |
| [Krea-2](/users-guide/models/local-models/krea-2/) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ |
| [Ideogram 4](/users-guide/models/local-models/ideogram4/) | ✓ | ✓ | ✓ | ✓ | R | ✓ | ✓ | ✗ | inside the Diffusers folder |
| [ERNIE-Image](/users-guide/models/local-models/ernie-image/) | ✓ | ✓ | L | L | L | ✗ | ✗ | ✗ | ✗ |
| [ERNIE-Image](/users-guide/models/local-models/ernie-image/) | ✓ | ✓ | L | L | L | ✗ | ✓ | ✗ | ✗ |
| [Wan 2.2](/users-guide/video-generation/wan-22/) | ✓ | ✓ | ✓ | L | L | L | ✓ | ✗ | ✗ |
| [LTX-2](/users-guide/video-generation/ltx-2/) (2.5 only) | official layout ✓, Diffusers layout ✗ | ✓ | L | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ |
| [LTX-2](/users-guide/video-generation/ltx-2/) (2.5 only) | official layout ✓, Diffusers layout ✗ | ✓ | L | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ |
| [MiniMax H3](/users-guide/video-generation/minimax-h3/) | ✓ | ✓ | L | ✓ | L | ✗ | ✗ | ✗ | ✗ |

- **int8** means ComfyUI's `int8_convrot` (`int8_tensorwise`) builds. **nvfp4** and **fp8 scaled** mean ComfyUI's
checkpoint layouts; other exports of the same schemes (AWQ, ModelOpt) fail when the model loads.
- **GGUF** covers the llama.cpp types (Q4_0 through Q8_0, the K-quants, BF16). ComfyUI-GGUF's `Q8_CR` files load
for Krea-2 only; for every other model with GGUF support the install fails.
- LTX-2.0/2.3 files fail to install with a message. Wan 2.1, Wan Animate, S2V, Fun-Control and VACE single files and
GGUFs are not recognized as Wan 2.2 and install as unknown models; a Wan 2.1 Diffusers folder is not detected and
is not supported either.
- LTX-2.0/2.3 files, GGUF builds included, fail to install with a message. Wan 2.1, Wan Animate, S2V, Fun-Control
and VACE single files and GGUFs are not recognized as Wan 2.2 and install as unknown models; a Wan 2.1 Diffusers
folder is not detected and is not supported either.

## Text encoders

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: LTX-2
lastUpdated: 2026-10-03
lastUpdated: 2026-10-11
alsoListedIn:
- users-guide/models/local-models
sidebar:
Expand Down Expand Up @@ -135,6 +135,12 @@ Available separately in the starter models list:
* **LTX-2.5 Duration Head** (3.6 MB) — enables [Auto duration](#auto-duration). License-gated: needs a Hugging Face token whose account has accepted the LTX-2.5 license.
* **LTX-2.5 Prompt Enhancer (Gemma-4 E2B) (~10 GB) - Enables prompt enhancement.

**GGUF builds.** Community GGUF conversions of the LTX-2.5 transformer, such as `vantagewithai/LTX-2.5-GGUF` (Dev and Distilled; its Q4_K_M builds are in the starter models list), `Abiray/LTX-2.5-Distilled-GGUF` or `realrebelai/LTX-2.5_GGUFs`, run like the int8 single files: select one in the Video panel, with the LTX-2.5 Components in the **Model components** slot and the Gemma-4 encoder in its slot. The text connectors each build carries beside the transformer are not used; the components folder supplies them. Dev or Distilled is read from the file name, so keep the published names, or set **Variant** in the model's **Edit** view after renaming a file.

On the card, a Q4_K_M build holds ~12.5 GiB against ~18 GiB for int8, and Q8_0 ~19.2 GiB. On an RTX 4090 at 1248×704×121 frames (Distilled, 8 steps) Q4_K_M stayed fully resident and ran at 6.1 s/step against 5.8–6.0 s/step for int8, which ran 94 % resident; Q8_0 ran at 5.45 s/step. At small sizes the gap widens, because unpacking the 4- to 6-bit weights costs the same every step however short the clip: at 896×512×49 frames Q4_K_M ran at 1.87 s/step, int8 at 1.64 and Q8_0 at 1.28. At the same seed, Q8_0 renders nearly the same video as int8; Q4_K_M can frame the scene differently.

Two kinds of GGUF are refused when you install them: LTX-2.3 builds (`unsloth/LTX-2.3-GGUF`, `QuantStack/LTX-2.3-GGUF`), as every LTX-2.3 file is, and ComfyUI-GGUF `Q8_CR` builds; use a Q8_0 build or the int8 single file instead. A GGUF installed with an earlier version of Invoke was registered as an unknown model: select it in the Model Manager and use **Re-identify model**. Installing a GGUF reads the whole file into RAM once, so for a moment it needs about its own size in free RAM.

LTX-2 does not condition on the last hidden state of its text encoder the way most models do: it stacks the hidden states of *every* Gemma layer and runs them through a separate connector model. That is why the encoder is a large, separate download.

Measured on a 48 GB card at 1248×704×121 frames, the int8 Dev transformer is resident at ~18 GiB and the denoise working set peaks at ~2.8 GiB, with all four guidance passes running. The Distilled build at the same size needs the same memory and about a fifteenth of the time.
Expand Down
16 changes: 8 additions & 8 deletions invokeai/app/invocations/ltx2/ltx2_denoise.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,9 +543,9 @@ def invoke(self, context: InvocationContext) -> LTX2DenoiseOutput:
# that while holding this one is the deadlock `apply_smart_model_patches` documents. It also
# has to happen before the reservation below, which is sized from these patches.
lora_patch_specs = self._materialize_lora_patches(context)
# Resolved from the unlocked model, like the dequant transient below: only a build whose
# weights are buffers rather than parameters takes the sidecar path, and only that path
# leaves patch tensors on the device.
# Resolved from the unlocked model, like the dequant transient below: only a quantized build
# (GGUF, or weights held as buffers rather than parameters) takes the sidecar path, and only
# that path leaves patch tensors on the device.
# Walks the whole 22B module tree, so it is only asked when there is something to patch.
force_sidecar = bool(lora_patch_specs) and requires_sidecar_patching(
transformer_info.model, transformer_config.format
Expand All @@ -563,9 +563,9 @@ def invoke(self, context: InvocationContext) -> LTX2DenoiseOutput:
# distilled accelerator (8.3 GiB) would cost a rank-16 style LoRA the same.
sum(spec[0].calc_size() for spec in lora_patch_specs) if force_sidecar else 0,
)
# An int8-convrot build materializes each linear's dequantized weight inside the forward,
# which the model's resident size does not account for. Read from the unlocked model, before
# the VRAM lock the reservation applies to; zero on a bf16 build.
# An int8-convrot, nvfp4 or GGUF build materializes each linear's dequantized weight inside the
# forward, which the model's resident size does not account for. Read from the unlocked model,
# before the VRAM lock the reservation applies to; zero on a bf16 build.
estimated_working_memory += peak_dequant_transient_bytes(transformer_info.model, inference_dtype)

with (
Expand All @@ -585,8 +585,8 @@ def invoke(self, context: InvocationContext) -> LTX2DenoiseOutput:
cached_weights=cached_weights,
# An int8-convrot / nvfp4 build keeps its weights as buffers rather than
# parameters, so the patcher's own fp8 and CPU fallbacks would answer "not
# quantized" and merge the delta into a weight that is never read. This is the
# only reliable signal.
# quantized" and merge the delta into a weight that is never read; a GGUF
# weight stays packed. This is the only reliable signal.
force_sidecar_patching=force_sidecar,
)
if lora_patch_specs
Expand Down
22 changes: 15 additions & 7 deletions invokeai/backend/model_manager/configs/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
Main_GGUF_FLUX_Config,
Main_GGUF_Ideogram4_Config,
Main_GGUF_Krea2_Config,
Main_GGUF_LTX2_Config,
Main_GGUF_QwenImage_Config,
Main_GGUF_Wan_Config,
Main_GGUF_ZImage_Config,
Expand Down Expand Up @@ -200,7 +201,7 @@
ModelType,
variant_type_adapter,
)
from invokeai.backend.quantization.gguf.loaders import parse_q8_cr_markers
from invokeai.backend.quantization.gguf.loaders import COMFY_QUANT_KEY_PREFIX, parse_q8_cr_markers

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -435,6 +436,7 @@ def has_model_export(module: Any, name: Any, expected_bases: tuple[type, ...]) -
Annotated[Main_GGUF_Krea2_Config, Main_GGUF_Krea2_Config.get_tag()],
Annotated[Main_GGUF_Ideogram4_Config, Main_GGUF_Ideogram4_Config.get_tag()],
Annotated[Main_GGUF_ErnieImage_Config, Main_GGUF_ErnieImage_Config.get_tag()],
Annotated[Main_GGUF_LTX2_Config, Main_GGUF_LTX2_Config.get_tag()],
# IMPORTANT: FLUX.2 must be listed BEFORE FLUX.1 here. An ambiguous SDNQ transformer
# checkpoint (prefixed FLUX.2 keys) can look like a FLUX.1 main model, so FLUX.2 must get
# first refusal. Main_SDNQ_FLUX_Config additionally rejects FLUX.2 state dicts to keep the
Expand Down Expand Up @@ -673,15 +675,21 @@ def _raise_for_unsupported_gguf_quantization(mod: ModelOnDisk, config: Config_Ba
"""
if getattr(config, "format", None) is not ModelFormat.GGUFQuantized:
return
metadata = mod.metadata()
if not type(config).DECODES_GGUF_Q8_CR:
# Nothing this config's loader runs decodes any of the converter's schemes, so whatever its
# JSON says is beside the point -- parsing it first would report a detail of the scheme (a
# marker on a tensor that is not a weight, say) in place of the reason the file is refused.
if any(key.startswith(COMFY_QUANT_KEY_PREFIX) for key in metadata):
raise InvalidMatchError(
f"{mod.path.name} uses ComfyUI-GGUF's own quantization (Q8_CR or similar), which this model "
"type cannot load yet. Use a GGML-quantized (Q8_0, Q4_K, ...) or safetensors build instead."
)
return
try:
markers = parse_q8_cr_markers(mod.metadata(), mod.path.name)
parse_q8_cr_markers(metadata, mod.path.name)
except ValueError as e:
raise InvalidMatchError(str(e)) from None
if markers and not type(config).DECODES_GGUF_Q8_CR:
raise InvalidMatchError(
f"{mod.path.name} is a ComfyUI-GGUF Q8_CR (int8 convrot) file, which this model type cannot load "
"yet. Use a GGML-quantized (Q8_0, Q4_K, ...) or safetensors build instead."
)

@staticmethod
def from_dict(fields: dict[str, Any]) -> AnyModelConfig:
Expand Down
81 changes: 64 additions & 17 deletions invokeai/backend/model_manager/configs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1867,12 +1867,16 @@ def _ltx2_generation_from_keys(stripped_keys: set[str]) -> str | None:

LTX-2.5 dropped the video feed-forward biases (2.0/2.3 keep them on both streams), and its video
VAE decoder gained a fourth upsampling stage (``up_blocks.8``; 2.0/2.3 stop at ``up_blocks.6``).
Either component identifies the generation on its own.
A transformer is dated either way: with the video biases it is one of the two earlier generations,
which its structure cannot tell apart -- reported as such, so the refusal names it. A VAE is dated
only by the 2.5 stage, because the earlier decoder's keys are shared with LTX-Video 0.9.
"""
if "transformer_blocks.0.ff.net.0.proj.weight" in stripped_keys:
video_ff_bias = "transformer_blocks.0.ff.net.2.bias" in stripped_keys
audio_ff_bias = "transformer_blocks.0.audio_ff.net.2.bias" in stripped_keys
return "2.5" if (not video_ff_bias and audio_ff_bias) else None
if not audio_ff_bias:
return None
return "2.0/2.3" if video_ff_bias else "2.5"
for prefix in ("", "vae."):
if f"{prefix}decoder.up_blocks.0.res_blocks.0.conv1.conv.weight" in stripped_keys:
return "2.5" if f"{prefix}decoder.up_blocks.8.res_blocks.0.conv1.conv.weight" in stripped_keys else None
Expand Down Expand Up @@ -2004,11 +2008,8 @@ class Main_Checkpoint_LTX2_Config(Checkpoint_Config_Base, Main_Config_Base, Conf
Covers the bf16, Comfy ``int8_tensorwise``(+convrot) and ``nvfp4`` releases; the quantization
scheme is read from the file's own markers by the loader, not recorded here. The file may hold
only the transformer (the 2.5 releases) or bundle other components beside it (earlier
generations) -- either way everything else comes from an installed LTX-2 component folder.

Dev and Distilled transformers are key-for-key identical, so the FILENAME is the variant
classifier and ``variant`` is the user-correctable override for renamed files. A misclassified
variant runs but samples with the wrong recipe (guided schedule vs fixed distilled sigmas).
generations) -- either way everything else comes from an installed LTX-2 component folder. Dev and
Distilled are told apart by the filename.
"""

base: Literal[BaseModelType.LTX2] = Field(default=BaseModelType.LTX2)
Expand All @@ -2025,19 +2026,65 @@ def from_model_on_disk(cls, mod: ModelOnDisk, override_fields: dict[str, Any]) -
raise NotAMatchError(f"expected a .safetensors file, got {mod.path.suffix or '(no suffix)'}")

state_dict = mod.load_state_dict()
keys = {k for k in state_dict if isinstance(k, str)}
if ltx2_layout.ROLE_TRANSFORMER not in ltx2_layout.classify_roles(state_dict, mod.metadata()):
raise NotAMatchError("state dict does not look like an LTX-2 transformer")
if _has_ggml_tensors(state_dict):
raise NotAMatchError("GGUF-quantized LTX-2 checkpoints are not supported yet")

generation = override_fields.pop("generation", None) or _ltx2_generation_or_raise(
mod, mod.path, {ltx2_layout.strip_transformer_prefix(k) for k in keys}
)
variant = override_fields.pop("variant", None) or (
LTX2VariantType.Distilled if ltx2_layout.is_distilled_filename(mod.path.name) else LTX2VariantType.Dev
)
return cls(**override_fields, variant=LTX2VariantType(variant), generation=generation)
generation, variant = _ltx2_transformer_identity(mod, state_dict, override_fields)
return cls(**override_fields, variant=variant, generation=generation)


class Main_GGUF_LTX2_Config(Checkpoint_Config_Base, Main_Config_Base, Config_Base):
"""An LTX-2 single-file transformer in GGUF, in the official key layout.

The community builds are conversions of the official single files: the transformer's keys, bare or
under ``model.diffusion_model.``, with the two text connectors bundled beside them -- which, as for
the official files, the component folder supplies instead. Their ``general.architecture`` is ``ltxv``
for every generation, and some carry another model's metadata outright, so the keys identify them,
exactly as they do the safetensors files. Some keep the official ``model_version`` string and some
drop it; the structure dates those. Dev and Distilled are told apart by the filename.
"""

base: Literal[BaseModelType.LTX2] = Field(default=BaseModelType.LTX2)
format: Literal[ModelFormat.GGUFQuantized] = Field(default=ModelFormat.GGUFQuantized)
variant: LTX2VariantType = Field()
generation: str = Field(description="The LTX-2 generation the transformer belongs to, e.g. '2.5'.")

@classmethod
def from_model_on_disk(cls, mod: ModelOnDisk, override_fields: dict[str, Any]) -> Self:
raise_if_not_file(mod)
raise_for_override_fields(cls, override_fields)

if mod.path.suffix.lower() != ".gguf":
raise NotAMatchError(f"expected a .gguf file, got {mod.path.suffix or '(no suffix)'}")

state_dict = mod.load_state_dict()
if ltx2_layout.ROLE_TRANSFORMER not in ltx2_layout.classify_roles(state_dict, mod.metadata()):
raise NotAMatchError("state dict does not look like an LTX-2 transformer")
if not _has_ggml_tensors(state_dict):
raise NotAMatchError("state dict does not look like GGUF quantized")

generation, variant = _ltx2_transformer_identity(mod, state_dict, override_fields)
return cls(**override_fields, variant=variant, generation=generation)


def _ltx2_transformer_identity(
mod: ModelOnDisk, state_dict: dict[str | int, Any], override_fields: dict[str, Any]
) -> tuple[str, LTX2VariantType]:
"""The generation and variant of a single-file LTX-2 transformer, whatever its container.

The generation is the header's version or else the structure's, and one this version cannot run is
refused here. Dev and Distilled transformers are key-for-key identical, so the FILENAME is the
variant classifier and ``variant`` is the user-correctable override for renamed files. A
misclassified variant runs but samples with the wrong recipe (guided schedule vs fixed distilled
sigmas).
"""
generation = override_fields.pop("generation", None) or _ltx2_generation_or_raise(
mod, mod.path, {ltx2_layout.strip_transformer_prefix(k) for k in state_dict if isinstance(k, str)}
)
variant = override_fields.pop("variant", None) or (
LTX2VariantType.Distilled if ltx2_layout.is_distilled_filename(mod.path.name) else LTX2VariantType.Dev
)
return generation, LTX2VariantType(variant)


class Main_Checkpoint_Krea2_Config(Checkpoint_Config_Base, Main_Config_Base, Config_Base):
Expand Down
Loading
Loading