invoke-ai/InvokeAI · error · ValueError

Unexpected keys loading

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.

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.

Appendix: 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)