Skip to content

Commit 2cd0e03

Browse files
authored
Merge branch 'v2.3' into bugfix/lora-incompatibility-handling
2 parents 264af3c + c088cf0 commit 2cd0e03

File tree

8 files changed

+215
-156
lines changed

8 files changed

+215
-156
lines changed

installer/lib/installer.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,13 @@ def app_venv(self, path: str = None):
132132

133133
# Prefer to copy python executables
134134
# so that updates to system python don't break InvokeAI
135-
try:
136-
venv.create(venv_dir, with_pip=True)
137-
# If installing over an existing environment previously created with symlinks,
138-
# the executables will fail to copy. Keep symlinks in that case
139-
except shutil.SameFileError:
140-
venv.create(venv_dir, with_pip=True, symlinks=True)
135+
if not venv_dir.exists():
136+
try:
137+
venv.create(venv_dir, with_pip=True)
138+
# If installing over an existing environment previously created with symlinks,
139+
# the executables will fail to copy. Keep symlinks in that case
140+
except shutil.SameFileError:
141+
venv.create(venv_dir, with_pip=True, symlinks=True)
141142

142143
# upgrade pip in Python 3.9 environments
143144
if int(platform.python_version_tuple()[1]) == 9:

ldm/invoke/CLI.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import sys
55
import traceback
66
from argparse import Namespace
7-
from packaging import version
87
from pathlib import Path
98
from typing import Union
109

@@ -26,7 +25,6 @@
2625
from .globals import Globals, global_config_dir
2726
from .image_util import make_grid
2827
from .log import write_log
29-
from .model_manager import ModelManager
3028
from .pngwriter import PngWriter, retrieve_metadata, write_metadata
3129
from .readline import Completer, get_completer
3230
from ..util import url_attachment_name
@@ -66,9 +64,6 @@ def main():
6664
Globals.sequential_guidance = args.sequential_guidance
6765
Globals.ckpt_convert = True # always true as of 2.3.4 for LoRA support
6866

69-
# run any post-install patches needed
70-
run_patches()
71-
7267
print(f">> Internet connectivity is {Globals.internet_available}")
7368

7469
if not args.conf:
@@ -113,9 +108,6 @@ def main():
113108
if opt.lora_path:
114109
Globals.lora_models_dir = opt.lora_path
115110

116-
# migrate legacy models
117-
ModelManager.migrate_models()
118-
119111
# load the infile as a list of lines
120112
if opt.infile:
121113
try:
@@ -1299,62 +1291,6 @@ def retrieve_last_used_model()->str:
12991291
with open(model_file_path,'r') as f:
13001292
return f.readline()
13011293

1302-
# This routine performs any patch-ups needed after installation
1303-
def run_patches():
1304-
install_missing_config_files()
1305-
version_file = Path(Globals.root,'.version')
1306-
if version_file.exists():
1307-
with open(version_file,'r') as f:
1308-
root_version = version.parse(f.readline() or 'v2.3.2')
1309-
else:
1310-
root_version = version.parse('v2.3.2')
1311-
app_version = version.parse(ldm.invoke.__version__)
1312-
if root_version < app_version:
1313-
try:
1314-
do_version_update(root_version, ldm.invoke.__version__)
1315-
with open(version_file,'w') as f:
1316-
f.write(ldm.invoke.__version__)
1317-
except:
1318-
print("** Update failed. Will try again on next launch")
1319-
1320-
def install_missing_config_files():
1321-
"""
1322-
install ckpt configuration files that may have been added to the
1323-
distro after original root directory configuration
1324-
"""
1325-
import invokeai.configs as conf
1326-
from shutil import copyfile
1327-
1328-
root_configs = Path(global_config_dir(), 'stable-diffusion')
1329-
repo_configs = Path(conf.__path__[0], 'stable-diffusion')
1330-
for src in repo_configs.iterdir():
1331-
dest = root_configs / src.name
1332-
if not dest.exists():
1333-
copyfile(src,dest)
1334-
1335-
def do_version_update(root_version: version.Version, app_version: Union[str, version.Version]):
1336-
"""
1337-
Make any updates to the launcher .sh and .bat scripts that may be needed
1338-
from release to release. This is not an elegant solution. Instead, the
1339-
launcher should be moved into the source tree and installed using pip.
1340-
"""
1341-
if root_version < version.Version('v2.3.4'):
1342-
dest = Path(Globals.root,'loras')
1343-
dest.mkdir(exist_ok=True)
1344-
if root_version < version.Version('v2.3.3'):
1345-
if sys.platform == "linux":
1346-
print('>> Downloading new version of launcher script and its config file')
1347-
from ldm.util import download_with_progress_bar
1348-
url_base = f'https://raw.githubusercontent.com/invoke-ai/InvokeAI/v{str(app_version)}/installer/templates/'
1349-
1350-
dest = Path(Globals.root,'invoke.sh.in')
1351-
assert download_with_progress_bar(url_base+'invoke.sh.in',dest)
1352-
dest.replace(Path(Globals.root,'invoke.sh'))
1353-
os.chmod(Path(Globals.root,'invoke.sh'), 0o0755)
1354-
1355-
dest = Path(Globals.root,'dialogrc')
1356-
assert download_with_progress_bar(url_base+'dialogrc',dest)
1357-
dest.replace(Path(Globals.root,'.dialogrc'))
13581294

