Path: blob/master/modules/launch_utils.py
3055 views
# this scripts installs necessary requirements and launches main program in webui.py1import logging2import re3import subprocess4import os5import shutil6import sys7import importlib.util8import importlib.metadata9import platform10import json11import shlex12from functools import lru_cache1314from modules import cmd_args, errors15from modules.paths_internal import script_path, extensions_dir16from modules.timer import startup_timer17from modules import logging_config1819args, _ = cmd_args.parser.parse_known_args()20logging_config.setup_logging(args.loglevel)2122python = sys.executable23git = os.environ.get('GIT', "git")24index_url = os.environ.get('INDEX_URL', "")25dir_repos = "repositories"2627# Whether to default to printing command output28default_command_live = (os.environ.get('WEBUI_LAUNCH_LIVE_OUTPUT') == "1")2930os.environ.setdefault('GRADIO_ANALYTICS_ENABLED', 'False')313233def check_python_version():34is_windows = platform.system() == "Windows"35major = sys.version_info.major36minor = sys.version_info.minor37micro = sys.version_info.micro3839if is_windows:40supported_minors = [10]41else:42supported_minors = [7, 8, 9, 10, 11]4344if not (major == 3 and minor in supported_minors):45import modules.errors4647modules.errors.print_error_explanation(f"""48INCOMPATIBLE PYTHON VERSION4950This program is tested with 3.10.6 Python, but you have {major}.{minor}.{micro}.51If you encounter an error with "RuntimeError: Couldn't install torch." message,52or any other error regarding unsuccessful package (library) installation,53please downgrade (or upgrade) to the latest version of 3.10 Python54and delete current Python and "venv" folder in WebUI's directory.5556You can download 3.10 Python from here: https://www.python.org/downloads/release/python-3106/5758{"Alternatively, use a binary release of WebUI: https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases/tag/v1.0.0-pre" if is_windows else ""}5960Use --skip-python-version-check to suppress this warning.61""")626364@lru_cache()65def commit_hash():66try:67return subprocess.check_output([git, "-C", script_path, "rev-parse", "HEAD"], shell=False, encoding='utf8').strip()68except Exception:69return "<none>"707172@lru_cache()73def git_tag():74try:75return subprocess.check_output([git, "-C", script_path, "describe", "--tags"], shell=False, encoding='utf8').strip()76except Exception:77try:7879changelog_md = os.path.join(script_path, "CHANGELOG.md")80with open(changelog_md, "r", encoding="utf-8") as file:81line = next((line.strip() for line in file if line.strip()), "<none>")82line = line.replace("## ", "")83return line84except Exception:85return "<none>"868788def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live) -> str:89if desc is not None:90print(desc)9192run_kwargs = {93"args": command,94"shell": True,95"env": os.environ if custom_env is None else custom_env,96"encoding": 'utf8',97"errors": 'ignore',98}99100if not live:101run_kwargs["stdout"] = run_kwargs["stderr"] = subprocess.PIPE102103result = subprocess.run(**run_kwargs)104105if result.returncode != 0:106error_bits = [107f"{errdesc or 'Error running command'}.",108f"Command: {command}",109f"Error code: {result.returncode}",110]111if result.stdout:112error_bits.append(f"stdout: {result.stdout}")113if result.stderr:114error_bits.append(f"stderr: {result.stderr}")115raise RuntimeError("\n".join(error_bits))116117return (result.stdout or "")118119120def is_installed(package):121try:122dist = importlib.metadata.distribution(package)123except importlib.metadata.PackageNotFoundError:124try:125spec = importlib.util.find_spec(package)126except ModuleNotFoundError:127return False128129return spec is not None130131return dist is not None132133134def repo_dir(name):135return os.path.join(script_path, dir_repos, name)136137138def run_pip(command, desc=None, live=default_command_live):139if args.skip_install:140return141142index_url_line = f' --index-url {index_url}' if index_url != '' else ''143return run(f'"{python}" -m pip {command} --prefer-binary{index_url_line}', desc=f"Installing {desc}", errdesc=f"Couldn't install {desc}", live=live)144145146def check_run_python(code: str) -> bool:147result = subprocess.run([python, "-c", code], capture_output=True, shell=False)148return result.returncode == 0149150151def git_fix_workspace(dir, name):152run(f'"{git}" -C "{dir}" fetch --refetch --no-auto-gc', f"Fetching all contents for {name}", f"Couldn't fetch {name}", live=True)153run(f'"{git}" -C "{dir}" gc --aggressive --prune=now', f"Pruning {name}", f"Couldn't prune {name}", live=True)154return155156157def run_git(dir, name, command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live, autofix=True):158try:159return run(f'"{git}" -C "{dir}" {command}', desc=desc, errdesc=errdesc, custom_env=custom_env, live=live)160except RuntimeError:161if not autofix:162raise163164print(f"{errdesc}, attempting autofix...")165git_fix_workspace(dir, name)166167return run(f'"{git}" -C "{dir}" {command}', desc=desc, errdesc=errdesc, custom_env=custom_env, live=live)168169170def git_clone(url, dir, name, commithash=None):171# TODO clone into temporary dir and move if successful172173if os.path.exists(dir):174if commithash is None:175return176177current_hash = run_git(dir, name, 'rev-parse HEAD', None, f"Couldn't determine {name}'s hash: {commithash}", live=False).strip()178if current_hash == commithash:179return180181if run_git(dir, name, 'config --get remote.origin.url', None, f"Couldn't determine {name}'s origin URL", live=False).strip() != url:182run_git(dir, name, f'remote set-url origin "{url}"', None, f"Failed to set {name}'s origin URL", live=False)183184run_git(dir, name, 'fetch', f"Fetching updates for {name}...", f"Couldn't fetch {name}", autofix=False)185186run_git(dir, name, f'checkout {commithash}', f"Checking out commit for {name} with hash: {commithash}...", f"Couldn't checkout commit {commithash} for {name}", live=True)187188return189190try:191run(f'"{git}" clone --config core.filemode=false "{url}" "{dir}"', f"Cloning {name} into {dir}...", f"Couldn't clone {name}", live=True)192except RuntimeError:193shutil.rmtree(dir, ignore_errors=True)194raise195196if commithash is not None:197run(f'"{git}" -C "{dir}" checkout {commithash}', None, "Couldn't checkout {name}'s hash: {commithash}")198199200def git_pull_recursive(dir):201for subdir, _, _ in os.walk(dir):202if os.path.exists(os.path.join(subdir, '.git')):203try:204output = subprocess.check_output([git, '-C', subdir, 'pull', '--autostash'])205print(f"Pulled changes for repository in '{subdir}':\n{output.decode('utf-8').strip()}\n")206except subprocess.CalledProcessError as e:207print(f"Couldn't perform 'git pull' on repository in '{subdir}':\n{e.output.decode('utf-8').strip()}\n")208209210def version_check(commit):211try:212import requests213commits = requests.get('https://api.github.com/repos/AUTOMATIC1111/stable-diffusion-webui/branches/master').json()214if commit != "<none>" and commits['commit']['sha'] != commit:215print("--------------------------------------------------------")216print("| You are not up to date with the most recent release. |")217print("| Consider running `git pull` to update. |")218print("--------------------------------------------------------")219elif commits['commit']['sha'] == commit:220print("You are up to date with the most recent release.")221else:222print("Not a git clone, can't perform version check.")223except Exception as e:224print("version check failed", e)225226227def run_extension_installer(extension_dir):228path_installer = os.path.join(extension_dir, "install.py")229if not os.path.isfile(path_installer):230return231232try:233env = os.environ.copy()234env['PYTHONPATH'] = f"{script_path}{os.pathsep}{env.get('PYTHONPATH', '')}"235236stdout = run(f'"{python}" "{path_installer}"', errdesc=f"Error running install.py for extension {extension_dir}", custom_env=env).strip()237if stdout:238print(stdout)239except Exception as e:240errors.report(str(e))241242243def list_extensions(settings_file):244settings = {}245246try:247with open(settings_file, "r", encoding="utf8") as file:248settings = json.load(file)249except FileNotFoundError:250pass251except Exception:252errors.report(f'\nCould not load settings\nThe config file "{settings_file}" is likely corrupted\nIt has been moved to the "tmp/config.json"\nReverting config to default\n\n''', exc_info=True)253os.replace(settings_file, os.path.join(script_path, "tmp", "config.json"))254255disabled_extensions = set(settings.get('disabled_extensions', []))256disable_all_extensions = settings.get('disable_all_extensions', 'none')257258if disable_all_extensions != 'none' or args.disable_extra_extensions or args.disable_all_extensions or not os.path.isdir(extensions_dir):259return []260261return [x for x in os.listdir(extensions_dir) if x not in disabled_extensions]262263264def run_extensions_installers(settings_file):265if not os.path.isdir(extensions_dir):266return267268with startup_timer.subcategory("run extensions installers"):269for dirname_extension in list_extensions(settings_file):270logging.debug(f"Installing {dirname_extension}")271272path = os.path.join(extensions_dir, dirname_extension)273274if os.path.isdir(path):275run_extension_installer(path)276startup_timer.record(dirname_extension)277278279re_requirement = re.compile(r"\s*([-_a-zA-Z0-9]+)\s*(?:==\s*([-+_.a-zA-Z0-9]+))?\s*")280281282def requirements_met(requirements_file):283"""284Does a simple parse of a requirements.txt file to determine if all rerqirements in it285are already installed. Returns True if so, False if not installed or parsing fails.286"""287288import importlib.metadata289import packaging.version290291with open(requirements_file, "r", encoding="utf8") as file:292for line in file:293if line.strip() == "":294continue295296m = re.match(re_requirement, line)297if m is None:298return False299300package = m.group(1).strip()301version_required = (m.group(2) or "").strip()302303if version_required == "":304continue305306try:307version_installed = importlib.metadata.version(package)308except Exception:309return False310311if packaging.version.parse(version_required) != packaging.version.parse(version_installed):312return False313314return True315316317def prepare_environment():318torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu121")319torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.1.2 torchvision==0.16.2 --extra-index-url {torch_index_url}")320if args.use_ipex:321if platform.system() == "Windows":322# The "Nuullll/intel-extension-for-pytorch" wheels were built from IPEX source for Intel Arc GPU: https://github.com/intel/intel-extension-for-pytorch/tree/xpu-main323# This is NOT an Intel official release so please use it at your own risk!!324# See https://github.com/Nuullll/intel-extension-for-pytorch/releases/tag/v2.0.110%2Bxpu-master%2Bdll-bundle for details.325#326# Strengths (over official IPEX 2.0.110 windows release):327# - AOT build (for Arc GPU only) to eliminate JIT compilation overhead: https://github.com/intel/intel-extension-for-pytorch/issues/399328# - Bundles minimal oneAPI 2023.2 dependencies into the python wheels, so users don't need to install oneAPI for the whole system.329# - Provides a compatible torchvision wheel: https://github.com/intel/intel-extension-for-pytorch/issues/465330# Limitation:331# - Only works for python 3.10332url_prefix = "https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.0.110%2Bxpu-master%2Bdll-bundle"333torch_command = os.environ.get('TORCH_COMMAND', f"pip install {url_prefix}/torch-2.0.0a0+gite9ebda2-cp310-cp310-win_amd64.whl {url_prefix}/torchvision-0.15.2a0+fa99a53-cp310-cp310-win_amd64.whl {url_prefix}/intel_extension_for_pytorch-2.0.110+gitc6ea20b-cp310-cp310-win_amd64.whl")334else:335# Using official IPEX release for linux since it's already an AOT build.336# However, users still have to install oneAPI toolkit and activate oneAPI environment manually.337# See https://intel.github.io/intel-extension-for-pytorch/index.html#installation for details.338torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://pytorch-extension.intel.com/release-whl/stable/xpu/us/")339torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.0.0a0 intel-extension-for-pytorch==2.0.110+gitba7f6c1 --extra-index-url {torch_index_url}")340requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt")341requirements_file_for_npu = os.environ.get('REQS_FILE_FOR_NPU', "requirements_npu.txt")342343xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.23.post1')344clip_package = os.environ.get('CLIP_PACKAGE', "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip")345openclip_package = os.environ.get('OPENCLIP_PACKAGE', "https://github.com/mlfoundations/open_clip/archive/bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b.zip")346347assets_repo = os.environ.get('ASSETS_REPO', "https://github.com/AUTOMATIC1111/stable-diffusion-webui-assets.git")348stable_diffusion_repo = os.environ.get('STABLE_DIFFUSION_REPO', "https://github.com/Stability-AI/stablediffusion.git")349stable_diffusion_xl_repo = os.environ.get('STABLE_DIFFUSION_XL_REPO', "https://github.com/Stability-AI/generative-models.git")350k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git')351blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git')352353assets_commit_hash = os.environ.get('ASSETS_COMMIT_HASH', "6f7db241d2f8ba7457bac5ca9753331f0c266917")354stable_diffusion_commit_hash = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf")355stable_diffusion_xl_commit_hash = os.environ.get('STABLE_DIFFUSION_XL_COMMIT_HASH', "45c443b316737a4ab6e40413d7794a7f5657c19f")356k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "ab527a9a6d347f364e3d185ba6d714e22d80cb3c")357blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9")358359try:360# the existence of this file is a signal to webui.sh/bat that webui needs to be restarted when it stops execution361os.remove(os.path.join(script_path, "tmp", "restart"))362os.environ.setdefault('SD_WEBUI_RESTARTING', '1')363except OSError:364pass365366if not args.skip_python_version_check:367check_python_version()368369startup_timer.record("checks")370371commit = commit_hash()372tag = git_tag()373startup_timer.record("git version info")374375print(f"Python {sys.version}")376print(f"Version: {tag}")377print(f"Commit hash: {commit}")378379if args.reinstall_torch or not is_installed("torch") or not is_installed("torchvision"):380run(f'"{python}" -m {torch_command}', "Installing torch and torchvision", "Couldn't install torch", live=True)381startup_timer.record("install torch")382383if args.use_ipex:384args.skip_torch_cuda_test = True385if not args.skip_torch_cuda_test and not check_run_python("import torch; assert torch.cuda.is_available()"):386raise RuntimeError(387'Torch is not able to use GPU; '388'add --skip-torch-cuda-test to COMMANDLINE_ARGS variable to disable this check'389)390startup_timer.record("torch GPU test")391392if not is_installed("clip"):393run_pip(f"install {clip_package}", "clip")394startup_timer.record("install clip")395396if not is_installed("open_clip"):397run_pip(f"install {openclip_package}", "open_clip")398startup_timer.record("install open_clip")399400if (not is_installed("xformers") or args.reinstall_xformers) and args.xformers:401run_pip(f"install -U -I --no-deps {xformers_package}", "xformers")402startup_timer.record("install xformers")403404if not is_installed("ngrok") and args.ngrok:405run_pip("install ngrok", "ngrok")406startup_timer.record("install ngrok")407408os.makedirs(os.path.join(script_path, dir_repos), exist_ok=True)409410git_clone(assets_repo, repo_dir('stable-diffusion-webui-assets'), "assets", assets_commit_hash)411git_clone(stable_diffusion_repo, repo_dir('stable-diffusion-stability-ai'), "Stable Diffusion", stable_diffusion_commit_hash)412git_clone(stable_diffusion_xl_repo, repo_dir('generative-models'), "Stable Diffusion XL", stable_diffusion_xl_commit_hash)413git_clone(k_diffusion_repo, repo_dir('k-diffusion'), "K-diffusion", k_diffusion_commit_hash)414git_clone(blip_repo, repo_dir('BLIP'), "BLIP", blip_commit_hash)415416startup_timer.record("clone repositores")417418if not os.path.isfile(requirements_file):419requirements_file = os.path.join(script_path, requirements_file)420421if not requirements_met(requirements_file):422run_pip(f"install -r \"{requirements_file}\"", "requirements")423startup_timer.record("install requirements")424425if not os.path.isfile(requirements_file_for_npu):426requirements_file_for_npu = os.path.join(script_path, requirements_file_for_npu)427428if "torch_npu" in torch_command and not requirements_met(requirements_file_for_npu):429run_pip(f"install -r \"{requirements_file_for_npu}\"", "requirements_for_npu")430startup_timer.record("install requirements_for_npu")431432if not args.skip_install:433run_extensions_installers(settings_file=args.ui_settings_file)434435if args.update_check:436version_check(commit)437startup_timer.record("check version")438439if args.update_all_extensions:440git_pull_recursive(extensions_dir)441startup_timer.record("update extensions")442443if "--exit" in sys.argv:444print("Exiting because of --exit argument")445exit(0)446447448def configure_for_tests():449if "--api" not in sys.argv:450sys.argv.append("--api")451if "--ckpt" not in sys.argv:452sys.argv.append("--ckpt")453sys.argv.append(os.path.join(script_path, "test/test_files/empty.pt"))454if "--skip-torch-cuda-test" not in sys.argv:455sys.argv.append("--skip-torch-cuda-test")456if "--disable-nan-check" not in sys.argv:457sys.argv.append("--disable-nan-check")458459os.environ['COMMANDLINE_ARGS'] = ""460461462def start():463print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {shlex.join(sys.argv[1:])}")464import webui465if '--nowebui' in sys.argv:466webui.api_only()467else:468webui.webui()469470471def dump_sysinfo():472from modules import sysinfo473import datetime474475text = sysinfo.get()476filename = f"sysinfo-{datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}.json"477478with open(filename, "w", encoding="utf8") as file:479file.write(text)480481return filename482483484