Skip to content

Commit d3c177a

Browse files
authored
Refactor config class and reorganize image generation options (#4309)
## What type of PR is this? (check all applicable) - [X Refactor - [X] Feature ## Have you discussed this change with the InvokeAI team? - [X] Yes ## Have you updated all relevant documentation? - [X] Yes ## Description ### Refactoring This PR refactors `invokeai.app.services.config` to be easier to maintain by splitting off the argument, environment and init file parsing code from the InvokeAIAppConfig object. This will hopefully make it easier for people to find the place where the various settings are defined. ### New Features In collaboration with @StAlKeR7779 , I have renamed and reorganized the settings controlling image generation and model management to be more intuitive. The relevant portion of the init file now looks like this: ``` Model Cache: ram: 14.5 vram: 0.5 lazy_offload: true Device: precision: auto device: auto Generation: sequential_guidance: false attention_type: auto attention_slice_size: auto force_tiled_decode: false ``` Key differences are: 1. Split `Performance/Memory` into `Device`, `Generation` and `Model Cache` 2. Added the ability to force the `device`. The value of this option is one of {`auto`, `cpu`, `cuda`, `cuda:1`, `mps`} 3. Added the ability to force the `attention_type`. Possible values are {`auto`, `normal`, `xformers`, `sliced`, `torch-sdp`} 4. Added the ability to force the `attention_slice_size` when `sliced` attention is in use. The value of this option is one of {`auto`, `max`} or an integer between 1 and 8. @StAlKeR7779 Please confirm that I wired the `attention_type` and `attention_slice_size` configuration options to the diffusers backend correctly. In addition, I have exposed the generation-related configuration options to the TUI: ![image](https://github.com/invoke-ai/InvokeAI/assets/111189/8c0235d4-c3b0-494e-a1ab-ff45cdbfd9af) ### Backward Compatibility This refactor should be backward compatible with earlier versions of `invokeai.yaml`. If the user re-runs the `invokeai-configure` script, `invokeai.yaml` will be upgraded to the current format. Several configuration attributes had to be changed in order to preserve backward compatibility. These attributes been changed in the code where appropriate. For the record: | Old Name | Preferred New Name | Comment | | ------------| ---------------|------------| | `max_cache_size` | `ram_cache_size` | | `max_vram_cache` | `vram_cache_size` | | `always_use_cpu` | `use_cpu` | Better to check conf.device == "cpu" |
2 parents 8087b42 + 3f7ac55 commit d3c177a

File tree

13 files changed

+599
-377
lines changed

13 files changed

+599
-377
lines changed

docs/features/CONFIGURATION.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,27 @@ These configuration settings allow you to enable and disable various InvokeAI fe
175175
| `internet_available` | `true` | When a resource is not available locally, try to fetch it via the internet |
176176
| `log_tokenization` | `false` | Before each text2image generation, print a color-coded representation of the prompt to the console; this can help understand why a prompt is not working as expected |
177177
| `patchmatch` | `true` | Activate the "patchmatch" algorithm for improved inpainting |
178-
| `restore` | `true` | Activate the facial restoration features (DEPRECATED; restoration features will be removed in 3.0.0) |
179178

180-
### Memory/Performance
179+
### Generation
181180

182181
These options tune InvokeAI's memory and performance characteristics.
183182

184-
| Setting | Default Value | Description |
185-
|----------|----------------|--------------|
186-
| `always_use_cpu` | `false` | Use the CPU to generate images, even if a GPU is available |
187-
| `free_gpu_mem` | `false` | Aggressively free up GPU memory after each operation; this will allow you to run in low-VRAM environments with some performance penalties |
188-
| `max_cache_size` | `6` | Amount of CPU RAM (in GB) to reserve for caching models in memory; more cache allows you to keep models in memory and switch among them quickly |
189-
| `max_vram_cache_size` | `2.75` | Amount of GPU VRAM (in GB) to reserve for caching models in VRAM; more cache speeds up generation but reduces the size of the images that can be generated. This can be set to zero to maximize the amount of memory available for generation. |
190-
| `precision` | `auto` | Floating point precision. One of `auto`, `float16` or `float32`. `float16` will consume half the memory of `float32` but produce slightly lower-quality images. The `auto` setting will guess the proper precision based on your video card and operating system |
191-
| `sequential_guidance` | `false` | Calculate guidance in serial rather than in parallel, lowering memory requirements at the cost of some performance loss |
192-
| `xformers_enabled` | `true` | If the x-formers memory-efficient attention module is installed, activate it for better memory usage and generation speed|
193-
| `tiled_decode` | `false` | If true, then during the VAE decoding phase the image will be decoded a section at a time, reducing memory consumption at the cost of a performance hit |
183+
| Setting | Default Value | Description |
184+
|-----------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
185+
| `sequential_guidance` | `false` | Calculate guidance in serial rather than in parallel, lowering memory requirements at the cost of some performance loss |
186+
| `attention_type` | `auto` | Select the type of attention to use. One of `auto`,`normal`,`xformers`,`sliced`, or `torch-sdp` |
187+
| `attention_slice_size` | `auto` | When "sliced" attention is selected, set the slice size. One of `auto`, `balanced`, `max` or the integers 1-8|
188+
| `force_tiled_decode` | `false` | Force the VAE step to decode in tiles, reducing memory consumption at the cost of performance |
189+
190+
### Device
191+
192+
These options configure the generation execution device.
193+
194+
| Setting | Default Value | Description |
195+
|-----------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
196+
| `device` | `auto` | Preferred execution device. One of `auto`, `cpu`, `cuda`, `cuda:1`, `mps`. `auto` will choose the device depending on the hardware platform and the installed torch capabilities. |
197+
| `precision` | `auto` | Floating point precision. One of `auto`, `float16` or `float32`. `float16` will consume half the memory of `float32` but produce slightly lower-quality images. The `auto` setting will guess the proper precision based on your video card and operating system |
198+
194199

195200
### Paths
196201

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""
2+
Init file for InvokeAI configure package
3+
"""
4+
5+
from .invokeai_config import ( # noqa F401
6+
InvokeAIAppConfig,
7+
get_invokeai_config,
8+
)
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# Copyright (c) 2023 Lincoln Stein (https://github.com/lstein) and the InvokeAI Development Team
2+
3+
"""
4+
Base class for the InvokeAI configuration system.
5+
It defines a type of pydantic BaseSettings object that
6+
is able to read and write from an omegaconf-based config file,
7+
with overriding of settings from environment variables and/or
8+
the command line.
9+
"""
10+
11+
from __future__ import annotations
12+
import argparse
13+
import os
14+
import pydoc
15+
import sys
16+
from argparse import ArgumentParser
17+
from omegaconf import OmegaConf, DictConfig, ListConfig
18+
from pathlib import Path
19+
from pydantic import BaseSettings
20+
from typing import ClassVar, Dict, List, Literal, Union, get_origin, get_type_hints, get_args
21+
22+
23+
class PagingArgumentParser(argparse.ArgumentParser):
24+
"""
25+
A custom ArgumentParser that uses pydoc to page its output.
26+
It also supports reading defaults from an init file.
27+
"""
28+
29+
def print_help(self, file=None):
30+
text = self.format_help()
31+
pydoc.pager(text)
32+
33+
34+
class InvokeAISettings(BaseSettings):
35+
"""
36+
Runtime configuration settings in which default values are
37+
read from an omegaconf .yaml file.
38+
"""
39+
40+
initconf: ClassVar[DictConfig] = None
41+
argparse_groups: ClassVar[Dict] = {}
42+
43+
def parse_args(self, argv: list = sys.argv[1:]):
44+
parser = self.get_parser()
45+
opt = parser.parse_args(argv)
46+
for name in self.__fields__:
47+
if name not in self._excluded():
48+
value = getattr(opt, name)
49+
if isinstance(value, ListConfig):
50+
value = list(value)
51+
elif isinstance(value, DictConfig):
52+
value = dict(value)
53+
setattr(self, name, value)
54+
55+
def to_yaml(self) -> str:
56+
"""
57+
Return a YAML string representing our settings. This can be used
58+
as the contents of `invokeai.yaml` to restore settings later.
59+
"""
60+
cls = self.__class__
61+
type = get_args(get_type_hints(cls)["type"])[0]
62+
field_dict = dict({type: dict()})
63+
for name, field in self.__fields__.items():
64+
if name in cls._excluded_from_yaml():
65+
continue
66+
category = field.field_info.extra.get("category") or "Uncategorized"
67+
value = getattr(self, name)
68+
if category not in field_dict[type]:
69+
field_dict[type][category] = dict()
70+
# keep paths as strings to make it easier to read
71+
field_dict[type][category][name] = str(value) if isinstance(value, Path) else value
72+
conf = OmegaConf.create(field_dict)
73+
return OmegaConf.to_yaml(conf)
74+
75+
@classmethod
76+
def add_parser_arguments(cls, parser):
77+
if "type" in get_type_hints(cls):
78+
settings_stanza = get_args(get_type_hints(cls)["type"])[0]
79+
else:
80+
settings_stanza = "Uncategorized"
81+
82+
env_prefix = cls.Config.env_prefix if hasattr(cls.Config, "env_prefix") else settings_stanza.upper()
83+
84+
initconf = (
85+
cls.initconf.get(settings_stanza)
86+
if cls.initconf and settings_stanza in cls.initconf
87+
else OmegaConf.create()
88+
)
89+
90+
# create an upcase version of the environment in
91+
# order to achieve case-insensitive environment
92+
# variables (the way Windows does)
93+
upcase_environ = dict()
94+
for key, value in os.environ.items():
95+
upcase_environ[key.upper()] = value
96+
97+
fields = cls.__fields__
98+
cls.argparse_groups = {}
99+
100+
for name, field in fields.items():
101+
if name not in cls._excluded():
102+
current_default = field.default
103+
104+
category = field.field_info.extra.get("category", "Uncategorized")
105+
env_name = env_prefix + "_" + name
106+
if category in initconf and name in initconf.get(category):
107+
field.default = initconf.get(category).get(name)
108+
if env_name.upper() in upcase_environ:
109+
field.default = upcase_environ[env_name.upper()]
110+
cls.add_field_argument(parser, name, field)
111+
112+
field.default = current_default
113+
114+
@classmethod
115+
def cmd_name(self, command_field: str = "type") -> str:
116+
hints = get_type_hints(self)
117+
if command_field in hints:
118+
return get_args(hints[command_field])[0]
119+
else:
120+
return "Uncategorized"
121+
122+
@classmethod
123+
def get_parser(cls) -> ArgumentParser:
124+
parser = PagingArgumentParser(
125+
prog=cls.cmd_name(),
126+
description=cls.__doc__,
127+
)
128+
cls.add_parser_arguments(parser)
129+
return parser
130+
131+
@classmethod
132+
def add_subparser(cls, parser: argparse.ArgumentParser):
133+
parser.add_parser(cls.cmd_name(), help=cls.__doc__)
134+
135+
@classmethod
136+
def _excluded(self) -> List[str]:
137+
# internal fields that shouldn't be exposed as command line options
138+
return ["type", "initconf"]
139+
140+
@classmethod
141+
def _excluded_from_yaml(self) -> List[str]:
142+
# combination of deprecated parameters and internal ones that shouldn't be exposed as invokeai.yaml options
143+
return [
144+
"type",
145+
"initconf",
146+
"version",
147+
"from_file",
148+
"model",
149+
"root",
150+
"max_cache_size",
151+
"max_vram_cache_size",
152+
"always_use_cpu",
153+
"free_gpu_mem",
154+
"xformers_enabled",
155+
"tiled_decode",
156+
]
157+
158+
class Config:
159+
env_file_encoding = "utf-8"
160+
arbitrary_types_allowed = True
161+
case_sensitive = True
162+
163+
@classmethod
164+
def add_field_argument(cls, command_parser, name: str, field, default_override=None):
165+
field_type = get_type_hints(cls).get(name)
166+
default = (
167+
default_override
168+
if default_override is not None
169+
else field.default
170+
if field.default_factory is None
171+
else field.default_factory()
172+
)
173+
if category := field.field_info.extra.get("category"):
174+
if category not in cls.argparse_groups:
175+
cls.argparse_groups[category] = command_parser.add_argument_group(category)
176+
argparse_group = cls.argparse_groups[category]
177+
else:
178+
argparse_group = command_parser
179+
180+
if get_origin(field_type) == Literal:
181+
allowed_values = get_args(field.type_)
182+
allowed_types = set()
183+
for val in allowed_values:
184+
allowed_types.add(type(val))
185+
allowed_types_list = list(allowed_types)
186+
field_type = allowed_types_list[0] if len(allowed_types) == 1 else int_or_float_or_str
187+
188+
argparse_group.add_argument(
189+
f"--{name}",
190+
dest=name,
191+
type=field_type,
192+
default=default,
193+
choices=allowed_values,
194+
help=field.field_info.description,
195+
)
196+
197+
elif get_origin(field_type) == Union:
198+
argparse_group.add_argument(
199+
f"--{name}",
200+
dest=name,
201+
type=int_or_float_or_str,
202+
default=default,
203+
help=field.field_info.description,
204+
)
205+
206+
elif get_origin(field_type) == list:
207+
argparse_group.add_argument(
208+
f"--{name}",
209+
dest=name,
210+
nargs="*",
211+
type=field.type_,
212+
default=default,
213+
action=argparse.BooleanOptionalAction if field.type_ == bool else "store",
214+
help=field.field_info.description,
215+
)
216+
else:
217+
argparse_group.add_argument(
218+
f"--{name}",
219+
dest=name,
220+
type=field.type_,
221+
default=default,
222+
action=argparse.BooleanOptionalAction if field.type_ == bool else "store",
223+
help=field.field_info.description,
224+
)
225+
226+
227+
def int_or_float_or_str(value: str) -> Union[int, float, str]:
228+
"""
229+
Workaround for argparse type checking.
230+
"""
231+
try:
232+
return int(value)
233+
except Exception as e: # noqa F841
234+
pass
235+
try:
236+
return float(value)
237+
except Exception as e: # noqa F841
238+
pass
239+
return str(value)

0 commit comments

Comments
 (0)