Compare commits

..

1 Commits

Author SHA1 Message Date
brandonrising
0b238b1ece Update probe to always use cpu for loading models 2024-04-03 16:29:38 -04:00
31 changed files with 145 additions and 339 deletions

View File

@@ -32,5 +32,5 @@ As described in the [frontend dev toolchain] docs, you can run the UI using a de
[Fork and clone]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo
[InvokeAI repo]: https://github.com/invoke-ai/InvokeAI
[frontend dev toolchain]: ../contributing/frontend/OVERVIEW.md
[manual installation]: ./020_INSTALL_MANUAL.md
[manual installation]: installation/020_INSTALL_MANUAL.md
[editable install]: https://pip.pypa.io/en/latest/cli/pip_install/#cmdoption-e

View File

@@ -3,7 +3,6 @@ Invoke-managed custom node loader. See README.md for more information.
"""
import sys
import traceback
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
@@ -42,15 +41,11 @@ for d in Path(__file__).parent.iterdir():
logger.info(f"Loading node pack {module_name}")
try:
module = module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
module = module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
loaded_count += 1
except Exception:
full_error = traceback.format_exc()
logger.error(f"Failed to load node pack {module_name}:\n{full_error}")
loaded_count += 1
del init, module_name

View File

@@ -373,16 +373,13 @@ def migrate_v3_config_dict(config_dict: dict[str, Any]) -> InvokeAIAppConfig:
if k == "conf_path":
parsed_config_dict["legacy_models_yaml_path"] = v
if k == "legacy_conf_dir":
# The old default for this was "configs/stable-diffusion" ("configs\stable-diffusion" on Windows).
if v == "configs/stable-diffusion" or v == "configs\\stable-diffusion":
# If if the incoming config has the default value, skip
continue
elif Path(v).name == "stable-diffusion":
# Else if the path ends in "stable-diffusion", we assume the parent is the new correct path.
parsed_config_dict["legacy_conf_dir"] = str(Path(v).parent)
else:
# Else we do not attempt to migrate this setting
# The old default for this was "configs/stable-diffusion". If if the incoming config has that as the value, we won't set it.
# Else if the path ends in "stable-diffusion", we assume the parent is the new correct path.
# Else we do not attempt to migrate this setting
if v != "configs/stable-diffusion":
parsed_config_dict["legacy_conf_dir"] = v
elif Path(v).name == "stable-diffusion":
parsed_config_dict["legacy_conf_dir"] = str(Path(v).parent)
elif k in InvokeAIAppConfig.model_fields:
# skip unknown fields
parsed_config_dict[k] = v

View File

@@ -11,7 +11,6 @@ from invokeai.app.services.shared.sqlite_migrator.migrations.migration_5 import
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_6 import build_migration_6
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_7 import build_migration_7
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_8 import build_migration_8
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_9 import build_migration_9
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_impl import SqliteMigrator
@@ -40,7 +39,6 @@ def init_db(config: InvokeAIAppConfig, logger: Logger, image_files: ImageFileSto
migrator.register_migration(build_migration_6())
migrator.register_migration(build_migration_7())
migrator.register_migration(build_migration_8(app_config=config))
migrator.register_migration(build_migration_9())
migrator.run_migrations()
return db

View File

@@ -1,29 +0,0 @@
import sqlite3
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
class Migration9Callback:
def __call__(self, cursor: sqlite3.Cursor) -> None:
self._empty_session_queue(cursor)
def _empty_session_queue(self, cursor: sqlite3.Cursor) -> None:
"""Empties the session queue. This is done to prevent any lingering session queue items from causing pydantic errors due to changed schemas."""
cursor.execute("DELETE FROM session_queue;")
def build_migration_9() -> Migration:
"""
Build the migration from database version 8 to 9.
This migration does the following:
- Empties the session queue. This is done to prevent any lingering session queue items from causing pydantic errors due to changed schemas.
"""
migration_9 = Migration(
from_version=8,
to_version=9,
callback=Migration9Callback(),
)
return migration_9

View File

@@ -1,6 +1,4 @@
import sqlite3
from contextlib import closing
from datetime import datetime
from pathlib import Path
from typing import Optional
@@ -34,7 +32,6 @@ class SqliteMigrator:
self._db = db
self._logger = db.logger
self._migration_set = MigrationSet()
self._backup_path: Optional[Path] = None
def register_migration(self, migration: Migration) -> None:
"""Registers a migration."""
@@ -58,18 +55,6 @@ class SqliteMigrator:
return False
self._logger.info("Database update needed")
# Make a backup of the db if it needs to be updated and is a file db
if self._db.db_path is not None:
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
self._backup_path = self._db.db_path.parent / f"{self._db.db_path.stem}_backup_{timestamp}.db"
self._logger.info(f"Backing up database to {str(self._backup_path)}")
# Use SQLite to do the backup
with closing(sqlite3.connect(self._backup_path)) as backup_conn:
self._db.conn.backup(backup_conn)
else:
self._logger.info("Using in-memory database, no backup needed")
next_migration = self._migration_set.get(from_version=self._get_current_version(cursor))
while next_migration is not None:
self._run_migration(next_migration)

View File

@@ -3,7 +3,7 @@
import bisect
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Type, Union
from typing import Dict, List, Optional, Tuple, Union
import torch
from safetensors.torch import load_file
@@ -457,55 +457,6 @@ class LoRAModelRaw(RawModel): # (torch.nn.Module):
return new_state_dict
@classmethod
def _keys_match(cls, keys: set[str], required_keys: set[str], optional_keys: set[str]) -> bool:
"""Check if the set of keys matches the required and optional keys."""
if len(required_keys - keys) > 0:
# missing required keys.
return False
non_required_keys = keys - required_keys
for k in non_required_keys:
if k not in optional_keys:
# unexpected key
return False
return True
@classmethod
def get_layer_type_from_state_dict_keys(cls, peft_layer_keys: set[str]) -> Type[AnyLoRALayer]:
"""Infer the parameter-efficient finetuning model type from the state dict keys."""
common_optional_keys = {"alpha", "bias_indices", "bias_values", "bias_size"}
if cls._keys_match(
peft_layer_keys,
required_keys={"lora_down.weight", "lora_up.weight"},
optional_keys=common_optional_keys | {"lora_mid.weight"},
):
return LoRALayer
if cls._keys_match(
peft_layer_keys,
required_keys={"hada_w1_b", "hada_w1_a", "hada_w2_b", "hada_w2_a"},
optional_keys=common_optional_keys | {"hada_t1", "hada_t2"},
):
return LoHALayer
if cls._keys_match(
peft_layer_keys,
required_keys=set(),
optional_keys=common_optional_keys
| {"lokr_w1", "lokr_w1_a", "lokr_w1_b", "lokr_w2", "lokr_w2_a", "lokr_w2_b", "lokr_t2"},
):
return LoKRLayer
if cls._keys_match(peft_layer_keys, required_keys={"diff"}, optional_keys=common_optional_keys):
return FullLayer
if cls._keys_match(peft_layer_keys, required_keys={"weight", "on_input"}, optional_keys=common_optional_keys):
return IA3Layer
raise ValueError(f"Unsupported PEFT model type with keys: {peft_layer_keys}")
@classmethod
def from_checkpoint(
cls,
@@ -535,21 +486,37 @@ class LoRAModelRaw(RawModel): # (torch.nn.Module):
if base_model == BaseModelType.StableDiffusionXL:
state_dict = cls._convert_sdxl_keys_to_diffusers_format(state_dict)
# We assume that all layers have the same PEFT layer type. This saves time by not having to infer the type for
# each layer.
first_module_key = next(iter(state_dict))
peft_layer_keys = set(state_dict[first_module_key].keys())
layer_cls = cls.get_layer_type_from_state_dict_keys(peft_layer_keys)
for layer_key, values in state_dict.items():
layer = layer_cls(layer_key, values)
# lora and locon
if "lora_down.weight" in values:
layer: AnyLoRALayer = LoRALayer(layer_key, values)
# loha
elif "hada_w1_b" in values:
layer = LoHALayer(layer_key, values)
# lokr
elif "lokr_w1_b" in values or "lokr_w1" in values:
layer = LoKRLayer(layer_key, values)
# diff
elif "diff" in values:
layer = FullLayer(layer_key, values)
# ia3
elif "weight" in values and "on_input" in values:
layer = IA3Layer(layer_key, values)
else:
print(f">> Encountered unknown lora layer module in {model.name}: {layer_key} - {list(values.keys())}")
raise Exception("Unknown lora format!")
# lower memory consumption by removing already parsed layer values
state_dict[layer_key].clear()
# TODO(ryand): This .to() call causes an implicit CUDA sync point in a tight loop. This is very slow (even
# slower than loading the weights from disk). We should ideally only be copying the weights once - right
# before they are used. Or, if we want to do this here, then setting non_blocking = True would probably
# help.
layer.to(device=device, dtype=dtype)
model.layers[layer_key] = layer
return model
@staticmethod

View File

@@ -323,7 +323,7 @@ class ModelProbe(object):
with SilenceWarnings():
if model_path.suffix.endswith((".ckpt", ".pt", ".pth", ".bin")):
cls._scan_model(model_path.name, model_path)
model = torch.load(model_path)
model = torch.load(model_path, map_location="cpu")
assert isinstance(model, dict)
return model
else:

View File

@@ -94,7 +94,6 @@
"reactflow": "^11.10.4",
"redux-dynamic-middlewares": "^2.2.0",
"redux-remember": "^5.1.0",
"rfdc": "^1.3.1",
"roarr": "^7.21.1",
"serialize-error": "^11.0.3",
"socket.io-client": "^4.7.5",

View File

@@ -137,9 +137,6 @@ dependencies:
redux-remember:
specifier: ^5.1.0
version: 5.1.0(redux@5.0.1)
rfdc:
specifier: ^1.3.1
version: 1.3.1
roarr:
specifier: ^7.21.1
version: 7.21.1
@@ -12131,10 +12128,6 @@ packages:
resolution: {integrity: sha512-/x8uIPdTafBqakK0TmPNJzgkLP+3H+yxpUJhCQHsLBg1rYEVNR2D8BRYNWQhVBjyOd7oo1dZRVzIkwMY2oqfYQ==}
dev: true
/rfdc@1.3.1:
resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==}
dev: false
/rimraf@2.6.3:
resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==}
hasBin: true

View File

@@ -4,7 +4,7 @@
"reportBugLabel": "Fehler melden",
"settingsLabel": "Einstellungen",
"img2img": "Bild zu Bild",
"nodes": "Arbeitsabläufe",
"nodes": "Knoten Editor",
"upload": "Hochladen",
"load": "Laden",
"statusDisconnected": "Getrennt",
@@ -74,8 +74,7 @@
"updated": "Aktualisiert",
"copy": "Kopieren",
"aboutHeading": "Nutzen Sie Ihre kreative Energie",
"toResolve": "Lösen",
"add": "Hinzufügen"
"toResolve": "Lösen"
},
"gallery": {
"galleryImageSize": "Bildgröße",
@@ -105,16 +104,11 @@
"dropToUpload": "$t(gallery.drop) zum hochladen",
"dropOrUpload": "$t(gallery.drop) oder hochladen",
"drop": "Ablegen",
"problemDeletingImages": "Problem beim Löschen der Bilder",
"bulkDownloadRequested": "Download vorbereiten",
"bulkDownloadRequestedDesc": "Dein Download wird vorbereitet. Dies kann ein paar Momente dauern.",
"bulkDownloadRequestFailed": "Problem beim Download vorbereiten",
"bulkDownloadFailed": "Download fehlgeschlagen",
"alwaysShowImageSizeBadge": "Zeige immer Bilder Größe Abzeichen"
"problemDeletingImages": "Problem beim Löschen der Bilder"
},
"hotkeys": {
"keyboardShortcuts": "Tastenkürzel",
"appHotkeys": "App",
"appHotkeys": "App-Tastenkombinationen",
"generalHotkeys": "Allgemein",
"galleryHotkeys": "Galerie",
"unifiedCanvasHotkeys": "Leinwand",
@@ -763,9 +757,7 @@
"scheduler": "Planer",
"noRecallParameters": "Es wurden keine Parameter zum Abrufen gefunden",
"recallParameters": "Parameter wiederherstellen",
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)",
"allPrompts": "Alle Prompts",
"imageDimensions": "Bilder Auslösungen"
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)"
},
"popovers": {
"noiseUseCPU": {
@@ -1076,10 +1068,5 @@
},
"dynamicPrompts": {
"showDynamicPrompts": "Dynamische Prompts anzeigen"
},
"prompt": {
"noMatchingTriggers": "Keine passenden Auslöser",
"addPromptTrigger": "Auslöse Text hinzufügen",
"compatibleEmbeddings": "Kompatible Einbettungen"
}
}

View File

@@ -73,8 +73,7 @@
"ai": "ia",
"file": "File",
"toResolve": "Da risolvere",
"add": "Aggiungi",
"loglevel": "Livello di log"
"add": "Aggiungi"
},
"gallery": {
"galleryImageSize": "Dimensione dell'immagine",
@@ -935,9 +934,7 @@
"base": "Base",
"lineart": "Linea",
"controlnet": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.controlNet))",
"mediapipeFace": "Mediapipe Volto",
"ip_adapter": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.ipAdapter))",
"t2i_adapter": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.t2iAdapter))"
"mediapipeFace": "Mediapipe Volto"
},
"queue": {
"queueFront": "Aggiungi all'inizio della coda",
@@ -1493,8 +1490,7 @@
"title": "Generazione"
},
"advanced": {
"title": "Avanzate",
"options": "Opzioni $t(accordions.advanced.title)"
"title": "Avanzate"
},
"image": {
"title": "Immagine"

View File

@@ -75,8 +75,7 @@
"copy": "Копировать",
"localSystem": "Локальная система",
"aboutDesc": "Используя Invoke для работы? Проверьте это:",
"add": "Добавить",
"loglevel": "Уровень логов"
"add": "Добавить"
},
"gallery": {
"galleryImageSize": "Размер изображений",
@@ -1506,8 +1505,7 @@
"title": "Генерация"
},
"advanced": {
"title": "Расширенные",
"options": "Опции $t(accordions.advanced.title)"
"title": "Расширенные"
},
"image": {
"title": "Изображение"

View File

@@ -1,7 +1,7 @@
import type { UnknownAction } from '@reduxjs/toolkit';
import { deepClone } from 'common/util/deepClone';
import { isAnyGraphBuilt } from 'features/nodes/store/actions';
import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice';
import { cloneDeep } from 'lodash-es';
import { appInfoApi } from 'services/api/endpoints/appInfo';
import type { Graph } from 'services/api/types';
import { socketGeneratorProgress } from 'services/events/actions';
@@ -33,7 +33,7 @@ export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
}
if (socketGeneratorProgress.match(action)) {
const sanitized = deepClone(action);
const sanitized = cloneDeep(action);
if (sanitized.payload.data.progress_image) {
sanitized.payload.data.progress_image.dataURL = '<Progress image omitted>';
}

View File

@@ -1,5 +1,4 @@
import { deepClone } from 'common/util/deepClone';
import { merge } from 'lodash-es';
import { cloneDeep, merge } from 'lodash-es';
import { ClickScrollPlugin, OverlayScrollbars } from 'overlayscrollbars';
import type { UseOverlayScrollbarsParams } from 'overlayscrollbars-react';
@@ -23,7 +22,7 @@ export const getOverlayScrollbarsParams = (
overflowX: 'hidden' | 'scroll' = 'hidden',
overflowY: 'hidden' | 'scroll' = 'scroll'
) => {
const params = deepClone(overlayScrollbarsParams);
const params = cloneDeep(overlayScrollbarsParams);
merge(params, { options: { overflow: { y: overflowY, x: overflowX } } });
return params;
};

View File

@@ -1,15 +0,0 @@
import rfdc from 'rfdc';
const _rfdc = rfdc();
/**
* Deep-clones an object using Really Fast Deep Clone.
* This is the fastest deep clone library on Chrome, but not the fastest on FF. Still, it's much faster than lodash
* and structuredClone, so it's the best all-around choice.
*
* Simple Benchmark: https://www.measurethat.net/Benchmarks/Show/30358/0/lodash-clonedeep-vs-jsonparsejsonstringify-vs-recursive
* Repo: https://github.com/davidmarkclements/rfdc
*
* @param obj The object to deep-clone
* @returns The cloned object
*/
export const deepClone = <T>(obj: T): T => _rfdc(obj);

View File

@@ -1,7 +1,6 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
import calculateCoordinates from 'features/canvas/util/calculateCoordinates';
import calculateScale from 'features/canvas/util/calculateScale';
@@ -14,7 +13,7 @@ import { modelChanged } from 'features/parameters/store/generationSlice';
import type { PayloadActionWithOptimalDimension } from 'features/parameters/store/types';
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
import type { IRect, Vector2d } from 'konva/lib/types';
import { clamp } from 'lodash-es';
import { clamp, cloneDeep } from 'lodash-es';
import type { RgbaColor } from 'react-colorful';
import { queueApi } from 'services/api/endpoints/queue';
import type { ImageDTO } from 'services/api/types';
@@ -37,7 +36,7 @@ import { CANVAS_GRID_SIZE_FINE } from './constants';
/**
* The maximum history length to keep in the past/future layer states.
*/
const MAX_HISTORY = 100;
const MAX_HISTORY = 128;
const initialLayerState: CanvasLayerState = {
objects: [],
@@ -122,7 +121,7 @@ export const canvasSlice = createSlice({
state.brushSize = action.payload;
},
clearMask: (state) => {
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
state.layerState.objects = state.layerState.objects.filter((obj) => !isCanvasMaskLine(obj));
state.futureLayerStates = [];
state.shouldPreserveMaskedArea = false;
@@ -164,10 +163,10 @@ export const canvasSlice = createSlice({
state.boundingBoxDimensions = newBoundingBoxDimensions;
state.boundingBoxCoordinates = newBoundingBoxCoordinates;
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
state.layerState = {
...deepClone(initialLayerState),
...cloneDeep(initialLayerState),
objects: [
{
kind: 'image',
@@ -262,7 +261,11 @@ export const canvasSlice = createSlice({
return;
}
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift();
}
state.layerState.stagingArea.images.push({
kind: 'image',
@@ -276,9 +279,13 @@ export const canvasSlice = createSlice({
state.futureLayerStates = [];
},
discardStagedImages: (state) => {
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
state.layerState.stagingArea = deepClone(initialLayerState.stagingArea);
if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift();
}
state.layerState.stagingArea = cloneDeep(cloneDeep(initialLayerState)).stagingArea;
state.futureLayerStates = [];
state.shouldShowStagingOutline = true;
@@ -287,7 +294,11 @@ export const canvasSlice = createSlice({
},
discardStagedImage: (state) => {
const { images, selectedImageIndex } = state.layerState.stagingArea;
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift();
}
if (!images.length) {
return;
@@ -309,7 +320,11 @@ export const canvasSlice = createSlice({
addFillRect: (state) => {
const { boundingBoxCoordinates, boundingBoxDimensions, brushColor } = state;
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift();
}
state.layerState.objects.push({
kind: 'fillRect',
@@ -324,7 +339,11 @@ export const canvasSlice = createSlice({
addEraseRect: (state) => {
const { boundingBoxCoordinates, boundingBoxDimensions } = state;
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift();
}
state.layerState.objects.push({
kind: 'eraseRect',
@@ -348,7 +367,11 @@ export const canvasSlice = createSlice({
// set & then spread this to only conditionally add the "color" key
const newColor = layer === 'base' && tool === 'brush' ? { color: brushColor } : {};
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift();
}
const newLine: CanvasMaskLine | CanvasBaseLine = {
kind: 'line',
@@ -386,7 +409,11 @@ export const canvasSlice = createSlice({
return;
}
pushToFutureLayerStates(state);
state.futureLayerStates.unshift(cloneDeep(state.layerState));
if (state.futureLayerStates.length > MAX_HISTORY) {
state.futureLayerStates.pop();
}
state.layerState = targetState;
},
@@ -397,7 +424,11 @@ export const canvasSlice = createSlice({
return;
}
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift();
}
state.layerState = targetState;
},
@@ -414,8 +445,8 @@ export const canvasSlice = createSlice({
state.shouldShowIntermediates = action.payload;
},
resetCanvas: (state) => {
pushToPrevLayerStates(state);
state.layerState = deepClone(initialLayerState);
state.pastLayerStates.push(cloneDeep(state.layerState));
state.layerState = cloneDeep(initialLayerState);
state.futureLayerStates = [];
state.batchIds = [];
state.boundingBoxCoordinates = {
@@ -509,7 +540,11 @@ export const canvasSlice = createSlice({
const { images, selectedImageIndex } = state.layerState.stagingArea;
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates.shift();
}
const imageToCommit = images[selectedImageIndex];
@@ -518,7 +553,7 @@ export const canvasSlice = createSlice({
...imageToCommit,
});
}
state.layerState.stagingArea = deepClone(initialLayerState.stagingArea);
state.layerState.stagingArea = cloneDeep(initialLayerState).stagingArea;
state.futureLayerStates = [];
state.shouldShowStagingOutline = true;
@@ -588,7 +623,7 @@ export const canvasSlice = createSlice({
};
},
setMergedCanvas: (state, action: PayloadAction<CanvasImage>) => {
pushToPrevLayerStates(state);
state.pastLayerStates.push(cloneDeep(state.layerState));
state.futureLayerStates = [];
@@ -708,17 +743,3 @@ export const canvasPersistConfig: PersistConfig<CanvasState> = {
migrate: migrateCanvasState,
persistDenylist: [],
};
const pushToPrevLayerStates = (state: CanvasState) => {
state.pastLayerStates.push(deepClone(state.layerState));
if (state.pastLayerStates.length > MAX_HISTORY) {
state.pastLayerStates = state.pastLayerStates.slice(-MAX_HISTORY);
}
};
const pushToFutureLayerStates = (state: CanvasState) => {
state.futureLayerStates.unshift(deepClone(state.layerState));
if (state.futureLayerStates.length > MAX_HISTORY) {
state.futureLayerStates = state.futureLayerStates.slice(0, MAX_HISTORY);
}
};

View File

@@ -2,11 +2,10 @@ import type { PayloadAction, Update } from '@reduxjs/toolkit';
import { createEntityAdapter, createSlice, isAnyOf } from '@reduxjs/toolkit';
import { getSelectorsOptions } from 'app/store/createMemoizedSelector';
import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { buildControlAdapter } from 'features/controlAdapters/util/buildControlAdapter';
import { buildControlAdapterProcessor } from 'features/controlAdapters/util/buildControlAdapterProcessor';
import { zModelIdentifierField } from 'features/nodes/types/common';
import { merge, uniq } from 'lodash-es';
import { cloneDeep, merge, uniq } from 'lodash-es';
import type { ControlNetModelConfig, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
import { socketInvocationError } from 'services/events/actions';
import { v4 as uuidv4 } from 'uuid';
@@ -115,7 +114,7 @@ export const controlAdaptersSlice = createSlice({
if (!controlAdapter) {
return;
}
const newControlAdapter = merge(deepClone(controlAdapter), {
const newControlAdapter = merge(cloneDeep(controlAdapter), {
id: newId,
isEnabled: true,
});
@@ -271,7 +270,7 @@ export const controlAdaptersSlice = createSlice({
return;
}
const processorNode = merge(deepClone(cn.processorNode), params);
const processorNode = merge(cloneDeep(cn.processorNode), params);
caAdapter.updateOne(state, {
id,
@@ -294,7 +293,7 @@ export const controlAdaptersSlice = createSlice({
return;
}
const processorNode = deepClone(
const processorNode = cloneDeep(
CONTROLNET_PROCESSORS[processorType].buildDefaults(cn.model?.base)
) as RequiredControlAdapterProcessorNode;
@@ -334,7 +333,7 @@ export const controlAdaptersSlice = createSlice({
caAdapter.updateOne(state, update);
},
controlAdaptersReset: () => {
return deepClone(initialControlAdaptersState);
return cloneDeep(initialControlAdaptersState);
},
pendingControlImagesCleared: (state) => {
state.pendingControlImages = [];
@@ -407,7 +406,7 @@ const migrateControlAdaptersState = (state: any): any => {
state._version = 1;
}
if (state._version === 1) {
state = deepClone(initialControlAdaptersState);
state = cloneDeep(initialControlAdaptersState);
}
return state;
};

View File

@@ -1,4 +1,3 @@
import { deepClone } from 'common/util/deepClone';
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import type {
ControlAdapterConfig,
@@ -8,7 +7,7 @@ import type {
RequiredCannyImageProcessorInvocation,
T2IAdapterConfig,
} from 'features/controlAdapters/store/types';
import { merge } from 'lodash-es';
import { cloneDeep, merge } from 'lodash-es';
export const initialControlNet: Omit<ControlNetConfig, 'id'> = {
type: 'controlnet',
@@ -58,11 +57,11 @@ export const buildControlAdapter = (
): ControlAdapterConfig => {
switch (type) {
case 'controlnet':
return merge(deepClone(initialControlNet), { id, ...overrides });
return merge(cloneDeep(initialControlNet), { id, ...overrides });
case 't2i_adapter':
return merge(deepClone(initialT2IAdapter), { id, ...overrides });
return merge(cloneDeep(initialT2IAdapter), { id, ...overrides });
case 'ip_adapter':
return merge(deepClone(initialIPAdapter), { id, ...overrides });
return merge(cloneDeep(initialIPAdapter), { id, ...overrides });
default:
throw new Error(`Unknown control adapter type: ${type}`);
}

View File

@@ -1,9 +1,9 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { zModelIdentifierField } from 'features/nodes/types/common';
import type { ParameterLoRAModel } from 'features/parameters/types/parameterSchemas';
import { cloneDeep } from 'lodash-es';
import type { LoRAModelConfig } from 'services/api/types';
export type LoRA = {
@@ -58,7 +58,7 @@ export const loraSlice = createSlice({
}
lora.isEnabled = isEnabled;
},
lorasReset: () => deepClone(initialLoraState),
lorasReset: () => cloneDeep(initialLoraState),
},
});
@@ -74,7 +74,7 @@ const migrateLoRAState = (state: any): any => {
}
if (state._version === 1) {
// Model type has changed, so we need to reset the state - too risky to migrate
state = deepClone(initialLoraState);
state = cloneDeep(initialLoraState);
}
return state;
};

View File

@@ -1,7 +1,6 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice, isAnyOf } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { workflowLoaded } from 'features/nodes/store/actions';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type {
@@ -45,7 +44,7 @@ import {
} from 'features/nodes/types/field';
import type { AnyNode, InvocationTemplate, NodeExecutionState } from 'features/nodes/types/invocation';
import { isInvocationNode, isNotesNode, zNodeStatus } from 'features/nodes/types/invocation';
import { forEach } from 'lodash-es';
import { cloneDeep, forEach } from 'lodash-es';
import type {
Connection,
Edge,
@@ -572,23 +571,8 @@ export const nodesSlice = createSlice({
);
},
selectionCopied: (state) => {
const nodesToCopy: AnyNode[] = [];
const edgesToCopy: Edge[] = [];
for (const node of state.nodes) {
if (node.selected) {
nodesToCopy.push(deepClone(node));
}
}
for (const edge of state.edges) {
if (edge.selected) {
edgesToCopy.push(deepClone(edge));
}
}
state.nodesToCopy = nodesToCopy;
state.edgesToCopy = edgesToCopy;
state.nodesToCopy = state.nodes.filter((n) => n.selected).map(cloneDeep);
state.edgesToCopy = state.edges.filter((e) => e.selected).map(cloneDeep);
if (state.nodesToCopy.length > 0) {
const averagePosition = { x: 0, y: 0 };
@@ -610,21 +594,11 @@ export const nodesSlice = createSlice({
},
selectionPasted: (state, action: PayloadAction<{ cursorPosition?: XYPosition }>) => {
const { cursorPosition } = action.payload;
const newNodes: AnyNode[] = [];
for (const node of state.nodesToCopy) {
newNodes.push(deepClone(node));
}
const newNodes = state.nodesToCopy.map(cloneDeep);
const oldNodeIds = newNodes.map((n) => n.data.id);
const newEdges: Edge[] = [];
for (const edge of state.edgesToCopy) {
if (oldNodeIds.includes(edge.source) && oldNodeIds.includes(edge.target)) {
newEdges.push(deepClone(edge));
}
}
const newEdges = state.edgesToCopy
.filter((e) => oldNodeIds.includes(e.source) && oldNodeIds.includes(e.target))
.map(cloneDeep);
newEdges.forEach((e) => (e.selected = true));

View File

@@ -1,7 +1,6 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { workflowLoaded } from 'features/nodes/store/actions';
import { isAnyNodeOrEdgeMutation, nodeEditorReset, nodesChanged, nodesDeleted } from 'features/nodes/store/nodesSlice';
import type {
@@ -12,7 +11,7 @@ import type {
import type { FieldIdentifier } from 'features/nodes/types/field';
import { isInvocationNode } from 'features/nodes/types/invocation';
import type { WorkflowCategory, WorkflowV3 } from 'features/nodes/types/workflow';
import { isEqual, omit, uniqBy } from 'lodash-es';
import { cloneDeep, isEqual, omit, uniqBy } from 'lodash-es';
const blankWorkflow: Omit<WorkflowV3, 'nodes' | 'edges'> = {
name: '',
@@ -132,8 +131,8 @@ export const workflowSlice = createSlice({
});
return {
...deepClone(initialWorkflowState),
...deepClone(workflowExtra),
...cloneDeep(initialWorkflowState),
...cloneDeep(workflowExtra),
originalExposedFieldValues,
mode: state.mode,
};
@@ -145,7 +144,7 @@ export const workflowSlice = createSlice({
});
});
builder.addCase(nodeEditorReset, () => deepClone(initialWorkflowState));
builder.addCase(nodeEditorReset, () => cloneDeep(initialWorkflowState));
builder.addCase(nodesChanged, (state, action) => {
// Not all changes to nodes should result in the workflow being marked touched

View File

@@ -1,9 +1,8 @@
import { deepClone } from 'common/util/deepClone';
import { satisfies } from 'compare-versions';
import { NodeUpdateError } from 'features/nodes/types/error';
import type { InvocationNode, InvocationTemplate } from 'features/nodes/types/invocation';
import { zParsedSemver } from 'features/nodes/types/semver';
import { defaultsDeep, keys, pick } from 'lodash-es';
import { cloneDeep, defaultsDeep, keys, pick } from 'lodash-es';
import { buildInvocationNode } from './buildInvocationNode';
@@ -51,7 +50,7 @@ export const updateNode = (node: InvocationNode, template: InvocationTemplate):
// The updateability of a node, via semver comparison, relies on the this kind of recursive merge
// being valid. We rely on the template's major version to be majorly incremented if this kind of
// merge would result in an invalid node.
const clone = deepClone(node);
const clone = cloneDeep(node);
clone.data.version = template.version;
defaultsDeep(clone, defaults); // mutates!

View File

@@ -1,12 +1,11 @@
import { logger } from 'app/logging/logger';
import { deepClone } from 'common/util/deepClone';
import { parseify } from 'common/util/serialize';
import type { NodesState, WorkflowsState } from 'features/nodes/store/types';
import { isInvocationNode, isNotesNode } from 'features/nodes/types/invocation';
import type { WorkflowV3 } from 'features/nodes/types/workflow';
import { zWorkflowV3 } from 'features/nodes/types/workflow';
import i18n from 'i18n';
import { pick } from 'lodash-es';
import { cloneDeep, pick } from 'lodash-es';
import { fromZodError } from 'zod-validation-error';
export type BuildWorkflowArg = {
@@ -31,7 +30,7 @@ const workflowKeys = [
type BuildWorkflowFunction = (arg: BuildWorkflowArg) => WorkflowV3;
export const buildWorkflowFast: BuildWorkflowFunction = ({ nodes, edges, workflow }: BuildWorkflowArg): WorkflowV3 => {
const clonedWorkflow = pick(deepClone(workflow), workflowKeys);
const clonedWorkflow = pick(cloneDeep(workflow), workflowKeys);
const newWorkflow: WorkflowV3 = {
...clonedWorkflow,
@@ -44,14 +43,14 @@ export const buildWorkflowFast: BuildWorkflowFunction = ({ nodes, edges, workflo
newWorkflow.nodes.push({
id: node.id,
type: node.type,
data: deepClone(node.data),
data: cloneDeep(node.data),
position: { ...node.position },
});
} else if (isNotesNode(node) && node.type) {
newWorkflow.nodes.push({
id: node.id,
type: node.type,
data: deepClone(node.data),
data: cloneDeep(node.data),
position: { ...node.position },
});
}

View File

@@ -1,5 +1,4 @@
import { $store } from 'app/store/nanostores/store';
import { deepClone } from 'common/util/deepClone';
import { WorkflowMigrationError, WorkflowVersionError } from 'features/nodes/types/error';
import type { FieldType } from 'features/nodes/types/field';
import type { InvocationNodeData } from 'features/nodes/types/invocation';
@@ -12,7 +11,7 @@ import { zWorkflowV2 } from 'features/nodes/types/v2/workflow';
import type { WorkflowV3 } from 'features/nodes/types/workflow';
import { zWorkflowV3 } from 'features/nodes/types/workflow';
import { t } from 'i18next';
import { forEach } from 'lodash-es';
import { cloneDeep, forEach } from 'lodash-es';
import { z } from 'zod';
/**
@@ -90,7 +89,7 @@ export const parseAndMigrateWorkflow = (data: unknown): WorkflowV3 => {
throw new WorkflowVersionError(t('nodes.unableToGetWorkflowVersion'));
}
let workflow = deepClone(data) as WorkflowV1 | WorkflowV2 | WorkflowV3;
let workflow = cloneDeep(data) as WorkflowV1 | WorkflowV2 | WorkflowV3;
if (workflow.meta.version === '1.0.0') {
const v1 = zWorkflowV1.parse(workflow);

View File

@@ -280,7 +280,6 @@ const migrateGenerationState = (state: any): GenerationState => {
// The signature of the model has changed, so we need to reset it
state._version = 2;
state.model = null;
state.canvasCoherenceMode = initialGenerationState.canvasCoherenceMode;
}
return state;
};

View File

@@ -192,7 +192,7 @@ export const queueApi = api.injectEndpoints({
{ batch_id: string }
>({
query: ({ batch_id }) => ({
url: buildQueueUrl(`b/${batch_id}/status`),
url: buildQueueUrl(`/b/${batch_id}/status`),
method: 'GET',
}),
providesTags: (result) => {

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
__version__ = "4.0.1"
__version__ = "4.0.0rc6"

View File

@@ -98,32 +98,6 @@ def test_migrate_v3_config_from_file(tmp_path: Path, patch_rootdir: None):
assert not hasattr(config, "esrgan")
@pytest.mark.parametrize(
"legacy_conf_dir,expected_value,expected_is_set",
[
# not set, expected value is the default value
("configs/stable-diffusion", Path("configs"), False),
# not set, expected value is the default value
("configs\\stable-diffusion", Path("configs"), False),
# set, best-effort resolution of the path
("partial_custom_path/stable-diffusion", Path("partial_custom_path"), True),
# set, exact path
("full/custom/path", Path("full/custom/path"), True),
],
)
def test_migrate_v3_legacy_conf_dir_defaults(
tmp_path: Path, patch_rootdir: None, legacy_conf_dir: str, expected_value: Path, expected_is_set: bool
):
"""Test reading configuration from a file."""
config_content = f"InvokeAI:\n Paths:\n legacy_conf_dir: {legacy_conf_dir}"
temp_config_file = tmp_path / "temp_invokeai.yaml"
temp_config_file.write_text(config_content)
config = load_and_migrate_config(temp_config_file)
assert config.legacy_conf_dir == expected_value
assert ("legacy_conf_dir" in config.model_fields_set) is expected_is_set
def test_migrate_v3_backup(tmp_path: Path, patch_rootdir: None):
"""Test the backup of the config file."""
temp_config_file = tmp_path / "temp_invokeai.yaml"

View File

@@ -250,32 +250,6 @@ def test_migrator_runs_all_migrations_file(logger: Logger) -> None:
db.conn.close()
def test_migrator_backs_up_db(logger: Logger) -> None:
with TemporaryDirectory() as tempdir:
original_db_path = Path(tempdir) / "invokeai.db"
db = SqliteDatabase(db_path=original_db_path, logger=logger, verbose=False)
# Write some data to the db to test for successful backup
temp_cursor = db.conn.cursor()
temp_cursor.execute("CREATE TABLE test (id INTEGER PRIMARY KEY);")
db.conn.commit()
# Set up the migrator
migrator = SqliteMigrator(db=db)
migrations = [Migration(from_version=i, to_version=i + 1, callback=create_migrate(i)) for i in range(0, 3)]
for migration in migrations:
migrator.register_migration(migration)
migrator.run_migrations()
# Must manually close else we get an error on Windows
db.conn.close()
assert original_db_path.exists()
# We should have a backup file when we migrated a file db
assert migrator._backup_path
# Check that the test table exists as a proxy for successful backup
with closing(sqlite3.connect(migrator._backup_path)) as backup_db_conn:
backup_db_cursor = backup_db_conn.cursor()
backup_db_cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='test';")
assert backup_db_cursor.fetchone() is not None
def test_migrator_makes_no_changes_on_failed_migration(
migrator: SqliteMigrator, migration_no_op: Migration, failing_migrate_callback: MigrateCallback
) -> None: