Skip to content

Commit c852187

Browse files
feat(mm): normalized model storage
Store models in a flat directory structure. Each model is in a dir named its unique key (a UUID). Inside that dir is either the model file or the model dir.
1 parent 7f3e5ce commit c852187

File tree

4 files changed

+13
-74
lines changed

4 files changed

+13
-74
lines changed

invokeai/app/api/routers/model_manager.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,10 +297,8 @@ async def update_model_record(
297297
"""Update a model's config."""
298298
logger = ApiDependencies.invoker.services.logger
299299
record_store = ApiDependencies.invoker.services.model_manager.store
300-
installer = ApiDependencies.invoker.services.model_manager.install
301300
try:
302-
record_store.update_model(key, changes=changes)
303-
config = installer.sync_model_path(key)
301+
config = record_store.update_model(key, changes=changes)
304302
config = add_cover_image_to_model_config(config, ApiDependencies)
305303
logger.info(f"Updated model: {key}")
306304
except UnknownModelException as e:

invokeai/app/services/model_install/model_install_base.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from invokeai.app.services.invoker import Invoker
1313
from invokeai.app.services.model_install.model_install_common import ModelInstallJob, ModelSource
1414
from invokeai.app.services.model_records import ModelRecordChanges, ModelRecordServiceBase
15-
from invokeai.backend.model_manager import AnyModelConfig
1615

1716
if TYPE_CHECKING:
1817
from invokeai.app.services.events.events_base import EventServiceBase
@@ -231,19 +230,6 @@ def wait_for_installs(self, timeout: int = 0) -> List[ModelInstallJob]:
231230
will block indefinitely until the installs complete.
232231
"""
233232

234-
@abstractmethod
235-
def sync_model_path(self, key: str) -> AnyModelConfig:
236-
"""
237-
Move model into the location indicated by its basetype, type and name.
238-
239-
Call this after updating a model's attributes in order to move
240-
the model's path into the location indicated by its basetype, type and
241-
name. Applies only to models whose paths are within the root `models_dir`
242-
directory.
243-
244-
May raise an UnknownModelException.
245-
"""
246-
247233
@abstractmethod
248234
def download_and_cache_model(self, source: str | AnyHttpUrl) -> Path:
249235
"""

invokeai/app/services/model_install/model_install_default.py

Lines changed: 11 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -180,28 +180,27 @@ def install_path(
180180
self,
181181
model_path: Union[Path, str],
182182
config: Optional[ModelRecordChanges] = None,
183-
) -> str: # noqa D102
183+
) -> str:
184184
model_path = Path(model_path)
185185
config = config or ModelRecordChanges()
186186
info: AnyModelConfig = self._probe(Path(model_path), config) # type: ignore
187187

188-
if preferred_name := config.name:
189-
if Path(model_path).is_file():
190-
# Careful! Don't use pathlib.Path(...).with_suffix - it can will strip everything after the first dot.
191-
preferred_name = f"{preferred_name}{model_path.suffix}"
192-
193-
dest_path = (
194-
self.app_config.models_path / info.base.value / info.type.value / (preferred_name or model_path.name)
195-
)
188+
dest_dir = self.app_config.models_path / info.key
196189
try:
197-
new_path = self._move_model(model_path, dest_path)
190+
dest_dir.mkdir(parents=True)
191+
dest_path = dest_dir / model_path.name if model_path.is_file() else dest_dir
192+
if dest_path.exists():
193+
raise FileExistsError(
194+
f"Cannot install model {model_path.name} to {dest_path}: destination already exists"
195+
)
196+
move(model_path, dest_path)
198197
except FileExistsError as excp:
199198
raise DuplicateModelException(
200-
f"A model named {model_path.name} is already installed at {dest_path.as_posix()}"
199+
f"A model named {model_path.name} is already installed at {dest_dir.as_posix()}"
201200
) from excp
202201

203202
return self._register(
204-
new_path,
203+
dest_path,
205204
config,
206205
info,
207206
)
@@ -589,49 +588,6 @@ def on_model_found(model_path: Path) -> bool:
589588
found_models = search.search(self._app_config.models_path)
590589
self._logger.info(f"{len(found_models)} new models registered")
591590

592-
def sync_model_path(self, key: str) -> AnyModelConfig:
593-
"""
594-
Move model into the location indicated by its basetype, type and name.
595-
596-
Call this after updating a model's attributes in order to move
597-
the model's path into the location indicated by its basetype, type and
598-
name. Applies only to models whose paths are within the root `models_dir`
599-
directory.
600-
601-
May raise an UnknownModelException.
602-
"""
603-
model = self.record_store.get_model(key)
604-
models_dir = self.app_config.models_path
605-
old_path = self.app_config.models_path / model.path
606-
607-
if not old_path.is_relative_to(models_dir):
608-
# The model is not in the models directory - we don't need to move it.
609-
return model
610-
611-
new_path = models_dir / model.base.value / model.type.value / old_path.name
612-
613-
if old_path == new_path or new_path.exists() and old_path == new_path.resolve():
614-
return model
615-
616-
self._logger.info(f"Moving {model.name} to {new_path}.")
617-
new_path = self._move_model(old_path, new_path)
618-
model.path = new_path.relative_to(models_dir).as_posix()
619-
self.record_store.update_model(key, ModelRecordChanges(path=model.path))
620-
return model
621-
622-
def _move_model(self, old_path: Path, new_path: Path) -> Path:
623-
if old_path == new_path:
624-
return old_path
625-
626-
if new_path.exists():
627-
raise FileExistsError(f"Cannot move {old_path} to {new_path}: destination already exists")
628-
629-
new_path.parent.mkdir(parents=True, exist_ok=True)
630-
631-
move(old_path, new_path)
632-
633-
return new_path
634-
635591
def _probe(self, model_path: Path, config: Optional[ModelRecordChanges] = None):
636592
config = config or ModelRecordChanges()
637593
hash_algo = self._app_config.hashing_algorithm

tests/app/services/model_install/test_model_install.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ def test_rename(
107107
key = mm2_installer.install_path(embedding_file)
108108
model_record = store.get_model(key)
109109
assert model_record.path.endswith("sd-1/embedding/test_embedding.safetensors")
110-
store.update_model(key, ModelRecordChanges(name="new model name", base=BaseModelType("sd-2")))
111-
new_model_record = mm2_installer.sync_model_path(key)
110+
new_model_record = store.update_model(key, ModelRecordChanges(name="new model name", base=BaseModelType("sd-2")))
112111
# Renaming the model record shouldn't rename the file
113112
assert new_model_record.name == "new model name"
114113
assert new_model_record.path.endswith("sd-2/embedding/test_embedding.safetensors")

0 commit comments

Comments
 (0)