remove globals, args, generate and the legacy CLI

This commit is contained in:
Lincoln Stein 2023-05-03 23:36:51 -04:00
parent 90054ddf0d
commit 15ffb53e59
21 changed files with 76 additions and 4679 deletions

View File

@ -2,7 +2,6 @@ import os
import sys
import torch
from argparse import Namespace
from invokeai.backend import Args
from omegaconf import OmegaConf
from pathlib import Path
from typing import types
@ -13,7 +12,7 @@ from ...backend import ModelManager
from ...backend.util import choose_precision, choose_torch_device
# TODO: Replace with an abstract class base ModelManagerBase
def get_model_manager(config: Args, logger: types.ModuleType) -> ModelManager:
def get_model_manager(config: InvokeAISettings, logger: types.ModuleType) -> ModelManager:
model_config = config.model_conf_path
if not model_config.exists():
report_model_error(
@ -44,7 +43,7 @@ def get_model_manager(config: Args, logger: types.ModuleType) -> ModelManager:
else choose_precision(device)
model_manager = ModelManager(
OmegaConf.load(config.conf),
OmegaConf.load(config.model_conf_path),
precision=precision,
device_type=device,
max_loaded_models=config.max_loaded_models,

View File

@ -1,7 +1,6 @@
"""
Initialization file for invokeai.backend
"""
from .generate import Generate
from .generator import (
InvokeAIGeneratorBasicParams,
InvokeAIGenerator,
@ -12,5 +11,3 @@ from .generator import (
)
from .model_management import ModelManager, SDModelComponent
from .safety_checker import SafetyChecker
from .args import Args
from .globals import Globals

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,135 +0,0 @@
"""
invokeai.backend.globals defines a small number of global variables that would
otherwise have to be passed through long and complex call chains.
It defines a Namespace object named "Globals" that contains
the attributes:
- root - the root directory under which "models" and "outputs" can be found
- initfile - path to the initialization file
- try_patchmatch - option to globally disable loading of 'patchmatch' module
- always_use_cpu - force use of CPU even if GPU is available
"""
import os
import os.path as osp
from argparse import Namespace
from pathlib import Path
from typing import Union
from pydantic import BaseSettings
Globals = Namespace()
# Where to look for the initialization file and other key components
Globals.initfile = "invokeai.init"
Globals.models_file = "models.yaml"
Globals.models_dir = "models"
Globals.config_dir = "configs"
Globals.autoscan_dir = "weights"
Globals.converted_ckpts_dir = "converted_ckpts"
# Set the default root directory. This can be overwritten by explicitly
# passing the `--root <directory>` argument on the command line.
# logic is:
# 1) use INVOKEAI_ROOT environment variable (no check for this being a valid directory)
# 2) use VIRTUAL_ENV environment variable, with a check for initfile being there
# 3) use ~/invokeai
if os.environ.get("INVOKEAI_ROOT"):
Globals.root = osp.abspath(os.environ.get("INVOKEAI_ROOT"))
elif (
os.environ.get("VIRTUAL_ENV")
and Path(os.environ.get("VIRTUAL_ENV"), "..", Globals.initfile).exists()
):
Globals.root = osp.abspath(osp.join(os.environ.get("VIRTUAL_ENV"), ".."))
else:
Globals.root = osp.abspath(osp.expanduser("~/invokeai"))
# Try loading patchmatch
Globals.try_patchmatch = True
# Use CPU even if GPU is available (main use case is for debugging MPS issues)
Globals.always_use_cpu = False
# Whether the internet is reachable for dynamic downloads
# The CLI will test connectivity at startup time.
Globals.internet_available = True
# Whether to disable xformers
Globals.disable_xformers = False
# Low-memory tradeoff for guidance calculations.
Globals.sequential_guidance = False
# whether we are forcing full precision
Globals.full_precision = False
# whether we should convert ckpt files into diffusers models on the fly
Globals.ckpt_convert = True
# logging tokenization everywhere
Globals.log_tokenization = False
def global_config_file() -> Path:
return Path(Globals.root, Globals.config_dir, Globals.models_file)
def global_config_dir() -> Path:
return Path(Globals.root, Globals.config_dir)
def global_models_dir() -> Path:
return Path(Globals.root, Globals.models_dir)
def global_autoscan_dir() -> Path:
return Path(Globals.root, Globals.autoscan_dir)
def global_converted_ckpts_dir() -> Path:
return Path(global_models_dir(), Globals.converted_ckpts_dir)
def global_set_root(root_dir: Union[str, Path]):
Globals.root = root_dir
def global_cache_dir(subdir: Union[str, Path] = "") -> Path:
"""
Returns Path to the model cache directory. If a subdirectory
is provided, it will be appended to the end of the path, allowing
for Hugging Face-style conventions. Currently, Hugging Face has
moved all models into the "hub" subfolder, so for any pretrained
HF model, use:
global_cache_dir('hub')
The legacy location for transformers used to be global_cache_dir('transformers')
and global_cache_dir('diffusers') for diffusers.
"""
home: str = os.getenv("HF_HOME")
if home is None:
home = os.getenv("XDG_CACHE_HOME")
if home is not None:
# Set `home` to $XDG_CACHE_HOME/huggingface, which is the default location mentioned in Hugging Face Hub Client Library.
# See: https://huggingface.co/docs/huggingface_hub/main/en/package_reference/environment_variables#xdgcachehome
home += os.sep + "huggingface"
if home is not None:
return Path(home, subdir)
else:
return Path(Globals.root, "models", subdir)
def copy_conf_to_globals(conf: Union[dict,BaseSettings]):
'''
Given a dict or dict-like object, copy its keys and
values into the Globals Namespace. This is a transitional
workaround until we remove Globals entirely.
'''
if isinstance(conf,BaseSettings):
conf = conf.dict()
for key in conf.keys():
if key is not None:
setattr(Globals,key,conf[key])

View File

@ -6,7 +6,9 @@ be suppressed or deferred
"""
import numpy as np
import invokeai.backend.util.logging as logger
from invokeai.backend.globals import Globals
from invokeai.app.services.config import InvokeAIAppConfig
config = InvokeAIAppConfig()
class PatchMatch:
"""
@ -23,7 +25,7 @@ class PatchMatch:
def _load_patch_match(self):
if self.tried_load:
return
if Globals.try_patchmatch:
if config.try_patchmatch:
from patchmatch import patch_match as pm
if pm.patchmatch_available:

View File

@ -33,11 +33,11 @@ from PIL import Image, ImageOps
from transformers import AutoProcessor, CLIPSegForImageSegmentation
import invokeai.backend.util.logging as logger
from invokeai.backend.globals import global_cache_dir
from invokeai.app.services.config import InvokeAIAppConfig
CLIPSEG_MODEL = "CIDAS/clipseg-rd64-refined"
CLIPSEG_SIZE = 352
config = InvokeAIAppConfig()
class SegmentedGrayscale(object):
def __init__(self, image: Image, heatmap: torch.Tensor):
@ -88,10 +88,10 @@ class Txt2Mask(object):
# BUG: we are not doing anything with the device option at this time
self.device = device
self.processor = AutoProcessor.from_pretrained(
CLIPSEG_MODEL, cache_dir=global_cache_dir("hub")
CLIPSEG_MODEL, cache_dir=config.cache_dir
)
self.model = CLIPSegForImageSegmentation.from_pretrained(
CLIPSEG_MODEL, cache_dir=global_cache_dir("hub")
CLIPSEG_MODEL, cache_dir=config.cache_dir
)
@torch.no_grad()

View File

@ -26,7 +26,7 @@ import torch
from safetensors.torch import load_file
import invokeai.backend.util.logging as logger
from invokeai.backend.globals import global_cache_dir, global_config_dir
from invokeai.app.services.config import InvokeAIAppConfig
from .model_manager import ModelManager, SDLegacyType
@ -73,6 +73,7 @@ from transformers import (
from ..stable_diffusion import StableDiffusionGeneratorPipeline
config = InvokeAIAppConfig()
def shave_segments(path, n_shave_prefix_segments=1):
"""
@ -842,7 +843,7 @@ def convert_ldm_bert_checkpoint(checkpoint, config):
def convert_ldm_clip_checkpoint(checkpoint):
text_model = CLIPTextModel.from_pretrained(
"openai/clip-vit-large-patch14", cache_dir=global_cache_dir("hub")
"openai/clip-vit-large-patch14", cache_dir=config.cache_dir
)
keys = list(checkpoint.keys())
@ -897,7 +898,7 @@ textenc_pattern = re.compile("|".join(protected.keys()))
def convert_paint_by_example_checkpoint(checkpoint):
cache_dir = global_cache_dir("hub")
cache_dir = config.cache_dir
config = CLIPVisionConfig.from_pretrained(
"openai/clip-vit-large-patch14", cache_dir=cache_dir
)
@ -969,7 +970,7 @@ def convert_paint_by_example_checkpoint(checkpoint):
def convert_open_clip_checkpoint(checkpoint):
cache_dir = global_cache_dir("hub")
cache_dir = config.cache_dir
text_model = CLIPTextModel.from_pretrained(
"stabilityai/stable-diffusion-2", subfolder="text_encoder", cache_dir=cache_dir
)
@ -1105,7 +1106,7 @@ def load_pipeline_from_original_stable_diffusion_ckpt(
else:
checkpoint = load_file(checkpoint_path)
cache_dir = global_cache_dir("hub")
cache_dir = config.cache_dir
pipeline_class = (
StableDiffusionGeneratorPipeline
if return_generator_pipeline
@ -1129,25 +1130,23 @@ def load_pipeline_from_original_stable_diffusion_ckpt(
if model_type == SDLegacyType.V2_v:
original_config_file = (
global_config_dir() / "stable-diffusion" / "v2-inference-v.yaml"
config.legacy_conf_path / "v2-inference-v.yaml"
)
if global_step == 110000:
# v2.1 needs to upcast attention
upcast_attention = True
elif model_type == SDLegacyType.V2_e:
original_config_file = (
global_config_dir() / "stable-diffusion" / "v2-inference.yaml"
config.legacy_conf_path / "v2-inference.yaml"
)
elif model_type == SDLegacyType.V1_INPAINT:
original_config_file = (
global_config_dir()
/ "stable-diffusion"
/ "v1-inpainting-inference.yaml"
config.legacy_conf_path / "v1-inpainting-inference.yaml"
)
elif model_type == SDLegacyType.V1:
original_config_file = (
global_config_dir() / "stable-diffusion" / "v1-inference.yaml"
config.legacy_conf_path / "v1-inference.yaml"
)
else:
@ -1297,7 +1296,7 @@ def load_pipeline_from_original_stable_diffusion_ckpt(
)
safety_checker = StableDiffusionSafetyChecker.from_pretrained(
"CompVis/stable-diffusion-safety-checker",
cache_dir=global_cache_dir("hub"),
cache_dir=config.cache_dir,
)
feature_extractor = AutoFeatureExtractor.from_pretrained(
"CompVis/stable-diffusion-safety-checker", cache_dir=cache_dir

View File

@ -36,8 +36,6 @@ from omegaconf import OmegaConf
from omegaconf.dictconfig import DictConfig
from picklescan.scanner import scan_file_path
from invokeai.backend.globals import Globals, global_cache_dir
from transformers import (
CLIPTextModel,
CLIPTokenizer,
@ -49,9 +47,9 @@ from diffusers.pipelines.stable_diffusion.safety_checker import (
from ..stable_diffusion import (
StableDiffusionGeneratorPipeline,
)
from invokeai.app.services.config import InvokeAIAppConfig
from ..util import CUDA_DEVICE, ask_user, download_with_resume
class SDLegacyType(Enum):
V1 = auto()
V1_INPAINT = auto()
@ -70,6 +68,7 @@ class SDModelComponent(Enum):
feature_extractor="feature_extractor"
DEFAULT_MAX_MODELS = 2
config = InvokeAIAppConfig()
class ModelManager(object):
"""
@ -292,7 +291,7 @@ class ModelManager(object):
"""
# if we are converting legacy files automatically, then
# there are no legacy ckpts!
if Globals.ckpt_convert:
if config.ckpt_convert:
return False
info = self.model_info(model_name)
if "weights" in info and info["weights"].endswith((".ckpt", ".safetensors")):
@ -502,13 +501,13 @@ class ModelManager(object):
# TODO: scan weights maybe?
pipeline_args: dict[str, Any] = dict(
safety_checker=None, local_files_only=not Globals.internet_available
safety_checker=None, local_files_only=not config.internet_available
)
if "vae" in mconfig and mconfig["vae"] is not None:
if vae := self._load_vae(mconfig["vae"]):
pipeline_args.update(vae=vae)
if not isinstance(name_or_path, Path):
pipeline_args.update(cache_dir=global_cache_dir("hub"))
pipeline_args.update(cache_dir=config.cache_dir)
if using_fp16:
pipeline_args.update(torch_dtype=torch.float16)
fp_args_list = [{"revision": "fp16"}, {}]
@ -561,9 +560,9 @@ class ModelManager(object):
height = mconfig.height
if not os.path.isabs(config):
config = os.path.join(Globals.root, config)
config = os.path.join(config.root, config)
if not os.path.isabs(weights):
weights = os.path.normpath(os.path.join(Globals.root, weights))
weights = os.path.normpath(os.path.join(config.root, weights))
# Convert to diffusers and return a diffusers pipeline
self.logger.info(f"Converting legacy checkpoint {model_name} into a diffusers model...")
@ -581,7 +580,7 @@ class ModelManager(object):
vae_path = (
vae
if os.path.isabs(vae)
else os.path.normpath(os.path.join(Globals.root, vae))
else os.path.normpath(os.path.join(config.root, vae))
)
if self._has_cuda():
torch.cuda.empty_cache()
@ -616,7 +615,7 @@ class ModelManager(object):
if "path" in mconfig and mconfig["path"] is not None:
path = Path(mconfig["path"])
if not path.is_absolute():
path = Path(Globals.root, path).resolve()
path = Path(config.root, path).resolve()
return path
elif "repo_id" in mconfig:
return mconfig["repo_id"]
@ -864,25 +863,16 @@ class ModelManager(object):
model_type = self.probe_model_type(checkpoint)
if model_type == SDLegacyType.V1:
self.logger.debug("SD-v1 model detected")
model_config_file = Path(
Globals.root, "configs/stable-diffusion/v1-inference.yaml"
)
model_config_file = config.legacy_conf_path / "v1-inference.yaml"
elif model_type == SDLegacyType.V1_INPAINT:
self.logger.debug("SD-v1 inpainting model detected")
model_config_file = Path(
Globals.root,
"configs/stable-diffusion/v1-inpainting-inference.yaml",
)
model_config_file = config.legacy_conf_path / "v1-inpainting-inference.yaml",
elif model_type == SDLegacyType.V2_v:
self.logger.debug("SD-v2-v model detected")
model_config_file = Path(
Globals.root, "configs/stable-diffusion/v2-inference-v.yaml"
)
model_config_file = config.legacy_conf_path / "v2-inference-v.yaml"
elif model_type == SDLegacyType.V2_e:
self.logger.debug("SD-v2-e model detected")
model_config_file = Path(
Globals.root, "configs/stable-diffusion/v2-inference.yaml"
)
model_config_file = config.legacy_conf_path / "v2-inference.yaml"
elif model_type == SDLegacyType.V2:
self.logger.warning(
f"{thing} is a V2 checkpoint file, but its parameterization cannot be determined. Please provide configuration file path."
@ -909,9 +899,7 @@ class ModelManager(object):
self.logger.debug(f"Using VAE file {vae_path.name}")
vae = None if vae_path else dict(repo_id="stabilityai/sd-vae-ft-mse")
diffuser_path = Path(
Globals.root, "models", Globals.converted_ckpts_dir, model_path.stem
)
diffuser_path = config.root / "models/converted_ckpts" / model_path.stem
model_name = self.convert_and_import(
model_path,
diffusers_path=diffuser_path,
@ -1044,9 +1032,7 @@ class ModelManager(object):
"""
yaml_str = OmegaConf.to_yaml(self.config)
if not os.path.isabs(config_file_path):
config_file_path = os.path.normpath(
os.path.join(Globals.root, config_file_path)
)
config_file_path = config.model_conf_path
tmpfile = os.path.join(os.path.dirname(config_file_path), "new_config.tmp")
with open(tmpfile, "w", encoding="utf-8") as outfile:
outfile.write(self.preamble())
@ -1078,7 +1064,7 @@ class ModelManager(object):
"""
# Three transformer models to check: bert, clip and safety checker, and
# the diffusers as well
models_dir = Path(Globals.root, "models")
models_dir = config.root / "models"
legacy_locations = [
Path(
models_dir,
@ -1090,8 +1076,8 @@ class ModelManager(object):
"openai/clip-vit-large-patch14/models--openai--clip-vit-large-patch14",
),
]
legacy_locations.extend(list(global_cache_dir("diffusers").glob("*")))
legacy_cache_dir = config.cache_dir / "../diffusers"
legacy_locations.extend(list(legacy_cache_dir.glob("*")))
legacy_layout = False
for model in legacy_locations:
legacy_layout = legacy_layout or model.exists()
@ -1113,7 +1099,7 @@ class ModelManager(object):
# transformer files get moved into the hub directory
if cls._is_huggingface_hub_directory_present():
hub = global_cache_dir("hub")
hub = config.cache_dir
else:
hub = models_dir / "hub"
@ -1152,12 +1138,12 @@ class ModelManager(object):
if str(source).startswith(("http:", "https:", "ftp:")):
dest_directory = Path(dest_directory)
if not dest_directory.is_absolute():
dest_directory = Globals.root / dest_directory
dest_directory = config.root / dest_directory
dest_directory.mkdir(parents=True, exist_ok=True)
resolved_path = download_with_resume(str(source), dest_directory)
else:
if not os.path.isabs(source):
source = os.path.join(Globals.root, source)
source = config.root / source
resolved_path = Path(source)
return resolved_path
@ -1208,7 +1194,7 @@ class ModelManager(object):
path = name_or_path
else:
owner, repo = name_or_path.split("/")
path = Path(global_cache_dir("hub") / f"models--{owner}--{repo}")
path = Path(config.cache_dir / f"models--{owner}--{repo}")
if not path.exists():
return None
hashpath = path / "checksum.sha256"
@ -1269,8 +1255,8 @@ class ModelManager(object):
using_fp16 = self.precision == "float16"
vae_args.update(
cache_dir=global_cache_dir("hub"),
local_files_only=not Globals.internet_available,
cache_dir=config.cache_dir,
local_files_only=not config.internet_available,
)
self.logger.debug(f"Loading diffusers VAE from {name_or_path}")
@ -1308,7 +1294,7 @@ class ModelManager(object):
@classmethod
def _delete_model_from_cache(cls,repo_id):
cache_info = scan_cache_dir(global_cache_dir("hub"))
cache_info = scan_cache_dir(config.cache_dir)
# I'm sure there is a way to do this with comprehensions
# but the code quickly became incomprehensible!
@ -1327,7 +1313,7 @@ class ModelManager(object):
def _abs_path(path: str | Path) -> Path:
if path is None or Path(path).is_absolute():
return path
return Path(Globals.root, path).resolve()
return Path(config.root, path).resolve()
@staticmethod
def _is_huggingface_hub_directory_present() -> bool:

View File

@ -19,11 +19,12 @@ from compel.prompt_parser import (
)
import invokeai.backend.util.logging as logger
from invokeai.backend.globals import Globals
from invokeai.app.services.config import InvokeAIAppConfig
from ..stable_diffusion import InvokeAIDiffuserComponent
from ..util import torch_dtype
config = InvokeAIAppConfig()
def get_uc_and_c_and_ec(
prompt_string, model, log_tokens=False, skip_normalize_legacy_blend=False
@ -61,7 +62,7 @@ def get_uc_and_c_and_ec(
negative_prompt_string
)
if log_tokens or getattr(Globals, "log_tokenization", False):
if log_tokens or config.log_tokenization:
log_tokenization(positive_prompt, negative_prompt, tokenizer=tokenizer)
c, options = compel.build_conditioning_tensor_for_prompt_object(positive_prompt)

View File

@ -6,7 +6,8 @@ import numpy as np
import torch
import invokeai.backend.util.logging as logger
from ..globals import Globals
from invokeai.app.services.config import InvokeAIAppConfig
config = InvokeAIAppConfig()
pretrained_model_url = (
"https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth"
@ -18,7 +19,7 @@ class CodeFormerRestoration:
self, codeformer_dir="models/codeformer", codeformer_model_path="codeformer.pth"
) -> None:
if not os.path.isabs(codeformer_dir):
codeformer_dir = os.path.join(Globals.root, codeformer_dir)
codeformer_dir = os.path.join(config.root, codeformer_dir)
self.model_path = os.path.join(codeformer_dir, codeformer_model_path)
self.codeformer_model_exists = os.path.isfile(self.model_path)
@ -72,7 +73,7 @@ class CodeFormerRestoration:
use_parse=True,
device=device,
model_rootpath=os.path.join(
Globals.root, "models", "gfpgan", "weights"
config.root, "models", "gfpgan", "weights"
),
)
face_helper.clean_all()

View File

@ -7,13 +7,14 @@ import torch
from PIL import Image
import invokeai.backend.util.logging as logger
from invokeai.backend.globals import Globals
from invokeai.app.services.config import InvokeAIAppConfig
config = InvokeAIAppConfig()
class GFPGAN:
def __init__(self, gfpgan_model_path="models/gfpgan/GFPGANv1.4.pth") -> None:
if not os.path.isabs(gfpgan_model_path):
gfpgan_model_path = os.path.abspath(
os.path.join(Globals.root, gfpgan_model_path)
os.path.join(config.root, gfpgan_model_path)
)
self.model_path = gfpgan_model_path
self.gfpgan_model_exists = os.path.isfile(self.model_path)
@ -33,7 +34,7 @@ class GFPGAN:
warnings.filterwarnings("ignore", category=DeprecationWarning)
warnings.filterwarnings("ignore", category=UserWarning)
cwd = os.getcwd()
os.chdir(os.path.join(Globals.root, "models"))
os.chdir(os.path.join(config.root, "models"))
try:
from gfpgan import GFPGANer

View File

@ -15,9 +15,11 @@ from transformers import AutoFeatureExtractor
import invokeai.assets.web as web_assets
import invokeai.backend.util.logging as logger
from .globals import global_cache_dir
from invokeai.app.services.config import InvokeAIAppConfig
from .util import CPU_DEVICE
config = InvokeAIAppConfig()
class SafetyChecker(object):
CAUTION_IMG = "caution.png"
@ -29,7 +31,7 @@ class SafetyChecker(object):
try:
safety_model_id = "CompVis/stable-diffusion-safety-checker"
safety_model_path = global_cache_dir("hub")
safety_model_path = config.cache_dir
self.safety_checker = StableDiffusionSafetyChecker.from_pretrained(
safety_model_id,
local_files_only=True,

View File

@ -18,15 +18,15 @@ from huggingface_hub import (
)
import invokeai.backend.util.logging as logger
from invokeai.backend.globals import Globals
from invokeai.app.services.config import InvokeAIAppConfig
config = InvokeAIAppConfig()
class HuggingFaceConceptsLibrary(object):
def __init__(self, root=None):
"""
Initialize the Concepts object. May optionally pass a root directory.
"""
self.root = root or Globals.root
self.root = root or config.root
self.hf_api = HfApi()
self.local_concepts = dict()
self.concept_list = None
@ -58,7 +58,7 @@ class HuggingFaceConceptsLibrary(object):
self.concept_list.extend(list(local_concepts_to_add))
return self.concept_list
return self.concept_list
elif Globals.internet_available is True:
elif config.internet_available is True:
try:
models = self.hf_api.list_models(
filter=ModelFilter(model_name="sd-concepts-library/")

View File

@ -33,8 +33,7 @@ from torchvision.transforms.functional import resize as tv_resize
from transformers import CLIPFeatureExtractor, CLIPTextModel, CLIPTokenizer
from typing_extensions import ParamSpec
from invokeai.backend.globals import Globals
from invokeai.app.services.config import InvokeAIAppConfig
from ..util import CPU_DEVICE, normalize_device
from .diffusion import (
AttentionMapSaver,
@ -44,6 +43,7 @@ from .diffusion import (
from .offloading import FullyLoadedModelGroup, LazilyLoadedModelGroup, ModelGroup
from .textual_inversion_manager import TextualInversionManager
config = InvokeAIAppConfig()
@dataclass
class PipelineIntermediateState:
@ -351,7 +351,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
if (
torch.cuda.is_available()
and is_xformers_available()
and not Globals.disable_xformers
and not config.disable_xformers
):
self.enable_xformers_memory_efficient_attention()
else:

View File

@ -9,7 +9,7 @@ from diffusers.models.attention_processor import AttentionProcessor
from typing_extensions import TypeAlias
import invokeai.backend.util.logging as logger
from invokeai.backend.globals import Globals
from invokeai.app.services.config import InvokeAIAppConfig
from .cross_attention_control import (
Arguments,
@ -31,6 +31,7 @@ ModelForwardCallback: TypeAlias = Union[
Callable[[torch.Tensor, torch.Tensor, torch.Tensor], torch.Tensor],
]
config = InvokeAIAppConfig()
@dataclass(frozen=True)
class PostprocessingSettings:
@ -77,7 +78,7 @@ class InvokeAIDiffuserComponent:
self.is_running_diffusers = is_running_diffusers
self.model_forward_callback = model_forward_callback
self.cross_attention_control_context = None
self.sequential_guidance = Globals.sequential_guidance
self.sequential_guidance = config.sequential_guidance
@contextmanager
def custom_attention_context(

View File

@ -4,17 +4,16 @@ from contextlib import nullcontext
import torch
from torch import autocast
from invokeai.backend.globals import Globals
from invokeai.app.services.config import InvokeAIAppConfig
CPU_DEVICE = torch.device("cpu")
CUDA_DEVICE = torch.device("cuda")
MPS_DEVICE = torch.device("mps")
config = InvokeAIAppConfig()
def choose_torch_device() -> torch.device:
"""Convenience routine for guessing which GPU device to run model on"""
if Globals.always_use_cpu:
if config.always_use_cpu:
return CPU_DEVICE
if torch.cuda.is_available():
return torch.device("cuda")
@ -33,7 +32,7 @@ def choose_precision(device: torch.device) -> str:
def torch_dtype(device: torch.device) -> torch.dtype:
if Globals.full_precision:
if config.full_precision:
return torch.float32
if choose_precision(device) == "float16":
return torch.float16

File diff suppressed because it is too large Load Diff

View File

@ -1,497 +0,0 @@
"""
Readline helper functions for invoke.py.
You may import the global singleton `completer` to get access to the
completer object itself. This is useful when you want to autocomplete
seeds:
from invokeai.frontend.CLI.readline import completer
completer.add_seed(18247566)
completer.add_seed(9281839)
"""
import atexit
import os
import re
from ...backend.args import Args
from ...backend.globals import Globals
from ...backend.stable_diffusion import HuggingFaceConceptsLibrary
# ---------------readline utilities---------------------
try:
import readline
readline_available = True
except (ImportError, ModuleNotFoundError) as e:
print(f"** An error occurred when loading the readline module: {str(e)}")
readline_available = False
IMG_EXTENSIONS = (".png", ".jpg", ".jpeg", ".PNG", ".JPG", ".JPEG", ".gif", ".GIF")
WEIGHT_EXTENSIONS = (".ckpt", ".vae", ".safetensors")
TEXT_EXTENSIONS = (".txt", ".TXT")
CONFIG_EXTENSIONS = (".yaml", ".yml")
COMMANDS = (
"--steps",
"-s",
"--seed",
"-S",
"--iterations",
"-n",
"--width",
"-W",
"--height",
"-H",
"--cfg_scale",
"-C",
"--threshold",
"--perlin",
"--grid",
"-g",
"--individual",
"-i",
"--save_intermediates",
"--init_img",
"-I",
"--init_mask",
"-M",
"--init_color",
"--strength",
"-f",
"--variants",
"-v",
"--outdir",
"-o",
"--sampler",
"-A",
"-m",
"--embedding_path",
"--device",
"--grid",
"-g",
"--facetool",
"-ft",
"--facetool_strength",
"-G",
"--codeformer_fidelity",
"-cf",
"--upscale",
"-U",
"-save_orig",
"--save_original",
"--log_tokenization",
"-t",
"--hires_fix",
"--inpaint_replace",
"-r",
"--png_compression",
"-z",
"--text_mask",
"-tm",
"--h_symmetry_time_pct",
"--v_symmetry_time_pct",
"!fix",
"!fetch",
"!replay",
"!history",
"!search",
"!clear",
"!models",
"!switch",
"!import_model",
"!optimize_model",
"!convert_model",
"!edit_model",
"!del_model",
"!mask",
"!triggers",
)
MODEL_COMMANDS = (
"!switch",
"!edit_model",
"!del_model",
)
CKPT_MODEL_COMMANDS = ("!optimize_model",)
WEIGHT_COMMANDS = (
"!import_model",
"!convert_model",
)
IMG_PATH_COMMANDS = ("--outdir[=\s]",)
TEXT_PATH_COMMANDS = ("!replay",)
IMG_FILE_COMMANDS = (
"!fix",
"!fetch",
"!mask",
"--init_img[=\s]",
"-I",
"--init_mask[=\s]",
"-M",
"--init_color[=\s]",
"--embedding_path[=\s]",
)
path_regexp = "(" + "|".join(IMG_PATH_COMMANDS + IMG_FILE_COMMANDS) + ")\s*\S*$"
weight_regexp = "(" + "|".join(WEIGHT_COMMANDS) + ")\s*\S*$"
text_regexp = "(" + "|".join(TEXT_PATH_COMMANDS) + ")\s*\S*$"
class Completer(object):
def __init__(self, options, models={}):
self.options = sorted(options)
self.models = models
self.seeds = set()
self.matches = list()
self.default_dir = None
self.linebuffer = None
self.auto_history_active = True
self.extensions = None
self.concepts = None
self.embedding_terms = set()
return
def complete(self, text, state):
"""
Completes invoke command line.
BUG: it doesn't correctly complete files that have spaces in the name.
"""
buffer = readline.get_line_buffer()
if state == 0:
# extensions defined, so go directly into path completion mode
if self.extensions is not None:
self.matches = self._path_completions(text, state, self.extensions)
# looking for an image file
elif re.search(path_regexp, buffer):
do_shortcut = re.search("^" + "|".join(IMG_FILE_COMMANDS), buffer)
self.matches = self._path_completions(
text, state, IMG_EXTENSIONS, shortcut_ok=do_shortcut
)
# looking for a seed
elif re.search("(-S\s*|--seed[=\s])\d*$", buffer):
self.matches = self._seed_completions(text, state)
# looking for an embedding concept
elif re.search("<[\w-]*$", buffer):
self.matches = self._concept_completions(text, state)
# looking for a model
elif re.match("^" + "|".join(MODEL_COMMANDS), buffer):
self.matches = self._model_completions(text, state)
# looking for a ckpt model
elif re.match("^" + "|".join(CKPT_MODEL_COMMANDS), buffer):
self.matches = self._model_completions(text, state, ckpt_only=True)
elif re.search(weight_regexp, buffer):
self.matches = self._path_completions(
text,
state,
WEIGHT_EXTENSIONS,
default_dir=Globals.root,
)
elif re.search(text_regexp, buffer):
self.matches = self._path_completions(text, state, TEXT_EXTENSIONS)
# This is the first time for this text, so build a match list.
elif text:
self.matches = [s for s in self.options if s and s.startswith(text)]
else:
self.matches = self.options[:]
# Return the state'th item from the match list,
# if we have that many.
try:
response = self.matches[state]
except IndexError:
response = None
return response
def complete_extensions(self, extensions: list):
"""
If called with a list of extensions, will force completer
to do file path completions.
"""
self.extensions = extensions
def add_history(self, line):
"""
Pass thru to readline
"""
if not self.auto_history_active:
readline.add_history(line)
def clear_history(self):
"""
Pass clear_history() thru to readline
"""
readline.clear_history()
def search_history(self, match: str):
"""
Like show_history() but only shows items that
contain the match string.
"""
self.show_history(match)
def remove_history_item(self, pos):
readline.remove_history_item(pos)
def add_seed(self, seed):
"""
Add a seed to the autocomplete list for display when -S is autocompleted.
"""
if seed is not None:
self.seeds.add(str(seed))
def set_default_dir(self, path):
self.default_dir = path
def set_options(self, options):
self.options = options
def get_line(self, index):
try:
line = self.get_history_item(index)
except IndexError:
return None
return line
def get_current_history_length(self):
return readline.get_current_history_length()
def get_history_item(self, index):
return readline.get_history_item(index)
def show_history(self, match=None):
"""
Print the session history using the pydoc pager
"""
import pydoc
lines = list()
h_len = self.get_current_history_length()
if h_len < 1:
print("<empty history>")
return
for i in range(0, h_len):
line = self.get_history_item(i + 1)
if match and match not in line:
continue
lines.append(f"[{i+1}] {line}")
pydoc.pager("\n".join(lines))
def set_line(self, line) -> None:
"""
Set the default string displayed in the next line of input.
"""
self.linebuffer = line
readline.redisplay()
def update_models(self, models: dict) -> None:
"""
update our list of models
"""
self.models = models
def _seed_completions(self, text, state):
m = re.search("(-S\s?|--seed[=\s]?)(\d*)", text)
if m:
switch = m.groups()[0]
partial = m.groups()[1]
else:
switch = ""
partial = text
matches = list()
for s in self.seeds:
if s.startswith(partial):
matches.append(switch + s)
matches.sort()
return matches
def add_embedding_terms(self, terms: list[str]):
self.embedding_terms = set(terms)
if self.concepts:
self.embedding_terms.update(set(self.concepts.list_concepts()))
def _concept_completions(self, text, state):
if self.concepts is None:
# cache Concepts() instance so we can check for updates in concepts_list during runtime.
self.concepts = HuggingFaceConceptsLibrary()
self.embedding_terms.update(set(self.concepts.list_concepts()))
else:
self.embedding_terms.update(set(self.concepts.list_concepts()))
partial = text[1:] # this removes the leading '<'
if len(partial) == 0:
return list(self.embedding_terms) # whole dump - think if user wants this!
matches = list()
for concept in self.embedding_terms:
if concept.startswith(partial):
matches.append(f"<{concept}>")
matches.sort()
return matches
def _model_completions(self, text, state, ckpt_only=False):
m = re.search("(!switch\s+)(\w*)", text)
if m:
switch = m.groups()[0]
partial = m.groups()[1]
else:
switch = ""
partial = text
matches = list()
for s in self.models:
format = self.models[s]["format"]
if format == "vae":
continue
if ckpt_only and format != "ckpt":
continue
if s.startswith(partial):
matches.append(switch + s)
matches.sort()
return matches
def _pre_input_hook(self):
if self.linebuffer:
readline.insert_text(self.linebuffer)
readline.redisplay()
self.linebuffer = None
def _path_completions(
self, text, state, extensions, shortcut_ok=True, default_dir: str = ""
):
# separate the switch from the partial path
match = re.search("^(-\w|--\w+=?)(.*)", text)
if match is None:
switch = None
partial_path = text
else:
switch, partial_path = match.groups()
partial_path = partial_path.lstrip()
matches = list()
path = os.path.expanduser(partial_path)
if os.path.isdir(path):
dir = path
elif os.path.dirname(path) != "":
dir = os.path.dirname(path)
else:
dir = default_dir if os.path.exists(default_dir) else ""
path = os.path.join(dir, path)
dir_list = os.listdir(dir or ".")
if shortcut_ok and os.path.exists(self.default_dir) and dir == "":
dir_list += os.listdir(self.default_dir)
for node in dir_list:
if node.startswith(".") and len(node) > 1:
continue
full_path = os.path.join(dir, node)
if not (node.endswith(extensions) or os.path.isdir(full_path)):
continue
if path and not full_path.startswith(path):
continue
if switch is None:
match_path = os.path.join(dir, node)
matches.append(
match_path + "/" if os.path.isdir(full_path) else match_path
)
elif os.path.isdir(full_path):
matches.append(
switch + os.path.join(os.path.dirname(full_path), node) + "/"
)
elif node.endswith(extensions):
matches.append(switch + os.path.join(os.path.dirname(full_path), node))
return matches
class DummyCompleter(Completer):
def __init__(self, options):
super().__init__(options)
self.history = list()
def add_history(self, line):
self.history.append(line)
def clear_history(self):
self.history = list()
def get_current_history_length(self):
return len(self.history)
def get_history_item(self, index):
return self.history[index - 1]
def remove_history_item(self, index):
return self.history.pop(index - 1)
def set_line(self, line):
print(f"# {line}")
def generic_completer(commands: list) -> Completer:
if readline_available:
completer = Completer(commands, [])
readline.set_completer(completer.complete)
readline.set_pre_input_hook(completer._pre_input_hook)
readline.set_completer_delims(" ")
readline.parse_and_bind("tab: complete")
readline.parse_and_bind("set print-completions-horizontally off")
readline.parse_and_bind("set page-completions on")
readline.parse_and_bind("set skip-completed-text on")
readline.parse_and_bind("set show-all-if-ambiguous on")
else:
completer = DummyCompleter(commands)
return completer
def get_completer(opt: Args, models=[]) -> Completer:
if readline_available:
completer = Completer(COMMANDS, models)
readline.set_completer(completer.complete)
# pyreadline3 does not have a set_auto_history() method
try:
readline.set_auto_history(False)
completer.auto_history_active = False
except:
completer.auto_history_active = True
readline.set_pre_input_hook(completer._pre_input_hook)
readline.set_completer_delims(" ")
readline.parse_and_bind("tab: complete")
readline.parse_and_bind("set print-completions-horizontally off")
readline.parse_and_bind("set page-completions on")
readline.parse_and_bind("set skip-completed-text on")
readline.parse_and_bind("set show-all-if-ambiguous on")
outdir = os.path.expanduser(opt.outdir)
if os.path.isabs(outdir):
histfile = os.path.join(outdir, ".invoke_history")
else:
histfile = os.path.join(Globals.root, outdir, ".invoke_history")
try:
readline.read_history_file(histfile)
readline.set_history_length(1000)
except FileNotFoundError:
pass
except OSError: # file likely corrupted
newname = f"{histfile}.old"
print(
f"## Your history file {histfile} couldn't be loaded and may be corrupted. Renaming it to {newname}"
)
os.replace(histfile, newname)
atexit.register(readline.write_history_file, histfile)
else:
completer = DummyCompleter(COMMANDS)
return completer

View File

@ -1,30 +0,0 @@
'''
This is a modularized version of the sd-metadata.py script,
which retrieves and prints the metadata from a series of generated png files.
'''
import sys
import json
from invokeai.backend.image_util import retrieve_metadata
def print_metadata():
if len(sys.argv) < 2:
print("Usage: file2prompt.py <file1.png> <file2.png> <file3.png>...")
print("This script opens up the indicated invoke.py-generated PNG file(s) and prints out their metadata.")
exit(-1)
filenames = sys.argv[1:]
for f in filenames:
try:
metadata = retrieve_metadata(f)
print(f'{f}:\n',json.dumps(metadata['sd-metadata'], indent=4))
except FileNotFoundError:
sys.stderr.write(f'{f} not found\n')
continue
except PermissionError:
sys.stderr.write(f'{f} could not be opened due to inadequate permissions\n')
continue
if __name__== '__main__':
print_metadata()

View File

@ -104,7 +104,6 @@ dependencies = [
"textual_inversion.py" = "invokeai.frontend.training:invokeai_textual_inversion"
# modern entrypoints
"invokeai" = "invokeai.frontend.CLI:invokeai_command_line_interface"
"invokeai-configure" = "invokeai.frontend.install:invokeai_configure"
"invokeai-merge" = "invokeai.frontend.merge:invokeai_merge_diffusers"
"invokeai-ti" = "invokeai.frontend.training:invokeai_textual_inversion"