diff --git a/CHANGELOG.md b/CHANGELOG.md index 76fa1e083..ec4f4ee3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ *relighting*: automatic background replacement with reglighting so source image fits desired background with optional composite blending available in *img2img or control -> scripts* - - **TAESD** is now default preview type since its the only one that supports most new models - Add **FLUX.1-Kontext-Dev** inpaint workflow - Support **FLUX.1** all-in-one safetensors - Support TAESD preview and remote VAE for **HunyuanDit** @@ -25,14 +24,22 @@ enable in *settings -> compute settings -> sdp options* *note*: SD.Next will use either SageAttention v1/v2/v2++, depending which one is installed until authors provide pre-build wheels for v2++, you need to install it manually or SD.Next will auto-install v1 +- **Other** + - **TAESD** is now default preview type since its the only one that supports most new models + - SD.Next now starts with *locked* state preventing model loading until startup is complete +- **API** + - add `/sdapi/v1/lock-checkpoint` endpoint that can be used to lock/unlock model changes + if model is locked, it cannot be changed using normal load or unload methods - **Fixes** - allow theme type `None` to be set in config - installer dont cache installed state - fix Cosmos-Predict2 retrying TAESD download - better handle startup import errors + - fix ansi controle output from scripts/extensions - fix diffusers models non-unique hash - fix loading of manually downloaded diffuser models - fix api `/sdapi/v1/embeddings` endpoint + - improve extensions ui search - improve model type autodetection - improve model auth check for hf repos - improve Chroma prompt padding as per recommendations diff --git a/modules/api/api.py b/modules/api/api.py index 7d47c6ade..5e4d65198 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -94,6 +94,7 @@ class Api: self.add_api_route("/sdapi/v1/refresh-checkpoints", endpoints.post_refresh_checkpoints, methods=["POST"]) self.add_api_route("/sdapi/v1/unload-checkpoint", endpoints.post_unload_checkpoint, methods=["POST"]) self.add_api_route("/sdapi/v1/reload-checkpoint", endpoints.post_reload_checkpoint, methods=["POST"]) + self.add_api_route("/sdapi/v1/lock-checkpoint", endpoints.post_lock_checkpoint, methods=["POST"]) self.add_api_route("/sdapi/v1/refresh-vae", endpoints.post_refresh_vae, methods=["POST"]) self.add_api_route("/sdapi/v1/latents", endpoints.get_latent_history, methods=["GET"], response_model=List[str]) self.add_api_route("/sdapi/v1/latents", endpoints.post_latent_history, methods=["POST"], response_model=int) diff --git a/modules/api/endpoints.py b/modules/api/endpoints.py index cc1c0f192..28c573559 100644 --- a/modules/api/endpoints.py +++ b/modules/api/endpoints.py @@ -115,6 +115,11 @@ def post_reload_checkpoint(force:bool=False): sd_models.reload_model_weights() return {} +def post_lock_checkpoint(lock:bool=False): + from modules import modeldata + modeldata.model_data.locked = lock + return {} + def get_checkpoint(): if not shared.sd_loaded or shared.sd_model is None: checkpoint = { diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 79fe68d50..806b51e42 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -65,7 +65,8 @@ def main_args(): def compatibility_args(): # removed args are added here as hidden in fixed format for compatbility reasons group_compat = parser.add_argument_group('Compatibility options') - group_compat.add_argument('--backend', type=str, default=os.environ.get("SD_BACKEND", None), choices=['diffusers', 'original'], required=False, help='obsolete') + group_compat.add_argument('--backend', type=str, choices=['diffusers', 'original'], help=argparse.SUPPRESS) + group_compat.add_argument('--hypernetwork-dir', default='.', help=argparse.SUPPRESS) group_compat.add_argument("--allow-code", default=os.environ.get("SD_ALLOWCODE", False), action='store_true', help=argparse.SUPPRESS) group_compat.add_argument("--enable_insecure_extension_access", default=os.environ.get("SD_INSECURE", False), action='store_true', help=argparse.SUPPRESS) group_compat.add_argument("--use-cpu", nargs='+', default=[], type=str.lower, help=argparse.SUPPRESS) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py new file mode 100644 index 000000000..6d947fd3f --- /dev/null +++ b/modules/infotext_utils.py @@ -0,0 +1,3 @@ +# a1111 compatibility module: unused + +from modules.infotext import parse as parse_generation_parameters # pylint: disable=unused-import diff --git a/modules/modeldata.py b/modules/modeldata.py index 789cccfef..51cbc05df 100644 --- a/modules/modeldata.py +++ b/modules/modeldata.py @@ -1,3 +1,4 @@ +import os import sys import threading from modules import shared, errors @@ -77,15 +78,20 @@ class ModelData: self.sd_refiner = None self.sd_dict = 'None' self.initial = True + self.locked = True self.lock = threading.Lock() def get_sd_model(self): - from modules.sd_models import reload_model_weights - if self.sd_model is None and shared.opts.sd_model_checkpoint != 'None' and not self.lock.locked(): + if self.locked: + if self.sd_model is None: + fn = f'{os.path.basename(sys._getframe(2).f_code.co_filename)}:{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access + shared.log.warning(f'Model locked: fn={fn}') + return self.sd_model + elif (self.sd_model is None) and (shared.opts.sd_model_checkpoint != 'None') and (not self.lock.locked()): with self.lock: try: - # note: reload_model_weights directly updates model_data.sd_model and returns it at the end - self.sd_model = reload_model_weights(op='model') + from modules.sd_models import reload_model_weights + self.sd_model = reload_model_weights(op='model') # note: reload_model_weights directly updates model_data.sd_model and returns it at the end self.initial = False except Exception as e: shared.log.error("Failed to load stable diffusion model") @@ -94,13 +100,14 @@ class ModelData: return self.sd_model def set_sd_model(self, v): - self.sd_model = v + if not self.locked: + self.sd_model = v def get_sd_refiner(self): - from modules.sd_models import reload_model_weights - if self.sd_refiner is None and shared.opts.sd_model_refiner != 'None' and not self.lock.locked(): + if (self.sd_refiner is None) and (shared.opts.sd_model_refiner != 'None') and (not self.lock.locked()): with self.lock: try: + from modules.sd_models import reload_model_weights self.sd_refiner = reload_model_weights(op='refiner') self.initial = False except Exception as e: @@ -110,7 +117,8 @@ class ModelData: return self.sd_refiner def set_sd_refiner(self, v): - self.sd_refiner = v + if not self.locked: + self.sd_refiner = v # provides shared.sd_model field as a property @@ -124,7 +132,7 @@ class Shared(sys.modules[__name__].__class__): def sd_model(self): import modules.sd_models # pylint: disable=W0621 if modules.sd_models.model_data.sd_model is None: - fn = f'{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access + fn = f'{os.path.basename(sys._getframe(2).f_code.co_filename)}:{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access shared.log.debug(f'Model requested: fn={fn}') # pylint: disable=protected-access return modules.sd_models.model_data.get_sd_model() diff --git a/modules/script_loading.py b/modules/script_loading.py index 37f64b33f..0dd243214 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -26,7 +26,10 @@ def load_module(path): setup_logging() # reset since scripts can hijaack logging for line in stdout.getvalue().splitlines(): if len(line) > 0: - errors.log.info(f"Extension: script='{os.path.relpath(path)}' {line.strip()}") + if '2;36m' in line: # color escape sequence + print(line.strip()) + else: + errors.log.info(f"Extension: script='{os.path.relpath(path)}' {line.strip()}") except Exception as e: errors.display(e, f'Module load: {path}') return module diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index bc00a82ab..543f4b481 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -354,7 +354,13 @@ def create_html(search_text, sort_column): visible = 'table-row' if search_text: s = search_text.strip().lower() - if s not in html.escape(ext.get("name", "unknown")).lower() and s not in html.escape(ext.get("description", "")).lower() and s not in html.escape(tags_string).lower() and s not in author.lower(): + if ( + s not in html.escape(ext.get("name", "unknown")).lower() + and s not in html.escape(ext.get("description", "")).lower() + and s not in html.escape(ext.get("url", "")).lower() + and s not in html.escape(tags_string).lower() + and s not in author.lower() + ): stats['hidden'] += 1 visible = 'none' stats['processed'] += 1 diff --git a/webui.py b/webui.py index 236b220f2..4be187d26 100644 --- a/webui.py +++ b/webui.py @@ -13,7 +13,7 @@ import modules.loader import modules.hashes from installer import log, git_commit, custom_excepthook -from modules import timer, paths, shared, extensions, gr_tempdir, modelloader +from modules import timer, paths, shared, extensions, gr_tempdir, modelloader, modeldata from modules.call_queue import queue_lock, wrap_queued_call, wrap_gradio_gpu_call # pylint: disable=unused-import import modules.devices import modules.sd_checkpoint @@ -146,6 +146,7 @@ def initialize(): def load_model(): + modeldata.model_data.locked = False if not shared.opts.sd_checkpoint_autoload and shared.cmd_opts.ckpt is None: log.info('Model: autoload=False') else: