mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(mm): use relative paths for invoke-managed models
We switched all model paths to be absolute in #5900. In hindsight, this is a mistake, because it makes the `models_dir` non-portable. This change reverts to the previous model pathing: - Invoke-managed models (in the `models_dir`) are stored with relative paths - Non-invoke-managed models (outside the `models_dir`, i.e. in-place installed models) still have absolute paths. ## Why absolute paths make things non-portable Let's say my `models_dir` is `/media/rhino/invokeai/models/`. In the DB, all model paths will be absolute children of this path, like this: - `/media/rhino/invokeai/models/sd-1/main/model1.ckpt` I want to change my `models_dir` to `/home/bat/invokeai/models/`. I update my `invokeai.yaml` file and physically move the files to that directory. On startup, the app checks for missing models. Because all of my model paths were absolute, they now point to a nonexistent path. All models are broken. There are a couple options to recover from this situation, neither of which are reasonable: 1. The user must manually update every model's path. Unacceptable UX. 2. On startup, we check for missing models. For each missing model, we compare its path with the last-known models dir. If there is a match, we replace that portion of the path with the new models dir. Then we re-check to see if the path exists. If it does, we update the models DB entry. Brittle and requires a new DB entry for last-known models dir. It's better to use relative paths for Invoke-managed models.
This commit is contained in:
parent
3409711ed3
commit
c5d1bd1360
@ -368,11 +368,13 @@ class ModelInstallService(ModelInstallServiceBase):
|
|||||||
def delete(self, key: str) -> None: # noqa D102
|
def delete(self, key: str) -> None: # noqa D102
|
||||||
"""Unregister the model. Delete its files only if they are within our models directory."""
|
"""Unregister the model. Delete its files only if they are within our models directory."""
|
||||||
model = self.record_store.get_model(key)
|
model = self.record_store.get_model(key)
|
||||||
models_dir = self.app_config.models_path
|
model_path = self.app_config.models_path / model.path
|
||||||
model_path = models_dir / Path(model.path) # handle legacy relative model paths
|
|
||||||
if model_path.is_relative_to(models_dir):
|
if model_path.is_relative_to(self.app_config.models_path):
|
||||||
|
# If the models is in the Invoke-managed models dir, we delete it
|
||||||
self.unconditionally_delete(key)
|
self.unconditionally_delete(key)
|
||||||
else:
|
else:
|
||||||
|
# Else we only unregister it, leaving the file in place
|
||||||
self.unregister(key)
|
self.unregister(key)
|
||||||
|
|
||||||
def unconditionally_delete(self, key: str) -> None: # noqa D102
|
def unconditionally_delete(self, key: str) -> None: # noqa D102
|
||||||
@ -500,9 +502,9 @@ class ModelInstallService(ModelInstallServiceBase):
|
|||||||
def _scan_for_missing_models(self) -> list[AnyModelConfig]:
|
def _scan_for_missing_models(self) -> list[AnyModelConfig]:
|
||||||
"""Scan the models directory for missing models and return a list of them."""
|
"""Scan the models directory for missing models and return a list of them."""
|
||||||
missing_models: list[AnyModelConfig] = []
|
missing_models: list[AnyModelConfig] = []
|
||||||
for x in self.record_store.all_models():
|
for model_config in self.record_store.all_models():
|
||||||
if not Path(x.path).resolve().exists():
|
if not (self.app_config.models_path / model_config.path).resolve().exists():
|
||||||
missing_models.append(x)
|
missing_models.append(model_config)
|
||||||
return missing_models
|
return missing_models
|
||||||
|
|
||||||
def _register_orphaned_models(self) -> None:
|
def _register_orphaned_models(self) -> None:
|
||||||
@ -548,10 +550,11 @@ class ModelInstallService(ModelInstallServiceBase):
|
|||||||
May raise an UnknownModelException.
|
May raise an UnknownModelException.
|
||||||
"""
|
"""
|
||||||
model = self.record_store.get_model(key)
|
model = self.record_store.get_model(key)
|
||||||
old_path = Path(model.path).resolve()
|
models_dir = self.app_config.models_path
|
||||||
models_dir = self.app_config.models_path.resolve()
|
old_path = self.app_config.models_path / model.path
|
||||||
|
|
||||||
if not old_path.is_relative_to(models_dir):
|
if not old_path.is_relative_to(models_dir):
|
||||||
|
# The model is not in the models directory - we don't need to move it.
|
||||||
return model
|
return model
|
||||||
|
|
||||||
new_path = (models_dir / model.base.value / model.type.value / model.name).with_suffix(old_path.suffix)
|
new_path = (models_dir / model.base.value / model.type.value / model.name).with_suffix(old_path.suffix)
|
||||||
@ -561,7 +564,7 @@ class ModelInstallService(ModelInstallServiceBase):
|
|||||||
|
|
||||||
self._logger.info(f"Moving {model.name} to {new_path}.")
|
self._logger.info(f"Moving {model.name} to {new_path}.")
|
||||||
new_path = self._move_model(old_path, new_path)
|
new_path = self._move_model(old_path, new_path)
|
||||||
model.path = new_path.as_posix()
|
model.path = new_path.relative_to(models_dir).as_posix()
|
||||||
self.record_store.update_model(key, ModelRecordChanges(path=model.path))
|
self.record_store.update_model(key, ModelRecordChanges(path=model.path))
|
||||||
return model
|
return model
|
||||||
|
|
||||||
@ -600,12 +603,19 @@ class ModelInstallService(ModelInstallServiceBase):
|
|||||||
|
|
||||||
model_path = model_path.resolve()
|
model_path = model_path.resolve()
|
||||||
|
|
||||||
|
# Models in the Invoke-managed models dir should use relative paths.
|
||||||
|
if model_path.is_relative_to(self.app_config.models_path):
|
||||||
|
model_path = model_path.relative_to(self.app_config.models_path)
|
||||||
|
|
||||||
info.path = model_path.as_posix()
|
info.path = model_path.as_posix()
|
||||||
|
|
||||||
# Checkpoints have a config file needed for conversion - resolve this to an absolute path
|
|
||||||
if isinstance(info, CheckpointConfigBase):
|
if isinstance(info, CheckpointConfigBase):
|
||||||
legacy_conf = (self.app_config.legacy_conf_path / info.config_path).resolve()
|
# Checkpoints have a config file needed for conversion. Same handling as the model weights - if it's in the
|
||||||
info.config_path = legacy_conf.as_posix()
|
# invoke-managed legacy config dir, we use a relative path.
|
||||||
|
legacy_config_path = Path(info.config_path).resolve()
|
||||||
|
if legacy_config_path.is_relative_to(self.app_config.legacy_conf_path):
|
||||||
|
legacy_config_path = legacy_config_path.relative_to(self.app_config.legacy_conf_path)
|
||||||
|
info.config_path = legacy_config_path.as_posix()
|
||||||
self.record_store.add_model(info)
|
self.record_store.add_model(info)
|
||||||
return info.key
|
return info.key
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user