diff --git a/CHANGELOG.md b/CHANGELOG.md index 58e173bff..db60812f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,11 @@ *Note*: Pending release of `diffusers==0.22.0` -Support for several new models and additional optimizations. -Plus tons of fixes... +Another pretty big release, this time with focus on +new models, new backends and optimizations and tons of fixes + +Also, [Wiki](https://github.com/vladmandic/automatic/wiki) has been updated with new content, so check it out! +Some highlights: [OpenVINO](https://github.com/vladmandic/automatic/wiki/OpenVINO), [IntelArc](https://github.com/vladmandic/automatic/wiki/Intel-ARC), [DirectML](https://github.com/vladmandic/automatic/wiki/DirectML), [ONNX/Olive>](https://github.com/vladmandic/automatic/wiki/ONNX-Runtime) - **Diffusers** - new model type: [Segmind SSD-1B](https://huggingface.co/segmind/SSD-1B) @@ -24,6 +27,8 @@ Plus tons of fixes... - new custom pipeline: `Disty0/zero123plus-pipeline` generate 4 output images with different camera positions: front, side, top, back! for more details, see + - new backend: **ONNX/Olive** (experimental) + for details, see WiKi - extend support for [Free-U](https://github.com/ChenyangSi/FreeU) improve generations quality at no cost (other than finding params that work for you) - **General** @@ -35,6 +40,8 @@ Plus tons of fixes... - **Extra networks** sort by name, size, date, etc. - new option: *settings -> images -> keep incomplete* can be used to skip vae decode on aborted/skipped/interrupted image generations + - new option: *settings -> system paths -> models* + can be used to set custom base path for *all* models (previously only as cli option) - remove external clone of items in `/repositories` - switch core font in default theme to **noto-sans** previously default font was simply *system-ui*, but it lead to too much variations between browsers and platforms diff --git a/README.md b/README.md index b9e136c0e..8c120b481 100644 --- a/README.md +++ b/README.md @@ -56,21 +56,23 @@ Additional models will be added as they become available and there is public int - [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) +- [Segmind SSD-1B](https://huggingface.co/segmind/SSD-1B) +- [UniDiffusion](https://github.com/thu-ml/unidiffuser) +- [DeepFloyd IF](https://github.com/deep-floyd/IF) ## Platform support -- *nVidia* GPUs using **CUDA** libraries on both *Windows and Linux* -- *AMD* GPUs using **ROCm** libraries on *Linux*. - Support will be extended to *Windows* once AMD releases ROCm for Windows -- *Intel Arc* GPUs using **OneAPI** with *IPEX XPU* libraries on both *Windows and Linux* -- Any GPU compatible with *DirectX* on *Windows* using **DirectML** libraries. - This includes support for AMD GPUs that are not supported by native ROCm libraries -- Any GPU or device compatible with **OpenVINO** libraries on both *Windows and Linux* -- *Apple M1/M2* on *OSX* using built-in support in Torch with **MPS** optimizations +- *nVidia* GPUs using **CUDA** libraries on both *Windows and Linux* +- *AMD* GPUs using **ROCm** libraries on *Linux* + Support will be extended to *Windows* once AMD releases ROCm for Windows +- *Intel Arc* GPUs using **OneAPI** with *IPEX XPU* libraries on both *Windows and Linux* +- Any GPU compatible with *DirectX* on *Windows* using **DirectML** libraries + This includes support for AMD GPUs that are not supported by native ROCm libraries +- Any GPU or device compatible with **OpenVINO** libraries on both *Windows and Linux* +- *Apple M1/M2* on *OSX* using built-in support in Torch with **MPS** optimizations +- *ONNX/Olive* (experimental) ## Install & Run diff --git a/installer.py b/installer.py index b5fcd8335..2b0fe756c 100644 --- a/installer.py +++ b/installer.py @@ -661,7 +661,7 @@ def install_extensions(): pkg_resources._initialize_master_working_set() # pylint: disable=protected-access pkgs = [f'{p.project_name}=={p._version}' for p in pkg_resources.working_set] # pylint: disable=protected-access,not-an-iterable log.debug(f'Installed packages: {len(pkgs)}') - from modules.paths_internal import extensions_builtin_dir, extensions_dir + from modules.paths import extensions_builtin_dir, extensions_dir extensions_duplicates = [] extensions_enabled = [] extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir] @@ -795,7 +795,7 @@ def set_environment(): def check_extensions(): newest_all = os.path.getmtime('requirements.txt') - from modules.paths_internal import extensions_builtin_dir, extensions_dir + from modules.paths import extensions_builtin_dir, extensions_dir 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': @@ -983,7 +983,7 @@ def extensions_preload(parser): log.info('Running in safe mode without user extensions') try: from modules.script_loading import preload_extensions - from modules.paths_internal import extensions_builtin_dir, extensions_dir + from modules.paths 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: diff --git a/launch.py b/launch.py index b725961f9..d84cb96f6 100644 --- a/launch.py +++ b/launch.py @@ -28,9 +28,9 @@ def init_modules(): parser = modules.cmd_args.parser installer.add_args(parser) args, _ = parser.parse_known_args() - import modules.paths_internal - script_path = modules.paths_internal.script_path - extensions_dir = modules.paths_internal.extensions_dir + import modules.paths + script_path = modules.paths.script_path + extensions_dir = modules.paths.extensions_dir def get_custom_args(): diff --git a/modules/cmd_args.py b/modules/cmd_args.py index ef2074f1e..0fc387268 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -1,6 +1,6 @@ import os import argparse -from modules.paths_internal import data_path +from modules.paths import data_path parser = argparse.ArgumentParser(description="SD.Next", conflict_handler='resolve', epilog='For other options see UI Settings page', prog='', add_help=True, formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=55, indent_increment=2, width=200)) parser._optionals = parser.add_argument_group('Other options') # pylint: disable=protected-access diff --git a/modules/extensions.py b/modules/extensions.py index b81dbf993..4e37e4611 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -2,7 +2,7 @@ import os from datetime import datetime import git from modules import shared, errors -from modules.paths_internal import extensions_dir, extensions_builtin_dir +from modules.paths import extensions_dir, extensions_builtin_dir extensions = [] diff --git a/modules/paths.py b/modules/paths.py index d3cce3f4a..b3dbaef8b 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -1,9 +1,43 @@ +# this module must not have any dependencies as it first import import os import sys -from modules import paths_internal, errors +import json +import argparse +from modules.errors import log +# parse args, parse again after we have the data-dir and early-read the config file +parser = argparse.ArgumentParser(add_help=False) +parser.add_argument("--ckpt", type=str, default=os.environ.get("SD_MODEL", None), help="Path to model checkpoint to load immediately, default: %(default)s") +parser.add_argument("--data-dir", type=str, default=os.environ.get("SD_DATADIR", ''), help="Base path where all user data is stored, default: %(default)s") +parser.add_argument("--models-dir", type=str, default=os.environ.get("SD_MODELSDIR", None), help="Base path where all models are stored, default: %(default)s",) +cli = parser.parse_known_args()[0] +parser.add_argument("--config", type=str, default=os.environ.get("SD_CONFIG", os.path.join(cli.data_dir, 'config.json')), help="Use specific server configuration file, default: %(default)s") +cli = parser.parse_known_args()[0] +config_path = cli.config if os.path.isabs(cli.config) else os.path.join(cli.data_dir, cli.config) +try: + with open(config_path, 'r', encoding='utf8') as f: + config = json.load(f) +except Exception as err: + print('Error loading config file: ${config_path} {err}') + config = {} -debug = errors.log.info if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None +modules_path = os.path.dirname(os.path.realpath(__file__)) +script_path = os.path.dirname(modules_path) +data_path = cli.data_dir +models_config = cli.models_dir or config.get('models_dir') or 'models' +models_path = models_config if os.path.isabs(models_config) else os.path.join(data_path, models_config) +extensions_dir = os.path.join(data_path, "extensions") +extensions_builtin_dir = "extensions-builtin" +sd_configs_path = os.path.join(script_path, "configs") +sd_default_config = os.path.join(sd_configs_path, "v1-inference.yaml") +sd_model_file = cli.ckpt or os.path.join(script_path, 'model.ckpt') # not used +default_sd_model_file = sd_model_file # not used +debug = log.info if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None + +if os.environ.get('SD_PATH_DEBUG', None) is not None: + print(f'Paths: script-path="{script_path}" data-dir="{data_path}" models-dir="{models_path}" config="{config_path}"') + +""" data_path = paths_internal.data_path script_path = paths_internal.script_path models_path = paths_internal.models_path @@ -13,8 +47,8 @@ sd_model_file = paths_internal.sd_model_file default_sd_model_file = paths_internal.default_sd_model_file extensions_dir = paths_internal.extensions_dir extensions_builtin_dir = paths_internal.extensions_builtin_dir +""" -# data_path = cmd_opts_pre.data sys.path.insert(0, script_path) sd_path = os.path.join(script_path, 'repositories') @@ -31,25 +65,26 @@ paths = {} for d, must_exist, what, _options in path_dirs: must_exist_path = os.path.abspath(os.path.join(script_path, d, must_exist)) if not os.path.exists(must_exist_path): - errors.log.error(f'Required path not found: path={must_exist_path} item={what}') + log.error(f'Required path not found: path={must_exist_path} item={what}') else: d = os.path.abspath(d) sys.path.append(d) paths[what] = d -def create_paths(opts): - def create_path(folder): - if folder is None or folder == '': - return - if os.path.exists(folder): - return - try: - os.makedirs(folder, exist_ok=True) - errors.log.info(f'Create folder={folder}') - except Exception as e: - errors.log.error(f'Create Failed folder={folder} {e}') +def create_path(folder): + if folder is None or folder == '': + return + if os.path.exists(folder): + return + try: + os.makedirs(folder, exist_ok=True) + log.info(f'Create folder={folder}') + except Exception as e: + log.error(f'Create Failed folder={folder} {e}') + +def create_paths(opts): def fix_path(folder): tgt = opts.data.get(folder, None) or opts.data_labels[folder].default if tgt is None or tgt == '': diff --git a/modules/paths_internal.py b/modules/paths_internal.py index 83c097e46..49a055caa 100644 --- a/modules/paths_internal.py +++ b/modules/paths_internal.py @@ -1,5 +1,8 @@ -"""this module defines internal paths used by program and is safe to import before dependencies are installed in launch.py""" +# no longer used, all paths are defined in paths.py +from modules.paths import modules_path, script_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, data_path, models_path, extensions_dir, extensions_builtin_dir # pylint: disable=unused-import + +""" import argparse import os @@ -7,15 +10,21 @@ modules_path = os.path.dirname(os.path.realpath(__file__)) script_path = os.path.dirname(modules_path) sd_configs_path = os.path.join(script_path, "configs") sd_default_config = os.path.join(sd_configs_path, "v1-inference.yaml") -sd_model_file = os.path.join(script_path, 'model.ckpt') -default_sd_model_file = sd_model_file # Parse the --data-dir flag first so we can use it as a base for our other argument default values parser_pre = argparse.ArgumentParser(add_help=False) -parser_pre.add_argument("--data-dir", type=str, default="", help="base path where all user data is stored", ) -parser_pre.add_argument("--models-dir", type=str, default="models", help="base path where all models are stored",) +parser_pre.add_argument("--ckpt", type=str, default=os.environ.get("SD_MODEL", None), help="Path to model checkpoint to load immediately, default: %(default)s") +parser_pre.add_argument("--data-dir", type=str, default=os.environ.get("SD_DATADIR", ''), help="Base path where all user data is stored, default: %(default)s") +parser_pre.add_argument("--models-dir", type=str, default=os.environ.get("SD_MODELSDIR", 'models'), help="Base path where all models are stored, default: %(default)s",) cmd_opts_pre = parser_pre.parse_known_args()[0] + +# parser_pre.add_argument("--config", type=str, default=os.environ.get("SD_CONFIG", os.path.join(data_path, 'config.json')), help="Use specific server configuration file, default: %(default)s") + 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 = "extensions-builtin" + +sd_model_file = cmd_opts_pre.ckpt or os.path.join(script_path, 'model.ckpt') # not used +default_sd_model_file = sd_model_file # not used +""" diff --git a/modules/sd_models.py b/modules/sd_models.py index 229e449b2..9098c817f 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -24,7 +24,7 @@ from modules import paths, shared, shared_items, shared_state, modelloader, devi from modules.sd_hijack_inpainting import do_inpainting_hijack from modules.timer import Timer from modules.memstats import memory_stats -from modules.paths_internal import models_path, script_path +from modules.paths import models_path, script_path try: import diffusers diff --git a/modules/sd_vae.py b/modules/sd_vae.py index c8184d703..eeffcef44 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -3,7 +3,7 @@ import collections import glob from copy import deepcopy import torch -from modules import shared, paths, paths_internal, devices, script_callbacks, sd_models +from modules import shared, paths, devices, script_callbacks, sd_models vae_ignore_keys = {"model_ema.decay", "model_ema.num_updates"} @@ -200,7 +200,7 @@ def load_vae_diffusers(model_file, vae_file=None, vae_source="unknown-source"): import diffusers if os.path.isfile(vae_file): _pipeline, model_type = sd_models.detect_pipeline(model_file, 'vae') - diffusers_load_config = { "config_file": paths_internal.sd_default_config if model_type != 'Stable Diffusion XL' else os.path.join(paths_internal.sd_configs_path, 'sd_xl_base.yaml')} + diffusers_load_config = { "config_file": paths.sd_default_config if model_type != 'Stable Diffusion XL' else os.path.join(paths.sd_configs_path, 'sd_xl_base.yaml')} vae = diffusers.AutoencoderKL.from_single_file(vae_file, **diffusers_load_config) vae = vae.to(devices.dtype_vae) else: diff --git a/modules/shared.py b/modules/shared.py index b0059560b..523536387 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -12,13 +12,13 @@ import gradio as gr import fasteners from rich.console import Console from modules import errors, shared_items, shared_state, cmd_args, ui_components, theme -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.paths 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 import modules.interrogate import modules.memmon import modules.styles import modules.devices as devices # pylint: disable=R0402 -import modules.paths_internal as paths +import modules.paths as paths from installer import print_dict from installer import log as central_logger # pylint: disable=E0611 @@ -346,8 +346,8 @@ 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", folder=True), - "clean_temp_dir_at_start": OptionInfo(True, "Cleanup non-default temporary directory when starting webui"), + "models_paths_sep_options": OptionInfo("

Models paths

", "", gr.HTML), + "models_dir": OptionInfo('models', "Base path where all models are stored", folder=True), "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), @@ -366,6 +366,10 @@ options_templates.update(options_section(('system-paths', "System Paths"), { "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), + + "other_paths_sep_options": OptionInfo("

Other paths

", "", gr.HTML), + "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"), })) options_templates.update(options_section(('saving-images', "Image Options"), { diff --git a/modules/taesd/sd_vae_taesd.py b/modules/taesd/sd_vae_taesd.py index ff50b4156..8ad4f6f21 100644 --- a/modules/taesd/sd_vae_taesd.py +++ b/modules/taesd/sd_vae_taesd.py @@ -6,7 +6,7 @@ https://github.com/madebyollin/taesd """ import os from PIL import Image -from modules import devices, paths_internal +from modules import devices, paths from modules.taesd.taesd import TAESD taesd_models = { 'sd-decoder': None, 'sd-encoder': None, 'sdxl-decoder': None, 'sdxl-encoder': None } @@ -25,7 +25,7 @@ def download_model(model_path): def model(model_class = 'sd', model_type = 'decoder'): vae = taesd_models[f'{model_class}-{model_type}'] if vae is None: - model_path = os.path.join(paths_internal.models_path, "TAESD", f"tae{model_class}_{model_type}.pth") + model_path = os.path.join(paths.models_path, "TAESD", f"tae{model_class}_{model_type}.pth") download_model(model_path) if os.path.exists(model_path): from modules.shared import log @@ -52,7 +52,7 @@ def decode(latents): return Image.new('RGB', (8, 8), color = (0, 0, 0)) vae = taesd_models[f'{model_class}-decoder'] if vae is None: - model_path = os.path.join(paths_internal.models_path, "TAESD", f"tae{model_class}_decoder.pth") + model_path = os.path.join(paths.models_path, "TAESD", f"tae{model_class}_decoder.pth") download_model(model_path) if os.path.exists(model_path): taesd_models[f'{model_class}-decoder'] = TAESD(decoder_path=model_path, encoder_path=None) @@ -73,7 +73,7 @@ def encode(image): 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") + model_path = os.path.join(paths.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)