13591295
if __name__ == '__main__':
13601296
main()

ldm/invoke/config/invokeai_configure.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from shutil import get_terminal_size
2222

2323
import npyscreen
24-
import torch
2524
import transformers
2625
from diffusers import AutoencoderKL
2726
from huggingface_hub import HfFolder
@@ -664,8 +663,19 @@ def initialize_rootdir(root: str, yes_to_all: bool = False):
664663
configs_src = Path(configs.__path__[0])
665664
configs_dest = Path(root) / "configs"
666665
if not os.path.samefile(configs_src, configs_dest):
667-
shutil.copytree(configs_src, configs_dest, dirs_exist_ok=True)
668-
666+
shutil.copytree(configs_src,
667+
configs_dest,
668+
dirs_exist_ok=True,
669+
copy_function=shutil.copyfile,
670+
)
671+
# Fix up directory permissions so that they are writable
672+
# This can happen when running under Nix environment which
673+
# makes the runtime directory template immutable.
674+
for root,dirs,files in os.walk(os.path.join(root,name)):
675+
for d in dirs:
676+
Path(root,d).chmod(0o775)
677+
for f in files:
678+
Path(root,d).chmod(0o644)
669679

670680
# -------------------------------------
671681
def run_console_ui(

ldm/invoke/config/invokeai_update.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,18 @@ def invokeai_is_running()->bool:
4242
except psutil.AccessDenied:
4343
continue
4444
return False
45+
46+
def do_post_install():
47+
'''
48+
Run postinstallation script.
49+
'''
50+
print("Looking for postinstallation script to run on this version...")
51+
try:
52+
from ldm.invoke.config.post_install.py import post_install
53+
post_install()
54+
except:
55+
print("Postinstallation script not available for this version of InvokeAI")
4556

46-
4757
def welcome(versions: dict):
4858

4959
@group()
@@ -107,6 +117,7 @@ def main():
107117
print(f':heavy_check_mark: Upgrade successful')
108118
else:
109119
print(f':exclamation: [bold red]Upgrade failed[/red bold]')
120+
do_post_install()
110121

111122
if __name__ == "__main__":
112123
try:

ldm/invoke/config/model_install_backend.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,19 @@ def update_config_file(successfully_downloaded: dict, config_file: Path):
398398
if config_file is default_config_file() and not config_file.parent.exists():
399399
configs_src = Dataset_path.parent
400400
configs_dest = default_config_file().parent
401-
shutil.copytree(configs_src, configs_dest, dirs_exist_ok=True)
401+
shutil.copytree(configs_src,
402+
configs_dest,
403+
dirs_exist_ok=True,
404+
copy_function=shutil.copyfile,
405+
)
406+
# Fix up directory permissions so that they are writable
407+
# This can happen when running under Nix environment which
408+
# makes the runtime directory template immutable.
409+
for root,dirs,files in os.walk(default_config_file().parent):
410+
for d in dirs:
411+
Path(root,d).chmod(0o775)
412+
for f in files:
413+
Path(root,d).chmod(0o644)
402414

403415
yaml = new_config_file_contents(successfully_downloaded, config_file)
404416

ldm/invoke/config/post_install.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
'''ldm.invoke.config.post_install
2+
3+
This defines a single exportable function, post_install(), which does
4+
post-installation stuff like migrating models directories, adding new
5+
config files, etc.
6+
7+
From the command line, its entry point is invokeai-postinstall.
8+
'''
9+
10+
import os
11+
import sys
12+
from packaging import version
13+
from pathlib import Path
14+
from shutil import move,rmtree,copyfile
15+
from typing import Union
16+
17+
import invokeai.configs as conf
18+
import ldm.invoke
19+
from ..globals import Globals, global_cache_dir, global_config_dir
20+
21+
def post_install():
22+
'''
23+
Do version and model updates, etc.
24+
Should be called once after every version update.
25+
'''
26+
_migrate_models()
27+
_run_patches()
28+
29+
30+
def _migrate_models():
31+
"""
32+
Migrate the ~/invokeai/models directory from the legacy format used through 2.2.5
33+
to the 2.3.0 "diffusers" version. This should be a one-time operation, called at
34+
script startup time.
35+
"""
36+
# Three transformer models to check: bert, clip and safety checker, and
37+
# the diffusers as well
38+
models_dir = Path(Globals.root, "models")
39+
legacy_locations = [
40+
Path(
41+
models_dir,
42+
"CompVis/stable-diffusion-safety-checker/models--CompVis--stable-diffusion-safety-checker",
43+
),
44+
Path("bert-base-uncased/models--bert-base-uncased"),
45+
Path(
46+
"openai/clip-vit-large-patch14/models--openai--clip-vit-large-patch14"
47+
),
48+
]
49+
legacy_locations.extend(list(global_cache_dir("diffusers").glob("*")))
50+
legacy_layout = False
51+
for model in legacy_locations:
52+
legacy_layout = legacy_layout or model.exists()
53+
if not legacy_layout:
54+
return
55+
56+
print(
57+
"""
58+
>> ALERT:
59+
>> The location of your previously-installed diffusers models needs to move from
60+
>> invokeai/models/diffusers to invokeai/models/hub due to a change introduced by
61+
>> diffusers version 0.14. InvokeAI will now move all models from the "diffusers" directory
62+
>> into "hub" and then remove the diffusers directory. This is a quick, safe, one-time
63+
>> operation. However if you have customized either of these directories and need to
64+
>> make adjustments, please press ctrl-C now to abort and relaunch InvokeAI when you are ready.
65+
>> Otherwise press <enter> to continue."""
66+
)
67+
print("** This is a quick one-time operation.")
68+
input("continue> ")
69+
70+
# transformer files get moved into the hub directory
71+
if _is_huggingface_hub_directory_present():
72+
hub = global_cache_dir("hub")
73+
else:
74+
hub = models_dir / "hub"
75+
76+
os.makedirs(hub, exist_ok=True)
77+
for model in legacy_locations:
78+
source = models_dir / model
79+
dest = hub / model.stem
80+
if dest.exists() and not source.exists():
81+
continue
82+
print(f"** {source} => {dest}")
83+
if source.exists():
84+
if dest.is_symlink():
85+
print(f"** Found symlink at {dest.name}. Not migrating.")
86+
elif dest.exists():
87+
if source.is_dir():
88+
rmtree(source)
89+
else:
90+
source.unlink()
91+
else:
92+
move(source, dest)
93+
94+
# now clean up by removing any empty directories
95+
empty = [
96+
root
97+
for root, dirs, files, in os.walk(models_dir)
98+
if not len(dirs) and not len(files)
99+
]
100+
for d in empty:
101+
os.rmdir(d)
102+
print("** Migration is done. Continuing...")
103+
104+
105+
def _is_huggingface_hub_directory_present() -> bool:
106+
return (
107+
os.getenv("HF_HOME") is not None or os.getenv("XDG_CACHE_HOME") is not None
108+
)
109+
110+
# This routine performs any patch-ups needed after installation
111+
def _run_patches():
112+
_install_missing_config_files()
113+
version_file = Path(Globals.root,'.version')
114+
if version_file.exists():
115+
with open(version_file,'r') as f:
116+
root_version = version.parse(f.readline() or 'v2.3.2')
117+
else:
118+
root_version = version.parse('v2.3.2')
119+
app_version = version.parse(ldm.invoke.__version__)
120+
if root_version < app_version:
121+
try:
122+
_do_version_update(root_version, ldm.invoke.__version__)
123+
with open(version_file,'w') as f:
124+
f.write(ldm.invoke.__version__)
125+
except:
126+
print("** Version patching failed. Please try invokeai-postinstall later.")
127+
128+
def _install_missing_config_files():
129+
"""
130+
install ckpt configuration files that may have been added to the
131+
distro after original root directory configuration
132+
"""
133+
root_configs = Path(global_config_dir(), 'stable-diffusion')
134+
repo_configs = None
135+
for f in conf.__path__:
136+
if Path(f, 'stable-diffusion', 'v1-inference.yaml').exists():
137+
repo_configs = Path(f, 'stable-diffusion')
138+
break
139+
if not repo_configs:
140+
return
141+
for src in repo_configs.iterdir():
142+
dest = root_configs / src.name
143+
if not dest.exists():
144+
copyfile(src,dest)
145+
146+
def _do_version_update(root_version: version.Version, app_version: Union[str, version.Version]):
147+
"""
148+
Make any updates to the launcher .sh and .bat scripts that may be needed
149+
from release to release. This is not an elegant solution. Instead, the
150+
launcher should be moved into the source tree and installed using pip.
151+
"""
152+
if root_version < version.Version('v2.3.4'):
153+
dest = Path(Globals.root,'loras')
154+
dest.mkdir(exist_ok=True)
155+
if root_version < version.Version('v2.3.3'):
156+
if sys.platform == "linux":
157+
print('>> Downloading new version of launcher script and its config file')
158+
from ldm.util import download_with_progress_bar
159+
url_base = f'https://raw.githubusercontent.com/invoke-ai/InvokeAI/v{str(app_version)}/installer/templates/'
160+
161+
dest = Path(Globals.root,'invoke.sh.in')
162+
assert download_with_progress_bar(url_base+'invoke.sh.in',dest)
163+
dest.replace(Path(Globals.root,'invoke.sh'))
164+
os.chmod(Path(Globals.root,'invoke.sh'), 0o0755)
165+
166+
dest = Path(Globals.root,'dialogrc')
167+
assert download_with_progress_bar(url_base+'dialogrc',dest)
168+
dest.replace(Path(Globals.root,'.dialogrc'))

0 commit comments

Comments
 (0)