From 09b4c79f4a2de45e53d850ec94ed6ea4873ce2d7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 7 Sep 2023 10:40:07 -0400 Subject: [PATCH 01/64] add job info to all jobs --- launch.py | 17 +++++++++-------- modules/api/api.py | 14 +++++++------- modules/call_queue.py | 15 ++++++--------- modules/extras.py | 8 ++------ modules/interrogate.py | 3 +-- modules/modelloader.py | 9 +++------ modules/postprocessing.py | 3 +-- modules/sd_vae.py | 2 ++ modules/shared.py | 25 +++++++++++++++---------- modules/ui_extra_networks.py | 2 +- webui.py | 4 ++-- 11 files changed, 49 insertions(+), 53 deletions(-) diff --git a/launch.py b/launch.py index 6afbd7458..ab1530fac 100644 --- a/launch.py +++ b/launch.py @@ -150,12 +150,12 @@ def start_server(immediate=True, server=None): server.wants_restart = False else: if args.api_only: - server = server.api_only() + uvicorn = server.api_only() else: - server = server.webui(restart=not immediate) + uvicorn = server.webui(restart=not immediate) if args.profile: installer.print_profile(pr, 'WebUI') - return server + return uvicorn, server if __name__ == "__main__": @@ -207,20 +207,21 @@ if __name__ == "__main__": # installer.log.debug(f"Args: {vars(args)}") logging.disable(logging.NOTSET if args.debug else logging.DEBUG) - instance = start_server(immediate=True, server=None) + uv, instance = start_server(immediate=True, server=None) while True: try: - alive = instance.thread.is_alive() - requests = instance.server_state.total_requests if hasattr(instance, 'server_state') else 0 + alive = uv.thread.is_alive() + requests = uv.server_state.total_requests if hasattr(uv, 'server_state') else 0 except Exception: alive = False requests = 0 if round(time.time()) % 120 == 0: - installer.log.debug(f'Server alive={alive} requests={requests} memory {get_memory_stats()} ') + state = f'job="{instance.state.job}" {instance.state.job_no}/{instance.state.job_count}' + installer.log.debug(f'Server alive={alive} requests={requests} memory {get_memory_stats()} {state}') if not alive: if instance.wants_restart: installer.log.info('Server restarting...') - instance = start_server(immediate=False, server=instance) + uv, instance = start_server(immediate=False, server=instance) else: installer.log.info('Exiting...') break diff --git a/modules/api/api.py b/modules/api/api.py index ce5bda675..39436d14a 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -263,7 +263,7 @@ class Api: p.scripts = script_runner p.outpath_grids = shared.opts.outdir_grids or shared.opts.outdir_txt2img_grids p.outpath_samples = shared.opts.outdir_samples or shared.opts.outdir_txt2img_samples - shared.state.begin() + shared.state.begin('api-txt2img') script_args = self.init_script_args(p, txt2imgreq, self.default_script_arg_txt2img, selectable_scripts, selectable_script_idx, script_runner) if selectable_scripts is not None: processed = scripts.scripts_txt2img.run(p, *script_args) # Need to pass args as list here @@ -311,7 +311,7 @@ class Api: p.scripts = script_runner p.outpath_grids = shared.opts.outdir_img2img_grids p.outpath_samples = shared.opts.outdir_img2img_samples - shared.state.begin() + shared.state.begin('api-img2img') script_args = self.init_script_args(p, img2imgreq, self.default_script_arg_img2img, selectable_scripts, selectable_script_idx, script_runner) if selectable_scripts is not None: processed = scripts.scripts_img2img.run(p, *script_args) # Need to pass args as list here @@ -513,7 +513,7 @@ class Api: def create_embedding(self, args: dict): try: - shared.state.begin() + shared.state.begin('api-create-embedding') filename = create_embedding(**args) # create empty embedding sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() # reload embeddings so new one can be immediately used shared.state.end() @@ -524,7 +524,7 @@ class Api: def create_hypernetwork(self, args: dict): try: - shared.state.begin() + shared.state.begin('api-create-hypernetwork') filename = create_hypernetwork(**args) # create empty embedding # pylint: disable=E1111 shared.state.end() return models.CreateResponse(info = f"create hypernetwork filename: {filename}") @@ -534,7 +534,7 @@ class Api: def preprocess(self, args: dict): try: - shared.state.begin() + shared.state.begin('api-preprocess') preprocess(**args) # quick operation unless blip/booru interrogation is enabled shared.state.end() return models.PreprocessResponse(info = 'preprocess complete') @@ -550,7 +550,7 @@ class Api: def train_embedding(self, args: dict): try: - shared.state.begin() + shared.state.begin('api-train-embedding') apply_optimizations = False error = None filename = '' @@ -571,7 +571,7 @@ class Api: def train_hypernetwork(self, args: dict): try: - shared.state.begin() + shared.state.begin('api-train-hypernetwork') shared.loaded_hypernetworks = [] apply_optimizations = False error = None diff --git a/modules/call_queue.py b/modules/call_queue.py index 568d79344..d75ae55cd 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -19,6 +19,7 @@ def wrap_queued_call(func): def wrap_gradio_gpu_call(func, extra_outputs=None): + name = func.__name__ def f(*args, **kwargs): # if the first argument is a string that says "task(...)", it is treated as a job id if len(args) > 0 and type(args[0]) == str and args[0][0:5] == "task(" and args[0][-1] == ")": @@ -27,7 +28,6 @@ def wrap_gradio_gpu_call(func, extra_outputs=None): else: id_task = None with queue_lock: - shared.state.begin() progress.start_task(id_task) res = [None, '', '', ''] try: @@ -42,13 +42,15 @@ def wrap_gradio_gpu_call(func, extra_outputs=None): progress.finish_task(id_task) shared.state.end() return res - return wrap_gradio_call(f, extra_outputs=extra_outputs, add_stats=True) + return wrap_gradio_call(f, extra_outputs=extra_outputs, add_stats=True, name=name) -def wrap_gradio_call(func, extra_outputs=None, add_stats=False): +def wrap_gradio_call(func, extra_outputs=None, add_stats=False, name=None): + job_name = name if name is not None else func.__name__ def f(*args, extra_outputs_array=extra_outputs, **kwargs): t = time.perf_counter() shared.mem_mon.reset() + shared.state.begin(job_name) try: if shared.cmd_opts.profile: pr = cProfile.Profile() @@ -67,15 +69,10 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False): print('Profile Exec:', s.getvalue()) except Exception as e: errors.display(e, 'gradio call') - shared.state.job = "" - shared.state.job_count = 0 if extra_outputs_array is None: extra_outputs_array = [None, ''] res = extra_outputs_array + [f"
{html.escape(type(e).__name__+': '+str(e))}
"] - shared.state.skipped = False - shared.state.interrupted = False - shared.state.paused = False - shared.state.job_count = 0 + shared.state.end() if not add_stats: return tuple(res) elapsed = time.perf_counter() - t diff --git a/modules/extras.py b/modules/extras.py index f13b61306..6e61b8426 100644 --- a/modules/extras.py +++ b/modules/extras.py @@ -54,9 +54,7 @@ def to_half(tensor, enable): def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_model_name, interp_method, multiplier, save_as_half, custom_name, checkpoint_format, config_source, bake_in_vae, discard_weights, save_metadata): # pylint: disable=unused-argument - shared.state.begin() - shared.state.job = 'model-merge' - + shared.state.begin('model-merge') save_as_half = save_as_half == 0 def fail(message): @@ -321,9 +319,7 @@ def run_modelconvert(model, checkpoint_formats, precision, conv_type, custom_nam "vae": vae_conv, "other": others_conv } - shared.state.begin() - shared.state.job = 'model-convert' - + shared.state.begin('model-convert') model_info = sd_models.checkpoints_list[model] shared.state.textinfo = f"Loading {model_info.filename}..." shared.log.info(f"Model convert loading: {model_info.filename}") diff --git a/modules/interrogate.py b/modules/interrogate.py index be465d9b2..61bb14cc9 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -181,8 +181,7 @@ class InterrogateModels: def interrogate(self, pil_image): res = "" - shared.state.begin() - shared.state.job = 'interrogate' + shared.state.begin('interrogate') try: if shared.cmd_opts.lowvram or shared.cmd_opts.medvram: lowvram.send_everything_to_cpu() diff --git a/modules/modelloader.py b/modules/modelloader.py index bb0639389..38b9c8377 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -65,8 +65,7 @@ def download_civit_preview(model_path: str, preview_url: str): total_size = int(req.headers.get('content-length', 0)) block_size = 16384 # 16KB blocks written = 0 - shared.state.begin() - shared.state.job = 'download preview' + shared.state.begin('civitai-download-preview') try: with open(preview_file, 'wb') as f: with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn()) as progress: @@ -105,8 +104,7 @@ def download_civit_model(model_url: str, model_name: str, model_path: str, model total_size = int(req.headers.get('content-length', 0)) block_size = 16384 # 16KB blocks written = 0 - shared.state.begin() - shared.state.job = 'download model' + shared.state.begin('civitai-download-model') try: with open(model_file, 'wb') as f: with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn()) as progress: @@ -136,8 +134,7 @@ def download_civit_model(model_url: str, model_name: str, model_path: str, model def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config: Dict[str, str] = None, token = None, variant = None, revision = None, mirror = None): from diffusers import DiffusionPipeline import huggingface_hub as hf - shared.state.begin() - shared.state.job = 'download model' + shared.state.begin('huggingface-download-model') if download_config is None: download_config = { "force_download": False, diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 4ff648461..c82aac540 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -10,8 +10,7 @@ from modules.shared import opts def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemporaryFile], input_dir, output_dir, show_extras_results, *args, save_output: bool = True): devices.torch_gc() - shared.state.begin() - shared.state.job = 'extras' + shared.state.begin('extras') image_data = [] image_names = [] image_ext = [] diff --git a/modules/sd_vae.py b/modules/sd_vae.py index d632618a1..0360d83b3 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -232,6 +232,8 @@ def reload_vae_weights(sd_model=None, vae_file=unspecified): from modules import lowvram, sd_hijack if not sd_model: sd_model = shared.sd_model + if sd_model is None: + return global checkpoint_info # pylint: disable=global-statement checkpoint_info = sd_model.sd_checkpoint_info checkpoint_file = checkpoint_info.filename diff --git a/modules/shared.py b/modules/shared.py index ebc142a63..fe2ec4e4d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -137,19 +137,20 @@ class State: } return obj - def begin(self): - self.sampling_step = 0 - self.job_count = -1 - self.processing_has_refined_job_count = False - self.job_no = 0 - self.job_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - self.current_latent = None + def begin(self, title=""): self.current_image = None self.current_image_sampling_step = 0 + self.current_latent = None self.id_live_preview = 0 - self.skipped = False self.interrupted = False + self.job = title + self.job_count = -1 + self.job_no = 0 + self.job_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") self.paused = False + self.processing_has_refined_job_count = False + self.sampling_step = 0 + self.skipped = False self.textinfo = None self.time_start = time.time() devices.torch_gc() @@ -157,7 +158,10 @@ class State: def end(self): self.job = "" self.job_count = 0 + self.job_no = 0 self.paused = False + self.interrupted = False + self.skipped = False devices.torch_gc() def set_current_image(self): @@ -278,8 +282,9 @@ def temp_disable_extensions(): for ext in ['sd-webui-controlnet', 'multidiffusion-upscaler-for-automatic1111', 'a1111-sd-webui-lycoris']: if ext not in opts.disabled_extensions: disabled.append(ext) - log.warning(f'Diffusers disabling uncompatible extensions: {disabled}') + log.info(f'Diffusers disabling uncompatible extensions: {disabled}') if opts.lyco_patch_lora and backend != Backend.DIFFUSERS: + cmd_opts.lyco_dir = opts.lora_dir if 'Lora' not in opts.disabled_extensions: disabled.append('Lora') return disabled @@ -428,7 +433,7 @@ options_templates.update(options_section(('system-paths', "System Paths"), { "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Path to directory with stable diffusion checkpoints"), "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Path to directory with stable diffusion diffusers"), "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Path to directory with VAE files"), - "sd_lora": OptionInfo("", "Add LoRA to prompt", gr.CheckboxGroup, {"choices": [], "visible": False}), + "sd_lora": OptionInfo("", "Add LoRA to prompt", gr.Textbox, {"choices": [], "visible": False}), "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Path to directory with LoRA network(s)"), "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Path to directory with LyCORIS network(s)"), "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "Path to user-defined styles file"), diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 17cf429b9..1f5274e1f 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -219,7 +219,7 @@ class ExtraNetworksPage: else: return '' t1 = time.time() - shared.log.debug(f'Extra networks: {self.name} items={len(self.items)} subdirs={len(subdirs)} tab={tabname} time={round(t1-t0, 2)}') + shared.log.debug(f'Extra networks: page={self.name} items={len(self.items)} subdirs={len(subdirs)} tab={tabname} dirs={self.allowed_directories_for_previews()} time={round(t1-t0, 2)}') threading.Thread(target=self.create_thumb).start() def list_items(self): diff --git a/webui.py b/webui.py index 6624934cd..fb32f809f 100644 --- a/webui.py +++ b/webui.py @@ -39,6 +39,7 @@ from modules.shared import cmd_opts, opts import modules.hypernetworks.hypernetwork from modules.middleware import setup_middleware +state = shared.state if not modules.loader.initialized: timer.startup.record("libraries") log.info('Loaded librareis') @@ -152,8 +153,7 @@ def initialize(): def load_model(): if opts.sd_checkpoint_autoload: - shared.state.begin() - shared.state.job = 'load model' + shared.state.begin('load model') thread_model = Thread(target=lambda: shared.sd_model) thread_model.start() thread_refiner = Thread(target=lambda: shared.sd_refiner) From c81a909e85a63e5fae26da691665927c4d577fb2 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 7 Sep 2023 11:30:57 -0400 Subject: [PATCH 02/64] fix theme enum and preview --- CHANGELOG.md | 4 ++ extensions-builtin/sd-extension-system-info | 2 +- javascript/ui.js | 26 ++++++------ modules/shared.py | 46 ++++++++++++--------- modules/ui.py | 2 +- 5 files changed, 46 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d0558fdf..a1ae0c998 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log for SD.Next +## Update for 2023-09-07 + +Service release with many fixes + ## Update for 2023-09-06 One week later, another large update! diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index 761cf83c9..83dd4d8f6 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit 761cf83c93d8ac64b212ccd4a93b8efc2eaae305 +Subproject commit 83dd4d8f65511af720ddb034125fe4ac0a46fb18 diff --git a/javascript/ui.js b/javascript/ui.js index 001d4df30..47575c695 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -361,19 +361,21 @@ function create_theme_element() { return el; } -async function preview_theme() { +function previewTheme() { let name = gradioApp().getElementById('setting_gradio_theme').querySelectorAll('input')?.[0].value || ''; - const res = await fetch('/file=html/themes.json'); - const themes = await res.json(); - const theme = themes.find((t) => t.id === name); - if (theme) { - window.open(theme.subdomain, '_blank'); - } else { - const el = document.getElementById('theme-preview') || create_theme_element(); - el.style.display = el.style.display === 'block' ? 'none' : 'block'; - name = name.replace('/', '-'); - el.src = `/file=html/${name}.jpg`; - } + fetch('/file=html/themes.json').then((res) => { + res.json().then((themes) => { + const theme = themes.find((t) => t.id === name); + if (theme) { + window.open(theme.subdomain, '_blank'); + } else { + const el = document.getElementById('theme-preview') || create_theme_element(); + el.style.display = el.style.display === 'block' ? 'none' : 'block'; + name = name.replace('/', '-'); + el.src = `/file=html/${name}.jpg`; + } + }); + }); } async function reconnectUI() { diff --git a/modules/shared.py b/modules/shared.py index fe2ec4e4d..3a8fb74b9 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -248,33 +248,17 @@ def refresh_checkpoints(): import modules.sd_models # pylint: disable=W0621 return modules.sd_models.list_models() + def refresh_vaes(): import modules.sd_vae # pylint: disable=W0621 modules.sd_vae.refresh_vae_list() + def list_samplers(): import modules.sd_samplers # pylint: disable=W0621 modules.sd_samplers.set_samplers() return modules.sd_samplers.all_samplers -def list_builtin_themes(): - files = [os.path.splitext(f)[0] for f in os.listdir('javascript') if f.endswith('.css')] - return files - -def list_themes(): - fn = os.path.join('html', 'themes.json') - if not os.path.exists(fn): - refresh_themes() - if os.path.exists(fn): - with open(fn, mode='r', encoding='utf=8') as f: - res = json.loads(f.read()) - else: - res = [] - list_builtin_themes() - builtin = list_builtin_themes() + ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] - themes = sorted(builtin) + sorted({x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()}, key=str.casefold) - return themes - def temp_disable_extensions(): disabled = [] @@ -290,6 +274,28 @@ def temp_disable_extensions(): return disabled +def list_builtin_themes(): + files = [os.path.splitext(f)[0] for f in os.listdir('javascript') if f.endswith('.css')] + return files + + +def list_themes(): + fn = os.path.join('html', 'themes.json') + if not os.path.exists(fn): + refresh_themes() + if os.path.exists(fn): + with open(fn, mode='r', encoding='utf=8') as f: + res = json.loads(f.read()) + else: + res = [] + builtin = list_builtin_themes() + default = ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] + external = {x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()} + log.info(f'Themes list: builtin={len(builtin)} default={len(default)} external={len(external)}') + themes = sorted(builtin) + sorted(default) + sorted(external, key=str.casefold) + return themes + + def refresh_themes(): import requests try: @@ -297,8 +303,8 @@ def refresh_themes(): if req.status_code == 200: res = req.json() fn = os.path.join('html', 'themes.json') - with open(fn, mode='w', encoding='utf=8') as f: - f.write(json.dumps(res)) + writefile(res, fn) + list_themes() else: log.error('Error refreshing UI themes') except Exception: diff --git a/modules/ui.py b/modules/ui.py index 87c9cbb1b..1c10cb5af 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1085,7 +1085,7 @@ def create_ui(startup_timer = None): unload_sd_model.click(fn=unload_sd_weights, inputs=[], outputs=[]) reload_sd_model.click(fn=reload_sd_weights, inputs=[], outputs=[]) request_notifications.click(fn=lambda: None, inputs=[], outputs=[], _js='function(){}') - preview_theme.click(fn=None, _js='preview_theme', inputs=[dummy_component], outputs=[dummy_component]) + preview_theme.click(fn=None, _js='previewTheme', inputs=[], outputs=[]) timer.startup.record("ui-settings") From ee40033c8ef44f5f975c5bca34bda26f7e279aa8 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 7 Sep 2023 11:53:55 -0400 Subject: [PATCH 03/64] test-only startup mode --- launch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/launch.py b/launch.py index ab1530fac..33d1bca3e 100644 --- a/launch.py +++ b/launch.py @@ -145,6 +145,7 @@ def start_server(immediate=True, server=None): installer.log.info(f"Server arguments: {sys.argv[1:]}") get_custom_args() module_spec.loader.exec_module(server) + uvicorn = None if args.test: installer.log.info("Test only") server.wants_restart = False From 5712a8cb2022fbc6f0a4f7e555b7cecd62d9aa7f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 7 Sep 2023 14:14:07 -0400 Subject: [PATCH 04/64] add delay to desc/tag in en mouse events --- javascript/extraNetworks.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 4d833d8f9..9f9d6af88 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -162,15 +162,21 @@ function setupExtraNetworksForTab(tabname) { }); }); + let hoverTimer = null; gradioApp().getElementById(`${tabname}_extra_tabs`).onmouseover = (e) => { - const el = e?.target?.parentElement; - if (!el?.classList?.contains('card')) return; - if (el.title === previousCard) return; - readCardDescription(el.dataset.filename, el.dataset.description); - readCardTags(el, el.dataset.tags); - e.stopPropagation(); - e.preventDefault(); - previousCard = el.title; + const el = e.target.closest('.card'); // bubble-up to card + if (!el || (el.title === previousCard)) return; + if (!hoverTimer) { + hoverTimer = setTimeout(() => { + readCardDescription(el.dataset.filename, el.dataset.description); + readCardTags(el, el.dataset.tags); + previousCard = el.title; + }, 300); + } + el.onmouseout = () => { + clearTimeout(hoverTimer); + hoverTimer = null; + }; }; const intersectionObserver = new IntersectionObserver((entries) => { From a6dcb8c2f52f9a6b9ad4a904e37d4adc413fa56f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 7 Sep 2023 16:15:42 -0400 Subject: [PATCH 05/64] fix en refresh --- modules/styles.py | 2 +- modules/ui_extra_networks.py | 1 + modules/ui_extra_networks_styles.py | 13 ++++++------- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/styles.py b/modules/styles.py index a13da7bcd..a959fac89 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -65,7 +65,7 @@ class StyleDatabase: def reload(self): self.styles.clear() for fn in os.listdir(self.path): - if not fn.endswith(".json"): + if not fn.lower().endswith(".json"): continue with open(os.path.join(self.path, fn), 'r', encoding='utf-8') as f: try: diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 1f5274e1f..459851ada 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -369,6 +369,7 @@ def create_ui(container, button, tabname, skip_indexing = False): for page in extra_pages: if title == '' or title == page.title or len(page.html) == 0: page.refresh() + page.refresh_time = None page.create_page(ui.tabname) shared.log.debug(f"Refreshing Extra networks: page={page.title} items={len(page.items)} tab={ui.tabname}") res.append(page.html) diff --git a/modules/ui_extra_networks_styles.py b/modules/ui_extra_networks_styles.py index 2ccbed93b..974ea0614 100644 --- a/modules/ui_extra_networks_styles.py +++ b/modules/ui_extra_networks_styles.py @@ -13,20 +13,19 @@ class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): shared.prompt_styles.reload() def list_items(self): - styles = list(shared.prompt_styles.styles) - for style in styles: - path = os.path.join(shared.opts.styles_dir, style) - txt = f'Prompt: {shared.prompt_styles.styles[style].prompt}' - negative = shared.prompt_styles.styles[style].negative_prompt + for k in shared.prompt_styles.styles.keys(): + path = os.path.join(shared.opts.styles_dir, k) + txt = f'Prompt: {shared.prompt_styles.styles[k].prompt}' + negative = shared.prompt_styles.styles[k].negative_prompt if negative is not None and len(negative) > 0: txt += f'\nNegative: {negative}' yield { - "name": style, + "name": k, "search_term": path, "filename": path, "preview": self.find_preview(path), "description": txt, - "onclick": '"' + html.escape(f"""return selectStyle({json.dumps(style)})""") + '"', + "onclick": '"' + html.escape(f"""return selectStyle({json.dumps(k)})""") + '"', "local_preview": f"{path}.{shared.opts.samples_format}", } From 21877351872aef2bb402ac83501fe3f383e8ef0e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 7 Sep 2023 16:54:03 -0400 Subject: [PATCH 06/64] hf quick model scan --- modules/modelloader.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/modelloader.py b/modules/modelloader.py index 38b9c8377..152465941 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -173,7 +173,6 @@ def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config def load_diffusers_models(model_path: str, command_path: str = None): t0 = time.time() - import huggingface_hub as hf places = [] places.append(model_path) if command_path is not None and command_path != model_path: @@ -184,12 +183,32 @@ def load_diffusers_models(model_path: str, command_path: str = None): if not os.path.isdir(place): continue try: + """ + import huggingface_hub as hf res = hf.scan_cache_dir(cache_dir=place) for r in list(res.repos): cache_path = os.path.join(r.repo_path, "snapshots", list(r.revisions)[-1].commit_hash) diffuser_repos.append({ 'name': r.repo_id, 'filename': r.repo_id, 'path': cache_path, 'size': r.size_on_disk, 'mtime': r.last_modified, 'hash': list(r.revisions)[-1].commit_hash, 'model_info': str(os.path.join(cache_path, "model_info.json")) }) if not os.path.isfile(os.path.join(cache_path, "hidden")): output.append(str(r.repo_id)) + """ + for folder in os.listdir(place): + if "--" not in folder: + continue + _, name = folder.split("--", maxsplit=1) + name = name.replace("--", "/") + snapshots = os.listdir(os.path.join(place, folder, "snapshots")) + if len(snapshots) == 0: + shared.log.warning(f"Diffusers folder has no snapshots: location={place} folder={folder} name={name}") + continue + commit = snapshots[-1] + folder = os.path.join(place, folder, 'snapshots', commit) + mtime = os.path.getmtime(folder) + info = os.path.join(folder, "model_info.json") + diffuser_repos.append({ 'name': name, 'filename': name, 'path': folder, 'hash': commit, 'mtime': mtime, 'model_info': info }) + if os.path.exists(os.path.join(place, folder, 'snapshots', commit, "hidden")): + continue + output.append(name) except Exception as e: shared.log.error(f"Error listing diffusers: {place} {e}") shared.log.debug(f'Scanning diffusers cache: {model_path} {command_path} items={len(output)} time={time.time()-t0:.2f}s') From 9cadf4fc10e4e3918f283b2581cae95fc0d3478d Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 8 Sep 2023 03:51:44 +0300 Subject: [PATCH 07/64] IPEX fix and make SDP default --- modules/intel/ipex/__init__.py | 43 ++++----- modules/intel/ipex/attention.py | 131 ++++++++++++++++++++++++++ modules/intel/ipex/diffusers.py | 154 ++----------------------------- modules/intel/ipex/gradscaler.py | 18 ++-- modules/intel/ipex/hijacks.py | 126 ++++++++++++++++++------- modules/shared.py | 2 +- 6 files changed, 257 insertions(+), 217 deletions(-) create mode 100644 modules/intel/ipex/attention.py diff --git a/modules/intel/ipex/__init__.py b/modules/intel/ipex/__init__.py index 9794f98b7..bd2e8e142 100644 --- a/modules/intel/ipex/__init__.py +++ b/modules/intel/ipex/__init__.py @@ -2,22 +2,14 @@ import os import sys import contextlib import torch -import intel_extension_for_pytorch as ipex -from modules import shared -from .diffusers import ipex_diffusers +import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import from .hijacks import ipex_hijacks +from .attention import attention_init +from .diffusers import ipex_diffusers -#ControlNet depth_leres++ -class DummyDataParallel(torch.nn.Module): - def __new__(cls, module, device_ids=None, output_device=None, dim=0): - if type(device_ids) is list and len(device_ids) > 1: - shared.log.warning("IPEX backend doesn't support DataParallel on multiple XPU devices") - return module.to(shared.device) +# pylint: disable=protected-access, missing-function-docstring, line-too-long -def return_null_context(*args, **kwargs): - return contextlib.nullcontext() - -def ipex_init(): +def ipex_init(): # pylint: disable=too-many-statements try: #Replace cuda with xpu: torch.cuda.current_device = torch.xpu.current_device @@ -140,10 +132,13 @@ def ipex_init(): torch.cuda.amp.common.amp_definitely_not_available = lambda: False try: torch.cuda.amp.GradScaler = torch.xpu.amp.GradScaler - except Exception: - from .gradscaler import gradscaler_init - gradscaler_init() - torch.cuda.amp.GradScaler = torch.xpu.amp.GradScaler + except Exception: # pylint: disable=broad-exception-caught + try: + from .gradscaler import gradscaler_init # pylint: disable=import-outside-toplevel, import-error + gradscaler_init() + torch.cuda.amp.GradScaler = torch.xpu.amp.GradScaler + except Exception: # pylint: disable=broad-exception-caught + torch.cuda.amp.GradScaler = ipex.cpu.autocast._grad_scaler.GradScaler #C torch._C._cuda_getCurrentRawStream = ipex._C._getCurrentStream @@ -152,20 +147,20 @@ def ipex_init(): #Fix functions with ipex: torch.cuda.mem_get_info = lambda device=None: [(torch.xpu.get_device_properties(device).total_memory - torch.xpu.memory_allocated(device)), torch.xpu.get_device_properties(device).total_memory] - torch._utils._get_available_device_type = lambda: "xpu" # pylint: disable=protected-access + torch._utils._get_available_device_type = lambda: "xpu" torch.has_cuda = True torch.cuda.has_half = True - torch.cuda.is_bf16_supported = True + torch.cuda.is_bf16_supported = lambda *args, **kwargs: True + torch.cuda.is_fp16_supported = lambda *args, **kwargs: True #torch.version.cuda = "11.7" #Breaks System Info - torch.cuda.get_device_capability = lambda: [11,7] + torch.cuda.get_device_capability = lambda *args, **kwargs: [11,7] torch.cuda.get_device_properties.major = 11 torch.cuda.get_device_properties.minor = 7 - torch.backends.cuda.sdp_kernel = return_null_context - torch.nn.DataParallel = DummyDataParallel - torch.cuda.ipc_collect = lambda: None - torch.cuda.utilization = lambda: 0 + torch.cuda.ipc_collect = lambda *args, **kwargs: None + torch.cuda.utilization = lambda *args, **kwargs: 0 ipex_hijacks() + attention_init() ipex_diffusers() except Exception as e: return False, e diff --git a/modules/intel/ipex/attention.py b/modules/intel/ipex/attention.py new file mode 100644 index 000000000..87b14e978 --- /dev/null +++ b/modules/intel/ipex/attention.py @@ -0,0 +1,131 @@ +import torch +import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import +import diffusers #0.20.2 # pylint: disable=import-error + +# pylint: disable=protected-access, missing-function-docstring, line-too-long + +Attention = diffusers.models.attention_processor.Attention + +original_torch_bmm = torch.bmm +def torch_bmm(input, mat2, *, out=None): + if input.dtype != mat2.dtype: + mat2 = mat2.to(input.dtype) + + #ARC GPUs can't allocate more than 4GB to a single block, Slice it: + batch_size_attention, input_tokens, mat2_shape = input.shape[0], input.shape[1], mat2.shape[2] + block_multiply = 2.4 if input.dtype == torch.float32 else 1.2 + block_size = (batch_size_attention * input_tokens * mat2_shape) / 1024 * block_multiply #MB + split_slice_size = batch_size_attention + if block_size >= 4000: + do_split = True + #Find something divisible with the input_tokens + while ((split_slice_size * input_tokens * mat2_shape) / 1024 * block_multiply) > 4000: + split_slice_size = split_slice_size // 2 + if split_slice_size <= 1: + split_slice_size = 1 + break + else: + do_split = False + + split_block_size = (split_slice_size * input_tokens * mat2_shape) / 1024 * block_multiply #MB + split_2_slice_size = input_tokens + if split_block_size >= 4000: + do_split_2 = True + #Find something divisible with the input_tokens + while ((split_slice_size * split_2_slice_size * mat2_shape) / 1024 * block_multiply) > 4000: + split_2_slice_size = split_2_slice_size // 2 + if split_2_slice_size <= 1: + split_2_slice_size = 1 + break + else: + do_split_2 = False + + if do_split: + hidden_states = torch.zeros(input.shape[0], input.shape[1], mat2.shape[2], device=input.device, dtype=input.dtype) + for i in range(batch_size_attention // split_slice_size): + start_idx = i * split_slice_size + end_idx = (i + 1) * split_slice_size + if do_split_2: + for i2 in range(input_tokens // split_2_slice_size): # pylint: disable=invalid-name + start_idx_2 = i2 * split_2_slice_size + end_idx_2 = (i2 + 1) * split_2_slice_size + hidden_states[start_idx:end_idx, start_idx_2:end_idx_2] = original_torch_bmm( + input[start_idx:end_idx, start_idx_2:end_idx_2], + mat2[start_idx:end_idx, start_idx_2:end_idx_2], + out=out + ) + else: + hidden_states[start_idx:end_idx] = original_torch_bmm( + input[start_idx:end_idx], + mat2[start_idx:end_idx], + out=out + ) + else: + return original_torch_bmm(input, mat2, out=out) + return hidden_states + +original_scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention +def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False): + #ARC GPUs can't allocate more than 4GB to a single block, Slice it: + shape_one, batch_size_attention, query_tokens, shape_four = query.shape + block_multiply = 2.4 if query.dtype == torch.float32 else 1.2 + block_size = (shape_one * batch_size_attention * query_tokens * shape_four) / 1024 * block_multiply #MB + split_slice_size = batch_size_attention + if block_size >= 4000: + do_split = True + #Find something divisible with the shape_one + while ((shape_one * split_slice_size * query_tokens * shape_four) / 1024 * block_multiply) > 4000: + split_slice_size = split_slice_size // 2 + if split_slice_size <= 1: + split_slice_size = 1 + break + else: + do_split = False + + split_block_size = (shape_one * split_slice_size * query_tokens * shape_four) / 1024 * block_multiply #MB + split_2_slice_size = query_tokens + if split_block_size >= 4000: + do_split_2 = True + #Find something divisible with the batch_size_attention + while ((shape_one * split_slice_size * split_2_slice_size * shape_four) / 1024 * block_multiply) > 4000: + split_2_slice_size = split_2_slice_size // 2 + if split_2_slice_size <= 1: + split_2_slice_size = 1 + break + else: + do_split_2 = False + + if do_split: + hidden_states = torch.zeros(query.shape, device=query.device, dtype=query.dtype) + for i in range(batch_size_attention // split_slice_size): + start_idx = i * split_slice_size + end_idx = (i + 1) * split_slice_size + if do_split_2: + for i2 in range(query_tokens // split_2_slice_size): # pylint: disable=invalid-name + start_idx_2 = i2 * split_2_slice_size + end_idx_2 = (i2 + 1) * split_2_slice_size + hidden_states[:, start_idx:end_idx, start_idx_2:end_idx_2] = original_scaled_dot_product_attention( + query[:, start_idx:end_idx, start_idx_2:end_idx_2], + key[:, start_idx:end_idx, start_idx_2:end_idx_2], + value[:, start_idx:end_idx, start_idx_2:end_idx_2], + attn_mask=attn_mask[:, start_idx:end_idx, start_idx_2:end_idx_2] if attn_mask is not None else attn_mask, + dropout_p=dropout_p, is_causal=is_causal + ) + else: + hidden_states[:, start_idx:end_idx] = original_scaled_dot_product_attention( + query[:, start_idx:end_idx], + key[:, start_idx:end_idx], + value[:, start_idx:end_idx], + attn_mask=attn_mask[:, start_idx:end_idx] if attn_mask is not None else attn_mask, + dropout_p=dropout_p, is_causal=is_causal + ) + else: + return original_scaled_dot_product_attention( + query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal + ) + return hidden_states + +def attention_init(): + #ARC GPUs can't allocate more than 4GB to a single block: + torch.bmm = torch_bmm + torch.nn.functional.scaled_dot_product_attention = scaled_dot_product_attention diff --git a/modules/intel/ipex/diffusers.py b/modules/intel/ipex/diffusers.py index e03592197..3435abe14 100644 --- a/modules/intel/ipex/diffusers.py +++ b/modules/intel/ipex/diffusers.py @@ -1,11 +1,10 @@ import torch -import intel_extension_for_pytorch as ipex -import torch.nn.functional as F -import diffusers #0.20.2 +import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import +import diffusers #0.20.2 # pylint: disable=import-error -Attention = diffusers.models.attention_processor.Attention +# pylint: disable=protected-access, missing-function-docstring, line-too-long -class SlicedAttnProcessor: +class SlicedAttnProcessor: # pylint: disable=too-few-public-methods r""" Processor for implementing sliced attention. @@ -18,7 +17,7 @@ class SlicedAttnProcessor: def __init__(self, slice_size): self.slice_size = slice_size - def __call__(self, attn: Attention, hidden_states, encoder_hidden_states=None, attention_mask=None): + def __call__(self, attn: diffusers.models.attention_processor.Attention, hidden_states, encoder_hidden_states=None, attention_mask=None): # pylint: disable=too-many-statements, too-many-locals, too-many-branches residual = hidden_states input_ndim = hidden_states.ndim @@ -74,7 +73,7 @@ class SlicedAttnProcessor: end_idx = (i + 1) * self.slice_size if do_split_2: - for i2 in range(query_tokens // split_2_slice_size): + for i2 in range(query_tokens // split_2_slice_size): # pylint: disable=invalid-name start_idx_2 = i2 * split_2_slice_size end_idx_2 = (i2 + 1) * split_2_slice_size @@ -114,147 +113,6 @@ class SlicedAttnProcessor: return hidden_states -class AttnProcessor2_0: - r""" - Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0). - """ - - def __init__(self): - if not hasattr(F, "scaled_dot_product_attention"): - raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.") - - def __call__( - self, - attn: Attention, - hidden_states, - encoder_hidden_states=None, - attention_mask=None, - temb=None, - ): - residual = hidden_states - - if attn.spatial_norm is not None: - hidden_states = attn.spatial_norm(hidden_states, temb) - - input_ndim = hidden_states.ndim - - if input_ndim == 4: - batch_size, channel, height, width = hidden_states.shape - hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2) - - batch_size, sequence_length, _ = ( - hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape - ) - - if attention_mask is not None: - attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size) - # scaled_dot_product_attention expects attention_mask shape to be - # (batch, heads, source_length, target_length) - attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1]) - - if attn.group_norm is not None: - hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2) - - query = attn.to_q(hidden_states) - - if encoder_hidden_states is None: - encoder_hidden_states = hidden_states - elif attn.norm_cross: - encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states) - - key = attn.to_k(encoder_hidden_states) - value = attn.to_v(encoder_hidden_states) - - inner_dim = key.shape[-1] - head_dim = inner_dim // attn.heads - - query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) - - key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) - value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) - - #ARC GPUs can't allocate more than 4GB to a single block, Slice it: - shape_one, batch_size_attention, query_tokens, shape_four = query.shape - block_multiply = 2.4 if query.dtype == torch.float32 else 1.2 - block_size = (shape_one * batch_size_attention * query_tokens * shape_four) / 1024 * block_multiply #MB - split_slice_size = batch_size_attention - if block_size >= 4000: - do_split = True - #Find something divisible with the shape_one - while ((shape_one * split_slice_size * query_tokens * shape_four) / 1024 * block_multiply) > 4000: - split_slice_size = split_slice_size // 2 - if split_slice_size <= 1: - split_slice_size = 1 - break - else: - do_split = False - - split_block_size = (shape_one * split_slice_size * query_tokens * shape_four) / 1024 * block_multiply #MB - split_2_slice_size = query_tokens - if split_block_size >= 4000: - do_split_2 = True - #Find something divisible with the batch_size_attention - while ((shape_one * split_slice_size * split_2_slice_size * shape_four) / 1024 * block_multiply) > 4000: - split_2_slice_size = split_2_slice_size // 2 - if split_2_slice_size <= 1: - split_2_slice_size = 1 - break - else: - do_split_2 = False - - if do_split: - hidden_states = torch.zeros(query.shape, device=query.device, dtype=query.dtype) - for i in range(batch_size_attention // split_slice_size): - start_idx = i * split_slice_size - end_idx = (i + 1) * split_slice_size - if do_split_2: - for i2 in range(query_tokens // split_2_slice_size): - start_idx_2 = i2 * split_2_slice_size - end_idx_2 = (i2 + 1) * split_2_slice_size - - query_slice = query[:, start_idx:end_idx, start_idx_2:end_idx_2] - key_slice = key[:, start_idx:end_idx, start_idx_2:end_idx_2] - attn_mask_slice = attention_mask[:, start_idx:end_idx, start_idx_2:end_idx_2] if attention_mask is not None else None - - attn_slice = F.scaled_dot_product_attention( - query_slice, key_slice, value[:, start_idx:end_idx, start_idx_2:end_idx_2], - attn_mask=attn_mask_slice, dropout_p=0.0, is_causal=False - ) - hidden_states[:, start_idx:end_idx, start_idx_2:end_idx_2] = attn_slice - else: - query_slice = query[:, start_idx:end_idx] - key_slice = key[:, start_idx:end_idx] - attn_mask_slice = attention_mask[:, start_idx:end_idx] if attention_mask is not None else None - - attn_slice = F.scaled_dot_product_attention( - query_slice, key_slice, value[:, start_idx:end_idx], - attn_mask=attn_mask_slice, dropout_p=0.0, is_causal=False - ) - hidden_states[:, start_idx:end_idx] = attn_slice - else: - hidden_states = F.scaled_dot_product_attention( - query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False - ) - - hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim) - hidden_states = hidden_states.to(query.dtype) - - # linear proj - hidden_states = attn.to_out[0](hidden_states) - # dropout - hidden_states = attn.to_out[1](hidden_states) - - if input_ndim == 4: - hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width) - - if attn.residual_connection: - hidden_states = hidden_states + residual - - hidden_states = hidden_states / attn.rescale_output_factor - - return hidden_states - def ipex_diffusers(): #ARC GPUs can't allocate more than 4GB to a single block: diffusers.models.attention_processor.SlicedAttnProcessor = SlicedAttnProcessor - diffusers.models.attention_processor.AttnProcessor2_0 = AttnProcessor2_0 diff --git a/modules/intel/ipex/gradscaler.py b/modules/intel/ipex/gradscaler.py index 217f6cc7d..530212101 100644 --- a/modules/intel/ipex/gradscaler.py +++ b/modules/intel/ipex/gradscaler.py @@ -1,14 +1,15 @@ -import torch from collections import defaultdict -import intel_extension_for_pytorch as ipex -import intel_extension_for_pytorch._C as core -from modules import shared +import torch +import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import +import intel_extension_for_pytorch._C as core # pylint: disable=import-error, unused-import + +# pylint: disable=protected-access, missing-function-docstring, line-too-long OptState = ipex.cpu.autocast._grad_scaler.OptState _MultiDeviceReplicator = ipex.cpu.autocast._grad_scaler._MultiDeviceReplicator _refresh_per_optimizer_state = ipex.cpu.autocast._grad_scaler._refresh_per_optimizer_state -def _unscale_grads_(self, optimizer, inv_scale, found_inf, allow_fp16): +def _unscale_grads_(self, optimizer, inv_scale, found_inf, allow_fp16): # pylint: disable=unused-argument per_device_inv_scale = _MultiDeviceReplicator(inv_scale) per_device_found_inf = _MultiDeviceReplicator(found_inf) @@ -40,7 +41,7 @@ def _unscale_grads_(self, optimizer, inv_scale, found_inf, allow_fp16): else: to_unscale = param.grad - # TODO: is there a way to split by device and dtype without appending in the inner loop? + # -: is there a way to split by device and dtype without appending in the inner loop? to_unscale = to_unscale.to("cpu") per_device_and_dtype_grads[to_unscale.device][ to_unscale.dtype @@ -86,7 +87,7 @@ def unscale_(self, optimizer): optimizer_state = self._per_optimizer_states[id(optimizer)] - if optimizer_state["stage"] is OptState.UNSCALED: + if optimizer_state["stage"] is OptState.UNSCALED: # pylint: disable=no-else-raise raise RuntimeError( "unscale_() has already been called on this optimizer since the last update()." ) @@ -175,5 +176,4 @@ def gradscaler_init(): torch.xpu.amp.GradScaler._unscale_grads_ = _unscale_grads_ torch.xpu.amp.GradScaler.unscale_ = unscale_ torch.xpu.amp.GradScaler.update = update - - + return torch.xpu.amp.GradScaler diff --git a/modules/intel/ipex/hijacks.py b/modules/intel/ipex/hijacks.py index 9e1d9713e..1ad90d72b 100644 --- a/modules/intel/ipex/hijacks.py +++ b/modules/intel/ipex/hijacks.py @@ -1,19 +1,63 @@ +import contextlib import torch -import intel_extension_for_pytorch as ipex -from modules import devices +import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import from modules.sd_hijack_utils import CondFunc +from modules import devices + +# pylint: disable=protected-access, missing-function-docstring, line-too-long, unnecessary-lambda, no-else-return + +def _shutdown_workers(self): + if torch.utils.data._utils is None or torch.utils.data._utils.python_exit_status is True or torch.utils.data._utils.python_exit_status is None: + return + if hasattr(self, "_shutdown") and not self._shutdown: + self._shutdown = True + try: + if hasattr(self, '_pin_memory_thread'): + self._pin_memory_thread_done_event.set() + self._worker_result_queue.put((None, None)) + self._pin_memory_thread.join() + self._worker_result_queue.cancel_join_thread() + self._worker_result_queue.close() + self._workers_done_event.set() + for worker_id in range(len(self._workers)): + if self._persistent_workers or self._workers_status[worker_id]: + self._mark_worker_as_unavailable(worker_id, shutdown=True) + for w in self._workers: # pylint: disable=invalid-name + w.join(timeout=torch.utils.data._utils.MP_STATUS_CHECK_INTERVAL) + for q in self._index_queues: # pylint: disable=invalid-name + q.cancel_join_thread() + q.close() + finally: + if self._worker_pids_set: + torch.utils.data._utils.signal_handling._remove_worker_pids(id(self)) + self._worker_pids_set = False + for w in self._workers: # pylint: disable=invalid-name + if w.is_alive(): + w.terminate() + +class DummyDataParallel(torch.nn.Module): # pylint: disable=missing-class-docstring, unused-argument, too-few-public-methods + def __new__(cls, module, device_ids=None, output_device=None, dim=0): # pylint: disable=unused-argument + if isinstance(device_ids, list) and len(device_ids) > 1: + print("IPEX backend doesn't support DataParallel on multiple XPU devices") + return module.to(devices.device) + +def return_null_context(*args, **kwargs): # pylint: disable=unused-argument + return contextlib.nullcontext() def check_device(device): return bool((isinstance(device, torch.device) and device.type == "cuda") or (isinstance(device, str) and "cuda" in device) or isinstance(device, int)) -def ipex_no_cuda(orig_func, *args, **kwargs): # pylint: disable=redefined-outer-name +def return_xpu(device): + return f"xpu:{device[-1]}" if isinstance(device, str) and ":" in device else f"xpu:{device}" if isinstance(device, int) else torch.device(devices.device) if isinstance(device, torch.device) else devices.device + +def ipex_no_cuda(orig_func, *args, **kwargs): torch.cuda.is_available = lambda: False orig_func(*args, **kwargs) torch.cuda.is_available = torch.xpu.is_available original_autocast = torch.autocast def ipex_autocast(*args, **kwargs): - if args[0] == "cuda" or args[0] == "xpu": + if len(args) > 0 and args[0] == "cuda" or args[0] == "xpu": if "dtype" in kwargs: return original_autocast("xpu", *args[1:], **kwargs) else: @@ -23,66 +67,75 @@ def ipex_autocast(*args, **kwargs): #Embedding BF16 original_torch_cat = torch.cat -def torch_cat(input, *args, **kwargs): - if len(input) == 3 and (input[0].dtype != input[1].dtype or input[2].dtype != input[1].dtype): - return original_torch_cat([input[0].to(input[1].dtype), input[1], input[2].to(input[1].dtype)], *args, **kwargs) +def torch_cat(tensor, *args, **kwargs): + if len(tensor) == 3 and (tensor[0].dtype != tensor[1].dtype or tensor[2].dtype != tensor[1].dtype): + return original_torch_cat([tensor[0].to(tensor[1].dtype), tensor[1], tensor[2].to(tensor[1].dtype)], *args, **kwargs) else: - return original_torch_cat(input, *args, **kwargs) + return original_torch_cat(tensor, *args, **kwargs) #Latent antialias: original_interpolate = torch.nn.functional.interpolate -def interpolate(input, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False): - if antialias: - return original_interpolate(input.to("cpu", dtype=torch.float32), size=size, scale_factor=scale_factor, mode=mode, - align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias).to(devices.device, dtype=devices.dtype) +def interpolate(tensor, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False): # pylint: disable=too-many-arguments + if antialias or align_corners is not None: + return_device = tensor.device + return_dtype = tensor.dtype + return original_interpolate(tensor.to("cpu", dtype=torch.float32), size=size, scale_factor=scale_factor, mode=mode, + align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias).to(return_device, dtype=return_dtype) else: - return original_interpolate(input, size=size, scale_factor=scale_factor, mode=mode, + return original_interpolate(tensor, size=size, scale_factor=scale_factor, mode=mode, align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias) +original_linalg_solve = torch.linalg.solve +def linalg_solve(A, B, *args, **kwargs): # pylint: disable=invalid-name + if A.device != torch.device("cpu") or B.device != torch.device("cpu"): + return_device = A.device + return original_linalg_solve(A.to("cpu"), B.to("cpu"), *args, **kwargs).to(return_device) + else: + return original_linalg_solve(A, B, *args, **kwargs) + def ipex_hijacks(): CondFunc('torch.Tensor.to', - lambda orig_func, self, device=None, *args, **kwargs: orig_func(self, devices.device, *args, **kwargs), + lambda orig_func, self, device=None, *args, **kwargs: orig_func(self, return_xpu(device), *args, **kwargs), lambda orig_func, self, device=None, *args, **kwargs: check_device(device)) CondFunc('torch.Tensor.cuda', - lambda orig_func, self, device=None, *args, **kwargs: orig_func(self, devices.device, *args, **kwargs), + lambda orig_func, self, device=None, *args, **kwargs: orig_func(self, return_xpu(device), *args, **kwargs), lambda orig_func, self, device=None, *args, **kwargs: check_device(device)) CondFunc('torch.empty', - lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs), + lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs), lambda orig_func, *args, device=None, **kwargs: check_device(device)) CondFunc('torch.load', - lambda orig_func, *args, map_location=None, **kwargs: orig_func(*args, devices.device, **kwargs), + lambda orig_func, *args, map_location=None, **kwargs: orig_func(*args, return_xpu(map_location), **kwargs), lambda orig_func, *args, map_location=None, **kwargs: map_location is None or check_device(map_location)) CondFunc('torch.randn', - lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs), + lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs), lambda orig_func, *args, device=None, **kwargs: check_device(device)) CondFunc('torch.ones', - lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs), + lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs), lambda orig_func, *args, device=None, **kwargs: check_device(device)) CondFunc('torch.zeros', - lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs), + lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs), lambda orig_func, *args, device=None, **kwargs: check_device(device)) CondFunc('torch.tensor', - lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs), + lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs), + lambda orig_func, *args, device=None, **kwargs: check_device(device)) + CondFunc('torch.linspace', + lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs), lambda orig_func, *args, device=None, **kwargs: check_device(device)) CondFunc('torch.Generator', - lambda orig_func, device: torch.xpu.Generator(device), - lambda orig_func, device: device != torch.device("cpu") and device != "cpu") - #Crashes the GPU: - CondFunc('torch.linalg.solve', - lambda orig_func, A, B, *args, **kwargs: orig_func(A.to("cpu"), B.to("cpu"), *args, **kwargs).to(devices.device), - lambda orig_func, A, B, *args, **kwargs: A.device != torch.device("cpu") or B.device != torch.device("cpu")) + lambda orig_func, device=None: torch.xpu.Generator(device), + lambda orig_func, device=None: device is not None and device != torch.device("cpu") and device != "cpu") #TiledVAE and ControlNet: CondFunc('torch.batch_norm', lambda orig_func, input, weight, bias, *args, **kwargs: orig_func(input, - weight if weight is not None else torch.ones(input.size()[1], device=devices.device), - bias if bias is not None else torch.zeros(input.size()[1], device=devices.device), *args, **kwargs), + weight if weight is not None else torch.ones(input.size()[1], device=input.device), + bias if bias is not None else torch.zeros(input.size()[1], device=input.device), *args, **kwargs), lambda orig_func, input, *args, **kwargs: input.device != torch.device("cpu")) CondFunc('torch.instance_norm', lambda orig_func, input, weight, bias, *args, **kwargs: orig_func(input, - weight if weight is not None else torch.ones(input.size()[1], device=devices.device), - bias if bias is not None else torch.zeros(input.size()[1], device=devices.device), *args, **kwargs), + weight if weight is not None else torch.ones(input.size()[1], device=input.device), + bias if bias is not None else torch.zeros(input.size()[1], device=input.device), *args, **kwargs), lambda orig_func, input, *args, **kwargs: input.device != torch.device("cpu")) #Functions with dtype errors: @@ -94,10 +147,9 @@ def ipex_hijacks(): CondFunc('torch.nn.modules.linear.Linear.forward', lambda orig_func, self, input: orig_func(self, input.to(self.weight.data.dtype)), lambda orig_func, self, input: input.dtype != self.weight.data.dtype) - #Embedding FP32: - CondFunc('torch.bmm', - lambda orig_func, input, mat2, *args, **kwargs: orig_func(input, mat2.to(input.dtype), *args, **kwargs), - lambda orig_func, input, mat2, *args, **kwargs: input.dtype != mat2.dtype) + CondFunc('torch.nn.modules.conv.Conv2d.forward', + lambda orig_func, self, input: orig_func(self, input.to(self.weight.data.dtype)), + lambda orig_func, self, input: input.dtype != self.weight.data.dtype) #BF16: CondFunc('torch.nn.functional.layer_norm', lambda orig_func, input, normalized_shape=None, weight=None, *args, **kwargs: @@ -118,6 +170,10 @@ def ipex_hijacks(): lambda orig_func, *args, **kwargs: True) #Functions that make compile mad with CondFunc: + torch.utils.data.dataloader._MultiProcessingDataLoaderIter._shutdown_workers = _shutdown_workers + torch.nn.DataParallel = DummyDataParallel torch.autocast = ipex_autocast torch.cat = torch_cat + torch.linalg.solve = linalg_solve torch.nn.functional.interpolate = interpolate + torch.backends.cuda.sdp_kernel = return_null_context diff --git a/modules/shared.py b/modules/shared.py index 3a8fb74b9..c9e3c49ea 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -350,7 +350,7 @@ if devices.backend == "cpu": elif devices.backend == "mps": cross_attention_optimization_default = "Doggettx's" elif devices.backend == "ipex": - cross_attention_optimization_default = "Sub-quadratic" + cross_attention_optimization_default = "Scaled-Dot-Product" elif devices.backend == "directml": cross_attention_optimization_default = "Sub-quadratic" elif devices.backend == "rocm": From 12c2f4d6eee903f929f4c0b4a9b7ade2c445f9fa Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 8 Sep 2023 04:17:27 +0300 Subject: [PATCH 08/64] Cleanup --- modules/intel/ipex/attention.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/intel/ipex/attention.py b/modules/intel/ipex/attention.py index 87b14e978..d7335bfaf 100644 --- a/modules/intel/ipex/attention.py +++ b/modules/intel/ipex/attention.py @@ -1,11 +1,8 @@ import torch import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import -import diffusers #0.20.2 # pylint: disable=import-error # pylint: disable=protected-access, missing-function-docstring, line-too-long -Attention = diffusers.models.attention_processor.Attention - original_torch_bmm = torch.bmm def torch_bmm(input, mat2, *, out=None): if input.dtype != mat2.dtype: From 36f134ddd4d5d5811d0c88a024e2006a2cdd7cab Mon Sep 17 00:00:00 2001 From: QuantumSoul Date: Fri, 8 Sep 2023 04:23:56 +0200 Subject: [PATCH 09/64] Update sd_models.py If user uses --no-download, they most-likely don't need the error for not having any local models --- modules/sd_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 3243d4951..72422b498 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -256,7 +256,7 @@ def select_checkpoint(op='model'): if checkpoint_info is not None: shared.log.debug(f'Select checkpoint: {op} {checkpoint_info.title if checkpoint_info is not None else None}') return checkpoint_info - if len(checkpoints_list) == 0: + if len(checkpoints_list) == 0 and not shared.cmd_opts.no_download: shared.log.error("Cannot run without a checkpoint") shared.log.error("Use --ckpt to force using existing checkpoint") return None From 2a213e8e84945c907a3d87f259f294af0856b7ca Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Sep 2023 07:36:17 -0400 Subject: [PATCH 10/64] fix server restart --- launch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch.py b/launch.py index 33d1bca3e..a019d9356 100644 --- a/launch.py +++ b/launch.py @@ -220,7 +220,7 @@ if __name__ == "__main__": state = f'job="{instance.state.job}" {instance.state.job_no}/{instance.state.job_count}' installer.log.debug(f'Server alive={alive} requests={requests} memory {get_memory_stats()} {state}') if not alive: - if instance.wants_restart: + if uv.wants_restart: installer.log.info('Server restarting...') uv, instance = start_server(immediate=False, server=instance) else: From 0194620ec2d7c2f6420e672efd9b2d6793f18d82 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Sep 2023 07:59:58 -0400 Subject: [PATCH 11/64] cleanup options --- modules/images.py | 5 +---- modules/processing.py | 3 +-- modules/sd_models.py | 1 + modules/sd_vae.py | 1 + modules/shared.py | 4 ++-- scripts/outpainting_mk_2.py | 5 ++--- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/modules/images.py b/modules/images.py index 5d7684285..a4b1e1b3b 100644 --- a/modules/images.py +++ b/modules/images.py @@ -44,13 +44,10 @@ def image_grid(imgs, batch_size=1, rows=None): rows = shared.opts.n_rows elif shared.opts.n_rows == 0: rows = batch_size - elif shared.opts.grid_prevent_empty_spots: + else: rows = math.floor(math.sqrt(len(imgs))) while len(imgs) % rows != 0: rows -= 1 - else: - rows = math.sqrt(len(imgs)) - rows = round(rows) if rows > len(imgs): rows = len(imgs) cols = math.ceil(len(imgs) / rows) diff --git a/modules/processing.py b/modules/processing.py index 50870d405..f1ccc4901 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -853,8 +853,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.color_corrections = None index_of_first_image = 0 - unwanted_grid_because_of_img_count = len(output_images) < 2 and shared.opts.grid_only_if_multiple - if (shared.opts.return_grid or shared.opts.grid_save) and not p.do_not_save_grid and not unwanted_grid_because_of_img_count: + if (shared.opts.return_grid or shared.opts.grid_save) and not p.do_not_save_grid and len(output_images) > 2: if images.check_grid_size(output_images): grid = images.image_grid(output_images, p.batch_size) if shared.opts.return_grid: diff --git a/modules/sd_models.py b/modules/sd_models.py index 72422b498..88a058acb 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -140,6 +140,7 @@ def checkpoint_tiles(use_short=False): # pylint: disable=unused-argument return int(name) if name.isdigit() else name.lower() def alphanumeric_key(key): return [convert(c) for c in re.split('([0-9]+)', key)] + print('HERE', len(checkpoints_list.values())) return sorted([x.title for x in checkpoints_list.values()], key=alphanumeric_key) diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 0360d83b3..e979666e2 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -52,6 +52,7 @@ def get_filename(filepath): def refresh_vae_list(): + print('HERE') global vae_path # pylint: disable=global-statement vae_path = shared.opts.vae_dir vae_dict.clear() diff --git a/modules/shared.py b/modules/shared.py index c9e3c49ea..c876cf076 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -475,8 +475,8 @@ options_templates.update(options_section(('saving-images', "Image Options"), { "grid_save": OptionInfo(True, "Always save all generated image grids"), "grid_format": OptionInfo('jpg', 'File format for grids', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}), "n_rows": OptionInfo(-1, "Grid row count", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}), - "grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"), - "grid_prevent_empty_spots": OptionInfo(True, "Prevent empty spots in grid (when set to autodetect)"), + # "grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"), + # "grid_prevent_empty_spots": OptionInfo(True, "Prevent empty spots in grid (when set to autodetect)"), "save_sep_options": OptionInfo("

Intermediate Image Saving

", "", gr.HTML), "save_init_img": OptionInfo(True, "Save copy of img2img init images (helps track workflow)"), diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index 4b0af3053..bbf7aa537 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -264,8 +264,7 @@ class Script(scripts.Script): all_images = all_processed_images combined_grid_image = images.image_grid(all_processed_images) - unwanted_grid_because_of_img_count = len(all_processed_images) < 2 and opts.grid_only_if_multiple - if opts.return_grid and not unwanted_grid_because_of_img_count: + if opts.return_grid and len(all_processed_images) > 1: all_images = [combined_grid_image] + all_processed_images res = Processed(p, all_images, initial_seed_and_info[0], initial_seed_and_info[1]) @@ -274,7 +273,7 @@ class Script(scripts.Script): for img in all_processed_images: images.save_image(img, p.outpath_samples, "", res.seed, p.prompt, opts.samples_format, info=res.info, p=p) - if opts.grid_save and not unwanted_grid_because_of_img_count: + if opts.grid_save and len(all_processed_images) > 1: images.save_image(combined_grid_image, p.outpath_grids, "grid", res.seed, p.prompt, opts.samples_format, info=res.info, short_filename=not opts.grid_extended_filename, grid=True, p=p) return res From 5b41115bc89ed6e730aa18d8445d4bd2a9925b15 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Sep 2023 08:34:27 -0400 Subject: [PATCH 12/64] fix settings refresh button --- modules/sd_models.py | 1 - modules/sd_vae.py | 1 - modules/shared.py | 2 +- modules/ui.py | 4 ++-- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 88a058acb..72422b498 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -140,7 +140,6 @@ def checkpoint_tiles(use_short=False): # pylint: disable=unused-argument return int(name) if name.isdigit() else name.lower() def alphanumeric_key(key): return [convert(c) for c in re.split('([0-9]+)', key)] - print('HERE', len(checkpoints_list.values())) return sorted([x.title for x in checkpoints_list.values()], key=alphanumeric_key) diff --git a/modules/sd_vae.py b/modules/sd_vae.py index e979666e2..0360d83b3 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -52,7 +52,6 @@ def get_filename(filepath): def refresh_vae_list(): - print('HERE') global vae_path # pylint: disable=global-statement vae_path = shared.opts.vae_dir vae_dict.clear() diff --git a/modules/shared.py b/modules/shared.py index c876cf076..d68a744ce 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -633,7 +633,7 @@ options_templates.update(options_section(('interrogate', "Interrogate"), { "interrogate_clip_min_length": OptionInfo(32, "Interrogate: minimum description length", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}), "interrogate_clip_max_length": OptionInfo(192, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}), "interrogate_clip_dict_limit": OptionInfo(2048, "CLIP: maximum number of lines in text file"), - "interrogate_clip_skip_categories": OptionInfo(["artists", "movements", "flavors"], "CLIP: skip inquire categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types), + "interrogate_clip_skip_categories": OptionInfo(["artists", "movements", "flavors"], "Interrogate: skip categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types), "interrogate_deepbooru_score_threshold": OptionInfo(0.65, "Interrogate: deepbooru score threshold", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), "deepbooru_sort_alpha": OptionInfo(False, "Interrogate: deepbooru sort alphabetically"), "deepbooru_use_spaces": OptionInfo(False, "Use spaces for tags in deepbooru"), diff --git a/modules/ui.py b/modules/ui.py index 1c10cb5af..023941c54 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -928,11 +928,11 @@ def create_ui(startup_timer = None): if info.refresh is not None: if is_quicksettings: res = comp(label=info.label, value=fun(), elem_id=elem_id, **args) - create_refresh_button(res, info.refresh, args, f"refresh_{key}") + create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}") else: with FormRow(): res = comp(label=info.label, value=fun(), elem_id=elem_id, **args) - create_refresh_button(res, info.refresh, args, f"refresh_{key}") + create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}") else: try: res = comp(label=info.label, value=fun(), elem_id=elem_id, **args) From f36c1eb4762e7d63f0bc39ede315c576070e1038 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Sep 2023 13:01:20 -0400 Subject: [PATCH 13/64] jumbo patch --- CHANGELOG.md | 14 +++++- installer.py | 28 ++++++++++- launch.py | 6 ++- modules/codeformer_model.py | 2 +- modules/deepbooru.py | 2 +- modules/devices.py | 56 ++++++++++++++++++++-- modules/dml/hijack/kdiffusion.py | 14 +++--- modules/dml/hijack/plms.py | 5 +- modules/dml/hijack/realesrgan_model.py | 1 - modules/dml/hijack/stablediffusion.py | 5 +- modules/esrgan_model.py | 2 +- modules/img2img.py | 2 +- modules/intel/openvino/__init__.py | 4 +- modules/interrogate.py | 4 +- modules/loader.py | 4 +- modules/models/diffusion/uni_pc/sampler.py | 4 +- modules/models/diffusion/uni_pc/uni_pc.py | 4 +- modules/processing.py | 7 ++- modules/script_loading.py | 12 ++++- modules/sd_hijack_inpainting.py | 11 +++-- modules/sd_models.py | 21 ++++---- modules/sd_models_config.py | 4 +- modules/sd_samplers.py | 12 +++++ modules/sd_samplers_kdiffusion.py | 10 ++++ modules/shared.py | 45 +++++++++++------ modules/taesd/taesd.py | 4 +- modules/txt2img.py | 2 +- modules/ui_extra_networks.py | 6 +-- webui.py | 56 +++++++++++----------- 29 files changed, 244 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ae0c998..0174c5bdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,19 @@ ## Update for 2023-09-07 -Service release with many fixes +Mostly a service release +- tons of fixes +- new option **inference mode** + - default is standard `torch.no_grad` + new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus +- cache samplers between run + reduces overhead between generate calls slightly +- clean-up logging + - capture system info in startup log + - capture extension output + - capture ldm output + - cleaner server restart + ## Update for 2023-09-06 diff --git a/installer.py b/installer.py index 435d2a654..6796cf7cf 100644 --- a/installer.py +++ b/installer.py @@ -108,6 +108,10 @@ def setup_logging(): # logging.getLogger("DeepSpeed").handlers = log.handlers +def print_dict(d): + return ' '.join([f'{k}={v}' for k, v in d.items()]) + + def print_profile(profile: cProfile.Profile, msg: str): try: from rich import print # pylint: disable=redefined-builtin @@ -265,6 +269,26 @@ def clone(url, folder, commithash=None): git(f'-C "{folder}" checkout {commithash}') +def get_platform(): + try: + if platform.system() == 'Windows': + release = platform.platform(aliased = True, terse = False) + else: + release = platform.release() + return { + # 'host': platform.node(), + 'arch': platform.machine(), + 'cpu': platform.processor(), + 'system': platform.system(), + 'release': release, + # 'platform': platform.platform(aliased = True, terse = False), + # 'version': platform.version(), + 'python': platform.python_version(), + } + except Exception as e: + return { 'error': e } + + # check python version def check_python(): supported_minors = [9, 10, 11] @@ -873,11 +897,13 @@ def extensions_preload(parser): from modules.script_loading import preload_extensions from modules.paths_internal import extensions_builtin_dir, extensions_dir extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] + preload_time = {} for ext_dir in extension_folders: t0 = time.time() preload_extensions(ext_dir, parser) t1 = time.time() - log.info(f'Extension preload: {round(t1 - t0, 1)}s {ext_dir}') + preload_time[ext_dir] = round(t1 - t0, 2) + log.info(f'Extension preload: {preload_time}') except Exception: log.error('Error running extension preloading') if args.profile: diff --git a/launch.py b/launch.py index a019d9356..773ad9ac6 100644 --- a/launch.py +++ b/launch.py @@ -40,7 +40,7 @@ def get_custom_args(): current = getattr(args, arg) if current != default: custom[arg] = getattr(args, arg) - installer.log.info(f'Command line args: {custom}') + installer.log.info(f'Command line args: {installer.print_dict(custom)}') @lru_cache() @@ -137,7 +137,8 @@ def start_server(immediate=True, server=None): collected = gc.collect() if not immediate: time.sleep(3) - installer.log.debug(f'Memory {get_memory_stats()} Collected {collected}') + if collected > 0: + installer.log.debug(f'Memory {get_memory_stats()} Collected {collected}') module_spec = importlib.util.spec_from_file_location('webui', 'webui.py') # installer.log.debug(f'Loading module: {module_spec}') server = importlib.util.module_from_spec(module_spec) @@ -174,6 +175,7 @@ if __name__ == "__main__": if args.skip_git: installer.log.info('Skipping GIT operations') installer.check_version() + installer.log.info(f'Platform: {installer.print_dict(installer.get_platform())}') installer.set_environment() installer.check_torch() installer.check_modified_files() diff --git a/modules/codeformer_model.py b/modules/codeformer_model.py index e6e75b219..34e3a7a5a 100644 --- a/modules/codeformer_model.py +++ b/modules/codeformer_model.py @@ -97,7 +97,7 @@ def setup_model(dirname): cropped_face_t = cropped_face_t.unsqueeze(0).to(devices.device_codeformer) try: - with torch.no_grad(): + with devices.inference_context(): output = self.net(cropped_face_t, w=w if w is not None else shared.opts.code_former_weight, adain=True)[0] restored_face = tensor2img(output, rgb2bgr=True, min_max=(-1, 1)) del output diff --git a/modules/deepbooru.py b/modules/deepbooru.py index 547e1b4c6..de50853d6 100644 --- a/modules/deepbooru.py +++ b/modules/deepbooru.py @@ -56,7 +56,7 @@ class DeepDanbooru: pic = images.resize_image(2, pil_image.convert("RGB"), 512, 512) a = np.expand_dims(np.array(pic, dtype=np.float32), 0) / 255 - with torch.no_grad(), devices.autocast(): + with devices.inference_context(), devices.autocast(): x = torch.from_numpy(a).to(devices.device) y = self.model(x)[0].detach().cpu().numpy() diff --git a/modules/devices.py b/modules/devices.py index 8f306adbe..db05ed39a 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -17,6 +17,51 @@ def has_mps() -> bool: return mac_specific.has_mps +def get_gpu_info(): + def get_driver(): + import os + import subprocess + if torch.cuda.is_available() and torch.version.cuda: + try: + result = subprocess.run('nvidia-smi --query-gpu=driver_version --format=csv,noheader', shell=True, check=False, env=os.environ, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + version = result.stdout.decode(encoding="utf8", errors="ignore").strip() + return version + except Exception: + return '' + else: + return '' + + if not torch.cuda.is_available(): + return {} + else: + try: + if torch.version.cuda: + return { + 'device': f'{torch.cuda.get_device_name(torch.cuda.current_device())} ({str(torch.cuda.device_count())}) ({torch.cuda.get_arch_list()[-1]}) {str(torch.cuda.get_device_capability(device))}', + 'cuda': torch.version.cuda, + 'cudnn': torch.backends.cudnn.version(), + 'driver': get_driver(), + } + elif torch.version.hip: + return { + 'device': f'{torch.cuda.get_device_name(torch.cuda.current_device())} ({str(torch.cuda.device_count())})', + 'hip': torch.version.hip, + } + else: + try: + import intel_extension_for_pytorch as ipex# pylint: disable=import-error, unused-import + return { + 'device': f'{torch.xpu.get_device_name(torch.xpu.current_device())} ({str(torch.xpu.device_count())})', + 'ipex': ipex.__version__, + } + except Exception: + return { + 'device': 'unknown' + } + except Exception as ex: + return { 'error': ex } + + def extract_device_id(args, name): # pylint: disable=redefined-outer-name for x in range(len(args)): if name in args[x]: @@ -95,8 +140,8 @@ def test_fp16(): _y = layerNorm(x) shared.log.debug('Torch FP16 test passed') return True - except Exception as e: - shared.log.warning(f'Torch FP16 test failed: Forcing FP32 operations: {e}') + except Exception as ex: + shared.log.warning(f'Torch FP16 test failed: Forcing FP32 operations: {ex}') shared.opts.cuda_dtype = 'FP32' shared.opts.no_half = True shared.opts.no_half_vae = True @@ -133,7 +178,7 @@ def set_cuda_params(): torch.backends.cudnn.allow_tf32 = True except Exception: pass - global dtype, dtype_vae, dtype_unet, unet_needs_upcast # pylint: disable=global-statement + global dtype, dtype_vae, dtype_unet, unet_needs_upcast, inference_context # pylint: disable=global-statement if shared.opts.cuda_dtype == 'FP32': dtype = torch.float32 dtype_vae = torch.float32 @@ -159,12 +204,14 @@ def set_cuda_params(): shared.log.info('Torch override VAE dtype: no-half set') dtype_vae = torch.float32 unet_needs_upcast = shared.opts.upcast_sampling + inference_context = torch.inference_mode if shared.opts.inference_mode == 'inference-mode' else torch.no_grad shared.log.debug(f'Desired Torch parameters: dtype={shared.opts.cuda_dtype} no-half={shared.opts.no_half} no-half-vae={shared.opts.no_half_vae} upscast={shared.opts.upcast_sampling}') - shared.log.info(f'Setting Torch parameters: dtype={dtype} vae={dtype_vae} unet={dtype_unet}') + shared.log.info(f'Setting Torch parameters: dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__}') shared.log.debug(f'Torch default device: {torch.device(get_optimal_device_name())}') args = cmd_args.parser.parse_args() +backend = 'not set' if args.use_ipex or (hasattr(torch, 'xpu') and torch.xpu.is_available()): backend = 'ipex' from modules.intel.ipex import ipex_init @@ -188,6 +235,7 @@ elif sys.platform == 'darwin': else: backend = 'cpu' +inference_context = torch.no_grad cuda_ok = torch.cuda.is_available() cpu = torch.device("cpu") device = device_interrogate = device_gfpgan = device_esrgan = device_codeformer = None diff --git a/modules/dml/hijack/kdiffusion.py b/modules/dml/hijack/kdiffusion.py index 19e16b013..d772dc88f 100644 --- a/modules/dml/hijack/kdiffusion.py +++ b/modules/dml/hijack/kdiffusion.py @@ -1,7 +1,7 @@ import torch from tqdm.auto import tqdm from k_diffusion import sampling -from modules.shared import device +import modules.devices as devices def dpm_solver_adaptive(self, x, t_start, t_end, order=3, rtol=0.05, atol=0.0078, h_init=0.05, pcoeff=0., icoeff=1., dcoeff=0., accept_safety=0.81, eta=0., s_noise=1., noise_sampler=None): @@ -12,8 +12,8 @@ def dpm_solver_adaptive(self, x, t_start, t_end, order=3, rtol=0.05, atol=0.0078 if not forward and eta: raise ValueError('eta must be 0 for reverse sampling') h_init = abs(h_init) * (1 if forward else -1) - atol = torch.tensor(atol, device=device) - rtol = torch.tensor(rtol, device=device) + atol = torch.tensor(atol, device=devices.device) + rtol = torch.tensor(rtol, device=devices.device) s = t_start x_prev = x accept = True @@ -58,7 +58,7 @@ def dpm_solver_adaptive(self, x, t_start, t_end, order=3, rtol=0.05, atol=0.0078 return x, info -@torch.no_grad() +@devices.inference_context() def sample_dpm_fast(model, x, sigma_min, sigma_max, n, extra_args=None, callback=None, disable=None, eta=0., s_noise=1., noise_sampler=None): """DPM-Solver-Fast (fixed step size). See https://arxiv.org/abs/2206.00927.""" if sigma_min <= 0 or sigma_max <= 0: @@ -67,10 +67,10 @@ def sample_dpm_fast(model, x, sigma_min, sigma_max, n, extra_args=None, callback dpm_solver = sampling.DPMSolver(model, extra_args, eps_callback=pbar.update) if callback is not None: dpm_solver.info_callback = lambda info: callback({'sigma': dpm_solver.sigma(info['t']), 'sigma_hat': dpm_solver.sigma(info['t_up']), **info}) - return dpm_solver.dpm_solver_fast(x, dpm_solver.t(torch.tensor(sigma_max, device=device)), dpm_solver.t(torch.tensor(sigma_min, device=device)), n, eta, s_noise, noise_sampler) + return dpm_solver.dpm_solver_fast(x, dpm_solver.t(torch.tensor(sigma_max, device=devices.device)), dpm_solver.t(torch.tensor(sigma_min, device=devices.device)), n, eta, s_noise, noise_sampler) -@torch.no_grad() +@devices.inference_context() def sample_dpm_adaptive(model, x, sigma_min, sigma_max, extra_args=None, callback=None, disable=None, order=3, rtol=0.05, atol=0.0078, h_init=0.05, pcoeff=0., icoeff=1., dcoeff=0., accept_safety=0.81, eta=0., s_noise=1., noise_sampler=None, return_info=False): """DPM-Solver-12 and 23 (adaptive step size). See https://arxiv.org/abs/2206.00927.""" if sigma_min <= 0 or sigma_max <= 0: @@ -79,7 +79,7 @@ def sample_dpm_adaptive(model, x, sigma_min, sigma_max, extra_args=None, callbac dpm_solver = sampling.DPMSolver(model, extra_args, eps_callback=pbar.update) if callback is not None: dpm_solver.info_callback = lambda info: callback({'sigma': dpm_solver.sigma(info['t']), 'sigma_hat': dpm_solver.sigma(info['t_up']), **info}) - x, info = dpm_solver.dpm_solver_adaptive(x, dpm_solver.t(torch.tensor(sigma_max, device=device)), dpm_solver.t(torch.tensor(sigma_min, device=device)), order, rtol, atol, h_init, pcoeff, icoeff, dcoeff, accept_safety, eta, s_noise, noise_sampler) + x, info = dpm_solver.dpm_solver_adaptive(x, dpm_solver.t(torch.tensor(sigma_max, device=devices.device)), dpm_solver.t(torch.tensor(sigma_min, device=devices.device)), order, rtol, atol, h_init, pcoeff, icoeff, dcoeff, accept_safety, eta, s_noise, noise_sampler) if return_info: return x, info return x diff --git a/modules/dml/hijack/plms.py b/modules/dml/hijack/plms.py index 2baef815d..a8afcbd05 100644 --- a/modules/dml/hijack/plms.py +++ b/modules/dml/hijack/plms.py @@ -1,11 +1,10 @@ import torch - from ldm.models.diffusion.ddim import noise_like - import modules.sd_hijack_inpainting as plms_hijack +import modules.devices as devices -@torch.no_grad() +@devices.inference_context() def p_sample_plms(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, unconditional_guidance_scale=1., unconditional_conditioning=None, old_eps=None, t_next=None, dynamic_threshold=None): diff --git a/modules/dml/hijack/realesrgan_model.py b/modules/dml/hijack/realesrgan_model.py index 341e9aded..ad3b01cce 100644 --- a/modules/dml/hijack/realesrgan_model.py +++ b/modules/dml/hijack/realesrgan_model.py @@ -1,6 +1,5 @@ import math import torch - from realesrgan import RealESRGANer diff --git a/modules/dml/hijack/stablediffusion.py b/modules/dml/hijack/stablediffusion.py index b14b0ece1..3c634e802 100644 --- a/modules/dml/hijack/stablediffusion.py +++ b/modules/dml/hijack/stablediffusion.py @@ -1,9 +1,10 @@ import torch - from ldm.models.diffusion.ddim import DDIMSampler from ldm.modules.diffusionmodules.util import noise_like +import modules.devices as devices -@torch.no_grad() + +@devices.inference_context() def p_sample_ddim(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False, temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None, unconditional_guidance_scale=1., unconditional_conditioning=None, diff --git a/modules/esrgan_model.py b/modules/esrgan_model.py index e0b79069d..a7656ad58 100644 --- a/modules/esrgan_model.py +++ b/modules/esrgan_model.py @@ -199,7 +199,7 @@ def upscale_without_tiling(model, img): img = np.ascontiguousarray(np.transpose(img, (2, 0, 1))) / 255 img = torch.from_numpy(img).float() img = img.unsqueeze(0).to(devices.device_esrgan) - with torch.no_grad(): + with devices.inference_context(): output = model(img) output = output.squeeze().float().cpu().clamp_(0, 1).numpy() output = 255. * np.moveaxis(output, 0, 2) diff --git a/modules/img2img.py b/modules/img2img.py index fcfc50d90..01304b509 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -83,7 +83,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s shared.log.warning('Model not loaded') return [], '', '', 'Error: model not loaded' - shared.log.debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}|args={args}') + shared.log.debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}') if init_img is None: shared.log.debug('Init image not set') diff --git a/modules/intel/openvino/__init__.py b/modules/intel/openvino/__init__.py index b39eb3cdb..b6bcec4ab 100644 --- a/modules/intel/openvino/__init__.py +++ b/modules/intel/openvino/__init__.py @@ -8,7 +8,7 @@ from torch._dynamo.backends.registry import register_backend from torch.fx.experimental.proxy_tensor import make_fx from torch._inductor.compile_fx import compile_fx from hashlib import sha256 -from modules import shared +from modules import shared, devices @register_backend @fake_tensor_unsupported @@ -89,7 +89,7 @@ def openvino_fx(subgraph, example_inputs): else: example_inputs.reverse() model = make_fx(subgraph)(*example_inputs) - with torch.no_grad(): + with devices.inference_context(): model.eval() partitioner = Partitioner() compiled_model = partitioner.make_partitions(model) diff --git a/modules/interrogate.py b/modules/interrogate.py index 61bb14cc9..d95209792 100644 --- a/modules/interrogate.py +++ b/modules/interrogate.py @@ -174,7 +174,7 @@ class InterrogateModels: transforms.Normalize((0.48145466, 0.4578275, 0.40821073), (0.26862954, 0.26130258, 0.27577711)) ])(pil_image).unsqueeze(0).type(self.dtype).to(devices.device_interrogate) - with torch.no_grad(): + with devices.inference_context(): caption = self.blip_model.generate(gpu_image, sample=False, num_beams=shared.opts.interrogate_clip_num_beams, min_length=shared.opts.interrogate_clip_min_length, max_length=shared.opts.interrogate_clip_max_length) return caption[0] @@ -197,7 +197,7 @@ class InterrogateModels: clip_image = self.clip_preprocess(pil_image).unsqueeze(0).type(self.dtype).to(devices.device_interrogate) - with torch.no_grad(), devices.autocast(): + with devices.inference_context(), devices.autocast(): image_features = self.clip_model.encode_image(clip_image).type(self.dtype) image_features /= image_features.norm(dim=-1, keepdim=True) diff --git a/modules/loader.py b/modules/loader.py index f9c68c5d4..54dafa9e4 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -8,7 +8,6 @@ from modules import timer, errors initialized = False logging.getLogger("DeepSpeed").disabled = True import torch # pylint: disable=C0411 -errors.log.debug(f'Loaded Torch=={torch.__version__}') try: import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import errors.log.debug(f'Loaded IPEX=={ipex.__version__}') @@ -29,10 +28,9 @@ timer.startup.record("torch") from fastapi import FastAPI # pylint: disable=W0611,C0411 import gradio # pylint: disable=W0611,C0411 -errors.log.debug(f'Loaded Gradio=={gradio.__version__}') timer.startup.record("gradio") errors.install([gradio]) import diffusers # pylint: disable=W0611,C0411 -errors.log.debug(f'Loaded Diffusers=={diffusers.__version__}') timer.startup.record("diffusers") +errors.log.debug(f'Loaded packages: torch={torch.__version__} diffusers={diffusers.__version__} gradio={gradio.__version__}') diff --git a/modules/models/diffusion/uni_pc/sampler.py b/modules/models/diffusion/uni_pc/sampler.py index 21991d2b9..1800b9c2a 100644 --- a/modules/models/diffusion/uni_pc/sampler.py +++ b/modules/models/diffusion/uni_pc/sampler.py @@ -21,7 +21,7 @@ class UniPCSampler(object): # persist steps so we can eventually find denoising strength self.inflated_steps = ddim_num_steps - @torch.no_grad() + @devices.inference_context() def stochastic_encode(self, x0, t, use_original_steps=False, noise=None): if noise is None: noise = torch.randn_like(x0) @@ -119,7 +119,7 @@ class UniPCSampler(object): self.after_sample = after_sample self.after_update = after_update - @torch.no_grad() + @devices.inference_context() def sample(self, S, batch_size, diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index 6dddbcfbd..56604a7b6 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -3,7 +3,7 @@ import torch.nn.functional as F import math import time from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn -from modules import shared +from modules import shared, devices class NoiseScheduleVP: @@ -760,7 +760,7 @@ class UniPC: with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn()) as progress: task = progress.add_task(description="Initializing", total=steps) t = time.time() - with torch.no_grad(): + with devices.inference_context(): vec_t = timesteps[0].expand((x.shape[0])) model_prev_list = [self.model_fn(x, vec_t)] t_prev_list = [vec_t] diff --git a/modules/processing.py b/modules/processing.py index f1ccc4901..985773c80 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -707,7 +707,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: return '' ema_scope_context = p.sd_model.ema_scope if shared.backend == shared.Backend.ORIGINAL else nullcontext - with torch.no_grad(), ema_scope_context(): + with devices.inference_context(), ema_scope_context(): t0 = time.time() with devices.autocast(): p.init(p.all_prompts, p.all_seeds, p.all_subseeds) @@ -894,7 +894,6 @@ def old_hires_fix_first_pass_dimensions(width, height): class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): - sampler = None def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, firstphase_width: int = 0, firstphase_height: int = 0, hr_scale: float = 2.0, hr_upscaler: str = None, hr_second_pass_steps: int = 0, hr_resize_x: int = 0, hr_resize_y: int = 0, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs): @@ -920,11 +919,11 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.refiner_start = refiner_start self.refiner_prompt = refiner_prompt self.refiner_negative = refiner_negative + self.sampler = None def init(self, all_prompts, all_seeds, all_subseeds): if shared.backend == shared.Backend.DIFFUSERS: modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.TEXT_2_IMAGE) - self.width = self.width or 512 self.height = self.height or 512 @@ -1052,7 +1051,6 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): - sampler = None def __init__(self, init_images: list = None, resize_mode: int = 0, denoising_strength: float = 0.3, image_cfg_scale: float = None, mask: Any = None, mask_blur: int = 4, inpainting_fill: int = 0, inpaint_full_res: bool = True, inpaint_full_res_padding: int = 0, inpainting_mask_invert: int = 0, initial_noise_multiplier: float = None, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs): super().__init__(**kwargs) @@ -1080,6 +1078,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): self.enable_hr = None self.is_batch = False self.scale_by = 1.0 + self.sampler = None def init(self, all_prompts, all_seeds, all_subseeds): if shared.backend == shared.Backend.DIFFUSERS and self.image_mask is None: diff --git a/modules/script_loading.py b/modules/script_loading.py index b28f6b65d..49489c4ed 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -1,6 +1,9 @@ +import io import os +import contextlib import importlib.util import modules.errors as errors +from installer import setup_logging preloaded = [] @@ -10,13 +13,18 @@ def load_module(path): module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) module = importlib.util.module_from_spec(module_spec) try: - module_spec.loader.exec_module(module) + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + module_spec.loader.exec_module(module) + 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()}') except Exception as e: errors.display(e, f'Module load: {path}') return module - def preload_extensions(extensions_dir, parser): if not os.path.isdir(extensions_dir): return diff --git a/modules/sd_hijack_inpainting.py b/modules/sd_hijack_inpainting.py index 7b392b4f4..882560a1c 100644 --- a/modules/sd_hijack_inpainting.py +++ b/modules/sd_hijack_inpainting.py @@ -1,8 +1,11 @@ +import io +import contextlib import torch - -import ldm.models.diffusion.ddpm -import ldm.models.diffusion.ddim -import ldm.models.diffusion.plms +stdout = io.StringIO() +with contextlib.redirect_stdout(stdout): + import ldm.models.diffusion.ddpm + import ldm.models.diffusion.ddim + import ldm.models.diffusion.plms from ldm.models.diffusion.ddpm import LatentDiffusion # pylint: disable=unused-import from ldm.models.diffusion.plms import PLMSSampler # pylint: disable=unused-import diff --git a/modules/sd_models.py b/modules/sd_models.py index 72422b498..14b78921a 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -1,5 +1,3 @@ -import collections -import os.path import re import io import sys @@ -7,6 +5,9 @@ import json import time import logging import threading +import contextlib +import collections +import os.path from os import mkdir from urllib import request from enum import Enum @@ -990,13 +991,17 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None, timer.record("config") shared.log.debug(f'Model config loaded: {memory_stats()}') sd_model = None - # shared.log.debug(f'Model config: {sd_config.model.get("params", dict())}') - try: - clip_is_included_into_sd = sd1_clip_weight in state_dict or sd2_clip_weight in state_dict - with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd): + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + try: + clip_is_included_into_sd = sd1_clip_weight in state_dict or sd2_clip_weight in state_dict + with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd): + sd_model = instantiate_from_config(sd_config.model) + except Exception: sd_model = instantiate_from_config(sd_config.model) - except Exception: - sd_model = instantiate_from_config(sd_config.model) + for line in stdout.getvalue().splitlines(): + if len(line) > 0: + shared.log.info(f'LDM: {line.strip()}') shared.log.debug(f"Model created from config: {checkpoint_config}") sd_model.used_config = checkpoint_config timer.record("create") diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 819bebd34..40a0ed638 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -2,7 +2,7 @@ import os import torch -from modules import paths, sd_disable_initialization +from modules import paths, sd_disable_initialization, devices sd_repo_configs_path = os.path.join(paths.paths['Stable Diffusion'], "configs", "stable-diffusion") config_default = paths.sd_default_config @@ -47,7 +47,7 @@ def is_using_v_parameterization_for_sd2(state_dict): ) unet.eval() - with torch.no_grad(): + with devices.inference_context(): unet_sd = {k.replace("model.diffusion_model.", ""): v for k, v in state_dict.items() if "model.diffusion_model." in k} unet.load_state_dict(unet_sd, strict=True) unet.to(device=device, dtype=torch.float) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index e0f6493c8..c624ea8d7 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -34,10 +34,18 @@ def find_sampler_config(name): return config +last_sampler = None + + def create_sampler(name, model): + global last_sampler # pylint: disable=global-statement + if last_sampler is not None and last_sampler.name == name: + return last_sampler if name == 'Default' and hasattr(model, 'scheduler'): config = {k: v for k, v in model.scheduler.config.items() if not k.startswith('_')} shared.log.debug(f'Sampler default {type(model.scheduler).__name__}: {config}') + last_sampler = model.scheduler + last_sampler.name = type(model.scheduler).__name__ return model.scheduler config = find_sampler_config(name) if config is None: @@ -48,6 +56,8 @@ def create_sampler(name, model): sampler.config = config sampler.name = name shared.log.debug(f'Sampler: sampler={sampler.name} config={sampler.config.options}') + last_sampler = sampler + last_sampler.name = sampler.name return sampler elif shared.backend == shared.Backend.DIFFUSERS: sampler = config.constructor(model) @@ -55,6 +65,8 @@ def create_sampler(name, model): model.scheduler_config = sampler.sampler.config.copy() model.scheduler = sampler.sampler shared.log.debug(f'Sampler: sampler={sampler.name} config={sampler.config}') + last_sampler = sampler.sampler + last_sampler.name = sampler.name return sampler.sampler else: return None diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 2134ed0d7..e39f6ab19 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -142,6 +142,16 @@ class CFGDenoiser(torch.nn.Module): else: cond_in = torch.cat([tensor, uncond]) + """ + adjusted_cond_scale = cond_scale # Adjusted cond_scale for uncond + last_uncond_steps = max(0, state.sampling_steps - 2) # Determine the last two steps before uncond stops + if self.step >= last_uncond_steps: # Check if we're in the last two steps before uncond stops + adjusted_cond_scale *= 1.5 # Apply uncond with 150% cond_scale + else: + if (self.step - last_uncond_steps) % 3 == 0: # Check if it's one of every three steps after uncond stops + adjusted_cond_scale *= 1.5 # Apply uncond with 150% cond_scale + """ + if shared.batch_cond_uncond: x_out = self.inner_model(x_in, sigma_in, cond=make_condition_dict([cond_in], image_cond_in)) else: diff --git a/modules/shared.py b/modules/shared.py index d68a744ce..86a8af86c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -1,8 +1,10 @@ +import io import os import sys import time import json import datetime +import contextlib import urllib.request from urllib.parse import urlparse from enum import Enum @@ -17,6 +19,7 @@ import modules.memmon import modules.styles import modules.devices as devices # pylint: disable=R0402 import modules.paths_internal as paths +from installer import print_dict from installer import log as central_logger # pylint: disable=E0611 @@ -271,6 +274,7 @@ def temp_disable_extensions(): cmd_opts.lyco_dir = opts.lora_dir if 'Lora' not in opts.disabled_extensions: disabled.append('Lora') + cmd_opts.controlnet_loglevel = 'WARNING' return disabled @@ -358,6 +362,7 @@ elif devices.backend == "rocm": else: # cuda cross_attention_optimization_default ="Scaled-Dot-Product" + options_templates.update(options_section(('sd', "Execution & Models"), { "sd_backend": OptionInfo("diffusers" if cmd_opts.use_openvino else "original", "Execution backend", gr.Radio, lambda: {"choices": ["original", "diffusers"] }), "sd_checkpoint_autoload": OptionInfo(True, "Model autoload on server start"), @@ -384,6 +389,7 @@ options_templates.update(options_section(('optimizations', "Optimizations"), { "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for hires pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), + "inference_mode": OptionInfo("no-grad", "Torch inference mode", gr.Radio, lambda: {"choices": ["no-grad", "inference-only"]}), "sd_vae_sliced_encode": OptionInfo(False, "VAE Slicing (original)"), })) @@ -400,8 +406,9 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { "rollback_vae": OptionInfo(False, "Attempt VAE roll back when produced NaN values (experimental)"), "opt_channelslast": OptionInfo(False, "Use channels last as torch memory format "), "cudnn_benchmark": OptionInfo(False, "Enable full-depth cuDNN benchmark feature"), - # "cuda_allow_tf32": OptionInfo(True, "Allow TF32 math ops"), - # "cuda_allow_tf16_reduced": OptionInfo(True, "Allow TF16 reduced precision math ops"), + "ipex_optimize": OptionInfo(True if devices.backend == "ipex" else False, "Enable IPEX Optimize for Intel GPUs"), + "directml_memory_provider": OptionInfo(default_memory_provider, 'DirectML memory stats provider', gr.Radio, lambda: {"choices": memory_providers}), + "cuda_compile_sep": OptionInfo("

Model Compile

", "", gr.HTML), "cuda_compile": OptionInfo(True if cmd_opts.use_openvino else False, "Enable model compile"), "cuda_compile_backend": OptionInfo("openvino_fx" if cmd_opts.use_openvino else "none", "Model compile backend", gr.Radio, lambda: {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex', 'openvino_fx']}), "cuda_compile_mode": OptionInfo("default", "Model compile mode", gr.Radio, lambda: {"choices": ['default', 'reduce-overhead', 'max-autotune']}), @@ -409,8 +416,6 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { "cuda_compile_precompile": OptionInfo(False, "Model compile precompile"), "cuda_compile_verbose": OptionInfo(False, "Model compile verbose mode"), "cuda_compile_errors": OptionInfo(True, "Model compile suppress errors"), - "ipex_optimize": OptionInfo(True if devices.backend == "ipex" else False, "Enable IPEX Optimize for Intel GPUs"), - "directml_memory_provider": OptionInfo(default_memory_provider, 'DirectML memory stats provider', gr.Dropdown, lambda: {"choices": memory_providers}), })) options_templates.update(options_section(('diffusers', "Diffusers Settings"), { @@ -439,7 +444,7 @@ options_templates.update(options_section(('system-paths', "System Paths"), { "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Path to directory with stable diffusion checkpoints"), "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Path to directory with stable diffusion diffusers"), "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Path to directory with VAE files"), - "sd_lora": OptionInfo("", "Add LoRA to prompt", gr.Textbox, {"choices": [], "visible": False}), + "sd_lora": OptionInfo("", "Add LoRA to prompt", gr.Textbox, {"visible": False}), "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Path to directory with LoRA network(s)"), "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Path to directory with LyCORIS network(s)"), "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "Path to user-defined styles file"), @@ -816,7 +821,6 @@ class Options: value = expected_type(value) return value - opts = Options() config_filename = cmd_opts.config opts.load(config_filename) @@ -828,7 +832,8 @@ else: opts.data['sd_backend'] = 'diffusers' if backend == Backend.DIFFUSERS else 'original' opts.data['uni_pc_lower_order_final'] = opts.schedulers_use_loworder opts.data['uni_pc_order'] = opts.schedulers_solver_order -log.info(f'Engine: backend={backend}') +log.info(f'Engine: backend={backend} compute={devices.backend} mode={devices.inference_context.__name__} device={devices.get_optimal_device_name()}') +log.info(f'Device: {print_dict(devices.get_gpu_info())}') prompt_styles = modules.styles.StyleDatabase(opts) cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or (cmd_opts.server_name or False)) and not cmd_opts.insecure @@ -917,14 +922,26 @@ total_tqdm = TotalTQDM() def restart_server(restart=True): if demo is None: return - log.info('Server shutdown requested') + log.warning('Server shutdown requested') try: - demo.server.wants_restart = restart - demo.server.should_exit = True - demo.server.force_exit = True - demo.close(verbose=False) - demo.server.close() - demo.fns = [] + sys.tracebacklimit = 0 + stdout = io.StringIO() + stderr = io.StringIO() + with contextlib.redirect_stdout(stdout), contextlib.redirect_stdout(stderr): + print('HERE1') + demo.server.wants_restart = restart + print('HERE2') + demo.server.should_exit = True + print('HERE3') + demo.server.force_exit = True + print('HERE4') + demo.close(verbose=False) + print('HERE5') + demo.server.close() + print('HERE6') + demo.fns = [] + time.sleep(1) + sys.tracebacklimit = 100 # os._exit(0) except (Exception, BaseException) as e: log.error(f'Server shutdown error: {e}') diff --git a/modules/taesd/taesd.py b/modules/taesd/taesd.py index 0355a81ff..4900a5ab0 100644 --- a/modules/taesd/taesd.py +++ b/modules/taesd/taesd.py @@ -5,6 +5,8 @@ Tiny AutoEncoder for Stable Diffusion """ import torch import torch.nn as nn +from modules import devices + def conv(n_in, n_out, **kwargs): return nn.Conv2d(n_in, n_out, 3, padding=1, **kwargs) @@ -65,7 +67,7 @@ class TAESD(nn.Module): return x.sub(TAESD.latent_shift).mul(2 * TAESD.latent_magnitude) -@torch.no_grad() +@devices.inference_context() def main(): from PIL import Image import sys diff --git a/modules/txt2img.py b/modules/txt2img.py index 648eb836a..51b09192e 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -6,7 +6,7 @@ from modules.ui import plaintext_to_html def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, latent_index: int, full_quality: bool, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, clip_skip: int, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, refiner_steps: int, refiner_start: int, refiner_prompt: str, refiner_negative: str, override_settings_texts, *args): # pylint: disable=unused-argument - shared.log.debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}||height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}||refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings_texts={override_settings_texts}|args={args}') + shared.log.debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}||height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}||refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings_texts={override_settings_texts}') if shared.sd_model is None: shared.log.warning('Model not loaded') diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 459851ada..b12b21859 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -364,10 +364,10 @@ def create_ui(container, button, tabname, skip_indexing = False): button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button]) button_close.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container]) - def refresh(title): + def en_refresh(title): res = [] for page in extra_pages: - if title == '' or title == page.title or len(page.html) == 0: + if title is None or title == '' or title == page.title or len(page.html) == 0: page.refresh() page.refresh_time = None page.create_page(ui.tabname) @@ -376,7 +376,7 @@ def create_ui(container, button, tabname, skip_indexing = False): ui.search.update(value = ui.search.value) return res - button_refresh.click(_js='extraNetworksRefreshButton', fn=refresh, inputs=[ui.search], outputs=ui.pages) + button_refresh.click(_js='extraNetworksRefreshButton', fn=en_refresh, inputs=[ui.search], outputs=ui.pages) return ui diff --git a/webui.py b/webui.py index fb32f809f..48eb3c036 100644 --- a/webui.py +++ b/webui.py @@ -1,3 +1,4 @@ +import io import os import sys import glob @@ -5,19 +6,18 @@ import signal import asyncio import logging import importlib +import contextlib from threading import Thread import modules.loader import torch # pylint: disable=wrong-import-order from modules import timer, errors, paths # pylint: disable=unused-import local_url = None -if not modules.loader.initialized: - errors.log.debug('Loading modules') -from installer import log, setup_logging, git_commit +from installer import log, git_commit, print_dict import ldm.modules.encoders.modules # pylint: disable=W0611,C0411,E0401 -from modules.call_queue import queue_lock, wrap_queued_call, wrap_gradio_gpu_call # pylint: disable=W0611,C0411,C0412 +from modules import shared, extensions, extra_networks, ui_tempdir, ui_extra_networks, modelloader # pylint: disable=ungrouped-imports from modules.paths import create_paths -from modules import shared, extensions, extra_networks, ui_tempdir, ui_extra_networks, modelloader +from modules.call_queue import queue_lock, wrap_queued_call, wrap_gradio_gpu_call # pylint: disable=W0611,C0411,C0412 import modules.devices import modules.sd_samplers import modules.upscaler @@ -42,7 +42,6 @@ from modules.middleware import setup_middleware state = shared.state if not modules.loader.initialized: timer.startup.record("libraries") - log.info('Loaded librareis') log.setLevel(logging.DEBUG if cmd_opts.debug else logging.INFO) logging.disable(logging.NOTSET if cmd_opts.debug else logging.DEBUG) if cmd_opts.server_name: @@ -78,7 +77,7 @@ def check_rollback_vae(): def initialize(): - log.debug('Entering initialize') + log.debug('Initializing') check_rollback_vae() @@ -105,7 +104,6 @@ def initialize(): t_timer, t_total = modules.scripts.load_scripts() timer.startup.record("extensions") timer.startup.records["extensions"] = t_total # scripts can reset the time - setup_logging() # reset since scripts can hijaack logging log.info(f'Extensions time: {t_timer.summary()}') modelloader.load_upscalers() @@ -206,12 +204,12 @@ def async_policy(): def start_common(): log.debug('Entering start sequence') if cmd_opts.debug and hasattr(shared, 'get_version'): - log.debug(f'Version: {shared.get_version()}') + log.debug(f'Version: {print_dict(shared.get_version())}') logging.disable(logging.NOTSET if cmd_opts.debug else logging.DEBUG) if shared.cmd_opts.data_dir is not None and len(shared.cmd_opts.data_dir) > 0: log.info(f'Using data path: {shared.cmd_opts.data_dir}') - if shared.cmd_opts.models_dir is not None and len(shared.cmd_opts.models_dir) > 0: - log.info(f'Using models path: {shared.cmd_opts.data_dir}') + if shared.cmd_opts.models_dir is not None and len(shared.cmd_opts.models_dir) > 0 and shared.cmd_opts.models_dir != 'models': + log.info(f'Using models path: {shared.cmd_opts.models_dir}') create_paths(opts, log) async_policy() initialize() @@ -244,23 +242,25 @@ def start_ui(): gradio_auth_creds += [x.strip() for x in line.split(',') if x.strip()] global local_url # pylint: disable=global-statement - app, local_url, share_url = shared.demo.launch( # app is FastAPI(Starlette) instance - share=cmd_opts.share, - server_name=server_name, - server_port=cmd_opts.port if cmd_opts.port != 7860 else None, - ssl_keyfile=cmd_opts.tls_keyfile, - ssl_certfile=cmd_opts.tls_certfile, - ssl_verify=not cmd_opts.tls_selfsign, - debug=False, - auth=[tuple(cred.split(':')) for cred in gradio_auth_creds] if gradio_auth_creds else None, - prevent_thread_lock=True, - max_threads=64, - show_api=False, - quiet=True, - favicon_path='html/logo.ico', - allowed_paths=[os.path.dirname(__file__), cmd_opts.data_dir], - app_kwargs=fastapi_args, - ) + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + app, local_url, share_url = shared.demo.launch( # app is FastAPI(Starlette) instance + share=cmd_opts.share, + server_name=server_name, + server_port=cmd_opts.port if cmd_opts.port != 7860 else None, + ssl_keyfile=cmd_opts.tls_keyfile, + ssl_certfile=cmd_opts.tls_certfile, + ssl_verify=not cmd_opts.tls_selfsign, + debug=False, + auth=[tuple(cred.split(':')) for cred in gradio_auth_creds] if gradio_auth_creds else None, + prevent_thread_lock=True, + max_threads=64, + show_api=False, + quiet=True, + favicon_path='html/logo.ico', + allowed_paths=[os.path.dirname(__file__), cmd_opts.data_dir], + app_kwargs=fastapi_args, + ) if cmd_opts.data_dir is not None: ui_tempdir.register_tmp_file(shared.demo, os.path.join(cmd_opts.data_dir, 'x')) shared.log.info(f'Local URL: {local_url}') From 29d88cf5571bd659e539317fc60eee78eb86d34f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Sep 2023 13:29:33 -0400 Subject: [PATCH 14/64] cleanup logging --- installer.py | 6 +++--- modules/devices.py | 3 +-- modules/shared.py | 2 +- modules/ui.py | 5 ++--- modules/ui_extensions.py | 2 +- webui.py | 3 ++- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/installer.py b/installer.py index 6796cf7cf..9111d0dcc 100644 --- a/installer.py +++ b/installer.py @@ -604,7 +604,7 @@ def list_extensions_folder(folder, quiet=False): disabled_extensions = opts.get('disabled_extensions', []) enabled_extensions = [x for x in os.listdir(folder) if x not in disabled_extensions and not x.startswith('.')] if not quiet: - log.info(f'Enabled {name}: {enabled_extensions}') + log.info(f'Extensions: enabled={enabled_extensions} {name}') return enabled_extensions @@ -737,9 +737,9 @@ def check_extensions(): extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] disabled_extensions_all = opts.get('disable_all_extensions', 'none') if disabled_extensions_all != 'none': - log.info(f'Disabled extensions: {disabled_extensions_all}') + log.info(f'Extensions: disabled={disabled_extensions_all}') else: - log.info(f'Disabled extensions: {opts.get("disabled_extensions", [])}') + log.info(f'Extensions: disabled={opts.get("disabled_extensions", [])}') for folder in extension_folders: if not os.path.isdir(folder): continue diff --git a/modules/devices.py b/modules/devices.py index db05ed39a..a51f95a57 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -138,7 +138,6 @@ def test_fp16(): x = torch.tensor([[1.5,.0,.0,.0]]).to(device).half() layerNorm = torch.nn.LayerNorm(4, eps=0.00001, elementwise_affine=True, dtype=torch.float16, device=device) _y = layerNorm(x) - shared.log.debug('Torch FP16 test passed') return True except Exception as ex: shared.log.warning(f'Torch FP16 test failed: Forcing FP32 operations: {ex}') @@ -206,7 +205,7 @@ def set_cuda_params(): unet_needs_upcast = shared.opts.upcast_sampling inference_context = torch.inference_mode if shared.opts.inference_mode == 'inference-mode' else torch.no_grad shared.log.debug(f'Desired Torch parameters: dtype={shared.opts.cuda_dtype} no-half={shared.opts.no_half} no-half-vae={shared.opts.no_half_vae} upscast={shared.opts.upcast_sampling}') - shared.log.info(f'Setting Torch parameters: dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__}') + shared.log.info(f'Setting Torch parameters: dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__} fp16={fp16_ok}') shared.log.debug(f'Torch default device: {torch.device(get_optimal_device_name())}') diff --git a/modules/shared.py b/modules/shared.py index 86a8af86c..838cc40ab 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -295,7 +295,7 @@ def list_themes(): builtin = list_builtin_themes() default = ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"] external = {x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()} - log.info(f'Themes list: builtin={len(builtin)} default={len(default)} external={len(external)}') + log.info(f'Themes: builtin={len(builtin)} default={len(default)} external={len(external)}') themes = sorted(builtin) + sorted(default) + sorted(external, key=str.casefold) return themes diff --git a/modules/ui.py b/modules/ui.py index 023941c54..cd5c3b3eb 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -522,7 +522,7 @@ def create_ui(startup_timer = None): negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[negative_token_counter]) ui_extra_networks.setup_ui(extra_networks_ui, txt2img_gallery) - log.debug(f'UI interface: tab=txt2img batch={show_batch.value} seed={show_seed.value} advanced={show_advanced.value} second_pass={show_second_pass.value}') + # log.debug(f'UI interface: tab=txt2img batch={show_batch.value} seed={show_seed.value} advanced={show_advanced.value} second_pass={show_second_pass.value}') timer.startup.record("ui-txt2img") @@ -882,8 +882,7 @@ def create_ui(startup_timer = None): parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None, )) - - log.debug(f'UI interface: tab=img2img seed={show_seed.value} resize={show_resize.value} batch={show_batch.value} denoise={show_denoise.value} advanced={show_advanced.value}') + # log.debug(f'UI interface: tab=img2img seed={show_seed.value} resize={show_resize.value} batch={show_batch.value} denoise={show_denoise.value} advanced={show_advanced.value}') timer.startup.record("ui-img2img") diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index aa7c60ef5..ab6f86724 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -34,7 +34,7 @@ def update_extension_list(): try: with open(os.path.join(paths.script_path, "html", "extensions.json"), "r", encoding="utf-8") as f: extensions_list = json.loads(f.read()) - shared.log.debug(f'Extensions list loaded: {os.path.join(paths.script_path, "html", "extensions.json")}') + # shared.log.debug(f'Extensions list loaded: {os.path.join(paths.script_path, "html", "extensions.json")}') except Exception: shared.log.debug(f'Extensions list failed to load: {os.path.join(paths.script_path, "html", "extensions.json")}') found = [] diff --git a/webui.py b/webui.py index 48eb3c036..98856ee60 100644 --- a/webui.py +++ b/webui.py @@ -291,7 +291,8 @@ def start_ui(): time_setup = [f'{k}:{round(v,3)}s' for (k,v) in modules.scripts.time_setup.items() if v > 0.005] shared.log.debug(f'Scripts setup: {time_setup}') time_component = [f'{k}:{round(v,3)}s' for (k,v) in modules.scripts.time_component.items() if v > 0.005] - shared.log.debug(f'Scripts components: {time_component}') + if len(time_component) > 0: + shared.log.debug(f'Scripts components: {time_component}') def webui(restart=False): From b94556260a9179ecce8cef5d75075663933b965a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Sep 2023 13:56:05 -0400 Subject: [PATCH 15/64] fix postprocessing file name pattern --- launch.py | 2 +- modules/images.py | 9 +++++---- modules/shared.py | 6 ------ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/launch.py b/launch.py index 773ad9ac6..488ff8366 100644 --- a/launch.py +++ b/launch.py @@ -222,7 +222,7 @@ if __name__ == "__main__": state = f'job="{instance.state.job}" {instance.state.job_no}/{instance.state.job_count}' installer.log.debug(f'Server alive={alive} requests={requests} memory {get_memory_stats()} {state}') if not alive: - if uv.wants_restart: + if uv is not None and uv.wants_restart: installer.log.info('Server restarting...') uv, instance = start_server(immediate=False, server=instance) else: diff --git a/modules/images.py b/modules/images.py index a4b1e1b3b..9697a8823 100644 --- a/modules/images.py +++ b/modules/images.py @@ -382,8 +382,6 @@ class FilenameGenerator: def apply(self, x): res = '' - if self.p is None: - return res for m in re_pattern.finditer(x): text, pattern = m.groups() if pattern is None: @@ -509,7 +507,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', i The base filename which will be applied to `filename pattern`. seed, prompt, short_filename, extension (`str`): - Image file extension, default is `png`. + Image file extension, default is `jpg`. pngsectionname (`str`): Specify the name of the section which `info` will be saved in. info (`str` or `PngImagePlugin.iTXt`): @@ -550,7 +548,10 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', i file_decoration = shared.opts.samples_filename_pattern else: file_decoration = "[seq]-[prompt_words]" - file_decoration = namegen.apply(file_decoration).strip(' ').strip('-') + suffix + file_decoration = namegen.apply(file_decoration).strip(' ').strip('-') + if len(file_decoration) == 0: + file_decoration = namegen.apply('[seq]').strip(' ').strip('-') + file_decoration += suffix if shared.opts.save_images_add_number: if '[seq]' not in file_decoration: file_decoration = f"[seq]-{file_decoration}" diff --git a/modules/shared.py b/modules/shared.py index 838cc40ab..bf5f46933 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -928,17 +928,11 @@ def restart_server(restart=True): stdout = io.StringIO() stderr = io.StringIO() with contextlib.redirect_stdout(stdout), contextlib.redirect_stdout(stderr): - print('HERE1') demo.server.wants_restart = restart - print('HERE2') demo.server.should_exit = True - print('HERE3') demo.server.force_exit = True - print('HERE4') demo.close(verbose=False) - print('HERE5') demo.server.close() - print('HERE6') demo.fns = [] time.sleep(1) sys.tracebacklimit = 100 From 94d2f21981bf9a1a7686a7fbb52219e3b154d3ef Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Sep 2023 14:05:55 -0400 Subject: [PATCH 16/64] update gradio --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ccc093b77..6773b615c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -49,7 +49,7 @@ accelerate==0.20.3 opencv-python-headless==4.7.0.72 diffusers==0.20.2 einops==0.4.1 -gradio==3.41.2 +gradio==3.43.2 huggingface_hub==0.16.4 numexpr==2.8.4 numpy==1.24.4 From e56dd5544db6983c11af0667298b80223f168e1b Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Sep 2023 14:07:21 -0400 Subject: [PATCH 17/64] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0174c5bdc..417294843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Mostly a service release new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus - cache samplers between run reduces overhead between generate calls slightly +- updated gradio - clean-up logging - capture system info in startup log - capture extension output From 34ee67477ef1a84f257ab8d0eba029a323fd7038 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 8 Sep 2023 23:49:49 +0300 Subject: [PATCH 18/64] Fix BF16 and FP32 logging --- modules/devices.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index a51f95a57..cf58d5fe0 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -187,13 +187,15 @@ def set_cuda_params(): dtype = torch.bfloat16 if bf16_ok else torch.float16 dtype_vae = torch.bfloat16 if bf16_ok else torch.float16 dtype_unet = torch.bfloat16 if bf16_ok else torch.float16 + else: + bf16_ok = False if shared.opts.cuda_dtype == 'FP16' or dtype == torch.float16: fp16_ok = test_fp16() dtype = torch.float16 if fp16_ok else torch.float32 dtype_vae = torch.float16 if fp16_ok else torch.float32 dtype_unet = torch.float16 if fp16_ok else torch.float32 else: - pass + fp16_ok = False if shared.opts.no_half: shared.log.info('Torch override dtype: no-half set') dtype = torch.float32 @@ -205,7 +207,7 @@ def set_cuda_params(): unet_needs_upcast = shared.opts.upcast_sampling inference_context = torch.inference_mode if shared.opts.inference_mode == 'inference-mode' else torch.no_grad shared.log.debug(f'Desired Torch parameters: dtype={shared.opts.cuda_dtype} no-half={shared.opts.no_half} no-half-vae={shared.opts.no_half_vae} upscast={shared.opts.upcast_sampling}') - shared.log.info(f'Setting Torch parameters: dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__} fp16={fp16_ok}') + shared.log.info(f'Setting Torch parameters: dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__} fp16={fp16_ok} bf16={bf16_ok}') shared.log.debug(f'Torch default device: {torch.device(get_optimal_device_name())}') From c98a4ddb6a1a9f45591a7dae987b6607c39a7cf9 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 8 Sep 2023 17:53:46 -0400 Subject: [PATCH 19/64] update sampler logic --- CHANGELOG.md | 2 -- modules/sd_samplers.py | 12 ------------ 2 files changed, 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 417294843..614a2a0ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,6 @@ Mostly a service release - new option **inference mode** - default is standard `torch.no_grad` new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus -- cache samplers between run - reduces overhead between generate calls slightly - updated gradio - clean-up logging - capture system info in startup log diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index c624ea8d7..e0f6493c8 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -34,18 +34,10 @@ def find_sampler_config(name): return config -last_sampler = None - - def create_sampler(name, model): - global last_sampler # pylint: disable=global-statement - if last_sampler is not None and last_sampler.name == name: - return last_sampler if name == 'Default' and hasattr(model, 'scheduler'): config = {k: v for k, v in model.scheduler.config.items() if not k.startswith('_')} shared.log.debug(f'Sampler default {type(model.scheduler).__name__}: {config}') - last_sampler = model.scheduler - last_sampler.name = type(model.scheduler).__name__ return model.scheduler config = find_sampler_config(name) if config is None: @@ -56,8 +48,6 @@ def create_sampler(name, model): sampler.config = config sampler.name = name shared.log.debug(f'Sampler: sampler={sampler.name} config={sampler.config.options}') - last_sampler = sampler - last_sampler.name = sampler.name return sampler elif shared.backend == shared.Backend.DIFFUSERS: sampler = config.constructor(model) @@ -65,8 +55,6 @@ def create_sampler(name, model): model.scheduler_config = sampler.sampler.config.copy() model.scheduler = sampler.sampler shared.log.debug(f'Sampler: sampler={sampler.name} config={sampler.config}') - last_sampler = sampler.sampler - last_sampler.name = sampler.name return sampler.sampler else: return None From 7bda4117384067894b9c12b89d9da19707a0900c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 9 Sep 2023 13:47:46 -0400 Subject: [PATCH 20/64] improve styles, better logging --- CHANGELOG.md | 2 + extensions-builtin/sd-webui-controlnet | 2 +- installer.py | 25 +++- javascript/black-teal.css | 2 +- javascript/extraNetworks.js | 2 +- javascript/light-teal.css | 2 +- ...omptBracketChecker.js => promptChecker.js} | 10 +- javascript/style.css | 2 +- launch.py | 12 +- modules/api/api.py | 8 +- modules/api/models.py | 5 +- modules/modelloader.py | 4 +- modules/paths_internal.py | 2 +- modules/script_loading.py | 2 +- modules/sd_samplers.py | 2 +- modules/shared.py | 2 + modules/styles.py | 57 ++++---- modules/ui.py | 8 +- modules/ui_extra_networks.py | 133 ++++++++++-------- modules/ui_extra_networks_styles.py | 55 ++++++-- webui.py | 11 +- 21 files changed, 215 insertions(+), 133 deletions(-) rename javascript/{promptBracketChecker.js => promptChecker.js} (84%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 614a2a0ab..1c47398af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ Mostly a service release - default is standard `torch.no_grad` new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus - updated gradio +- styles support for subfolders - clean-up logging - capture system info in startup log + - better diagnostic output - capture extension output - capture ldm output - cleaner server restart diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 2b12b2760..b15636ed3 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 2b12b2760dcde0e29c0d33640739ba3fe2c0cd32 +Subproject commit b15636ed35eff934af69985bcdfbc407cfedfe7d diff --git a/installer.py b/installer.py index 9111d0dcc..f4c4e0a3b 100644 --- a/installer.py +++ b/installer.py @@ -760,6 +760,28 @@ def check_extensions(): return round(newest_all) +def get_version(): + version = None + if version is None: + try: + res = subprocess.run('git log --pretty=format:"%h %ad" -1 --date=short', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) + ver = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ' ' + githash, updated = ver.split(' ') + res = subprocess.run('git remote get-url origin', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) + origin = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' + res = subprocess.run('git rev-parse --abbrev-ref HEAD', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True) + branch_name = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else '' + version = { + 'app': 'sd.next', + 'updated': updated, + 'hash': githash, + 'url': origin.replace('\n', '') + '/tree/' + branch_name.replace('\n', '') + } + except Exception: + version = { 'app': 'sd.next', 'version': 'unknown' } + return version + + # check version of the main repo and optionally upgrade it def check_version(offline=False, reset=True): # pylint: disable=unused-argument if args.skip_all: @@ -768,8 +790,7 @@ def check_version(offline=False, reset=True): # pylint: disable=unused-argument log.error('Not a git repository') if not args.ignore: sys.exit(1) - ver = git('log -1 --pretty=format:"%h %ad"') - log.info(f'Version: {ver}') + log.info(f'Version: {print_dict(get_version())}') if args.version or args.skip_git: return commit = git('rev-parse HEAD') diff --git a/javascript/black-teal.css b/javascript/black-teal.css index 207e5f2f8..aa79023e6 100644 --- a/javascript/black-teal.css +++ b/javascript/black-teal.css @@ -77,7 +77,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } .py-6 { padding-bottom: 0; } .tabs { background-color: var(--background-color); } .block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } -.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } +.tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } .label-wrap { margin: 16px 0px 8px 0px; } .gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; } .gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); } diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 9f9d6af88..65931e286 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -284,7 +284,7 @@ function extraNetworksSearchButton(event) { updateInput(searchTextarea); } -function extraNetworksRefreshButton() { +function getENActivePage() { const tabname = getENActiveTab(); const page = gradioApp().querySelector(`#${tabname}_extra_networks > .tabs > .tab-nav > .selected`); return page ? page.innerText : ''; diff --git a/javascript/light-teal.css b/javascript/light-teal.css index a7f285951..accdeea16 100644 --- a/javascript/light-teal.css +++ b/javascript/light-teal.css @@ -77,7 +77,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } .py-6 { padding-bottom: 0; } .tabs { background-color: var(--background-color); } .block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; } -.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } +.tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; } .label-wrap { margin: 16px 0px 8px 0px; } .gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; } .gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); } diff --git a/javascript/promptBracketChecker.js b/javascript/promptChecker.js similarity index 84% rename from javascript/promptBracketChecker.js rename to javascript/promptChecker.js index f5aa1f79a..66fa4442d 100644 --- a/javascript/promptBracketChecker.js +++ b/javascript/promptChecker.js @@ -3,15 +3,17 @@ // Counts open and closed brackets (round, square, curly) in the prompt and negative prompt text boxes in the txt2img and img2img tabs. // If there's a mismatch, the keyword counter turns red and if you hover on it, a tooltip tells you what's wrong. +let promptCheckerInitialized = false; + function checkBrackets(textArea, counterElt) { const counts = {}; - (textArea.value.match(/[(){}[\]]/g) || []).forEach((bracket) => { counts[bracket] = (counts[bracket] || 0) + 1; }); const errors = []; function checkPair(open, close, kind) { if (counts[open] !== counts[close]) errors.push(`${open}...${close} - Detected ${counts[open] || 0} opening and ${counts[close] || 0} closing ${kind}.`); } + (textArea.value.match(/[(){}[\]]/g) || []).forEach((bracket) => { counts[bracket] = (counts[bracket] || 0) + 1; }); checkPair('(', ')', 'round brackets'); checkPair('[', ']', 'square brackets'); checkPair('{', '}', 'curly brackets'); @@ -22,10 +24,14 @@ function checkBrackets(textArea, counterElt) { function setupBracketChecking(idPrompt, idCounter) { const textarea = gradioApp().querySelector(`#${idPrompt} > label > textarea`); const counter = gradioApp().getElementById(idCounter); - if (textarea && counter) textarea.addEventListener('input', () => checkBrackets(textarea, counter)); + if (!textarea || !counter) return; + if (!promptCheckerInitialized) log('promptChecker'); + promptCheckerInitialized = true; + textarea.addEventListener('input', () => checkBrackets(textarea, counter)); } onAfterUiUpdate(() => { + if (promptCheckerInitialized) return; setupBracketChecking('txt2img_prompt', 'txt2img_token_counter'); setupBracketChecking('txt2img_neg_prompt', 'txt2img_negative_token_counter'); setupBracketChecking('img2img_prompt', 'img2img_token_counter'); diff --git a/javascript/style.css b/javascript/style.css index 381598f15..b1255e14d 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -19,7 +19,7 @@ div.gradio-html.min{ min-height: 0; } .gradio-dropdown label span:not(.has-info), .gradio-textbox label span:not(.has-info), .gradio-number label span:not(.has-info) { margin-bottom: 0; } .gradio-dropdown ul.options li.item { padding: 0.05em 0; } .gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-100); } -.gradio-dropdown ul.options{ z-index: 3000; min-width: fit-content; max-width: inherit; white-space: nowrap; } +.gradio-dropdown ul.options { z-index: 3000; min-width: fit-content; max-width: inherit; max-height: 25vh !important; white-space: nowrap; } .gradio-dropdown:not(.multiselect) .wrap-inner.wrap-inner.wrap-inner{ flex-wrap: unset; } .gradio-dropdown.multiselect .token-remove.remove-all.remove-all{ display: flex; } .gradio-dropdown.multiselect div.wrap-inner { overflow-x: hidden; overflow-y: auto; max-height: 50vh; overflow-wrap: anywhere; } diff --git a/launch.py b/launch.py index 488ff8366..1b2106992 100644 --- a/launch.py +++ b/launch.py @@ -40,7 +40,7 @@ def get_custom_args(): current = getattr(args, arg) if current != default: custom[arg] = getattr(args, arg) - installer.log.info(f'Command line args: {installer.print_dict(custom)}') + installer.log.info(f'Command line args: {sys.argv[1:]} {installer.print_dict(custom)}') @lru_cache() @@ -121,7 +121,7 @@ def get_memory_stats(): process = psutil.Process(os.getpid()) res = process.memory_info() ram_total = 100 * res.rss / process.memory_percent() - return f'used: {gb(res.rss)} total: {gb(ram_total)}' + return f'used={gb(res.rss)} total={gb(ram_total)}' def start_server(immediate=True, server=None): @@ -143,7 +143,6 @@ def start_server(immediate=True, server=None): # installer.log.debug(f'Loading module: {module_spec}') server = importlib.util.module_from_spec(module_spec) installer.log.debug(f'Starting module: {server}') - installer.log.info(f"Server arguments: {sys.argv[1:]}") get_custom_args() module_spec.loader.exec_module(server) uvicorn = None @@ -218,9 +217,10 @@ if __name__ == "__main__": except Exception: alive = False requests = 0 - if round(time.time()) % 120 == 0: - state = f'job="{instance.state.job}" {instance.state.job_no}/{instance.state.job_count}' - installer.log.debug(f'Server alive={alive} requests={requests} memory {get_memory_stats()} {state}') + if round(time.time()) % 10 == 0: + state = f'job="{instance.state.job}" {instance.state.job_no}/{instance.state.job_count}' if instance.state.job != '' or instance.state.job_no != 0 or instance.state.job_count != 0 else 'idle' + uptime = round(time.time() - instance.state.server_start) + installer.log.debug(f'Server alive={alive} jobs={instance.state.total_jobs} requests={requests} uptime={uptime}s memory {get_memory_stats()} {state}') if not alive: if uv is not None and uv.wants_restart: installer.log.info('Server restarting...') diff --git a/modules/api/api.py b/modules/api/api.py index 39436d14a..72fdf0829 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -131,7 +131,7 @@ class Api: self.add_api_route("/sdapi/v1/hypernetworks", self.get_hypernetworks, methods=["GET"], response_model=List[models.HypernetworkItem]) self.add_api_route("/sdapi/v1/face-restorers", self.get_face_restorers, methods=["GET"], response_model=List[models.FaceRestorerItem]) self.add_api_route("/sdapi/v1/realesrgan-models", self.get_realesrgan_models, methods=["GET"], response_model=List[models.RealesrganItem]) - self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=List[models.PromptStyleItem]) + self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=List[models.StyleItem]) self.add_api_route("/sdapi/v1/embeddings", self.get_embeddings, methods=["GET"], response_model=models.EmbeddingsResponse) self.add_api_route("/sdapi/v1/refresh-checkpoints", self.refresh_checkpoints, methods=["POST"]) self.add_api_route("/sdapi/v1/sd-vae", self.get_sd_vaes, methods=["GET"], response_model=List[models.SDVaeItem]) @@ -479,10 +479,8 @@ class Api: def get_prompt_styles(self): styleList = [] - for k in shared.prompt_styles.styles: - style = shared.prompt_styles.styles[k] - styleList.append({"name":style[0], "prompt": style[1], "negative_prompt": style[2]}) - + for _k, v in shared.prompt_styles.styles.items(): + styleList.append(v) return styleList def get_embeddings(self): diff --git a/modules/api/models.py b/modules/api/models.py index 142d4db55..da4158dcd 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -264,10 +264,13 @@ class RealesrganItem(BaseModel): path: Optional[str] = Field(title="Path") scale: Optional[int] = Field(title="Scale") -class PromptStyleItem(BaseModel): +class StyleItem(BaseModel): name: str = Field(title="Name") prompt: Optional[str] = Field(title="Prompt") negative_prompt: Optional[str] = Field(title="Negative Prompt") + extra: Optional[str] = Field(title="Extra") + filename: Optional[str] = Field(title="Filename") + preview: Optional[str] = Field(title="Preview") class ArtistItem(BaseModel): name: str = Field(title="Name") diff --git a/modules/modelloader.py b/modules/modelloader.py index 152465941..afd2bcac3 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -434,6 +434,6 @@ def load_upscalers(): datas += scaler.scalers shared.sd_upscalers = sorted( datas, - # Special case for UpscalerNone keeps it at the beginning of the list. - key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else "" + key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else "" # Special case for UpscalerNone keeps it at the beginning of the list. ) + shared.log.debug(f"Loaded upscalers: items={len(shared.sd_upscalers)}") diff --git a/modules/paths_internal.py b/modules/paths_internal.py index 44afc4ad3..83c097e46 100644 --- a/modules/paths_internal.py +++ b/modules/paths_internal.py @@ -18,4 +18,4 @@ cmd_opts_pre = parser_pre.parse_known_args()[0] data_path = cmd_opts_pre.data_dir models_path = cmd_opts_pre.models_dir if os.path.isabs(cmd_opts_pre.models_dir) else os.path.join(data_path, cmd_opts_pre.models_dir) extensions_dir = os.path.join(data_path, "extensions") -extensions_builtin_dir = os.path.join(script_path, "extensions-builtin") +extensions_builtin_dir = "extensions-builtin" diff --git a/modules/script_loading.py b/modules/script_loading.py index 49489c4ed..64f16e681 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -19,7 +19,7 @@ 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()}') + 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/sd_samplers.py b/modules/sd_samplers.py index e0f6493c8..61a56c82a 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -23,7 +23,7 @@ def list_samplers(backend_name = shared.backend): samplers = all_samplers samplers_for_img2img = all_samplers samplers_map = {} - shared.log.debug(f'Available samplers: {[x.name for x in all_samplers]}') + # shared.log.debug(f'Available samplers: {[x.name for x in all_samplers]}') def find_sampler_config(name): diff --git a/modules/shared.py b/modules/shared.py index bf5f46933..292c60b4d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -94,6 +94,7 @@ class State: job = "" job_no = 0 job_count = 0 + total_jobs = 0 processing_has_refined_job_count = False job_timestamp = '0' sampling_step = 0 @@ -141,6 +142,7 @@ class State: return obj def begin(self, title=""): + self.total_jobs += 1 self.current_image = None self.current_image_sampling_step = 0 self.current_latent = None diff --git a/modules/styles.py b/modules/styles.py index a959fac89..82ad05593 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -3,22 +3,18 @@ from __future__ import annotations import csv import os import json -import shutil -import typing from installer import log from modules import paths -if typing.TYPE_CHECKING: - # Only import this when code is being type-checked, it doesn't have any effect at runtime - from .processing import StableDiffusionProcessing - - -class PromptStyle(typing.NamedTuple): - name: str - prompt: str - negative_prompt: str - extra: str = "" +class Style(): + def __init__(self, name: str, prompt: str = "", negative_prompt: str = "", extra: str = "", filename: str = "", preview: str = ""): + self.name = name + self.prompt = prompt + self.negative_prompt = negative_prompt + self.extra = extra + self.filename = filename + self.preview = preview def merge_prompts(style_prompt: str, prompt: str) -> str: @@ -43,7 +39,7 @@ def apply_styles_to_prompt(prompt, styles): class StyleDatabase: def __init__(self, opts): - self.no_style = PromptStyle("None", "", "") + self.no_style = Style("None") self.styles = {} self.path = opts.styles_dir if os.path.isfile(opts.styles_dir): @@ -54,8 +50,8 @@ class StyleDatabase: self.mkdir() self.save_styles(opts.styles_dir, verbose=True) log.debug(f'Migrated styles: file={legacy_file} folder={self.path}') + self.reload() self.mkdir() - self.reload() def mkdir(self): if not os.path.isdir(self.path): @@ -64,15 +60,21 @@ class StyleDatabase: def reload(self): self.styles.clear() - for fn in os.listdir(self.path): - if not fn.lower().endswith(".json"): - continue - with open(os.path.join(self.path, fn), 'r', encoding='utf-8') as f: - try: - style = json.load(f) - self.styles[style["name"]] = PromptStyle(style["name"], style["prompt"], style["negative"], style["extra"]) - except Exception as e: - log.error(f'Failed to load style: file={fn} error={e}') + def list_folder(folder): + for filename in os.listdir(folder): + fn = os.path.join(folder, filename) + if os.path.isfile(fn) and fn.lower().endswith(".json"): + with open(fn, 'r', encoding='utf-8') as f: + try: + style = json.load(f) + fn = os.path.splitext(os.path.relpath(fn, self.path))[0] + self.styles[style["name"]] = Style(style["name"], style.get("prompt", ""), style.get("negative", ""), style.get("extra", ""), fn, style.get("preview", "")) + except Exception as e: + log.error(f'Failed to load style: file={fn} error={e}') + elif os.path.isdir(fn): + list_folder(fn) + + list_folder(self.path) log.debug(f'Loaded styles: folder={self.path} items={len(self.styles.keys())}') def get_style_prompts(self, styles): @@ -94,6 +96,7 @@ class StyleDatabase: "prompt": self.styles[name].prompt, "negative": self.styles[name].negative_prompt, "extra": "", + "preview": "", } fn = os.path.join(path, name + ".json") try: @@ -110,13 +113,12 @@ class StyleDatabase: reader = csv.DictReader(file, skipinitialspace=True) for row in reader: try: - prompt = row["prompt"] if "prompt" in row else row["text"] - negative_prompt = row.get("negative_prompt", "") - self.styles[row["name"]] = PromptStyle(row["name"], prompt, negative_prompt) + self.styles[row["name"]] = Style(row["name"], row["prompt"] if "prompt" in row else row["text"], row.get("negative_prompt", "")) except Exception: log.error(f'Styles error: file={legacy_file} row={row}') log.debug(f'Loaded legacy styles: file={legacy_file} items={len(self.styles.keys())}') + """ def save_csv(self, path: str) -> None: import tempfile basedir = os.path.dirname(path) @@ -124,8 +126,9 @@ class StyleDatabase: os.makedirs(basedir, exist_ok=True) fd, temp_path = tempfile.mkstemp(".csv") with os.fdopen(fd, "w", encoding="utf-8-sig", newline='') as file: - writer = csv.DictWriter(file, fieldnames=PromptStyle._fields) + writer = csv.DictWriter(file, fieldnames=Style._fields) writer.writeheader() writer.writerows(style._asdict() for k, style in self.styles.items()) log.debug(f'Saved legacy styles: {path} {len(self.styles.keys())}') shutil.move(temp_path, path) + """ diff --git a/modules/ui.py b/modules/ui.py index cd5c3b3eb..ad3754416 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -73,7 +73,7 @@ def send_gradio_gallery_to_image(x): def add_style(name: str, prompt: str, negative_prompt: str): if name is None: return [gr_show() for x in range(4)] - style = modules.styles.PromptStyle(name, prompt, negative_prompt) + style = modules.styles.Style(name, prompt, negative_prompt) modules.shared.prompt_styles.styles[style.name] = style modules.shared.prompt_styles.save_styles(modules.shared.opts.styles_dir) return [gr.Dropdown.update(visible=True, choices=list(modules.shared.prompt_styles.styles)) for _ in range(2)] @@ -235,11 +235,11 @@ def create_toprow(is_img2img): with gr.Row(): with gr.Column(scale=80): with gr.Row(): - prompt = gr.Textbox(label="Prompt", elem_id=f"{id_part}_prompt", show_label=False, lines=3, placeholder="Prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"]) + prompt = gr.Textbox(elem_id=f"{id_part}_prompt", show_label=False, lines=3, placeholder="Prompt", elem_classes=["prompt"]) with gr.Row(): with gr.Column(scale=80): with gr.Row(): - negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"]) + negative_prompt = gr.Textbox(elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt", elem_classes=["prompt"]) button_interrogate = None button_deepbooru = None if is_img2img: @@ -270,7 +270,7 @@ def create_toprow(is_img2img): negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button") with gr.Row(elem_id=f"{id_part}_styles_row"): prompt_styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[k for k, v in modules.shared.prompt_styles.styles.items()], value=[], multiselect=True) - create_refresh_button(prompt_styles, modules.shared.prompt_styles.reload, lambda: {"choices": [k for k, v in modules.shared.prompt_styles.styles.items()]}, f"refresh_{id_part}_styles") + # create_refresh_button(prompt_styles, modules.shared.prompt_styles.reload, lambda: {"choices": [k for k, v in modules.shared.prompt_styles.styles.items()]}, f"refresh_{id_part}_styles") prompt_styles_btn = gr.Button('Apply', elem_id=f"{id_part}_styles_select", visible=False) prompt_styles_btn.click(_js="applyStyles", fn=parse_style, inputs=[prompt_styles], outputs=[prompt_styles]) return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index b12b21859..bc33ba7a4 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -64,7 +64,7 @@ def get_metadata(page: str = "", item: str = ""): metadata = page.metadata.get(item, 'none') if metadata is None: metadata = '' - shared.log.debug(f'Extra networks metadata: page={page} item={item} len={len(metadata)}') + shared.log.debug(f"Extra networks metadata: page='{page}' item={item} len={len(metadata)}") return JSONResponse({"metadata": metadata}) @@ -75,7 +75,7 @@ def get_info(page: str = "", item: str = ""): info = page.info.get(item, 'none') if info is None: info = '' - shared.log.debug(f'Extra networks info: page={page} item={item} len={len(info)}') + shared.log.debug(f"Extra networks info: page='{page}' item={item} len={len(info)}") return JSONResponse({"info": info}) @@ -150,7 +150,7 @@ class ExtraNetworksPage: def is_empty(self, folder): for f in listdir(folder): _fn, ext = os.path.splitext(f) - if ext.lower() in ['.ckpt', '.safetensors', '.pt'] or os.path.isdir(os.path.join(folder, f)): + if ext.lower() in ['.ckpt', '.safetensors', '.pt', '.json'] or os.path.isdir(os.path.join(folder, f)): return False return True @@ -164,21 +164,20 @@ class ExtraNetworksPage: continue try: img = Image.open(f) - if img.width > 1024 or img.height > 1024 or os.path.getsize(f) > 70000: + if img.width > 1024 or img.height > 1024 or os.path.getsize(f) > 65536: img = img.convert('RGB') img.thumbnail((512, 512), Image.HAMMING) - img.save(fn) + img.save(fn, quality=50) img.close() created += 1 except Exception as e: shared.log.error(f'Extra network error creating thumbnail: {f} {e}') if created > 0: - shared.log.info(f"Extra network created thumbnails: {self.name} {created}") + shared.log.info(f"Extra network thumbnails: {self.name} created={created}") self.missing_thumbs.clear() def create_page(self, tabname, skip = False): - if self.refresh_time is not None and self.refresh_time > refresh_time: - # shared.log.debug(f'Extra networks: {self.name} items={len(self.items)} tab={tabname} cached') + if self.refresh_time is not None and self.refresh_time > refresh_time: # cached page return self.html t0 = time.time() self_name_id = self.name.replace(" ", "_") @@ -219,7 +218,7 @@ class ExtraNetworksPage: else: return '' t1 = time.time() - shared.log.debug(f'Extra networks: page={self.name} items={len(self.items)} subdirs={len(subdirs)} tab={tabname} dirs={self.allowed_directories_for_previews()} time={round(t1-t0, 2)}') + shared.log.debug(f"Extra networks: page='{self.name}' items={len(self.items)} subdirs={len(subdirs)} tab={tabname} dirs={self.allowed_directories_for_previews()} time={round(t1-t0, 2)}") threading.Thread(target=self.create_thumb).start() def list_items(self): @@ -302,12 +301,48 @@ class ExtraNetworksPage: pass return '' + def save_preview(self, index, images, filename): + try: + image = image_from_url_text(images[int(index)]) + except Exception as e: + shared.log.error(f'Extra network save preview: {filename} {e}') + return + is_allowed = False + for page in extra_pages: + if any(path_is_parent(x, filename) for x in page.allowed_directories_for_previews()): + is_allowed = True + break + if not is_allowed: + shared.log.error(f'Extra network save preview: {filename} not allowed') + return + if image.width > 512 or image.height > 512: + image = image.convert('RGB') + image.thumbnail((512, 512), Image.HAMMING) + image.save(filename, quality=50) + fn, _ext = os.path.splitext(filename) + thumb = fn + '.thumb.jpg' + if os.path.exists(thumb): + shared.log.debug(f'Extra network delete thumbnail: {thumb}') + os.remove(thumb) + shared.log.info(f'Extra network save preview: {filename}') + + def save_description(self, filename, desc): + lastDotIndex = filename.rindex('.') + filename = filename[0:lastDotIndex]+".txt" + if desc != "": + try: + with open(filename, 'w', encoding='utf-8') as f: + f.write(desc) + shared.log.info(f'Extra network save description: {filename} {desc}') + except Exception as e: + shared.log.error(f'Extra network save description: {filename} {e}') + def initialize(): extra_pages.clear() -def register_default_pages(): +def register_pages(): from modules.ui_extra_networks_textual_inversion import ExtraNetworksPageTextualInversion from modules.ui_extra_networks_hypernets import ExtraNetworksPageHypernetworks from modules.ui_extra_networks_checkpoints import ExtraNetworksPageCheckpoints @@ -360,10 +395,6 @@ def create_ui(container, button, tabname, skip_indexing = False): is_visible = not is_visible return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")) - state_visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated - button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button]) - button_close.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container]) - def en_refresh(title): res = [] for page in extra_pages: @@ -371,12 +402,15 @@ def create_ui(container, button, tabname, skip_indexing = False): page.refresh() page.refresh_time = None page.create_page(ui.tabname) - shared.log.debug(f"Refreshing Extra networks: page={page.title} items={len(page.items)} tab={ui.tabname}") + shared.log.debug(f"Refreshing Extra networks: page='{page.title}' items={len(page.items)} tab={ui.tabname}") res.append(page.html) ui.search.update(value = ui.search.value) return res - button_refresh.click(_js='extraNetworksRefreshButton', fn=en_refresh, inputs=[ui.search], outputs=ui.pages) + state_visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated + button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button]) + button_close.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container]) + button_refresh.click(_js='getENActivePage', fn=en_refresh, inputs=[ui.search], outputs=ui.pages) return ui @@ -388,54 +422,37 @@ def path_is_parent(parent_path, child_path): def setup_ui(ui, gallery): - def save_preview(index, images, filename): - if len(images) == 0: - for page in extra_pages: - page.create_page(ui.tabname) - return [page.html for page in extra_pages] - index = int(index) - index = 0 if index < 0 else index - index = len(images) - 1 if index >= len(images) else index - img_info = images[index if index >= 0 else 0] - image = image_from_url_text(img_info) - is_allowed = False - for extra_page in extra_pages: - if any(path_is_parent(x, filename) for x in extra_page.allowed_directories_for_previews()): - is_allowed = True - break - assert is_allowed, f'writing to {filename} is not allowed' - image.save(filename) - fn, _ext = os.path.splitext(filename) - thumb = fn + '.thumb.jpg' - if os.path.exists(thumb): - shared.log.debug(f'Extra network delete thumbnail: {thumb}') - os.remove(thumb) - shared.log.info(f'Extra network save preview: {filename}') - return [page.create_page(ui.tabname) for page in extra_pages] + def save_preview(pagename, index, images, filename): + res = [] + for page in extra_pages: + if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0: + page.save_preview(index, images, filename) + res.append(page.create_page(ui.tabname)) + else: + res.append(page.html) + return res + ui.button_save_preview.click( fn=save_preview, - _js="function(x, y, z) {return [selected_gallery_index(), y, z]}", - inputs=[ui.preview_target_filename, gallery, ui.preview_target_filename], - outputs=[*ui.pages] + _js="function(t, i, y, z) {return [getENActivePage(), selected_gallery_index(), y, z]}", + inputs=[ui.search, ui.preview_target_filename, gallery, ui.preview_target_filename], + outputs=ui.pages ) - # write description to a file - def save_description(filename, desc): - lastDotIndex = filename.rindex('.') - filename = filename[0:lastDotIndex]+".txt" - if desc != "": - try: - with open(filename,'w', encoding='utf-8') as f: - f.write(desc) - shared.log.info(f'Extra network save description: {filename} {desc}') - except Exception as e: - shared.log.error(f'Extra network save description: {filename} {e}') - return [page.create_page(ui.tabname) for page in extra_pages] + def save_description(pagename, filename, desc): + res = [] + for page in extra_pages: + if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0: + page.save_description(filename, desc) + res.append(page.create_page(ui.tabname)) + else: + res.append(page.html) + return res ui.button_save_description.click( fn=save_description, - _js="function(x, y) { return [x, y] }", - inputs=[ui.description_target_filename, ui.description], - outputs=[*ui.pages] + _js="function(t, x, y) { return [getENActivePage(), x, y] }", + inputs=[ui.search, ui.description_target_filename, ui.description], + outputs=ui.pages ) diff --git a/modules/ui_extra_networks_styles.py b/modules/ui_extra_networks_styles.py index 974ea0614..c3e402d70 100644 --- a/modules/ui_extra_networks_styles.py +++ b/modules/ui_extra_networks_styles.py @@ -1,7 +1,6 @@ import os import html import json - from modules import shared, ui_extra_networks @@ -12,21 +11,53 @@ class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage): def refresh(self): shared.prompt_styles.reload() - def list_items(self): + """ + import io + import base64 + from PIL import Image + + def image2str(image): + buff = io.BytesIO() + image.save(buff, format="JPEG", quality=80) + encoded = base64.b64encode(buff.getvalue()) + return encoded + + def str2image(data): + buff = io.BytesIO(base64.b64decode(data)) + return Image.open(buff) + + def save_preview(self, index, images, filename): + from modules.generation_parameters_copypaste import image_from_url_text + try: + image = image_from_url_text(images[int(index)]) + except Exception: + shared.log.error(f'Extra network save preview: {filename} no image') + return + if image.width > 512 or image.height > 512: + image = image.convert('RGB').thumbnail((512, 512), Image.HAMMING) for k in shared.prompt_styles.styles.keys(): - path = os.path.join(shared.opts.styles_dir, k) - txt = f'Prompt: {shared.prompt_styles.styles[k].prompt}' - negative = shared.prompt_styles.styles[k].negative_prompt - if negative is not None and len(negative) > 0: - txt += f'\nNegative: {negative}' + if k == filename: + shared.prompt_styles.styles[k].preview = image2str(image) + break + + def save_description(self, filename, desc): + pass + """ + + def list_items(self): + for k, v in shared.prompt_styles.styles.items(): + fn = os.path.join(shared.opts.styles_dir, v.filename) + txt = f'Prompt: {v.prompt}' + if len(v.negative_prompt) > 0: + txt += f'\nNegative: {v.negative_prompt}' yield { - "name": k, - "search_term": path, - "filename": path, - "preview": self.find_preview(path), + "name": v.name, + "search_term": f'{txt} /{v.filename}', + "filename": v.filename, + "preview": self.find_preview(fn), "description": txt, "onclick": '"' + html.escape(f"""return selectStyle({json.dumps(k)})""") + '"', - "local_preview": f"{path}.{shared.opts.samples_format}", + "local_preview": f"{fn}.{shared.opts.samples_format}", } def allowed_directories_for_previews(self): diff --git a/webui.py b/webui.py index 98856ee60..f4496d169 100644 --- a/webui.py +++ b/webui.py @@ -13,7 +13,7 @@ import torch # pylint: disable=wrong-import-order from modules import timer, errors, paths # pylint: disable=unused-import local_url = None -from installer import log, git_commit, print_dict +from installer import log, git_commit import ldm.modules.encoders.modules # pylint: disable=W0611,C0411,E0401 from modules import shared, extensions, extra_networks, ui_tempdir, ui_extra_networks, modelloader # pylint: disable=ungrouped-imports from modules.paths import create_paths @@ -116,9 +116,10 @@ def initialize(): modules.textual_inversion.textual_inversion.list_textual_inversion_templates() shared.reload_hypernetworks() + shared.prompt_styles.reload() ui_extra_networks.initialize() - ui_extra_networks.register_default_pages() + ui_extra_networks.register_pages() extra_networks.initialize() extra_networks.register_default_extra_networks() timer.startup.record("extra-networks") @@ -196,15 +197,13 @@ def async_policy(): super().__init__() self.loop = self.get_event_loop() self.loop.set_exception_handler(self.handle_exception) - log.debug(f"Event loop: {self.loop}") + # log.debug(f"Event loop: {self.loop}") asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy()) def start_common(): log.debug('Entering start sequence') - if cmd_opts.debug and hasattr(shared, 'get_version'): - log.debug(f'Version: {print_dict(shared.get_version())}') logging.disable(logging.NOTSET if cmd_opts.debug else logging.DEBUG) if shared.cmd_opts.data_dir is not None and len(shared.cmd_opts.data_dir) > 0: log.info(f'Using data path: {shared.cmd_opts.data_dir}') @@ -265,7 +264,7 @@ def start_ui(): ui_tempdir.register_tmp_file(shared.demo, os.path.join(cmd_opts.data_dir, 'x')) shared.log.info(f'Local URL: {local_url}') if cmd_opts.docs: - shared.log.info(f'API Docs: {local_url[:-1]}/docs') # {local_url[:-1]}?view=api + shared.log.info(f'API Docs: {local_url[:-1]}/docs') # pylint: disable=unsubscriptable-object if share_url is not None: shared.log.info(f'Share URL: {share_url}') shared.log.debug(f'Gradio registered functions: {len(shared.demo.fns)}') From 36001151bbbcbe817ff2103579f5df930571461e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 9 Sep 2023 19:39:21 -0400 Subject: [PATCH 21/64] ti fixes --- javascript/promptChecker.js | 2 +- modules/prompt_parser_diffusers.py | 3 ++- modules/sd_hijack_clip.py | 2 +- .../textual_inversion/textual_inversion.py | 20 ++++++------------- .../ui_extra_networks_textual_inversion.py | 4 ++++ 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/javascript/promptChecker.js b/javascript/promptChecker.js index 66fa4442d..d18a030ec 100644 --- a/javascript/promptChecker.js +++ b/javascript/promptChecker.js @@ -25,7 +25,7 @@ function setupBracketChecking(idPrompt, idCounter) { const textarea = gradioApp().querySelector(`#${idPrompt} > label > textarea`); const counter = gradioApp().getElementById(idCounter); if (!textarea || !counter) return; - if (!promptCheckerInitialized) log('promptChecker'); + if (!promptCheckerInitialized) log('initPromptChecker'); promptCheckerInitialized = true; textarea.addEventListener('input', () => checkBrackets(textarea, counter)); } diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 6a8b1bef0..57cbf7f17 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -62,7 +62,8 @@ class DiffusersTextualInversionManager(BaseTextualInversionManager): replacement += f" {token}_{i}" i += 1 prompt = prompt.replace(token, replacement) - self.pipe.embedding_db.embeddings_used = list(set(self.pipe.embedding_db.embeddings_used)) + if hasattr(self.pipe, 'embedding_db'): + self.pipe.embedding_db.embeddings_used = list(set(self.pipe.embedding_db.embeddings_used)) return prompt def expand_textual_inversion_token_ids_if_necessary(self, token_ids: typing.List[int]) -> typing.List[int]: diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 73fc00c9c..045833de4 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -179,7 +179,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): used_embeddings[embedding.name] = embedding z = self.process_tokens(tokens, multipliers) zs.append(z) - self.hijack.embedding_db.embeddings_used = [name for name, embedding in used_embeddings.items()] + self.hijack.embedding_db.embeddings_used = [name for name in used_embeddings.keys()] return torch.hstack(zs) def process_tokens(self, remade_batch_tokens, batch_multipliers): diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index a0090cdd3..c8cee595d 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -21,13 +21,10 @@ textual_inversion_templates = {} def list_textual_inversion_templates(): textual_inversion_templates.clear() - for root, _dirs, fns in os.walk(shared.opts.embeddings_templates_dir): for fn in fns: path = os.path.join(root, fn) - textual_inversion_templates[fn] = TextualInversionTemplate(fn, path) - return textual_inversion_templates @@ -35,6 +32,7 @@ class Embedding: def __init__(self, vec, name, step=None): self.vec = vec self.name = name + self.tag = name self.step = step self.shape = None self.vectors = 0 @@ -81,13 +79,11 @@ class DirWithTextualInversionEmbeddings: def has_changed(self): if not os.path.isdir(self.path): return False - return directory_mtime(self.path) != self.mtime def update(self): if not os.path.isdir(self.path): return - self.mtime = directory_mtime(self.path) @@ -177,19 +173,14 @@ class EmbeddingDatabase: return if ext in ['.PNG', '.WEBP', '.JXL', '.AVIF']: - _, second_ext = os.path.splitext(name) - if second_ext.upper() == '.PREVIEW': + if '.preview' in filename.lower(): return embed_image = Image.open(path) if hasattr(embed_image, 'text') and 'sd-ti-embedding' in embed_image.text: data = embedding_from_b64(embed_image.text['sd-ti-embedding']) - name = data.get('name', name) else: data = extract_image_data_embed(embed_image) - if data: - name = data.get('name', name) - else: - # if data is None, means this is not an embeding, just a preview image + if not data: # if data is None, means this is not an embeding, just a preview image return elif ext in ['.BIN', '.PT']: data = torch.load(path, map_location="cpu") @@ -207,17 +198,18 @@ class EmbeddingDatabase: # diffuser concepts elif type(data) == dict and type(next(iter(data.values()))) == torch.Tensor: if len(data.keys()) != 1: - # shared.log.warning(f"Skipping embedding: {filename} multiple keys found") self.skipped_embeddings[name] = Embedding(None, name) return emb = next(iter(data.values())) if len(emb.shape) == 1: emb = emb.unsqueeze(0) else: - raise RuntimeError(f"Couldn't identify {filename} as neither textual inversion embedding nor diffuser concept.") + raise RuntimeError(f"Couldn't identify {filename} as textual inversion embedding") vec = emb.detach().to(devices.device, dtype=torch.float32) + # name = data.get('name', name) embedding = Embedding(vec, name) + embedding.tag = data.get('name', None) embedding.step = data.get('step', None) embedding.sd_checkpoint = data.get('sd_checkpoint', None) embedding.sd_checkpoint_name = data.get('sd_checkpoint_name', None) diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index 4ad9ac10a..51d077855 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -35,6 +35,9 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): embeddings = [] for embedding in embeddings: path, _ext = os.path.splitext(embedding.filename) + tags = {} + if embedding.tag is not None: + tags[embedding.tag]=1 yield { "name": os.path.splitext(embedding.name)[0], "filename": embedding.filename, @@ -44,6 +47,7 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): "search_term": self.search_terms_from_path(embedding.filename), "prompt": json.dumps(os.path.splitext(embedding.name)[0]), "local_preview": f"{path}.preview.{shared.opts.samples_format}", + "tags": tags, } def allowed_directories_for_previews(self): From 4898c0ffa72c103a8d5d289567dec0223f40eb31 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Sun, 10 Sep 2023 12:00:52 +0300 Subject: [PATCH 22/64] Fix inference-mode --- modules/intel/ipex/hijacks.py | 2 +- modules/shared.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/intel/ipex/hijacks.py b/modules/intel/ipex/hijacks.py index 1ad90d72b..0fd39137d 100644 --- a/modules/intel/ipex/hijacks.py +++ b/modules/intel/ipex/hijacks.py @@ -48,7 +48,7 @@ def check_device(device): return bool((isinstance(device, torch.device) and device.type == "cuda") or (isinstance(device, str) and "cuda" in device) or isinstance(device, int)) def return_xpu(device): - return f"xpu:{device[-1]}" if isinstance(device, str) and ":" in device else f"xpu:{device}" if isinstance(device, int) else torch.device(devices.device) if isinstance(device, torch.device) else devices.device + return f"xpu:{device.split(':')[-1]}" if isinstance(device, str) and ":" in device else f"xpu:{device}" if isinstance(device, int) else torch.device(devices.device) if isinstance(device, torch.device) else devices.device def ipex_no_cuda(orig_func, *args, **kwargs): torch.cuda.is_available = lambda: False diff --git a/modules/shared.py b/modules/shared.py index 292c60b4d..6e42b3ff8 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -391,7 +391,7 @@ options_templates.update(options_section(('optimizations', "Optimizations"), { "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for hires pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), - "inference_mode": OptionInfo("no-grad", "Torch inference mode", gr.Radio, lambda: {"choices": ["no-grad", "inference-only"]}), + "inference_mode": OptionInfo("no-grad", "Torch inference mode", gr.Radio, lambda: {"choices": ["no-grad", "inference-mode"]}), "sd_vae_sliced_encode": OptionInfo(False, "VAE Slicing (original)"), })) From 60df52f6365769cef7fe175ab0bfc0707560a6ec Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 08:32:53 -0400 Subject: [PATCH 23/64] add no-metadata --- CHANGELOG.md | 10 ++++++---- extensions-builtin/Lora/lora.py | 9 --------- .../Lora/ui_extra_networks_lora.py | 16 ++++++++-------- javascript/style.css | 4 ++-- modules/cmd_args.py | 1 + modules/sd_models.py | 2 ++ wiki | 2 +- 7 files changed, 20 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c47398af..f076ffac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,19 @@ # Change Log for SD.Next -## Update for 2023-09-07 +## Update for 2023-09-10 Mostly a service release - tons of fixes - new option **inference mode** - default is standard `torch.no_grad` new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus -- updated gradio -- styles support for subfolders +- new cmdline param `--no-metadata` + skips reading metadata from models that are not already cached +- updated gradio +- styles support for subfolders - clean-up logging - capture system info in startup log - - better diagnostic output + - better diagnostic output - capture extension output - capture ldm output - cleaner server restart diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 607dd8e33..4705830b8 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -77,19 +77,16 @@ class LoraOnDisk: self.filename = filename self.metadata = {} self.is_safetensors = os.path.splitext(filename)[1].lower() == ".safetensors" - if self.is_safetensors: try: self.metadata = sd_models.read_metadata_from_safetensors(filename) except Exception as e: errors.display(e, f"reading lora metadata: {filename}") - if self.metadata: m = {} for k, v in sorted(self.metadata.items(), key=lambda x: metadata_tags_order.get(x[0], 999)): m[k] = v self.metadata = m - self.ssmd_cover_images = self.metadata.pop('ssmd_cover_images', None) # those are cover images and they are too big to display in UI as text self.alias = self.metadata.get('ss_output_name', self.name) self.hash = None @@ -442,19 +439,13 @@ def list_available_loras(): forbidden_lora_aliases.clear() available_lora_hash_lookup.clear() forbidden_lora_aliases.update({"none": 1, "Addams": 1}) - os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) - for filename in sorted([*filter(extension_filter(['.PT', '.CKPT', '.SAFETENSORS']), directory_files(shared.cmd_opts.lora_dir))], key=str.lower): - name = os.path.splitext(os.path.basename(filename))[0] entry = LoraOnDisk(name, filename) - available_loras[name] = entry - if entry.alias in available_lora_aliases: forbidden_lora_aliases[entry.alias.lower()] = 1 - available_lora_aliases[name] = entry available_lora_aliases[entry.alias] = entry diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index a06ab27d8..1327ea76f 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -13,13 +13,13 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): lora.list_available_loras() def list_items(self): - for name, lora_on_disk in lora.available_loras.items(): - path, _ext = os.path.splitext(lora_on_disk.filename) - alias = lora_on_disk.get_alias() + for name, l in lora.available_loras.items(): + path, _ext = os.path.splitext(l.filename) + alias = l.get_alias() prompt = f" " prompt = json.dumps(prompt) - metadata = json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None - possible_tags = lora_on_disk.metadata.get('ss_tag_frequency', {}) if lora_on_disk.metadata is not None else {} + metadata = json.dumps(l.metadata, indent=4) if l.metadata else None + possible_tags = l.metadata.get('ss_tag_frequency', {}) if l.metadata is not None else {} if isinstance(possible_tags, str): possible_tags = {} shared.log.debug(f'Lora has invalid metadata: {path}') @@ -33,12 +33,12 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): yield { "name": name, "filename": path, - "fullname": lora_on_disk.filename, - "hash": lora_on_disk.shorthash, + "fullname": l.filename, + "hash": l.shorthash, "preview": self.find_preview(path), "description": self.find_description(path), "info": self.find_info(path), - "search_term": self.search_terms_from_path(lora_on_disk.filename) + ' '.join(tags.keys()), + "search_term": self.search_terms_from_path(l.filename) + ' '.join(tags.keys()), "prompt": prompt, "local_preview": f"{path}.{shared.opts.samples_format}", "metadata": metadata, diff --git a/javascript/style.css b/javascript/style.css index b1255e14d..c563f0a78 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -19,7 +19,7 @@ div.gradio-html.min{ min-height: 0; } .gradio-dropdown label span:not(.has-info), .gradio-textbox label span:not(.has-info), .gradio-number label span:not(.has-info) { margin-bottom: 0; } .gradio-dropdown ul.options li.item { padding: 0.05em 0; } .gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-100); } -.gradio-dropdown ul.options { z-index: 3000; min-width: fit-content; max-width: inherit; max-height: 25vh !important; white-space: nowrap; } +.gradio-dropdown ul.options { z-index: 3000; min-width: fit-content; max-width: fit-content; max-height: 25vh !important; white-space: nowrap; } .gradio-dropdown:not(.multiselect) .wrap-inner.wrap-inner.wrap-inner{ flex-wrap: unset; } .gradio-dropdown.multiselect .token-remove.remove-all.remove-all{ display: flex; } .gradio-dropdown.multiselect div.wrap-inner { overflow-x: hidden; overflow-y: auto; max-height: 50vh; overflow-wrap: anywhere; } @@ -120,7 +120,7 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } #quicksettings > button { margin-left: -0.5em; } #settings { display: flex; gap: var(--layout-gap); } -#settings div { border: none; justify-content: normal; gap: 0.5em; } +#settings div { border: none; gap: 0.5em; width: fit-content; } #settings > div.tab-content { flex: 10 0 75%; display: grid; } #settings > div.tab-content > div { border: none; padding: 0; } diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 41b6340da..23e13c08b 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -36,6 +36,7 @@ group.add_argument("--tls-certfile", type=str, help="Enable TLS and specify cert group.add_argument("--tls-selfsign", action="store_true", help="Enable TLS with self-signed certificates, default: %(default)s", default=None) group.add_argument("--server-name", type=str, help="Sets hostname of server, default: %(default)s", default=None) group.add_argument("--no-hashing", action='store_true', help="Disable hashing of checkpoints, default: %(default)s", default=False) +group.add_argument("--no-metadata", action='store_true', help="Disable reading of metadata from models, default: %(default)s", default=False) group.add_argument("--no-download", action='store_true', help="Disable download of default model, default: %(default)s", default=False) group.add_argument("--profile", action='store_true', help="Run profiler, default: %(default)s") group.add_argument("--disable-queue", action='store_true', help="Disable queues, default: %(default)s") diff --git a/modules/sd_models.py b/modules/sd_models.py index 14b78921a..90795368a 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -321,6 +321,8 @@ def read_metadata_from_safetensors(filename): if res is not None: return res res = {} + if shared.cmd_opts.no_metadata: + return {} try: t0 = time.time() with open(filename, mode="rb") as file: diff --git a/wiki b/wiki index de31c082f..c069991c3 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit de31c082f8bc04ebf33667587320bbafa469f4c1 +Subproject commit c069991c32e4bdc5c7355062868550f8459f3df0 From 2d0ea97a14cb7acf323aecc81f7fa8e85351c0cd Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 10:55:54 -0400 Subject: [PATCH 24/64] css optimizations, flat icons, metadata scrubbing --- .../Lora/ui_extra_networks_lora.py | 8 +--- javascript/amethyst-nightfall.css | 1 - javascript/black-orange.css | 1 - javascript/black-teal.css | 1 - javascript/light-teal.css | 1 - javascript/midnight-barbie.css | 1 - javascript/style.css | 17 ++++---- modules/sd_models.py | 39 +++++++++++++++---- modules/ui_symbols.py | 14 ++++++- 9 files changed, 55 insertions(+), 28 deletions(-) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 1327ea76f..e99105142 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -22,14 +22,10 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage): possible_tags = l.metadata.get('ss_tag_frequency', {}) if l.metadata is not None else {} if isinstance(possible_tags, str): possible_tags = {} - shared.log.debug(f'Lora has invalid metadata: {path}') tags = {} - for tag in possible_tags.keys(): - if '_' not in tag: - tag = f'0_{tag}' - words = tag.split('_', 1) + for k, v in possible_tags.items(): + words = k.split('_', 1) if '_' in k else [v, k] tags[' '.join(words[1:])] = words[0] - # shared.log.debug(f'Lora: {path}: name={name} alias={alias} tags={tags}') yield { "name": name, "filename": path, diff --git a/javascript/amethyst-nightfall.css b/javascript/amethyst-nightfall.css index ad370bb4d..265112d13 100644 --- a/javascript/amethyst-nightfall.css +++ b/javascript/amethyst-nightfall.css @@ -95,7 +95,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } -#quicksettings > div, #quicksettings > fieldset { min-width: 24em; max-width: 26em; line-height: 1.6em; margin-top: 0.4em; } #open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } #save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; } #script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; } diff --git a/javascript/black-orange.css b/javascript/black-orange.css index d98a42141..39406d41c 100644 --- a/javascript/black-orange.css +++ b/javascript/black-orange.css @@ -95,7 +95,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } -#quicksettings > div, #quicksettings > fieldset { line-height: 1.4em; margin-top: 0.4em; } #open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } #save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; } #script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; } diff --git a/javascript/black-teal.css b/javascript/black-teal.css index aa79023e6..c82ecd8cd 100644 --- a/javascript/black-teal.css +++ b/javascript/black-teal.css @@ -103,7 +103,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } -#quicksettings > div, #quicksettings > fieldset { line-height: 1.4em; margin-top: 0.4em; } #open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } #save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; } #script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; } diff --git a/javascript/light-teal.css b/javascript/light-teal.css index accdeea16..2e2779b47 100644 --- a/javascript/light-teal.css +++ b/javascript/light-teal.css @@ -103,7 +103,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } -#quicksettings > div, #quicksettings > fieldset { line-height: 1.4em; margin-top: 0.4em; } #open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } #save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; } #script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; } diff --git a/javascript/midnight-barbie.css b/javascript/midnight-barbie.css index b2c7130a9..a52980c27 100644 --- a/javascript/midnight-barbie.css +++ b/javascript/midnight-barbie.css @@ -95,7 +95,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } -#quicksettings > div, #quicksettings > fieldset { line-height: 1.4em; margin-top: 0.4em; } #open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; } #save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; } #script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; } diff --git a/javascript/style.css b/javascript/style.css index c563f0a78..e1b32b943 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -19,7 +19,7 @@ div.gradio-html.min{ min-height: 0; } .gradio-dropdown label span:not(.has-info), .gradio-textbox label span:not(.has-info), .gradio-number label span:not(.has-info) { margin-bottom: 0; } .gradio-dropdown ul.options li.item { padding: 0.05em 0; } .gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-100); } -.gradio-dropdown ul.options { z-index: 3000; min-width: fit-content; max-width: fit-content; max-height: 25vh !important; white-space: nowrap; } +.gradio-dropdown ul.options { z-index: 3000; min-width: fit-content; max-height: 25vh !important; white-space: nowrap; } .gradio-dropdown:not(.multiselect) .wrap-inner.wrap-inner.wrap-inner{ flex-wrap: unset; } .gradio-dropdown.multiselect .token-remove.remove-all.remove-all{ display: flex; } .gradio-dropdown.multiselect div.wrap-inner { overflow-x: hidden; overflow-y: auto; max-height: 50vh; overflow-wrap: anywhere; } @@ -30,10 +30,10 @@ footer { display: none; } td { border-bottom: none !important; } /* general styled components */ -.gradio-button.tool{ max-width: 1em; min-width: 1em !important; align-self: end; font-size: 1.4em } -.gradio-button.secondary-down{ background: var(--button-secondary-background-fill); color: var(--button-secondary-text-color); } -.gradio-button.secondary-down, .gradio-button.secondary-down:hover{ box-shadow: 1px 1px 1px rgba(0,0,0,0.25) inset, 0px 0px 3px rgba(0,0,0,0.15) inset; } -.gradio-button.secondary-down:hover{ background: var(--button-secondary-background-fill-hover); color: var(--button-secondary-text-color-hover); } +.gradio-button.tool { max-width: min-content; min-width: min-content !important; align-self: end; font-size: 1.4em; color: var(--body-text-color) !important; } +.gradio-button.secondary-down { background: var(--button-secondary-background-fill); color: var(--button-secondary-text-color); } +.gradio-button.secondary-down, .gradio-button.secondary-down:hover { box-shadow: 1px 1px 1px rgba(0,0,0,0.25) inset, 0px 0px 3px rgba(0,0,0,0.15) inset; } +.gradio-button.secondary-down:hover { background: var(--button-secondary-background-fill-hover); color: var(--button-secondary-text-color-hover); } .checkboxes-row { margin-bottom: 1em; gap: 0 !important; justify-content: space-around; flex-wrap: unset !important; } .checkboxes-row > div{ flex: 0; white-space: nowrap; min-width: auto; } @@ -115,9 +115,8 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } /* settings */ #si-sparkline-memo, #si-sparkline-load { background-color: #111; } .licenses { display: block !important; } -#quicksettings { width: fit-content; align-items: end; } -#quicksettings > div, #quicksettings > fieldset{ max-width: 20em; min-width: 24em; padding: 0; border: none; box-shadow: none; background: none; } -#quicksettings > button { margin-left: -0.5em; } +#quicksettings { width: fit-content; margin-top: 1em; } +#quicksettings > button { padding: 0 1em 0 0 } #settings { display: flex; gap: var(--layout-gap); } #settings div { border: none; gap: 0.5em; width: fit-content; } @@ -239,7 +238,7 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .extra-network-cards .card { height: fit-content; margin: 0 0 0.5em 0.5em; position: relative; scroll-snap-align: start; scroll-margin-top: 0; } .extra-network-cards .card .overlay { position: absolute; bottom: 0; padding: 0.2em; z-index: 10; width: 100%; background: none; } .extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); } -.extra-network-cards .card .overlay .name { font-size: 1.1em; font-weight: bold; text-shadow: 1px 1px black; color: white; } +.extra-network-cards .card .overlay .name { font-size: 1.1em; font-weight: bold; text-shadow: 1px 1px black; color: white; overflow-wrap: break-word; } .extra-network-cards .card .overlay .tags { margin: 4px; display: none; overflow-wrap: break-word; } .extra-network-cards .card .overlay .tag { padding: 2px; margin: 2px; background: var(--neutral-700); cursor: pointer; display: inline-block; } .extra-network-cards .card .overlay .actions { font-size: 2.2em; display: none; text-align-last: center; cursor: pointer; font-variant: unicase; height: 0.8em } diff --git a/modules/sd_models.py b/modules/sd_models.py index 90795368a..d4db1da88 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -310,6 +310,20 @@ def write_metadata(): sd_metadata_pending = 0 +def scrub_dict(dict_obj, keys): + for key in list(dict_obj.keys()): + if not isinstance(dict_obj, dict): + continue + elif key in keys: + dict_obj.pop(key, None) + elif isinstance(dict_obj[key], dict): + scrub_dict(dict_obj[key], keys) + elif isinstance(dict_obj[key], list): + for item in dict_obj[key]: + scrub_dict(item, keys) + return + + def read_metadata_from_safetensors(filename): global sd_metadata # pylint: disable=global-statement if sd_metadata is None: @@ -334,15 +348,26 @@ def read_metadata_from_safetensors(filename): json_data = json_start + file.read(metadata_len-2) json_obj = json.loads(json_data) for k, v in json_obj.get("__metadata__", {}).items(): + if v.startswith("data:"): + v = 'data' if k == 'format' and v == 'pt': continue - if isinstance(v, str) and v[0:1] == '{': - try: - res[k] = json.loads(v) - except Exception: - pass - else: - res[k] = v + large = True if len(v) > 4096 else False + if large and k == 'ss_datasets': + continue + if large and k == 'workflow': + continue + if large and k == 'prompt': + continue + if large and k == 'ss_bucket_info': + continue + if v[0:1] == '{': + v = json.loads(v) + if large and k == 'ss_tag_frequency': + v = { i: len(j) for i, j in v.items() } + if large and k == 'sd_merge_models': + scrub_dict(v, ['sd_merge_recipe']) + res[k] = v sd_metadata[filename] = res global sd_metadata_pending # pylint: disable=global-statement sd_metadata_pending += 1 diff --git a/modules/ui_symbols.py b/modules/ui_symbols.py index 86b9a5e64..68fc60aff 100644 --- a/modules/ui_symbols.py +++ b/modules/ui_symbols.py @@ -1,3 +1,14 @@ +refresh = '⟲' +close = '🗙' +load = '⇧' +save = '⇩' +apply = '⇰' +clear = '⊗' +fill = '⊜' +networks = '🗁' +paste = '⇦' + +""" refresh = '🔄' close = '🛗' load = '⬆️' @@ -6,9 +17,10 @@ apply = '⏩' clear = '🚮' fill = '⏫' networks = '🌐' +paste = '📘' +""" switch = '⇅' detect = '📐' folder = '📂' random = '🎲️' reuse = '♻️' -paste = '📘' From 250d1bf2fbb0901250aee3ac48b10a8126584401 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 13:05:31 -0400 Subject: [PATCH 25/64] update hints --- CHANGELOG.md | 1 + cli/validate-locale.py | 4 + html/locale_en.json | 156 ++++++++++++++++++++++---------------- html/locale_ko.json | 2 +- javascript/black-teal.css | 1 + javascript/setHints.js | 2 +- modules/devices.py | 7 +- modules/modelloader.py | 14 +++- modules/sd_models.py | 2 +- modules/shared.py | 23 +++--- modules/ui.py | 2 +- modules/ui_models.py | 4 +- 12 files changed, 130 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f076ffac5..f69d8c7a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Mostly a service release - tons of fixes +- update ui hints - new option **inference mode** - default is standard `torch.no_grad` new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus diff --git a/cli/validate-locale.py b/cli/validate-locale.py index 5eb574b84..b5dd48ba7 100755 --- a/cli/validate-locale.py +++ b/cli/validate-locale.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import os import sys import json from rich import print # pylint: disable=redefined-builtin @@ -7,6 +8,9 @@ from rich import print # pylint: disable=redefined-builtin if __name__ == "__main__": sys.argv.pop(0) fn = sys.argv[0] if len(sys.argv) > 0 else 'locale_en.json' + if not os.path.isfile(fn): + print(f'File not found: {fn}') + sys.exit(1) with open(fn, 'r', encoding="utf-8") as f: data = json.load(f) keys = [] diff --git a/html/locale_en.json b/html/locale_en.json index 07aa68b55..99fa6b8bc 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -9,7 +9,17 @@ {"id":"","label":"⏫","localized":"","hint":"Fill"}, {"id":"","label":"🎲️","localized":"","hint":"Use random seed"}, {"id":"","label":"♻️","localized":"","hint":"Reuse previous seed"}, - {"id":"","label":"⇅","localized":"","hint":"Swap values"} + {"id":"","label":"⇅","localized":"","hint":"Swap values"}, + {"id":"","label":"⇦","localized":"","hint":"Read generation parameters from prompt or last generation if prompt is empty into user interface"}, + {"id":"","label":"⊗","localized":"","hint":"Clear prompt"}, + {"id":"","label":"🗁","localized":"","hint":"Show/hide extra networks"}, + {"id":"","label":"⇰","localized":"","hint":"Apply selected styles to current prompt"}, + {"id":"","label":"⇩","localized":"","hint":"Save current prompt as style template"}, + {"id":"","label":"⟲","localized":"","hint":"Refresh"}, + {"id":"","label":"🗙","localized":"","hint":"Close"}, + {"id":"","label":"⊜","localized":"","hint":"Fill"}, + {"id":"","label":"📐","localized":"","hint":"Measure"}, + {"id":"","label":"🔍","localized":"","hint":"Search"} ], "prompts": [ {"id":"","label":"Prompt","localized":"","hint":"Type what you want to see in the image"}, @@ -31,11 +41,13 @@ {"id":"","label":"Train","localized":"","hint":"Run training or model merging"}, {"id":"","label":"Models","localized":"","hint":"Convert or merge your models"}, {"id":"","label":"Interrogator","localized":"","hint":"Run interrogate to get description of your image"}, - {"id":"","label":"System Info","localized":"","hint":"System information and benchmarking"}, + {"id":"","label":"System Info","localized":"","hint":"System information"}, {"id":"","label":"Agent Scheduler","localized":"","hint":"Enqueue your generate requests and run them in the background"}, {"id":"","label":"Image Browser","localized":"","hint":"Browse through your generated image database"}, + {"id":"","label":"System","localized":"","hint":"System settings and information"}, {"id":"","label":"Settings","localized":"","hint":"Application settings"}, - {"id":"","label":"Extensions","localized":"","hint":"Application extensions"} + {"id":"","label":"Extensions","localized":"","hint":"Application extensions"}, + {"id":"","label":"Script","localized":"","hint":"Addtional scripts to be used"} ], "action panel": [ {"id":"","label":"Generate","localized":"","hint":"Start processing"}, @@ -48,8 +60,10 @@ {"id":"","label":"Interrogate\nDeepBooru","localized":"","hint":"Run interrogate using DeepBooru model"} ], "extra networks": [ - {"id":"","label":"Extra networks tab order","localized":"","hint":"Comma-separated list of tab names; tabs listed here will appear in the extra networks UI first and in order listed"}, - {"id":"","label":"UI position","localized":"","hint":""}, + {"id":"","label":"UI position","localized":"","hint":"Location of extra networks"}, + {"id":"","label":"cover","localized":"","hint":"cover full area"}, + {"id":"","label":"inline","localized":"","hint":"inline with all additional elelemtns (scrollable)"}, + {"id":"","label":"sidebar","localized":"","hint":"sidebar on the right side of the screen"}, {"id":"","label":"UI height (%)","localized":"","hint":""}, {"id":"","label":"UI sidebar width (%)","localized":"","hint":""}, {"id":"","label":"UI card preview lazy loading","localized":"","hint":""}, @@ -58,20 +72,19 @@ {"id":"","label":"UI image contain method","localized":"","hint":""}, {"id":"","label":"Do not automatically build extra network pages","localized":"","hint":""}, {"id":"","label":"Use LyCoris handler for all Lora types","localized":"","hint":""}, - {"id":"","label":"Disable built-in Lora handler","localized":"","hint":""}, - {"id":"","label":"Use Kohya method for handling multiple Loras","localized":"","hint":""}, + {"id":"","label":"Use Kohya method for handling multiple LoRA","localized":"","hint":""}, {"id":"","label":"Multiplier for extra networks","localized":"","hint":"When adding extra network such as Hypernetwork or Lora to prompt, use this multiplier for it"}, {"id":"","label":"Add hypernetwork to prompt","localized":"","hint":""}, {"id":"","label":"Add Lora to prompt","localized":"","hint":""}, {"id":"","label":"shuffle tags by ',' when creating prompts.","localized":"","hint":""}, - {"id":"","label":"extra text to add before <...> when adding extra network to prompt","localized":"","hint":""}, {"id":"","label":"When adding to prompt, refer to Lora by","localized":"","hint":""}, {"id":"","label":"add lora hashes to infotext","localized":"","hint":""}, - {"id":"","label":"Checkpoints","localized":"","hint":""}, - {"id":"","label":"Lora","localized":"","hint":""}, - {"id":"","label":"LyCORIS","localized":"","hint":""}, - {"id":"","label":"Textual Inversion","localized":"","hint":""}, - {"id":"","label":"Hypernetworks","localized":"","hint":""}, + {"id":"","label":"Checkpoints","localized":"","hint":"Trained model checkpoints"}, + {"id":"","label":"Styles","localized":"","hint":"Additional styles to be applied on selected generation paramters"}, + {"id":"","label":"Lora","localized":"","hint":"LoRA: Low-Rank Adaptation. Fine-tuned model that is applied on top of a loaded model"}, + {"id":"","label":"LyCORIS","localized":"","hint":"LyCORIS: Lora beYond Conventional methods. Fine-tuned model that is applied on top of a loaded model"}, + {"id":"","label":"Textual Inversion","localized":"","hint":"Textual inversion embedding is a trained embedded information about the subject"}, + {"id":"","label":"Hypernetworks","localized":"","hint":"Small trained neural network that modifies behavior of the loaded model"}, {"id":"","label":"Save preview","localized":"","hint":"Save current image as extra network preview"}, {"id":"","label":"Save description","localized":"","hint":"Save current text as extra network description"}, {"id":"","label":"Read description","localized":"","hint":"Read stored extra network description"} @@ -102,17 +115,26 @@ {"id":"","label":"Apply changes & restart server","localized":"","hint":"Apply all changes and restart server"}, {"id":"","label":"install","localized":"","hint":"install this extension"}, {"id":"","label":"uninstall","localized":"","hint":"uninstall this extension"}, - {"id":"","label":"User interface defaults","localized":"","hint":"Review and set current values as default values for the user interface"}, + {"id":"","label":"UI Config","localized":"","hint":"Review and set current values as default values for the user interface"}, {"id":"","label":"View changes","localized":"","hint":"Review changes between default user interface values and current values"}, {"id":"","label":"Set new defaults","localized":"","hint":"Set current values as default values for the user interface"}, + {"id":"","label":"Benchmark","localized":"","hint":"Run benchmarks"}, + {"id":"","label":"Models & Networks","localized":"","hint":"View lists of all available models and networks"}, {"id":"","label":"Restore system defaults","localized":"","hint":"Restore default user interface values"} ], "txt2img tab": [ + {"id":"","label":"Batch","localized":"","hint":"Additional batching options"}, + {"id":"","label":"Seed details","localized":"","hint":"Additional options regarding initial seed used to produce images"}, + {"id":"","label":"Advanced","localized":"","hint":"Additional advanced options"}, {"id":"","label":"Sampling method","localized":"","hint":"Which algorithm to use to produce the image"}, {"id":"","label":"Sampling steps","localized":"","hint":"How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results"}, {"id":"","label":"Restore faces","localized":"","hint":"Use a pre-trained model to correct the generated faces. See GFPGAN or Codeformer."}, {"id":"","label":"Tiling","localized":"","hint":"Produce an image that can be tiled"}, - {"id":"","label":"Hires fix","localized":"","hint":"Use a similar process as image to image to upscale and add detail to the final image."}, + {"id":"","label":"full quality","localized":"","hint":"Use full quality VAE to decode latent samples"}, + {"id":"","label":"face restore","localized":"","hint":"Run processed image through additional face restoration model"}, + {"id":"","label":"denoise","localized":"","hint":"Denoising details for img2img"}, + {"id":"","label":"remove background","localized":"","hint":"Run processed image through additional background removal model"}, + {"id":"","label":"Second pass","localized":"","hint":"Use a similar process as image to image to upscale and/or add detail to the final image. Optionally uses refiner model to enhance image details."}, {"id":"","label":"Denoising strength","localized":"","hint":"Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies"}, {"id":"","label":"Denoise start","localized":"","hint":"Override denoise strength by stating how early base model should finish and when refiner should start. Only applicable to refiner usage. If set to 0 or 1, denoising strength will be used"}, {"id":"","label":"Hires steps","localized":"","hint":"Number of sampling steps for upscaled picture. If 0, uses same as for original"}, @@ -121,7 +143,8 @@ {"id":"","label":"Resize width to","localized":"","hint":"Resizes image to this width. If 0, width is inferred from either of two nearby sliders"}, {"id":"","label":"Resize height to","localized":"","hint":"Resizes image to this height. If 0, height is inferred from either of two nearby sliders"}, {"id":"","label":"Secondary sampler","localized":"","hint":"Use specific sampler as fallback sampler if primary is not supported for specific operation"}, - {"id":"","label":"Secondary steps","localized":"","hint":"Number of steps to use for second pass"}, + {"id":"","label":"Refiner start","localized":"","hint":"Refiner pass will start when base model is this much complete (set to 0 or 1 to run after full base model run)"}, + {"id":"","label":"Refiner steps","localized":"","hint":"Number of steps to use for refiner pass"}, {"id":"","label":"Secondary CFG Scale","localized":"","hint":"CFG scale used for refiner pass"}, {"id":"","label":"Guidance rescale","localized":"","hint":"Rescale CFG generated noise to avoid overexposed images"}, {"id":"","label":"Secondary Prompt","localized":"","hint":"Prompt used for both second encoder in base model (if it exists) and for refiner pass (if enabled)"}, @@ -130,7 +153,7 @@ {"id":"","label":"Height","localized":"","hint":"Image height"}, {"id":"","label":"Batch count","localized":"","hint":"How many batches of images to create (has no impact on generation performance or VRAM usage)"}, {"id":"","label":"Batch size","localized":"","hint":"How many image to create in a single batch (increases generation performance at cost of higher VRAM usage)"}, - {"id":"","label":"CFG Scale","localized":"","hint":"Classifier Free Guidance scale: how strongly the image should conform to prompt. Lower values produce more creative results, higher values make it follow the prompt more strictly; recommended values between 5-10"}, + {"id":"","label":"cfg scale","localized":"","hint":"Classifier Free Guidance scale: how strongly the image should conform to prompt. Lower values produce more creative results, higher values make it follow the prompt more strictly; recommended values between 5-10"}, {"id":"","label":"CLIP skip","localized":"","hint":"Clip skip is a feature that allows users to control the level of specificity of the prompt, the higher the CLIP skip value, the less deep the prompt will be interpreted. CLIP Skip 1 is typical while some anime models produce better results at CLIP skip 2"}, {"id":"","label":"Seed","localized":"","hint":"A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result"}, {"id":"","label":"Extra","localized":"","hint":"Show additional options"}, @@ -149,7 +172,7 @@ {"id":"","label":"Input directory","localized":"","hint":"Folder where the images are that you want to process"}, {"id":"","label":"Output directory","localized":"","hint":"Folder where the processed images should be saved to"}, {"id":"","label":"Show result images","localized":"","hint":"Enable to show the processed images in the image pane"}, - {"id":"","label":"Resize","localized":"","hint":"Factor for resizing 1x mean no upscale, 4x means 4 times upscale, high values might lead to memory issues on small graphics cards"}, + {"id":"","label":"Resize","localized":"","hint":"Resizing details. Higher resolutions require additional processing memory."}, {"id":"","label":"Crop to fit","localized":"","hint":"If the dimensions of your source image (e.g. 512x510) deviate from your target dimensions (e.g. 1024x768) this function will fit your upscaled image into your target size image. Excess will be cropped"}, {"id":"","label":"Secondary Upscaler","localized":"","hint":"Select secondary upscaler to run after initial upscaler"}, {"id":"","label":"Upscaler 2 visibility","localized":"","hint":"Strength of the secondary upscaler"}, @@ -167,24 +190,22 @@ {"id":"sett_reload_sd_model","label":"Reload checkpoint","localized":"","hint":"Reload currently selected model checkpoint"} ], "settings sections": [ - {"id":"","label":"Stable Diffusion","localized":"","hint":""}, + {"id":"","label":"execution & models","localized":"","hint":""}, {"id":"","label":"Optimizations","localized":"","hint":""}, {"id":"","label":"Compute Settings","localized":"","hint":""}, {"id":"","label":"Diffusers Settings","localized":"","hint":""}, {"id":"","label":"System Paths","localized":"","hint":""}, {"id":"","label":"Image Options","localized":"","hint":""}, - {"id":"","label":"Image Processing","localized":"","hint":""}, - {"id":"","label":"Image Paths","localized":"","hint":""}, + {"id":"","label":"image naming & paths","localized":"","hint":""}, {"id":"","label":"User Interface","localized":"","hint":""}, {"id":"","label":"Live Previews","localized":"","hint":""}, {"id":"","label":"Sampler Settings","localized":"","hint":""}, {"id":"","label":"Postprocessing","localized":"","hint":""}, {"id":"","label":"Training","localized":"","hint":""}, {"id":"","label":"Interrogate","localized":"","hint":""}, - {"id":"","label":"Upscaling","localized":"","hint":""}, {"id":"","label":"Extra Networks","localized":"","hint":""}, - {"id":"","label":"Licenses","localized":"","hint":""}, - {"id":"","label":"Show all pages","localized":"","hint":""}, + {"id":"","label":"Licenses","localized":"","hint":"View licenses of all additional included libraries"}, + {"id":"","label":"Show all pages","localized":"","hint":"Show all settings pages"}, {"id":"","label":"Request browser notifications","localized":"","hint":""} ], "img2img tabs": [ @@ -192,8 +213,7 @@ {"id":"","label":"Sketch","localized":"","hint":""}, {"id":"","label":"Inpaint","localized":"","hint":""}, {"id":"","label":"Inpaint sketch","localized":"","hint":""}, - {"id":"","label":"Inpaint upload","localized":"","hint":""}, - {"id":"","label":"Batch","localized":"","hint":""} + {"id":"","label":"Inpaint upload","localized":"","hint":""} ], "img2img tab": [ {"id":"","label":"Inpaint batch input directory","localized":"","hint":""}, @@ -207,7 +227,7 @@ {"id":"","label":"Mask transparency","localized":"","hint":""}, {"id":"","label":"Inpaint masked","localized":"","hint":""}, {"id":"","label":"Inpaint not masked","localized":"","hint":""}, - {"id":"","label":"fill","localized":"","hint":"fill it with colors of the image"}, + {"id":"","label":"fill","localized":"","hint":"fill"}, {"id":"","label":"original","localized":"","hint":"keep whatever was there originally"}, {"id":"","label":"latent noise","localized":"","hint":"fill it with latent space noise"}, {"id":"","label":"latent nothing","localized":"","hint":"fill it with latent space zeroes"}, @@ -318,20 +338,20 @@ {"id":"","label":"Original model","localized":"","hint":""} ], "settings": [ - {"id":"","label":"Stable Diffusion checkpoint","localized":"","hint":""}, - {"id":"","label":"Stable Diffusion refiner","localized":"","hint":""}, - {"id":"","label":"Stable Diffusion checkpoint autoload on server start","localized":"","hint":""}, - {"id":"","label":"stable diffusion checkpoint dict","localized":"","hint":""}, - {"id":"","label":"disallow usage of checkpoints in ckpt format","localized":"","hint":""}, + {"id":"","label":"base model","localized":"","hint":"Main model used for all operations"}, + {"id":"","label":"refiner model","localized":"","hint":"Refiner model used for second-pass operations"}, + {"id":"","label":"model autoload on server start","localized":"","hint":""}, + {"id":"","label":"use baseline data from a different model","localized":"","hint":""}, + {"id":"","label":"Disallow usage of models in ckpt format","localized":"","hint":""}, {"id":"","label":"model compile fullgraph","localized":"","hint":""}, {"id":"","label":"create zip archive when downloading multiple images","localized":"","hint":""}, {"id":"","label":"samplers solver order where applicable","localized":"","hint":""}, - {"id":"","label":"samplers should use karras sigmas where applicable","localized":"","hint":""}, - {"id":"","label":"samplers should use use lower-order solvers in the final steps where applicable","localized":"","hint":""}, - {"id":"","label":"samplers should use dynamic thresholding where applicable","localized":"","hint":""}, - {"id":"","label":"Number of cached model checkpoints","localized":"","hint":"The amount of models to store in RAM for quick access"}, - {"id":"","label":"Number of cached VAE checkpoints","localized":"","hint":"The amount of VAE files to store in RAM for quick access"}, - {"id":"","label":"Select VAE","localized":"","hint":"VAE helps with fine details in the final image and may also alter colors"}, + {"id":"","label":"samplers use karras sigmas where applicable","localized":"","hint":""}, + {"id":"","label":"samplers use simplified solvers in final steps where applicable","localized":"","hint":""}, + {"id":"","label":"samplers use dynamic thresholding where applicable","localized":"","hint":""}, + {"id":"","label":"Number of cached models","localized":"","hint":"The amount of models to store in RAM for quick access"}, + {"id":"","label":"Number of cached VAEs","localized":"","hint":"The amount of VAE files to store in RAM for quick access"}, + {"id":"","label":"VAE model","localized":"","hint":"VAE helps with fine details in the final image and may also alter colors"}, {"id":"","label":"Enable splitting of hires batch processing","localized":"","hint":"Reduces VRAM usage when using hires fix on batches of images"}, {"id":"","label":"Load models using stream loading method","localized":"","hint":"When loading models attempt stream loading optimized for slow or network storage"}, {"id":"","label":"When loading models attempt to reuse previous model dictionary","localized":"","hint":""}, @@ -355,14 +375,13 @@ {"id":"","label":"Disable conditional batching enabled on low memory systems","localized":"","hint":""}, {"id":"","label":"Enable samplers quantization for sharper and cleaner results","localized":"","hint":""}, {"id":"","label":"Prompt padding for long prompts","localized":"","hint":"Increase coherency by padding from the last comma within n tokens when using more than 75 tokens"}, - {"id":"","label":"Original","localized":"","hint":""}, - {"id":"","label":"Diffusers","localized":"","hint":""}, - {"id":"","label":"VRAM usage polls per second during generation","localized":"","hint":""}, - {"id":"","label":"Autocast","localized":"","hint":""}, - {"id":"","label":"Full","localized":"","hint":""}, - {"id":"","label":"FP32","localized":"","hint":""}, - {"id":"","label":"FP16","localized":"","hint":""}, - {"id":"","label":"BF16","localized":"","hint":""}, + {"id":"","label":"Original","localized":"","hint":"Original LDM backend"}, + {"id":"","label":"Diffusers","localized":"","hint":"Diffusers backend"}, + {"id":"","label":"Autocast","localized":"","hint":"Automatically determine precision during runtime"}, + {"id":"","label":"Full","localized":"","hint":"Always use full precision"}, + {"id":"","label":"FP32","localized":"","hint":"Use 32-bit floating point precision for calculations"}, + {"id":"","label":"FP16","localized":"","hint":"Use 16-bit floating point precision for calculations"}, + {"id":"","label":"BF16","localized":"","hint":"Use modified 16-bit floating point precision for calculations"}, {"id":"","label":"Use full precision for model (--no-half)","localized":"","hint":"Uses FP32 for the model. May produce better results while using more VRAM and slower generation"}, {"id":"","label":"Use full precision for VAE (--no-half-vae)","localized":"","hint":"Uses FP32 for the VAE. May produce better results while using more VRAM and slower generation"}, {"id":"","label":"Enable upcast sampling","localized":"","hint":"Usually produces similar results to --no-half with better performance while using less memory"}, @@ -371,9 +390,7 @@ {"id":"","label":"Attempt VAE roll back when produced NaN values (experimental)","localized":"","hint":"Requires Torch 2.1 and NaN check enabled"}, {"id":"","label":"Use channels last as torch memory format","localized":"","hint":""}, {"id":"","label":"Enable full-depth cuDNN benchmark feature","localized":"","hint":""}, - {"id":"","label":"Allow TF32 math ops","localized":"","hint":""}, - {"id":"","label":"Allow TF16 reduced precision math ops","localized":"","hint":""}, - {"id":"","label":"Enable model compile (experimental)","localized":"","hint":""}, + {"id":"","label":"Enable model compile","localized":"","hint":""}, {"id":"","label":"inductor","localized":"","hint":""}, {"id":"","label":"cudagraphs","localized":"","hint":""}, {"id":"","label":"aot_ts_nvfuser","localized":"","hint":""}, @@ -381,7 +398,6 @@ {"id":"","label":"ipex","localized":"","hint":""}, {"id":"","label":"Model compile verbose mode","localized":"","hint":""}, {"id":"","label":"Model compile suppress errors","localized":"","hint":""}, - {"id":"","label":"Disable Torch memory garbage collection","localized":"","hint":"Disable Torch memory garbage collection on each generation. CG will still run before & after model load as well when low GPU memory threshold is reached."}, {"id":"","label":"Directory for temporary images; leave empty for default","localized":"","hint":""}, {"id":"","label":"Enable IPEX Optimize for Intel GPUs","localized":"","hint":""}, {"id":"","label":"Cleanup non-default temporary directory when starting webui","localized":"","hint":""}, @@ -410,8 +426,6 @@ {"id":"","label":"Always save all generated image grids","localized":"","hint":""}, {"id":"","label":"File format for grids","localized":"","hint":""}, {"id":"","label":"Add extended info (seed, prompt) to filename when saving grid","localized":"","hint":""}, - {"id":"","label":"Do not save grids consisting of one picture","localized":"","hint":""}, - {"id":"","label":"Prevent empty spots in grid (when set to autodetect)","localized":"","hint":""}, {"id":"","label":"Grid row count","localized":"","hint":"Use -1 for autodetect and 0 for it to be same as batch size"}, {"id":"","label":"Create text file next to every image with generation parameters","localized":"","hint":""}, {"id":"","label":"Create JSON log file for each saved image","localized":"","hint":"Save image information to a JSON file"}, @@ -420,7 +434,7 @@ {"id":"","label":"Save copy of image before applying color correction","localized":"","hint":""}, {"id":"","label":"Save copy of the inpainting greyscale mask","localized":"","hint":""}, {"id":"","label":"Save copy of inpainting masked composite","localized":"","hint":""}, - {"id":"","label":"Save copy of processing init images","localized":"","hint":""}, + {"id":"","label":"Save copy of img2img init images","localized":"","hint":""}, {"id":"","label":"Quality for saved jpeg images","localized":"","hint":""}, {"id":"","label":"Use lossless compression for webp images","localized":"","hint":""}, {"id":"","label":"Maximum allowed image size in megapixels","localized":"","hint":""}, @@ -462,10 +476,7 @@ {"id":"","label":"Ctrl+up/down precision when editing ","localized":"","hint":""}, {"id":"","label":"Ctrl+up/down word delimiters","localized":"","hint":""}, {"id":"","label":"Quicksettings list","localized":"","hint":"List of setting names, separated by commas, for settings that should go to the quick access bar at the top instead the setting tab"}, - {"id":"","label":"Hidden UI tabs","localized":"","hint":""}, - {"id":"","label":"UI tabs order","localized":"","hint":""}, {"id":"","label":"UI scripts order","localized":"","hint":""}, - {"id":"","label":"txt2img/img2img UI item order","localized":"","hint":""}, {"id":"","label":"Show progressbar","localized":"","hint":""}, {"id":"","label":"Show live previews of the created image","localized":"","hint":""}, {"id":"","label":"Show previews of all images generated in a batch as a grid","localized":"","hint":""}, @@ -477,7 +488,7 @@ {"id":"","label":"Approximate simple","localized":"","hint":"Very cheap approximation. Very fast compared to VAE, but produces pictures with 8 times smaller horizontal/vertical resolution and extremely low quality"}, {"id":"","label":"TAESD","localized":"","hint":""}, {"id":"","label":"Combined","localized":"","hint":""}, - {"id":"","label":"Progressbar/preview update period, in milliseconds","localized":"","hint":""}, + {"id":"","label":"Progress update period","localized":"","hint":"Update period for UI progress bar and preview checks, in miliseconds"}, {"id":"","label":"Euler a","localized":"","hint":"Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps higher than 30-40 does not help"}, {"id":"","label":"Euler","localized":"","hint":""}, {"id":"","label":"LMS","localized":"","hint":""}, @@ -499,7 +510,6 @@ {"id":"","label":"DPM++ 2M SDE Karras","localized":"","hint":""}, {"id":"","label":"DDIM","localized":"","hint":"Denoising Diffusion Implicit Models - best at inpainting"}, {"id":"","label":"UniPC","localized":"","hint":"Unified Predictor-Corrector Framework for Fast Sampling of Diffusion Models"}, - {"id":"","label":"Force latent upscaler sampler","localized":"","hint":"Force specific sampler for second pass operations"}, {"id":"","label":"Noise multiplier for ancestral samplers (eta)","localized":"","hint":""}, {"id":"","label":"Noise multiplier for DDIM (eta)","localized":"","hint":""}, {"id":"","label":"uniform","localized":"","hint":""}, @@ -553,7 +563,6 @@ {"id":"","label":"Tile overlap in pixels for ESRGAN upscalers","localized":"","hint":"Low values = visible seam"}, {"id":"","label":"Tile size for SCUNET upscalers","localized":"","hint":"0 = no tiling"}, {"id":"","label":"Tile overlap for SCUNET upscalers","localized":"","hint":" Low values = visible seam"}, - {"id":"","label":"Hires fix uses width & height to set final resolution","localized":"","hint":"Hires fix uses width & height to set final resolution rather than first pass"}, {"id":"","label":"Do not fix prompt schedule for second order samplers","localized":"","hint":""}, {"id":"","label":"CodeFormer","localized":"","hint":""}, {"id":"","label":"GFPGAN","localized":"","hint":"Restore low quality faces using GFPGAN neural network"}, @@ -562,30 +571,45 @@ {"id":"","label":"Token merging ratio","localized":"","hint":"Enable redundant token merging via tomesd for speed and memory improvements, 0=disabled"}, {"id":"","label":"Token merging ratio for img2img","localized":"","hint":"Enable redundant token merging for img2img via tomesd for speed and memory improvements, 0=disabled"}, {"id":"","label":"Token merging ratio for hires pass","localized":"","hint":"Enable redundant token merging for hires pass via tomesd for speed and memory improvements, 0=disabled"}, - {"id":"","label":"Select diffuser pipeline when loading from safetensors","localized":"","hint":""}, + {"id":"","label":"Diffusers pipeline","localized":"","hint":"If autodetect does not detect model automatically, select model type before loading a model"}, {"id":"","label":"Move base model to CPU when using refiner","localized":"","hint":""}, + {"id":"","label":"Move base model to CPU when using VAE","localized":"","hint":""}, {"id":"","label":"Move refiner model to CPU when not in use","localized":"","hint":""}, - {"id":"","label":"Move UNet to CPU while VAE decoding","localized":"","hint":""}, {"id":"","label":"Use model EMA weights when possible","localized":"","hint":""}, {"id":"","label":"Generator device","localized":"","hint":""}, - {"id":"","label":"Enable sequential CPU offload","localized":"","hint":"Reduces GPU memory usage by transferring weights to the CPU. Increases inference time approximately 10%"}, - {"id":"","label":"Enable model CPU offload","localized":"","hint":"Transferring of entire models to the CPU, negligible impact on inference time while still providing some memory savings"}, + {"id":"","label":"Enable sequential CPU offload (--lowvram)","localized":"","hint":"Reduces GPU memory usage by transferring weights to the CPU. Increases inference time approximately 10%"}, + {"id":"","label":"Enable model CPU offload (--medvram)","localized":"","hint":"Transferring of entire models to the CPU, negligible impact on inference time while still providing some memory savings"}, {"id":"","label":"Enable VAE slicing","localized":"","hint":"Decodes batch latents one image at a time with limited VRAM. Small performance boost in VAE decode on multi-image batches"}, {"id":"","label":"Enable VAE tiling","localized":"","hint":"Divide large images into overlapping tiles with limited VRAM. Results in a minor increase in processing time"}, {"id":"","label":"Enable attention slicing","localized":"","hint":"Performs attention computation in steps instead of all at once. Slower inference times, but greatly reduced memory usage"}, {"id":"","label":"Diffusers model loading variant","localized":"","hint":""}, {"id":"","label":"Diffusers VAE loading variant","localized":"","hint":""}, - {"id":"","label":"Diffusers LoRA loading variant","localized":"","hint":"'sequential apply' loads and applies each LoRA in order of appearance, 'merge and apply' loads all LoRAs and merges them in-memory before applying to model, 'diffusers default' uses single LoRA loading method"} + {"id":"","label":"Diffusers LoRA loading variant","localized":"","hint":"'sequential apply' loads and applies each LoRA in order of appearance, 'merge and apply' loads all LoRAs and merges them in-memory before applying to model, 'diffusers default' uses single LoRA loading method"}, + {"id":"","label":"Torch inference mode","localized":"","hint":"Use torch inference mode"}, + {"id":"","label":"inference-mode","localized":"","hint":"Use torch.inference_mode"}, + {"id":"","label":"no-grad","localized":"","hint":"Use torch.no_grad"}, + {"id":"","label":"vae slicing (original)","localized":"","hint":"Run VAE on sliced samples to reduce memory requirements when processing high resolution images"}, + {"id":"","label":"use fixed unet precision","localized":"","hint":""}, + {"id":"","label":"enable model compile","localized":"","hint":"Enable usage of torch.compile"}, + {"id":"","label":"reduce-overhead","localized":"","hint":""}, + {"id":"","label":"max-autotune","localized":"","hint":""}, + {"id":"","label":"model compile precompile","localized":"","hint":"Run model compile immediately on model load instead of first use"}, + {"id":"","label":"sequential apply","localized":"","hint":"When loading multiple LoRAs, apply each in order of loading"}, + {"id":"","label":"merge and apply","localized":"","hint":"When loading multiple LoRAs, load all and merge them before applying to model"}, + {"id":"","label":"force zeros for prompts when empty","localized":"","hint":"Force full zero tensor when prompt is empty to remove any residual noise"}, + {"id":"","label":"require aesthetics score","localized":"","hint":"Automatically guide model towards higher-pleasing results, applicable only to refiner model"}, + {"id":"","label":"include watermark in saved images","localized":"","hint":"Add invisible watermark to image by altering some pixel values"}, + {"id":"","label":"image watermark string","localized":"","hint":"Watermark string to add to image. Keep very short to avoid image corruption."}, + {"id":"","label":"show log view","localized":"","hint":"Show log view at the bottom of the main window"}, + {"id":"","label":"Log view update period","localized":"","hint":"Log view update period, in miliseconds"} ], "scripts": [ - {"id":"","label":"Script","localized":"","hint":""}, {"id":"","label":"Swap X/Y","localized":"","hint":""}, {"id":"","label":"Swap Y/Z","localized":"","hint":""}, {"id":"","label":"Swap X/Z","localized":"","hint":""}, {"id":"","label":"Resize to","localized":"","hint":""}, {"id":"","label":"Resize by","localized":"","hint":""}, {"id":"","label":"Use via API","localized":"","hint":""}, - {"id":"","label":"Styles","localized":"","hint":""}, {"id":"","label":"Put variable parts at start of prompt","localized":"","hint":""}, {"id":"","label":"Use different seed for each picture","localized":"","hint":""}, {"id":"","label":"positive","localized":"","hint":""}, diff --git a/html/locale_ko.json b/html/locale_ko.json index ca3653222..c36545816 100644 --- a/html/locale_ko.json +++ b/html/locale_ko.json @@ -518,7 +518,7 @@ {"id":"","label":"logSNR","localized":"","hint":""}, {"id":"","label":"UniPC order (must be < sampling steps)","localized":"","hint":""}, {"id":"","label":"UniPC lower order final","localized":"","hint":""}, - {"id":"","label":"Enable addtional postprocessing operations","localized":"추가 후처리 작업","hint":""}, + {"id":"","label":"Enable additional postprocessing operations","localized":"추가 후처리 작업","hint":""}, {"id":"","label":"Postprocessing operation order","localized":"후처리 작업 순서","hint":""}, {"id":"","label":"Maximum number of images in upscaling cache","localized":"","hint":""}, {"id":"","label":"Move VAE and CLIP to RAM when training if possible","localized":"가능하다면 학습 시 VAE와 CLIP 모델을 램으로 이동","hint":""}, diff --git a/javascript/black-teal.css b/javascript/black-teal.css index c82ecd8cd..1f76b8476 100644 --- a/javascript/black-teal.css +++ b/javascript/black-teal.css @@ -93,6 +93,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } .extra-networks { border-left: 2px solid var(--highlight-color) !important; padding-left: 4px; } .image-buttons { gap: 10px !important; justify-content: center; } .image-buttons > button { max-width: 160px; } +.tooltip { background: var(--primary-300); color: black; border: none; border-radius: var(--radius-lg) } #system_row > button, #settings_row > button, #config_row > button { max-width: 190px; } /* gradio elements overrides */ diff --git a/javascript/setHints.js b/javascript/setHints.js index da98175ca..f755e6dee 100644 --- a/javascript/setHints.js +++ b/javascript/setHints.js @@ -87,7 +87,7 @@ async function setHints() { log('setHints', { type: localeData.type, elements: elements.length, localized, hints, data: localeData.data.length, time: t1 - t0 }); // sortUIElements(); removeSplash(); - // validateHints(elements, localeData.data) + // validateHints(elements, localeData.data); } onAfterUiUpdate(async () => { diff --git a/modules/devices.py b/modules/devices.py index cf58d5fe0..ed3951cf9 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -205,7 +205,12 @@ def set_cuda_params(): shared.log.info('Torch override VAE dtype: no-half set') dtype_vae = torch.float32 unet_needs_upcast = shared.opts.upcast_sampling - inference_context = torch.inference_mode if shared.opts.inference_mode == 'inference-mode' else torch.no_grad + if shared.opts.inference_mode == 'inference-mode': + inference_context = torch.inference_mode + elif shared.opts.inference_mode == 'no-grad': + inference_context = torch.no_grad + else: + inference_context = contextlib.nullcontext shared.log.debug(f'Desired Torch parameters: dtype={shared.opts.cuda_dtype} no-half={shared.opts.no_half} no-half-vae={shared.opts.no_half_vae} upscast={shared.opts.upcast_sampling}') shared.log.info(f'Setting Torch parameters: dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__} fp16={fp16_ok} bf16={bf16_ok}') shared.log.debug(f'Torch default device: {torch.device(get_optimal_device_name())}') diff --git a/modules/modelloader.py b/modules/modelloader.py index afd2bcac3..27371dd6e 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -316,6 +316,19 @@ def extension_filter(ext_filter=None, ext_blacklist=None): return (not ext_filter or any(fp.upper().endswith(ew) for ew in ext_filter)) and (not ext_blacklist or not any(fp.upper().endswith(ew) for ew in ext_blacklist)) return filter +def load_file_from_url(url: str, *, model_dir: str, progress: bool = True, file_name: str | None = None) -> str: + """Download a file from url into model_dir, using the file present if possible. Returns the path to the downloaded file.""" + os.makedirs(model_dir, exist_ok=True) + if not file_name: + parts = urlparse(url) + file_name = os.path.basename(parts.path) + cached_file = os.path.abspath(os.path.join(model_dir, file_name)) + if not os.path.exists(cached_file): + shared.log.info(f'Downloading: url="{url}" file={cached_file}') + from torch.hub import download_url_to_file + download_url_to_file(url, cached_file, progress=progress) + return cached_file + def load_models(model_path: str, model_url: str = None, command_path: str = None, ext_filter=None, download_name=None, ext_blacklist=None) -> list: """ @@ -404,7 +417,6 @@ def move_files(src_path: str, dest_path: str, ext_filter: str = None): pass - def load_upscalers(): # We can only do this 'magic' method to dynamically load upscalers if they are referenced, so we'll try to import any _model.py files before looking in __subclasses__ modules_dir = os.path.join(shared.script_path, "modules") diff --git a/modules/sd_models.py b/modules/sd_models.py index d4db1da88..45bce3d0d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -352,7 +352,7 @@ def read_metadata_from_safetensors(filename): v = 'data' if k == 'format' and v == 'pt': continue - large = True if len(v) > 4096 else False + large = True if len(v) > 2048 else False if large and k == 'ss_datasets': continue if large and k == 'workflow': diff --git a/modules/shared.py b/modules/shared.py index 6e42b3ff8..277ae33af 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -373,7 +373,7 @@ options_templates.update(options_section(('sd', "Execution & Models"), { "sd_checkpoint_cache": OptionInfo(0, "Number of cached models", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_vae_checkpoint_cache": OptionInfo(0, "Number of cached VAEs", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "sd_vae": OptionInfo("Automatic", "VAE model", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), - "sd_model_dict": OptionInfo('None', "Use dict from model", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_tiles()}, refresh=refresh_checkpoints), + "sd_model_dict": OptionInfo('None', "Use baseline data from a different model", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_tiles()}, refresh=refresh_checkpoints), "stream_load": OptionInfo(False, "Load models using stream loading method"), "model_reuse_dict": OptionInfo(False, "When loading models attempt to reuse previous model dictionary"), "prompt_attention": OptionInfo("Full parser", "Prompt attention parser", gr.Radio, lambda: {"choices": ["Full parser", "Compel parser", "A1111 parser", "Fixed attention"] }), @@ -391,7 +391,7 @@ options_templates.update(options_section(('optimizations', "Optimizations"), { "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for hires pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}), - "inference_mode": OptionInfo("no-grad", "Torch inference mode", gr.Radio, lambda: {"choices": ["no-grad", "inference-mode"]}), + "inference_mode": OptionInfo("no-grad", "Torch inference mode", gr.Radio, lambda: {"choices": ["no-grad", "inference-mode", "none"]}), "sd_vae_sliced_encode": OptionInfo(False, "VAE Slicing (original)"), })) @@ -482,11 +482,9 @@ options_templates.update(options_section(('saving-images', "Image Options"), { "grid_save": OptionInfo(True, "Always save all generated image grids"), "grid_format": OptionInfo('jpg', 'File format for grids', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}), "n_rows": OptionInfo(-1, "Grid row count", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}), - # "grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"), - # "grid_prevent_empty_spots": OptionInfo(True, "Prevent empty spots in grid (when set to autodetect)"), "save_sep_options": OptionInfo("

Intermediate Image Saving

", "", gr.HTML), - "save_init_img": OptionInfo(True, "Save copy of img2img init images (helps track workflow)"), + "save_init_img": OptionInfo(True, "Save copy of img2img init images"), "save_images_before_highres_fix": OptionInfo(False, "Save copy of image before applying highres fix"), "save_images_before_refiner": OptionInfo(False, "Save copy of image before running refiner"), "save_images_before_face_restoration": OptionInfo(False, "Save copy of image before doing face restoration"), @@ -551,9 +549,9 @@ options_templates.update(options_section(('live-preview', "Live Previews"), { "show_progress_every_n_steps": OptionInfo(1, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), "show_progress_type": OptionInfo("Approximate NN", "Live preview method", gr.Radio, {"choices": ["Full VAE", "Approximate NN", "Approximate simple", "TAESD"]}), "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), - "live_preview_refresh_period": OptionInfo(500, "Progressbar/preview update period, in milliseconds", gr.Slider, {"minimum": 0, "maximum": 5000, "step": 25}), + "live_preview_refresh_period": OptionInfo(500, "Progress update period", gr.Slider, {"minimum": 0, "maximum": 5000, "step": 25}), "logmonitor_show": OptionInfo(True, "Show log view"), - "logmonitor_refresh_period": OptionInfo(5000, "Log view update period, in milliseconds", gr.Slider, {"minimum": 0, "maximum": 30000, "step": 25}), + "logmonitor_refresh_period": OptionInfo(5000, "Log view update period", gr.Slider, {"minimum": 0, "maximum": 30000, "step": 25}), })) options_templates.update(options_section(('sampler-params', "Sampler Settings"), { @@ -566,9 +564,9 @@ options_templates.update(options_section(('sampler-params', "Sampler Settings"), "schedulers_sep_diffusers": OptionInfo("

Diffusers specific config

", "", gr.HTML), "schedulers_prediction_type": OptionInfo("default", "Samplers override model prediction type", gr.Radio, lambda: {"choices": ['default', 'epsilon', 'sample', 'v-prediction']}), - "schedulers_use_karras": OptionInfo(True, "Samplers should use Karras sigmas where applicable"), - "schedulers_use_loworder": OptionInfo(True, "Samplers should use use lower-order solvers in the final steps where applicable"), - "schedulers_use_thresholding": OptionInfo(False, "Samplers should use dynamic thresholding where applicable"), + "schedulers_use_karras": OptionInfo(True, "Samplers use Karras sigmas where applicable"), + "schedulers_use_loworder": OptionInfo(True, "Samplers use simplified solvers in final steps where applicable"), + "schedulers_use_thresholding": OptionInfo(False, "Samplers use dynamic thresholding where applicable"), "schedulers_dpm_solver": OptionInfo("sde-dpmsolver++", "Samplers DPM solver algorithm", gr.Radio, lambda: {"choices": ['dpmsolver', 'dpmsolver++', 'sde-dpmsolver++']}), "schedulers_beta_schedule": OptionInfo("default", "Samplers override beta schedule", gr.Radio, lambda: {"choices": ['default', 'linear', 'scaled_linear', 'squaredcos_cap_v2']}), 'schedulers_beta_start': OptionInfo(0, "Samplers override beta start", gr.Number, {}), @@ -590,11 +588,8 @@ options_templates.update(options_section(('sampler-params', "Sampler Settings"), })) options_templates.update(options_section(('postprocessing', "Postprocessing"), { - 'postprocessing_enable_in_main_ui': OptionInfo([], "Enable addtional postprocessing operations", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), + 'postprocessing_enable_in_main_ui': OptionInfo([], "Enable additional postprocessing operations", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), 'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), - # "use_old_hires_fix_width_height": OptionInfo(False, "Hires fix uses width & height to set final resolution"), - # "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers"), - "postprocessing_sep_img2img": OptionInfo("

Img2Img & Inpainting

", "", gr.HTML), "img2img_color_correction": OptionInfo(False, "Apply color correction to match original colors"), "img2img_fix_steps": OptionInfo(False, "For image processing do exact number of steps as specified"), diff --git a/modules/ui.py b/modules/ui.py index ad3754416..d1b7a6b2a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -1153,7 +1153,7 @@ def create_ui(startup_timer = None): show_progress=info.refresh is not None, ) - button_set_checkpoint = gr.Button('Change checkpoint', elem_id='change_checkpoint', visible=False) + button_set_checkpoint = gr.Button('Change model', elem_id='change_checkpoint', visible=False) button_set_checkpoint.click( fn=lambda value, _: run_settings_single(value, key='sd_model_checkpoint'), _js="function(v){ var res = desiredCheckpointName; desiredCheckpointName = ''; return [res || v, null]; }", diff --git a/modules/ui_models.py b/modules/ui_models.py index 33cdf8b06..7998e13ac 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -213,7 +213,7 @@ def create_ui(): with gr.Column(scale=6): with gr.Row(): - hf_search_text = gr.Textbox('', label = 'Seach models', placeholder='search huggingface models') + hf_search_text = gr.Textbox('', label = 'Search models', placeholder='search huggingface models') hf_search_btn = ToolButton(value="🔍", label="Search") with gr.Row(): with gr.Column(scale=2): @@ -365,7 +365,7 @@ def create_ui(): civit_model_type = gr.Dropdown(label='Model type', choices=['SD 1.5', 'SD XL', 'LoRA', 'Other'], value='LoRA') with gr.Column(scale=15): with gr.Row(): - civit_search_text = gr.Textbox('', label = 'Seach models', placeholder='keyword') + civit_search_text = gr.Textbox('', label = 'Search models', placeholder='keyword') civit_search_tag = gr.Textbox('', label = '', placeholder='tags') civit_search_btn = ToolButton(value="🔍", label="Search", interactive=False) with gr.Row(): From d4b2a2cf3d30b9047e34e41898d361f5c62eb253 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 13:06:08 -0400 Subject: [PATCH 26/64] update wiki --- wiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki b/wiki index c069991c3..d22dc3427 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit c069991c32e4bdc5c7355062868550f8459f3df0 +Subproject commit d22dc342708db363e15063ab88e5e0e404aa22fd From 5a649f951aeaa314b5e2e6159667c718cb56531a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 15:19:39 -0400 Subject: [PATCH 27/64] skip invalid diffusers model --- modules/modelloader.py | 44 ++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/modules/modelloader.py b/modules/modelloader.py index 27371dd6e..051aa7372 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -154,7 +154,11 @@ def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config if token is not None and len(token) > 2: shared.log.debug(f"Diffusers authentication: {token}") hf.login(token) - pipeline_dir = DiffusionPipeline.download(hub_id, **download_config) + pipeline_dir = None + try: + pipeline_dir = DiffusionPipeline.download(hub_id, **download_config) + except Exception as e: + shared.log.error(f"Diffusers download error: {hub_id} {e}") try: model_info_dict = hf.model_info(hub_id).cardData # pylint: disable=no-member # TODO Diffusers is this real error? except Exception: @@ -193,22 +197,25 @@ def load_diffusers_models(model_path: str, command_path: str = None): output.append(str(r.repo_id)) """ for folder in os.listdir(place): - if "--" not in folder: - continue - _, name = folder.split("--", maxsplit=1) - name = name.replace("--", "/") - snapshots = os.listdir(os.path.join(place, folder, "snapshots")) - if len(snapshots) == 0: - shared.log.warning(f"Diffusers folder has no snapshots: location={place} folder={folder} name={name}") - continue - commit = snapshots[-1] - folder = os.path.join(place, folder, 'snapshots', commit) - mtime = os.path.getmtime(folder) - info = os.path.join(folder, "model_info.json") - diffuser_repos.append({ 'name': name, 'filename': name, 'path': folder, 'hash': commit, 'mtime': mtime, 'model_info': info }) - if os.path.exists(os.path.join(place, folder, 'snapshots', commit, "hidden")): - continue - output.append(name) + try: + if "--" not in folder: + continue + _, name = folder.split("--", maxsplit=1) + name = name.replace("--", "/") + snapshots = os.listdir(os.path.join(place, folder, "snapshots")) + if len(snapshots) == 0: + shared.log.warning(f"Diffusers folder has no snapshots: location={place} folder={folder} name={name}") + continue + commit = snapshots[-1] + folder = os.path.join(place, folder, 'snapshots', commit) + mtime = os.path.getmtime(folder) + info = os.path.join(folder, "model_info.json") + diffuser_repos.append({ 'name': name, 'filename': name, 'path': folder, 'hash': commit, 'mtime': mtime, 'model_info': info }) + if os.path.exists(os.path.join(place, folder, 'snapshots', commit, "hidden")): + continue + output.append(name) + except Exception as e: + shared.log.error(f"Error analyzing diffusers model: {place}/{folder} {e}") except Exception as e: shared.log.error(f"Error listing diffusers: {place} {e}") shared.log.debug(f'Scanning diffusers cache: {model_path} {command_path} items={len(output)} time={time.time()-t0:.2f}s') @@ -346,8 +353,7 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None output:list = [*filter(extension_filter(ext_filter, ext_blacklist), directory_files(*places))] if model_url is not None and len(output) == 0: if download_name is not None: - from basicsr.utils.download_util import load_file_from_url - dl = load_file_from_url(model_url, places[0], True, download_name) + dl = load_file_from_url(model_url, model_dir=places[0], progress=True, file_name=download_name) output.append(dl) else: output.append(model_url) From 56e041c3b6fac90a979e0b86d0f3382ee705cb59 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 18:13:20 -0400 Subject: [PATCH 28/64] improve civitai integration --- CHANGELOG.md | 11 +++++---- extensions-builtin/Lora/lora.py | 2 +- modules/hashes.py | 13 +++++++---- modules/modelloader.py | 28 +++++++++++----------- modules/ui_models.py | 41 ++++++++++++++++++++------------- 5 files changed, 57 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f69d8c7a3..e8bcd38c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,18 @@ Mostly a service release - tons of fixes -- update ui hints +- update **ui hints** +- updated **models -> civitai** + - search and download loras + - find previews for already downloaded models or loras - new option **inference mode** - default is standard `torch.no_grad` new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus - new cmdline param `--no-metadata` skips reading metadata from models that are not already cached -- updated gradio -- styles support for subfolders -- clean-up logging +- updated **gradio** +- **styles** support for subfolders +- clean-up **logging** - capture system info in startup log - better diagnostic output - capture extension output diff --git a/extensions-builtin/Lora/lora.py b/extensions-builtin/Lora/lora.py index 4705830b8..fbac7e8fc 100644 --- a/extensions-builtin/Lora/lora.py +++ b/extensions-builtin/Lora/lora.py @@ -95,7 +95,7 @@ class LoraOnDisk: def set_hash(self, v): self.hash = v - self.shorthash = self.hash[0:12] + self.shorthash = self.hash[0:10] if self.shorthash: available_lora_hash_lookup[self.shorthash] = self diff --git a/modules/hashes.py b/modules/hashes.py index ea8b91609..84071bfb5 100644 --- a/modules/hashes.py +++ b/modules/hashes.py @@ -21,12 +21,17 @@ def cache(subsection): return s -def calculate_sha256(filename): +def calculate_sha256(filename, quiet=False): hash_sha256 = hashlib.sha256() blksize = 1024 * 1024 - with progress.open(filename, 'rb', description=f'Calculating model hash: [cyan]{filename}', auto_refresh=True) as f: - for chunk in iter(lambda: f.read(blksize), b""): - hash_sha256.update(chunk) + if not quiet: + with progress.open(filename, 'rb', description=f'Calculating model hash: [cyan]{filename}', auto_refresh=True) as f: + for chunk in iter(lambda: f.read(blksize), b""): + hash_sha256.update(chunk) + else: + with open(filename, 'rb') as f: + for chunk in iter(lambda: f.read(blksize), b""): + hash_sha256.update(chunk) return hash_sha256.hexdigest() diff --git a/modules/modelloader.py b/modules/modelloader.py index 051aa7372..40136479d 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -4,6 +4,7 @@ import shutil import importlib from typing import Dict from urllib.parse import urlparse +import PIL.Image as Image from modules import shared from modules.upscaler import Upscaler, UpscalerLanczos, UpscalerNearest, UpscalerNone from modules.paths import script_path, models_path @@ -59,12 +60,13 @@ def download_civit_preview(model_path: str, preview_url: str): import rich.progress as p _, ext = os.path.splitext(preview_url) model_name, _ = os.path.splitext(os.path.basename(model_path)) - preview_file = os.path.splitext(model_path)[0] + ext + preview_file = f'{os.path.splitext(model_path)[0]}{ext}' if '.safetensors' in model_path.lower() else f'{model_path}{ext}' res = f'CivitAI download: name={model_name} url={preview_url}' req = requests.get(preview_url, stream=True, timeout=30) total_size = int(req.headers.get('content-length', 0)) block_size = 16384 # 16KB blocks written = 0 + img = None shared.state.begin('civitai-download-preview') try: with open(preview_file, 'wb') as f: @@ -77,13 +79,14 @@ def download_civit_preview(model_path: str, preview_url: str): if written < 1024: # min threshold os.remove(preview_file) raise ValueError(f'removed invalid download: bytes={written}') + img = Image.open(preview_file) except Exception as e: shared.log.error(f'CivitAI download error: name={model_name} url={preview_url} {e}') - if total_size == written: - shared.log.info(f'{res} size={total_size}') - else: - shared.log.error(f'{res} size={total_size} written={written}') shared.state.end() + if img is None: + return res + shared.log.info(f'{res} size={total_size} image={img.size}') + img.close() return res @@ -114,7 +117,7 @@ def download_civit_model(model_url: str, model_name: str, model_path: str, model written = written + len(data) f.write(data) progress.update(task, advance=block_size, description="Downloading") - if written < 1024 * 1024 * 1024: # min threshold + if written < 1024 * 1024: # min threshold os.remove(model_file) raise ValueError(f'removed invalid download: bytes={written}') if preview is not None: @@ -160,17 +163,16 @@ def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config except Exception as e: shared.log.error(f"Diffusers download error: {hub_id} {e}") try: - model_info_dict = hf.model_info(hub_id).cardData # pylint: disable=no-member # TODO Diffusers is this real error? + model_info_dict = hf.model_info(hub_id).cardData if pipeline_dir is not None else None # pylint: disable=no-member # TODO Diffusers is this real error? except Exception: model_info_dict = None - # some checkpoints need to be downloaded as "hidden" as they just serve as pre- or post-pipelines of other pipelines - if model_info_dict is not None and "prior" in model_info_dict: + if model_info_dict is not None and "prior" in model_info_dict: # some checkpoints need to be downloaded as "hidden" as they just serve as pre- or post-pipelines of other pipelines download_dir = DiffusionPipeline.download(model_info_dict["prior"][0], **download_config) model_info_dict["prior"] = download_dir - # mark prior as hidden - with open(os.path.join(download_dir, "hidden"), "w", encoding="utf-8") as f: + with open(os.path.join(download_dir, "hidden"), "w", encoding="utf-8") as f: # mark prior as hidden f.write("True") - shared.writefile(model_info_dict, os.path.join(pipeline_dir, "model_info.json")) + if pipeline_dir is not None: + shared.writefile(model_info_dict, os.path.join(pipeline_dir, "model_info.json")) shared.state.end() return pipeline_dir @@ -323,7 +325,7 @@ def extension_filter(ext_filter=None, ext_blacklist=None): return (not ext_filter or any(fp.upper().endswith(ew) for ew in ext_filter)) and (not ext_blacklist or not any(fp.upper().endswith(ew) for ew in ext_blacklist)) return filter -def load_file_from_url(url: str, *, model_dir: str, progress: bool = True, file_name: str | None = None) -> str: +def load_file_from_url(url: str, *, model_dir: str, progress: bool = True, file_name = None): """Download a file from url into model_dir, using the file present if possible. Returns the path to the downloaded file.""" os.makedirs(model_dir, exist_ok=True) if not file_name: diff --git a/modules/ui_models.py b/modules/ui_models.py index 7998e13ac..bb90ef822 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -8,6 +8,7 @@ from modules.ui_common import create_refresh_button from modules.call_queue import wrap_gradio_gpu_call from modules.shared import opts, log import modules.errors +import modules.hashes def create_ui(): @@ -201,15 +202,11 @@ def create_ui(): def hf_download_model(hub_id: str, token, variant, revision, mirror): from modules.modelloader import download_diffusers_model - try: - download_diffusers_model(hub_id, cache_dir=opts.diffusers_dir, token=token, variant=variant, revision=revision, mirror=mirror) - except Exception as e: - log.error(f"Diffuser model downloaded error: model={hub_id} {e}") - return f"Diffuser model downloaded error: model={hub_id} {e}" + download_diffusers_model(hub_id, cache_dir=opts.diffusers_dir, token=token, variant=variant, revision=revision, mirror=mirror) from modules.sd_models import list_models # pylint: disable=W0621 list_models() - log.info(f"Diffuser model downloaded: model={hub_id}") - return f'Diffuser model downloaded: model={hub_id}' + log.info(f'Diffuser model downloaded: model="{hub_id}"') + return f'Diffuser model downloaded: model="{hub_id}"' with gr.Column(scale=6): with gr.Row(): @@ -252,7 +249,7 @@ def create_ui(): if tag is not None and len(tag) > 0: url += f'&tag={tag}' r = requests.get(url, timeout=60, headers=headers) - log.debug(f'CivitAI search: name={name} tag={tag} status={r.status_code}') + log.debug(f'CivitAI search: name="{name}" tag={tag or "none"} status={r.status_code}') if r.status_code != 200: return [], [], [] body = r.json() @@ -261,6 +258,8 @@ def create_ui(): data1 = [] for model in data: found = 0 + if model_type == 'LoRA' and model['type'] == 'LORA': + found += 1 for variant in model['modelVersions']: if model_type == 'SD 1.5': if 'SD 1.' in variant['baseModel']: @@ -297,7 +296,7 @@ def create_ui(): d['baseModel'], d['createdAt'], ]) - log.debug(f'CivitAI select: model={in_data[evt.index[0]]} versions={len(data2)}') + log.debug(f'CivitAI select: model="{in_data[evt.index[0]]}" versions={len(data2)}') return data2, preview_img def civit_select2(evt: gr.SelectData, in_data): @@ -315,7 +314,7 @@ def create_ui(): json.dumps(f['metadata']), f['downloadUrl'], ]) - log.debug(f'CivitAI select: model={in_data[evt.index[0]]} files={len(data3)}') + log.debug(f'CivitAI select: model="{in_data[evt.index[0]]}" files={len(data3)}') return data3 def civit_select3(evt: gr.SelectData, in_data): @@ -336,7 +335,7 @@ def create_ui(): list_models() return res - def civit_download_previews(): + def civit_download_previews(civit_previews_rehash): import requests from modules.ui_extra_networks import extra_pages from modules.modelloader import download_civit_preview @@ -347,17 +346,26 @@ def create_ui(): if item.get('fullname', None) is None: continue if 'card-no-preview.png' in item['preview'] and os.path.isfile(item['fullname']): + sha = item.get('hash', None) if item.get('hash', None) is None: - log.debug(f'CivitAI skipping item without hash: name={item["name"]}') + log.debug(f'CivitAI skipping item without hash: name="{item["name"]}"') continue - url = f'https://civitai.com/api/v1/model-versions/by-hash/{item["hash"]}' - r = requests.get(url, timeout=5, headers=headers) - log.debug(f'CivitAI search: name={item["name"]} hash={item["hash"]} status={r.status_code}') + r = requests.get(f'https://civitai.com/api/v1/model-versions/by-hash/{sha}', timeout=5, headers=headers) + log.debug(f'CivitAI search: name="{item["name"]}" hash={sha} status={r.status_code}') if r.status_code == 200: d = r.json() if d.get('images') is not None and len(d['images']) > 0 and len(d['images'][0]['url']) > 0: preview_url = d['images'][0]['url'] res += download_civit_preview(item['filename'], preview_url) + '
' + elif civit_previews_rehash and os.stat(item['fullname']).st_size < (1024 * 1024 * 1024): + sha = modules.hashes.calculate_sha256(item['fullname'], quiet=True) + r = requests.get(f'https://civitai.com/api/v1/model-versions/by-hash/{sha}', timeout=5, headers=headers) + log.debug(f'CivitAI search: name="{item["name"]}" hash={sha} status={r.status_code}') + if r.status_code == 200: + d = r.json() + if d.get('images') is not None and len(d['images']) > 0 and len(d['images'][0]['url']) > 0: + preview_url = d['images'][0]['url'] + res += download_civit_preview(item['filename'], preview_url) + '
' return res with gr.Row(): @@ -389,6 +397,7 @@ def create_ui(): civit_results1 = gr.DataFrame(value = None, label = 'Search results', show_label = True, interactive = False, wrap = True, overflow_row_behaviour = 'paginate', max_rows = 10, headers = civit_headers1, datatype = civit_types1, type='array') with gr.Row(): civit_previews_btn = gr.Button(value="Fetch previews for existing models", variant='primary') + civit_previews_rehash = gr.Checkbox(value=False, label="Check alternative hash") civit_search_text.submit(fn=civit_search, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3]) civit_search_tag.submit(fn=civit_search, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3]) @@ -397,4 +406,4 @@ def create_ui(): civit_results2.select(fn=civit_select2, inputs=[civit_results2], outputs=[civit_results3]) civit_results3.select(fn=civit_select3, inputs=[civit_results3], outputs=[civit_selected, civit_name, civit_search_btn]) civit_download_model_btn.click(fn=civit_download_model, inputs=[civit_selected, civit_name, civit_path, civit_model_type, models_image], outputs=[models_outcome]) - civit_previews_btn.click(fn=civit_download_previews, inputs=[], outputs=[models_outcome]) + civit_previews_btn.click(fn=civit_download_previews, inputs=[civit_previews_rehash], outputs=[models_outcome]) From 19d92dae526cf2e2da142fb62a8d17844044b45e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 18:16:59 -0400 Subject: [PATCH 29/64] fix esrgan --- modules/esrgan_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/esrgan_model.py b/modules/esrgan_model.py index a7656ad58..7ed5c33ec 100644 --- a/modules/esrgan_model.py +++ b/modules/esrgan_model.py @@ -201,7 +201,7 @@ def upscale_without_tiling(model, img): img = img.unsqueeze(0).to(devices.device_esrgan) with devices.inference_context(): output = model(img) - output = output.squeeze().float().cpu().clamp_(0, 1).numpy() + output = output.squeeze().float().cpu().clamp_(0, 1).detach().numpy() output = 255. * np.moveaxis(output, 0, 2) output = output.astype(np.uint8) output = output[:, :, ::-1] From f8fcb6f853564c9d26541b3f2047c8c12140b18a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 18:30:20 -0400 Subject: [PATCH 30/64] fix original hires non-latent --- modules/devices.py | 6 +++--- modules/processing.py | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index ed3951cf9..011ef9cc0 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -207,10 +207,10 @@ def set_cuda_params(): unet_needs_upcast = shared.opts.upcast_sampling if shared.opts.inference_mode == 'inference-mode': inference_context = torch.inference_mode - elif shared.opts.inference_mode == 'no-grad': - inference_context = torch.no_grad - else: + elif shared.opts.inference_mode == 'none': inference_context = contextlib.nullcontext + else: + inference_context = torch.no_grad shared.log.debug(f'Desired Torch parameters: dtype={shared.opts.cuda_dtype} no-half={shared.opts.no_half} no-half-vae={shared.opts.no_half_vae} upscast={shared.opts.upcast_sampling}') shared.log.info(f'Setting Torch parameters: dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__} fp16={fp16_ok} bf16={bf16_ok}') shared.log.debug(f'Torch default device: {torch.device(get_optimal_device_name())}') diff --git a/modules/processing.py b/modules/processing.py index 985773c80..d38b58270 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1000,7 +1000,6 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.ops.append('hires') target_width = self.hr_upscale_to_x target_height = self.hr_upscale_to_y - if latent_scale_mode is not None: for i in range(samples.shape[0]): save_intermediate(samples, i) @@ -1009,6 +1008,16 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): image_conditioning = self.img2img_image_conditioning(decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae)), samples) else: image_conditioning = self.txt2img_image_conditioning(samples.to(dtype=devices.dtype_vae)) + if self.latent_sampler == "PLMS": + self.latent_sampler = 'UniPC' + self.sampler = modules.sd_samplers.create_sampler(self.latent_sampler or self.sampler_name, self.sd_model) + samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2] + noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=subseed_strength, p=self) + x = None + devices.torch_gc() # GC now before running the next img2img to prevent running out of memory + modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True)) + samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) + modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio()) else: decoded_samples = decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae)) lowres_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0) @@ -1035,16 +1044,6 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): samples = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(decoded_samples)) image_conditioning = self.img2img_image_conditioning(decoded_samples, samples) shared.state.nextjob() - if self.latent_sampler == "PLMS": - self.latent_sampler = 'UniPC' - self.sampler = modules.sd_samplers.create_sampler(self.latent_sampler or self.sampler_name, self.sd_model) - samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2] - noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=subseed_strength, p=self) - x = None - devices.torch_gc() # GC now before running the next img2img to prevent running out of memory - modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True)) - samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) - modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio()) self.is_hr_pass = False return samples From 2c06f841fb2b84838138898b5e0f2c22204b0188 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 18:52:48 -0400 Subject: [PATCH 31/64] fix styles api --- modules/api/api.py | 5 +-- modules/processing_diffusers.py | 76 +++++++++++++++++++++++---------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 72fdf0829..cf721d445 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -478,10 +478,7 @@ class Api: return [{"name":x.name,"path":x.data_path, "scale":x.scale} for x in get_realesrgan_models(None)] def get_prompt_styles(self): - styleList = [] - for _k, v in shared.prompt_styles.styles.items(): - styleList.append(v) - return styleList + return [{ 'name': v.name, 'prompt': v.prompt, 'negative_prompt': v.negative_prompt, 'extra': v.extra, 'filename': v.filename, 'preview': v.preview} for v in shared.prompt_styles.styles.values()] def get_embeddings(self): db = sd_hijack.model_hijack.embedding_db diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 02291c986..4b29447ec 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -317,33 +317,63 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro # optional hires pass if p.is_hr_pass: p.init_hr() - recompile_model(hires=True) + latent_scale_mode = shared.latent_upscale_modes.get(p.hr_upscaler, None) if p.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None") + print('HERE1', latent_scale_mode) if p.width != p.hr_upscale_to_x or p.height != p.hr_upscale_to_y: if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_highres_fix and hasattr(shared.sd_model, 'vae'): save_intermediate(latents=output.images, suffix="-before-hires") - hires_resize(latents=output.images) - sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) p.ops.append('hires') - hires_args = set_pipeline_args( - model=shared.sd_model, - prompts=prompts, - negative_prompts=negative_prompts, - prompts_2=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts, - negative_prompts_2=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts, - num_inference_steps=int(p.hr_second_pass_steps // p.denoising_strength + 1), - eta=shared.opts.eta_ddim, - guidance_scale=p.image_cfg_scale if p.image_cfg_scale is not None else p.cfg_scale, - guidance_rescale=p.diffusers_guidance_rescale, - output_type='latent' if hasattr(shared.sd_model, 'vae') else 'np', - clip_skip=p.clip_skip, - image=p.init_images, - strength=p.denoising_strength, - desc='Hires', - ) - try: - output = shared.sd_model(**hires_args) # pylint: disable=not-callable - except AssertionError as e: - shared.log.info(e) + if latent_scale_mode is not None: + recompile_model(hires=True) + hires_resize(latents=output.images) + sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) + hires_args = set_pipeline_args( + model=shared.sd_model, + prompts=prompts, + negative_prompts=negative_prompts, + prompts_2=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts, + negative_prompts_2=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts, + num_inference_steps=int(p.hr_second_pass_steps // p.denoising_strength + 1), + eta=shared.opts.eta_ddim, + guidance_scale=p.image_cfg_scale if p.image_cfg_scale is not None else p.cfg_scale, + guidance_rescale=p.diffusers_guidance_rescale, + output_type='latent' if hasattr(shared.sd_model, 'vae') else 'np', + clip_skip=p.clip_skip, + image=p.init_images, + strength=p.denoising_strength, + desc='Hires', + ) + try: + output = shared.sd_model(**hires_args) # pylint: disable=not-callable + except AssertionError as e: + shared.log.info(e) + else: + """ + decoded_samples = decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae)) + lowres_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0) + batch_images = [] + for i, x_sample in enumerate(lowres_samples): + x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) + x_sample = validate_sample(x_sample) + image = Image.fromarray(x_sample) + save_intermediate(image, i) + image = images.resize_image(1, image, target_width, target_height, upscaler_name=self.hr_upscaler) + image = np.array(image).astype(np.float32) / 255.0 + image = np.moveaxis(image, 2, 0) + batch_images.append(image) + decoded_samples = torch.from_numpy(np.array(batch_images)) + decoded_samples = decoded_samples.to(device=shared.device, dtype=devices.dtype_vae) + decoded_samples = 2. * decoded_samples - 1. + if shared.opts.sd_vae_sliced_encode and len(decoded_samples) > 1: + samples = torch.stack([ + self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(torch.unsqueeze(decoded_sample, 0)))[0] + for decoded_sample + in decoded_samples + ]) + else: + samples = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(decoded_samples)) + image_conditioning = self.img2img_image_conditioning(decoded_samples, samples) + """ # optional refiner pass or decode if is_refiner_enabled: From cbed61732f8344e5f782df76cb407f2f58ca5020 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 19:48:22 -0400 Subject: [PATCH 32/64] enable non-latent hires upscalers --- CHANGELOG.md | 3 +++ modules/processing.py | 2 +- modules/processing_diffusers.py | 48 +++++++++++++-------------------- 3 files changed, 22 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8bcd38c0..fb753fa63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Mostly a service release - updated **models -> civitai** - search and download loras - find previews for already downloaded models or loras +- **hires** enable non-latent upscale modes (standard upscalers) + for both *original* and *diffusers* backend + note: when using refiner, latent upscale works before refiner pass, but non-latent upscale works after refiner pass - new option **inference mode** - default is standard `torch.no_grad` new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus diff --git a/modules/processing.py b/modules/processing.py index d38b58270..e82432978 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -492,7 +492,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su } if 'txt2img' in p.ops: pass - if 'hires' in p.ops: + if 'hires' or 'upscale' in p.ops: args["Hires steps"] = p.hr_second_pass_steps args["Hires upscaler"] = p.hr_upscaler args["Hires upscale"] = p.hr_scale diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 4b29447ec..46498ef4f 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -2,6 +2,8 @@ import time import inspect import typing import torch +import numpy as np +from PIL import Image import modules.devices as devices import modules.shared as shared import modules.sd_samplers as sd_samplers @@ -315,15 +317,14 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro return results # optional hires pass + latent_scale_mode = shared.latent_upscale_modes.get(p.hr_upscaler, None) if p.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None") if p.is_hr_pass: p.init_hr() - latent_scale_mode = shared.latent_upscale_modes.get(p.hr_upscaler, None) if p.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None") - print('HERE1', latent_scale_mode) if p.width != p.hr_upscale_to_x or p.height != p.hr_upscale_to_y: if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_highres_fix and hasattr(shared.sd_model, 'vae'): save_intermediate(latents=output.images, suffix="-before-hires") - p.ops.append('hires') if latent_scale_mode is not None: + p.ops.append('hires') recompile_model(hires=True) hires_resize(latents=output.images) sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) @@ -347,33 +348,6 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro output = shared.sd_model(**hires_args) # pylint: disable=not-callable except AssertionError as e: shared.log.info(e) - else: - """ - decoded_samples = decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae)) - lowres_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0) - batch_images = [] - for i, x_sample in enumerate(lowres_samples): - x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2) - x_sample = validate_sample(x_sample) - image = Image.fromarray(x_sample) - save_intermediate(image, i) - image = images.resize_image(1, image, target_width, target_height, upscaler_name=self.hr_upscaler) - image = np.array(image).astype(np.float32) / 255.0 - image = np.moveaxis(image, 2, 0) - batch_images.append(image) - decoded_samples = torch.from_numpy(np.array(batch_images)) - decoded_samples = decoded_samples.to(device=shared.device, dtype=devices.dtype_vae) - decoded_samples = 2. * decoded_samples - 1. - if shared.opts.sd_vae_sliced_encode and len(decoded_samples) > 1: - samples = torch.stack([ - self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(torch.unsqueeze(decoded_sample, 0)))[0] - for decoded_sample - in decoded_samples - ]) - else: - samples = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(decoded_samples)) - image_conditioning = self.img2img_image_conditioning(decoded_samples, samples) - """ # optional refiner pass or decode if is_refiner_enabled: @@ -429,6 +403,20 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro shared.sd_refiner.to(devices.cpu) devices.torch_gc() + if p.is_hr_pass and latent_scale_mode is None: + if p.width != p.hr_upscale_to_x or p.height != p.hr_upscale_to_y: + p.ops.append('upscale') + if not is_refiner_enabled: + results = vae_decode(latents=output.images, model=shared.sd_model, full_quality=p.full_quality) + upscaled = [] + for image in results: + image = (image * 255.0).astype(np.uint8) + image = Image.fromarray(image) + image = images.resize_image(1, image, p.hr_upscale_to_x, p.hr_upscale_to_y, upscaler_name=p.hr_upscaler) + image = np.array(image).astype(np.float32) / 255.0 + upscaled.append(image) + return upscaled + # final decode since there is no refiner if not is_refiner_enabled: results = vae_decode(latents=output.images, model=shared.sd_model, full_quality=p.full_quality) From a9b3b0e8060c3db2c1bc151dc7d46762dcb35d59 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 19:52:51 -0400 Subject: [PATCH 33/64] update css and changelog --- CHANGELOG.md | 1 + javascript/style.css | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb753fa63..c7758307c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Mostly a service release skips reading metadata from models that are not already cached - updated **gradio** - **styles** support for subfolders +- **css** optimizations - clean-up **logging** - capture system info in startup log - better diagnostic output diff --git a/javascript/style.css b/javascript/style.css index e1b32b943..5b7ed81a6 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -119,7 +119,7 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } #quicksettings > button { padding: 0 1em 0 0 } #settings { display: flex; gap: var(--layout-gap); } -#settings div { border: none; gap: 0.5em; width: fit-content; } +#settings div { border: none; gap: 0.5em; width: fit-content; display: inline-flex; } #settings > div.tab-content { flex: 10 0 75%; display: grid; } #settings > div.tab-content > div { border: none; padding: 0; } From cb51e55c28b10a3e7bc4378fcc0cda68ea8d0646 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 19:57:28 -0400 Subject: [PATCH 34/64] handle invalid filenames in styles --- modules/styles.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/styles.py b/modules/styles.py index 82ad05593..efb2bfc42 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -98,7 +98,9 @@ class StyleDatabase: "extra": "", "preview": "", } - fn = os.path.join(path, name + ".json") + keepcharacters = (' ','.','_') + fn = "".join(c for c in name if c.isalnum() or c in keepcharacters).rstrip() + fn = os.path.join(path, fn + ".json") try: with open(fn, 'w', encoding='utf-8') as f: json.dump(style, f, indent=2) From b504f1d6a885545400188c6f074daa65f3872b44 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 20:03:57 -0400 Subject: [PATCH 35/64] update todo --- TODO.md | 1 + launch.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 58172fdbe..2ee1c56d3 100644 --- a/TODO.md +++ b/TODO.md @@ -30,6 +30,7 @@ Stuff to be added, in no particular order... - [Localization](https://app.transifex.com/signup/open-source/) - New Minor - Prompt padding for positive/negative + - Add EN provider for VAEs - XYZ grid upscalers - Built-in `motd`-style notifications - Docker PR diff --git a/launch.py b/launch.py index 1b2106992..7e3b46725 100644 --- a/launch.py +++ b/launch.py @@ -217,7 +217,7 @@ if __name__ == "__main__": except Exception: alive = False requests = 0 - if round(time.time()) % 10 == 0: + if round(time.time()) % 120 == 0: state = f'job="{instance.state.job}" {instance.state.job_no}/{instance.state.job_count}' if instance.state.job != '' or instance.state.job_no != 0 or instance.state.job_count != 0 else 'idle' uptime = round(time.time() - instance.state.server_start) installer.log.debug(f'Server alive={alive} jobs={instance.state.total_jobs} requests={requests} uptime={uptime}s memory {get_memory_stats()} {state}') From fa5e0a38da4a1c51fafb290b22dbde7a978c54f4 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 10 Sep 2023 20:42:14 -0400 Subject: [PATCH 36/64] update todo --- CHANGELOG.md | 3 ++- TODO.md | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7758307c..b7106236e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ Mostly a service release - find previews for already downloaded models or loras - **hires** enable non-latent upscale modes (standard upscalers) for both *original* and *diffusers* backend - note: when using refiner, latent upscale works before refiner pass, but non-latent upscale works after refiner pass + - when using non-latent upscalers, hires is now skipped - hires is only used for latent upscale + - when using refiner, latent upscale works before refiner pass, but non-latent upscale works after refiner pass - new option **inference mode** - default is standard `torch.no_grad` new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus diff --git a/TODO.md b/TODO.md index 2ee1c56d3..8c5441e7a 100644 --- a/TODO.md +++ b/TODO.md @@ -22,7 +22,6 @@ Stuff to be added, in no particular order... - Port **A1111** stuff - Port `p.all_hr_prompts` - Import core repos to reduce dependencies - - Parse StabilityAI `modelspec` metadata - Non-technical: - Update Wiki - Get more high-quality upscalers @@ -34,11 +33,12 @@ Stuff to be added, in no particular order... - XYZ grid upscalers - Built-in `motd`-style notifications - Docker PR + - Add force hires - New Major - - Style editor (use json format instead of csv) - - Profile manager (for config.json and ui-config.json) + - Style editor + - Profile manager (for `config.json` and `ui-config.json`) - Multi-user support - - Add [SAG](https://huggingface.co/docs/diffusers/v0.19.3/en/api/pipelines/self_attention_guidance),(https://github.com/ashen-sensored/sd_webui_SAG) + - Add [SAG](https://huggingface.co/docs/diffusers/v0.19.3/en/api/pipelines/self_attention_guidance), [SAG](https://github.com/ashen-sensored/sd_webui_SAG) - Image phash and hdash using `imagehash` - Model merge using `git-rebasin` - Enable refiner-style workflow for `ldm` backend @@ -50,11 +50,9 @@ Stuff to be added, in no particular order... - Templates for SD-XL training - Lora train UI - Redesign - - Extensions reporting framework - New UI - New inpainting canvas controls (move from backend to purely frontend) - New image browser (move from backend to purely frontend) - - New extra networks (move from backend to purely frontend) - Change workflows from static/legacy to steps-based ## Investigate From f9096194da0b0f50db3583ac8eedd46971761327 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 11 Sep 2023 12:02:06 +0300 Subject: [PATCH 37/64] Fix typo --- modules/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing.py b/modules/processing.py index e82432978..09cfee1ee 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -492,7 +492,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su } if 'txt2img' in p.ops: pass - if 'hires' or 'upscale' in p.ops: + if 'hires' in p.ops or 'upscale' in p.ops: args["Hires steps"] = p.hr_second_pass_steps args["Hires upscaler"] = p.hr_upscaler args["Hires upscale"] = p.hr_scale From 7a8fed1e94df00551fd4b02509d3c883f17541ad Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 11 Sep 2023 12:24:54 +0300 Subject: [PATCH 38/64] Fix img2img doesn't have hires error --- modules/processing_diffusers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 46498ef4f..13be1752e 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -317,7 +317,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro return results # optional hires pass - latent_scale_mode = shared.latent_upscale_modes.get(p.hr_upscaler, None) if p.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None") + latent_scale_mode = shared.latent_upscale_modes.get(p.hr_upscaler, None) if (hasattr(p, "hr_upscaler") and p.hr_upscaler is not None) else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None") if p.is_hr_pass: p.init_hr() if p.width != p.hr_upscale_to_x or p.height != p.hr_upscale_to_y: From 1f730b129facb001063c13d10b1d32583bc1a21d Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 11 Sep 2023 09:09:38 -0400 Subject: [PATCH 39/64] update hires logic --- CHANGELOG.md | 11 ++-- extensions-builtin/sd-webui-controlnet | 2 +- .../stable-diffusion-webui-images-browser | 2 +- html/locale_en.json | 1 + javascript/setHints.js | 3 +- javascript/style.css | 2 +- modules/processing.py | 54 ++++++++++--------- modules/processing_diffusers.py | 38 +++++-------- modules/txt2img.py | 5 +- modules/ui.py | 5 +- 10 files changed, 62 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7106236e..6dd331f57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,17 @@ Mostly a service release - tons of fixes +- changes to **hires** + - enable non-latent upscale modes (standard upscalers) + - when using latent upscale, hires pass is run automatically + - when using non-latent upscalers, hires pass is skipped by default + enabled using **force hires** option in ui + hires was not designed to work with standard upscalers, but i understand this is a common workflow + - when using refiner, upscale/hires runs before refiner pass - update **ui hints** - updated **models -> civitai** - search and download loras - find previews for already downloaded models or loras -- **hires** enable non-latent upscale modes (standard upscalers) - for both *original* and *diffusers* backend - - when using non-latent upscalers, hires is now skipped - hires is only used for latent upscale - - when using refiner, latent upscale works before refiner pass, but non-latent upscale works after refiner pass - new option **inference mode** - default is standard `torch.no_grad` new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index b15636ed3..e67e01773 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit b15636ed35eff934af69985bcdfbc407cfedfe7d +Subproject commit e67e017731aad05796b9615dc6eadce911298ea1 diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser index b8f6e05d1..9f95e6d48 160000 --- a/extensions-builtin/stable-diffusion-webui-images-browser +++ b/extensions-builtin/stable-diffusion-webui-images-browser @@ -1 +1 @@ -Subproject commit b8f6e05d1d23b3d0d17b0a9cfbf824bf4a1f98e9 +Subproject commit 9f95e6d4812ca7acb3ec56cbf45fbc85aef0236d diff --git a/html/locale_en.json b/html/locale_en.json index 99fa6b8bc..25a6516b9 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -140,6 +140,7 @@ {"id":"","label":"Hires steps","localized":"","hint":"Number of sampling steps for upscaled picture. If 0, uses same as for original"}, {"id":"","label":"Upscaler","localized":"","hint":"Which pre-trained model to use for the upscaling process."}, {"id":"","label":"Upscale by","localized":"","hint":"Adjusts the size of the image by multiplying the original width and height by the selected value. Ignored if either Resize width to or Resize height to are non-zero"}, + {"id":"","label":"Force Hires","localized":"","hint":"Hires runs automatically when Latent upscale is selected, but its skipped when using non-latent upscalers. Enable force hires to run hires with non-latent upscalers"}, {"id":"","label":"Resize width to","localized":"","hint":"Resizes image to this width. If 0, width is inferred from either of two nearby sliders"}, {"id":"","label":"Resize height to","localized":"","hint":"Resizes image to this height. If 0, height is inferred from either of two nearby sliders"}, {"id":"","label":"Secondary sampler","localized":"","hint":"Use specific sampler as fallback sampler if primary is not supported for specific operation"}, diff --git a/javascript/setHints.js b/javascript/setHints.js index f755e6dee..7398c7a5e 100644 --- a/javascript/setHints.js +++ b/javascript/setHints.js @@ -52,6 +52,7 @@ async function setHints() { const res = await fetch('/file=html/locale_en.json'); const json = await res.json(); localeData.data = Object.values(json).flat(); + for (const e of localeData.data) e.label = e.label.toLowerCase().trim(); } const elements = [ ...Array.from(gradioApp().querySelectorAll('button')), @@ -65,7 +66,7 @@ async function setHints() { localeData.finished = true; const t0 = performance.now(); for (const el of elements) { - const found = localeData.data.find((l) => l.label === el.textContent.trim()); + const found = localeData.data.find((l) => l.label === el.textContent.toLowerCase().trim()); if (found?.localized?.length > 0) { localized++; el.textContent = found.localized; diff --git a/javascript/style.css b/javascript/style.css index 5b7ed81a6..b8b4edbb8 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -6,7 +6,7 @@ div.tabitem { padding: 0 !important; } div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; gap: 0.5em 1em; flex-grow: 1 !important; } div.compact{ gap: 1em; } div.gradio-html.min{ min-height: 0; } -.block.gradio-checkbox { margin: 0.75em 1.5em 0 0; } +.block.gradio-checkbox { margin: 0.75em 1.5em 0 0; align-self: center; } .block.gradio-dropdown, .block.gradio-slider, .block.gradio-checkbox, .block.gradio-textbox, .block.gradio-radio, .block.gradio-checkboxgroup, .block.gradio-number, .block.gradio-colorpicker { border-width: 0 !important; box-shadow: none !important;} .block.padded:not(.gradio-accordion) { padding: 0 !important; margin-right: 0; min-width: 100px !important; } .compact{ background: transparent !important; padding: 0 !important; } diff --git a/modules/processing.py b/modules/processing.py index 09cfee1ee..a07434ed7 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -158,6 +158,7 @@ class StableDiffusionProcessing: self.clip_skip = clip_skip self.iteration = 0 self.is_hr_pass = False + self.hr_force = False self.enable_hr = None self.refiner_steps = 5 self.refiner_start = 0 @@ -895,13 +896,14 @@ def old_hires_fix_first_pass_dimensions(width, height): class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): - def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, firstphase_width: int = 0, firstphase_height: int = 0, hr_scale: float = 2.0, hr_upscaler: str = None, hr_second_pass_steps: int = 0, hr_resize_x: int = 0, hr_resize_y: int = 0, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs): + def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, firstphase_width: int = 0, firstphase_height: int = 0, hr_scale: float = 2.0, hr_force: bool = False, hr_upscaler: str = None, hr_second_pass_steps: int = 0, hr_resize_x: int = 0, hr_resize_y: int = 0, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs): super().__init__(**kwargs) self.enable_hr = enable_hr self.denoising_strength = denoising_strength self.hr_scale = hr_scale self.hr_upscaler = hr_upscaler + self.hr_force = hr_force self.hr_second_pass_steps = hr_second_pass_steps self.hr_resize_x = hr_resize_x self.hr_resize_y = hr_resize_y @@ -983,13 +985,14 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): if shared.backend == shared.Backend.DIFFUSERS: modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.TEXT_2_IMAGE) + latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None") + if self.enable_hr and (latent_scale_mode is None or self.hr_force): + if len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) == 0: + shared.log.warning(f"Cannot find upscaler for hires: {self.hr_upscaler}") + self.enable_hr = False + self.ops.append('txt2img') self.sampler = modules.sd_samplers.create_sampler(self.sampler_name, self.sd_model) - latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None") - if self.enable_hr and latent_scale_mode is None: - if len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) == 0: - shared.log.warning("Could not find upscaler to use with hrfix") - self.enable_hr = False x = create_random_tensors([4, self.height // 8, self.width // 8], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self) samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.txt2img_image_conditioning(x)) if not self.enable_hr or shared.state.interrupted or shared.state.skipped: @@ -1000,25 +1003,9 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): self.ops.append('hires') target_width = self.hr_upscale_to_x target_height = self.hr_upscale_to_y - if latent_scale_mode is not None: - for i in range(samples.shape[0]): - save_intermediate(samples, i) - samples = torch.nn.functional.interpolate(samples, size=(target_height // 8, target_width // 8), mode=latent_scale_mode["mode"], antialias=latent_scale_mode["antialias"]) - if getattr(self, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) < 1.0: - image_conditioning = self.img2img_image_conditioning(decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae)), samples) - else: - image_conditioning = self.txt2img_image_conditioning(samples.to(dtype=devices.dtype_vae)) - if self.latent_sampler == "PLMS": - self.latent_sampler = 'UniPC' - self.sampler = modules.sd_samplers.create_sampler(self.latent_sampler or self.sampler_name, self.sd_model) - samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2] - noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=subseed_strength, p=self) - x = None - devices.torch_gc() # GC now before running the next img2img to prevent running out of memory - modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True)) - samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) - modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio()) - else: + for i in range(samples.shape[0]): + save_intermediate(samples, i) + if latent_scale_mode is None or self.hr_force: # non-latent upscaling decoded_samples = decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae)) lowres_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0) batch_images = [] @@ -1043,6 +1030,23 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): else: samples = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(decoded_samples)) image_conditioning = self.img2img_image_conditioning(decoded_samples, samples) + else: + samples = torch.nn.functional.interpolate(samples, size=(target_height // 8, target_width // 8), mode=latent_scale_mode["mode"], antialias=latent_scale_mode["antialias"]) + if getattr(self, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) < 1.0: + image_conditioning = self.img2img_image_conditioning(decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae)), samples) + else: + image_conditioning = self.txt2img_image_conditioning(samples.to(dtype=devices.dtype_vae)) + if self.latent_sampler == "PLMS": + self.latent_sampler = 'UniPC' + if self.hr_force or latent_scale_mode is not None: + devices.torch_gc() # GC now before running the next img2img to prevent running out of memory + self.sampler = modules.sd_samplers.create_sampler(self.latent_sampler or self.sampler_name, self.sd_model) + samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2] + noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=subseed_strength, p=self) + modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True)) + samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning) + modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio()) + x = None shared.state.nextjob() self.is_hr_pass = False diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 13be1752e..915acd316 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -2,8 +2,6 @@ import time import inspect import typing import torch -import numpy as np -from PIL import Image import modules.devices as devices import modules.shared as shared import modules.sd_samplers as sd_samplers @@ -26,7 +24,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro results = [] if p.enable_hr and p.hr_upscaler != 'None' and p.denoising_strength > 0 and len(getattr(p, 'init_images', [])) == 0: p.is_hr_pass = True - is_refiner_enabled = p.enable_hr and p.refiner_steps > 0 and shared.sd_refiner is not None + is_refiner_enabled = p.enable_hr and p.refiner_steps > 0 and p.refiner_start > 0 and p.refiner_start < 1 and shared.sd_refiner is not None def hires_resize(latents): # input=latents output=pil latent_upscaler = shared.latent_upscale_modes.get(p.hr_upscaler, None) @@ -36,10 +34,11 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro first_pass_images = vae_decode(latents=latents, model=shared.sd_model, full_quality=True, output_type='pil') p.init_images = [] for first_pass_image in first_pass_images: - init_image = images.resize_image(1, first_pass_image, p.hr_upscale_to_x, p.hr_upscale_to_y, upscaler_name=p.hr_upscaler) if latent_upscaler is None else first_pass_image + if latent_upscaler is None: + init_image = images.resize_image(1, first_pass_image, p.hr_upscale_to_x, p.hr_upscale_to_y, upscaler_name=p.hr_upscaler) + else: + init_image = first_pass_image p.init_images.append(init_image) - p.width = p.hr_upscale_to_x - p.height = p.hr_upscale_to_y def save_intermediate(latents, suffix): for i in range(len(latents)): @@ -321,12 +320,12 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro if p.is_hr_pass: p.init_hr() if p.width != p.hr_upscale_to_x or p.height != p.hr_upscale_to_y: + p.ops.append('upscale') if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_highres_fix and hasattr(shared.sd_model, 'vae'): save_intermediate(latents=output.images, suffix="-before-hires") - if latent_scale_mode is not None: - p.ops.append('hires') + hires_resize(latents=output.images) + if latent_scale_mode is not None or p.hr_force: recompile_model(hires=True) - hires_resize(latents=output.images) sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) hires_args = set_pipeline_args( model=shared.sd_model, @@ -372,6 +371,11 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro refiner_is_sdxl = bool("StableDiffusionXL" in shared.sd_refiner.__class__.__name__) p.ops.append('refine') for i in range(len(output.images)): + image = output.images[i] + if (image.shape[2] == 3) and (image.shape[0] % 8 != 0 or image.shape[1] % 8 != 0): + shared.log.warning(f'Refiner requires image size to be divisible by 8: {image.shape}') + results.append(image) + return results refiner_args = set_pipeline_args( model=shared.sd_refiner, prompts=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts[i], @@ -383,7 +387,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro guidance_rescale=p.diffusers_guidance_rescale, denoising_start=p.refiner_start if p.refiner_start > 0 and p.refiner_start < 1 else None, denoising_end=1 if p.refiner_start > 0 and p.refiner_start < 1 else None, - image=output.images[i], + image=image, output_type='latent' if hasattr(shared.sd_refiner, 'vae') else 'np', clip_skip=p.clip_skip, desc='Refiner', @@ -403,20 +407,6 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro shared.sd_refiner.to(devices.cpu) devices.torch_gc() - if p.is_hr_pass and latent_scale_mode is None: - if p.width != p.hr_upscale_to_x or p.height != p.hr_upscale_to_y: - p.ops.append('upscale') - if not is_refiner_enabled: - results = vae_decode(latents=output.images, model=shared.sd_model, full_quality=p.full_quality) - upscaled = [] - for image in results: - image = (image * 255.0).astype(np.uint8) - image = Image.fromarray(image) - image = images.resize_image(1, image, p.hr_upscale_to_x, p.hr_upscale_to_y, upscaler_name=p.hr_upscaler) - image = np.array(image).astype(np.float32) / 255.0 - upscaled.append(image) - return upscaled - # final decode since there is no refiner if not is_refiner_enabled: results = vae_decode(latents=output.images, model=shared.sd_model, full_quality=p.full_quality) diff --git a/modules/txt2img.py b/modules/txt2img.py index 51b09192e..899af07db 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -4,9 +4,9 @@ from modules.generation_parameters_copypaste import create_override_settings_dic from modules.ui import plaintext_to_html -def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, latent_index: int, full_quality: bool, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, clip_skip: int, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, refiner_steps: int, refiner_start: int, refiner_prompt: str, refiner_negative: str, override_settings_texts, *args): # pylint: disable=unused-argument +def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, latent_index: int, full_quality: bool, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, clip_skip: int, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_force: bool, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, refiner_steps: int, refiner_start: int, refiner_prompt: str, refiner_negative: str, override_settings_texts, *args): # pylint: disable=unused-argument - shared.log.debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}||height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}||refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings_texts={override_settings_texts}') + shared.log.debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}||height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_force={hr_force}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}|refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings_texts={override_settings_texts}') if shared.sd_model is None: shared.log.warning('Model not loaded') @@ -49,6 +49,7 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step denoising_strength=denoising_strength, hr_scale=hr_scale, hr_upscaler=hr_upscaler, + hr_force=hr_force, hr_second_pass_steps=hr_second_pass_steps, hr_resize_x=hr_resize_x, hr_resize_y=hr_resize_y, diff --git a/modules/ui.py b/modules/ui.py index d1b7a6b2a..1b12839a3 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -396,8 +396,9 @@ def create_ui(startup_timer = None): hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"): hr_upscaler = gr.Dropdown(label="Upscaler", elem_id="txt2img_hr_upscaler", choices=[*modules.shared.latent_upscale_modes, *[x.name for x in modules.shared.sd_upscalers]], value=modules.shared.latent_upscale_default_mode) - hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id="txt2img_steps_alt", value=20) + hr_force = gr.Checkbox(label='Force Hires', value=False, elem_id="txt2img_hr_force") with FormRow(elem_id="txt2img_hires_fix_row2", variant="compact"): + hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id="txt2img_steps_alt", value=20) hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale") with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact"): hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x") @@ -450,7 +451,7 @@ def create_ui(startup_timer = None): seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, height, width, show_second_pass, denoising_strength, - hr_scale, hr_upscaler, hr_second_pass_steps, hr_resize_x, hr_resize_y, + hr_scale, hr_upscaler, hr_force, hr_second_pass_steps, hr_resize_x, hr_resize_y, refiner_steps, refiner_start, refiner_prompt, refiner_negative, override_settings, ] + custom_inputs, From 2f071c65867652bc9530a1ea44e1789ce3aac4d7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 11 Sep 2023 09:55:28 -0400 Subject: [PATCH 40/64] cleanup --- .gitignore | 1 + TODO.md | 1 - modules/processing.py | 1 - modules/ui.py | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index bde1bf3bd..362e9c257 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__ .ruff_cache /cache.json /*.json +/*.yaml /params.txt /styles.csv /user.css diff --git a/TODO.md b/TODO.md index 8c5441e7a..e3e463db8 100644 --- a/TODO.md +++ b/TODO.md @@ -33,7 +33,6 @@ Stuff to be added, in no particular order... - XYZ grid upscalers - Built-in `motd`-style notifications - Docker PR - - Add force hires - New Major - Style editor - Profile manager (for `config.json` and `ui-config.json`) diff --git a/modules/processing.py b/modules/processing.py index a07434ed7..bf68f0d6c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -212,7 +212,6 @@ class StableDiffusionProcessing: conditioning_mask = np.array(image_mask.convert("L")) conditioning_mask = conditioning_mask.astype(np.float32) / 255.0 conditioning_mask = torch.from_numpy(conditioning_mask[None, None]) - # Inpainting model uses a discretized mask as input, so we round to either 1.0 or 0.0 conditioning_mask = torch.round(conditioning_mask) else: diff --git a/modules/ui.py b/modules/ui.py index 1b12839a3..dd6fb198a 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -391,7 +391,7 @@ def create_ui(startup_timer = None): with FormGroup(visible=show_second_pass.value, elem_id="txt2img_second_pass") as second_pass_group: with FormRow(elem_id="sampler_selection_txt2img_alt_row1"): latent_index = gr.Dropdown(label='Secondary sampler', elem_id="txt2img_sampling_alt", choices=[x.name for x in modules.sd_samplers.samplers], value='Default', type="index") - denoising_strength = gr.Slider(minimum=0.05, maximum=1.0, step=0.01, label='Denoising strength', value=0.3, elem_id="txt2img_denoising_strength") + denoising_strength = gr.Slider(minimum=0.05, maximum=1.0, step=0.01, label='Denoising strength', value=0.5, elem_id="txt2img_denoising_strength") with FormRow(elem_id="txt2img_hires_finalres", variant="compact"): hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"): From d44c9d0c33f0cc71c740b00f94f3435cb561c45b Mon Sep 17 00:00:00 2001 From: AI-Casanova <54461896+AI-Casanova@users.noreply.github.com> Date: Mon, 11 Sep 2023 17:12:29 -0500 Subject: [PATCH 41/64] Fix for mixed case TI filenames (SDXL) --- modules/textual_inversion/textual_inversion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index c8cee595d..a9e200822 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -144,9 +144,9 @@ class EmbeddingDatabase: embeddings_dict[k] = f.get_tensor(k) for i in range(len(embeddings_dict["clip_l"])): if i == 0: - token = name + token = name.lower() else: - token = f"{name}_{i}" + token = f"{name.lower()}_{i}" pipe.tokenizer.add_tokens(token) token_id = pipe.tokenizer.convert_tokens_to_ids(token) pipe.text_encoder.resize_token_embeddings(len(pipe.tokenizer)) From 9cf7fc4a75e66956dc698f040905f7c948e46c8f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 12 Sep 2023 11:54:07 -0400 Subject: [PATCH 42/64] add new hires with refiner and non-latent modes --- CHANGELOG.md | 12 ++++- extensions-builtin/sd-webui-agent-scheduler | 2 +- installer.py | 12 +++++ javascript/style.css | 2 +- launch.py | 1 + modules/images.py | 4 +- modules/processing.py | 13 +++-- modules/processing_diffusers.py | 57 +++++++++++++++++---- modules/taesd/sd_vae_taesd.py | 19 +++++++ modules/ui.py | 7 +-- webui.py | 6 ++- wiki | 2 +- 12 files changed, 113 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dd331f57..ced0fa4ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # Change Log for SD.Next -## Update for 2023-09-10 +## Update for 2023-09-12 -Mostly a service release +Mostly a service release, but with some changes in behavior, especially in HiRes area of the code... + - tons of fixes - changes to **hires** - enable non-latent upscale modes (standard upscalers) @@ -11,6 +12,12 @@ Mostly a service release enabled using **force hires** option in ui hires was not designed to work with standard upscalers, but i understand this is a common workflow - when using refiner, upscale/hires runs before refiner pass + - second pass can now also utilize full/quick vae quality + - note that when combining non-latent upscale, hires and refiner output quality is maximum, + but operations are really resource intensive as it includes: *base->decode->upscale->encode->hires->refine* + - all combinations of: decode full/quick + upscale none/latent/non-latent + hires on/off + refiner on/off + should be supported, but given the number of combinations, issues are possible + - all operations are captured in image medata - update **ui hints** - updated **models -> civitai** - search and download loras @@ -29,6 +36,7 @@ Mostly a service release - capture extension output - capture ldm output - cleaner server restart + - custom exception handling ## Update for 2023-09-06 diff --git a/extensions-builtin/sd-webui-agent-scheduler b/extensions-builtin/sd-webui-agent-scheduler index 310bb4eac..097fe4e5c 160000 --- a/extensions-builtin/sd-webui-agent-scheduler +++ b/extensions-builtin/sd-webui-agent-scheduler @@ -1 +1 @@ -Subproject commit 310bb4eace9e7fc03074dd49a91797bd6df2c43b +Subproject commit 097fe4e5c97612520b3e4d80d44f36088c53e2c4 diff --git a/installer.py b/installer.py index f4c4e0a3b..1f853cc24 100644 --- a/installer.py +++ b/installer.py @@ -108,6 +108,18 @@ def setup_logging(): # logging.getLogger("DeepSpeed").handlers = log.handlers +def custom_excepthook(exc_type, exc_value, exc_traceback): + import traceback + if issubclass(exc_type, KeyboardInterrupt): + sys.__excepthook__(exc_type, exc_value, exc_traceback) + return + log.error(f"Uncaught exception occurred: type={exc_type} value={exc_value}") + if exc_traceback: + format_exception = traceback.format_tb(exc_traceback) + for line in format_exception: + log.error(repr(line)) + + def print_dict(d): return ' '.join([f'{k}={v}' for k, v in d.items()]) diff --git a/javascript/style.css b/javascript/style.css index b8b4edbb8..11e6a5a88 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -119,7 +119,7 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } #quicksettings > button { padding: 0 1em 0 0 } #settings { display: flex; gap: var(--layout-gap); } -#settings div { border: none; gap: 0.5em; width: fit-content; display: inline-flex; } +#settings div { border: none; gap: 0.5em; } #settings > div.tab-content { flex: 10 0 75%; display: grid; } #settings > div.tab-content > div { border: none; padding: 0; } diff --git a/launch.py b/launch.py index 7e3b46725..fd06897e5 100644 --- a/launch.py +++ b/launch.py @@ -165,6 +165,7 @@ if __name__ == "__main__": installer.args = args installer.setup_logging() installer.log.info('Starting SD.Next') + sys.excepthook = installer.custom_excepthook installer.read_options() if args.skip_all: args.quick = True diff --git a/modules/images.py b/modules/images.py index 9697a8823..317ee815a 100644 --- a/modules/images.py +++ b/modules/images.py @@ -201,7 +201,7 @@ def draw_prompt_matrix(im, width, height, all_prompts, margin=0): return draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin) -def resize_image(resize_mode, im, width, height, upscaler_name=None): +def resize_image(resize_mode, im, width, height, upscaler_name=None, output_type='image'): """ Resizes an image with the specified resize_mode, width, and height. Args: @@ -261,6 +261,8 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None): fill_width = width // 2 - src_w // 2 res.paste(resized.resize((fill_width, height), box=(0, 0, 0, height)), box=(0, 0)) res.paste(resized.resize((fill_width, height), box=(resized.width, 0, resized.width, height)), box=(fill_width + src_w, 0)) + if output_type == 'np': + return np.array(res) return res diff --git a/modules/processing.py b/modules/processing.py index bf68f0d6c..b7bd2af1a 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -488,7 +488,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su "Backend": 'Diffusers' if shared.backend == shared.Backend.DIFFUSERS else 'Original', "Version": git_commit, "Comment": comment, - "Operations": ', '.join(list(set(p.ops))).replace('"', '') if len(p.ops) > 0 else None, + "Operations": '; '.join(p.ops).replace('"', '') if len(p.ops) > 0 else 'none', } if 'txt2img' in p.ops: pass @@ -800,8 +800,13 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: for i, x_sample in enumerate(x_samples_ddim): p.batch_index = i - x_sample = 255. * (np.moveaxis(x_sample.cpu().numpy(), 0, 2) if shared.backend == shared.Backend.ORIGINAL else x_sample) - x_sample = validate_sample(x_sample) + if type(x_sample) == Image.Image: + image = x_sample + x_sample = np.array(x_sample) + else: + x_sample = 255. * (np.moveaxis(x_sample.cpu().numpy(), 0, 2) if shared.backend == shared.Backend.ORIGINAL else x_sample) + x_sample = validate_sample(x_sample) + image = Image.fromarray(x_sample) if p.restore_faces: if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_face_restoration: orig = p.restore_faces @@ -811,7 +816,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: images.save_image(Image.fromarray(x_sample), path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix="-before-face-restoration") p.ops.append('face') x_sample = modules.face_restoration.restore_faces(x_sample) - image = Image.fromarray(x_sample) + image = Image.fromarray(x_sample) if p.scripts is not None: pp = modules.scripts.PostprocessImageArgs(image) p.scripts.postprocess_image(p, pp) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 915acd316..dfbae5e63 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -2,6 +2,7 @@ import time import inspect import typing import torch +import torchvision.transforms.functional as TF import modules.devices as devices import modules.shared as shared import modules.sd_samplers as sd_samplers @@ -31,14 +32,17 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro shared.log.info(f'Hires: upscaler={p.hr_upscaler} width={p.hr_upscale_to_x} height={p.hr_upscale_to_y} images={latents.shape[0]}') if latent_upscaler is not None: latents = torch.nn.functional.interpolate(latents, size=(p.hr_upscale_to_y // 8, p.hr_upscale_to_x // 8), mode=latent_upscaler["mode"], antialias=latent_upscaler["antialias"]) - first_pass_images = vae_decode(latents=latents, model=shared.sd_model, full_quality=True, output_type='pil') + first_pass_images = vae_decode(latents=latents, model=shared.sd_model, full_quality=p.full_quality, output_type='pil') p.init_images = [] for first_pass_image in first_pass_images: if latent_upscaler is None: init_image = images.resize_image(1, first_pass_image, p.hr_upscale_to_x, p.hr_upscale_to_y, upscaler_name=p.hr_upscaler) else: init_image = first_pass_image + # if is_refiner_enabled: + # init_image = vae_encode(init_image, model=shared.sd_model, full_quality=p.full_quality) p.init_images.append(init_image) + return p.init_images def save_intermediate(latents, suffix): for i in range(len(latents)): @@ -64,7 +68,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro time.sleep(0.1) def full_vae_decode(latents, model): - shared.log.debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)} images={latents.shape[0]}') + shared.log.debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)} images={latents.shape[0]} latents={latents.shape}') if shared.opts.diffusers_move_unet and not model.has_accelerate: shared.log.debug('Moving to CPU: model=UNet') unet_device = model.unet.device @@ -78,13 +82,34 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro model.unet.to(unet_device) return decoded + def full_vae_encode(image, model): + shared.log.debug(f'VAE encode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)}') + if shared.opts.diffusers_move_unet and not model.has_accelerate: + shared.log.debug('Moving to CPU: model=UNet') + unet_device = model.unet.device + model.unet.to(devices.cpu) + devices.torch_gc() + if not shared.cmd_opts.lowvram and not shared.opts.diffusers_seq_cpu_offload: + model.vae.to(devices.device) + encoded = model.vae.encode(image.to(model.vae.device, model.vae.dtype)) + if shared.opts.diffusers_move_unet and not model.has_accelerate: + model.unet.to(unet_device) + return encoded + def taesd_vae_decode(latents): - shared.log.debug(f'VAE decode: name=TAESD images={latents.shape[0]}') - decoded = torch.zeros((len(latents), 3, p.height, p.width), dtype=devices.dtype_vae, device=devices.device) + shared.log.debug(f'VAE decode: name=TAESD images={len(latents)} latents={latents.shape}') + if len(latents) == 0: + return [] + decoded = torch.zeros((len(latents), 3, latents.shape[2] * 8, latents.shape[3] * 8), dtype=devices.dtype_vae, device=devices.device) for i in range(len(output.images)): decoded[i] = (sd_vae_taesd.decode(latents[i]) * 2.0) - 1.0 return decoded + def taesd_vae_encode(image): + shared.log.debug(f'VAE encode: name=TAESD image={image.shape}') + encoded = sd_vae_taesd.encode(image) + return encoded + def vae_decode(latents, model, output_type='np', full_quality=True): if not torch.is_tensor(latents): # already decoded return latents @@ -105,6 +130,19 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro imgs = model.image_processor.postprocess(decoded, output_type=output_type) return imgs + def vae_encode(image, model, full_quality=True): # pylint: disable=unused-variable + if shared.state.interrupted or shared.state.skipped: + return [] + if not hasattr(model, 'vae'): + shared.log.error('VAE not found in model') + return [] + tensor = TF.to_tensor(image.convert("RGB")).unsqueeze(0).to(devices.device, devices.dtype_vae) + if full_quality: + latents = full_vae_encode(image=tensor, model=shared.sd_model) + else: + latents = taesd_vae_encode(image=tensor) + return latents + def fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2): if type(prompts) is str: prompts = [prompts] @@ -323,8 +361,9 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro p.ops.append('upscale') if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_highres_fix and hasattr(shared.sd_model, 'vae'): save_intermediate(latents=output.images, suffix="-before-hires") - hires_resize(latents=output.images) + output.images = hires_resize(latents=output.images) if latent_scale_mode is not None or p.hr_force: + p.ops.append('hires') recompile_model(hires=True) sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) hires_args = set_pipeline_args( @@ -372,10 +411,10 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro p.ops.append('refine') for i in range(len(output.images)): image = output.images[i] - if (image.shape[2] == 3) and (image.shape[0] % 8 != 0 or image.shape[1] % 8 != 0): - shared.log.warning(f'Refiner requires image size to be divisible by 8: {image.shape}') - results.append(image) - return results + # if (image.shape[2] == 3) and (image.shape[0] % 8 != 0 or image.shape[1] % 8 != 0): + # shared.log.warning(f'Refiner requires image size to be divisible by 8: {image.shape}') + # results.append(image) + # return results refiner_args = set_pipeline_args( model=shared.sd_refiner, prompts=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts[i], diff --git a/modules/taesd/sd_vae_taesd.py b/modules/taesd/sd_vae_taesd.py index 179416624..d50f533c3 100644 --- a/modules/taesd/sd_vae_taesd.py +++ b/modules/taesd/sd_vae_taesd.py @@ -61,3 +61,22 @@ def decode(latents): enc = latents.unsqueeze(0).to(devices.device, devices.dtype_vae) image = vae.decoder(enc).clamp(0, 1).detach() return image[0] + +def encode(image): + from modules import shared + model_class = shared.sd_model_type + if model_class == 'ldm': + model_class = 'sd' + if 'sd' not in model_class: + shared.log.warning(f'TAESD unsupported model type: {model_class}') + return Image.new('RGB', (8, 8), color = (0, 0, 0)) + vae = taesd_models[f'{model_class}-encoder'] + if vae is None: + model_path = os.path.join(paths_internal.models_path, "TAESD", f"tae{model_class}_encoder.pth") + download_model(model_path) + if os.path.exists(model_path): + taesd_models[f'{model_class}-encoder'] = TAESD(encoder_path=model_path, decoder_path=None) + vae = taesd_models[f'{model_class}-encoder'] + vae.to(devices.device, devices.dtype_vae) + latents = vae.encoder(image).detach() + return latents diff --git a/modules/ui.py b/modules/ui.py index dd6fb198a..fdac4969b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -466,13 +466,14 @@ def create_ui(startup_timer = None): txt2img_prompt.submit(**txt2img_args) submit.click(**txt2img_args) - def enable_hr_change(visible: bool): - return {"visible": visible, "__type__": "update"}, f'Refiner: {"disabled" if modules.shared.opts.sd_model_refiner == "None" else "enabled"}' + def enable_hr_change(visible: bool, refiner_start): + enabled = modules.shared.opts.sd_model_refiner != "None" and refiner_start > 0 and refiner_start < 1 + return {"visible": visible, "__type__": "update"}, f'Refiner: {"enabled" if enabled else "disabled"}' res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False) batch_switch_btn.click(lambda w, h: (h, w), inputs=[batch_count, batch_size], outputs=[batch_count, batch_size], show_progress=False) txt_prompt_img.change(fn=modules.images.image_data, inputs=[txt_prompt_img], outputs=[txt2img_prompt, txt_prompt_img]) - show_second_pass.change(enable_hr_change, inputs=[show_second_pass], outputs=[second_pass_group, hr_refiner], show_progress = False) + show_second_pass.change(enable_hr_change, inputs=[show_second_pass, refiner_start], outputs=[second_pass_group, hr_refiner], show_progress = False) show_seed.change(gr_show, inputs=[show_seed], outputs=[seed_group], show_progress = False) show_batch.change(gr_show, inputs=[show_batch], outputs=[batch_group], show_progress = False) show_advanced.change(gr_show, inputs=[show_advanced], outputs=[advanced_group], show_progress = False) diff --git a/webui.py b/webui.py index f4496d169..5a1699abe 100644 --- a/webui.py +++ b/webui.py @@ -11,9 +11,8 @@ from threading import Thread import modules.loader import torch # pylint: disable=wrong-import-order from modules import timer, errors, paths # pylint: disable=unused-import - local_url = None -from installer import log, git_commit +from installer import log, git_commit, custom_excepthook import ldm.modules.encoders.modules # pylint: disable=W0611,C0411,E0401 from modules import shared, extensions, extra_networks, ui_tempdir, ui_extra_networks, modelloader # pylint: disable=ungrouped-imports from modules.paths import create_paths @@ -39,6 +38,8 @@ from modules.shared import cmd_opts, opts import modules.hypernetworks.hypernetwork from modules.middleware import setup_middleware + +sys.excepthook = custom_excepthook state = shared.state if not modules.loader.initialized: timer.startup.record("libraries") @@ -63,6 +64,7 @@ fastapi_args = { } modules.loader.initialized = True + def check_rollback_vae(): if shared.cmd_opts.rollback_vae: if not torch.cuda.is_available(): diff --git a/wiki b/wiki index d22dc3427..fea51bf38 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit d22dc342708db363e15063ab88e5e0e404aa22fd +Subproject commit fea51bf38c010520dbf30fb8cb58043f94fb2e8e From 4d94beabe9707e1e089b52e9cef28b66db3d87fc Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 12 Sep 2023 14:56:09 -0400 Subject: [PATCH 43/64] add callback --- modules/script_callbacks.py | 19 +++++++++++++++++++ modules/ui_common.py | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 29cd9470a..28b906aaf 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -98,6 +98,7 @@ callback_map = dict( callbacks_ui_settings=[], callbacks_before_image_saved=[], callbacks_image_saved=[], + callbacks_image_save_btn=[], callbacks_cfg_denoiser=[], callbacks_cfg_denoised=[], callbacks_cfg_after_cfg=[], @@ -205,6 +206,16 @@ def image_saved_callback(params: ImageSaveParams): report_exception(e, c, 'image_saved_callback') +def image_save_btn_callback(filename: str): + for c in callback_map['callbacks_image_save_btn']: + try: + t0 = time.time() + c.callback(filename) + timer(t0, c.script, 'image_save_btn') + except Exception as e: + report_exception(e, c, 'image_save_btn_callback') + + def cfg_denoiser_callback(params: CFGDenoiserParams): for c in callback_map['callbacks_cfg_denoiser']: try: @@ -376,6 +387,14 @@ def on_image_saved(callback): add_callback(callback_map['callbacks_image_saved'], callback) +def on_image_save_btn(callback): + """register a function to be called after an image save button is pressed. + The callback is called with one argument: + - params: ImageSaveParams - parameters the image was saved with. Changing fields in this object does nothing. + """ + add_callback(callback_map['callbacks_image_save_btn'], callback) + + def on_cfg_denoiser(callback): """register a function to be called in the kdiffussion cfg_denoiser method after building the inner model inputs. The callback is called with one argument: diff --git a/modules/ui_common.py b/modules/ui_common.py index 58ea9f8f8..dd4d2225e 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -9,6 +9,7 @@ from modules import call_queue, shared from modules.generation_parameters_copypaste import image_from_url_text import modules.ui_symbols as symbols import modules.images +import modules.script_callbacks def update_generation_info(generation_info, html_info, img_index): @@ -115,6 +116,8 @@ def save_files(js_data, images, html_info, index): os.makedirs(destination, exist_ok = True) shutil.copy(fullfn, destination) shared.log.info(f"Copying image: {fullfn} -> {destination}") + tgt_filename = os.path.join(destination, os.path.basename(fullfn)) + modules.script_callbacks.image_save_btn_callback(tgt_filename) else: image = image_from_url_text(filedata) info = p.infotexts[i + 1] if len(p.infotexts) > len(p.all_seeds) else p.infotexts[i] # infotexts may be offset by 1 because the first image is the grid @@ -127,6 +130,7 @@ def save_files(js_data, images, html_info, index): if txt_fullfn: filenames.append(os.path.basename(txt_fullfn)) fullfns.append(txt_fullfn) + modules.script_callbacks.image_save_btn_callback(filename) if shared.opts.samples_save_zip and len(fullfns) > 1: zip_filepath = os.path.join(shared.opts.outdir_save, "images.zip") from zipfile import ZipFile From 5142b2ab301ebc2a8f9faa5130a18ba334fe4551 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 12 Sep 2023 15:51:55 -0400 Subject: [PATCH 44/64] catch empty processing object --- modules/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/images.py b/modules/images.py index 317ee815a..49f83669f 100644 --- a/modules/images.py +++ b/modules/images.py @@ -291,7 +291,7 @@ def sanitize_filename_part(text, replace_spaces=True): class FilenameGenerator: replacements = { - 'batch_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.batch_size == 1 else self.p.batch_index + 1, + 'batch_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p is None or self.p.batch_size == 1 else self.p.batch_index + 1, 'cfg': lambda self: self.p and self.p.cfg_scale, 'clip_skip': lambda self: self.p and self.p.clip_skip, 'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'), From c3cbb6a48ba4d1019dc69801fd5fbe85c004a64e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 12 Sep 2023 17:45:31 -0400 Subject: [PATCH 45/64] error handling on invalid metadata --- modules/sd_models.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 45bce3d0d..8cf20af56 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -362,11 +362,14 @@ def read_metadata_from_safetensors(filename): if large and k == 'ss_bucket_info': continue if v[0:1] == '{': - v = json.loads(v) - if large and k == 'ss_tag_frequency': - v = { i: len(j) for i, j in v.items() } - if large and k == 'sd_merge_models': - scrub_dict(v, ['sd_merge_recipe']) + try: + v = json.loads(v) + if large and k == 'ss_tag_frequency': + v = { i: len(j) for i, j in v.items() } + if large and k == 'sd_merge_models': + scrub_dict(v, ['sd_merge_recipe']) + except Exception: + pass res[k] = v sd_metadata[filename] = res global sd_metadata_pending # pylint: disable=global-statement From 7439272219c53b2941100831e618386858793eb0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 09:04:55 -0400 Subject: [PATCH 46/64] redesign ui defaults --- html/locale_en.json | 32 ++++++------- javascript/black-teal.css | 8 ++-- javascript/style.css | 5 +- modules/shared.py | 34 +++++++------- modules/styles.py | 2 +- modules/ui.py | 6 +-- modules/ui_extensions.py | 2 +- .../ui_extra_networks_textual_inversion.py | 16 +++++-- modules/ui_loadsave.py | 47 ++++++++++++++----- 9 files changed, 89 insertions(+), 63 deletions(-) diff --git a/html/locale_en.json b/html/locale_en.json index 25a6516b9..2c6e77e49 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -402,22 +402,22 @@ {"id":"","label":"Directory for temporary images; leave empty for default","localized":"","hint":""}, {"id":"","label":"Enable IPEX Optimize for Intel GPUs","localized":"","hint":""}, {"id":"","label":"Cleanup non-default temporary directory when starting webui","localized":"","hint":""}, - {"id":"","label":"Path to directory with stable diffusion checkpoints","localized":"","hint":""}, - {"id":"","label":"Path to directory with stable diffusion diffusers","localized":"","hint":""}, - {"id":"","label":"Path to directory with VAE files","localized":"","hint":""}, - {"id":"","label":"Embeddings directory for textual inversion","localized":"","hint":""}, - {"id":"","label":"Hypernetwork directory","localized":"","hint":""}, - {"id":"","label":"Path to directory with codeformer model file(s)","localized":"","hint":""}, - {"id":"","label":"Path to directory with GFPGAN model file(s)","localized":"","hint":""}, - {"id":"","label":"Path to directory with ESRGAN model file(s)","localized":"","hint":""}, - {"id":"","label":"Path to directory with BSRGAN model file(s)","localized":"","hint":""}, - {"id":"","label":"Path to directory with RealESRGAN model file(s)","localized":"","hint":""}, - {"id":"","label":"Path to directory with ScuNET model file(s)","localized":"","hint":""}, - {"id":"","label":"Path to directory with SwinIR model file(s)","localized":"","hint":""}, - {"id":"","label":"Path to directory with LDSR model file(s)","localized":"","hint":""}, - {"id":"","label":"Path to directory with CLIP model file(s)","localized":"","hint":""}, - {"id":"","label":"Path to directory with Lora network(s)","localized":"","hint":""}, - {"id":"","label":"Path to directory with LyCORIS network(s)","localized":"","hint":""}, + {"id":"","label":"Folder with stable diffusion models","localized":"","hint":""}, + {"id":"","label":"Folder with stable diffusion diffusers","localized":"","hint":""}, + {"id":"","label":"Folder with VAE files","localized":"","hint":""}, + {"id":"","label":"Folder with textual inversion embeddings","localized":"","hint":""}, + {"id":"","label":"Folder with Hypernetwork models","localized":"","hint":""}, + {"id":"","label":"Folder with codeformer Folder","localized":"","hint":""}, + {"id":"","label":"Folder with GFPGAN models","localized":"","hint":""}, + {"id":"","label":"Folder with ESRGAN models","localized":"","hint":""}, + {"id":"","label":"Folder with BSRGAN models","localized":"","hint":""}, + {"id":"","label":"Folder with RealESRGAN models","localized":"","hint":""}, + {"id":"","label":"Folder with ScuNET models","localized":"","hint":""}, + {"id":"","label":"Folder with SwinIR models","localized":"","hint":""}, + {"id":"","label":"Folder with LDSR models","localized":"","hint":""}, + {"id":"","label":"Folder with CLIP models","localized":"","hint":""}, + {"id":"","label":"Folder with Lora networks","localized":"","hint":""}, + {"id":"","label":"Folder with LyCORIS networks","localized":"","hint":""}, {"id":"","label":"Path to user-defined styles file","localized":"","hint":""}, {"id":"","label":"Always save all generated images","localized":"","hint":""}, {"id":"","label":"File format for generated images","localized":"","hint":"Select file format for images"}, diff --git a/javascript/black-teal.css b/javascript/black-teal.css index 1f76b8476..9bbcc4276 100644 --- a/javascript/black-teal.css +++ b/javascript/black-teal.css @@ -81,9 +81,10 @@ svg.feather.feather-image, .feather .feather-image { display: none } .label-wrap { margin: 16px 0px 8px 0px; } .gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; } .gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); } -#tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; } -#tab_extensions table { width: 96vw } -#tab_extensions table thead { background-color: var(--neutral-700); } +#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: none; padding: 0.5em; } +#tab_extensions table, #tab_config table { width: 96vw } +#tab_extensions table thead, #tab_config table thead { background-color: var(--neutral-700); } +#tab_extensions table, #tab_config table { background-color: #222222; } /* automatic style classes */ .progressDiv { border-radius: var(--radius-sm) !important; position: fixed; top: 44px; right: 26px; max-width: 262px; height: 48px; z-index: 99; box-shadow: var(--button-shadow); } @@ -108,7 +109,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } #save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; } #script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; } #settings > div.flex-wrap { width: 15em; } -#tab_extensions table { background-color: #222222; } #txt2img_cfg_scale { min-width: 200px; } #txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; } #txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; } diff --git a/javascript/style.css b/javascript/style.css index 11e6a5a88..396c2d5ed 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -149,7 +149,6 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } .global-popup-close:before { content: "×"; } .global-popup-close{ position: fixed; right: 0.5em; top: 0; cursor: pointer; color: white; font-size: 32pt; } .global-popup-inner{ display: inline-block; margin: auto; padding: 2em; } -.ui-defaults-none{ color: #aaa !important; } /* fullpage image viewer */ @@ -210,8 +209,8 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .context-menu-items a:hover { background: #a55000; } /* extensions */ -#tab_extensions table{ border-collapse: collapse; } -#tab_extensions table td, #tab_extensions table th { border: 1px solid #ccc; padding: 0.25em 0.5em; } +#tab_extensions table, #tab_config table{ border-collapse: collapse; } +#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: 1px solid #ccc; padding: 0.25em 0.5em; } #tab_extensions table input[type="checkbox"] { margin-right: 0.5em; appearance: checkbox; } #tab_extensions button{ max-width: 16em; } #tab_extensions input[disabled="disabled"]{ opacity: 0.5; } diff --git a/modules/shared.py b/modules/shared.py index 277ae33af..f8a2f5d02 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -443,24 +443,24 @@ options_templates.update(options_section(('diffusers', "Diffusers Settings"), { options_templates.update(options_section(('system-paths', "System Paths"), { "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"), "clean_temp_dir_at_start": OptionInfo(True, "Cleanup non-default temporary directory when starting webui"), - "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Path to directory with stable diffusion checkpoints"), - "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Path to directory with stable diffusion diffusers"), - "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Path to directory with VAE files"), + "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Folder with stable diffusion models"), + "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Folder with Hugggingface models"), + "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Folder with VAE files"), "sd_lora": OptionInfo("", "Add LoRA to prompt", gr.Textbox, {"visible": False}), - "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Path to directory with LoRA network(s)"), - "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Path to directory with LyCORIS network(s)"), - "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "Path to user-defined styles file"), - "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Embeddings directory for textual inversion"), - "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Hypernetwork directory"), - "codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Path to directory with codeformer model file(s)"), - "gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Path to directory with GFPGAN model file(s)"), - "esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Path to directory with ESRGAN model file(s)"), - "bsrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'BSRGAN'), "Path to directory with BSRGAN model file(s)"), - "realesrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'RealESRGAN'), "Path to directory with RealESRGAN model file(s)"), - "scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'ScuNET'), "Path to directory with ScuNET model file(s)"), - "swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Path to directory with SwinIR model file(s)"), - "ldsr_models_path": OptionInfo(os.path.join(paths.models_path, 'LDSR'), "Path to directory with LDSR model file(s)"), - "clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Path to directory with CLIP model file(s)"), + "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Folder with LoRA network(s)"), + "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Folder with LyCORIS network(s)"), + "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "File or Folder with user-defined styles"), + "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Folder with textual inversion embeddings"), + "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Folder with Hypernetwork models"), + "codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Folder with codeformer models"), + "gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Folder with GFPGAN models"), + "esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Folder with ESRGAN models"), + "bsrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'BSRGAN'), "Folder with BSRGAN models"), + "realesrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'RealESRGAN'), "Folder with RealESRGAN models"), + "scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'ScuNET'), "Folder with ScuNET models"), + "swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Folder with SwinIR models"), + "ldsr_models_path": OptionInfo(os.path.join(paths.models_path, 'LDSR'), "Folder with LDSR models"), + "clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Folder with CLIP models"), })) options_templates.update(options_section(('saving-images', "Image Options"), { diff --git a/modules/styles.py b/modules/styles.py index efb2bfc42..54815350f 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -71,7 +71,7 @@ class StyleDatabase: self.styles[style["name"]] = Style(style["name"], style.get("prompt", ""), style.get("negative", ""), style.get("extra", ""), fn, style.get("preview", "")) except Exception as e: log.error(f'Failed to load style: file={fn} error={e}') - elif os.path.isdir(fn): + elif os.path.isdir(fn) and not fn.startswith('.'): list_folder(fn) list_folder(self.path) diff --git a/modules/ui.py b/modules/ui.py index fdac4969b..34d777990 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -235,11 +235,11 @@ def create_toprow(is_img2img): with gr.Row(): with gr.Column(scale=80): with gr.Row(): - prompt = gr.Textbox(elem_id=f"{id_part}_prompt", show_label=False, lines=3, placeholder="Prompt", elem_classes=["prompt"]) + prompt = gr.Textbox(elem_id=f"{id_part}_prompt", label="Prompt", show_label=False, lines=3, placeholder="Prompt", elem_classes=["prompt"]) with gr.Row(): with gr.Column(scale=80): with gr.Row(): - negative_prompt = gr.Textbox(elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt", elem_classes=["prompt"]) + negative_prompt = gr.Textbox(elem_id=f"{id_part}_neg_prompt", label="Negative prompt", show_label=False, lines=3, placeholder="Negative prompt", elem_classes=["prompt"]) button_interrogate = None button_deepbooru = None if is_img2img: @@ -1068,7 +1068,7 @@ def create_ui(startup_timer = None): with gr.TabItem("Show all pages", variant='primary', elem_id="settings_show_all_pages"): create_dirty_indicator("show_all_pages", [], interactive=False) - with gr.TabItem("UI Config", id="system_config", elem_id="system_config_tab"): + with gr.TabItem("UI Config", id="system_config", elem_id="tab_config"): loadsave.create_ui() create_dirty_indicator("tab_defaults", [], interactive=False) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index ab6f86724..c789391a1 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -290,7 +290,7 @@ def refresh_extensions_list_from_data(search_text, sort_column): Current version - + """ if len(extensions_list) == 0: update_extension_list() diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py index 51d077855..520c630d1 100644 --- a/modules/ui_extra_networks_textual_inversion.py +++ b/modules/ui_extra_networks_textual_inversion.py @@ -21,12 +21,18 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage): def list_items(self): if sd_models.model_data.sd_model is None: embeddings = [] - for root, _dirs, fns in os.walk(shared.opts.embeddings_dir, followlinks=True): - for fn in fns: - if fn.lower().endswith(".pt") or fn.lower().endswith(".safetensors"): - embedding = Embedding(0, fn) - embedding.filename = os.path.join(root, fn) + + def list_folder(folder): + for filename in os.listdir(folder): + fn = os.path.join(folder, filename) + if os.path.isfile(fn) and (fn.lower().endswith(".pt") or fn.lower().endswith(".safetensors")): + embedding = Embedding(0, os.path.basename(fn)) + embedding.filename = fn embeddings.append(embedding) + elif os.path.isdir(fn) and not fn.startswith('.'): + list_folder(fn) + + list_folder(shared.opts.embeddings_dir) elif shared.backend == shared.Backend.ORIGINAL: embeddings = list(sd_hijack.model_hijack.embedding_db.word_embeddings.values()) elif hasattr(sd_models.model_data.sd_model, 'embedding_db'): diff --git a/modules/ui_loadsave.py b/modules/ui_loadsave.py index b0c647e05..0bd24ff74 100644 --- a/modules/ui_loadsave.py +++ b/modules/ui_loadsave.py @@ -112,7 +112,6 @@ class UiLoadsave: self.write_to_file(self.ui_settings) def iter_changes(self, values): - from modules.shared import log """ given a dictionary with defaults from a file and current values from gradio elements, returns an iterator over tuples of values that are not the same between the file and the current; @@ -138,27 +137,49 @@ class UiLoadsave: continue if (new_value == default_value) and (old_value is None): continue - log.debug(f'Settings: name={name} component={component} old={old_value} default={default_value} new={new_value}') yield name, old_value, new_value, default_value return [] def ui_view(self, *values): - text = [''] - for path, old_value, new_value, default_value in self.iter_changes(values): + text = """ +
VariableUser valueNew valueDefault value
+ + + + + + + + + + + + + + + """ + changed = 0 + for name, old_value, new_value, default_value in self.iter_changes(values): + changed += 1 if old_value is None: - old_value = "None" - text.append(f"") - if len(text) == 1: - text.append("") - text.append("") - return "".join(text) + old_value = "None" + text += f"" + text += "
NameSaved valueNew valueDefault value
{path}{old_value}{new_value}{default_value}
No changes
{name}{old_value}{new_value}{default_value}
" + if changed == 0: + text = '

No changes

' + else: + text = f'

Changed values: {changed}

' + text + return text def ui_apply(self, *values): + from modules.shared import log num_changed = 0 current_ui_settings = self.read_from_file() - for path, _, new_value, _ in self.iter_changes(values): + for name, old_value, new_value, default_value in self.iter_changes(values): + component = self.component_mapping[name] + log.debug(f'Settings: name={name} component={component} old={old_value} default={default_value} new={new_value}') num_changed += 1 - current_ui_settings[path] = new_value + current_ui_settings[name] = new_value if num_changed == 0: return "No changes" self.write_to_file(current_ui_settings) @@ -173,12 +194,12 @@ class UiLoadsave: def create_ui(self): """creates ui elements for editing defaults UI, without adding any logic to them""" - gr.HTML(f"Review changed values and apply them as new user interface defaults
Config file: {self.filename}") with gr.Row(elem_id="config_row"): self.ui_defaults_view = gr.Button(value='View changes', elem_id="ui_defaults_view", variant="secondary") self.ui_defaults_apply = gr.Button(value='Set new defaults', elem_id="ui_defaults_apply", variant="primary") self.ui_defaults_restore = gr.Button(value='Restore system defaults', elem_id="ui_defaults_restore", variant="primary") self.ui_defaults_review = gr.HTML("") + gr.HTML(f"Review changed values and apply them as new user interface defaults

Config file: {self.filename}") def setup_ui(self): """adds logic to elements created with create_ui; all add_block class must be made before this""" From a7755ac6d82416925de90bbc3b33ef3d6342e0de Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 09:16:44 -0400 Subject: [PATCH 47/64] fix slow hypernetworks enum --- modules/hypernetworks/hypernetwork.py | 67 ++++----------------------- 1 file changed, 10 insertions(+), 57 deletions(-) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 85c58cbb4..351e261ea 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -1,5 +1,4 @@ import datetime -import glob import html import os from collections import deque @@ -35,19 +34,14 @@ class HypernetworkModule(torch.nn.Module): def __init__(self, dim, state_dict=None, layer_structure=None, activation_func=None, weight_init='Normal', add_layer_norm=False, activate_output=False, dropout_structure=None): super().__init__() - self.multiplier = 1.0 - assert layer_structure is not None, "layer_structure must not be None" assert layer_structure[0] == 1, "Multiplier Sequence should start with size 1!" assert layer_structure[-1] == 1, "Multiplier Sequence should end with size 1!" - linears = [] for i in range(len(layer_structure) - 1): - # Add a fully-connected layer linears.append(torch.nn.Linear(int(dim * layer_structure[i]), int(dim * layer_structure[i+1]))) - # Add an activation func except last layer if activation_func == "linear" or activation_func is None or (i >= len(layer_structure) - 2 and not activate_output): pass @@ -55,20 +49,16 @@ class HypernetworkModule(torch.nn.Module): linears.append(self.activation_dict[activation_func]()) else: raise RuntimeError(f'hypernetwork uses an unsupported activation function: {activation_func}') - # Add layer normalization if add_layer_norm: linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1]))) - # Everything should be now parsed into dropout structure, and applied here. # Since we only have dropouts after layers, dropout structure should start with 0 and end with 0. if dropout_structure is not None and dropout_structure[i+1] > 0: assert 0 < dropout_structure[i+1] < 1, "Dropout probability should be 0 or float between 0 and 1!" linears.append(torch.nn.Dropout(p=dropout_structure[i+1])) # Code explanation : [1, 2, 1] -> dropout is missing when last_layer_dropout is false. [1, 2, 2, 1] -> [0, 0.3, 0, 0], when its True, [0, 0.3, 0.3, 0]. - self.linear = torch.nn.Sequential(*linears) - if state_dict is not None: self.fix_old_state_dict(state_dict) self.load_state_dict(state_dict) @@ -102,12 +92,10 @@ class HypernetworkModule(torch.nn.Module): 'linear2.bias': 'linear.1.bias', 'linear2.weight': 'linear.1.weight', } - for fr, to in changes.items(): x = state_dict.get(fr, None) if x is None: continue - del state_dict[fr] state_dict[to] = x @@ -162,7 +150,6 @@ class Hypernetwork: self.optimizer_name = None self.optimizer_state_dict = None self.optional_info = None - for size in enable_sizes or []: self.layers[size] = ( HypernetworkModule(size, None, self.layer_structure, self.activation_func, self.weight_init, @@ -210,10 +197,8 @@ class Hypernetwork: def save(self, filename): state_dict = {} optimizer_saved_dict = {} - for k, v in self.layers.items(): state_dict[k] = (v[0].state_dict(), v[1].state_dict()) - state_dict['step'] = self.step state_dict['name'] = self.name state_dict['layer_structure'] = self.layer_structure @@ -227,10 +212,8 @@ class Hypernetwork: state_dict['dropout_structure'] = self.dropout_structure state_dict['last_layer_dropout'] = (self.dropout_structure[-2] != 0) if self.dropout_structure is not None else self.last_layer_dropout state_dict['optional_info'] = self.optional_info if self.optional_info else None - if self.optimizer_name is not None: optimizer_saved_dict['optimizer_name'] = self.optimizer_name - torch.save(state_dict, filename) if shared.opts.save_optimizer_state and self.optimizer_state_dict: optimizer_saved_dict['hash'] = self.shorthash() @@ -241,10 +224,8 @@ class Hypernetwork: self.filename = filename if self.name is None: self.name = os.path.splitext(os.path.basename(filename))[0] - with progress.open(filename, 'rb', description=f'Loading hypernetwork: [cyan]{filename}', auto_refresh=True) as f: state_dict = torch.load(f, map_location='cpu') - self.layer_structure = state_dict.get('layer_structure', [1, 2, 1]) self.optional_info = state_dict.get('optional_info', None) self.activation_func = state_dict.get('activation_func', None) @@ -257,11 +238,9 @@ class Hypernetwork: # Dropout structure should have same length as layer structure, Every digits should be in [0,1), and last digit must be 0. if self.dropout_structure is None: self.dropout_structure = parse_dropout_structure(self.layer_structure, self.use_dropout, self.last_layer_dropout) - if shared.opts.print_hypernet_extra: if self.optional_info is not None: print(f" INFO:\n {self.optional_info}\n") - print(f" Layer structure: {self.layer_structure}") print(f" Activation function: {self.activation_func}") print(f" Weight initialization: {self.weight_init}") @@ -269,9 +248,7 @@ class Hypernetwork: print(f" Dropout usage: {self.use_dropout}" ) print(f" Activate last layer: {self.activate_output}") print(f" Dropout structure: {self.dropout_structure}") - optimizer_saved_dict = torch.load(self.filename + '.optim', map_location='cpu') if os.path.exists(self.filename + '.optim') else {} - if self.shorthash() == optimizer_saved_dict.get('hash', None): self.optimizer_state_dict = optimizer_saved_dict.get('optimizer_state_dict', None) else: @@ -285,7 +262,6 @@ class Hypernetwork: self.optimizer_name = "AdamW" if shared.opts.print_hypernet_extra: print("No saved optimizer exists in checkpoint") - for size, sd in state_dict.items(): if type(size) == int: self.layers[size] = ( @@ -294,7 +270,6 @@ class Hypernetwork: HypernetworkModule(size, sd[1], self.layer_structure, self.activation_func, self.weight_init, self.add_layer_norm, self.activate_output, self.dropout_structure), ) - self.name = state_dict.get('name', self.name) self.step = state_dict.get('step', 0) self.sd_checkpoint = state_dict.get('sd_checkpoint', None) @@ -303,54 +278,49 @@ class Hypernetwork: def shorthash(self): sha256 = hashes.sha256(self.filename, f'hypernet/{self.name}') - return sha256[0:10] if sha256 else None def list_hypernetworks(path): res = {} - for filename in sorted(glob.iglob(os.path.join(path, '**/*.pt'), recursive=True), key=str.lower): - name = os.path.splitext(os.path.basename(filename))[0] - # Prevent a hypothetical "None.pt" from being listed. - if name != "None": - res[name] = filename + def list_folder(folder): + for filename in os.listdir(folder): + fn = os.path.join(folder, filename) + if os.path.isfile(fn) and fn.lower().endswith(".pt"): + name = os.path.splitext(os.path.basename(fn))[0] + res[name] = filename + elif os.path.isdir(fn) and not fn.startswith('.'): + list_folder(fn) + + list_folder(path) return res def load_hypernetwork(name): path = shared.hypernetworks.get(name, None) - if path is None: return None - hypernetwork = Hypernetwork() - try: hypernetwork.load(path) except Exception as e: errors.display(e, f'hypernetwork load: {path}') return None - return hypernetwork def load_hypernetworks(names, multipliers=None): already_loaded = {} - for hypernetwork in shared.loaded_hypernetworks: if hypernetwork.name in names: already_loaded[hypernetwork.name] = hypernetwork - shared.loaded_hypernetworks.clear() - for i, name in enumerate(names): hypernetwork = already_loaded.get(name, None) if hypernetwork is None: hypernetwork = load_hypernetwork(name) - if hypernetwork is None: continue - hypernetwork.set_multiplier(multipliers[i] if multipliers else 1.0) shared.loaded_hypernetworks.append(hypernetwork) @@ -368,14 +338,11 @@ def find_closest_hypernetwork_name(search: str): def apply_single_hypernetwork(hypernetwork, context_k, context_v, layer=None): hypernetwork_layers = (hypernetwork.layers if hypernetwork is not None else {}).get(context_k.shape[2], None) - if hypernetwork_layers is None: return context_k, context_v - if layer is not None: layer.hyper_k = hypernetwork_layers[0] layer.hyper_v = hypernetwork_layers[1] - context_k = devices.cond_cast_unet(hypernetwork_layers[0](devices.cond_cast_float(context_k))) context_v = devices.cond_cast_unet(hypernetwork_layers[1](devices.cond_cast_float(context_v))) return context_k, context_v @@ -386,33 +353,25 @@ def apply_hypernetworks(hypernetworks, context, layer=None): context_v = context for hypernetwork in hypernetworks: context_k, context_v = apply_single_hypernetwork(hypernetwork, context_k, context_v, layer) - return context_k, context_v def attention_CrossAttention_forward(self, x, context=None, mask=None): h = self.heads - q = self.to_q(x) context = default(context, x) - context_k, context_v = apply_hypernetworks(shared.loaded_hypernetworks, context, self) k = self.to_k(context_k) v = self.to_v(context_v) - q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q, k, v)) - sim = einsum('b i d, b j d -> b i j', q, k) * self.scale - if mask is not None: mask = rearrange(mask, 'b ... -> b (...)') max_neg_value = -torch.finfo(sim.dtype).max mask = repeat(mask, 'b j -> (b h) () j', h=h) sim.masked_fill_(~mask, max_neg_value) - # attention, what we cannot get enough of attn = sim.softmax(dim=-1) - out = einsum('b i j, b j d -> b i d', attn, v) out = rearrange(out, '(b h) n d -> b n (h d)', h=h) return self.to_out(out) @@ -421,7 +380,6 @@ def attention_CrossAttention_forward(self, x, context=None, mask=None): def stack_conds(conds): if len(conds) == 1: return torch.stack(conds) - # same as in reconstruct_multicond_batch token_count = max([x.shape[0] for x in conds]) for i in range(len(conds)): @@ -429,7 +387,6 @@ def stack_conds(conds): last_vector = conds[i][-1:] last_vector_repeated = last_vector.repeat([token_count - conds[i].shape[0], 1]) conds[i] = torch.vstack([conds[i], last_vector_repeated]) - return torch.stack(conds) @@ -464,19 +421,15 @@ def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None, # Remove illegal characters from name. name = "".join( x for x in name if (x.isalnum() or x in "._- ")) assert name, "Name cannot be empty!" - fn = os.path.join(shared.opts.hypernetwork_dir, f"{name}.pt") if not overwrite_old: assert not os.path.exists(fn), f"file {fn} already exists" - if type(layer_structure) == str: layer_structure = [float(x.strip()) for x in layer_structure.split(",")] - if use_dropout and dropout_structure and type(dropout_structure) == str: dropout_structure = [float(x.strip()) for x in dropout_structure.split(",")] else: dropout_structure = [0] * len(layer_structure) - hypernet = modules.hypernetworks.hypernetwork.Hypernetwork( name=name, enable_sizes=[int(x) for x in enable_sizes], From 08f594a54bc33e6bac642229f56022b4e151554c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 11:26:28 -0400 Subject: [PATCH 48/64] fix missing paths --- javascript/style.css | 3 +++ javascript/ui.js | 6 +++++ modules/paths.py | 2 +- modules/shared.py | 61 ++++++++++++++++++++++---------------------- modules/ui.py | 8 ++++-- modules/ui_common.py | 14 ++++++++++ 6 files changed, 61 insertions(+), 33 deletions(-) diff --git a/javascript/style.css b/javascript/style.css index 396c2d5ed..d5778e9ed 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -276,6 +276,9 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri .log-monitor { display: none; justify-content: unset !important; overflow: hidden; padding: 0; margin-top: auto; font-family: monospace; font-size: 0.85em; } .log-monitor td, .log-monitor th { padding-left: 1em; } +/* custom component */ +.folder-selector textarea { height: 2em !important; padding: 6px !important; } + /* Workaround for Gradio dropdowns capturing clicks during and after fadeout */ .gradio-dropdown > label > div > div:first-child:not(.showOptions) ~ ul.options { pointer-events: none; } diff --git a/javascript/ui.js b/javascript/ui.js index 47575c695..232bb5eb1 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -378,6 +378,12 @@ function previewTheme() { }); } +async function browseFolder() { + const f = await window.showDirectoryPicker(); + if (f && f.kind === 'directory') return f.name; + return null; +} + async function reconnectUI() { const gallery = gradioApp().getElementById('txt2img_gallery'); if (!gallery) return; diff --git a/modules/paths.py b/modules/paths.py index f4b991f5c..18dea2fb1 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -68,7 +68,7 @@ def create_paths(opts, log=None): fullpath = os.path.join(data_path, tgt) relpath = os.path.relpath(fullpath, script_path) opts.data[folder] = relpath - return + return relpath create_path(data_path) create_path(script_path) diff --git a/modules/shared.py b/modules/shared.py index f8a2f5d02..d98bbf448 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -203,7 +203,7 @@ else: class OptionInfo: - def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, submit=None, comment_before='', comment_after=''): + def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, folder=None, submit=None, comment_before='', comment_after=''): self.default = default self.label = label self.component = component @@ -211,6 +211,7 @@ class OptionInfo: self.onchange = onchange self.section = section self.refresh = refresh + self.folder = folder self.comment_before = comment_before # HTML text that will be added after label in UI self.comment_after = comment_after # HTML text that will be added before label in UI self.submit = submit @@ -441,26 +442,26 @@ options_templates.update(options_section(('diffusers', "Diffusers Settings"), { })) options_templates.update(options_section(('system-paths', "System Paths"), { - "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"), + "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default", folder=True), "clean_temp_dir_at_start": OptionInfo(True, "Cleanup non-default temporary directory when starting webui"), - "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Folder with stable diffusion models"), - "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Folder with Hugggingface models"), - "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Folder with VAE files"), + "ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Folder with stable diffusion models", folder=True), + "diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Folder with Hugggingface models", folder=True), + "vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Folder with VAE files", folder=True), "sd_lora": OptionInfo("", "Add LoRA to prompt", gr.Textbox, {"visible": False}), - "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Folder with LoRA network(s)"), - "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Folder with LyCORIS network(s)"), - "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "File or Folder with user-defined styles"), - "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Folder with textual inversion embeddings"), - "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Folder with Hypernetwork models"), - "codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Folder with codeformer models"), - "gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Folder with GFPGAN models"), - "esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Folder with ESRGAN models"), - "bsrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'BSRGAN'), "Folder with BSRGAN models"), - "realesrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'RealESRGAN'), "Folder with RealESRGAN models"), - "scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'ScuNET'), "Folder with ScuNET models"), - "swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Folder with SwinIR models"), - "ldsr_models_path": OptionInfo(os.path.join(paths.models_path, 'LDSR'), "Folder with LDSR models"), - "clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Folder with CLIP models"), + "lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Folder with LoRA network(s)", folder=True), + "lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Folder with LyCORIS network(s)", folder=True), + "styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "File or Folder with user-defined styles", folder=True), + "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Folder with textual inversion embeddings", folder=True), + "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Folder with Hypernetwork models", folder=True), + "codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Folder with codeformer models", folder=True), + "gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Folder with GFPGAN models", folder=True), + "esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Folder with ESRGAN models", folder=True), + "bsrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'BSRGAN'), "Folder with BSRGAN models", folder=True), + "realesrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'RealESRGAN'), "Folder with RealESRGAN models", folder=True), + "scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'ScuNET'), "Folder with ScuNET models", folder=True), + "swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Folder with SwinIR models", folder=True), + "ldsr_models_path": OptionInfo(os.path.join(paths.models_path, 'LDSR'), "Folder with LDSR models", folder=True), + "clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Folder with CLIP models", folder=True), })) options_templates.update(options_section(('saving-images', "Image Options"), { @@ -506,19 +507,19 @@ options_templates.update(options_section(('saving-paths', "Image Naming & Paths" "use_save_to_dirs_for_ui": OptionInfo(False, "Save images to a subdirectory when using Save button"), "directories_filename_pattern": OptionInfo("[date]", "Directory name pattern", component_args=hide_dirs), "directories_max_prompt_words": OptionInfo(8, "Max prompt words for [prompt_words] pattern", gr.Slider, {"minimum": 1, "maximum": 99, "step": 1, **hide_dirs}), - "outdir_samples": OptionInfo("", "Output directory for images", component_args=hide_dirs), - "outdir_txt2img_samples": OptionInfo("outputs/text", 'Output directory for txt2img images', component_args=hide_dirs), - "outdir_img2img_samples": OptionInfo("outputs/image", 'Output directory for img2img images', component_args=hide_dirs), - "outdir_extras_samples": OptionInfo("outputs/extras", 'Output directory for images from extras tab', component_args=hide_dirs), - "outdir_save": OptionInfo("outputs/save", "Directory for saving images using the Save button", component_args=hide_dirs), - "outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs), + "outdir_samples": OptionInfo("", "Output directory for images", component_args=hide_dirs, folder=True), + "outdir_txt2img_samples": OptionInfo("outputs/text", 'Output directory for txt2img images', component_args=hide_dirs, folder=True), + "outdir_img2img_samples": OptionInfo("outputs/image", 'Output directory for img2img images', component_args=hide_dirs, folder=True), + "outdir_extras_samples": OptionInfo("outputs/extras", 'Output directory for images from extras tab', component_args=hide_dirs, folder=True), + "outdir_save": OptionInfo("outputs/save", "Directory for saving images using the Save button", component_args=hide_dirs, folder=True), + "outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs, folder=True), "outdir_sep_grids": OptionInfo("

Grids

", "", gr.HTML), "grid_extended_filename": OptionInfo(True, "Add extended info (seed, prompt) to filename when saving grid"), "grid_save_to_dirs": OptionInfo(False, "Save grids to a subdirectory"), - "outdir_grids": OptionInfo("", "Output directory for grids", component_args=hide_dirs), - "outdir_txt2img_grids": OptionInfo("outputs/grids", 'Output directory for txt2img grids', component_args=hide_dirs), - "outdir_img2img_grids": OptionInfo("outputs/grids", 'Output directory for img2img grids', component_args=hide_dirs), + "outdir_grids": OptionInfo("", "Output directory for grids", component_args=hide_dirs, folder=True), + "outdir_txt2img_grids": OptionInfo("outputs/grids", 'Output directory for txt2img grids', component_args=hide_dirs, folder=True), + "outdir_img2img_grids": OptionInfo("outputs/grids", 'Output directory for img2img grids', component_args=hide_dirs, folder=True), })) @@ -545,7 +546,7 @@ options_templates.update(options_section(('live-preview', "Live Previews"), { "live_previews_enable": OptionInfo(True, "Show live previews of the created image"), "show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"), "notification_audio_enable": OptionInfo(False, "Play a sound when images are finished generating"), - "notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs), + "notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs, folder=True), "show_progress_every_n_steps": OptionInfo(1, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), "show_progress_type": OptionInfo("Approximate NN", "Live preview method", gr.Radio, {"choices": ["Full VAE", "Approximate NN", "Approximate simple", "TAESD"]}), "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), @@ -620,7 +621,7 @@ options_templates.update(options_section(('training', "Training"), { "save_training_settings_to_txt": OptionInfo(True, "Save training settings to a text file on training start"), "dataset_filename_word_regex": OptionInfo("", "Filename word regex"), "dataset_filename_join_string": OptionInfo(" ", "Filename join string"), - "embeddings_templates_dir": OptionInfo(os.path.join(paths.script_path, 'train', 'templates'), "Embeddings train templates directory"), + "embeddings_templates_dir": OptionInfo(os.path.join(paths.script_path, 'train', 'templates'), "Embeddings train templates directory", folder=True), "training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch", gr.Number, {"precision": 0}), "training_write_csv_every": OptionInfo(0, "Save CSV file containing the loss to log directory"), "training_enable_tensorboard": OptionInfo(False, "Enable tensorboard logging"), diff --git a/modules/ui.py b/modules/ui.py index 34d777990..d4c3ec908 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -929,11 +929,15 @@ def create_ui(startup_timer = None): if info.refresh is not None: if is_quicksettings: res = comp(label=info.label, value=fun(), elem_id=elem_id, **args) - create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}") + ui_common.create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}") else: with FormRow(): res = comp(label=info.label, value=fun(), elem_id=elem_id, **args) - create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}") + ui_common.create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}") + elif info.folder is not None: + with FormRow(): + res = comp(label=info.label, value=fun(), elem_id=elem_id, elem_classes="folder-selector", **args) + ui_common.create_browse_button(res, f"folder_{key}") else: try: res = comp(label=info.label, value=fun(), elem_id=elem_id, **args) diff --git a/modules/ui_common.py b/modules/ui_common.py index dd4d2225e..b4f31734c 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -229,3 +229,17 @@ def create_refresh_button(refresh_component, refresh_method, refreshed_args, ele refresh_button = ToolButton(value=symbols.refresh, elem_id=elem_id) refresh_button.click(fn=refresh, inputs=[], outputs=[refresh_component]) return refresh_button + +def create_browse_button(browse_component, elem_id): + + def browse(folder): + # import subprocess + if folder is not None: + return gr.update(value = folder) + return gr.update() + + from modules.ui_components import ToolButton + browse_button = ToolButton(value=symbols.folder, elem_id=elem_id) + browse_button.click(fn=browse, _js="async () => await browseFolder()", inputs=[browse_component], outputs=[browse_component]) + # browse_button.click(fn=browse, inputs=[browse_component], outputs=[browse_component]) + return browse_button From 76c444fbc8bddc795859d0910ecfe89aa9caeb08 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 11:48:13 -0400 Subject: [PATCH 49/64] cleanup --- modules/devices.py | 6 +++--- modules/sd_hijack_clip.py | 2 +- modules/sd_models_config.py | 3 --- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index 011ef9cc0..c08b88a30 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -37,21 +37,21 @@ def get_gpu_info(): try: if torch.version.cuda: return { - 'device': f'{torch.cuda.get_device_name(torch.cuda.current_device())} ({str(torch.cuda.device_count())}) ({torch.cuda.get_arch_list()[-1]}) {str(torch.cuda.get_device_capability(device))}', + 'device': f'{torch.cuda.get_device_name(torch.cuda.current_device())} n={torch.cuda.device_count()} arch={torch.cuda.get_arch_list()[-1]} cap={torch.cuda.get_device_capability(device)}', 'cuda': torch.version.cuda, 'cudnn': torch.backends.cudnn.version(), 'driver': get_driver(), } elif torch.version.hip: return { - 'device': f'{torch.cuda.get_device_name(torch.cuda.current_device())} ({str(torch.cuda.device_count())})', + 'device': f'{torch.cuda.get_device_name(torch.cuda.current_device())} n={torch.cuda.device_count()}', 'hip': torch.version.hip, } else: try: import intel_extension_for_pytorch as ipex# pylint: disable=import-error, unused-import return { - 'device': f'{torch.xpu.get_device_name(torch.xpu.current_device())} ({str(torch.xpu.device_count())})', + 'device': f'{torch.xpu.get_device_name(torch.xpu.current_device())} n={torch.xpu.device_count()}', 'ipex': ipex.__version__, } except Exception: diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 045833de4..14510a689 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -179,7 +179,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): used_embeddings[embedding.name] = embedding z = self.process_tokens(tokens, multipliers) zs.append(z) - self.hijack.embedding_db.embeddings_used = [name for name in used_embeddings.keys()] + self.hijack.embedding_db.embeddings_used = list(used_embeddings) return torch.hstack(zs) def process_tokens(self, remade_batch_tokens, batch_multipliers): diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index 40a0ed638..163390ca6 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -21,12 +21,9 @@ def is_using_v_parameterization_for_sd2(state_dict): """ Detects whether unet in state_dict is using v-parameterization. Returns True if it is. You're welcome. """ - import ldm.modules.diffusionmodules.openaimodel - from modules import devices device = devices.cpu - with sd_disable_initialization.DisableInitialization(): unet = ldm.modules.diffusionmodules.openaimodel.UNetModel( use_checkpoint=True, From 336bc0de4073dfe3b6dff40806d884e37f42a83e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 13:01:10 -0400 Subject: [PATCH 50/64] re-layout of main settings --- CHANGELOG.md | 9 +++++---- javascript/style.css | 7 ++++++- modules/scripts.py | 30 +++++++++++++++--------------- modules/ui.py | 3 +-- scripts/prompt_matrix.py | 2 +- scripts/prompts_from_file.py | 2 +- scripts/xyz_grid.py | 2 +- 7 files changed, 30 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ced0fa4ed..d209e13a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Change Log for SD.Next -## Update for 2023-09-12 +## Update for 2023-09-13 Mostly a service release, but with some changes in behavior, especially in HiRes area of the code... - tons of fixes -- changes to **hires** +- changes to **hires** - enable non-latent upscale modes (standard upscalers) - when using latent upscale, hires pass is run automatically - when using non-latent upscalers, hires pass is skipped by default @@ -18,8 +18,9 @@ Mostly a service release, but with some changes in behavior, especially in HiRes - all combinations of: decode full/quick + upscale none/latent/non-latent + hires on/off + refiner on/off should be supported, but given the number of combinations, issues are possible - all operations are captured in image medata -- update **ui hints** -- updated **models -> civitai** +- minor re-layout of the main ui +- update **ui hints** +- updated **models -> civitai** - search and download loras - find previews for already downloaded models or loras - new option **inference mode** diff --git a/javascript/style.css b/javascript/style.css index d5778e9ed..1dbcea1b7 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -25,6 +25,7 @@ div.gradio-html.min{ min-height: 0; } .gradio-dropdown.multiselect div.wrap-inner { overflow-x: hidden; overflow-y: auto; max-height: 50vh; overflow-wrap: anywhere; } .gradio-html div.wrap{ height: 100%; } .gradio-slider input[type="number"]{ width: 6em; margin-left: 0.5em; } +.gradio-accordion { padding-top: var(--spacing-md) !important; padding-right: 0 !important; padding-bottom: 0 !important; color: var(--body-text-color); } .hidden { display: none; } footer { display: none; } td { border-bottom: none !important; } @@ -265,7 +266,11 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri /* specific elements */ #modelmerger_interp_description { margin-top: 1em; margin-bottom: 1em; } -#scripts_alwayson_txt2img, #scripts_alwayson_img2img { display: grid } +#scripts_alwayson_txt2img, #scripts_alwayson_img2img { display: grid; padding: 0 } +#scripts_alwayson_txt2img > .label-wrap, #scripts_alwayson_img2img > .label-wrap { background: var(--input-background-fill); padding: 0; margin: 0; border-radius: var(--radius-lg); } +#scripts_alwayson_txt2img > .label-wrap > span, #scripts_alwayson_img2img > .label-wrap > span { padding: var(--spacing-xxl); } +#script_txt2img_agent_scheduler { display: none; } + #extras_generate, #extras_interrupt, #extras_skip { display: block !important; position: relative; height: 36px; } #extras_upscale { margin-top: 10px } #refresh_tac_refreshTempFiles { display: none; } diff --git a/modules/scripts.py b/modules/scripts.py index 33111a3d3..b81ce2aa1 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -343,7 +343,7 @@ class ScriptRunner: def setup_ui(self): import modules.api.models as api_models self.titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.selectable_scripts] - inputs = [None] + inputs = [] inputs_alwayson = [True] def create_script_ui(script, inputs, inputs_alwayson): @@ -377,38 +377,28 @@ class ScriptRunner: inputs_alwayson += [script.alwayson for _ in controls] script.args_to = len(inputs) - with gr.Group(elem_id='scripts_alwayson_img2img' if self.is_img2img else 'scripts_alwayson_txt2img'): - for script in self.alwayson_scripts: - t0 = time.time() - elem_id = f'script_{"txt2img" if script.is_txt2img else "img2img"}_{script.title().lower().replace(" ", "_")}' - with gr.Group(elem_id=elem_id) as group: - create_script_ui(script, inputs, inputs_alwayson) - script.group = group - time_setup[script.title()] = time_setup.get(script.title(), 0) + (time.time()-t0) - dropdown = gr.Dropdown(label="Script", elem_id="script_list", choices=["None"] + self.titles, value="None", type="index") - inputs[0] = dropdown + inputs.insert(0, dropdown) for script in self.selectable_scripts: with gr.Group(visible=False) as group: t0 = time.time() create_script_ui(script, inputs, inputs_alwayson) time_setup[script.title()] = time_setup.get(script.title(), 0) + (time.time()-t0) - script.group = group + script.group = group def select_script(script_index): selected_script = self.selectable_scripts[script_index - 1] if script_index > 0 else None return [gr.update(visible=selected_script == s) for s in self.selectable_scripts] def init_field(title): - """called when an initial value is set from ui-config.json to show script's UI components""" - if title == 'None': + if title == 'None': # called when an initial value is set from ui-config.json to show script's UI components return script_index = self.titles.index(title) self.selectable_scripts[script_index].group.visible = True dropdown.init_field = init_field dropdown.change(fn=select_script, inputs=[dropdown], outputs=[script.group for script in self.selectable_scripts]) - + def onload_script_visibility(params): title = params.get('Script', None) if title: @@ -419,6 +409,16 @@ class ScriptRunner: else: return gr.update(visible=False) + # with gr.Group(elem_id='scripts_alwayson_img2img' if self.is_img2img else 'scripts_alwayson_txt2img'): + with gr.Accordion(label="Extensions", elem_id='scripts_alwayson_img2img' if self.is_img2img else 'scripts_alwayson_txt2img'): + for script in self.alwayson_scripts: + t0 = time.time() + elem_id = f'script_{"txt2img" if script.is_txt2img else "img2img"}_{script.title().lower().replace(" ", "_")}' + with gr.Group(elem_id=elem_id) as group: + create_script_ui(script, inputs, inputs_alwayson) + script.group = group + time_setup[script.title()] = time_setup.get(script.title(), 0) + (time.time()-t0) + self.infotext_fields.append( (dropdown, lambda x: gr.update(value=x.get('Script', 'None'))) ) self.infotext_fields.extend( [(script.group, onload_script_visibility) for script in self.selectable_scripts] ) return inputs diff --git a/modules/ui.py b/modules/ui.py index d4c3ec908..6d86fdb84 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -417,8 +417,7 @@ def create_ui(startup_timer = None): with FormRow(elem_id="txt2img_override_settings_row") as row: override_settings = create_override_settings_dropdown('txt2img', row) - with FormGroup(elem_id="txt2img_script_container"): - custom_inputs = modules.scripts.scripts_txt2img.setup_ui() + custom_inputs = modules.scripts.scripts_txt2img.setup_ui() hr_resolution_preview_inputs = [show_second_pass, width, height, hr_scale, hr_resize_x, hr_resize_y, hr_upscaler] for preview_input in hr_resolution_preview_inputs: diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index 3d8d682db..4efd0dfbe 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -39,7 +39,7 @@ def draw_xy_grid(xs, ys, x_label, y_label, cell): class Script(scripts.Script): def title(self): - return "Prompt matrix" + return "Prompt Matrix" def ui(self, is_img2img): gr.HTML('
') diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 6c2008043..f53186f3f 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -101,7 +101,7 @@ def load_prompt_file(file): class Script(scripts.Script): def title(self): - return "Prompts from file" + return "Prompts from File" def ui(self, is_img2img): checkbox_iterate = gr.Checkbox(label="Iterate seed every line", value=False, elem_id=self.elem_id("checkbox_iterate")) diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 31307d431..4168d6790 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -375,7 +375,7 @@ re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+ class Script(scripts.Script): def title(self): - return "X/Y/Z grid" + return "X/Y/Z Grid" def ui(self, is_img2img): self.current_axis_options = [x for x in axis_options if type(x) == AxisOption or x.is_img2img == is_img2img] From 80051ce3653af3c9e01c49120d43f85db84e29a0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 15:29:37 -0400 Subject: [PATCH 51/64] hide refiner if not working in diffusers --- javascript/style.css | 2 +- modules/sd_models.py | 8 +++++++- modules/ui.py | 49 ++++++++++++++++++++++---------------------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/javascript/style.css b/javascript/style.css index 1dbcea1b7..75cb34361 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -76,7 +76,7 @@ button.custom-button{ #txt2img_generate_line2, #img2img_generate_line2 { display: flex; } #txt2img_generate_line2 > button, #img2img_generate_line2 > button, #extras_generate_box > button { height: 2.2em; line-height: 0; min-width: unset; display: block !important; } #txt2img_tools > div, #img2img_tools > div { justify-content: space-around; margin-top: 0.5em; margin-bottom: 0em; } -#txt2img_tools > div > button, #img2img_tools > div > button { scale: 120%; } +#txt2img_tools > div > button, #img2img_tools > div > button { scale: 120%; min-width: 1em !important; min-height: 1em !important; } #txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { display: contents; } .interrogate-col{ min-width: 0 !important; max-width: fit-content; gap: 0.5em; } .interrogate-col > button{ flex: 1; } diff --git a/modules/sd_models.py b/modules/sd_models.py index 8cf20af56..2147e31db 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -736,7 +736,13 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No try: if model_type.startswith('Stable Diffusion'): diffusers_load_config['force_zeros_for_empty_prompt '] = shared.opts.diffusers_force_zeros - diffusers_load_config['requires_aesthetics_score '] = shared.opts.diffusers_aesthetics_score + diffusers_load_config['requires_aesthetics_score'] = shared.opts.diffusers_aesthetics_score + diffusers_load_config['config_files'] = { + 'v1': 'configs/v1-inference.yaml', + 'v2': 'configs/v2-inference-768-v.yaml', + 'xl': 'configs/sd_xl_base.yaml', + 'xl_refiner': 'configs/sd_xl_refiner.yaml', + } if hasattr(pipeline, 'from_single_file'): diffusers_load_config['use_safetensors'] = True sd_model = pipeline.from_single_file(checkpoint_info.path, **diffusers_load_config) diff --git a/modules/ui.py b/modules/ui.py index 6d86fdb84..c1154d9d9 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -389,30 +389,31 @@ def create_ui(startup_timer = None): tiling = gr.Checkbox(label='Tiling', value=False, elem_id="txt2img_tiling") with FormGroup(visible=show_second_pass.value, elem_id="txt2img_second_pass") as second_pass_group: - with FormRow(elem_id="sampler_selection_txt2img_alt_row1"): - latent_index = gr.Dropdown(label='Secondary sampler', elem_id="txt2img_sampling_alt", choices=[x.name for x in modules.sd_samplers.samplers], value='Default', type="index") - denoising_strength = gr.Slider(minimum=0.05, maximum=1.0, step=0.01, label='Denoising strength', value=0.5, elem_id="txt2img_denoising_strength") - with FormRow(elem_id="txt2img_hires_finalres", variant="compact"): - hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) - with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"): - hr_upscaler = gr.Dropdown(label="Upscaler", elem_id="txt2img_hr_upscaler", choices=[*modules.shared.latent_upscale_modes, *[x.name for x in modules.shared.sd_upscalers]], value=modules.shared.latent_upscale_default_mode) - hr_force = gr.Checkbox(label='Force Hires', value=False, elem_id="txt2img_hr_force") - with FormRow(elem_id="txt2img_hires_fix_row2", variant="compact"): - hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id="txt2img_steps_alt", value=20) - hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale") - with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact"): - hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x") - hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y") - - with FormRow(): - hr_refiner = FormHTML(value="Refiner", elem_id="txtimg_hr_refiner", interactive=False) - with FormRow(elem_id="txt2img_refiner_row1", variant="compact"): - refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Refiner start', value=0.8, elem_id="txt2img_refiner_start") - refiner_steps = gr.Slider(minimum=0, maximum=99, step=1, label="Refiner steps", elem_id="txt2img_refiner_steps", value=5) - with FormRow(elem_id="txt2img_refiner_row3", variant="compact"): - refiner_prompt = gr.Textbox(value='', label='Secondary Prompt') - with FormRow(elem_id="txt2img_refiner_row4", variant="compact"): - refiner_negative = gr.Textbox(value='', label='Secondary negative prompt') + with FormGroup(): + with FormRow(elem_id="sampler_selection_txt2img_alt_row1"): + latent_index = gr.Dropdown(label='Secondary sampler', elem_id="txt2img_sampling_alt", choices=[x.name for x in modules.sd_samplers.samplers], value='Default', type="index") + denoising_strength = gr.Slider(minimum=0.05, maximum=1.0, step=0.01, label='Denoising strength', value=0.5, elem_id="txt2img_denoising_strength") + with FormRow(elem_id="txt2img_hires_finalres", variant="compact"): + hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False) + with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"): + hr_upscaler = gr.Dropdown(label="Upscaler", elem_id="txt2img_hr_upscaler", choices=[*modules.shared.latent_upscale_modes, *[x.name for x in modules.shared.sd_upscalers]], value=modules.shared.latent_upscale_default_mode) + hr_force = gr.Checkbox(label='Force Hires', value=False, elem_id="txt2img_hr_force") + with FormRow(elem_id="txt2img_hires_fix_row2", variant="compact"): + hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id="txt2img_steps_alt", value=20) + hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale") + with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact"): + hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x") + hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y") + with FormGroup(visible=modules.shared.backend == modules.shared.Backend.DIFFUSERS): + with FormRow(): + hr_refiner = FormHTML(value="Refiner", elem_id="txtimg_hr_refiner", interactive=False) + with FormRow(elem_id="txt2img_refiner_row1", variant="compact"): + refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Refiner start', value=0.8, elem_id="txt2img_refiner_start") + refiner_steps = gr.Slider(minimum=0, maximum=99, step=1, label="Refiner steps", elem_id="txt2img_refiner_steps", value=5) + with FormRow(elem_id="txt2img_refiner_row3", variant="compact"): + refiner_prompt = gr.Textbox(value='', label='Secondary Prompt') + with FormRow(elem_id="txt2img_refiner_row4", variant="compact"): + refiner_negative = gr.Textbox(value='', label='Secondary negative prompt') with FormRow(elem_id="txt2img_override_settings_row") as row: override_settings = create_override_settings_dropdown('txt2img', row) From 287d46748e77cdb47fb95b828788f2fae16a398b Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 15:44:31 -0400 Subject: [PATCH 52/64] fix firefox styling --- javascript/style.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/style.css b/javascript/style.css index 75cb34361..8baeb1602 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -142,7 +142,7 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } .progressDiv{ position: relative; height: 20px; background: #b4c0cc; margin-bottom: -3px; } .dark .progressDiv{ background: #424c5b; } .progressDiv .progress{ width: 0%; height: 20px; background: #0060df; color: white; font-weight: bold; line-height: 20px; padding: 0 8px 0 0; text-align: right; overflow: visible; white-space: nowrap; padding: 0 0.5em; } -.livePreview { position: absolute; z-index: 300; background-color: transparent; width: -webkit-fill-available; } +.livePreview { position: absolute; z-index: 300; background-color: transparent; width: -moz-available; width: -webkit-fill-available; } .livePreview img { position: absolute; object-fit: contain; width: 100%; height: 100%; } .dark .livePreview { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } .popup-metadata { color: white; background: #0000; display: inline-block; white-space: pre-wrap; font-size: 0.75em; } @@ -225,16 +225,16 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt /* extra networks */ .extra-networks > div { margin: 0; gap: 0.2em; border-bottom: none !important; } -.extra-networks .second-line { display: flex; width: -webkit-fill-available; gap: 0.3em; box-shadow: var(--input-shadow); } +.extra-networks .second-line { display: flex; width: -moz-available; width: -webkit-fill-available; gap: 0.3em; box-shadow: var(--input-shadow); } .extra-networks .search { flex: 1; } .extra-networks .description { flex: 3; } .extra-networks .tab-nav > button { margin-right: 0; height: 24px; padding: 2px 4px 2px 4px; } .extra-networks-tab { padding: 0 !important; } -.extra-network-subdirs { background: var(--input-background-fill); overflow-x: hidden; overflow-y: auto; min-width: 120px; padding-top: 0.5em; } +.extra-network-subdirs { background: var(--input-background-fill); overflow-x: hidden; overflow-y: auto; min-width: 20%; padding-top: 0.5em; } .extra-networks-page { display: flex } .extra-networks .custom-button { width: 120px; width: 100%; background: none; justify-content: left; text-align: left; padding: 2px 8px 2px 16px; text-indent: -8px; box-shadow: none; line-break: auto; } .extra-networks .custom-button:hover { background: var(--button-primary-background-fill) } -.extra-network-cards { display: flex; flex-wrap: wrap; overflow-y: auto; overflow-x: hidden; align-content: flex-start; width: -webkit-fill-available; } +.extra-network-cards { display: flex; flex-wrap: wrap; overflow-y: auto; overflow-x: hidden; align-content: flex-start; width: -moz-available; width: -webkit-fill-available; } .extra-network-cards .card { height: fit-content; margin: 0 0 0.5em 0.5em; position: relative; scroll-snap-align: start; scroll-margin-top: 0; } .extra-network-cards .card .overlay { position: absolute; bottom: 0; padding: 0.2em; z-index: 10; width: 100%; background: none; } .extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); } From af672d10ebd59442018827370585fda79e14db94 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 16:10:29 -0400 Subject: [PATCH 53/64] fix paths with data-dir --- modules/paths.py | 8 +++++--- modules/styles.py | 23 ++++++++++++----------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/modules/paths.py b/modules/paths.py index 18dea2fb1..c0d7cbd11 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -62,10 +62,12 @@ def create_paths(opts, log=None): def fix_path(folder): tgt = opts.data.get(folder, None) or opts.data_labels[folder].default if tgt is None or tgt == '': - return - if os.path.isabs(tgt) or (len(data_path) > 0 and tgt.startswith(data_path)) and not tgt.startswith(script_path): - return + return + if len(data_path) > 0 and tgt.startswith(data_path): # path is already relative to data_path + return tgt fullpath = os.path.join(data_path, tgt) + if len(data_path) > 0 and os.path.isabs(data_path): + return fullpath relpath = os.path.relpath(fullpath, script_path) opts.data[folder] = relpath return relpath diff --git a/modules/styles.py b/modules/styles.py index 54815350f..94052d548 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -41,22 +41,19 @@ class StyleDatabase: def __init__(self, opts): self.no_style = Style("None") self.styles = {} - self.path = opts.styles_dir - if os.path.isfile(opts.styles_dir): + if os.path.isfile(opts.styles_dir) or opts.styles_dir.endswith(".csv"): legacy_file = opts.styles_dir self.load_csv(legacy_file) opts.styles_dir = os.path.join(paths.models_path, "styles") self.path = opts.styles_dir - self.mkdir() + os.makedirs(opts.styles_dir, exist_ok=True) self.save_styles(opts.styles_dir, verbose=True) - log.debug(f'Migrated styles: file={legacy_file} folder={self.path}') + log.debug(f'Migrated styles: file={legacy_file} folder={opts.styles_dir}') self.reload() - self.mkdir() - - def mkdir(self): - if not os.path.isdir(self.path): - os.makedirs(self.path, exist_ok=True) - log.debug(f'Created styles: folder={self.path}') + if not os.path.isdir(opts.styles_dir): + opts.styles_dir = os.path.join(paths.models_path, "styles") + self.path = opts.styles_dir + os.makedirs(opts.styles_dir, exist_ok=True) def reload(self): self.styles.clear() @@ -108,9 +105,13 @@ class StyleDatabase: log.debug(f'Saved style: name={name} file={fn}') except Exception as e: log.error(f'Failed to save style: name={name} file={path} error={e}') - log.debug(f'Saved styles: {path} {len(self.styles.keys())}') + count = len(list(self.styles)) + if count > 0: + log.debug(f'Saved styles: {path} {count}') def load_csv(self, legacy_file): + if not os.path.isfile(legacy_file): + return with open(legacy_file, "r", encoding="utf-8-sig", newline='') as file: reader = csv.DictReader(file, skipinitialspace=True) for row in reader: From a1120666d83787b0015985ca9ebb6ef1880b10d9 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 16:50:03 -0400 Subject: [PATCH 54/64] fix styles and console logging --- modules/hashes.py | 4 ++-- modules/hypernetworks/hypernetwork.py | 2 +- modules/modelloader.py | 4 ++-- modules/models/diffusion/uni_pc/uni_pc.py | 2 +- modules/paths.py | 2 +- modules/script_loading.py | 4 ++-- modules/sd_models.py | 2 +- modules/shared.py | 3 ++- modules/styles.py | 1 + 9 files changed, 13 insertions(+), 11 deletions(-) diff --git a/modules/hashes.py b/modules/hashes.py index 84071bfb5..288a175d5 100644 --- a/modules/hashes.py +++ b/modules/hashes.py @@ -25,7 +25,7 @@ def calculate_sha256(filename, quiet=False): hash_sha256 = hashlib.sha256() blksize = 1024 * 1024 if not quiet: - with progress.open(filename, 'rb', description=f'Calculating model hash: [cyan]{filename}', auto_refresh=True) as f: + with progress.open(filename, 'rb', description=f'Calculating model hash: [cyan]{filename}', auto_refresh=True, console=shared.console) as f: for chunk in iter(lambda: f.read(blksize), b""): hash_sha256.update(chunk) else: @@ -57,7 +57,7 @@ def sha256(filename, title, use_addnet_hash=False): if not os.path.isfile(filename): return None if use_addnet_hash: - with progress.open(filename, 'rb', description=f'Calculating model hash: [cyan]{filename}', auto_refresh=True) as f: + with progress.open(filename, 'rb', description=f'Calculating model hash: [cyan]{filename}', auto_refresh=True, console=shared.console) as f: sha256_value = addnet_hash_safetensors(f) else: sha256_value = calculate_sha256(filename) diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index 351e261ea..d9cc95e86 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -224,7 +224,7 @@ class Hypernetwork: self.filename = filename if self.name is None: self.name = os.path.splitext(os.path.basename(filename))[0] - with progress.open(filename, 'rb', description=f'Loading hypernetwork: [cyan]{filename}', auto_refresh=True) as f: + with progress.open(filename, 'rb', description=f'Loading hypernetwork: [cyan]{filename}', auto_refresh=True, console=shared.console) as f: state_dict = torch.load(f, map_location='cpu') self.layer_structure = state_dict.get('layer_structure', [1, 2, 1]) self.optional_info = state_dict.get('optional_info', None) diff --git a/modules/modelloader.py b/modules/modelloader.py index 40136479d..f9b6048a0 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -70,7 +70,7 @@ def download_civit_preview(model_path: str, preview_url: str): shared.state.begin('civitai-download-preview') try: with open(preview_file, 'wb') as f: - with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn()) as progress: + with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn(), console=shared.console) as progress: task = progress.add_task(description="Download starting", total=total_size) for data in req.iter_content(block_size): written = written + len(data) @@ -110,7 +110,7 @@ def download_civit_model(model_url: str, model_name: str, model_path: str, model shared.state.begin('civitai-download-model') try: with open(model_file, 'wb') as f: - with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn()) as progress: + with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn(), console=shared.console) as progress: task = progress.add_task(description="Download starting", total=total_size) # for data in tqdm(req.iter_content(block_size), total=total_size//1024, unit='KB', unit_scale=False): for data in req.iter_content(block_size): diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index 56604a7b6..fa16a0b48 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -757,7 +757,7 @@ class UniPC: #print(f"Running UniPC Sampling with {timesteps.shape[0]} timesteps, order {order}") assert steps >= order, "UniPC order must be < sampling steps" assert timesteps.shape[0] - 1 == steps - with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn()) as progress: + with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn(), console=shared.console) as progress: task = progress.add_task(description="Initializing", total=steps) t = time.time() with devices.inference_context(): diff --git a/modules/paths.py b/modules/paths.py index c0d7cbd11..9530ff5ba 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -62,7 +62,7 @@ def create_paths(opts, log=None): def fix_path(folder): tgt = opts.data.get(folder, None) or opts.data_labels[folder].default if tgt is None or tgt == '': - return + return tgt if len(data_path) > 0 and tgt.startswith(data_path): # path is already relative to data_path return tgt fullpath = os.path.join(data_path, tgt) diff --git a/modules/script_loading.py b/modules/script_loading.py index 64f16e681..61f08527a 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -13,8 +13,8 @@ def load_module(path): module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path) module = importlib.util.module_from_spec(module_spec) try: - stdout = io.StringIO() - with contextlib.redirect_stdout(stdout): + # stdout = io.StringIO() + with contextlib.redirect_stdout(io.StringIO()) as stdout: module_spec.loader.exec_module(module) setup_logging() # reset since scripts can hijaack logging for line in stdout.getvalue().splitlines(): diff --git a/modules/sd_models.py b/modules/sd_models.py index 2147e31db..8fba59aa1 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -387,7 +387,7 @@ def read_state_dict(checkpoint_file, map_location=None): # pylint: disable=unuse return None try: pl_sd = None - with progress.open(checkpoint_file, 'rb', description=f'[cyan]Loading weights: [yellow]{checkpoint_file}', auto_refresh=True) as f: + with progress.open(checkpoint_file, 'rb', description=f'[cyan]Loading weights: [yellow]{checkpoint_file}', auto_refresh=True, console=shared.console) as f: _, extension = os.path.splitext(checkpoint_file) if extension.lower() == ".ckpt" and shared.opts.sd_disable_ckpt: shared.log.warning(f"Checkpoint loading disabled: {checkpoint_file}") diff --git a/modules/shared.py b/modules/shared.py index d98bbf448..b5fbbe12a 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -11,6 +11,7 @@ from enum import Enum import gradio as gr import tqdm import fasteners +from rich.console import Console from modules import errors, ui_components, shared_items, cmd_args from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611 from modules.dml import memory_providers, default_memory_provider, directml_do_hijack @@ -69,7 +70,7 @@ restricted_opts = { "outdir_init_images" } compatibility_opts = ['clip_skip', 'uni_pc_lower_order_final', 'uni_pc_order'] - +console = Console(log_time=True, log_time_format='%H:%M:%S-%f') def is_url(string): parsed_url = urlparse(string) diff --git a/modules/styles.py b/modules/styles.py index 94052d548..abeeb5e08 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -41,6 +41,7 @@ class StyleDatabase: def __init__(self, opts): self.no_style = Style("None") self.styles = {} + self.path = opts.styles_dir if os.path.isfile(opts.styles_dir) or opts.styles_dir.endswith(".csv"): legacy_file = opts.styles_dir self.load_csv(legacy_file) From 26383c7950b244f4abdb27f29b352576e4574169 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 16:53:26 -0400 Subject: [PATCH 55/64] handle cross-mounting --- modules/paths.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/paths.py b/modules/paths.py index 9530ff5ba..0a7095215 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -68,8 +68,11 @@ def create_paths(opts, log=None): fullpath = os.path.join(data_path, tgt) if len(data_path) > 0 and os.path.isabs(data_path): return fullpath - relpath = os.path.relpath(fullpath, script_path) - opts.data[folder] = relpath + try: + relpath = os.path.relpath(fullpath, script_path) + opts.data[folder] = relpath + except: + opts.data[folder] = fullpath return relpath create_path(data_path) From d4871414ea5cd2bbb87532cedea04b7d9e4d5b94 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 13 Sep 2023 17:26:15 -0400 Subject: [PATCH 56/64] fix fullpath --- modules/paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/paths.py b/modules/paths.py index 0a7095215..a502be0cf 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -73,7 +73,7 @@ def create_paths(opts, log=None): opts.data[folder] = relpath except: opts.data[folder] = fullpath - return relpath + return opts.data[folder] create_path(data_path) create_path(script_path) From c869e9c99225c40d251be6b396222e9930d1d94e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 Sep 2023 08:46:09 -0400 Subject: [PATCH 57/64] fix small grids --- modules/processing.py | 2 +- modules/sd_models.py | 2 +- modules/textual_inversion/autocrop.py | 4 +--- scripts/xyz_grid.py | 8 ++++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index b7bd2af1a..b5e437097 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -858,7 +858,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.color_corrections = None index_of_first_image = 0 - if (shared.opts.return_grid or shared.opts.grid_save) and not p.do_not_save_grid and len(output_images) > 2: + if (shared.opts.return_grid or shared.opts.grid_save) and not p.do_not_save_grid and len(output_images) > 1: if images.check_grid_size(output_images): grid = images.image_grid(output_images, p.batch_size) if shared.opts.return_grid: diff --git a/modules/sd_models.py b/modules/sd_models.py index 8fba59aa1..01292eddc 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -131,7 +131,7 @@ class NoWatermark: def setup_model(): if not os.path.exists(model_path): - os.makedirs(model_path) + os.makedirs(model_path, exist_ok=True) list_models() enable_midas_autodownload() diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py index 31abd782d..bac4e618e 100644 --- a/modules/textual_inversion/autocrop.py +++ b/modules/textual_inversion/autocrop.py @@ -295,10 +295,8 @@ def is_square(w, h): def download_and_cache_models(dirname): download_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true' model_file_name = 'face_detection_yunet.onnx' - if not os.path.exists(dirname): - os.makedirs(dirname) - + os.makedirs(dirname, exist_ok=True) cache_file = os.path.join(dirname, model_file_name) if not os.path.exists(cache_file): print(f"downloading face detection model from '{download_url}' to '{cache_file}'") diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 4168d6790..28c46a999 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -400,10 +400,10 @@ class Script(scripts.Script): fill_z_button = ToolButton(value=symbols.fill, elem_id="xyz_grid_fill_z_tool_button", visible=False) with gr.Row(variant="compact", elem_id="axis_options"): draw_legend = gr.Checkbox(label='Draw legend', value=True, elem_id=self.elem_id("draw_legend")) - no_fixed_seeds = gr.Checkbox(label='Keep random for seeds', value=False, elem_id=self.elem_id("no_fixed_seeds")) - no_grid = gr.Checkbox(label='Do not create grid', value=False, elem_id=self.elem_id("no_xyz_grid")) - include_lone_images = gr.Checkbox(label='Include Sub Images', value=False, elem_id=self.elem_id("include_lone_images")) - include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids")) + no_fixed_seeds = gr.Checkbox(label='Keep random seeds', value=False, elem_id=self.elem_id("no_fixed_seeds")) + no_grid = gr.Checkbox(label='Skip grid', value=False, elem_id=self.elem_id("no_xyz_grid")) + include_lone_images = gr.Checkbox(label='Include sub images', value=False, elem_id=self.elem_id("include_lone_images")) + include_sub_grids = gr.Checkbox(label='Include sub grids', value=False, elem_id=self.elem_id("include_sub_grids")) with gr.Row(variant="compact", elem_id="axis_options"): margin_size = gr.Slider(label="Grid margins", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size")) with gr.Row(variant="compact", elem_id="swap_axes"): From 25133420f4b118847ecefba00ce3db8daab4ea35 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 14 Sep 2023 15:51:35 +0300 Subject: [PATCH 58/64] IPEX hijacks fix diffusers 0.21.1 lazy_import --- modules/intel/ipex/diffusers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/intel/ipex/diffusers.py b/modules/intel/ipex/diffusers.py index 3435abe14..4c39896ed 100644 --- a/modules/intel/ipex/diffusers.py +++ b/modules/intel/ipex/diffusers.py @@ -1,6 +1,7 @@ import torch import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import -import diffusers #0.20.2 # pylint: disable=import-error +import diffusers #0.21.1 # pylint: disable=import-error +from diffusers.models.attention_processor import Attention # pylint: disable=protected-access, missing-function-docstring, line-too-long @@ -17,7 +18,7 @@ class SlicedAttnProcessor: # pylint: disable=too-few-public-methods def __init__(self, slice_size): self.slice_size = slice_size - def __call__(self, attn: diffusers.models.attention_processor.Attention, hidden_states, encoder_hidden_states=None, attention_mask=None): # pylint: disable=too-many-statements, too-many-locals, too-many-branches + def __call__(self, attn: Attention, hidden_states, encoder_hidden_states=None, attention_mask=None): # pylint: disable=too-many-statements, too-many-locals, too-many-branches residual = hidden_states input_ndim = hidden_states.ndim From 484dae8dbde05dd7d253ab152c1e6261aeddbbf9 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 Sep 2023 09:38:17 -0400 Subject: [PATCH 59/64] upgrade diffusers --- CHANGELOG.md | 2 ++ javascript/extraNetworks.js | 17 +++++++++++------ javascript/style.css | 2 +- modules/devices.py | 5 ++--- modules/processing_diffusers.py | 6 ++++-- modules/sd_models.py | 2 +- requirements.txt | 2 +- 7 files changed, 22 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d209e13a8..c819a8584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Mostly a service release, but with some changes in behavior, especially in HiRes - all combinations of: decode full/quick + upscale none/latent/non-latent + hires on/off + refiner on/off should be supported, but given the number of combinations, issues are possible - all operations are captured in image medata +- diffusers: + - allow loading of sd/sdxl models from safetensors without online connectivity - minor re-layout of the main ui - update **ui hints** - updated **models -> civitai** diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 65931e286..b59129103 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -153,13 +153,18 @@ function setupExtraNetworksForTab(tabname) { tabs.appendChild(div); div.appendChild(search); div.appendChild(description); + let searchTimer = null; search.addEventListener('input', (evt) => { - const searchTerm = search.value.toLowerCase(); - gradioApp().querySelectorAll(`#${tabname}_extra_tabs div.card`).forEach((elem) => { - let text = `${elem.querySelector('.name').textContent.toLowerCase()} ${elem.querySelector('.search_term').textContent.toLowerCase()}`; - text = text.replace('models--', 'Diffusers'); - elem.style.display = text.indexOf(searchTerm) === -1 ? 'none' : ''; - }); + if (searchTimer) clearTimeout(searchTimer); + searchTimer = setTimeout(() => { + const searchTerm = search.value.toLowerCase(); + gradioApp().querySelectorAll(`#${tabname}_extra_tabs div.card`).forEach((elem) => { + let text = `${elem.querySelector('.name').textContent.toLowerCase()} ${elem.querySelector('.search_term').textContent.toLowerCase()}`; + text = text.replace('models--', 'Diffusers'); + elem.style.display = text.indexOf(searchTerm) === -1 ? 'none' : ''; + }); + searchTimer = null; + }, 100); }); let hoverTimer = null; diff --git a/javascript/style.css b/javascript/style.css index 8baeb1602..e06d61642 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -230,7 +230,7 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt .extra-networks .description { flex: 3; } .extra-networks .tab-nav > button { margin-right: 0; height: 24px; padding: 2px 4px 2px 4px; } .extra-networks-tab { padding: 0 !important; } -.extra-network-subdirs { background: var(--input-background-fill); overflow-x: hidden; overflow-y: auto; min-width: 20%; padding-top: 0.5em; } +.extra-network-subdirs { background: var(--input-background-fill); overflow-x: hidden; overflow-y: auto; min-width: max(20%, 120px); padding-top: 0.5em; } .extra-networks-page { display: flex } .extra-networks .custom-button { width: 120px; width: 100%; background: none; justify-content: left; text-align: left; padding: 2px 8px 2px 16px; text-indent: -8px; box-shadow: none; line-break: auto; } .extra-networks .custom-button:hover { background: var(--button-primary-background-fill) } diff --git a/modules/devices.py b/modules/devices.py index c08b88a30..8a7db4244 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -160,7 +160,7 @@ def test_bf16(): def set_cuda_params(): - shared.log.debug('Verifying Torch settings') + # shared.log.debug('Verifying Torch settings') if cuda_ok: try: torch.backends.cuda.matmul.allow_tf32 = True @@ -212,8 +212,7 @@ def set_cuda_params(): else: inference_context = torch.no_grad shared.log.debug(f'Desired Torch parameters: dtype={shared.opts.cuda_dtype} no-half={shared.opts.no_half} no-half-vae={shared.opts.no_half_vae} upscast={shared.opts.upcast_sampling}') - shared.log.info(f'Setting Torch parameters: dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__} fp16={fp16_ok} bf16={bf16_ok}') - shared.log.debug(f'Torch default device: {torch.device(get_optimal_device_name())}') + shared.log.info(f'Setting Torch parameters: device={torch.device(get_optimal_device_name())} dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__} fp16={fp16_ok} bf16={bf16_ok}') args = cmd_args.parser.parse_args() diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index dfbae5e63..08eac3a37 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -68,7 +68,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro time.sleep(0.1) def full_vae_decode(latents, model): - shared.log.debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)} images={latents.shape[0]} latents={latents.shape}') + t0 = time.time() if shared.opts.diffusers_move_unet and not model.has_accelerate: shared.log.debug('Moving to CPU: model=UNet') unet_device = model.unet.device @@ -80,6 +80,8 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro decoded = model.vae.decode(latents / model.vae.config.scaling_factor, return_dict=False)[0] if shared.opts.diffusers_move_unet and not model.has_accelerate: model.unet.to(unet_device) + t1 = time.time() + shared.log.debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)} images={latents.shape[0]} latents={latents.shape} time={round(t1-t0, 3)}s') return decoded def full_vae_encode(image, model): @@ -168,7 +170,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro except Exception: is_refiner = False if hasattr(model, "set_progress_bar_config"): - model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} '+desc, ncols=80, colour='#327fba') + model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + desc, ncols=80, colour='#327fba') args = {} signature = inspect.signature(type(model).__call__) possible = signature.parameters.keys() diff --git a/modules/sd_models.py b/modules/sd_models.py index 01292eddc..5fc50dd61 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -719,7 +719,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No if vae is not None: diffusers_load_config["vae"] = vae - shared.log.info(f'Loading diffuser {op}: {checkpoint_info.filename}') + # shared.log.info(f'Loading diffuser {op}: {checkpoint_info.filename}') if not os.path.isfile(checkpoint_info.path): try: # shared.log.debug(f'Diffusers load {op} config: {diffusers_load_config}') diff --git a/requirements.txt b/requirements.txt index 6773b615c..41e37dfa1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,7 +47,7 @@ requests==2.31.0 tqdm==4.65.0 accelerate==0.20.3 opencv-python-headless==4.7.0.72 -diffusers==0.20.2 +diffusers==0.21.1 einops==0.4.1 gradio==3.43.2 huggingface_hub==0.16.4 From fc75b5ec41289b85d61b2caa9b83f13312d461f7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 Sep 2023 11:51:09 -0400 Subject: [PATCH 60/64] add wuerstchen model --- CHANGELOG.md | 9 +++++++-- README.md | 17 ++++++++++------- modules/modelloader.py | 2 +- modules/processing_diffusers.py | 2 +- modules/sd_models.py | 13 ++++++++++--- modules/shared.py | 5 +++-- requirements.txt | 4 ++-- 7 files changed, 34 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c819a8584..03eb91b8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ## Update for 2023-09-13 -Mostly a service release, but with some changes in behavior, especially in HiRes area of the code... - +Started as a mostly a service release with quite a few fixes, but then... +Major changes how **hires** works as well as support for a very interesting new model [wuerstchen](https://huggingface.co/blog/wuertschen) + - tons of fixes - changes to **hires** - enable non-latent upscale modes (standard upscalers) @@ -20,6 +21,10 @@ Mostly a service release, but with some changes in behavior, especially in HiRes - all operations are captured in image medata - diffusers: - allow loading of sd/sdxl models from safetensors without online connectivity + - support for new model: [wuerstchen](https://huggingface.co/warp-ai/wuerstchen) + its a high-resolution model (1024px+) that nearly doubls performance of sd-xl with much lower resource requirements + go to *models -> huggingface -> search "warp-ai/wuerstchen" -> download* + its nearly 12gb in size, so be patient :) - minor re-layout of the main ui - update **ui hints** - updated **models -> civitai** diff --git a/README.md b/README.md index bb5725346..bc1f07692 100644 --- a/README.md +++ b/README.md @@ -46,20 +46,23 @@ All Individual features are not listed here, instead check [ChangeLog](CHANGELOG - **Original**: Based on [LDM](https://github.com/Stability-AI/stablediffusion) reference implementation and significantly expanded on by [A1111](https://github.com/AUTOMATIC1111/stable-diffusion-webui) This is the default backend and it is fully compatible with all existing functionality and extensions + It supports **SD 1.x** and **SD 2.x** models - **Diffusers**: Based on new [Huggingface Diffusers](https://huggingface.co/docs/diffusers/index) implementation - It is also the only backend that supports **Stable Diffusion XL** model + It supports All models listed below + It is also the *only backend* that supports **Stable Diffusion XL** model See [wiki article](https://github.com/vladmandic/automatic/wiki/Diffusers) for more information ## Model support Additional models will be added as they become available and there is public interest in them -- Stable Diffusion 1.x and 2.x *(all variants)* -- Stable Diffusion XL -- Kandinsky 2.1 and 2.2 -- DeepFloyd IF -- UniDiffusion -- SD-Distilled *(all variants)* +- [Stable Diffusion](https://github.com/Stability-AI/stablediffusion/) 1.x and 2.x *(all variants)* +- [Stable Diffusion XL](https://github.com/Stability-AI/generative-models) +- [Kandinsky](https://github.com/ai-forever/Kandinsky-2) 2.1 and 2.2 +- [DeepFloyd IF](https://github.com/deep-floyd/IF) +- [UniDiffusion](https://github.com/thu-ml/unidiffuser) +- [SD-Distilled](https://huggingface.co/blog/sd_distillation) *(all variants)* +- [Wuerstchen](https://huggingface.co/blog/wuertschen) ## Platform support diff --git a/modules/modelloader.py b/modules/modelloader.py index f9b6048a0..7d99f1714 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -213,7 +213,7 @@ def load_diffusers_models(model_path: str, command_path: str = None): mtime = os.path.getmtime(folder) info = os.path.join(folder, "model_info.json") diffuser_repos.append({ 'name': name, 'filename': name, 'path': folder, 'hash': commit, 'mtime': mtime, 'model_info': info }) - if os.path.exists(os.path.join(place, folder, 'snapshots', commit, "hidden")): + if os.path.exists(os.path.join(folder, 'hidden')): continue output.append(name) except Exception as e: diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 08eac3a37..e2f5187df 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -181,7 +181,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro negative_embed = None negative_pooled = None prompts, negative_prompts, prompts_2, negative_prompts_2 = fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2) - if shared.opts.prompt_attention in {'Compel parser', 'Full parser'}: + if shared.opts.prompt_attention in {'Compel parser', 'Full parser'} and 'StableDiffusion' in model.__class__.__name__: prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompts(model, prompts, negative_prompts, prompts_2, negative_prompts_2, is_refiner, kwargs.pop("clip_skip", None)) if 'prompt' in possible: if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None: diff --git a/modules/sd_models.py b/modules/sd_models.py index 5fc50dd61..e83f461c2 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -701,6 +701,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No try: shared.log.debug(f'Model load {op} config: {diffusers_load_config}') sd_model = diffusers.DiffusionPipeline.from_pretrained(model_file, **diffusers_load_config) + sd_model.model_type = sd_model.__class__.__name__ except Exception as e: shared.log.error(f'Failed loading model: {model_file} {e}') list_models() # rescan for downloaded model @@ -722,10 +723,16 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No # shared.log.info(f'Loading diffuser {op}: {checkpoint_info.filename}') if not os.path.isfile(checkpoint_info.path): try: + # os.environ.setdefault('HUGGINGFACE_HUB_CACHE', shared.opts.diffusers_dir) # evalulated only on initial diffusers load + # diffusers_load_config["cache_dir "] = shared.opts.diffusers_dir # ignored for connected pipelines such as kandinsky-prior + # diffusers.utils.constants.DIFFUSERS_CACHE = shared.opts.diffusers_dir # shared.log.debug(f'Diffusers load {op} config: {diffusers_load_config}') - sd_model = diffusers.DiffusionPipeline.from_pretrained(checkpoint_info.path, **diffusers_load_config) + # sd_model = diffusers.DiffusionPipeline.from_pretrained(checkpoint_info.path, **diffusers_load_config) + sd_model = diffusers.AutoPipelineForText2Image.from_pretrained(checkpoint_info.path, cache_dir=shared.opts.diffusers_dir, **diffusers_load_config) + sd_model.model_type = sd_model.__class__.__name__ except Exception as e: shared.log.error(f'Failed loading model {op}: {checkpoint_info.path} {e}') + return else: diffusers_load_config["local_files_only "] = True diffusers_load_config["extract_ema"] = shared.opts.diffusers_extract_ema @@ -1184,7 +1191,7 @@ def apply_token_merging(sd_model, token_merging_ratio=0): return if current_token_merging_ratio > 0: tomesd.remove_patch(sd_model) - if token_merging_ratio > 0: + if token_merging_ratio > 0 and sd_model.model_type in ['ldm', 'sd', 'sdxl']: shared.log.debug(f'Applying token merging: ratio={token_merging_ratio}') tomesd.apply_patch( sd_model, @@ -1194,4 +1201,4 @@ def apply_token_merging(sd_model, token_merging_ratio=0): merge_crossattn=False, merge_mlp=False ) - sd_model.applied_token_merged_ratio = token_merging_ratio + sd_model.applied_token_merged_ratio = token_merging_ratio diff --git a/modules/shared.py b/modules/shared.py index b5fbbe12a..01f39e13c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -185,8 +185,9 @@ class State: image = modules.sd_samplers.samples_to_image_grid(self.current_latent) if opts.show_progress_grid else modules.sd_samplers.sample_to_image(self.current_latent) self.assign_current_image(image) self.current_image_sampling_step = self.sampling_step - except Exception as e: - log.error(f'Error setting current image: step={self.sampling_step} {e}') + except Exception: + # log.error(f'Error setting current image: step={self.sampling_step} {e}') + pass def assign_current_image(self, image): self.current_image = image diff --git a/requirements.txt b/requirements.txt index 41e37dfa1..67aad81ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,13 +44,13 @@ fasteners typing-extensions==4.7.1 antlr4-python3-runtime==4.9.3 requests==2.31.0 -tqdm==4.65.0 +tqdm==4.66.1 accelerate==0.20.3 opencv-python-headless==4.7.0.72 diffusers==0.21.1 einops==0.4.1 gradio==3.43.2 -huggingface_hub==0.16.4 +huggingface_hub==0.17.1 numexpr==2.8.4 numpy==1.24.4 numba==0.57.1 From f14fd7aa2a1e281d4ed11384c27469d99f17bb6c Mon Sep 17 00:00:00 2001 From: AI-Casanova <54461896+AI-Casanova@users.noreply.github.com> Date: Thu, 14 Sep 2023 11:03:03 -0500 Subject: [PATCH 61/64] Fix type error in diffusers --- scripts/prompt_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prompt_matrix.py b/scripts/prompt_matrix.py index 4efd0dfbe..77ce207a7 100644 --- a/scripts/prompt_matrix.py +++ b/scripts/prompt_matrix.py @@ -92,7 +92,7 @@ class Script(scripts.Script): p.prompt = all_prompts else: p.negative_prompt = all_prompts - p.seed = [p.seed + (i if different_seeds else 0) for i in range(len(all_prompts))] + p.seed = [int(p.seed + (i if different_seeds else 0)) for i in range(len(all_prompts))] p.prompt_for_display = positive_prompt processed = process_images(p) From b41c3009becd6f2ecb1d4d326444e2d6531abe2d Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 Sep 2023 13:03:36 -0400 Subject: [PATCH 62/64] revert diffusers --- modules/sd_models.py | 6 +++++- requirements.txt | 2 +- webui.bat | 14 +++++++------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index e83f461c2..3e053988e 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -557,6 +557,7 @@ class ModelData: shared.log.error("Failed to load stable diffusion model") errors.display(e, "loading stable diffusion model") self.sd_model = None + self.sd_model.model_type = shared.sd_model_type return self.sd_model def set_sd_model(self, v): @@ -577,6 +578,7 @@ class ModelData: shared.log.error("Failed to load stable diffusion model") errors.display(e, "loading stable diffusion model") self.sd_refiner = None + self.sd_refiner.model_type = shared.sd_refiner_type return self.sd_refiner def set_sd_refiner(self, v): @@ -701,7 +703,6 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No try: shared.log.debug(f'Model load {op} config: {diffusers_load_config}') sd_model = diffusers.DiffusionPipeline.from_pretrained(model_file, **diffusers_load_config) - sd_model.model_type = sd_model.__class__.__name__ except Exception as e: shared.log.error(f'Failed loading model: {model_file} {e}') list_models() # rescan for downloaded model @@ -830,6 +831,9 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No else: sd_model.vae.config["force_upcast"] = False sd_model.vae.config.force_upcast = False + if shared.opts.no_half_vae: + devices.dtype_vae = torch.float32 + sd_model.vae.to(devices.dtype_vae) shared.log.debug(f'Model {op} VAE: name={sd_vae.loaded_vae_file} upcast={sd_model.vae.config.get("force_upcast", None)}') if shared.opts.cross_attention_optimization == "xFormers" and hasattr(sd_model, 'enable_xformers_memory_efficient_attention'): sd_model.enable_xformers_memory_efficient_attention() diff --git a/requirements.txt b/requirements.txt index 67aad81ef..d926ae426 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,7 +47,7 @@ requests==2.31.0 tqdm==4.66.1 accelerate==0.20.3 opencv-python-headless==4.7.0.72 -diffusers==0.21.1 +diffusers==0.20.2 einops==0.4.1 gradio==3.43.2 huggingface_hub==0.17.1 diff --git a/webui.bat b/webui.bat index 6a6edcf10..022be1367 100755 --- a/webui.bat +++ b/webui.bat @@ -7,7 +7,7 @@ mkdir tmp 2>NUL %PYTHON% -c "" >tmp/stdout.txt 2>tmp/stderr.txt if %ERRORLEVEL% == 0 goto :check_pip -echo Couldn't launch python +echo Cannot launch python goto :show_stdout_stderr :check_pip @@ -16,7 +16,7 @@ if %ERRORLEVEL% == 0 goto :start_venv if "%PIP_INSTALLER_LOCATION%" == "" goto :show_stdout_stderr %PYTHON% "%PIP_INSTALLER_LOCATION%" >tmp/stdout.txt 2>tmp/stderr.txt if %ERRORLEVEL% == 0 goto :start_venv -echo Couldn't install pip +echo Cannot install pip goto :show_stdout_stderr :start_venv @@ -27,10 +27,11 @@ dir "%VENV_DIR%\Scripts\Python.exe" >tmp/stdout.txt 2>tmp/stderr.txt if %ERRORLEVEL% == 0 goto :activate_venv for /f "delims=" %%i in ('CALL %PYTHON% -c "import sys; print(sys.executable)"') do set PYTHON_FULLNAME="%%i" -echo Creating venv in directory %VENV_DIR% using python %PYTHON_FULLNAME% +echo Using python: %PYTHON_FULLNAME% +echo Creating VENV: %VENV_DIR% %PYTHON_FULLNAME% -m venv "%VENV_DIR%" >tmp/stdout.txt 2>tmp/stderr.txt if %ERRORLEVEL% == 0 goto :activate_venv -echo Unable to create venv in directory "%VENV_DIR%" +echo Failed creating VENV: "%VENV_DIR%" goto :show_stdout_stderr :activate_venv @@ -42,7 +43,6 @@ if [%ACCELERATE%] == ["True"] goto :accelerate goto :launch :accelerate -echo Checking for accelerate: %ACCELERATE% set ACCELERATE="%VENV_DIR%\Scripts\accelerate.exe" if EXIST %ACCELERATE% goto :accelerate_launch @@ -52,7 +52,7 @@ pause exit /b :accelerate_launch -echo Accelerating +echo Using accelerate %ACCELERATE% launch --num_cpu_threads_per_process=6 launch.py %* pause exit /b @@ -78,5 +78,5 @@ type tmp\stderr.txt :endofscript echo. -echo Launch unsuccessful. Exiting. +echo Launch Failed pause From ef51f5502670787c334b0286d888cf136c943ae0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 Sep 2023 14:41:43 -0400 Subject: [PATCH 63/64] critical fix --- CHANGELOG.md | 1 + modules/sd_models.py | 36 ++++++++++++++++++++---------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03eb91b8b..6816ccfe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Major changes how **hires** works as well as support for a very interesting new - cleaner server restart - custom exception handling +Note: **wuerstchen** is pending critical bugfix in `diffusers=0.21.1` ## Update for 2023-09-06 diff --git a/modules/sd_models.py b/modules/sd_models.py index 3e053988e..318d2915e 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -557,7 +557,6 @@ class ModelData: shared.log.error("Failed to load stable diffusion model") errors.display(e, "loading stable diffusion model") self.sd_model = None - self.sd_model.model_type = shared.sd_model_type return self.sd_model def set_sd_model(self, v): @@ -578,7 +577,6 @@ class ModelData: shared.log.error("Failed to load stable diffusion model") errors.display(e, "loading stable diffusion model") self.sd_refiner = None - self.sd_refiner.model_type = shared.sd_refiner_type return self.sd_refiner def set_sd_refiner(self, v): @@ -732,7 +730,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No sd_model = diffusers.AutoPipelineForText2Image.from_pretrained(checkpoint_info.path, cache_dir=shared.opts.diffusers_dir, **diffusers_load_config) sd_model.model_type = sd_model.__class__.__name__ except Exception as e: - shared.log.error(f'Failed loading model {op}: {checkpoint_info.path} {e}') + shared.log.error(f'Failed loading {op}: {checkpoint_info.path} {e}') return else: diffusers_load_config["local_files_only "] = True @@ -1193,16 +1191,22 @@ def apply_token_merging(sd_model, token_merging_ratio=0): current_token_merging_ratio = getattr(sd_model, 'applied_token_merged_ratio', 0) if token_merging_ratio is None or current_token_merging_ratio is None or current_token_merging_ratio == token_merging_ratio: return - if current_token_merging_ratio > 0: - tomesd.remove_patch(sd_model) - if token_merging_ratio > 0 and sd_model.model_type in ['ldm', 'sd', 'sdxl']: - shared.log.debug(f'Applying token merging: ratio={token_merging_ratio}') - tomesd.apply_patch( - sd_model, - ratio=token_merging_ratio, - use_rand=False, # can cause issues with some samplers - merge_attn=True, - merge_crossattn=False, - merge_mlp=False - ) - sd_model.applied_token_merged_ratio = token_merging_ratio + try: + if current_token_merging_ratio > 0: + tomesd.remove_patch(sd_model) + except Exception: + pass + if token_merging_ratio > 0: + try: + tomesd.apply_patch( + sd_model, + ratio=token_merging_ratio, + use_rand=False, # can cause issues with some samplers + merge_attn=True, + merge_crossattn=False, + merge_mlp=False + ) + shared.log.debug(f'Applying token merging: ratio={token_merging_ratio}') + sd_model.applied_token_merged_ratio = token_merging_ratio + except: + shared.log.warning(f'Token merging not supported: pipeline={sd_model.__class__.__name__}') From 9d85a9702b0942702e1e58845b0aeda1e6e5157a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 14 Sep 2023 15:28:38 -0400 Subject: [PATCH 64/64] fix vae upscale --- CHANGELOG.md | 4 +--- modules/processing_diffusers.py | 6 ++++++ requirements.txt | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6816ccfe4..8af1d23a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Update for 2023-09-13 Started as a mostly a service release with quite a few fixes, but then... -Major changes how **hires** works as well as support for a very interesting new model [wuerstchen](https://huggingface.co/blog/wuertschen) +Major changes how **hires** works as well as support for a very interesting new model [Wuerstchen](https://huggingface.co/blog/wuertschen) - tons of fixes - changes to **hires** @@ -46,8 +46,6 @@ Major changes how **hires** works as well as support for a very interesting new - cleaner server restart - custom exception handling -Note: **wuerstchen** is pending critical bugfix in `diffusers=0.21.1` - ## Update for 2023-09-06 One week later, another large update! diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index e2f5187df..d22c73a13 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -77,6 +77,12 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro if not shared.cmd_opts.lowvram and not shared.opts.diffusers_seq_cpu_offload: model.vae.to(devices.device) latents.to(model.vae.device) + + needs_upcasting = model.vae.dtype == torch.float16 and model.vae.config.force_upcast + if needs_upcasting: # this is done by diffusers automatically if output_type != 'latent' + model.upcast_vae() + latents = latents.to(next(iter(model.vae.post_quant_conv.parameters())).dtype) + decoded = model.vae.decode(latents / model.vae.config.scaling_factor, return_dict=False)[0] if shared.opts.diffusers_move_unet and not model.has_accelerate: model.unet.to(unet_device) diff --git a/requirements.txt b/requirements.txt index d926ae426..67aad81ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,7 +47,7 @@ requests==2.31.0 tqdm==4.66.1 accelerate==0.20.3 opencv-python-headless==4.7.0.72 -diffusers==0.20.2 +diffusers==0.21.1 einops==0.4.1 gradio==3.43.2 huggingface_hub==0.17.1