From 067b805044b00b5beb8b808d46f48348ead8c106 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Fri, 7 Jun 2024 15:34:14 -0400 Subject: [PATCH 01/10] use model_class.load_singlefile() instead of converting; works, but performance is poor --- .../model_manager/load/load_default.py | 15 +- .../load/model_loaders/controlnet.py | 79 +++++----- .../load/model_loaders/stable_diffusion.py | 136 ++++++++++++------ .../model_manager/load/model_loaders/vae.py | 83 ++++++----- 4 files changed, 194 insertions(+), 119 deletions(-) diff --git a/invokeai/backend/model_manager/load/load_default.py b/invokeai/backend/model_manager/load/load_default.py index a58741763fa..f406b1a3910 100644 --- a/invokeai/backend/model_manager/load/load_default.py +++ b/invokeai/backend/model_manager/load/load_default.py @@ -84,12 +84,15 @@ def _convert_and_load( except IndexError: pass - cache_path: Path = self._convert_cache.cache_path(config.key) - if self._needs_conversion(config, model_path, cache_path): - loaded_model = self._do_convert(config, model_path, cache_path, submodel_type) - else: - config.path = str(cache_path) if cache_path.exists() else str(self._get_model_path(config)) - loaded_model = self._load_model(config, submodel_type) + config.path = str(self._get_model_path(config)) + loaded_model = self._load_model(config, submodel_type) + + # cache_path: Path = self._convert_cache.cache_path(config.key) + # if self._needs_conversion(config, model_path, cache_path): + # loaded_model = self._do_convert(config, model_path, cache_path, submodel_type) + # else: + # config.path = str(cache_path) if cache_path.exists() else str(self._get_model_path(config)) + # loaded_model = self._load_model(config, submodel_type) self._ram_cache.put( config.key, diff --git a/invokeai/backend/model_manager/load/model_loaders/controlnet.py b/invokeai/backend/model_manager/load/model_loaders/controlnet.py index 0b93d8d2cad..b05367d4e30 100644 --- a/invokeai/backend/model_manager/load/model_loaders/controlnet.py +++ b/invokeai/backend/model_manager/load/model_loaders/controlnet.py @@ -3,7 +3,7 @@ from pathlib import Path from typing import Optional - +from diffusers import ControlNetModel from invokeai.backend.model_manager import ( AnyModel, AnyModelConfig, @@ -11,8 +11,7 @@ ModelFormat, ModelType, ) -from invokeai.backend.model_manager.config import CheckpointConfigBase -from invokeai.backend.model_manager.convert_ckpt_to_diffusers import convert_controlnet_to_diffusers +from invokeai.backend.model_manager.config import SubModelType, ControlNetCheckpointConfig from .. import ModelLoaderRegistry from .generic_diffusers import GenericDiffusersLoader @@ -23,36 +22,46 @@ class ControlNetLoader(GenericDiffusersLoader): """Class to load ControlNet models.""" - def _needs_conversion(self, config: AnyModelConfig, model_path: Path, dest_path: Path) -> bool: - if not isinstance(config, CheckpointConfigBase): - return False - elif ( - dest_path.exists() - and (dest_path / "config.json").stat().st_mtime >= (config.converted_at or 0.0) - and (dest_path / "config.json").stat().st_mtime >= model_path.stat().st_mtime - ): - return False + def _load_model( + self, + config: AnyModelConfig, + submodel_type: Optional[SubModelType] = None, + ) -> AnyModel: + if isinstance(config, ControlNetCheckpointConfig): + return ControlNetModel.from_single_file(config.path, config=self._app_config.legacy_conf_path / config.config_path) else: - return True - - def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: - assert isinstance(config, CheckpointConfigBase) - image_size = ( - 512 - if config.base == BaseModelType.StableDiffusion1 - else 768 - if config.base == BaseModelType.StableDiffusion2 - else 1024 - ) - - self._logger.info(f"Converting {model_path} to diffusers format") - with open(self._app_config.legacy_conf_path / config.config_path, "r") as config_stream: - result = convert_controlnet_to_diffusers( - model_path, - output_path, - original_config_file=config_stream, - image_size=image_size, - precision=self._torch_dtype, - from_safetensors=model_path.suffix == ".safetensors", - ) - return result + return super()._load_model(config, submodel_type) + + # def _needs_conversion(self, config: AnyModelConfig, model_path: Path, dest_path: Path) -> bool: + # if not isinstance(config, CheckpointConfigBase): + # return False + # elif ( + # dest_path.exists() + # and (dest_path / "config.json").stat().st_mtime >= (config.converted_at or 0.0) + # and (dest_path / "config.json").stat().st_mtime >= model_path.stat().st_mtime + # ): + # return False + # else: + # return True + + # def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: + # assert isinstance(config, CheckpointConfigBase) + # image_size = ( + # 512 + # if config.base == BaseModelType.StableDiffusion1 + # else 768 + # if config.base == BaseModelType.StableDiffusion2 + # else 1024 + # ) + + # self._logger.info(f"Converting {model_path} to diffusers format") + # with open(self._app_config.legacy_conf_path / config.config_path, "r") as config_stream: + # result = convert_controlnet_to_diffusers( + # model_path, + # output_path, + # original_config_file=config_stream, + # image_size=image_size, + # precision=self._torch_dtype, + # from_safetensors=model_path.suffix == ".safetensors", + # ) + # return result diff --git a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py index 3ca7a5b2e4a..6bf9c5a0370 100644 --- a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +++ b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py @@ -3,13 +3,21 @@ from pathlib import Path from typing import Optional +from diffusers import ( + StableDiffusionPipeline, + StableDiffusionInpaintPipeline, + StableDiffusionXLPipeline, + StableDiffusionXLInpaintPipeline, +) +from invokeai.backend.model_manager.load.model_util import calc_model_size_by_data from invokeai.backend.model_manager import ( AnyModel, AnyModelConfig, BaseModelType, ModelFormat, ModelType, + ModelVariantType, SchedulerPredictionType, SubModelType, ) @@ -50,6 +58,10 @@ def _load_model( ) -> AnyModel: if not submodel_type is not None: raise Exception("A submodel type must be provided when loading main pipelines.") + + if isinstance(config, CheckpointConfigBase): + return self._load_from_singlefile(config, submodel_type) + model_path = Path(config.path) load_class = self.get_hf_load_class(model_path, submodel_type) repo_variant = config.repo_variant if isinstance(config, DiffusersConfigBase) else None @@ -71,46 +83,86 @@ def _load_model( return result - def _needs_conversion(self, config: AnyModelConfig, model_path: Path, dest_path: Path) -> bool: - if not isinstance(config, CheckpointConfigBase): - return False - elif ( - dest_path.exists() - and (dest_path / "model_index.json").stat().st_mtime >= (config.converted_at or 0.0) - and (dest_path / "model_index.json").stat().st_mtime >= model_path.stat().st_mtime - ): - return False - else: - return True - - def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: + def _load_from_singlefile( + self, + config: AnyModelConfig, + submodel_type: SubModelType, + ) -> AnyModel: + load_classes = { + BaseModelType.StableDiffusion1: { + ModelVariantType.Normal: StableDiffusionPipeline, + ModelVariantType.Inpaint: StableDiffusionInpaintPipeline, + }, + BaseModelType.StableDiffusion2: { + ModelVariantType.Normal: StableDiffusionPipeline, + ModelVariantType.Inpaint: StableDiffusionInpaintPipeline, + }, + BaseModelType.StableDiffusionXL: { + ModelVariantType.Normal: StableDiffusionXLPipeline, + ModelVariantType.Inpaint: StableDiffusionXLInpaintPipeline, + } + } assert isinstance(config, MainCheckpointConfig) - base = config.base - - prediction_type = config.prediction_type.value - upcast_attention = config.upcast_attention - image_size = ( - 1024 - if base == BaseModelType.StableDiffusionXL - else 768 - if config.prediction_type == SchedulerPredictionType.VPrediction and base == BaseModelType.StableDiffusion2 - else 512 - ) - - self._logger.info(f"Converting {model_path} to diffusers format") - - loaded_model = convert_ckpt_to_diffusers( - model_path, - output_path, - model_type=self.model_base_to_model_type[base], - original_config_file=self._app_config.legacy_conf_path / config.config_path, - extract_ema=True, - from_safetensors=model_path.suffix == ".safetensors", - precision=self._torch_dtype, - prediction_type=prediction_type, - image_size=image_size, - upcast_attention=upcast_attention, - load_safety_checker=False, - num_in_channels=VARIANT_TO_IN_CHANNEL_MAP[config.variant], - ) - return loaded_model + try: + load_class = load_classes[config.base][config.variant] + except KeyError as e: + raise Exception(f'No diffusers pipeline known for base={config.base}, variant={config.variant}') from e + print(f'DEBUG: load_class={load_class}') + original_config_file=self._app_config.legacy_conf_path / config.config_path # should try without using this... + pipeline = load_class.from_single_file(config.path, config=original_config_file) + + # Proactively load the various submodels into the RAM cache so that we don't have to re-convert + # the entire pipeline every time a new submodel is needed. + for subtype in SubModelType: + if subtype == submodel_type: + continue + if submodel := getattr(pipeline, subtype.value, None): + self._ram_cache.put( + config.key, submodel_type=subtype, model=submodel, size=calc_model_size_by_data(submodel) + ) + return getattr(pipeline, submodel_type.value) + + + # def _needs_conversion(self, config: AnyModelConfig, model_path: Path, dest_path: Path) -> bool: + # if not isinstance(config, CheckpointConfigBase): + # return False + # elif ( + # dest_path.exists() + # and (dest_path / "model_index.json").stat().st_mtime >= (config.converted_at or 0.0) + # and (dest_path / "model_index.json").stat().st_mtime >= model_path.stat().st_mtime + # ): + # return False + # else: + # return True + + # def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: + # assert isinstance(config, MainCheckpointConfig) + # base = config.base + + # prediction_type = config.prediction_type.value + # upcast_attention = config.upcast_attention + # image_size = ( + # 1024 + # if base == BaseModelType.StableDiffusionXL + # else 768 + # if config.prediction_type == SchedulerPredictionType.VPrediction and base == BaseModelType.StableDiffusion2 + # else 512 + # ) + + # self._logger.info(f"Converting {model_path} to diffusers format") + + # loaded_model = convert_ckpt_to_diffusers( + # model_path, + # output_path, + # model_type=self.model_base_to_model_type[base], + # original_config_file=self._app_config.legacy_conf_path / config.config_path, + # extract_ema=True, + # from_safetensors=model_path.suffix == ".safetensors", + # precision=self._torch_dtype, + # prediction_type=prediction_type, + # image_size=image_size, + # upcast_attention=upcast_attention, + # load_safety_checker=False, + # num_in_channels=VARIANT_TO_IN_CHANNEL_MAP[config.variant], + # ) + # return loaded_model diff --git a/invokeai/backend/model_manager/load/model_loaders/vae.py b/invokeai/backend/model_manager/load/model_loaders/vae.py index 122b2f07972..830abd53b0a 100644 --- a/invokeai/backend/model_manager/load/model_loaders/vae.py +++ b/invokeai/backend/model_manager/load/model_loaders/vae.py @@ -1,6 +1,7 @@ # Copyright (c) 2024, Lincoln D. Stein and the InvokeAI Development Team """Class for VAE model loading in InvokeAI.""" +from diffusers import AutoencoderKL from pathlib import Path from typing import Optional @@ -14,7 +15,7 @@ ModelFormat, ModelType, ) -from invokeai.backend.model_manager.config import AnyModel, CheckpointConfigBase +from invokeai.backend.model_manager.config import AnyModel, CheckpointConfigBase, SubModelType, VAECheckpointConfig from invokeai.backend.model_manager.convert_ckpt_to_diffusers import convert_ldm_vae_to_diffusers from .. import ModelLoaderRegistry @@ -27,43 +28,53 @@ class VAELoader(GenericDiffusersLoader): """Class to load VAE models.""" - def _needs_conversion(self, config: AnyModelConfig, model_path: Path, dest_path: Path) -> bool: - if not isinstance(config, CheckpointConfigBase): - return False - elif ( - dest_path.exists() - and (dest_path / "config.json").stat().st_mtime >= (config.converted_at or 0.0) - and (dest_path / "config.json").stat().st_mtime >= model_path.stat().st_mtime - ): - return False + def _load_model( + self, + config: AnyModelConfig, + submodel_type: Optional[SubModelType] = None, + ) -> AnyModel: + if isinstance(config, VAECheckpointConfig): + return AutoencoderKL.from_single_file(config.path, config=self._app_config.legacy_conf_path / config.config_path) else: - return True + return super()._load_model(config, submodel_type) - def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: - # TODO(MM2): check whether sdxl VAE models convert. - if config.base not in {BaseModelType.StableDiffusion1, BaseModelType.StableDiffusion2}: - raise Exception(f"VAE conversion not supported for model type: {config.base}") - else: - assert isinstance(config, CheckpointConfigBase) - config_file = self._app_config.legacy_conf_path / config.config_path + # def _needs_conversion(self, config: AnyModelConfig, model_path: Path, dest_path: Path) -> bool: + # if not isinstance(config, CheckpointConfigBase): + # return False + # elif ( + # dest_path.exists() + # and (dest_path / "config.json").stat().st_mtime >= (config.converted_at or 0.0) + # and (dest_path / "config.json").stat().st_mtime >= model_path.stat().st_mtime + # ): + # return False + # else: + # return True - if model_path.suffix == ".safetensors": - checkpoint = safetensors_load_file(model_path, device="cpu") - else: - checkpoint = torch.load(model_path, map_location="cpu") + # def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: + # # TODO(MM2): check whether sdxl VAE models convert. + # if config.base not in {BaseModelType.StableDiffusion1, BaseModelType.StableDiffusion2}: + # raise Exception(f"VAE conversion not supported for model type: {config.base}") + # else: + # assert isinstance(config, CheckpointConfigBase) + # config_file = self._app_config.legacy_conf_path / config.config_path + + # if model_path.suffix == ".safetensors": + # checkpoint = safetensors_load_file(model_path, device="cpu") + # else: + # checkpoint = torch.load(model_path, map_location="cpu") - # sometimes weights are hidden under "state_dict", and sometimes not - if "state_dict" in checkpoint: - checkpoint = checkpoint["state_dict"] + # # sometimes weights are hidden under "state_dict", and sometimes not + # if "state_dict" in checkpoint: + # checkpoint = checkpoint["state_dict"] - ckpt_config = OmegaConf.load(config_file) - assert isinstance(ckpt_config, DictConfig) - self._logger.info(f"Converting {model_path} to diffusers format") - vae_model = convert_ldm_vae_to_diffusers( - checkpoint=checkpoint, - vae_config=ckpt_config, - image_size=512, - precision=self._torch_dtype, - dump_path=output_path, - ) - return vae_model + # ckpt_config = OmegaConf.load(config_file) + # assert isinstance(ckpt_config, DictConfig) + # self._logger.info(f"Converting {model_path} to diffusers format") + # vae_model = convert_ldm_vae_to_diffusers( + # checkpoint=checkpoint, + # vae_config=ckpt_config, + # image_size=512, + # precision=self._torch_dtype, + # dump_path=output_path, + # ) + # return vae_model From b268cc2db953bdd0177470ae28ca956a66758c0f Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Fri, 7 Jun 2024 22:00:48 -0400 Subject: [PATCH 02/10] adjust the convert api - not right just yet --- invokeai/app/api/routers/model_manager.py | 15 +++++---------- .../load/model_loaders/controlnet.py | 18 +++++------------- .../load/model_loaders/stable_diffusion.py | 7 +++++-- .../model_manager/load/model_loaders/vae.py | 18 +++++------------- 4 files changed, 20 insertions(+), 38 deletions(-) diff --git a/invokeai/app/api/routers/model_manager.py b/invokeai/app/api/routers/model_manager.py index b1221f7a345..ec260c7684a 100644 --- a/invokeai/app/api/routers/model_manager.py +++ b/invokeai/app/api/routers/model_manager.py @@ -619,17 +619,12 @@ async def convert_model( logger.error(f"The model with key {key} is not a main checkpoint model.") raise HTTPException(400, f"The model with key {key} is not a main checkpoint model.") - # loading the model will convert it into a cached diffusers file - try: - cc_size = loader.convert_cache.max_size - if cc_size == 0: # temporary set the convert cache to a positive number so that cached model is written - loader._convert_cache.max_size = 1.0 - loader.load_model(model_config, submodel_type=SubModelType.Scheduler) - finally: - loader._convert_cache.max_size = cc_size - - # Get the path of the converted model from the loader cache_path = loader.convert_cache.cache_path(key) + converted_model = loader.load_model(model_config, submodel_type=SubModelType.Scheduler) + # write the converted file to the model cache directory + raw_model = converted_model.model + assert hasattr(raw_model, 'save_pretrained') + raw_model.save_pretrained(cache_path) assert cache_path.exists() # temporarily rename the original safetensors file so that there is no naming conflict diff --git a/invokeai/backend/model_manager/load/model_loaders/controlnet.py b/invokeai/backend/model_manager/load/model_loaders/controlnet.py index b05367d4e30..ffd851e31f0 100644 --- a/invokeai/backend/model_manager/load/model_loaders/controlnet.py +++ b/invokeai/backend/model_manager/load/model_loaders/controlnet.py @@ -28,22 +28,14 @@ def _load_model( submodel_type: Optional[SubModelType] = None, ) -> AnyModel: if isinstance(config, ControlNetCheckpointConfig): - return ControlNetModel.from_single_file(config.path, config=self._app_config.legacy_conf_path / config.config_path) + return ControlNetModel.from_single_file(config.path, + config=self._app_config.legacy_conf_path / config.config_path, + torch_dtype=self._torch_dtype, + local_files_only=True, + ) else: return super()._load_model(config, submodel_type) - # def _needs_conversion(self, config: AnyModelConfig, model_path: Path, dest_path: Path) -> bool: - # if not isinstance(config, CheckpointConfigBase): - # return False - # elif ( - # dest_path.exists() - # and (dest_path / "config.json").stat().st_mtime >= (config.converted_at or 0.0) - # and (dest_path / "config.json").stat().st_mtime >= model_path.stat().st_mtime - # ): - # return False - # else: - # return True - # def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: # assert isinstance(config, CheckpointConfigBase) # image_size = ( diff --git a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py index 6bf9c5a0370..dce3990f373 100644 --- a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +++ b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py @@ -107,9 +107,12 @@ def _load_from_singlefile( load_class = load_classes[config.base][config.variant] except KeyError as e: raise Exception(f'No diffusers pipeline known for base={config.base}, variant={config.variant}') from e - print(f'DEBUG: load_class={load_class}') original_config_file=self._app_config.legacy_conf_path / config.config_path # should try without using this... - pipeline = load_class.from_single_file(config.path, config=original_config_file) + pipeline = load_class.from_single_file(config.path, + config=original_config_file, + torch_dtype=self._torch_dtype, + local_files_only=True, + ) # Proactively load the various submodels into the RAM cache so that we don't have to re-convert # the entire pipeline every time a new submodel is needed. diff --git a/invokeai/backend/model_manager/load/model_loaders/vae.py b/invokeai/backend/model_manager/load/model_loaders/vae.py index 830abd53b0a..81cf36130dc 100644 --- a/invokeai/backend/model_manager/load/model_loaders/vae.py +++ b/invokeai/backend/model_manager/load/model_loaders/vae.py @@ -34,22 +34,14 @@ def _load_model( submodel_type: Optional[SubModelType] = None, ) -> AnyModel: if isinstance(config, VAECheckpointConfig): - return AutoencoderKL.from_single_file(config.path, config=self._app_config.legacy_conf_path / config.config_path) + return AutoencoderKL.from_single_file(config.path, + config=self._app_config.legacy_conf_path / config.config_path, + torch_dtype=self._torch_dtype, + local_files_only=True, + ) else: return super()._load_model(config, submodel_type) - # def _needs_conversion(self, config: AnyModelConfig, model_path: Path, dest_path: Path) -> bool: - # if not isinstance(config, CheckpointConfigBase): - # return False - # elif ( - # dest_path.exists() - # and (dest_path / "config.json").stat().st_mtime >= (config.converted_at or 0.0) - # and (dest_path / "config.json").stat().st_mtime >= model_path.stat().st_mtime - # ): - # return False - # else: - # return True - # def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: # # TODO(MM2): check whether sdxl VAE models convert. # if config.base not in {BaseModelType.StableDiffusion1, BaseModelType.StableDiffusion2}: From acce4d393eb483c176f3b36ebf70bec5129a1ec9 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Wed, 12 Jun 2024 16:18:15 -0400 Subject: [PATCH 03/10] working, needs sql migrator update --- invokeai/app/api/routers/model_manager.py | 80 +++++++-------- .../services/model_load/model_load_base.py | 6 -- .../services/model_load/model_load_default.py | 9 -- .../model_manager/model_manager_default.py | 4 +- .../app/services/shared/sqlite/sqlite_util.py | 2 + .../migrations/migration_11.py | 36 +++++++ invokeai/backend/model_manager/config.py | 3 +- .../convert_ckpt_to_diffusers.py | 83 ---------------- .../backend/model_manager/load/__init__.py | 2 - .../load/convert_cache/__init__.py | 4 - .../load/convert_cache/convert_cache_base.py | 28 ------ .../convert_cache/convert_cache_default.py | 81 ---------------- .../backend/model_manager/load/load_base.py | 8 -- .../model_manager/load/load_default.py | 50 +--------- .../load/model_loaders/controlnet.py | 38 ++------ .../model_manager/load/model_loaders/lora.py | 4 +- .../load/model_loaders/stable_diffusion.py | 97 ++++++------------- .../model_manager/load/model_loaders/vae.py | 49 ++-------- invokeai/backend/model_manager/probe.py | 2 + .../model_manager/model_manager_fixtures.py | 6 +- 20 files changed, 131 insertions(+), 461 deletions(-) create mode 100644 invokeai/app/services/shared/sqlite_migrator/migrations/migration_11.py delete mode 100644 invokeai/backend/model_manager/convert_ckpt_to_diffusers.py delete mode 100644 invokeai/backend/model_manager/load/convert_cache/__init__.py delete mode 100644 invokeai/backend/model_manager/load/convert_cache/convert_cache_base.py delete mode 100644 invokeai/backend/model_manager/load/convert_cache/convert_cache_default.py diff --git a/invokeai/app/api/routers/model_manager.py b/invokeai/app/api/routers/model_manager.py index ec260c7684a..c3e5e50c4be 100644 --- a/invokeai/app/api/routers/model_manager.py +++ b/invokeai/app/api/routers/model_manager.py @@ -3,9 +3,9 @@ import io import pathlib -import shutil import traceback from copy import deepcopy +from tempfile import TemporaryDirectory from typing import Any, Dict, List, Optional, Type from fastapi import Body, Path, Query, Response, UploadFile @@ -19,7 +19,6 @@ from invokeai.app.services.model_images.model_images_common import ModelImageFileNotFoundException from invokeai.app.services.model_install.model_install_common import ModelInstallJob from invokeai.app.services.model_records import ( - DuplicateModelException, InvalidModelException, ModelRecordChanges, UnknownModelException, @@ -30,7 +29,6 @@ MainCheckpointConfig, ModelFormat, ModelType, - SubModelType, ) from invokeai.backend.model_manager.metadata.fetch.huggingface import HuggingFaceMetadataFetch from invokeai.backend.model_manager.metadata.metadata_base import ModelMetadataWithFiles, UnknownMetadataException @@ -174,18 +172,6 @@ async def get_model_record( raise HTTPException(status_code=404, detail=str(e)) -# @model_manager_router.get("/summary", operation_id="list_model_summary") -# async def list_model_summary( -# page: int = Query(default=0, description="The page to get"), -# per_page: int = Query(default=10, description="The number of models per page"), -# order_by: ModelRecordOrderBy = Query(default=ModelRecordOrderBy.Default, description="The attribute to order by"), -# ) -> PaginatedResults[ModelSummary]: -# """Gets a page of model summary data.""" -# record_store = ApiDependencies.invoker.services.model_manager.store -# results: PaginatedResults[ModelSummary] = record_store.list_models(page=page, per_page=per_page, order_by=order_by) -# return results - - class FoundModel(BaseModel): path: str = Field(description="Path to the model") is_installed: bool = Field(description="Whether or not the model is already installed") @@ -619,34 +605,38 @@ async def convert_model( logger.error(f"The model with key {key} is not a main checkpoint model.") raise HTTPException(400, f"The model with key {key} is not a main checkpoint model.") - cache_path = loader.convert_cache.cache_path(key) - converted_model = loader.load_model(model_config, submodel_type=SubModelType.Scheduler) - # write the converted file to the model cache directory - raw_model = converted_model.model - assert hasattr(raw_model, 'save_pretrained') - raw_model.save_pretrained(cache_path) - assert cache_path.exists() - - # temporarily rename the original safetensors file so that there is no naming conflict - original_name = model_config.name - model_config.name = f"{original_name}.DELETE" - changes = ModelRecordChanges(name=model_config.name) - store.update_model(key, changes=changes) - - # install the diffusers - try: - new_key = installer.install_path( - cache_path, - config={ - "name": original_name, - "description": model_config.description, - "hash": model_config.hash, - "source": model_config.source, - }, - ) - except DuplicateModelException as e: - logger.error(str(e)) - raise HTTPException(status_code=409, detail=str(e)) + with TemporaryDirectory(dir=ApiDependencies.invoker.services.configuration.models_path) as tmpdir: + convert_path = pathlib.Path(tmpdir) / pathlib.Path(model_config.path).stem + print(f"DEBUG: convert_path={convert_path}") + converted_model = loader.load_model(model_config) + # write the converted file to the convert path + raw_model = converted_model.model + print(f"DEBUG: raw_model = {raw_model}") + assert hasattr(raw_model, "save_pretrained") + raw_model.save_pretrained(convert_path) + assert convert_path.exists() + + # temporarily rename the original safetensors file so that there is no naming conflict + original_name = model_config.name + model_config.name = f"{original_name}.DELETE" + changes = ModelRecordChanges(name=model_config.name) + store.update_model(key, changes=changes) + + # install the diffusers + try: + new_key = installer.install_path( + convert_path, + config={ + "name": original_name, + "description": model_config.description, + "hash": model_config.hash, + "source": model_config.source, + }, + ) + except Exception as e: + logger.error(str(e)) + store.update_model(key, changes=ModelRecordChanges(name=original_name)) + raise HTTPException(status_code=409, detail=str(e)) # Update the model image if the model had one try: @@ -659,8 +649,8 @@ async def convert_model( # delete the original safetensors file installer.delete(key) - # delete the cached version - shutil.rmtree(cache_path) + # delete the temporary directory + # shutil.rmtree(cache_path) # return the config record for the new diffusers directory new_config = store.get_model(new_key) diff --git a/invokeai/app/services/model_load/model_load_base.py b/invokeai/app/services/model_load/model_load_base.py index 9d75aafde12..a2328a98a52 100644 --- a/invokeai/app/services/model_load/model_load_base.py +++ b/invokeai/app/services/model_load/model_load_base.py @@ -6,7 +6,6 @@ from invokeai.backend.model_manager import AnyModel, AnyModelConfig, SubModelType from invokeai.backend.model_manager.load import LoadedModel -from invokeai.backend.model_manager.load.convert_cache import ModelConvertCacheBase from invokeai.backend.model_manager.load.model_cache.model_cache_base import ModelCacheBase @@ -26,8 +25,3 @@ def load_model(self, model_config: AnyModelConfig, submodel_type: Optional[SubMo @abstractmethod def ram_cache(self) -> ModelCacheBase[AnyModel]: """Return the RAM cache used by this loader.""" - - @property - @abstractmethod - def convert_cache(self) -> ModelConvertCacheBase: - """Return the checkpoint convert cache used by this loader.""" diff --git a/invokeai/app/services/model_load/model_load_default.py b/invokeai/app/services/model_load/model_load_default.py index e9f527bc86c..85440fe3caf 100644 --- a/invokeai/app/services/model_load/model_load_default.py +++ b/invokeai/app/services/model_load/model_load_default.py @@ -11,7 +11,6 @@ ModelLoaderRegistry, ModelLoaderRegistryBase, ) -from invokeai.backend.model_manager.load.convert_cache import ModelConvertCacheBase from invokeai.backend.model_manager.load.model_cache.model_cache_base import ModelCacheBase from invokeai.backend.util.logging import InvokeAILogger @@ -25,7 +24,6 @@ def __init__( self, app_config: InvokeAIAppConfig, ram_cache: ModelCacheBase[AnyModel], - convert_cache: ModelConvertCacheBase, registry: Optional[Type[ModelLoaderRegistryBase]] = ModelLoaderRegistry, ): """Initialize the model load service.""" @@ -34,7 +32,6 @@ def __init__( self._logger = logger self._app_config = app_config self._ram_cache = ram_cache - self._convert_cache = convert_cache self._registry = registry def start(self, invoker: Invoker) -> None: @@ -45,11 +42,6 @@ def ram_cache(self) -> ModelCacheBase[AnyModel]: """Return the RAM cache used by this loader.""" return self._ram_cache - @property - def convert_cache(self) -> ModelConvertCacheBase: - """Return the checkpoint convert cache used by this loader.""" - return self._convert_cache - def load_model(self, model_config: AnyModelConfig, submodel_type: Optional[SubModelType] = None) -> LoadedModel: """ Given a model's configuration, load it and return the LoadedModel object. @@ -68,7 +60,6 @@ def load_model(self, model_config: AnyModelConfig, submodel_type: Optional[SubMo app_config=self._app_config, logger=self._logger, ram_cache=self._ram_cache, - convert_cache=self._convert_cache, ).load_model(model_config, submodel_type) if hasattr(self, "_invoker"): diff --git a/invokeai/app/services/model_manager/model_manager_default.py b/invokeai/app/services/model_manager/model_manager_default.py index 1a2b9a34022..f695c3c8c17 100644 --- a/invokeai/app/services/model_manager/model_manager_default.py +++ b/invokeai/app/services/model_manager/model_manager_default.py @@ -7,7 +7,7 @@ from typing_extensions import Self from invokeai.app.services.invoker import Invoker -from invokeai.backend.model_manager.load import ModelCache, ModelConvertCache, ModelLoaderRegistry +from invokeai.backend.model_manager.load import ModelCache, ModelLoaderRegistry from invokeai.backend.util.devices import TorchDevice from invokeai.backend.util.logging import InvokeAILogger @@ -86,11 +86,9 @@ def build_model_manager( logger=logger, execution_device=execution_device or TorchDevice.choose_torch_device(), ) - convert_cache = ModelConvertCache(cache_path=app_config.convert_cache_path, max_size=app_config.convert_cache) loader = ModelLoadService( app_config=app_config, ram_cache=ram_cache, - convert_cache=convert_cache, registry=ModelLoaderRegistry, ) installer = ModelInstallService( diff --git a/invokeai/app/services/shared/sqlite/sqlite_util.py b/invokeai/app/services/shared/sqlite/sqlite_util.py index cadf09f4575..05e430c4db0 100644 --- a/invokeai/app/services/shared/sqlite/sqlite_util.py +++ b/invokeai/app/services/shared/sqlite/sqlite_util.py @@ -13,6 +13,7 @@ from invokeai.app.services.shared.sqlite_migrator.migrations.migration_8 import build_migration_8 from invokeai.app.services.shared.sqlite_migrator.migrations.migration_9 import build_migration_9 from invokeai.app.services.shared.sqlite_migrator.migrations.migration_10 import build_migration_10 +from invokeai.app.services.shared.sqlite_migrator.migrations.migration_11 import build_migration_11 from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_impl import SqliteMigrator @@ -43,6 +44,7 @@ def init_db(config: InvokeAIAppConfig, logger: Logger, image_files: ImageFileSto migrator.register_migration(build_migration_8(app_config=config)) migrator.register_migration(build_migration_9()) migrator.register_migration(build_migration_10()) + migrator.register_migration(build_migration_11(app_config=config)) migrator.run_migrations() return db diff --git a/invokeai/app/services/shared/sqlite_migrator/migrations/migration_11.py b/invokeai/app/services/shared/sqlite_migrator/migrations/migration_11.py new file mode 100644 index 00000000000..d2c281110af --- /dev/null +++ b/invokeai/app/services/shared/sqlite_migrator/migrations/migration_11.py @@ -0,0 +1,36 @@ +import sqlite3 +import shutil + +from invokeai.app.services.config import InvokeAIAppConfig +from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration + + +class Migration11Callback: + def __init__(self, app_config: InvokeAIAppConfig) -> None: + self._app_config = app_config + + def __call__(self, cursor: sqlite3.Cursor) -> None: + self._remove_model_convert_cache_dir() + + def _remove_model_convert_cache_dir(self) -> None: + """ + Removes unused model convert cache directory + """ + convert_cache = self._app_config.convert_cache_path + print(f'DEBUG: convert_cache = {convert_cache}') + # shutil.rmtree(convert_cache) + + +def build_migration_11(app_config: InvokeAIAppConfig) -> Migration: + """ + Build the migration from database version 10 to 11. + + This migration removes the now-unused model convert cache directory. + """ + migration_11 = Migration( + from_version=10, + to_version=11, + callback=Migration11Callback(app_config), + ) + + return migration_11 diff --git a/invokeai/backend/model_manager/config.py b/invokeai/backend/model_manager/config.py index b19501843cf..1e92499d302 100644 --- a/invokeai/backend/model_manager/config.py +++ b/invokeai/backend/model_manager/config.py @@ -24,6 +24,7 @@ from enum import Enum from typing import Literal, Optional, Type, TypeAlias, Union +import diffusers import torch from diffusers.models.modeling_utils import ModelMixin from pydantic import BaseModel, ConfigDict, Discriminator, Field, Tag, TypeAdapter @@ -36,7 +37,7 @@ # ModelMixin is the base class for all diffusers and transformers models # RawModel is the InvokeAI wrapper class for ip_adapters, loras, textual_inversion and onnx runtime -AnyModel = Union[ModelMixin, RawModel, torch.nn.Module] +AnyModel = Union[ModelMixin, RawModel, torch.nn.Module, diffusers.DiffusionPipeline] class InvalidModelConfigException(Exception): diff --git a/invokeai/backend/model_manager/convert_ckpt_to_diffusers.py b/invokeai/backend/model_manager/convert_ckpt_to_diffusers.py deleted file mode 100644 index 450e69cf38a..00000000000 --- a/invokeai/backend/model_manager/convert_ckpt_to_diffusers.py +++ /dev/null @@ -1,83 +0,0 @@ -# Adapted for use in InvokeAI by Lincoln Stein, July 2023 -# -"""Conversion script for the Stable Diffusion checkpoints.""" - -from pathlib import Path -from typing import Optional - -import torch -from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL -from diffusers.pipelines.stable_diffusion.convert_from_ckpt import ( - convert_ldm_vae_checkpoint, - create_vae_diffusers_config, - download_controlnet_from_original_ckpt, - download_from_original_stable_diffusion_ckpt, -) -from omegaconf import DictConfig - -from . import AnyModel - - -def convert_ldm_vae_to_diffusers( - checkpoint: torch.Tensor | dict[str, torch.Tensor], - vae_config: DictConfig, - image_size: int, - dump_path: Optional[Path] = None, - precision: torch.dtype = torch.float16, -) -> AutoencoderKL: - """Convert a checkpoint-style VAE into a Diffusers VAE""" - vae_config = create_vae_diffusers_config(vae_config, image_size=image_size) - converted_vae_checkpoint = convert_ldm_vae_checkpoint(checkpoint, vae_config) - - vae = AutoencoderKL(**vae_config) - vae.load_state_dict(converted_vae_checkpoint) - vae.to(precision) - - if dump_path: - vae.save_pretrained(dump_path, safe_serialization=True) - - return vae - - -def convert_ckpt_to_diffusers( - checkpoint_path: str | Path, - dump_path: Optional[str | Path] = None, - precision: torch.dtype = torch.float16, - use_safetensors: bool = True, - **kwargs, -) -> AnyModel: - """ - Takes all the arguments of download_from_original_stable_diffusion_ckpt(), - and in addition a path-like object indicating the location of the desired diffusers - model to be written. - """ - pipe = download_from_original_stable_diffusion_ckpt(Path(checkpoint_path).as_posix(), **kwargs) - pipe = pipe.to(precision) - - # TO DO: save correct repo variant - if dump_path: - pipe.save_pretrained( - dump_path, - safe_serialization=use_safetensors, - ) - return pipe - - -def convert_controlnet_to_diffusers( - checkpoint_path: Path, - dump_path: Optional[Path] = None, - precision: torch.dtype = torch.float16, - **kwargs, -) -> AnyModel: - """ - Takes all the arguments of download_controlnet_from_original_ckpt(), - and in addition a path-like object indicating the location of the desired diffusers - model to be written. - """ - pipe = download_controlnet_from_original_ckpt(checkpoint_path.as_posix(), **kwargs) - pipe = pipe.to(precision) - - # TO DO: save correct repo variant - if dump_path: - pipe.save_pretrained(dump_path, safe_serialization=True) - return pipe diff --git a/invokeai/backend/model_manager/load/__init__.py b/invokeai/backend/model_manager/load/__init__.py index f47a2c43684..f862e071d49 100644 --- a/invokeai/backend/model_manager/load/__init__.py +++ b/invokeai/backend/model_manager/load/__init__.py @@ -6,7 +6,6 @@ from importlib import import_module from pathlib import Path -from .convert_cache.convert_cache_default import ModelConvertCache from .load_base import LoadedModel, ModelLoaderBase from .load_default import ModelLoader from .model_cache.model_cache_default import ModelCache @@ -20,7 +19,6 @@ __all__ = [ "LoadedModel", "ModelCache", - "ModelConvertCache", "ModelLoaderBase", "ModelLoader", "ModelLoaderRegistryBase", diff --git a/invokeai/backend/model_manager/load/convert_cache/__init__.py b/invokeai/backend/model_manager/load/convert_cache/__init__.py deleted file mode 100644 index 5be56d2d584..00000000000 --- a/invokeai/backend/model_manager/load/convert_cache/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .convert_cache_base import ModelConvertCacheBase -from .convert_cache_default import ModelConvertCache - -__all__ = ["ModelConvertCacheBase", "ModelConvertCache"] diff --git a/invokeai/backend/model_manager/load/convert_cache/convert_cache_base.py b/invokeai/backend/model_manager/load/convert_cache/convert_cache_base.py deleted file mode 100644 index ef363cc7f46..00000000000 --- a/invokeai/backend/model_manager/load/convert_cache/convert_cache_base.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Disk-based converted model cache. -""" - -from abc import ABC, abstractmethod -from pathlib import Path - - -class ModelConvertCacheBase(ABC): - @property - @abstractmethod - def max_size(self) -> float: - """Return the maximum size of this cache directory.""" - pass - - @abstractmethod - def make_room(self, size: float) -> None: - """ - Make sufficient room in the cache directory for a model of max_size. - - :param size: Size required (GB) - """ - pass - - @abstractmethod - def cache_path(self, key: str) -> Path: - """Return the path for a model with the indicated key.""" - pass diff --git a/invokeai/backend/model_manager/load/convert_cache/convert_cache_default.py b/invokeai/backend/model_manager/load/convert_cache/convert_cache_default.py deleted file mode 100644 index 8dc2aff74b7..00000000000 --- a/invokeai/backend/model_manager/load/convert_cache/convert_cache_default.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Placeholder for convert cache implementation. -""" - -import shutil -from pathlib import Path - -from invokeai.backend.util import GIG, directory_size -from invokeai.backend.util.logging import InvokeAILogger - -from .convert_cache_base import ModelConvertCacheBase - - -class ModelConvertCache(ModelConvertCacheBase): - def __init__(self, cache_path: Path, max_size: float = 10.0): - """Initialize the convert cache with the base directory and a limit on its maximum size (in GBs).""" - if not cache_path.exists(): - cache_path.mkdir(parents=True) - self._cache_path = cache_path - self._max_size = max_size - - # adjust cache size at startup in case it has been changed - if self._cache_path.exists(): - self.make_room(0.0) - - @property - def max_size(self) -> float: - """Return the maximum size of this cache directory (GB).""" - return self._max_size - - @max_size.setter - def max_size(self, value: float) -> None: - """Set the maximum size of this cache directory (GB).""" - self._max_size = value - - def cache_path(self, key: str) -> Path: - """Return the path for a model with the indicated key.""" - return self._cache_path / key - - def make_room(self, size: float) -> None: - """ - Make sufficient room in the cache directory for a model of max_size. - - :param size: Size required (GB) - """ - size_needed = directory_size(self._cache_path) + size - max_size = int(self.max_size) * GIG - logger = InvokeAILogger.get_logger() - - if size_needed <= max_size: - return - - logger.debug( - f"Convert cache has gotten too large {(size_needed / GIG):4.2f} > {(max_size / GIG):4.2f}G.. Trimming." - ) - - # For this to work, we make the assumption that the directory contains - # a 'model_index.json', 'unet/config.json' file, or a 'config.json' file at top level. - # This should be true for any diffusers model. - def by_atime(path: Path) -> float: - for config in ["model_index.json", "unet/config.json", "config.json"]: - sentinel = path / config - if sentinel.exists(): - return sentinel.stat().st_atime - - # no sentinel file found! - pick the most recent file in the directory - try: - atimes = sorted([x.stat().st_atime for x in path.iterdir() if x.is_file()], reverse=True) - return atimes[0] - except IndexError: - return 0.0 - - # sort by last access time - least accessed files will be at the end - lru_models = sorted(self._cache_path.iterdir(), key=by_atime, reverse=True) - logger.debug(f"cached models in descending atime order: {lru_models}") - while size_needed > max_size and len(lru_models) > 0: - next_victim = lru_models.pop() - victim_size = directory_size(next_victim) - logger.debug(f"Removing cached converted model {next_victim} to free {victim_size / GIG} GB") - shutil.rmtree(next_victim) - size_needed -= victim_size diff --git a/invokeai/backend/model_manager/load/load_base.py b/invokeai/backend/model_manager/load/load_base.py index 1bb093a9901..4a6aba87cac 100644 --- a/invokeai/backend/model_manager/load/load_base.py +++ b/invokeai/backend/model_manager/load/load_base.py @@ -18,7 +18,6 @@ AnyModelConfig, SubModelType, ) -from invokeai.backend.model_manager.load.convert_cache.convert_cache_base import ModelConvertCacheBase from invokeai.backend.model_manager.load.model_cache.model_cache_base import ModelCacheBase, ModelLockerBase @@ -106,7 +105,6 @@ def __init__( app_config: InvokeAIAppConfig, logger: Logger, ram_cache: ModelCacheBase[AnyModel], - convert_cache: ModelConvertCacheBase, ): """Initialize the loader.""" pass @@ -132,12 +130,6 @@ def get_size_fs( """Return size in bytes of the model, calculated before loading.""" pass - @property - @abstractmethod - def convert_cache(self) -> ModelConvertCacheBase: - """Return the convert cache associated with this loader.""" - pass - @property @abstractmethod def ram_cache(self) -> ModelCacheBase[AnyModel]: diff --git a/invokeai/backend/model_manager/load/load_default.py b/invokeai/backend/model_manager/load/load_default.py index f406b1a3910..21a119f6bce 100644 --- a/invokeai/backend/model_manager/load/load_default.py +++ b/invokeai/backend/model_manager/load/load_default.py @@ -12,8 +12,7 @@ InvalidModelConfigException, SubModelType, ) -from invokeai.backend.model_manager.config import DiffusersConfigBase, ModelType -from invokeai.backend.model_manager.load.convert_cache import ModelConvertCacheBase +from invokeai.backend.model_manager.config import DiffusersConfigBase from invokeai.backend.model_manager.load.load_base import LoadedModel, ModelLoaderBase from invokeai.backend.model_manager.load.model_cache.model_cache_base import ModelCacheBase, ModelLockerBase from invokeai.backend.model_manager.load.model_util import calc_model_size_by_data, calc_model_size_by_fs @@ -30,13 +29,11 @@ def __init__( app_config: InvokeAIAppConfig, logger: Logger, ram_cache: ModelCacheBase[AnyModel], - convert_cache: ModelConvertCacheBase, ): """Initialize the loader.""" self._app_config = app_config self._logger = logger self._ram_cache = ram_cache - self._convert_cache = convert_cache self._torch_dtype = TorchDevice.choose_torch_dtype() def load_model(self, model_config: AnyModelConfig, submodel_type: Optional[SubModelType] = None) -> LoadedModel: @@ -50,23 +47,15 @@ def load_model(self, model_config: AnyModelConfig, submodel_type: Optional[SubMo :param submodel_type: an ModelType enum indicating the portion of the model to retrieve (e.g. ModelType.Vae) """ - if model_config.type is ModelType.Main and not submodel_type: - raise InvalidModelConfigException("submodel_type is required when loading a main model") - model_path = self._get_model_path(model_config) if not model_path.exists(): raise InvalidModelConfigException(f"Files for model '{model_config.name}' not found at {model_path}") with skip_torch_weight_init(): - locker = self._convert_and_load(model_config, model_path, submodel_type) + locker = self._load_and_cache(model_config, submodel_type) return LoadedModel(config=model_config, _locker=locker) - @property - def convert_cache(self) -> ModelConvertCacheBase: - """Return the convert cache associated with this loader.""" - return self._convert_cache - @property def ram_cache(self) -> ModelCacheBase[AnyModel]: """Return the ram cache associated with this loader.""" @@ -76,9 +65,7 @@ def _get_model_path(self, config: AnyModelConfig) -> Path: model_base = self._app_config.models_path return (model_base / config.path).resolve() - def _convert_and_load( - self, config: AnyModelConfig, model_path: Path, submodel_type: Optional[SubModelType] = None - ) -> ModelLockerBase: + def _load_and_cache(self, config: AnyModelConfig, submodel_type: Optional[SubModelType] = None) -> ModelLockerBase: try: return self._ram_cache.get(config.key, submodel_type) except IndexError: @@ -86,13 +73,6 @@ def _convert_and_load( config.path = str(self._get_model_path(config)) loaded_model = self._load_model(config, submodel_type) - - # cache_path: Path = self._convert_cache.cache_path(config.key) - # if self._needs_conversion(config, model_path, cache_path): - # loaded_model = self._do_convert(config, model_path, cache_path, submodel_type) - # else: - # config.path = str(cache_path) if cache_path.exists() else str(self._get_model_path(config)) - # loaded_model = self._load_model(config, submodel_type) self._ram_cache.put( config.key, @@ -117,30 +97,6 @@ def get_size_fs( variant=config.repo_variant if isinstance(config, DiffusersConfigBase) else None, ) - def _do_convert( - self, config: AnyModelConfig, model_path: Path, cache_path: Path, submodel_type: Optional[SubModelType] = None - ) -> AnyModel: - self.convert_cache.make_room(calc_model_size_by_fs(model_path)) - pipeline = self._convert_model(config, model_path, cache_path if self.convert_cache.max_size > 0 else None) - if submodel_type: - # Proactively load the various submodels into the RAM cache so that we don't have to re-convert - # the entire pipeline every time a new submodel is needed. - for subtype in SubModelType: - if subtype == submodel_type: - continue - if submodel := getattr(pipeline, subtype.value, None): - self._ram_cache.put( - config.key, submodel_type=subtype, model=submodel, size=calc_model_size_by_data(submodel) - ) - return getattr(pipeline, submodel_type.value) if submodel_type else pipeline - - def _needs_conversion(self, config: AnyModelConfig, model_path: Path, dest_path: Path) -> bool: - return False - - # This needs to be implemented in subclasses that handle checkpoints - def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: - raise NotImplementedError - # This needs to be implemented in the subclass def _load_model( self, diff --git a/invokeai/backend/model_manager/load/model_loaders/controlnet.py b/invokeai/backend/model_manager/load/model_loaders/controlnet.py index ffd851e31f0..6b88e279bab 100644 --- a/invokeai/backend/model_manager/load/model_loaders/controlnet.py +++ b/invokeai/backend/model_manager/load/model_loaders/controlnet.py @@ -1,9 +1,10 @@ # Copyright (c) 2024, Lincoln D. Stein and the InvokeAI Development Team """Class for ControlNet model loading in InvokeAI.""" -from pathlib import Path from typing import Optional + from diffusers import ControlNetModel + from invokeai.backend.model_manager import ( AnyModel, AnyModelConfig, @@ -11,7 +12,7 @@ ModelFormat, ModelType, ) -from invokeai.backend.model_manager.config import SubModelType, ControlNetCheckpointConfig +from invokeai.backend.model_manager.config import ControlNetCheckpointConfig, SubModelType from .. import ModelLoaderRegistry from .generic_diffusers import GenericDiffusersLoader @@ -28,32 +29,11 @@ def _load_model( submodel_type: Optional[SubModelType] = None, ) -> AnyModel: if isinstance(config, ControlNetCheckpointConfig): - return ControlNetModel.from_single_file(config.path, - config=self._app_config.legacy_conf_path / config.config_path, - torch_dtype=self._torch_dtype, - local_files_only=True, - ) + return ControlNetModel.from_single_file( + config.path, + config=self._app_config.legacy_conf_path / config.config_path, + torch_dtype=self._torch_dtype, + local_files_only=True, + ) else: return super()._load_model(config, submodel_type) - - # def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: - # assert isinstance(config, CheckpointConfigBase) - # image_size = ( - # 512 - # if config.base == BaseModelType.StableDiffusion1 - # else 768 - # if config.base == BaseModelType.StableDiffusion2 - # else 1024 - # ) - - # self._logger.info(f"Converting {model_path} to diffusers format") - # with open(self._app_config.legacy_conf_path / config.config_path, "r") as config_stream: - # result = convert_controlnet_to_diffusers( - # model_path, - # output_path, - # original_config_file=config_stream, - # image_size=image_size, - # precision=self._torch_dtype, - # from_safetensors=model_path.suffix == ".safetensors", - # ) - # return result diff --git a/invokeai/backend/model_manager/load/model_loaders/lora.py b/invokeai/backend/model_manager/load/model_loaders/lora.py index 53814279ecd..aa0acab6bc2 100644 --- a/invokeai/backend/model_manager/load/model_loaders/lora.py +++ b/invokeai/backend/model_manager/load/model_loaders/lora.py @@ -15,7 +15,6 @@ ModelType, SubModelType, ) -from invokeai.backend.model_manager.load.convert_cache import ModelConvertCacheBase from invokeai.backend.model_manager.load.model_cache.model_cache_base import ModelCacheBase from .. import ModelLoader, ModelLoaderRegistry @@ -32,10 +31,9 @@ def __init__( app_config: InvokeAIAppConfig, logger: Logger, ram_cache: ModelCacheBase[AnyModel], - convert_cache: ModelConvertCacheBase, ): """Initialize the loader.""" - super().__init__(app_config, logger, ram_cache, convert_cache) + super().__init__(app_config, logger, ram_cache) self._model_base: Optional[BaseModelType] = None def _load_model( diff --git a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py index dce3990f373..15abfe593ae 100644 --- a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +++ b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py @@ -3,14 +3,14 @@ from pathlib import Path from typing import Optional + from diffusers import ( - StableDiffusionPipeline, StableDiffusionInpaintPipeline, - StableDiffusionXLPipeline, + StableDiffusionPipeline, StableDiffusionXLInpaintPipeline, + StableDiffusionXLPipeline, ) -from invokeai.backend.model_manager.load.model_util import calc_model_size_by_data from invokeai.backend.model_manager import ( AnyModel, AnyModelConfig, @@ -18,16 +18,14 @@ ModelFormat, ModelType, ModelVariantType, - SchedulerPredictionType, SubModelType, ) from invokeai.backend.model_manager.config import ( CheckpointConfigBase, DiffusersConfigBase, MainCheckpointConfig, - ModelVariantType, ) -from invokeai.backend.model_manager.convert_ckpt_to_diffusers import convert_ckpt_to_diffusers +from invokeai.backend.model_manager.load.model_util import calc_model_size_by_data from .. import ModelLoaderRegistry from .generic_diffusers import GenericDiffusersLoader @@ -56,12 +54,12 @@ def _load_model( config: AnyModelConfig, submodel_type: Optional[SubModelType] = None, ) -> AnyModel: - if not submodel_type is not None: - raise Exception("A submodel type must be provided when loading main pipelines.") - if isinstance(config, CheckpointConfigBase): return self._load_from_singlefile(config, submodel_type) + if not submodel_type is not None: + raise Exception("A submodel type must be provided when loading main pipelines.") + model_path = Path(config.path) load_class = self.get_hf_load_class(model_path, submodel_type) repo_variant = config.repo_variant if isinstance(config, DiffusersConfigBase) else None @@ -84,9 +82,9 @@ def _load_model( return result def _load_from_singlefile( - self, - config: AnyModelConfig, - submodel_type: SubModelType, + self, + config: AnyModelConfig, + submodel_type: Optional[SubModelType] = None, ) -> AnyModel: load_classes = { BaseModelType.StableDiffusion1: { @@ -100,22 +98,32 @@ def _load_from_singlefile( BaseModelType.StableDiffusionXL: { ModelVariantType.Normal: StableDiffusionXLPipeline, ModelVariantType.Inpaint: StableDiffusionXLInpaintPipeline, - } + }, } assert isinstance(config, MainCheckpointConfig) try: load_class = load_classes[config.base][config.variant] except KeyError as e: - raise Exception(f'No diffusers pipeline known for base={config.base}, variant={config.variant}') from e - original_config_file=self._app_config.legacy_conf_path / config.config_path # should try without using this... - pipeline = load_class.from_single_file(config.path, - config=original_config_file, - torch_dtype=self._torch_dtype, - local_files_only=True, - ) - - # Proactively load the various submodels into the RAM cache so that we don't have to re-convert + raise Exception(f"No diffusers pipeline known for base={config.base}, variant={config.variant}") from e + original_config_file = self._app_config.legacy_conf_path / config.config_path + prediction_type = config.prediction_type.value + upcast_attention = config.upcast_attention + + pipeline = load_class.from_single_file( + config.path, + config=original_config_file, + torch_dtype=self._torch_dtype, + local_files_only=True, + prediction_type=prediction_type, + upcast_attention=upcast_attention, + load_safety_checker=False, + ) + + # Proactively load the various submodels into the RAM cache so that we don't have to re-load # the entire pipeline every time a new submodel is needed. + if not submodel_type: + return pipeline + for subtype in SubModelType: if subtype == submodel_type: continue @@ -124,48 +132,3 @@ def _load_from_singlefile( config.key, submodel_type=subtype, model=submodel, size=calc_model_size_by_data(submodel) ) return getattr(pipeline, submodel_type.value) - - - # def _needs_conversion(self, config: AnyModelConfig, model_path: Path, dest_path: Path) -> bool: - # if not isinstance(config, CheckpointConfigBase): - # return False - # elif ( - # dest_path.exists() - # and (dest_path / "model_index.json").stat().st_mtime >= (config.converted_at or 0.0) - # and (dest_path / "model_index.json").stat().st_mtime >= model_path.stat().st_mtime - # ): - # return False - # else: - # return True - - # def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: - # assert isinstance(config, MainCheckpointConfig) - # base = config.base - - # prediction_type = config.prediction_type.value - # upcast_attention = config.upcast_attention - # image_size = ( - # 1024 - # if base == BaseModelType.StableDiffusionXL - # else 768 - # if config.prediction_type == SchedulerPredictionType.VPrediction and base == BaseModelType.StableDiffusion2 - # else 512 - # ) - - # self._logger.info(f"Converting {model_path} to diffusers format") - - # loaded_model = convert_ckpt_to_diffusers( - # model_path, - # output_path, - # model_type=self.model_base_to_model_type[base], - # original_config_file=self._app_config.legacy_conf_path / config.config_path, - # extract_ema=True, - # from_safetensors=model_path.suffix == ".safetensors", - # precision=self._torch_dtype, - # prediction_type=prediction_type, - # image_size=image_size, - # upcast_attention=upcast_attention, - # load_safety_checker=False, - # num_in_channels=VARIANT_TO_IN_CHANNEL_MAP[config.variant], - # ) - # return loaded_model diff --git a/invokeai/backend/model_manager/load/model_loaders/vae.py b/invokeai/backend/model_manager/load/model_loaders/vae.py index 81cf36130dc..f278a2069ed 100644 --- a/invokeai/backend/model_manager/load/model_loaders/vae.py +++ b/invokeai/backend/model_manager/load/model_loaders/vae.py @@ -1,13 +1,9 @@ # Copyright (c) 2024, Lincoln D. Stein and the InvokeAI Development Team """Class for VAE model loading in InvokeAI.""" -from diffusers import AutoencoderKL -from pathlib import Path from typing import Optional -import torch -from omegaconf import DictConfig, OmegaConf -from safetensors.torch import load_file as safetensors_load_file +from diffusers import AutoencoderKL from invokeai.backend.model_manager import ( AnyModelConfig, @@ -15,8 +11,7 @@ ModelFormat, ModelType, ) -from invokeai.backend.model_manager.config import AnyModel, CheckpointConfigBase, SubModelType, VAECheckpointConfig -from invokeai.backend.model_manager.convert_ckpt_to_diffusers import convert_ldm_vae_to_diffusers +from invokeai.backend.model_manager.config import AnyModel, SubModelType, VAECheckpointConfig from .. import ModelLoaderRegistry from .generic_diffusers import GenericDiffusersLoader @@ -34,39 +29,11 @@ def _load_model( submodel_type: Optional[SubModelType] = None, ) -> AnyModel: if isinstance(config, VAECheckpointConfig): - return AutoencoderKL.from_single_file(config.path, - config=self._app_config.legacy_conf_path / config.config_path, - torch_dtype=self._torch_dtype, - local_files_only=True, - ) + return AutoencoderKL.from_single_file( + config.path, + config=self._app_config.legacy_conf_path / config.config_path, + torch_dtype=self._torch_dtype, + local_files_only=True, + ) else: return super()._load_model(config, submodel_type) - - # def _convert_model(self, config: AnyModelConfig, model_path: Path, output_path: Optional[Path] = None) -> AnyModel: - # # TODO(MM2): check whether sdxl VAE models convert. - # if config.base not in {BaseModelType.StableDiffusion1, BaseModelType.StableDiffusion2}: - # raise Exception(f"VAE conversion not supported for model type: {config.base}") - # else: - # assert isinstance(config, CheckpointConfigBase) - # config_file = self._app_config.legacy_conf_path / config.config_path - - # if model_path.suffix == ".safetensors": - # checkpoint = safetensors_load_file(model_path, device="cpu") - # else: - # checkpoint = torch.load(model_path, map_location="cpu") - - # # sometimes weights are hidden under "state_dict", and sometimes not - # if "state_dict" in checkpoint: - # checkpoint = checkpoint["state_dict"] - - # ckpt_config = OmegaConf.load(config_file) - # assert isinstance(ckpt_config, DictConfig) - # self._logger.info(f"Converting {model_path} to diffusers format") - # vae_model = convert_ldm_vae_to_diffusers( - # checkpoint=checkpoint, - # vae_config=ckpt_config, - # image_size=512, - # precision=self._torch_dtype, - # dump_path=output_path, - # ) - # return vae_model diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index 8f33e4b49fa..2e34c2fb1d1 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -257,6 +257,8 @@ def get_model_type_from_folder(cls, folder_path: Path) -> ModelType: if (folder_path / "image_encoder.txt").exists(): return ModelType.IPAdapter + print(f'DEBUG: {folder_path} contents = {list(folder_path.glob("**"))}') + i = folder_path / "model_index.json" c = folder_path / "config.json" config_path = i if i.exists() else c if c.exists() else None diff --git a/tests/backend/model_manager/model_manager_fixtures.py b/tests/backend/model_manager/model_manager_fixtures.py index 5ddccd05bb4..60da14db6d6 100644 --- a/tests/backend/model_manager/model_manager_fixtures.py +++ b/tests/backend/model_manager/model_manager_fixtures.py @@ -25,7 +25,7 @@ ModelVariantType, VAEDiffusersConfig, ) -from invokeai.backend.model_manager.load import ModelCache, ModelConvertCache +from invokeai.backend.model_manager.load import ModelCache from invokeai.backend.util.logging import InvokeAILogger from tests.backend.model_manager.model_metadata.metadata_examples import ( HFTestLoraMetadata, @@ -82,17 +82,15 @@ def mm2_download_queue(mm2_session: Session) -> DownloadQueueServiceBase: @pytest.fixture -def mm2_loader(mm2_app_config: InvokeAIAppConfig, mm2_record_store: ModelRecordServiceBase) -> ModelLoadServiceBase: +def mm2_loader(mm2_app_config: InvokeAIAppConfig) -> ModelLoadServiceBase: ram_cache = ModelCache( logger=InvokeAILogger.get_logger(), max_cache_size=mm2_app_config.ram, max_vram_cache_size=mm2_app_config.vram, ) - convert_cache = ModelConvertCache(mm2_app_config.convert_cache_path) return ModelLoadService( app_config=mm2_app_config, ram_cache=ram_cache, - convert_cache=convert_cache, ) From 76349911075f20912caeaab88e9b926b6cc12aaa Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Wed, 12 Jun 2024 16:19:41 -0400 Subject: [PATCH 04/10] rename migration_11 before conflict merge with main --- .../migrations/{migration_11.py => migration_12.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename invokeai/app/services/shared/sqlite_migrator/migrations/{migration_11.py => migration_12.py} (100%) diff --git a/invokeai/app/services/shared/sqlite_migrator/migrations/migration_11.py b/invokeai/app/services/shared/sqlite_migrator/migrations/migration_12.py similarity index 100% rename from invokeai/app/services/shared/sqlite_migrator/migrations/migration_11.py rename to invokeai/app/services/shared/sqlite_migrator/migrations/migration_12.py From 6b788bff51917a4bc4d62f8b87d460e5cca5fc1d Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Sat, 15 Jun 2024 19:08:15 -0400 Subject: [PATCH 05/10] Update invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py Co-authored-by: Ryan Dick --- .../model_manager/load/model_loaders/stable_diffusion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py index 89a23b7152e..7e83e03684f 100644 --- a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +++ b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py @@ -57,7 +57,7 @@ def _load_model( if isinstance(config, CheckpointConfigBase): return self._load_from_singlefile(config, submodel_type) - if not submodel_type is not None: + if submodel_type is None: raise Exception("A submodel type must be provided when loading main pipelines.") model_path = Path(config.path) From 1411fbbd1ade445b0ca3e6fa8b677854ca9698d4 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Sat, 15 Jun 2024 19:08:29 -0400 Subject: [PATCH 06/10] Update invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py Co-authored-by: Ryan Dick --- .../model_manager/load/model_loaders/stable_diffusion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py index 7e83e03684f..829c4a9d5cd 100644 --- a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +++ b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py @@ -128,11 +128,11 @@ def _load_from_singlefile( load_safety_checker=False, ) - # Proactively load the various submodels into the RAM cache so that we don't have to re-load - # the entire pipeline every time a new submodel is needed. if not submodel_type: return pipeline + # Proactively load the various submodels into the RAM cache so that we don't have to re-load + # the entire pipeline every time a new submodel is needed. for subtype in SubModelType: if subtype == submodel_type: continue From 17e9d4f7afa220ec2ba05e2eae625d25d2265e55 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Sat, 15 Jun 2024 19:57:35 -0400 Subject: [PATCH 07/10] implement lightweight version-by-version config migration --- .../app/services/config/config_default.py | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/invokeai/app/services/config/config_default.py b/invokeai/app/services/config/config_default.py index 4393930fa80..5575f1c89e5 100644 --- a/invokeai/app/services/config/config_default.py +++ b/invokeai/app/services/config/config_default.py @@ -352,14 +352,14 @@ def settings_customise_sources( return (init_settings,) -def migrate_v3_config_dict(config_dict: dict[str, Any]) -> InvokeAIAppConfig: - """Migrate a v3 config dictionary to a current config object. +def migrate_v3_config_dict(config_dict: dict[str, Any]) -> dict[str, Any]: + """Migrate a v3 config dictionary to a v4.0.0. Args: config_dict: A dictionary of settings from a v3 config file. Returns: - An instance of `InvokeAIAppConfig` with the migrated settings. + An `InvokeAIAppConfig` config dict. """ parsed_config_dict: dict[str, Any] = {} @@ -393,35 +393,50 @@ def migrate_v3_config_dict(config_dict: dict[str, Any]) -> InvokeAIAppConfig: elif k in InvokeAIAppConfig.model_fields: # skip unknown fields parsed_config_dict[k] = v - # When migrating the config file, we should not include currently-set environment variables. - config = DefaultInvokeAIAppConfig.model_validate(parsed_config_dict) - - return config + parsed_config_dict["schema_version"] = "4.0.0" + return parsed_config_dict -def migrate_v4_0_X_config_dict(config_dict: dict[str, Any]) -> InvokeAIAppConfig: - """Migrate v4.0.{0,1} config dictionary to a current config object. +def migrate_v4_0_0_to_4_0_1_config_dict(config_dict: dict[str, Any]) -> dict[str, Any]: + """Migrate v4.0.0 config dictionary to a v4.0.1 config dictionary Args: - config_dict: A dictionary of settings from a v4.0.{0,1} config file. + config_dict: A dictionary of settings from a v4.0.0 config file. Returns: - An instance of `InvokeAIAppConfig` with the migrated settings. + A config dict with the settings migrated to v4.0.1. """ parsed_config_dict: dict[str, Any] = {} for k, v in config_dict.items(): # autocast was removed from precision in v4.0.1 if k == "precision" and v == "autocast": parsed_config_dict["precision"] = "auto" - # convert_cache was removed in v4.0.2 - elif k == "convert_cache": + else: + parsed_config_dict[k] = v + if k == "schema_version": + parsed_config_dict[k] = "4.0.1" + return parsed_config_dict + + +def migrate_v4_0_1_to_4_0_2_config_dict(config_dict: dict[str, Any]) -> dict[str, Any]: + """Migrate v4.0.1 config dictionary to a v4.0.2 config dictionary. + + Args: + config_dict: A dictionary of settings from a v4.0.1 config file. + + Returns: + An config dict with the settings migrated to v4.0.2. + """ + parsed_config_dict: dict[str, Any] = {} + for k, v in config_dict.items(): + # autocast was removed from precision in v4.0.1 + if k == "convert_cache": continue else: parsed_config_dict[k] = v if k == "schema_version": - parsed_config_dict[k] = CONFIG_SCHEMA_VERSION - config = DefaultInvokeAIAppConfig.model_validate(parsed_config_dict) - return config + parsed_config_dict[k] = "4.0.2" + return parsed_config_dict def load_and_migrate_config(config_path: Path) -> InvokeAIAppConfig: @@ -435,27 +450,31 @@ def load_and_migrate_config(config_path: Path) -> InvokeAIAppConfig: """ assert config_path.suffix == ".yaml" with open(config_path, "rt", encoding=locale.getpreferredencoding()) as file: - loaded_config_dict = yaml.safe_load(file) + loaded_config_dict: dict[str, Any] = yaml.safe_load(file) assert isinstance(loaded_config_dict, dict) + migrated = False if "InvokeAI" in loaded_config_dict: - # This is a v3 config file, attempt to migrate it + migrated = True + loaded_config_dict = migrate_v3_config_dict(loaded_config_dict) # pyright: ignore [reportUnknownArgumentType] + if loaded_config_dict["schema_version"] == "4.0.0": + migrated = True + loaded_config_dict = migrate_v4_0_0_to_4_0_1_config_dict(loaded_config_dict) + if loaded_config_dict["schema_version"] == "4.0.1": + migrated = True + loaded_config_dict = migrate_v4_0_1_to_4_0_2_config_dict(loaded_config_dict) + + if migrated: shutil.copy(config_path, config_path.with_suffix(".yaml.bak")) try: - # loaded_config_dict could be the wrong shape, but we will catch all exceptions below - migrated_config = migrate_v3_config_dict(loaded_config_dict) # pyright: ignore [reportUnknownArgumentType] + # load and write without environment variables + migrated_config = DefaultInvokeAIAppConfig.model_validate(loaded_config_dict) + migrated_config.write_file(config_path) except Exception as e: shutil.copy(config_path.with_suffix(".yaml.bak"), config_path) raise RuntimeError(f"Failed to load and migrate v3 config file {config_path}: {e}") from e - migrated_config.write_file(config_path) - return migrated_config - - if loaded_config_dict["schema_version"] in ["4.0.0", "4.0.1"]: - loaded_config_dict = migrate_v4_0_X_config_dict(loaded_config_dict) - loaded_config_dict.write_file(config_path) - # Attempt to load as a v4 config file try: # Meta is not included in the model fields, so we need to validate it separately config = InvokeAIAppConfig.model_validate(loaded_config_dict) From c87cad3e91b04c163c0049490491b88fde5f6d30 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Tue, 18 Jun 2024 13:43:12 -0400 Subject: [PATCH 08/10] simplified config schema migration code --- .../app/services/config/config_default.py | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/invokeai/app/services/config/config_default.py b/invokeai/app/services/config/config_default.py index 5575f1c89e5..9fc6b4e0bd8 100644 --- a/invokeai/app/services/config/config_default.py +++ b/invokeai/app/services/config/config_default.py @@ -3,6 +3,7 @@ from __future__ import annotations +import copy import locale import os import re @@ -84,7 +85,7 @@ class InvokeAIAppConfig(BaseSettings): log_tokenization: Enable logging of parsed prompt tokens. patchmatch: Enable patchmatch inpaint code. models_dir: Path to the models directory. - convert_cache_dir: Path to the converted models cache directory (DEPRECATED). + convert_cache_dir: Path to the converted models cache directory (DEPRECATED, but do not delete because it is needed for migration from previous versions). download_cache_dir: Path to the directory that contains dynamically downloaded models. legacy_conf_dir: Path to directory of legacy checkpoint config files. db_dir: Path to InvokeAI databases directory. @@ -145,7 +146,7 @@ class InvokeAIAppConfig(BaseSettings): # PATHS models_dir: Path = Field(default=Path("models"), description="Path to the models directory.") - convert_cache_dir: Path = Field(default=Path("models/.convert_cache"), description="Path to the converted models cache directory (DEPRECATED).") + convert_cache_dir: Path = Field(default=Path("models/.convert_cache"), description="Path to the converted models cache directory (DEPRECATED, but do not delete because it is needed for migration from previous versions).") download_cache_dir: Path = Field(default=Path("models/.download_cache"), description="Path to the directory that contains dynamically downloaded models.") legacy_conf_dir: Path = Field(default=Path("configs"), description="Path to directory of legacy checkpoint config files.") db_dir: Path = Field(default=Path("databases"), description="Path to InvokeAI databases directory.") @@ -406,15 +407,11 @@ def migrate_v4_0_0_to_4_0_1_config_dict(config_dict: dict[str, Any]) -> dict[str Returns: A config dict with the settings migrated to v4.0.1. """ - parsed_config_dict: dict[str, Any] = {} - for k, v in config_dict.items(): - # autocast was removed from precision in v4.0.1 - if k == "precision" and v == "autocast": - parsed_config_dict["precision"] = "auto" - else: - parsed_config_dict[k] = v - if k == "schema_version": - parsed_config_dict[k] = "4.0.1" + parsed_config_dict: dict[str, Any] = copy.deepcopy(config_dict) + # precision "autocast" was replaced by "auto" in v4.0.1 + if parsed_config_dict.get("precision") == "autocast": + parsed_config_dict["precision"] = "auto" + parsed_config_dict["schema_version"] = "4.0.1" return parsed_config_dict @@ -427,15 +424,10 @@ def migrate_v4_0_1_to_4_0_2_config_dict(config_dict: dict[str, Any]) -> dict[str Returns: An config dict with the settings migrated to v4.0.2. """ - parsed_config_dict: dict[str, Any] = {} - for k, v in config_dict.items(): - # autocast was removed from precision in v4.0.1 - if k == "convert_cache": - continue - else: - parsed_config_dict[k] = v - if k == "schema_version": - parsed_config_dict[k] = "4.0.2" + parsed_config_dict: dict[str, Any] = copy.deepcopy(config_dict) + # convert_cache was removed in 4.0.2 + parsed_config_dict.pop("convert_cache", None) + parsed_config_dict["schema_version"] = "4.0.2" return parsed_config_dict From 349239e336f792d87e3afc6f9350d3245341292f Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Wed, 19 Jun 2024 23:43:56 -0400 Subject: [PATCH 09/10] associate sdxl config with sdxl VAEs --- invokeai/backend/model_manager/probe.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/invokeai/backend/model_manager/probe.py b/invokeai/backend/model_manager/probe.py index a19a7727642..2f18f1a8a60 100644 --- a/invokeai/backend/model_manager/probe.py +++ b/invokeai/backend/model_manager/probe.py @@ -312,6 +312,8 @@ def _get_checkpoint_config_path( config_file = ( "stable-diffusion/v1-inference.yaml" if base_type is BaseModelType.StableDiffusion1 + else "stable-diffusion/sd_xl_base.yaml" + if base_type is BaseModelType.StableDiffusionXL else "stable-diffusion/v2-inference.yaml" ) else: From 5c8cf991a9c028aa1cfd294fa9e2cea43edc8e84 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Thu, 20 Jun 2024 21:21:21 -0400 Subject: [PATCH 10/10] remove use of original_config_file in load_single_file() --- .../backend/model_manager/load/model_loaders/controlnet.py | 2 -- .../model_manager/load/model_loaders/stable_diffusion.py | 3 --- invokeai/backend/model_manager/load/model_loaders/vae.py | 2 -- 3 files changed, 7 deletions(-) diff --git a/invokeai/backend/model_manager/load/model_loaders/controlnet.py b/invokeai/backend/model_manager/load/model_loaders/controlnet.py index 6b88e279bab..b2fae37d292 100644 --- a/invokeai/backend/model_manager/load/model_loaders/controlnet.py +++ b/invokeai/backend/model_manager/load/model_loaders/controlnet.py @@ -31,9 +31,7 @@ def _load_model( if isinstance(config, ControlNetCheckpointConfig): return ControlNetModel.from_single_file( config.path, - config=self._app_config.legacy_conf_path / config.config_path, torch_dtype=self._torch_dtype, - local_files_only=True, ) else: return super()._load_model(config, submodel_type) diff --git a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py index 829c4a9d5cd..95caf848e5d 100644 --- a/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +++ b/invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py @@ -105,7 +105,6 @@ def _load_from_singlefile( load_class = load_classes[config.base][config.variant] except KeyError as e: raise Exception(f"No diffusers pipeline known for base={config.base}, variant={config.variant}") from e - original_config_file = self._app_config.legacy_conf_path / config.config_path prediction_type = config.prediction_type.value upcast_attention = config.upcast_attention @@ -120,9 +119,7 @@ def _load_from_singlefile( with SilenceWarnings(): pipeline = load_class.from_single_file( config.path, - config=original_config_file, torch_dtype=self._torch_dtype, - local_files_only=True, prediction_type=prediction_type, upcast_attention=upcast_attention, load_safety_checker=False, diff --git a/invokeai/backend/model_manager/load/model_loaders/vae.py b/invokeai/backend/model_manager/load/model_loaders/vae.py index d6a82479f8b..3c496f59ab2 100644 --- a/invokeai/backend/model_manager/load/model_loaders/vae.py +++ b/invokeai/backend/model_manager/load/model_loaders/vae.py @@ -30,9 +30,7 @@ def _load_model( if isinstance(config, VAECheckpointConfig): return AutoencoderKL.from_single_file( config.path, - config=self._app_config.legacy_conf_path / config.config_path, torch_dtype=self._torch_dtype, - local_files_only=True, ) else: return super()._load_model(config, submodel_type)