immich-app/immich · error · ValueError

Cannot load model!

Error message

Cannot load model!

What it means

Raised by Ann.load() when the native libann.load() call returns a negative network id. At this point the file exists and has a valid extension, but the Arm NN runtime could not parse/load it. The native return code is negative on any internal failure (corrupt file, unsupported operator, I/O error reading the network, cache deserialization failure).

Source

Thrown at machine-learning/immich_ml/sessions/ann/loader.py:124

        if not exists(model_path):
            raise ValueError("model_path must point to an existing file!")

        save_cached_network = False
        if cached_network_path is not None and not exists(cached_network_path):
            save_cached_network = True
            # create empty model cache file
            open(cached_network_path, "a").close()

        net_id: int = libann.load(
            self.ann,
            model_path.encode(),
            fast_math,
            fp16,
            save_cached_network,
            cached_network_path.encode() if cached_network_path is not None else None,
        )
        if net_id < 0:
            raise ValueError("Cannot load model!")

        self.input_shapes[net_id] = tuple(
            self.shape(net_id, input=True, index=i) for i in range(self.tensors(net_id, input=True))
        )
        self.output_shapes[net_id] = tuple(
            self.shape(net_id, input=False, index=i) for i in range(self.tensors(net_id, input=False))
        )
        return net_id

    def unload(self, network_id: int) -> None:
        libann.unload(self.ann, network_id)
        del self.output_shapes[network_id]

    def execute(self, network_id: int, input_tensors: list[NDArray[np.float32]]) -> list[NDArray[np.float32]]:
        if not isinstance(input_tensors, list):
            raise ValueError("input_tensors needs to be a list!")
        net_input_shapes = self.input_shapes[network_id]
        if len(input_tensors) != len(net_input_shapes):

View on GitHub (pinned to 199723261c)

Solutions

  1. Check the file size/hash against the source repo to detect truncation or corruption; re-download if mismatched.
  2. Delete any cached_network_path you passed in (or let save_cached_network recreate it) so a stale cache is not deserialized.
  3. Inspect Arm NN / libann logs (raise Ann log_level to 1 or 0) for the specific parser error, then re-export the model avoiding the unsupported op.
  4. Re-export the ONNX model to an opset/version Arm NN supports and retry.
  5. Ensure no other process is writing the model file while load() runs.

Example fix

# before
ann = Ann(log_level=3, tuning_level=1)
net_id = ann.load('/models/model.armnn', cached_network_path='/cache/model.ann')
# ValueError: Cannot load model!  (stale /cache/model.ann from old libann)

# after
from pathlib import Path
Path('/cache/model.ann').unlink(missing_ok=True)
ann = Ann(log_level=0)  # trace to see the parser error
net_id = ann.load('/models/model.armnn')
Defensive patterns

Strategy: try-catch

Validate before calling

import hashlib
from pathlib import Path

def validate_model_integrity(model_path: str, expected_sha256: str | None = None) -> None:
    p = Path(model_path)
    if not p.is_file() or p.stat().st_size == 0:
        raise ValueError(f"{model_path} missing or empty; cannot load")
    if expected_sha256:
        h = hashlib.sha256()
        h.update(p.read_bytes())
        if h.hexdigest() != expected_sha256:
            raise ValueError(f"{model_path} hash mismatch; re-download")

# call before Ann.load():
validate_model_integrity(model_path, expected_sha256=EXPECTED_HASH)

Type guard

from pathlib import Path

def looks_like_complete_model(model_path: str, min_size: int = 1024) -> bool:
    p = Path(model_path)
    return p.is_file() and p.stat().st_size > min_size

Try / catch

try:
    net_id = ann.load(model_path, cached_network_path=cache)
except ValueError as e:
    if 'Cannot load model' in str(e):
        log.error("libann rejected %s; removing stale cache and re-downloading", model_path)
        Path(model_path).unlink(missing_ok=True)
        if cache:
            Path(cache).unlink(missing_ok=True)
        redownload(model_path)
        net_id = ann.load(model_path)  # single retry without stale cache
    else:
        raise

Prevention

When it happens

Trigger: Passing a truncated or partially-written .armnn/.onnx/.tflite file to libann; a model using ops not supported by the Arm NN version bundled in libann.so; a corrupt cached_network_path that libann tries to deserialize; mismatch between the model format and libann's parser; disk read error during load.

Common situations: Interrupted download leaving a half-size model file (passes exists() check but fails to parse); using an ONNX opset newer than Arm NN supports; reusing a cached_network_path produced by a different libann version; bit-rot on the volume holding the model; concurrent writers corrupting the file.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/131a0f72a48d0419. Report an issue: GitHub.