Skip to content

Commit ce5122f

Browse files
authored
Add installer support for ip-adapters (invoke-ai#4677)
## What type of PR is this? (check all applicable) - [X] Feature ## Have you discussed this change with the InvokeAI team? - [X] Yes ## Have you updated all relevant documentation? - [X] Yes ## Description This PR adds support for selecting and installing IP-Adapters at configure time. The user is offered the four existing InvokeAI IP Adapters in the UI as shown below. The matching image encoders are selected and installed behind the scenes. That is, if the user selects one of the three sd15 adapters, then the SD encoder will be installed. If they select the sdxl adapter, then the SDXL encoder will be installed. ![image](https://github.com/invoke-ai/InvokeAI/assets/111189/19f46401-99fb-4f7b-9a5e-8f2efd0a5b77) Note that the automatic selection of the encoder does not work when the installer is run in headless mode. I may be able to fix that soon, but I'm out of time today.
2 parents 6fcc7d4 + 43ebd68 commit ce5122f

3 files changed

Lines changed: 89 additions & 12 deletions

File tree

invokeai/backend/install/model_install_backend.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,6 @@
7575
}
7676

7777

78-
@dataclass
79-
class ModelInstallList:
80-
"""Class for listing models to be installed/removed"""
81-
82-
install_models: List[str] = field(default_factory=list)
83-
remove_models: List[str] = field(default_factory=list)
84-
85-
8678
@dataclass
8779
class InstallSelections:
8880
install_models: List[str] = field(default_factory=list)
@@ -100,6 +92,7 @@ class ModelLoadInfo:
10092
installed: bool = False
10193
recommended: bool = False
10294
default: bool = False
95+
requires: Optional[List[str]] = field(default_factory=list)
10396

10497

10598
class ModelInstall(object):
@@ -137,8 +130,6 @@ def all_models(self) -> Dict[str, ModelLoadInfo]:
137130

138131
# supplement with entries in models.yaml
139132
installed_models = [x for x in self.mgr.list_models()]
140-
# suppresses autoloaded models
141-
# installed_models = [x for x in self.mgr.list_models() if not self._is_autoloaded(x)]
142133

143134
for md in installed_models:
144135
base = md["base_model"]
@@ -170,9 +161,12 @@ def _is_autoloaded(self, model_info: dict) -> bool:
170161

171162
def list_models(self, model_type):
172163
installed = self.mgr.list_models(model_type=model_type)
164+
print()
173165
print(f"Installed models of type `{model_type}`:")
166+
print(f"{'Model Key':50} Model Path")
174167
for i in installed:
175-
print(f"{i['model_name']}\t{i['base_model']}\t{i['path']}")
168+
print(f"{'/'.join([i['base_model'],i['model_type'],i['model_name']]):50} {i['path']}")
169+
print()
176170

177171
# logic here a little reversed to maintain backward compatibility
178172
def starter_models(self, all_models: bool = False) -> Set[str]:
@@ -210,6 +204,8 @@ def install(self, selections: InstallSelections):
210204
job += 1
211205

