Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
automatic1111
GitHub Repository: automatic1111/stable-diffusion-webui
Path: blob/master/modules/launch_utils.py
3055 views
1
# this scripts installs necessary requirements and launches main program in webui.py
2
import logging
3
import re
4
import subprocess
5
import os
6
import shutil
7
import sys
8
import importlib.util
9
import importlib.metadata
10
import platform
11
import json
12
import shlex
13
from functools import lru_cache
14
15
from modules import cmd_args, errors
16
from modules.paths_internal import script_path, extensions_dir
17
from modules.timer import startup_timer
18
from modules import logging_config
19
20
args, _ = cmd_args.parser.parse_known_args()
21
logging_config.setup_logging(args.loglevel)
22
23
python = sys.executable
24
git = os.environ.get('GIT', "git")
25
index_url = os.environ.get('INDEX_URL', "")
26
dir_repos = "repositories"
27
28
# Whether to default to printing command output
29
default_command_live = (os.environ.get('WEBUI_LAUNCH_LIVE_OUTPUT') == "1")
30
31
os.environ.setdefault('GRADIO_ANALYTICS_ENABLED', 'False')
32
33
34
def check_python_version():
35
is_windows = platform.system() == "Windows"
36
major = sys.version_info.major
37
minor = sys.version_info.minor
38
micro = sys.version_info.micro
39
40
if is_windows:
41
supported_minors = [10]
42
else:
43
supported_minors = [7, 8, 9, 10, 11]
44
45
if not (major == 3 and minor in supported_minors):
46
import modules.errors
47
48
modules.errors.print_error_explanation(f"""
49
INCOMPATIBLE PYTHON VERSION
50
51
This program is tested with 3.10.6 Python, but you have {major}.{minor}.{micro}.
52
If you encounter an error with "RuntimeError: Couldn't install torch." message,
53
or any other error regarding unsuccessful package (library) installation,
54
please downgrade (or upgrade) to the latest version of 3.10 Python
55
and delete current Python and "venv" folder in WebUI's directory.
56
57
You can download 3.10 Python from here: https://www.python.org/downloads/release/python-3106/
58
59
{"Alternatively, use a binary release of WebUI: https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases/tag/v1.0.0-pre" if is_windows else ""}
60
61
Use --skip-python-version-check to suppress this warning.
62
""")
63
64
65
@lru_cache()
66
def commit_hash():
67
try:
68
return subprocess.check_output([git, "-C", script_path, "rev-parse", "HEAD"], shell=False, encoding='utf8').strip()
69
except Exception:
70
return "<none>"
71
72
73
@lru_cache()
74
def git_tag():
75
try:
76
return subprocess.check_output([git, "-C", script_path, "describe", "--tags"], shell=False, encoding='utf8').strip()
77
except Exception:
78
try:
79
80
changelog_md = os.path.join(script_path, "CHANGELOG.md")
81
with open(changelog_md, "r", encoding="utf-8") as file:
82
line = next((line.strip() for line in file if line.strip()), "<none>")
83
line = line.replace("## ", "")
84
return line
85
except Exception:
86
return "<none>"
87
88
89
def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live) -> str:
90
if desc is not None:
91
print(desc)
92
93
run_kwargs = {
94
"args": command,
95
"shell": True,
96
"env": os.environ if custom_env is None else custom_env,
97
"encoding": 'utf8',
98
"errors": 'ignore',
99
}
100
101
if not live:
102
run_kwargs["stdout"] = run_kwargs["stderr"] = subprocess.PIPE
103
104
result = subprocess.run(**run_kwargs)
105
106
if result.returncode != 0:
107
error_bits = [
108
f"{errdesc or 'Error running command'}.",
109
f"Command: {command}",
110
f"Error code: {result.returncode}",
111
]
112
if result.stdout:
113
error_bits.append(f"stdout: {result.stdout}")
114
if result.stderr:
115
error_bits.append(f"stderr: {result.stderr}")
116
raise RuntimeError("\n".join(error_bits))
117
118
return (result.stdout or "")
119
120
121
def is_installed(package):
122
try:
123
dist = importlib.metadata.distribution(package)
124
except importlib.metadata.PackageNotFoundError:
125
try:
126
spec = importlib.util.find_spec(package)
127
except ModuleNotFoundError:
128
return False
129
130
return spec is not None
131
132
return dist is not None
133
134
135
def repo_dir(name):
136
return os.path.join(script_path, dir_repos, name)
137
138
139
def run_pip(command, desc=None, live=default_command_live):
140
if args.skip_install:
141
return
142
143
index_url_line = f' --index-url {index_url}' if index_url != '' else ''
144
return run(f'"{python}" -m pip {command} --prefer-binary{index_url_line}', desc=f"Installing {desc}", errdesc=f"Couldn't install {desc}", live=live)
145
146
147
def check_run_python(code: str) -> bool:
148
result = subprocess.run([python, "-c", code], capture_output=True, shell=False)
149
return result.returncode == 0
150
151
152
def git_fix_workspace(dir, name):
153
run(f'"{git}" -C "{dir}" fetch --refetch --no-auto-gc', f"Fetching all contents for {name}", f"Couldn't fetch {name}", live=True)
154
run(f'"{git}" -C "{dir}" gc --aggressive --prune=now', f"Pruning {name}", f"Couldn't prune {name}", live=True)
155
return
156
157
158
def run_git(dir, name, command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live, autofix=True):
159
try:
160
return run(f'"{git}" -C "{dir}" {command}', desc=desc, errdesc=errdesc, custom_env=custom_env, live=live)
161
except RuntimeError:
162
if not autofix:
163
raise
164
165
print(f"{errdesc}, attempting autofix...")
166
git_fix_workspace(dir, name)
167
168
return run(f'"{git}" -C "{dir}" {command}', desc=desc, errdesc=errdesc, custom_env=custom_env, live=live)
169
170
171
def git_clone(url, dir, name, commithash=None):
172
# TODO clone into temporary dir and move if successful
173
174
if os.path.exists(dir):
175
if commithash is None:
176
return
177
178
current_hash = run_git(dir, name, 'rev-parse HEAD', None, f"Couldn't determine {name}'s hash: {commithash}", live=False).strip()
179
if current_hash == commithash:
180
return
181
182
if run_git(dir, name, 'config --get remote.origin.url', None, f"Couldn't determine {name}'s origin URL", live=False).strip() != url:
183
run_git(dir, name, f'remote set-url origin "{url}"', None, f"Failed to set {name}'s origin URL", live=False)
184
185
run_git(dir, name, 'fetch', f"Fetching updates for {name}...", f"Couldn't fetch {name}", autofix=False)
186
187
run_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)
188
189
return
190
191
try:
192
run(f'"{git}" clone --config core.filemode=false "{url}" "{dir}"', f"Cloning {name} into {dir}...", f"Couldn't clone {name}", live=True)
193
except RuntimeError:
194
shutil.rmtree(dir, ignore_errors=True)
195
raise
196
197
if commithash is not None:
198
run(f'"{git}" -C "{dir}" checkout {commithash}', None, "Couldn't checkout {name}'s hash: {commithash}")
199
200
201
def git_pull_recursive(dir):
202
for subdir, _, _ in os.walk(dir):
203
if os.path.exists(os.path.join(subdir, '.git')):
204
try:
205
output = subprocess.check_output([git, '-C', subdir, 'pull', '--autostash'])
206
print(f"Pulled changes for repository in '{subdir}':\n{output.decode('utf-8').strip()}\n")
207
except subprocess.CalledProcessError as e:
208
print(f"Couldn't perform 'git pull' on repository in '{subdir}':\n{e.output.decode('utf-8').strip()}\n")
209
210
211
def version_check(commit):
212
try:
213
import requests
214
commits = requests.get('https://api.github.com/repos/AUTOMATIC1111/stable-diffusion-webui/branches/master').json()
215
if commit != "<none>" and commits['commit']['sha'] != commit:
216
print("--------------------------------------------------------")
217
print("| You are not up to date with the most recent release. |")
218
print("| Consider running `git pull` to update. |")
219
print("--------------------------------------------------------")
220
elif commits['commit']['sha'] == commit:
221
print("You are up to date with the most recent release.")
222
else:
223
print("Not a git clone, can't perform version check.")
224
except Exception as e:
225
print("version check failed", e)
226
227
228
def run_extension_installer(extension_dir):
229
path_installer = os.path.join(extension_dir, "install.py")
230
if not os.path.isfile(path_installer):
231
return
232
233
try:
234
env = os.environ.copy()
235
env['PYTHONPATH'] = f"{script_path}{os.pathsep}{env.get('PYTHONPATH', '')}"
236
237
stdout = run(f'"{python}" "{path_installer}"', errdesc=f"Error running install.py for extension {extension_dir}", custom_env=env).strip()
238
if stdout:
239
print(stdout)
240
except Exception as e:
241
errors.report(str(e))
242
243
244
def list_extensions(settings_file):
245
settings = {}
246
247
try:
248
with open(settings_file, "r", encoding="utf8") as file:
249
settings = json.load(file)
250
except FileNotFoundError:
251
pass
252
except Exception:
253
errors.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)
254
os.replace(settings_file, os.path.join(script_path, "tmp", "config.json"))
255
256
disabled_extensions = set(settings.get('disabled_extensions', []))
257
disable_all_extensions = settings.get('disable_all_extensions', 'none')
258
259
if disable_all_extensions != 'none' or args.disable_extra_extensions or args.disable_all_extensions or not os.path.isdir(extensions_dir):
260
return []
261
262
return [x for x in os.listdir(extensions_dir) if x not in disabled_extensions]
263
264
265
def run_extensions_installers(settings_file):
266
if not os.path.isdir(extensions_dir):
267
return
268
269
with startup_timer.subcategory("run extensions installers"):
270
for dirname_extension in list_extensions(settings_file):
271
logging.debug(f"Installing {dirname_extension}")
272
273
path = os.path.join(extensions_dir, dirname_extension)
274
275
if os.path.isdir(path):
276
run_extension_installer(path)
277
startup_timer.record(dirname_extension)
278
279
280
re_requirement = re.compile(r"\s*([-_a-zA-Z0-9]+)\s*(?:==\s*([-+_.a-zA-Z0-9]+))?\s*")
281
282
283
def requirements_met(requirements_file):
284
"""
285
Does a simple parse of a requirements.txt file to determine if all rerqirements in it
286
are already installed. Returns True if so, False if not installed or parsing fails.
287
"""
288
289
import importlib.metadata
290
import packaging.version
291
292
with open(requirements_file, "r", encoding="utf8") as file:
293
for line in file:
294
if line.strip() == "":
295
continue
296
297
m = re.match(re_requirement, line)
298
if m is None:
299
return False
300
301
package = m.group(1).strip()
302
version_required = (m.group(2) or "").strip()
303
304
if version_required == "":
305
continue
306
307
try:
308
version_installed = importlib.metadata.version(package)
309
except Exception:
310
return False
311
312
if packaging.version.parse(version_required) != packaging.version.parse(version_installed):
313
return False
314
315
return True
316
317
318
def prepare_environment():
319
torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu121")
320
torch_command = os.environ.get('TORCH_COMMAND', f"pip install torch==2.1.2 torchvision==0.16.2 --extra-index-url {torch_index_url}")
321
if args.use_ipex:
322
if platform.system() == "Windows":
323
# 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-main
324
# This is NOT an Intel official release so please use it at your own risk!!
325
# See https://github.com/Nuullll/intel-extension-for-pytorch/releases/tag/v2.0.110%2Bxpu-master%2Bdll-bundle for details.
326
#
327
# Strengths (over official IPEX 2.0.110 windows release):
328
# - AOT build (for Arc GPU only) to eliminate JIT compilation overhead: https://github.com/intel/intel-extension-for-pytorch/issues/399
329
# - Bundles minimal oneAPI 2023.2 dependencies into the python wheels, so users don't need to install oneAPI for the whole system.
330
# - Provides a compatible torchvision wheel: https://github.com/intel/intel-extension-for-pytorch/issues/465
331
# Limitation:
332
# - Only works for python 3.10
333
url_prefix = "https://github.com/Nuullll/intel-extension-for-pytorch/releases/download/v2.0.110%2Bxpu-master%2Bdll-bundle"
334
torch_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")
335
else:
336
# Using official IPEX release for linux since it's already an AOT build.
337
# However, users still have to install oneAPI toolkit and activate oneAPI environment manually.
338
# See https://intel.github.io/intel-extension-for-pytorch/index.html#installation for details.
339
torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://pytorch-extension.intel.com/release-whl/stable/xpu/us/")
340
torch_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}")
341
requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt")
342
requirements_file_for_npu = os.environ.get('REQS_FILE_FOR_NPU', "requirements_npu.txt")
343
344
xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.23.post1')
345
clip_package = os.environ.get('CLIP_PACKAGE', "https://github.com/openai/CLIP/archive/d50d76daa670286dd6cacf3bcd80b5e4823fc8e1.zip")
346
openclip_package = os.environ.get('OPENCLIP_PACKAGE', "https://github.com/mlfoundations/open_clip/archive/bb6e834e9c70d9c27d0dc3ecedeebeaeb1ffad6b.zip")
347
348
assets_repo = os.environ.get('ASSETS_REPO', "https://github.com/AUTOMATIC1111/stable-diffusion-webui-assets.git")
349
stable_diffusion_repo = os.environ.get('STABLE_DIFFUSION_REPO', "https://github.com/Stability-AI/stablediffusion.git")
350
stable_diffusion_xl_repo = os.environ.get('STABLE_DIFFUSION_XL_REPO', "https://github.com/Stability-AI/generative-models.git")
351
k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git')
352
blip_repo = os.environ.get('BLIP_REPO', 'https://github.com/salesforce/BLIP.git')
353
354
assets_commit_hash = os.environ.get('ASSETS_COMMIT_HASH', "6f7db241d2f8ba7457bac5ca9753331f0c266917")
355
stable_diffusion_commit_hash = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "cf1d67a6fd5ea1aa600c4df58e5b47da45f6bdbf")
356
stable_diffusion_xl_commit_hash = os.environ.get('STABLE_DIFFUSION_XL_COMMIT_HASH', "45c443b316737a4ab6e40413d7794a7f5657c19f")
357
k_diffusion_commit_hash = os.environ.get('K_DIFFUSION_COMMIT_HASH', "ab527a9a6d347f364e3d185ba6d714e22d80cb3c")
358
blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9")
359
360
try:
361
# the existence of this file is a signal to webui.sh/bat that webui needs to be restarted when it stops execution
362
os.remove(os.path.join(script_path, "tmp", "restart"))
363
os.environ.setdefault('SD_WEBUI_RESTARTING', '1')
364
except OSError:
365
pass
366
367
if not args.skip_python_version_check:
368
check_python_version()
369
370
startup_timer.record("checks")
371
372
commit = commit_hash()
373
tag = git_tag()
374
startup_timer.record("git version info")
375
376
print(f"Python {sys.version}")
377
print(f"Version: {tag}")
378
print(f"Commit hash: {commit}")
379
380
if args.reinstall_torch or not is_installed("torch") or not is_installed("torchvision"):
381
run(f'"{python}" -m {torch_command}', "Installing torch and torchvision", "Couldn't install torch", live=True)
382
startup_timer.record("install torch")
383
384
if args.use_ipex:
385
args.skip_torch_cuda_test = True
386
if not args.skip_torch_cuda_test and not check_run_python("import torch; assert torch.cuda.is_available()"):
387
raise RuntimeError(
388
'Torch is not able to use GPU; '
389
'add --skip-torch-cuda-test to COMMANDLINE_ARGS variable to disable this check'
390
)
391
startup_timer.record("torch GPU test")
392
393
if not is_installed("clip"):
394
run_pip(f"install {clip_package}", "clip")
395
startup_timer.record("install clip")
396
397
if not is_installed("open_clip"):
398
run_pip(f"install {openclip_package}", "open_clip")
399
startup_timer.record("install open_clip")
400
401
if (not is_installed("xformers") or args.reinstall_xformers) and args.xformers:
402
run_pip(f"install -U -I --no-deps {xformers_package}", "xformers")
403
startup_timer.record("install xformers")
404
405
if not is_installed("ngrok") and args.ngrok:
406
run_pip("install ngrok", "ngrok")
407
startup_timer.record("install ngrok")
408
409
os.makedirs(os.path.join(script_path, dir_repos), exist_ok=True)
410
411
git_clone(assets_repo, repo_dir('stable-diffusion-webui-assets'), "assets", assets_commit_hash)
412
git_clone(stable_diffusion_repo, repo_dir('stable-diffusion-stability-ai'), "Stable Diffusion", stable_diffusion_commit_hash)
413
git_clone(stable_diffusion_xl_repo, repo_dir('generative-models'), "Stable Diffusion XL", stable_diffusion_xl_commit_hash)
414
git_clone(k_diffusion_repo, repo_dir('k-diffusion'), "K-diffusion", k_diffusion_commit_hash)
415
git_clone(blip_repo, repo_dir('BLIP'), "BLIP", blip_commit_hash)
416
417
startup_timer.record("clone repositores")
418
419
if not os.path.isfile(requirements_file):
420
requirements_file = os.path.join(script_path, requirements_file)
421
422
if not requirements_met(requirements_file):
423
run_pip(f"install -r \"{requirements_file}\"", "requirements")
424
startup_timer.record("install requirements")
425
426
if not os.path.isfile(requirements_file_for_npu):
427
requirements_file_for_npu = os.path.join(script_path, requirements_file_for_npu)
428
429
if "torch_npu" in torch_command and not requirements_met(requirements_file_for_npu):
430
run_pip(f"install -r \"{requirements_file_for_npu}\"", "requirements_for_npu")
431
startup_timer.record("install requirements_for_npu")
432
433
if not args.skip_install:
434
run_extensions_installers(settings_file=args.ui_settings_file)
435
436
if args.update_check:
437
version_check(commit)
438
startup_timer.record("check version")
439
440
if args.update_all_extensions:
441
git_pull_recursive(extensions_dir)
442
startup_timer.record("update extensions")
443
444
if "--exit" in sys.argv:
445
print("Exiting because of --exit argument")
446
exit(0)
447
448
449
def configure_for_tests():
450
if "--api" not in sys.argv:
451
sys.argv.append("--api")
452
if "--ckpt" not in sys.argv:
453
sys.argv.append("--ckpt")
454
sys.argv.append(os.path.join(script_path, "test/test_files/empty.pt"))
455
if "--skip-torch-cuda-test" not in sys.argv:
456
sys.argv.append("--skip-torch-cuda-test")
457
if "--disable-nan-check" not in sys.argv:
458
sys.argv.append("--disable-nan-check")
459
460
os.environ['COMMANDLINE_ARGS'] = ""
461
462
463
def start():
464
print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {shlex.join(sys.argv[1:])}")
465
import webui
466
if '--nowebui' in sys.argv:
467
webui.api_only()
468
else:
469
webui.webui()
470
471
472
def dump_sysinfo():
473
from modules import sysinfo
474
import datetime
475
476
text = sysinfo.get()
477
filename = f"sysinfo-{datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M')}.json"
478
479
with open(filename, "w", encoding="utf8") as file:
480
file.write(text)
481
482
return filename
483
484