invoke-ai/InvokeAI · error · ValueError

Unexpected keys loading {model_name}: {unexpected}

Error message

Unexpected keys loading {model_name}: {unexpected}

What it means

After loading an SDNQ-quantized model, raise_on_incomplete_sdnq_load verifies no unexpected state_dict keys remain; leftovers indicate the checkpoint does not match the model architecture. Any unexpected keys raise ValueError naming the model and the offending keys.

Source

Thrown at invokeai/backend/quantization/sdnq/loaders.py:44

    ``load_state_dict`` with ``strict=False`` silently ignores the missing/unexpected key lists.
    For SDNQ folder loads that is dangerous: a partial export, missing shard key or architecture
    mismatch leaves required parameters on the meta device and returns a model that fails much later
    during device movement or inference, far from the real cause. This raises with the offending
    keys instead.

    Args:
        model_name: Human-readable name for the error message (e.g. "SDNQ Z-Image transformer").
        missing_keys: The ``missing_keys`` returned by ``load_state_dict``.
        unexpected_keys: The ``unexpected_keys`` returned by ``load_state_dict``.
        allowed_missing: Keys that are expected to be absent (e.g. tied weights the caller re-shares
            after load), which must not trigger a failure.
    """
    allowed = set(allowed_missing)
    real_missing = [k for k in missing_keys if k not in allowed]
    unexpected = list(unexpected_keys)
    if unexpected:
        raise ValueError(f"Unexpected keys loading {model_name}: {unexpected}")
    if real_missing:
        raise ValueError(f"Missing keys loading {model_name} (required parameters left on meta): {real_missing}")


def _parse_quantization_config(config_path: Path) -> dict[str, Any]:
    """Parse quantization_config.json for SDNQ parameters."""
    if not config_path.exists():
        return {}

    with open(config_path, "r", encoding="utf-8") as f:
        return json.load(f)


_DTYPE_NAME_TO_QUANT_TYPE = {
    "uint4": SDNQQuantizationType.UINT4_ASYM,
    "int4": SDNQQuantizationType.UINT4_ASYM,  # signed naming, same packed storage
    "uint5": SDNQQuantizationType.INT5_ASYM,
    "int5": SDNQQuantizationType.INT5_ASYM,  # SDNQ dynamic-mixed uses this label

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Load the checkpoint into the model architecture it was quantized from (match config and class)
  2. Re-export/re-quantize the model so keys match the current architecture
  3. Diff the reported unexpected keys against the model's state_dict to identify stale/extra tensors and remove or remap them

Example fix

// before
model = _load_sdnq_transformer(folder_with_wrong_variant)
// after
model = _load_sdnq_transformer(folder_matching_config)  # same arch as quantization_config.json
Defensive patterns

Strategy: validation

Validate before calling

model_keys = set(model.state_dict())
ckpt_keys = set(load_file(shard).keys() for shard in shards)  # union across shards
extra = set().union(*ckpt_keys) - model_keys
assert not extra, f"checkpoint has keys not in model: {sorted(extra)[:5]}"

Type guard

def is_compatible_checkpoint(ckpt_keys: set, model_keys: set) -> bool:
    return ckpt_keys.issubset(model_keys)

Try / catch

try:
    model = _load_sdnq_transformer(path)
except ValueError as e:
    logger.error(f"SDNQ checkpoint incompatible: {e}")
    raise

Prevention

When it happens

Trigger: Loading an SDNQ checkpoint whose state_dict contains keys the target model class does not own — wrong architecture folder, renamed layers across versions, or extra quantization tensors the loader did not consume.

Common situations: Pointing the loader at a model variant different from the config (e.g. a different transformer revision); loading a checkpoint saved from a modified model; upstream diffusers key renames after a version bump.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/2fb6bd2c6ea27910. Report an issue: GitHub.