212206
# add requested models
207+
self._remove_installed(selections.install_models)
208+
self._add_required_models(selections.install_models)
213209
for path in selections.install_models:
214210
logger.info(f"Installing {path} [{job}/{jobs}]")
215211
try:
@@ -269,6 +265,26 @@ def heuristic_import(
269265

270266
return models_installed
271267

268+
def _remove_installed(self, model_list: List[str]):
269+
all_models = self.all_models()
270+
for path in model_list:
271+
key = self.reverse_paths.get(path)
272+
if key and all_models[key].installed:
273+
logger.warning(f"{path} already installed. Skipping.")
274+
model_list.remove(path)
275+
276+
def _add_required_models(self, model_list: List[str]):
277+
additional_models = []
278+
all_models = self.all_models()
279+
for path in model_list:
280+
if not (key := self.reverse_paths.get(path)):
281+
continue
282+
for requirement in all_models[key].requires:
283+
requirement_key = self.reverse_paths.get(requirement)
284+
if not all_models[requirement_key].installed:
285+
additional_models.append(requirement)
286+
model_list.extend(additional_models)
287+
272288
# install a model from a local path. The optional info parameter is there to prevent
273289
# the model from being probed twice in the event that it has already been probed.
274290
def _install_path(self, path: Path, info: ModelProbeInfo = None) -> AddModelResult:

invokeai/configs/INITIAL_MODELS.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,35 @@ sd-1/lora/LowRA:
103103
recommended: True
104104
sd-1/lora/Ink scenery:
105105
path: https://civitai.com/api/download/models/83390
106+
sd-1/ip_adapter/ip_adapter_sd15:
107+
repo_id: InvokeAI/ip_adapter_sd15
108+
recommended: True
109+
requires:
110+
- InvokeAI/ip_adapter_sd_image_encoder
111+
description: IP-Adapter for SD 1.5 models
112+
sd-1/ip_adapter/ip_adapter_plus_sd15:
113+
repo_id: InvokeAI/ip_adapter_plus_sd15
114+
recommended: False
115+
requires:
116+
- InvokeAI/ip_adapter_sd_image_encoder
117+
description: Refined IP-Adapter for SD 1.5 models
118+
sd-1/ip_adapter/ip_adapter_plus_face_sd15:
119+
repo_id: InvokeAI/ip_adapter_plus_face_sd15
120+
recommended: False
121+
requires:
122+
- InvokeAI/ip_adapter_sd_image_encoder
123+
description: Refined IP-Adapter for SD 1.5 models, adapted for faces
124+
sdxl/ip_adapter/ip_adapter_sdxl:
125+
repo_id: InvokeAI/ip_adapter_sdxl
126+
recommended: False
127+
requires:
128+
- InvokeAI/ip_adapter_sdxl_image_encoder
129+
description: IP-Adapter for SDXL models
130+
any/clip_vision/ip_adapter_sd_image_encoder:
131+
repo_id: InvokeAI/ip_adapter_sd_image_encoder
132+
recommended: False
133+
description: Required model for using IP-Adapters with SD-1/2 models
134+
any/clip_vision/ip_adapter_sdxl_image_encoder:
135+
repo_id: InvokeAI/ip_adapter_sdxl_image_encoder
136+
recommended: False
137+
description: Required model for using IP-Adapters with SDXL models

invokeai/frontend/install/model_install.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,12 @@ def create(self):
101101
"STARTER MODELS",
102102
"MAIN MODELS",
103103
"CONTROLNETS",
104+
"IP-ADAPTERS",
104105
"LORA/LYCORIS",
105106
"TEXTUAL INVERSION",
106107
],
107108
value=[self.current_tab],
108-
columns=5,
109+
columns=6,
109110
max_height=2,
110111
relx=8,
111112
scroll_exit=True,
@@ -130,6 +131,13 @@ def create(self):
130131
)
131132
bottom_of_table = max(bottom_of_table, self.nextrely)
132133

134+
self.nextrely = top_of_table
135+
self.ipadapter_models = self.add_model_widgets(
136+
model_type=ModelType.IPAdapter,
137+
window_width=window_width,
138+
)
139+
bottom_of_table = max(bottom_of_table, self.nextrely)
140+
133141
self.nextrely = top_of_table
134142
self.lora_models = self.add_model_widgets(
135143
model_type=ModelType.Lora,
@@ -343,6 +351,7 @@ def _toggle_tables(self, value=None):
343351
self.starter_pipelines,
344352
self.pipeline_models,
345353
self.controlnet_models,
354+
self.ipadapter_models,
346355
self.lora_models,
347356
self.ti_models,
348357
]
@@ -532,6 +541,7 @@ def marshall_arguments(self):
532541
self.starter_pipelines,
533542
self.pipeline_models,
534543
self.controlnet_models,
544+
self.ipadapter_models,
535545
self.lora_models,
536546
self.ti_models,
537547
]
@@ -553,6 +563,25 @@ def marshall_arguments(self):
553563
if downloads := section.get("download_ids"):
554564
selections.install_models.extend(downloads.value.split())
555565

566+
# NOT NEEDED - DONE IN BACKEND NOW
567+
# # special case for the ipadapter_models. If any of the adapters are
568+
# # chosen, then we add the corresponding encoder(s) to the install list.
569+
# section = self.ipadapter_models
570+
# if section.get("models_selected"):
571+
# selected_adapters = [
572+
# self.all_models[section["models"][x]].name for x in section.get("models_selected").value
573+
# ]
574+
# encoders = []
575+
# if any(["sdxl" in x for x in selected_adapters]):
576+
# encoders.append("ip_adapter_sdxl_image_encoder")
577+
# if any(["sd15" in x for x in selected_adapters]):
578+
# encoders.append("ip_adapter_sd_image_encoder")
579+
# for encoder in encoders:
580+
# key = f"any/clip_vision/{encoder}"
581+
# repo_id = f"InvokeAI/{encoder}"
582+
# if key not in self.all_models:
583+
# selections.install_models.append(repo_id)
584+
556585

557586
class AddModelApplication(npyscreen.NPSAppManaged):
558587
def __init__(self, opt):

0 commit comments

Comments
 (0)