InvokeAI 2.2.4.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- backend/__init__.py +0 -0
- backend/invoke_ai_web_server.py +1724 -0
- backend/modules/__init__.py +0 -0
- backend/modules/create_cmd_parser.py +55 -0
- backend/modules/get_canvas_generation_mode.py +117 -0
- backend/modules/parameters.py +71 -0
- backend/modules/parse_seed_weights.py +47 -0
- configs/INITIAL_MODELS.yaml +80 -0
- configs/models.yaml.example +29 -0
- configs/sd-concepts.txt +803 -0
- configs/stable-diffusion/v1-finetune.yaml +110 -0
- configs/stable-diffusion/v1-finetune_style.yaml +103 -0
- configs/stable-diffusion/v1-inference.yaml +79 -0
- configs/stable-diffusion/v1-inpainting-inference.yaml +79 -0
- configs/stable-diffusion/v1-m1-finetune.yaml +110 -0
- docs/assets/invoke-web-server-1.png +0 -0
- docs/assets/invoke_ai_banner.png +0 -0
- invokeai-2.2.4.5.data/data/assets/caution.png +0 -0
- invokeai-2.2.4.5.data/data/configs/INITIAL_MODELS.yaml +80 -0
- invokeai-2.2.4.5.data/data/configs/models.yaml.example +29 -0
- invokeai-2.2.4.5.data/data/configs/sd-concepts.txt +803 -0
- invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-finetune.yaml +110 -0
- invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-finetune_style.yaml +103 -0
- invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-inference.yaml +79 -0
- invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-inpainting-inference.yaml +79 -0
- invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-m1-finetune.yaml +110 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/Inter-Bold.790c108b.ttf +0 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/Inter.b9a8e5e2.ttf +0 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/favicon.0d253ced.ico +0 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/index-legacy-b741077f.js +48 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/index.0dadf5d0.css +1 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/index.21ff81eb.js +625 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/logo.13003d72.png +0 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/polyfills-legacy-dde3a68a.js +4 -0
- invokeai-2.2.4.5.data/data/frontend/dist/assets/polyfills.1ff60148.js +1 -0
- invokeai-2.2.4.5.data/data/frontend/dist/index.html +23 -0
- invokeai-2.2.4.5.data/data/scripts/configure_invokeai.py +797 -0
- invokeai-2.2.4.5.data/data/scripts/create_root.py +43 -0
- invokeai-2.2.4.5.data/data/scripts/dream.py +10 -0
- invokeai-2.2.4.5.data/data/scripts/images2prompt.py +30 -0
- invokeai-2.2.4.5.data/data/scripts/invoke.py +5 -0
- invokeai-2.2.4.5.data/data/scripts/legacy_api.py +681 -0
- invokeai-2.2.4.5.data/data/scripts/load_models.py +12 -0
- invokeai-2.2.4.5.data/data/scripts/merge_embeddings.py +124 -0
- invokeai-2.2.4.5.data/data/scripts/preload_models.py +12 -0
- invokeai-2.2.4.5.data/data/scripts/sd-metadata.py +22 -0
- invokeai-2.2.4.5.dist-info/LICENSE +21 -0
- invokeai-2.2.4.5.dist-info/LICENSE-ModelWeights.txt +294 -0
- invokeai-2.2.4.5.dist-info/METADATA +288 -0
- invokeai-2.2.4.5.dist-info/RECORD +169 -0
- invokeai-2.2.4.5.dist-info/WHEEL +5 -0
- invokeai-2.2.4.5.dist-info/entry_points.txt +4 -0
- invokeai-2.2.4.5.dist-info/top_level.txt +6 -0
- invokeai-2.2.4.5.dist-info/zip-safe +1 -0
- ldm/__init__.py +0 -0
- ldm/data/__init__.py +0 -0
- ldm/data/base.py +31 -0
- ldm/data/imagenet.py +466 -0
- ldm/data/lsun.py +126 -0
- ldm/data/personalized.py +202 -0
- ldm/data/personalized_style.py +169 -0
- ldm/generate.py +1159 -0
- ldm/invoke/CLI.py +960 -0
- ldm/invoke/__init__.py +3 -0
- ldm/invoke/args.py +1257 -0
- ldm/invoke/concepts_lib.py +169 -0
- ldm/invoke/conditioning.py +289 -0
- ldm/invoke/devices.py +27 -0
- ldm/invoke/generator/__init__.py +4 -0
- ldm/invoke/generator/base.py +335 -0
- ldm/invoke/generator/embiggen.py +501 -0
- ldm/invoke/generator/img2img.py +97 -0
- ldm/invoke/generator/inpaint.py +358 -0
- ldm/invoke/generator/omnibus.py +175 -0
- ldm/invoke/generator/txt2img.py +88 -0
- ldm/invoke/generator/txt2img2img.py +182 -0
- ldm/invoke/globals.py +31 -0
- ldm/invoke/image_util.py +70 -0
- ldm/invoke/log.py +66 -0
- ldm/invoke/model_cache.py +449 -0
- ldm/invoke/patchmatch.py +44 -0
- ldm/invoke/pngwriter.py +113 -0
- ldm/invoke/prompt_parser.py +655 -0
- ldm/invoke/readline.py +442 -0
- ldm/invoke/restoration/__init__.py +4 -0
- ldm/invoke/restoration/base.py +38 -0
- ldm/invoke/restoration/codeformer.py +103 -0
- ldm/invoke/restoration/codeformer_arch.py +276 -0
- ldm/invoke/restoration/gfpgan.py +84 -0
- ldm/invoke/restoration/outcrop.py +108 -0
- ldm/invoke/restoration/outpaint.py +92 -0
- ldm/invoke/restoration/realesrgan.py +90 -0
- ldm/invoke/restoration/vqgan_arch.py +435 -0
- ldm/invoke/seamless.py +30 -0
- ldm/invoke/server.py +282 -0
- ldm/invoke/server_legacy.py +246 -0
- ldm/invoke/txt2mask.py +136 -0
- ldm/lr_scheduler.py +143 -0
- ldm/models/__init__.py +0 -0
- ldm/models/autoencoder.py +596 -0
- ldm/models/diffusion/__init__.py +0 -0
- ldm/models/diffusion/classifier.py +355 -0
- ldm/models/diffusion/cross_attention_control.py +443 -0
- ldm/models/diffusion/cross_attention_map_saving.py +95 -0
- ldm/models/diffusion/ddim.py +111 -0
- ldm/models/diffusion/ddpm.py +2263 -0
- ldm/models/diffusion/ksampler.py +312 -0
- ldm/models/diffusion/plms.py +146 -0
- ldm/models/diffusion/sampler.py +450 -0
- ldm/models/diffusion/shared_invokeai_diffusion.py +244 -0
- ldm/modules/__init__.py +0 -0
- ldm/modules/attention.py +274 -0
- ldm/modules/diffusionmodules/__init__.py +0 -0
- ldm/modules/diffusionmodules/model.py +894 -0
- ldm/modules/diffusionmodules/openaimodel.py +1036 -0
- ldm/modules/diffusionmodules/util.py +314 -0
- ldm/modules/distributions/__init__.py +0 -0
- ldm/modules/distributions/distributions.py +104 -0
- ldm/modules/ema.py +88 -0
- ldm/modules/embedding_manager.py +413 -0
- ldm/modules/encoders/__init__.py +0 -0
- ldm/modules/encoders/modules.py +772 -0
- ldm/modules/image_degradation/__init__.py +6 -0
- ldm/modules/image_degradation/bsrgan.py +846 -0
- ldm/modules/image_degradation/bsrgan_light.py +751 -0
- ldm/modules/image_degradation/utils_image.py +1024 -0
- ldm/modules/losses/__init__.py +1 -0
- ldm/modules/losses/contperceptual.py +175 -0
- ldm/modules/losses/vqperceptual.py +240 -0
- ldm/modules/x_transformer.py +752 -0
- ldm/simplet2i.py +13 -0
- ldm/util.py +264 -0
- scripts/configure_invokeai.py +797 -0
- scripts/create_root.py +43 -0
- scripts/dream.py +10 -0
- scripts/images2prompt.py +30 -0
- scripts/invoke.py +5 -0
- scripts/legacy_api.py +681 -0
- scripts/load_models.py +12 -0
- scripts/merge_embeddings.py +124 -0
- scripts/orig_scripts/img2img.py +296 -0
- scripts/orig_scripts/inpaint.py +98 -0
- scripts/orig_scripts/knn2img.py +397 -0
- scripts/orig_scripts/sample_diffusion.py +313 -0
- scripts/orig_scripts/train_searcher.py +147 -0
- scripts/orig_scripts/txt2img.py +324 -0
- scripts/preload_models.py +12 -0
- scripts/sd-metadata.py +22 -0
- vendored/clip/clip/__init__.py +1 -0
- vendored/clip/clip/bpe_simple_vocab_16e6.txt.gz +0 -0
- vendored/clip/clip/clip.py +237 -0
- vendored/clip/clip/model.py +436 -0
- vendored/clip/clip/simple_tokenizer.py +132 -0
- vendored/clip/hubconf.py +42 -0
- vendored/clip/setup.py +21 -0
- vendored/clip/tests/test_consistency.py +25 -0
- vendored/clipseg/datasets/coco_wrapper.py +99 -0
- vendored/clipseg/datasets/pascal_zeroshot.py +60 -0
- vendored/clipseg/datasets/pfe_dataset.py +129 -0
- vendored/clipseg/datasets/phrasecut.py +380 -0
- vendored/clipseg/datasets/utils.py +68 -0
- vendored/clipseg/evaluation_utils.py +292 -0
- vendored/clipseg/general_utils.py +272 -0
- vendored/clipseg/metrics.py +271 -0
- vendored/clipseg/models/clipseg.py +538 -0
- vendored/clipseg/models/vitseg.py +286 -0
- vendored/clipseg/score.py +453 -0
- vendored/clipseg/setup.py +30 -0
- vendored/clipseg/training.py +266 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Query and install embeddings from the HuggingFace SD Concepts Library
|
|
3
|
+
at https://huggingface.co/sd-concepts-library.
|
|
4
|
+
|
|
5
|
+
The interface is through the Concepts() object.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import traceback
|
|
10
|
+
from typing import Callable
|
|
11
|
+
from urllib import request, error as ul_error
|
|
12
|
+
from huggingface_hub import HfFolder, hf_hub_url, ModelSearchArguments, ModelFilter, HfApi
|
|
13
|
+
from ldm.invoke.globals import Globals
|
|
14
|
+
|
|
15
|
+
class Concepts(object):
|
|
16
|
+
def __init__(self, root=None):
|
|
17
|
+
'''
|
|
18
|
+
Initialize the Concepts object. May optionally pass a root directory.
|
|
19
|
+
'''
|
|
20
|
+
self.root = root or Globals.root
|
|
21
|
+
self.hf_api = HfApi()
|
|
22
|
+
self.concept_list = None
|
|
23
|
+
self.concepts_loaded = dict()
|
|
24
|
+
self.triggers = dict() # concept name to trigger phrase
|
|
25
|
+
self.concept_names = dict() # trigger phrase to concept name
|
|
26
|
+
self.match_trigger = re.compile('(<[\w\- >]+>)') # trigger is slightly less restrictive than HF concept name
|
|
27
|
+
self.match_concept = re.compile('<([\w\-]+)>') # HF concept name can only contain A-Za-z0-9_-
|
|
28
|
+
|
|
29
|
+
def list_concepts(self)->list:
|
|
30
|
+
'''
|
|
31
|
+
Return a list of all the concepts by name, without the 'sd-concepts-library' part.
|
|
32
|
+
'''
|
|
33
|
+
if self.concept_list is not None:
|
|
34
|
+
return self.concept_list
|
|
35
|
+
try:
|
|
36
|
+
models = self.hf_api.list_models(filter=ModelFilter(model_name='sd-concepts-library/'))
|
|
37
|
+
self.concept_list = [a.id.split('/')[1] for a in models]
|
|
38
|
+
except Exception as e:
|
|
39
|
+
print(f' ** WARNING: Hugging Face textual inversion concepts libraries could not be loaded. The error was {str(e)}.')
|
|
40
|
+
print(' ** You may load .bin and .pt file(s) manually using the --embedding_directory argument.')
|
|
41
|
+
return self.concept_list
|
|
42
|
+
|
|
43
|
+
def get_concept_model_path(self, concept_name:str)->str:
|
|
44
|
+
'''
|
|
45
|
+
Returns the path to the 'learned_embeds.bin' file in
|
|
46
|
+
the named concept. Returns None if invalid or cannot
|
|
47
|
+
be downloaded.
|
|
48
|
+
'''
|
|
49
|
+
if not concept_name in self.list_concepts():
|
|
50
|
+
print(f'This concept is not known to the Hugging Face library. Generation will continue without the concept.')
|
|
51
|
+
return None
|
|
52
|
+
return self.get_concept_file(concept_name.lower(),'learned_embeds.bin')
|
|
53
|
+
|
|
54
|
+
def concept_to_trigger(self, concept_name:str)->str:
|
|
55
|
+
'''
|
|
56
|
+
Given a concept name returns its trigger by looking in the
|
|
57
|
+
"token_identifier.txt" file.
|
|
58
|
+
'''
|
|
59
|
+
if concept_name in self.triggers:
|
|
60
|
+
return self.triggers[concept_name]
|
|
61
|
+
file = self.get_concept_file(concept_name, 'token_identifier.txt', local_only=True)
|
|
62
|
+
if not file:
|
|
63
|
+
return None
|
|
64
|
+
with open(file,'r') as f:
|
|
65
|
+
trigger = f.readline()
|
|
66
|
+
trigger = trigger.strip()
|
|
67
|
+
self.triggers[concept_name] = trigger
|
|
68
|
+
self.concept_names[trigger] = concept_name
|
|
69
|
+
return trigger
|
|
70
|
+
|
|
71
|
+
def trigger_to_concept(self, trigger:str)->str:
|
|
72
|
+
'''
|
|
73
|
+
Given a trigger phrase, maps it to the concept library name.
|
|
74
|
+
Only works if concept_to_trigger() has previously been called
|
|
75
|
+
on this library. There needs to be a persistent database for
|
|
76
|
+
this.
|
|
77
|
+
'''
|
|
78
|
+
concept = self.concept_names.get(trigger,None)
|
|
79
|
+
return f'<{concept}>' if concept else f'{trigger}'
|
|
80
|
+
|
|
81
|
+
def replace_triggers_with_concepts(self, prompt:str)->str:
|
|
82
|
+
'''
|
|
83
|
+
Given a prompt string that contains <trigger> tags, replace these
|
|
84
|
+
tags with the concept name. The reason for this is so that the
|
|
85
|
+
concept names get stored in the prompt metadata. There is no
|
|
86
|
+
controlling of colliding triggers in the SD library, so it is
|
|
87
|
+
better to store the concept name (unique) than the concept trigger
|
|
88
|
+
(not necessarily unique!)
|
|
89
|
+
'''
|
|
90
|
+
if not prompt:
|
|
91
|
+
return prompt
|
|
92
|
+
triggers = self.match_trigger.findall(prompt)
|
|
93
|
+
if not triggers:
|
|
94
|
+
return prompt
|
|
95
|
+
|
|
96
|
+
def do_replace(match)->str:
|
|
97
|
+
return self.trigger_to_concept(match.group(1)) or f'<{match.group(1)}>'
|
|
98
|
+
return self.match_trigger.sub(do_replace, prompt)
|
|
99
|
+
|
|
100
|
+
def replace_concepts_with_triggers(self, prompt:str, load_concepts_callback: Callable[[list], any])->str:
|
|
101
|
+
'''
|
|
102
|
+
Given a prompt string that contains `<concept_name>` tags, replace
|
|
103
|
+
these tags with the appropriate trigger.
|
|
104
|
+
|
|
105
|
+
If any `<concept_name>` tags are found, `load_concepts_callback()` is called with a list
|
|
106
|
+
of `concepts_name` strings.
|
|
107
|
+
'''
|
|
108
|
+
concepts = self.match_concept.findall(prompt)
|
|
109
|
+
if not concepts:
|
|
110
|
+
return prompt
|
|
111
|
+
load_concepts_callback(concepts)
|
|
112
|
+
|
|
113
|
+
def do_replace(match)->str:
|
|
114
|
+
return self.concept_to_trigger(match.group(1)) or f'<{match.group(1)}>'
|
|
115
|
+
return self.match_concept.sub(do_replace, prompt)
|
|
116
|
+
|
|
117
|
+
def get_concept_file(self, concept_name:str, file_name:str='learned_embeds.bin' , local_only:bool=False)->str:
|
|
118
|
+
if not self.concept_is_downloaded(concept_name) and not local_only:
|
|
119
|
+
self.download_concept(concept_name)
|
|
120
|
+
path = os.path.join(self._concept_path(concept_name), file_name)
|
|
121
|
+
return path if os.path.exists(path) else None
|
|
122
|
+
|
|
123
|
+
def concept_is_downloaded(self, concept_name)->bool:
|
|
124
|
+
concept_directory = self._concept_path(concept_name)
|
|
125
|
+
return os.path.exists(concept_directory)
|
|
126
|
+
|
|
127
|
+
def download_concept(self,concept_name)->bool:
|
|
128
|
+
repo_id = self._concept_id(concept_name)
|
|
129
|
+
dest = self._concept_path(concept_name)
|
|
130
|
+
|
|
131
|
+
access_token = HfFolder.get_token()
|
|
132
|
+
header = [("Authorization", f'Bearer {access_token}')] if access_token else []
|
|
133
|
+
opener = request.build_opener()
|
|
134
|
+
opener.addheaders = header
|
|
135
|
+
request.install_opener(opener)
|
|
136
|
+
|
|
137
|
+
os.makedirs(dest, exist_ok=True)
|
|
138
|
+
succeeded = True
|
|
139
|
+
|
|
140
|
+
bytes = 0
|
|
141
|
+
def tally_download_size(chunk, size, total):
|
|
142
|
+
nonlocal bytes
|
|
143
|
+
if chunk==0:
|
|
144
|
+
bytes += total
|
|
145
|
+
|
|
146
|
+
print(f'>> Downloading {repo_id}...',end='')
|
|
147
|
+
try:
|
|
148
|
+
for file in ('README.md','learned_embeds.bin','token_identifier.txt','type_of_concept.txt'):
|
|
149
|
+
url = hf_hub_url(repo_id, file)
|
|
150
|
+
request.urlretrieve(url, os.path.join(dest,file),reporthook=tally_download_size)
|
|
151
|
+
except ul_error.HTTPError as e:
|
|
152
|
+
if e.code==404:
|
|
153
|
+
print(f'This concept is not known to the Hugging Face library. Generation will continue without the concept.')
|
|
154
|
+
else:
|
|
155
|
+
print(f'Failed to download {concept_name}/{file} ({str(e)}. Generation will continue without the concept.)')
|
|
156
|
+
os.rmdir(dest)
|
|
157
|
+
return False
|
|
158
|
+
except ul_error.URLError as e:
|
|
159
|
+
print(f'ERROR: {str(e)}. This may reflect a network issue. Generation will continue without the concept.')
|
|
160
|
+
os.rmdir(dest)
|
|
161
|
+
return False
|
|
162
|
+
print('...{:.2f}Kb'.format(bytes/1024))
|
|
163
|
+
return succeeded
|
|
164
|
+
|
|
165
|
+
def _concept_id(self, concept_name:str)->str:
|
|
166
|
+
return f'sd-concepts-library/{concept_name}'
|
|
167
|
+
|
|
168
|
+
def _concept_path(self, concept_name:str)->str:
|
|
169
|
+
return os.path.join(self.root,'models','sd-concepts-library',concept_name)
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
'''
|
|
2
|
+
This module handles the generation of the conditioning tensors.
|
|
3
|
+
|
|
4
|
+
Useful function exports:
|
|
5
|
+
|
|
6
|
+
get_uc_and_c_and_ec() get the conditioned and unconditioned latent, and edited conditioning if we're doing cross-attention control
|
|
7
|
+
|
|
8
|
+
'''
|
|
9
|
+
import re
|
|
10
|
+
from typing import Union
|
|
11
|
+
|
|
12
|
+
import torch
|
|
13
|
+
|
|
14
|
+
from .prompt_parser import PromptParser, Blend, FlattenedPrompt, \
|
|
15
|
+
CrossAttentionControlledFragment, CrossAttentionControlSubstitute, Fragment
|
|
16
|
+
from ..models.diffusion import cross_attention_control
|
|
17
|
+
from ..models.diffusion.shared_invokeai_diffusion import InvokeAIDiffuserComponent
|
|
18
|
+
from ..modules.encoders.modules import WeightedFrozenCLIPEmbedder
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_uc_and_c_and_ec(prompt_string, model, log_tokens=False, skip_normalize_legacy_blend=False):
|
|
22
|
+
prompt, negative_prompt = get_prompt_structure(prompt_string,
|
|
23
|
+
skip_normalize_legacy_blend=skip_normalize_legacy_blend)
|
|
24
|
+
conditioning = _get_conditioning_for_prompt(prompt, negative_prompt, model, log_tokens)
|
|
25
|
+
|
|
26
|
+
return conditioning
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_prompt_structure(prompt_string, skip_normalize_legacy_blend: bool = False) -> (
|
|
30
|
+
Union[FlattenedPrompt, Blend], FlattenedPrompt):
|
|
31
|
+
"""
|
|
32
|
+
parse the passed-in prompt string and return tuple (positive_prompt, negative_prompt)
|
|
33
|
+
"""
|
|
34
|
+
prompt, negative_prompt = _parse_prompt_string(prompt_string,
|
|
35
|
+
skip_normalize_legacy_blend=skip_normalize_legacy_blend)
|
|
36
|
+
return prompt, negative_prompt
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_tokens_for_prompt(model, parsed_prompt: FlattenedPrompt, truncate_if_too_long=True) -> [str]:
|
|
40
|
+
text_fragments = [x.text if type(x) is Fragment else
|
|
41
|
+
(" ".join([f.text for f in x.original]) if type(x) is CrossAttentionControlSubstitute else
|
|
42
|
+
str(x))
|
|
43
|
+
for x in parsed_prompt.children]
|
|
44
|
+
text = " ".join(text_fragments)
|
|
45
|
+
tokens = model.cond_stage_model.tokenizer.tokenize(text)
|
|
46
|
+
if truncate_if_too_long:
|
|
47
|
+
max_tokens_length = model.cond_stage_model.max_length - 2 # typically 75
|
|
48
|
+
tokens = tokens[0:max_tokens_length]
|
|
49
|
+
return tokens
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _parse_prompt_string(prompt_string_uncleaned, skip_normalize_legacy_blend=False) -> Union[FlattenedPrompt, Blend]:
|
|
53
|
+
# Extract Unconditioned Words From Prompt
|
|
54
|
+
unconditioned_words = ''
|
|
55
|
+
unconditional_regex = r'\[(.*?)\]'
|
|
56
|
+
unconditionals = re.findall(unconditional_regex, prompt_string_uncleaned)
|
|
57
|
+
|
|
58
|
+
if len(unconditionals) > 0:
|
|
59
|
+
unconditioned_words = ' '.join(unconditionals)
|
|
60
|
+
|
|
61
|
+
# Remove Unconditioned Words From Prompt
|
|
62
|
+
unconditional_regex_compile = re.compile(unconditional_regex)
|
|
63
|
+
clean_prompt = unconditional_regex_compile.sub(' ', prompt_string_uncleaned)
|
|
64
|
+
prompt_string_cleaned = re.sub(' +', ' ', clean_prompt)
|
|
65
|
+
else:
|
|
66
|
+
prompt_string_cleaned = prompt_string_uncleaned
|
|
67
|
+
|
|
68
|
+
pp = PromptParser()
|
|
69
|
+
|
|
70
|
+
parsed_prompt: Union[FlattenedPrompt, Blend] = None
|
|
71
|
+
legacy_blend: Blend = pp.parse_legacy_blend(prompt_string_cleaned, skip_normalize_legacy_blend)
|
|
72
|
+
if legacy_blend is not None:
|
|
73
|
+
parsed_prompt = legacy_blend
|
|
74
|
+
else:
|
|
75
|
+
# we don't support conjunctions for now
|
|
76
|
+
parsed_prompt = pp.parse_conjunction(prompt_string_cleaned).prompts[0]
|
|
77
|
+
|
|
78
|
+
parsed_negative_prompt: FlattenedPrompt = pp.parse_conjunction(unconditioned_words).prompts[0]
|
|
79
|
+
return parsed_prompt, parsed_negative_prompt
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _get_conditioning_for_prompt(parsed_prompt: Union[Blend, FlattenedPrompt], parsed_negative_prompt: FlattenedPrompt,
|
|
83
|
+
model, log_tokens=False) \
|
|
84
|
+
-> tuple[torch.Tensor, torch.Tensor, InvokeAIDiffuserComponent.ExtraConditioningInfo]:
|
|
85
|
+
"""
|
|
86
|
+
Process prompt structure and tokens, and return (conditioning, unconditioning, extra_conditioning_info)
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
if log_tokens:
|
|
90
|
+
print(f">> Parsed prompt to {parsed_prompt}")
|
|
91
|
+
print(f">> Parsed negative prompt to {parsed_negative_prompt}")
|
|
92
|
+
|
|
93
|
+
conditioning = None
|
|
94
|
+
cac_args: cross_attention_control.Arguments = None
|
|
95
|
+
|
|
96
|
+
if type(parsed_prompt) is Blend:
|
|
97
|
+
conditioning = _get_conditioning_for_blend(model, parsed_prompt, log_tokens)
|
|
98
|
+
elif type(parsed_prompt) is FlattenedPrompt:
|
|
99
|
+
if parsed_prompt.wants_cross_attention_control:
|
|
100
|
+
conditioning, cac_args = _get_conditioning_for_cross_attention_control(model, parsed_prompt, log_tokens)
|
|
101
|
+
|
|
102
|
+
else:
|
|
103
|
+
conditioning, _ = _get_embeddings_and_tokens_for_prompt(model,
|
|
104
|
+
parsed_prompt,
|
|
105
|
+
log_tokens=log_tokens,
|
|
106
|
+
log_display_label="(prompt)")
|
|
107
|
+
else:
|
|
108
|
+
raise ValueError(f"parsed_prompt is '{type(parsed_prompt)}' which is not a supported prompt type")
|
|
109
|
+
|
|
110
|
+
unconditioning, _ = _get_embeddings_and_tokens_for_prompt(model,
|
|
111
|
+
parsed_negative_prompt,
|
|
112
|
+
log_tokens=log_tokens,
|
|
113
|
+
log_display_label="(unconditioning)")
|
|
114
|
+
if isinstance(conditioning, dict):
|
|
115
|
+
# hybrid conditioning is in play
|
|
116
|
+
unconditioning, conditioning = _flatten_hybrid_conditioning(unconditioning, conditioning)
|
|
117
|
+
if cac_args is not None:
|
|
118
|
+
print(
|
|
119
|
+
">> Hybrid conditioning cannot currently be combined with cross attention control. Cross attention control will be ignored.")
|
|
120
|
+
cac_args = None
|
|
121
|
+
|
|
122
|
+
if type(parsed_prompt) is Blend:
|
|
123
|
+
blend: Blend = parsed_prompt
|
|
124
|
+
all_token_sequences = [get_tokens_for_prompt(model, p) for p in blend.prompts]
|
|
125
|
+
longest_token_sequence = max(all_token_sequences, key=lambda t: len(t))
|
|
126
|
+
eos_token_index = len(longest_token_sequence)+1
|
|
127
|
+
else:
|
|
128
|
+
tokens = get_tokens_for_prompt(model, parsed_prompt)
|
|
129
|
+
eos_token_index = len(tokens)+1
|
|
130
|
+
return (
|
|
131
|
+
unconditioning, conditioning, InvokeAIDiffuserComponent.ExtraConditioningInfo(
|
|
132
|
+
tokens_count_including_eos_bos=eos_token_index + 1,
|
|
133
|
+
cross_attention_control_args=cac_args
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _get_conditioning_for_cross_attention_control(model, prompt: FlattenedPrompt, log_tokens: bool = True):
|
|
139
|
+
original_prompt = FlattenedPrompt()
|
|
140
|
+
edited_prompt = FlattenedPrompt()
|
|
141
|
+
# for name, a0, a1, b0, b1 in edit_opcodes: only name == 'equal' is currently parsed
|
|
142
|
+
original_token_count = 0
|
|
143
|
+
edited_token_count = 0
|
|
144
|
+
edit_options = []
|
|
145
|
+
edit_opcodes = []
|
|
146
|
+
# beginning of sequence
|
|
147
|
+
edit_opcodes.append(
|
|
148
|
+
('equal', original_token_count, original_token_count + 1, edited_token_count, edited_token_count + 1))
|
|
149
|
+
edit_options.append(None)
|
|
150
|
+
original_token_count += 1
|
|
151
|
+
edited_token_count += 1
|
|
152
|
+
for fragment in prompt.children:
|
|
153
|
+
if type(fragment) is CrossAttentionControlSubstitute:
|
|
154
|
+
original_prompt.append(fragment.original)
|
|
155
|
+
edited_prompt.append(fragment.edited)
|
|
156
|
+
|
|
157
|
+
to_replace_token_count = _get_tokens_length(model, fragment.original)
|
|
158
|
+
replacement_token_count = _get_tokens_length(model, fragment.edited)
|
|
159
|
+
edit_opcodes.append(('replace',
|
|
160
|
+
original_token_count, original_token_count + to_replace_token_count,
|
|
161
|
+
edited_token_count, edited_token_count + replacement_token_count
|
|
162
|
+
))
|
|
163
|
+
original_token_count += to_replace_token_count
|
|
164
|
+
edited_token_count += replacement_token_count
|
|
165
|
+
edit_options.append(fragment.options)
|
|
166
|
+
# elif type(fragment) is CrossAttentionControlAppend:
|
|
167
|
+
# edited_prompt.append(fragment.fragment)
|
|
168
|
+
else:
|
|
169
|
+
# regular fragment
|
|
170
|
+
original_prompt.append(fragment)
|
|
171
|
+
edited_prompt.append(fragment)
|
|
172
|
+
|
|
173
|
+
count = _get_tokens_length(model, [fragment])
|
|
174
|
+
edit_opcodes.append(('equal', original_token_count, original_token_count + count, edited_token_count,
|
|
175
|
+
edited_token_count + count))
|
|
176
|
+
edit_options.append(None)
|
|
177
|
+
original_token_count += count
|
|
178
|
+
edited_token_count += count
|
|
179
|
+
# end of sequence
|
|
180
|
+
edit_opcodes.append(
|
|
181
|
+
('equal', original_token_count, original_token_count + 1, edited_token_count, edited_token_count + 1))
|
|
182
|
+
edit_options.append(None)
|
|
183
|
+
original_token_count += 1
|
|
184
|
+
edited_token_count += 1
|
|
185
|
+
original_embeddings, original_tokens = _get_embeddings_and_tokens_for_prompt(model,
|
|
186
|
+
original_prompt,
|
|
187
|
+
log_tokens=log_tokens,
|
|
188
|
+
log_display_label="(.swap originals)")
|
|
189
|
+
# naïvely building a single edited_embeddings like this disregards the effects of changing the absolute location of
|
|
190
|
+
# subsequent tokens when there is >1 edit and earlier edits change the total token count.
|
|
191
|
+
# eg "a cat.swap(smiling dog, s_start=0.5) eating a hotdog.swap(pizza)" - when the 'pizza' edit is active but the
|
|
192
|
+
# 'cat' edit is not, the 'pizza' feature vector will nevertheless be affected by the introduction of the extra
|
|
193
|
+
# token 'smiling' in the inactive 'cat' edit.
|
|
194
|
+
# todo: build multiple edited_embeddings, one for each edit, and pass just the edited fragments through to the CrossAttentionControl functions
|
|
195
|
+
edited_embeddings, edited_tokens = _get_embeddings_and_tokens_for_prompt(model,
|
|
196
|
+
edited_prompt,
|
|
197
|
+
log_tokens=log_tokens,
|
|
198
|
+
log_display_label="(.swap replacements)")
|
|
199
|
+
conditioning = original_embeddings
|
|
200
|
+
edited_conditioning = edited_embeddings
|
|
201
|
+
# print('>> got edit_opcodes', edit_opcodes, 'options', edit_options)
|
|
202
|
+
cac_args = cross_attention_control.Arguments(
|
|
203
|
+
edited_conditioning=edited_conditioning,
|
|
204
|
+
edit_opcodes=edit_opcodes,
|
|
205
|
+
edit_options=edit_options
|
|
206
|
+
)
|
|
207
|
+
return conditioning, cac_args
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _get_conditioning_for_blend(model, blend: Blend, log_tokens: bool = False):
|
|
211
|
+
embeddings_to_blend = None
|
|
212
|
+
for i, flattened_prompt in enumerate(blend.prompts):
|
|
213
|
+
this_embedding, _ = _get_embeddings_and_tokens_for_prompt(model,
|
|
214
|
+
flattened_prompt,
|
|
215
|
+
log_tokens=log_tokens,
|
|
216
|
+
log_display_label=f"(blend part {i + 1}, weight={blend.weights[i]})")
|
|
217
|
+
embeddings_to_blend = this_embedding if embeddings_to_blend is None else torch.cat(
|
|
218
|
+
(embeddings_to_blend, this_embedding))
|
|
219
|
+
conditioning = WeightedFrozenCLIPEmbedder.apply_embedding_weights(embeddings_to_blend.unsqueeze(0),
|
|
220
|
+
blend.weights,
|
|
221
|
+
normalize=blend.normalize_weights)
|
|
222
|
+
return conditioning
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _get_embeddings_and_tokens_for_prompt(model, flattened_prompt: FlattenedPrompt, log_tokens: bool = False,
|
|
226
|
+
log_display_label: str = None):
|
|
227
|
+
if type(flattened_prompt) is not FlattenedPrompt:
|
|
228
|
+
raise Exception(f"embeddings can only be made from FlattenedPrompts, got {type(flattened_prompt)} instead")
|
|
229
|
+
fragments = [x.text for x in flattened_prompt.children]
|
|
230
|
+
weights = [x.weight for x in flattened_prompt.children]
|
|
231
|
+
embeddings, tokens = model.get_learned_conditioning([fragments], return_tokens=True, fragment_weights=[weights])
|
|
232
|
+
if log_tokens:
|
|
233
|
+
text = " ".join(fragments)
|
|
234
|
+
log_tokenization(text, model, display_label=log_display_label)
|
|
235
|
+
|
|
236
|
+
return embeddings, tokens
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _get_tokens_length(model, fragments: list[Fragment]):
|
|
240
|
+
fragment_texts = [x.text for x in fragments]
|
|
241
|
+
tokens = model.cond_stage_model.get_tokens(fragment_texts, include_start_and_end_markers=False)
|
|
242
|
+
return sum([len(x) for x in tokens])
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _flatten_hybrid_conditioning(uncond, cond):
|
|
246
|
+
'''
|
|
247
|
+
This handles the choice between a conditional conditioning
|
|
248
|
+
that is a tensor (used by cross attention) vs one that has additional
|
|
249
|
+
dimensions as well, as used by 'hybrid'
|
|
250
|
+
'''
|
|
251
|
+
assert isinstance(uncond, dict)
|
|
252
|
+
assert isinstance(cond, dict)
|
|
253
|
+
cond_flattened = dict()
|
|
254
|
+
for k in cond:
|
|
255
|
+
if isinstance(cond[k], list):
|
|
256
|
+
cond_flattened[k] = [
|
|
257
|
+
torch.cat([uncond[k][i], cond[k][i]])
|
|
258
|
+
for i in range(len(cond[k]))
|
|
259
|
+
]
|
|
260
|
+
else:
|
|
261
|
+
cond_flattened[k] = torch.cat([uncond[k], cond[k]])
|
|
262
|
+
return uncond, cond_flattened
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def log_tokenization(text, model, display_label=None):
|
|
266
|
+
""" shows how the prompt is tokenized
|
|
267
|
+
# usually tokens have '</w>' to indicate end-of-word,
|
|
268
|
+
# but for readability it has been replaced with ' '
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
tokens = model.cond_stage_model.tokenizer.tokenize(text)
|
|
272
|
+
tokenized = ""
|
|
273
|
+
discarded = ""
|
|
274
|
+
usedTokens = 0
|
|
275
|
+
totalTokens = len(tokens)
|
|
276
|
+
for i in range(0, totalTokens):
|
|
277
|
+
token = tokens[i].replace('</w>', ' ')
|
|
278
|
+
# alternate color
|
|
279
|
+
s = (usedTokens % 6) + 1
|
|
280
|
+
if i < model.cond_stage_model.max_length:
|
|
281
|
+
tokenized = tokenized + f"\x1b[0;3{s};40m{token}"
|
|
282
|
+
usedTokens += 1
|
|
283
|
+
else: # over max token length
|
|
284
|
+
discarded = discarded + f"\x1b[0;3{s};40m{token}"
|
|
285
|
+
print(f"\n>> Tokens {display_label or ''} ({usedTokens}):\n{tokenized}\x1b[0m")
|
|
286
|
+
if discarded != "":
|
|
287
|
+
print(
|
|
288
|
+
f">> Tokens Discarded ({totalTokens - usedTokens}):\n{discarded}\x1b[0m"
|
|
289
|
+
)
|
ldm/invoke/devices.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from torch import autocast
|
|
3
|
+
from contextlib import nullcontext
|
|
4
|
+
|
|
5
|
+
def choose_torch_device() -> str:
|
|
6
|
+
'''Convenience routine for guessing which GPU device to run model on'''
|
|
7
|
+
if torch.cuda.is_available():
|
|
8
|
+
return 'cuda'
|
|
9
|
+
if hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
|
|
10
|
+
return 'mps'
|
|
11
|
+
return 'cpu'
|
|
12
|
+
|
|
13
|
+
def choose_precision(device) -> str:
|
|
14
|
+
'''Returns an appropriate precision for the given torch device'''
|
|
15
|
+
if device.type == 'cuda':
|
|
16
|
+
device_name = torch.cuda.get_device_name(device)
|
|
17
|
+
if not ('GeForce GTX 1660' in device_name or 'GeForce GTX 1650' in device_name):
|
|
18
|
+
return 'float16'
|
|
19
|
+
return 'float32'
|
|
20
|
+
|
|
21
|
+
def choose_autocast(precision):
|
|
22
|
+
'''Returns an autocast context or nullcontext for the given precision string'''
|
|
23
|
+
# float16 currently requires autocast to avoid errors like:
|
|
24
|
+
# 'expected scalar type Half but found Float'
|
|
25
|
+
if precision == 'autocast' or precision == 'float16':
|
|
26
|
+
return autocast
|
|
27
|
+
return nullcontext
|