fix(ml): preloaded model loaded twice (#32610)

fix(ml): reuse cached model across names that clean to the same model

(cherry picked from commit 166e93559f123313cf9cb4e7324c4ee30e21c618)
This commit is contained in:
Yaros
2026-10-22 05:23:22 +03:00
committed by Codex
parent 01ba579766
commit 3f3671fbe5
2 changed files with 11 additions and 1 deletions
+3 -1
View File
@@ -3,6 +3,7 @@ from collections.abc import Hashable
from typing import Any
from immich_ml import allocator
from immich_ml.config import clean_name
from immich_ml.models.base import InferenceEntry, InferenceModel
from immich_ml.schemas import Options
@@ -15,7 +16,8 @@ class ModelCache:
self._expiries: dict[Key, asyncio.TimerHandle] = {}
def get[O: Options](self, entry: InferenceEntry[O], ttl: int | None = None) -> InferenceModel[O]:
key = (entry.model, entry.name, entry.model.graph(entry.options))
# names that differ only in what cleaning drops, like an `immich-app/` prefix, are the same model
key = (entry.model, clean_name(entry.name), entry.model.graph(entry.options))
if key not in self._models:
self._models[key] = entry.model.create(entry.name, entry.options)
elif key not in self._expiries:
+8
View File
@@ -1856,6 +1856,14 @@ class TestCache:
assert len(model_cache._models) == 1
model.create.assert_called_once()
async def test_shares_an_instance_across_names_of_the_same_model(self) -> None:
model_cache = ModelCache()
bare = model_cache.get(InferenceEntry(OpenClipTextualEncoder, "ViT-B-32__openai", TextualOptions()))
prefixed = model_cache.get(
InferenceEntry(OpenClipTextualEncoder, "immich-app/ViT-B-32__openai", TextualOptions(language="en-US"))
)
assert bare is prefixed
async def test_separate_instances_per_graph_option(self) -> None:
# the real TextDetector, so that keying it on anything but its resolution fails this
model_cache = ModelCache()