mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 01:04:32 +02:00
+10
-2
@@ -1,5 +1,13 @@
|
||||
# Change Log for SD.Next
|
||||
|
||||
## Update for 2023-09-18
|
||||
|
||||
Downgrade of `diffusers` to 0.20.2 due to critical issue with model offloading
|
||||
This means that new model **Wuerstchen** is not supported until diffusers issue is resolved
|
||||
|
||||
- Added **change log** to UI, see *System -> Changelog*
|
||||
- **Extra networks**: faster search, ability to show/hide/sort networks
|
||||
|
||||
## Update for 2023-09-13
|
||||
|
||||
Started as a mostly a service release with quite a few fixes, but then...
|
||||
@@ -22,11 +30,11 @@ Major changes how **hires** works as well as support for a very interesting new
|
||||
- diffusers:
|
||||
- allow loading of sd/sdxl models from safetensors without online connectivity
|
||||
- support for new model: [wuerstchen](https://huggingface.co/warp-ai/wuerstchen)
|
||||
its a high-resolution model (1024px+) that nearly doubls performance of sd-xl with much lower resource requirements
|
||||
its a high-resolution model (1024px+) thats ~40% faster than sd-xl with a bit lower resource requirements
|
||||
go to *models -> huggingface -> search "warp-ai/wuerstchen" -> download*
|
||||
its nearly 12gb in size, so be patient :)
|
||||
- minor re-layout of the main ui
|
||||
- update **ui hints**
|
||||
- updated **ui hints**
|
||||
- updated **models -> civitai**
|
||||
- search and download loras
|
||||
- find previews for already downloaded models or loras
|
||||
|
||||
@@ -2,8 +2,6 @@ import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from basicsr.utils.download_util import load_file_from_url
|
||||
|
||||
from modules.upscaler import Upscaler, UpscalerData
|
||||
from ldsr_model_arch import LDSR
|
||||
from modules import shared, script_callbacks
|
||||
@@ -42,6 +40,7 @@ class UpscalerLDSR(Upscaler):
|
||||
print("Renaming model from model.pth to model.ckpt")
|
||||
os.rename(old_model_path, new_model_path)
|
||||
|
||||
from modules.modelloader import load_file_from_url
|
||||
if local_safetensors_path is not None and os.path.exists(local_safetensors_path):
|
||||
model = local_safetensors_path
|
||||
else:
|
||||
|
||||
@@ -145,11 +145,11 @@ def assign_lora_names_to_compvis_modules(sd_model):
|
||||
|
||||
sd_model.lora_layer_mapping = lora_layer_mapping
|
||||
|
||||
def load_diffuser_lora(name, lora_on_disk, multiplier):
|
||||
def load_diffuser_lora(name, lora_on_disk, multiplier, num_loras):
|
||||
lora = LoraModule(name, lora_on_disk)
|
||||
lora.mtime = os.path.getmtime(lora_on_disk.filename)
|
||||
from modules.lora_diffusers import load_diffusers_lora
|
||||
load_diffusers_lora(name, lora_on_disk, multiplier)
|
||||
load_diffusers_lora(name, lora_on_disk, multiplier, num_loras)
|
||||
return lora
|
||||
|
||||
|
||||
@@ -239,24 +239,36 @@ def load_loras(names, multipliers=None):
|
||||
|
||||
failed_to_load_loras = []
|
||||
|
||||
recompile_model = False
|
||||
if shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx":
|
||||
if len(names) == len(shared.compiled_model_state.lora_model):
|
||||
for i, name in enumerate(names):
|
||||
if shared.compiled_model_state.lora_model[i] != f"{name}:{multipliers[i]}":
|
||||
recompile_model = True
|
||||
break
|
||||
else:
|
||||
recompile_model = True
|
||||
shared.compiled_model_state.lora_model = []
|
||||
if recompile_model:
|
||||
sd_models.unload_model_weights(op='model')
|
||||
shared.opts.cuda_compile = False
|
||||
sd_models.reload_model_weights(op='model')
|
||||
shared.opts.cuda_compile = True
|
||||
|
||||
for i, name in enumerate(names):
|
||||
lora = already_loaded.get(name, None) if shared.backend == shared.Backend.ORIGINAL else None
|
||||
|
||||
lora_on_disk = loras_on_disk[i]
|
||||
|
||||
if lora_on_disk is not None:
|
||||
if lora is None or os.path.getmtime(lora_on_disk.filename) > lora.mtime:
|
||||
try:
|
||||
if shared.backend == shared.Backend.DIFFUSERS:
|
||||
lora = load_diffuser_lora(name, lora_on_disk, multipliers[i] if multipliers else 1.0)
|
||||
lora = load_diffuser_lora(name, lora_on_disk, multipliers[i] if multipliers else 1.0, len(names))
|
||||
else:
|
||||
lora = load_lora(name, lora_on_disk)
|
||||
except Exception as e:
|
||||
errors.display(e, f"loading Lora {lora_on_disk.filename}")
|
||||
continue
|
||||
|
||||
lora.mentioned_name = name
|
||||
|
||||
lora_on_disk.read_hash()
|
||||
|
||||
if lora is None:
|
||||
@@ -270,6 +282,10 @@ def load_loras(names, multipliers=None):
|
||||
if len(failed_to_load_loras) > 0:
|
||||
sd_hijack.model_hijack.comments.append("Failed to find Loras: " + ", ".join(failed_to_load_loras))
|
||||
|
||||
if recompile_model:
|
||||
shared.log.info("Lora: Recompiling model")
|
||||
sd_models.compile_diffusers(shared.sd_model)
|
||||
|
||||
|
||||
def lora_calc_updown(lora, module, target):
|
||||
with torch.no_grad():
|
||||
|
||||
@@ -7,8 +7,6 @@ import numpy as np
|
||||
import torch
|
||||
from tqdm import tqdm
|
||||
|
||||
from basicsr.utils.download_util import load_file_from_url
|
||||
|
||||
import modules.upscaler
|
||||
from modules import devices, modelloader, script_callbacks
|
||||
from scunet_model_arch import SCUNet as net
|
||||
@@ -121,6 +119,7 @@ class UpscalerScuNET(modules.upscaler.Upscaler):
|
||||
def load_model(self, path: str):
|
||||
device = devices.get_device_for('scunet')
|
||||
if "http" in path:
|
||||
from modules.modelloader import load_file_from_url
|
||||
filename = load_file_from_url(url=self.model_url, model_dir=self.model_download_path, file_name="%s.pth" % self.name, progress=True)
|
||||
else:
|
||||
filename = path
|
||||
|
||||
@@ -2,8 +2,7 @@ import os
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image
|
||||
from basicsr.utils.download_util import load_file_from_url
|
||||
from tqdm.rich import tqdm
|
||||
from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn
|
||||
from swinir_model_arch import SwinIR as net
|
||||
from swinir_model_arch_v2 import Swin2SR as net2
|
||||
from modules import modelloader, devices, script_callbacks, shared
|
||||
@@ -45,11 +44,13 @@ class UpscalerSwinIR(Upscaler):
|
||||
|
||||
def load_model(self, path, scale=4):
|
||||
if "http" in path:
|
||||
dl_name = "%s%s" % (self.model_name.replace(" ", "_"), ".pth")
|
||||
from modules.modelloader import load_file_from_url
|
||||
dl_name = "%s%s" % (self.model_name.replace(" ", "_"), ".pth") # pylint: disable=consider-using-f-string
|
||||
filename = load_file_from_url(url=path, model_dir=self.model_download_path, file_name=dl_name, progress=True)
|
||||
else:
|
||||
filename = path
|
||||
if filename is None or not os.path.exists(filename):
|
||||
shared.log.error(f"Model failed loading: type=SwinIR model={filename}")
|
||||
return None
|
||||
model_v2 = net2(
|
||||
upscale=scale,
|
||||
@@ -78,6 +79,8 @@ class UpscalerSwinIR(Upscaler):
|
||||
resi_connection="3conv",
|
||||
)
|
||||
pretrained_model = torch.load(filename)
|
||||
shared.log.info(f"Model loaded: type=SwinIR model={filename}")
|
||||
|
||||
for model in [model_v1, model_v2]:
|
||||
for param in ["params_ema", "params", None]:
|
||||
try:
|
||||
@@ -140,7 +143,8 @@ def inference(img, model, tile, tile_overlap, window_size, scale):
|
||||
E = torch.zeros(b, c, h * sf, w * sf, dtype=devices.dtype, device=device_swinir).type_as(img)
|
||||
W = torch.zeros_like(E, dtype=devices.dtype, device=device_swinir)
|
||||
|
||||
with tqdm(total=len(h_idx_list) * len(w_idx_list), desc="Upscaling SwinIR") as pbar:
|
||||
with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn(), console=shared.console) as progress:
|
||||
task = progress.add_task(description="Upscaling Initializing", total=len(h_idx_list) * len(w_idx_list))
|
||||
for h_idx in h_idx_list:
|
||||
if state.interrupted or state.skipped:
|
||||
break
|
||||
@@ -159,7 +163,7 @@ def inference(img, model, tile, tile_overlap, window_size, scale):
|
||||
W[
|
||||
..., h_idx * sf: (h_idx + tile) * sf, w_idx * sf: (w_idx + tile) * sf
|
||||
].add_(out_patch_mask)
|
||||
pbar.update(1)
|
||||
progress.update(task, advance=1, description="Upscaling")
|
||||
output = E.div_(W)
|
||||
|
||||
return output
|
||||
|
||||
+3
-3
@@ -63,7 +63,7 @@
|
||||
{"id":"","label":"UI position","localized":"","hint":"Location of extra networks"},
|
||||
{"id":"","label":"cover","localized":"","hint":"cover full area"},
|
||||
{"id":"","label":"inline","localized":"","hint":"inline with all additional elelemtns (scrollable)"},
|
||||
{"id":"","label":"sidebar","localized":"","hint":"sidebar on the right side of the screen"},
|
||||
{"id":"","label":"sidebar","localized":"","hint":"sidebar on the right side of the screen"},
|
||||
{"id":"","label":"UI height (%)","localized":"","hint":""},
|
||||
{"id":"","label":"UI sidebar width (%)","localized":"","hint":""},
|
||||
{"id":"","label":"UI card preview lazy loading","localized":"","hint":""},
|
||||
@@ -431,7 +431,7 @@
|
||||
{"id":"","label":"Create text file next to every image with generation parameters","localized":"","hint":""},
|
||||
{"id":"","label":"Create JSON log file for each saved image","localized":"","hint":"Save image information to a JSON file"},
|
||||
{"id":"","label":"Save copy of image before doing face restoration","localized":"","hint":""},
|
||||
{"id":"","label":"Save copy of image before applying highres fix","localized":"","hint":""},
|
||||
{"id":"","label":"Save copy of image before applying hires","localized":"","hint":""},
|
||||
{"id":"","label":"Save copy of image before applying color correction","localized":"","hint":""},
|
||||
{"id":"","label":"Save copy of the inpainting greyscale mask","localized":"","hint":""},
|
||||
{"id":"","label":"Save copy of inpainting masked composite","localized":"","hint":""},
|
||||
@@ -585,7 +585,7 @@
|
||||
{"id":"","label":"Enable attention slicing","localized":"","hint":"Performs attention computation in steps instead of all at once. Slower inference times, but greatly reduced memory usage"},
|
||||
{"id":"","label":"Diffusers model loading variant","localized":"","hint":""},
|
||||
{"id":"","label":"Diffusers VAE loading variant","localized":"","hint":""},
|
||||
{"id":"","label":"Diffusers LoRA loading variant","localized":"","hint":"'sequential apply' loads and applies each LoRA in order of appearance, 'merge and apply' loads all LoRAs and merges them in-memory before applying to model, 'diffusers default' uses single LoRA loading method"},
|
||||
{"id":"","label":"Diffusers LoRA loading variant","localized":"","hint":"'sequential apply' loads and applies each LoRA in order of appearance, 'merge and apply' loads all LoRAs and merges them in-memory before applying to model, 'diffusers' uses diffusers default LoRA loading method"},
|
||||
{"id":"","label":"Torch inference mode","localized":"","hint":"Use torch inference mode"},
|
||||
{"id":"","label":"inference-mode","localized":"","hint":"Use torch.inference_mode"},
|
||||
{"id":"","label":"no-grad","localized":"","hint":"Use torch.no_grad"},
|
||||
|
||||
+2
-2
@@ -429,8 +429,8 @@ def check_torch():
|
||||
torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.0a0 intel_extension_for_pytorch==2.0.110+gitba7f6c1 -f https://developer.intel.com/ipex-whl-stable-xpu')
|
||||
elif allow_openvino and args.use_openvino:
|
||||
#Remove this after 2.1.0 releases
|
||||
log.info('Using OpenVINO with Torch Nightly CPU')
|
||||
torch_command = os.environ.get('TORCH_COMMAND', '--pre torch==2.1.0.dev20230713+cpu torchvision==0.16.0.dev20230713+cpu -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html')
|
||||
log.info('Using OpenVINO')
|
||||
torch_command = os.environ.get('TORCH_COMMAND', '--pre torch==2.1.0.dev20230726+cpu torchvision==0.16.0.dev20230726+cpu -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html')
|
||||
else:
|
||||
machine = platform.machine()
|
||||
if sys.platform == 'darwin':
|
||||
|
||||
@@ -136,6 +136,31 @@ function saveCardDescription(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
async function filterExtraNetworksForTab(tabname, searchTerm) {
|
||||
let found = 0;
|
||||
let items = 0;
|
||||
const t0 = performance.now();
|
||||
const cards = Array.from(gradioApp().querySelectorAll(`#${tabname}_extra_tabs div.card`));
|
||||
cards.forEach((elem) => {
|
||||
items += 1;
|
||||
if (searchTerm === '') {
|
||||
elem.style.display = '';
|
||||
} else {
|
||||
let text = `${elem.querySelector('.name').textContent.toLowerCase()} ${elem.querySelector('.search_term').textContent}`;
|
||||
text = text.toLowerCase().replace('models--', 'Diffusers').replace('\\', '/');
|
||||
if (text.indexOf(searchTerm) === -1) {
|
||||
elem.style.display = 'none';
|
||||
} else {
|
||||
elem.style.display = '';
|
||||
found += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
const t1 = performance.now();
|
||||
if (found > 0) log(`filterExtraNetworks: text=${searchTerm} items=${items} match=${found} time=${Math.round(1000 * (t1 - t0)) / 1000000}`);
|
||||
else log(`filterExtraNetworks: text=all items=${items} time=${Math.round(1000 * (t1 - t0)) / 1000000}`);
|
||||
}
|
||||
|
||||
function setupExtraNetworksForTab(tabname) {
|
||||
gradioApp().querySelector(`#${tabname}_extra_tabs`).classList.add('extra-networks');
|
||||
const tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`);
|
||||
@@ -157,14 +182,9 @@ function setupExtraNetworksForTab(tabname) {
|
||||
search.addEventListener('input', (evt) => {
|
||||
if (searchTimer) clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => {
|
||||
const searchTerm = search.value.toLowerCase();
|
||||
gradioApp().querySelectorAll(`#${tabname}_extra_tabs div.card`).forEach((elem) => {
|
||||
let text = `${elem.querySelector('.name').textContent.toLowerCase()} ${elem.querySelector('.search_term').textContent.toLowerCase()}`;
|
||||
text = text.replace('models--', 'Diffusers');
|
||||
elem.style.display = text.indexOf(searchTerm) === -1 ? 'none' : '';
|
||||
});
|
||||
filterExtraNetworksForTab(tabname, search.value.toLowerCase());
|
||||
searchTimer = null;
|
||||
}, 100);
|
||||
}, 150);
|
||||
});
|
||||
|
||||
let hoverTimer = null;
|
||||
@@ -186,7 +206,10 @@ function setupExtraNetworksForTab(tabname) {
|
||||
|
||||
const intersectionObserver = new IntersectionObserver((entries) => {
|
||||
if (!en) return;
|
||||
for (const el of Array.from(gradioApp().querySelectorAll('.extra-networks-page'))) el.style.height = `${window.opts.extra_networks_height}vh`;
|
||||
for (const el of Array.from(gradioApp().querySelectorAll('.extra-networks-page'))) {
|
||||
el.style.height = `${window.opts.extra_networks_height}vh`;
|
||||
el.parentElement.style.width = '-webkit-fill-available';
|
||||
}
|
||||
if (entries[0].intersectionRatio > 0) {
|
||||
if (window.opts.extra_networks_card_cover === 'cover') {
|
||||
en.style.transition = '';
|
||||
@@ -269,6 +292,7 @@ function tryToRemoveExtraNetworkFromPrompt(textarea, text) {
|
||||
}
|
||||
|
||||
function refreshExtraNetworks(tabname) {
|
||||
console.log('refreshExtraNetworks', tabname, gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.value);
|
||||
gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.dispatchEvent(new Event('input'));
|
||||
}
|
||||
|
||||
|
||||
@@ -314,4 +314,4 @@ svg.feather.feather-image, .feather .feather-image { display: none }
|
||||
--size-9: 64px;
|
||||
--size-14: 64px;
|
||||
}
|
||||
/*Midnight-Barbie, By Nyxxia*/
|
||||
/*Midnight-Barbie, By Nyxxia*/
|
||||
|
||||
+11
-3
@@ -249,10 +249,10 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt
|
||||
.extra-network-cards .card:hover .overlay .tags { display: block; }
|
||||
.extra-network-cards .card:hover .overlay .description { display: block; }
|
||||
.extra-network-cards .card:hover .preview { box-shadow: none; filter: grayscale(100%); }
|
||||
#txt2img_description, #img2img_description { max-height: 63px; overflow-y: auto !important; }
|
||||
#txt2img_description > label > textarea, #img2img_description > label > textarea { font-size: 0.9em }
|
||||
#txt2img_description, #img2img_description { max-height: 63px; overflow-y: auto !important; }
|
||||
#txt2img_description > label > textarea, #img2img_description > label > textarea { font-size: 0.9em }
|
||||
|
||||
/* controlnet */
|
||||
/* controlnet
|
||||
.controlnet_control_type .controlnet_control_type_filter_group .wrap:last-of-type { display: grid; grid-auto-flow: row; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
fieldset.controlnet_resize_mode_radio .wrap:last-of-type, fieldset.controlnet_control_mode_radio .wrap:last-of-type { flex-direction: column; }
|
||||
div.controlnet_preprocessor_model { display: grid; grid-auto-flow: row; grid-template-columns: 1fr max-content; }
|
||||
@@ -263,6 +263,7 @@ div.controlnet_image_controls { display: grid; grid-template-columns: repeat(4,
|
||||
div.controlnet_image_controls .controlnet_invert_warning { grid-column: 1 / -1; }
|
||||
div.controlnet_image_controls button { justify-self: center; }
|
||||
div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; grid-auto-flow: row; }
|
||||
*/
|
||||
|
||||
/* specific elements */
|
||||
#modelmerger_interp_description { margin-top: 1em; margin-bottom: 1em; }
|
||||
@@ -281,6 +282,12 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri
|
||||
.log-monitor { display: none; justify-content: unset !important; overflow: hidden; padding: 0; margin-top: auto; font-family: monospace; font-size: 0.85em; }
|
||||
.log-monitor td, .log-monitor th { padding-left: 1em; }
|
||||
|
||||
/* changelog */
|
||||
.md h2 { background-color: var(--background-fill-primary); padding: 0.5em; }
|
||||
.md ul { list-style-type: square !important; text-indent: 1em; margin-left: 4em; }
|
||||
.md li { list-style-position: outside !important; text-indent: 0; }
|
||||
.md p { margin-left: 2em; }
|
||||
|
||||
/* custom component */
|
||||
.folder-selector textarea { height: 2em !important; padding: 6px !important; }
|
||||
|
||||
@@ -295,6 +302,7 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri
|
||||
.loader::before { border-top-color: var(--primary-900); animation: 3s spin linear infinite; }
|
||||
.loader::after { border-top-color: var(--primary-300); animation: spin 1.5s linear infinite; }
|
||||
|
||||
|
||||
@keyframes move {
|
||||
from { background-position-x: 0, -40px; }
|
||||
to { background-position-x: 0, 40px; }
|
||||
|
||||
+33
-3
@@ -2,7 +2,7 @@ import io
|
||||
import time
|
||||
import base64
|
||||
from io import BytesIO
|
||||
from typing import List, Dict, Any
|
||||
from typing import List, Dict, Any, Optional
|
||||
from threading import Lock
|
||||
from secrets import compare_digest
|
||||
from fastapi import FastAPI, APIRouter, Depends
|
||||
@@ -148,6 +148,7 @@ class Api:
|
||||
self.add_api_route("/sdapi/v1/scripts", self.get_scripts_list, methods=["GET"], response_model=models.ScriptsList)
|
||||
self.add_api_route("/sdapi/v1/script-info", self.get_script_info, methods=["GET"], response_model=List[models.ScriptInfo])
|
||||
self.add_api_route("/sdapi/v1/log", self.get_log_buffer, methods=["GET"], response_model=List) # bypass auth
|
||||
self.add_api_route("/sdapi/v1/extra-networks", self.get_extra_networks, methods=["GET"], response_model=List[models.ExtraNetworkItem])
|
||||
self.default_script_arg_txt2img = []
|
||||
self.default_script_arg_img2img = []
|
||||
|
||||
@@ -180,10 +181,12 @@ class Api:
|
||||
i2ilist = [script.name for script in scripts.scripts_img2img.scripts if script.name is not None]
|
||||
return models.ScriptsList(txt2img = t2ilist, img2img = i2ilist)
|
||||
|
||||
def get_script_info(self):
|
||||
def get_script_info(self, script_name: Optional[str] = None):
|
||||
res = []
|
||||
for script_list in [scripts.scripts_txt2img.scripts, scripts.scripts_img2img.scripts]:
|
||||
res += [script.api_info for script in script_list if script.api_info is not None]
|
||||
for script in script_list:
|
||||
if script.api_info is not None and (script_name is None or script_name == script.api_info.name):
|
||||
res.append(script.api_info)
|
||||
return res
|
||||
|
||||
def get_script(self, script_name, script_runner):
|
||||
@@ -500,6 +503,33 @@ class Api:
|
||||
"skipped": convert_embeddings(db.skipped_embeddings),
|
||||
}
|
||||
|
||||
def get_extra_networks(self, page: Optional[str] = None, name: Optional[str] = None, filename: Optional[str] = None, title: Optional[str] = None, fullname: Optional[str] = None, hash: Optional[str] = None): # pylint: disable=redefined-builtin
|
||||
res = []
|
||||
for pg in shared.extra_networks:
|
||||
if page is not None and pg.name != page.lower():
|
||||
continue
|
||||
for item in pg.items:
|
||||
if name is not None and item.get('name', '') != name:
|
||||
continue
|
||||
if title is not None and item.get('title', '') != title:
|
||||
continue
|
||||
if filename is not None and item.get('filename', '') != filename:
|
||||
continue
|
||||
if fullname is not None and item.get('fullname', '') != fullname:
|
||||
continue
|
||||
if hash is not None and (item.get('shorthash', None) or item.get('hash')) != hash:
|
||||
continue
|
||||
res.append({
|
||||
'name': item.get('name', ''),
|
||||
'type': pg.name,
|
||||
'title': item.get('title', None),
|
||||
'fullname': item.get('fullname', None),
|
||||
'filename': item.get('filename', None),
|
||||
'hash': item.get('shorthash', None) or item.get('hash'),
|
||||
"preview": item.get('preview', None),
|
||||
})
|
||||
return res
|
||||
|
||||
def refresh_checkpoints(self):
|
||||
return shared.refresh_checkpoints()
|
||||
|
||||
|
||||
+15
-1
@@ -272,6 +272,20 @@ class StyleItem(BaseModel):
|
||||
filename: Optional[str] = Field(title="Filename")
|
||||
preview: Optional[str] = Field(title="Preview")
|
||||
|
||||
class ExtraNetworkItem(BaseModel):
|
||||
name: str = Field(title="Name")
|
||||
type: str = Field(title="Type")
|
||||
title: Optional[str] = Field(title="Title")
|
||||
fullname: Optional[str] = Field(title="Fullname")
|
||||
filename: Optional[str] = Field(title="Filename")
|
||||
hash: Optional[str] = Field(title="Hash")
|
||||
preview: Optional[str] = Field(title="Preview image URL")
|
||||
# description: Optional[str] = Field(title="Description")
|
||||
# info: Optional[str] = Field(title="Information")
|
||||
# metadata: Optional[Any] = Field(title="Metadata")
|
||||
# local: Optional[str] = Field(title="Local")
|
||||
|
||||
|
||||
class ArtistItem(BaseModel):
|
||||
name: str = Field(title="Name")
|
||||
score: float = Field(title="Score")
|
||||
@@ -303,7 +317,7 @@ class ScriptArg(BaseModel):
|
||||
minimum: Optional[Any] = Field(default=None, title="Minimum", description="Minimum allowed value for the argumentin UI")
|
||||
maximum: Optional[Any] = Field(default=None, title="Minimum", description="Maximum allowed value for the argumentin UI")
|
||||
step: Optional[Any] = Field(default=None, title="Minimum", description="Step for changing value of the argumentin UI")
|
||||
choices: Optional[List[str]] = Field(default=None, title="Choices", description="Possible values for the argument")
|
||||
choices: Optional[Any] = Field(default=None, title="Choices", description="Possible values for the argument")
|
||||
|
||||
|
||||
class ScriptInfo(BaseModel):
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ def torch_gc(force=False):
|
||||
if oom > previous_oom:
|
||||
previous_oom = oom
|
||||
shared.log.warning(f'GPU out-of-memory error: {mem}')
|
||||
if used > 95:
|
||||
if used > 90:
|
||||
shared.log.info(f'GPU high memory utilization: {used}% {mem}')
|
||||
force = True
|
||||
if not force:
|
||||
|
||||
@@ -9,7 +9,7 @@ default_memory_provider = "None"
|
||||
if platform.system() == "Windows":
|
||||
memory_providers.append("Performance Counter")
|
||||
default_memory_provider = "Performance Counter"
|
||||
do_nothing = lambda: None
|
||||
do_nothing = lambda: None # pylint: disable=unnecessary-lambda-assignment
|
||||
|
||||
def _set_memory_provider():
|
||||
from modules.shared import opts, cmd_opts, log
|
||||
@@ -63,7 +63,7 @@ def directml_init():
|
||||
return True, None
|
||||
|
||||
def directml_do_hijack():
|
||||
import modules.dml.hijack
|
||||
import modules.dml.hijack # pylint: disable=unused-import
|
||||
from modules.devices import device
|
||||
|
||||
if not torch.dml.has_float64_support(device):
|
||||
@@ -79,9 +79,9 @@ class OverrideItem(NamedTuple):
|
||||
message: Optional[str]
|
||||
|
||||
opts_override_table = {
|
||||
"diffusers_generator_device": OverrideItem("cpu", None, "DirectML does not support torch Generator API."),
|
||||
"diffusers_model_cpu_offload": OverrideItem(False, None, "Diffusers' model CPU offloading does not support DirectML devices."),
|
||||
"diffusers_seq_cpu_offload": OverrideItem(False, lambda opts: opts.diffusers_pipeline != "Stable Diffusion XL", "Diffusers' sequential CPU offloading is available only on StableDiffusionXLPipeline with DirectML devices."),
|
||||
"diffusers_generator_device": OverrideItem("cpu", None, "DirectML does not support torch Generator API"),
|
||||
"diffusers_model_cpu_offload": OverrideItem(False, None, "Diffusers model CPU offloading does not support DirectML devices"),
|
||||
"diffusers_seq_cpu_offload": OverrideItem(False, lambda opts: opts.diffusers_pipeline != "Stable Diffusion XL", "Diffusers sequential CPU offloading is available only on StableDiffusionXLPipeline with DirectML devices"),
|
||||
}
|
||||
|
||||
def directml_override_opts():
|
||||
@@ -96,11 +96,9 @@ def directml_override_opts():
|
||||
if getattr(shared.opts, key) != item.value and (item.condition is None or item.condition(shared.opts)):
|
||||
count += 1
|
||||
setattr(shared.opts, key, item.value)
|
||||
if item.message is not None:
|
||||
shared.log.warning(item.message)
|
||||
shared.log.warning(f'{key} is automatically overriden to {item.value}.')
|
||||
shared.log.warning(f'Overriding: {key}={item.value} {item.message if item.message is not None else ""}')
|
||||
|
||||
if count > 0:
|
||||
shared.log.info(f'{count} options are automatically overriden. If you want to keep them from overriding, run with --experimental argument.')
|
||||
shared.log.info(f'Options override: count={count}. If you want to keep them from overriding, run with --experimental argument.')
|
||||
|
||||
_set_memory_provider()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import math
|
||||
import torch
|
||||
from realesrgan import RealESRGANer
|
||||
from modules.realesrgan_model_arch import RealESRGANer
|
||||
|
||||
|
||||
# DML Solution: Some of contents of output tensor turn to 0 after Extended Slices. Move it to cpu.
|
||||
|
||||
+19
-13
@@ -3,12 +3,12 @@ import os
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image
|
||||
from basicsr.utils.download_util import load_file_from_url
|
||||
from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn
|
||||
|
||||
import modules.esrgan_model_arch as arch
|
||||
from modules import modelloader, images, devices
|
||||
from modules.upscaler import Upscaler, UpscalerData
|
||||
from modules.shared import opts
|
||||
from modules.shared import opts, log, console
|
||||
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ class UpscalerESRGAN(Upscaler):
|
||||
|
||||
def load_model(self, path: str):
|
||||
if "http" in path:
|
||||
from modules.modelloader import load_file_from_url
|
||||
filename = load_file_from_url(
|
||||
url=self.model_url,
|
||||
model_dir=self.model_download_path,
|
||||
@@ -161,10 +162,11 @@ class UpscalerESRGAN(Upscaler):
|
||||
else:
|
||||
filename = path
|
||||
if not os.path.exists(filename) or filename is None:
|
||||
print(f"Unable to load {self.model_path} from {filename}")
|
||||
log.error(f"Model failed loading: type=ESRGAN model={filename}")
|
||||
return None
|
||||
|
||||
state_dict = torch.load(filename, map_location='cpu' if devices.device_esrgan.type == 'mps' else None)
|
||||
log.info(f"Model loaded: type=ESRGAN model={filename}")
|
||||
|
||||
if "params_ema" in state_dict:
|
||||
state_dict = state_dict["params_ema"]
|
||||
@@ -216,16 +218,20 @@ def esrgan_upscale(model, img):
|
||||
newtiles = []
|
||||
scale_factor = 1
|
||||
|
||||
for y, h, row in grid.tiles:
|
||||
newrow = []
|
||||
for tiledata in row:
|
||||
x, w, tile = tiledata
|
||||
|
||||
output = upscale_without_tiling(model, tile)
|
||||
scale_factor = output.width // tile.width
|
||||
|
||||
newrow.append([x * scale_factor, w * scale_factor, output])
|
||||
newtiles.append([y * scale_factor, h * scale_factor, newrow])
|
||||
with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn(), console=console) as progress:
|
||||
total = 0
|
||||
for y, h, row in grid.tiles:
|
||||
total += len(row)
|
||||
task = progress.add_task(description="Upscaling", total=total)
|
||||
for y, h, row in grid.tiles:
|
||||
newrow = []
|
||||
for tiledata in row:
|
||||
x, w, tile = tiledata
|
||||
output = upscale_without_tiling(model, tile)
|
||||
scale_factor = output.width // tile.width
|
||||
newrow.append([x * scale_factor, w * scale_factor, output])
|
||||
progress.update(task, advance=1, description="Upscaling")
|
||||
newtiles.append([y * scale_factor, h * scale_factor, newrow])
|
||||
|
||||
newgrid = images.Grid(newtiles, grid.tile_w * scale_factor, grid.tile_h * scale_factor, grid.image_w * scale_factor, grid.image_h * scale_factor, grid.overlap * scale_factor)
|
||||
output = images.combine_grid(newgrid)
|
||||
|
||||
@@ -29,12 +29,13 @@ def gfpgann():
|
||||
latest_file = max(models, key=os.path.getctime)
|
||||
model_file = latest_file
|
||||
else:
|
||||
print("Unable to load gfpgan model!")
|
||||
shared.log.error(f"Model failed loading: type=GFPGAN model={model_file}")
|
||||
return None
|
||||
if hasattr(facexlib.detection.retinaface, 'device'):
|
||||
facexlib.detection.retinaface.device = devices.device_gfpgan
|
||||
model = gfpgan_constructor(model_path=model_file, upscale=1, arch='clean', channel_multiplier=2, bg_upsampler=None, device=devices.device_gfpgan)
|
||||
loaded_gfpgan_model = model
|
||||
shared.log.info(f"Model loaded: type=GFPGAN model={model_file}")
|
||||
|
||||
return model
|
||||
|
||||
|
||||
@@ -221,10 +221,10 @@ class Hypernetwork:
|
||||
torch.save(optimizer_saved_dict, f"{filename}.optim")
|
||||
|
||||
def load(self, filename):
|
||||
self.filename = filename
|
||||
self.filename = filename if os.path.exists(filename) else os.path.join(shared.opts.hypernetwork_dir, filename)
|
||||
if self.name is None:
|
||||
self.name = os.path.splitext(os.path.basename(filename))[0]
|
||||
with progress.open(filename, 'rb', description=f'Loading hypernetwork: [cyan]{filename}', auto_refresh=True, console=shared.console) as f:
|
||||
self.name = os.path.splitext(os.path.basename(self.filename))[0]
|
||||
with progress.open(self.filename, 'rb', description=f'Loading hypernetwork: [cyan]{self.filename}', auto_refresh=True, console=shared.console) as f:
|
||||
state_dict = torch.load(f, map_location='cpu')
|
||||
self.layer_structure = state_dict.get('layer_structure', [1, 2, 1])
|
||||
self.optional_info = state_dict.get('optional_info', None)
|
||||
|
||||
+9
-8
@@ -273,7 +273,7 @@ re_nonletters = re.compile(r'[\s' + string.punctuation + ']+')
|
||||
re_pattern = re.compile(r"(.*?)(?:\[([^\[\]]+)\]|$)")
|
||||
re_pattern_arg = re.compile(r"(.*)<([^>]*)>$")
|
||||
max_filename_part_length = 128
|
||||
NOTHING_AND_SKIP_PREVIOUS_TEXT = object()
|
||||
NOTHING = object()
|
||||
|
||||
|
||||
def sanitize_filename_part(text, replace_spaces=True):
|
||||
@@ -291,13 +291,13 @@ def sanitize_filename_part(text, replace_spaces=True):
|
||||
|
||||
class FilenameGenerator:
|
||||
replacements = {
|
||||
'batch_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p is None or self.p.batch_size == 1 else self.p.batch_index + 1,
|
||||
'batch_number': lambda self: NOTHING if self.index <= 1 else self.index,
|
||||
'cfg': lambda self: self.p and self.p.cfg_scale,
|
||||
'clip_skip': lambda self: self.p and self.p.clip_skip,
|
||||
'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'),
|
||||
'datetime': lambda self, *args: self.datetime(*args), # accepts formats: [datetime], [datetime<Format>], [datetime<Format><Time Zone>]
|
||||
'denoising': lambda self: self.p.denoising_strength if self.p and self.p.denoising_strength else NOTHING_AND_SKIP_PREVIOUS_TEXT,
|
||||
'generation_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.n_iter == 1 and self.p.batch_size == 1 else self.p.iteration * self.p.batch_size + self.p.batch_index + 1,
|
||||
'denoising': lambda self: self.p.denoising_strength if self.p and self.p.denoising_strength else NOTHING,
|
||||
'generation_number': lambda self: NOTHING if self.p.n_iter == 1 and self.p.batch_size == 1 else self.p.iteration * self.p.batch_size + self.p.batch_index + 1,
|
||||
'hasprompt': lambda self, *args: self.hasprompt(*args), # accepts formats:[hasprompt<prompt1|default><prompt2>..]
|
||||
'height': lambda self: self.image.height,
|
||||
'image_hash': lambda self: self.image_hash(),
|
||||
@@ -320,11 +320,12 @@ class FilenameGenerator:
|
||||
}
|
||||
default_time_format = '%Y%m%d%H%M%S'
|
||||
|
||||
def __init__(self, p, seed, prompt, image):
|
||||
def __init__(self, p, seed, prompt, image, index = 0):
|
||||
self.p = p
|
||||
self.seed = seed
|
||||
self.prompt = prompt
|
||||
self.image = image
|
||||
self.index = index if self.p is None or self.p.batch_size == 1 else self.p.batch_index + 1
|
||||
|
||||
def hasprompt(self, *args):
|
||||
lower = self.prompt.lower()
|
||||
@@ -403,7 +404,7 @@ class FilenameGenerator:
|
||||
except Exception as e:
|
||||
replacement = None
|
||||
errors.display(e, 'filename pattern')
|
||||
if replacement == NOTHING_AND_SKIP_PREVIOUS_TEXT:
|
||||
if replacement == NOTHING:
|
||||
continue
|
||||
elif replacement is not None:
|
||||
res += text + str(replacement)
|
||||
@@ -498,7 +499,7 @@ save_thread = threading.Thread(target=atomically_save_image, daemon=True)
|
||||
save_thread.start()
|
||||
|
||||
|
||||
def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix="", save_to_dirs=None):
|
||||
def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix="", save_to_dirs=None, index=0):
|
||||
"""Save an image.
|
||||
Args:
|
||||
image (`PIL.Image`):
|
||||
@@ -536,7 +537,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', i
|
||||
return None, None
|
||||
if path is None or len(path) == 0: # set default path to avoid errors when functions are triggered manually or via api and param is not set
|
||||
path = shared.opts.outdir_save
|
||||
namegen = FilenameGenerator(p, seed, prompt, image)
|
||||
namegen = FilenameGenerator(p, seed, prompt, image, index)
|
||||
if save_to_dirs is None:
|
||||
save_to_dirs = (grid and shared.opts.grid_save_to_dirs) or (not grid and shared.opts.save_to_dirs and not no_prompt)
|
||||
if save_to_dirs:
|
||||
|
||||
@@ -64,8 +64,14 @@ def torch_bmm(input, mat2, *, out=None):
|
||||
original_scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention
|
||||
def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False):
|
||||
#ARC GPUs can't allocate more than 4GB to a single block, Slice it:
|
||||
shape_one, batch_size_attention, query_tokens, shape_four = query.shape
|
||||
block_multiply = 2.4 if query.dtype == torch.float32 else 1.2
|
||||
if len(query.shape) == 3:
|
||||
batch_size_attention, query_tokens, shape_four = query.shape
|
||||
shape_one = 1
|
||||
no_shape_one = True
|
||||
else:
|
||||
shape_one, batch_size_attention, query_tokens, shape_four = query.shape
|
||||
no_shape_one = False
|
||||
block_multiply = 3.6 if query.dtype == torch.float32 else 1.8
|
||||
block_size = (shape_one * batch_size_attention * query_tokens * shape_four) / 1024 * block_multiply #MB
|
||||
split_slice_size = batch_size_attention
|
||||
if block_size >= 4000:
|
||||
@@ -101,21 +107,39 @@ def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.
|
||||
for i2 in range(query_tokens // split_2_slice_size): # pylint: disable=invalid-name
|
||||
start_idx_2 = i2 * split_2_slice_size
|
||||
end_idx_2 = (i2 + 1) * split_2_slice_size
|
||||
hidden_states[:, start_idx:end_idx, start_idx_2:end_idx_2] = original_scaled_dot_product_attention(
|
||||
query[:, start_idx:end_idx, start_idx_2:end_idx_2],
|
||||
key[:, start_idx:end_idx, start_idx_2:end_idx_2],
|
||||
value[:, start_idx:end_idx, start_idx_2:end_idx_2],
|
||||
attn_mask=attn_mask[:, start_idx:end_idx, start_idx_2:end_idx_2] if attn_mask is not None else attn_mask,
|
||||
if no_shape_one:
|
||||
hidden_states[start_idx:end_idx, start_idx_2:end_idx_2] = original_scaled_dot_product_attention(
|
||||
query[start_idx:end_idx, start_idx_2:end_idx_2],
|
||||
key[start_idx:end_idx, start_idx_2:end_idx_2],
|
||||
value[start_idx:end_idx, start_idx_2:end_idx_2],
|
||||
attn_mask=attn_mask[start_idx:end_idx, start_idx_2:end_idx_2] if attn_mask is not None else attn_mask,
|
||||
dropout_p=dropout_p, is_causal=is_causal
|
||||
)
|
||||
else:
|
||||
hidden_states[:, start_idx:end_idx, start_idx_2:end_idx_2] = original_scaled_dot_product_attention(
|
||||
query[:, start_idx:end_idx, start_idx_2:end_idx_2],
|
||||
key[:, start_idx:end_idx, start_idx_2:end_idx_2],
|
||||
value[:, start_idx:end_idx, start_idx_2:end_idx_2],
|
||||
attn_mask=attn_mask[:, start_idx:end_idx, start_idx_2:end_idx_2] if attn_mask is not None else attn_mask,
|
||||
dropout_p=dropout_p, is_causal=is_causal
|
||||
)
|
||||
else:
|
||||
if no_shape_one:
|
||||
hidden_states[start_idx:end_idx] = original_scaled_dot_product_attention(
|
||||
query[start_idx:end_idx],
|
||||
key[start_idx:end_idx],
|
||||
value[start_idx:end_idx],
|
||||
attn_mask=attn_mask[start_idx:end_idx] if attn_mask is not None else attn_mask,
|
||||
dropout_p=dropout_p, is_causal=is_causal
|
||||
)
|
||||
else:
|
||||
hidden_states[:, start_idx:end_idx] = original_scaled_dot_product_attention(
|
||||
query[:, start_idx:end_idx],
|
||||
key[:, start_idx:end_idx],
|
||||
value[:, start_idx:end_idx],
|
||||
attn_mask=attn_mask[:, start_idx:end_idx] if attn_mask is not None else attn_mask,
|
||||
dropout_p=dropout_p, is_causal=is_causal
|
||||
)
|
||||
else:
|
||||
hidden_states[:, start_idx:end_idx] = original_scaled_dot_product_attention(
|
||||
query[:, start_idx:end_idx],
|
||||
key[:, start_idx:end_idx],
|
||||
value[:, start_idx:end_idx],
|
||||
attn_mask=attn_mask[:, start_idx:end_idx] if attn_mask is not None else attn_mask,
|
||||
dropout_p=dropout_p, is_causal=is_causal
|
||||
)
|
||||
else:
|
||||
return original_scaled_dot_product_attention(
|
||||
query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal
|
||||
|
||||
@@ -1,30 +1,53 @@
|
||||
import os
|
||||
import torch
|
||||
from openvino.frontend.pytorch.torchdynamo.execute import execute, partitioned_modules, compiled_cache
|
||||
from openvino.frontend import FrontEndManager
|
||||
from openvino.frontend.pytorch.fx_decoder import TorchFXPythonDecoder
|
||||
from openvino.frontend.pytorch.torchdynamo.partition import Partitioner
|
||||
from openvino.runtime import Core, Type, PartialShape
|
||||
from openvino.runtime import Core, Type, PartialShape, serialize
|
||||
from torch._dynamo.backends.common import fake_tensor_unsupported
|
||||
from torch._dynamo.backends.registry import register_backend
|
||||
from torch.fx.experimental.proxy_tensor import make_fx
|
||||
from torch._inductor.compile_fx import compile_fx
|
||||
from torch.utils._pytree import tree_flatten
|
||||
from types import MappingProxyType
|
||||
from hashlib import sha256
|
||||
import functools
|
||||
from modules import shared, devices
|
||||
|
||||
@register_backend
|
||||
@fake_tensor_unsupported
|
||||
def openvino_fx(subgraph, example_inputs):
|
||||
executor_parameters = None
|
||||
compiled_cache = {}
|
||||
max_openvino_partitions = 0
|
||||
partitioned_modules = {}
|
||||
|
||||
DEFAULT_OPENVINO_PYTHON_CONFIG = MappingProxyType(
|
||||
{
|
||||
"use_python_fusion_cache": True,
|
||||
"allow_single_op_fusion": True,
|
||||
},
|
||||
)
|
||||
|
||||
class OpenVINOGraphModule(torch.nn.Module):
|
||||
def __init__(self, gm, partition_id, use_python_fusion_cache, model_hash_str: str = None, file_name=""):
|
||||
super().__init__()
|
||||
self.gm = gm
|
||||
self.partition_id = partition_id
|
||||
self.executor_parameters = {"use_python_fusion_cache": use_python_fusion_cache,
|
||||
"model_hash_str": model_hash_str}
|
||||
self.file_name = file_name
|
||||
self.perm_fallback = False
|
||||
|
||||
def __call__(self, *args):
|
||||
#if self.perm_fallback:
|
||||
# return self.gm(*args)
|
||||
|
||||
#try:
|
||||
result = openvino_execute(self.gm, *args, executor_parameters=self.executor_parameters, partition_id=self.partition_id, file_name=self.file_name)
|
||||
#except Exception:
|
||||
# self.perm_fallback = True
|
||||
# return self.gm(*args)
|
||||
|
||||
return result
|
||||
|
||||
def get_device():
|
||||
core = Core()
|
||||
if os.getenv("OPENVINO_TORCH_MODEL_CACHING") != "0":
|
||||
os.environ.setdefault('OPENVINO_TORCH_MODEL_CACHING', "1")
|
||||
model_hash_str = sha256(subgraph.code.encode('utf-8')).hexdigest()
|
||||
executor_parameters = {"model_hash_str": model_hash_str}
|
||||
|
||||
example_inputs.reverse()
|
||||
cache_root = "./cache/"
|
||||
if os.getenv("OPENVINO_TORCH_CACHE_DIR") is not None:
|
||||
cache_root = os.getenv("OPENVINO_TORCH_CACHE_DIR")
|
||||
|
||||
if os.getenv("OPENVINO_TORCH_BACKEND_DEVICE") is not None:
|
||||
device = os.getenv("OPENVINO_TORCH_BACKEND_DEVICE")
|
||||
elif any(openvino_cpu in cpu_module.lower() for cpu_module in shared.cmd_opts.use_cpu for openvino_cpu in ["openvino", "all"]):
|
||||
@@ -43,83 +66,316 @@ def openvino_fx(subgraph, example_inputs):
|
||||
os.environ.setdefault('OPENVINO_TORCH_BACKEND_DEVICE', device)
|
||||
shared.log.debug(f"OpenVINO Device: {device}")
|
||||
if shared.opts.cuda_compile_errors and device not in core.available_devices:
|
||||
shared.log.warning(f"OpenVINO: Specified device {device} is not in the list of OpenVINO Available Devices")
|
||||
shared.log.error(f"OpenVINO: Specified device {device} is not in the list of OpenVINO Available Devices")
|
||||
assert device in core.available_devices, f"OpenVINO: Specified device {device} is not in the list of OpenVINO Available Devices"
|
||||
|
||||
#Cache saving keeps increasing the partition id
|
||||
#This loop check if non 0 partition id caches exist
|
||||
#Takes 0.002 seconds when nothing is found
|
||||
use_cached_file = False
|
||||
for i in range(100):
|
||||
file_name = get_cached_file_name(*example_inputs, model_hash_str=str(model_hash_str + str(i)), device=device, cache_root=cache_root)
|
||||
if file_name is not None and os.path.isfile(file_name + ".xml") and os.path.isfile(file_name + ".bin"):
|
||||
use_cached_file = True
|
||||
break
|
||||
return device
|
||||
|
||||
if use_cached_file:
|
||||
om = core.read_model(file_name + ".xml")
|
||||
def cache_root_path():
|
||||
cache_root = "./cache/"
|
||||
if os.getenv("OPENVINO_TORCH_CACHE_DIR") is not None:
|
||||
cache_root = os.getenv("OPENVINO_TORCH_CACHE_DIR")
|
||||
return cache_root
|
||||
|
||||
dtype_mapping = {
|
||||
torch.float32: Type.f32,
|
||||
torch.float64: Type.f64,
|
||||
torch.float16: Type.f16,
|
||||
torch.int64: Type.i64,
|
||||
torch.int32: Type.i32,
|
||||
torch.uint8: Type.u8,
|
||||
torch.int8: Type.i8,
|
||||
torch.bool: Type.boolean
|
||||
}
|
||||
def cached_model_name(model_hash_str, device, args, cache_root, reversed = False):
|
||||
if model_hash_str is None:
|
||||
return None
|
||||
|
||||
for idx, input_data in enumerate(example_inputs):
|
||||
om.inputs[idx].get_node().set_element_type(dtype_mapping[input_data.dtype])
|
||||
om.inputs[idx].get_node().set_partial_shape(PartialShape(list(input_data.shape)))
|
||||
om.validate_nodes_and_infer_types()
|
||||
model_cache_dir = cache_root + "/model/"
|
||||
|
||||
if model_hash_str is not None:
|
||||
core.set_property({'CACHE_DIR': cache_root + '/blob'})
|
||||
try:
|
||||
os.makedirs(model_cache_dir, exist_ok=True)
|
||||
file_name = model_cache_dir + model_hash_str + "_" + device
|
||||
except OSError as error:
|
||||
shared.log.error(f"Cache directory {cache_root} cannot be created. Model caching is disabled. Error: {error}")
|
||||
return None
|
||||
|
||||
compiled_model = core.compile_model(om, device)
|
||||
def _call(*args):
|
||||
ov_inputs = [a.detach().cpu().numpy() for a in args]
|
||||
ov_inputs.reverse()
|
||||
res = compiled_model(ov_inputs)
|
||||
result = [torch.from_numpy(res[out]) for out in compiled_model.outputs]
|
||||
return result
|
||||
return _call
|
||||
else:
|
||||
example_inputs.reverse()
|
||||
model = make_fx(subgraph)(*example_inputs)
|
||||
with devices.inference_context():
|
||||
model.eval()
|
||||
partitioner = Partitioner()
|
||||
compiled_model = partitioner.make_partitions(model)
|
||||
inputs_str = ""
|
||||
for input_data in args:
|
||||
if reversed:
|
||||
inputs_str = "_" + str(input_data.type()) + str(input_data.size())[11:-1].replace(" ", "") + inputs_str
|
||||
else:
|
||||
inputs_str += "_" + str(input_data.type()) + str(input_data.size())[11:-1].replace(" ", "")
|
||||
inputs_str = sha256(inputs_str.encode('utf-8')).hexdigest()
|
||||
file_name += inputs_str
|
||||
|
||||
def _call(*args):
|
||||
res = execute(compiled_model, *args, executor="openvino",
|
||||
executor_parameters=executor_parameters)
|
||||
return res
|
||||
return _call
|
||||
|
||||
|
||||
def get_cached_file_name(*args, model_hash_str, device, cache_root):
|
||||
file_name = None
|
||||
if model_hash_str is not None:
|
||||
model_cache_dir = cache_root + "/model/"
|
||||
try:
|
||||
os.makedirs(model_cache_dir, exist_ok=True)
|
||||
file_name = model_cache_dir + model_hash_str + "_" + device
|
||||
for input_data in args:
|
||||
if file_name is not None:
|
||||
file_name += "_" + str(input_data.type()) + str(input_data.size())[11:-1].replace(" ", "")
|
||||
except OSError as error:
|
||||
print("Cache directory ", cache_root, " cannot be created. Model caching is disabled. Error: ", error)
|
||||
file_name = None
|
||||
model_hash_str = None
|
||||
return file_name
|
||||
|
||||
def check_fully_supported(self, graph_module):
|
||||
num_fused = 0
|
||||
for node in graph_module.graph.nodes:
|
||||
if node.op == "call_module" and "fused_" in node.name:
|
||||
num_fused += 1
|
||||
elif node.op != "placeholder" and node.op != "output":
|
||||
return False
|
||||
if num_fused == 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
Partitioner.check_fully_supported = functools.partial(check_fully_supported, Partitioner)
|
||||
|
||||
def execute(
|
||||
gm,
|
||||
*args,
|
||||
executor = "openvino",
|
||||
executor_parameters = None,
|
||||
file_name = ""
|
||||
):
|
||||
if executor == "openvino":
|
||||
return openvino_execute_partitioned(gm, *args, executor_parameters=executor_parameters, file_name=file_name)
|
||||
elif executor == "strictly_openvino":
|
||||
return openvino_execute(gm, *args, executor_parameters=executor_parameters, file_name=file_name)
|
||||
|
||||
msg = "Received unexpected value for 'executor': {0}. Allowed values are: openvino, strictly_openvino.".format(executor)
|
||||
raise ValueError(msg)
|
||||
|
||||
def execute_cached(compiled_model, *args):
|
||||
flat_args, _ = tree_flatten(args)
|
||||
ov_inputs = [a.detach().cpu().numpy() for a in flat_args]
|
||||
|
||||
if (shared.compiled_model_state.cn_model == []):
|
||||
ov_inputs.reverse()
|
||||
|
||||
res = compiled_model(ov_inputs)
|
||||
result = [torch.from_numpy(res[out]) for out in compiled_model.outputs]
|
||||
return result
|
||||
|
||||
def openvino_clear_caches():
|
||||
global partitioned_modules
|
||||
global compiled_cache
|
||||
|
||||
compiled_cache.clear()
|
||||
partitioned_modules.clear()
|
||||
|
||||
def openvino_compile(gm, *args, model_hash_str: str = None, file_name=""):
|
||||
core = Core()
|
||||
|
||||
device = get_device()
|
||||
cache_root = cache_root_path()
|
||||
|
||||
if file_name is not None and os.path.isfile(file_name + ".xml") and os.path.isfile(file_name + ".bin"):
|
||||
om = core.read_model(file_name + ".xml")
|
||||
else:
|
||||
fe_manager = FrontEndManager()
|
||||
fe = fe_manager.load_by_framework("pytorch")
|
||||
|
||||
input_shapes = []
|
||||
input_types = []
|
||||
for input_data in args:
|
||||
input_types.append(input_data.type())
|
||||
input_shapes.append(input_data.size())
|
||||
|
||||
decoder = TorchFXPythonDecoder(gm, gm, input_shapes=input_shapes, input_types=input_types)
|
||||
|
||||
im = fe.load(decoder)
|
||||
|
||||
om = fe.convert(im)
|
||||
|
||||
if (file_name is not None):
|
||||
serialize(om, file_name + ".xml", file_name + ".bin")
|
||||
if (shared.compiled_model_state.cn_model != []):
|
||||
f = open(file_name + ".txt", "w")
|
||||
for input_data in args:
|
||||
f.write(str(input_data.size()))
|
||||
f.write("\n")
|
||||
f.close()
|
||||
|
||||
dtype_mapping = {
|
||||
torch.float32: Type.f32,
|
||||
torch.float64: Type.f64,
|
||||
torch.float16: Type.f16,
|
||||
torch.int64: Type.i64,
|
||||
torch.int32: Type.i32,
|
||||
torch.uint8: Type.u8,
|
||||
torch.int8: Type.i8,
|
||||
torch.bool: Type.boolean
|
||||
}
|
||||
|
||||
for idx, input_data in enumerate(args):
|
||||
om.inputs[idx].get_node().set_element_type(dtype_mapping[input_data.dtype])
|
||||
om.inputs[idx].get_node().set_partial_shape(PartialShape(list(input_data.shape)))
|
||||
om.validate_nodes_and_infer_types()
|
||||
|
||||
if model_hash_str is not None:
|
||||
core.set_property({'CACHE_DIR': cache_root + '/blob'})
|
||||
|
||||
compiled = core.compile_model(om, device)
|
||||
return compiled
|
||||
|
||||
def openvino_compile_cached_model(cached_model_path, *example_inputs):
|
||||
core = Core()
|
||||
om = core.read_model(cached_model_path + ".xml")
|
||||
|
||||
dtype_mapping = {
|
||||
torch.float32: Type.f32,
|
||||
torch.float64: Type.f64,
|
||||
torch.float16: Type.f16,
|
||||
torch.int64: Type.i64,
|
||||
torch.int32: Type.i32,
|
||||
torch.uint8: Type.u8,
|
||||
torch.int8: Type.i8,
|
||||
torch.bool: Type.boolean
|
||||
}
|
||||
|
||||
for idx, input_data in enumerate(example_inputs):
|
||||
om.inputs[idx].get_node().set_element_type(dtype_mapping[input_data.dtype])
|
||||
om.inputs[idx].get_node().set_partial_shape(PartialShape(list(input_data.shape)))
|
||||
om.validate_nodes_and_infer_types()
|
||||
|
||||
core.set_property({'CACHE_DIR': cache_root_path() + '/blob'})
|
||||
|
||||
compiled_model = core.compile_model(om, get_device())
|
||||
|
||||
return compiled_model
|
||||
|
||||
def openvino_execute(gm, *args, executor_parameters=None, partition_id, file_name=""):
|
||||
executor_parameters = executor_parameters or DEFAULT_OPENVINO_PYTHON_CONFIG
|
||||
|
||||
use_cache = executor_parameters.get(
|
||||
"use_python_fusion_cache",
|
||||
DEFAULT_OPENVINO_PYTHON_CONFIG["use_python_fusion_cache"],
|
||||
)
|
||||
global compiled_cache
|
||||
|
||||
model_hash_str = executor_parameters.get("model_hash_str", None)
|
||||
if model_hash_str is not None:
|
||||
model_hash_str = model_hash_str + str(partition_id)
|
||||
|
||||
if use_cache and (partition_id in compiled_cache):
|
||||
compiled = compiled_cache[partition_id]
|
||||
else:
|
||||
if (shared.compiled_model_state.cn_model != [] and file_name is not None
|
||||
and os.path.isfile(file_name + ".xml") and os.path.isfile(file_name + ".bin")):
|
||||
compiled = openvino_compile_cached_model(file_name, *args)
|
||||
else:
|
||||
compiled = openvino_compile(gm, *args, model_hash_str=model_hash_str, file_name=file_name)
|
||||
compiled_cache[partition_id] = compiled
|
||||
|
||||
flat_args, _ = tree_flatten(args)
|
||||
ov_inputs = [a.detach().cpu().numpy() for a in flat_args]
|
||||
|
||||
res = compiled(ov_inputs)
|
||||
|
||||
results1 = [torch.from_numpy(res[out]) for out in compiled.outputs]
|
||||
if len(results1) == 1:
|
||||
return results1[0]
|
||||
return results1
|
||||
|
||||
def openvino_execute_partitioned(gm, *args, executor_parameters=None, file_name=""):
|
||||
executor_parameters = executor_parameters or DEFAULT_OPENVINO_PYTHON_CONFIG
|
||||
|
||||
global partitioned_modules
|
||||
|
||||
use_python_fusion_cache = executor_parameters.get(
|
||||
"use_python_fusion_cache",
|
||||
DEFAULT_OPENVINO_PYTHON_CONFIG["use_python_fusion_cache"],
|
||||
)
|
||||
model_hash_str = executor_parameters.get("model_hash_str", None)
|
||||
|
||||
signature = str(id(gm))
|
||||
for idx, input_data in enumerate(args):
|
||||
if isinstance(input_data, torch.Tensor):
|
||||
signature = signature + "_" + str(idx) + ":" + str(input_data.type())[6:] + ":" + str(input_data.size())[11:-1].replace(" ", "")
|
||||
else:
|
||||
signature = signature + "_" + str(idx) + ":" + type(input_data).__name__ + ":val(" + str(input_data) + ")"
|
||||
|
||||
if signature not in partitioned_modules:
|
||||
partitioned_modules[signature] = partition_graph(gm, use_python_fusion_cache=use_python_fusion_cache,
|
||||
model_hash_str=model_hash_str, file_name=file_name)
|
||||
|
||||
return partitioned_modules[signature](*args)
|
||||
|
||||
def partition_graph(gm, use_python_fusion_cache: bool, model_hash_str: str = None, file_name=""):
|
||||
global max_openvino_partitions
|
||||
for node in gm.graph.nodes:
|
||||
if node.op == "call_module" and "fused_" in node.name:
|
||||
openvino_submodule = getattr(gm, node.name)
|
||||
gm.delete_submodule(node.target)
|
||||
gm.add_submodule(
|
||||
node.target,
|
||||
OpenVINOGraphModule(openvino_submodule, shared.compiled_model_state.partition_id, use_python_fusion_cache,
|
||||
model_hash_str=model_hash_str, file_name=file_name),
|
||||
)
|
||||
shared.compiled_model_state.partition_id = shared.compiled_model_state.partition_id + 1
|
||||
|
||||
return gm
|
||||
|
||||
@register_backend
|
||||
@fake_tensor_unsupported
|
||||
def openvino_fx(subgraph, example_inputs):
|
||||
executor_parameters = None
|
||||
inputs_reversed = False
|
||||
if os.getenv("OPENVINO_TORCH_MODEL_CACHING") != "0":
|
||||
os.environ.setdefault('OPENVINO_TORCH_MODEL_CACHING', "1")
|
||||
# Create a hash to be used for caching
|
||||
model_hash_str = sha256(subgraph.code.encode('utf-8')).hexdigest()
|
||||
if (shared.compiled_model_state.cn_model != [] and shared.compiled_model_state.partition_id == 0):
|
||||
model_hash_str = model_hash_str + str(shared.compiled_model_state.cn_model)
|
||||
|
||||
if (shared.compiled_model_state.lora_model != []):
|
||||
model_hash_str = model_hash_str + str(shared.compiled_model_state.lora_model)
|
||||
|
||||
executor_parameters = {"model_hash_str": model_hash_str}
|
||||
# Check if the model was fully supported and already cached
|
||||
example_inputs.reverse()
|
||||
inputs_reversed = True
|
||||
maybe_fs_cached_name = cached_model_name(model_hash_str + "_fs", get_device(), example_inputs, cache_root_path())
|
||||
|
||||
if os.path.isfile(maybe_fs_cached_name + ".xml") and os.path.isfile(maybe_fs_cached_name + ".bin"):
|
||||
if (shared.compiled_model_state.cn_model != [] and str(shared.compiled_model_state.cn_model) in maybe_fs_cached_name):
|
||||
example_inputs_reordered = []
|
||||
if (os.path.isfile(maybe_fs_cached_name + ".txt")):
|
||||
f = open(maybe_fs_cached_name + ".txt", "r")
|
||||
for input_data in example_inputs:
|
||||
shape = f.readline()
|
||||
if (str(input_data.size()) != shape):
|
||||
for idx1, input_data1 in enumerate(example_inputs):
|
||||
if (str(input_data1.size()).strip() == str(shape).strip()):
|
||||
example_inputs_reordered.append(example_inputs[idx1])
|
||||
example_inputs = example_inputs_reordered
|
||||
|
||||
# Model is fully supported and already cached. Run the cached OV model directly.
|
||||
compiled_model = openvino_compile_cached_model(maybe_fs_cached_name, *example_inputs)
|
||||
|
||||
def _call(*args):
|
||||
if (shared.compiled_model_state.cn_model != [] and str(shared.compiled_model_state.cn_model) in maybe_fs_cached_name):
|
||||
args_reordered = []
|
||||
if (os.path.isfile(maybe_fs_cached_name + ".txt")):
|
||||
f = open(maybe_fs_cached_name + ".txt", "r")
|
||||
for input_data in args:
|
||||
shape = f.readline()
|
||||
if (str(input_data.size()) != shape):
|
||||
for idx1, input_data1 in enumerate(args):
|
||||
if (str(input_data1.size()).strip() == str(shape).strip()):
|
||||
args_reordered.append(args[idx1])
|
||||
args = args_reordered
|
||||
|
||||
res = execute_cached(compiled_model, *args)
|
||||
shared.compiled_model_state.partition_id = shared.compiled_model_state.partition_id + 1
|
||||
return res
|
||||
return _call
|
||||
else:
|
||||
maybe_fs_cached_name = ""
|
||||
|
||||
if inputs_reversed:
|
||||
example_inputs.reverse()
|
||||
model = make_fx(subgraph)(*example_inputs)
|
||||
for node in model.graph.nodes:
|
||||
if node.target == torch.ops.aten.mul_.Tensor:
|
||||
node.target = torch.ops.aten.mul.Tensor
|
||||
with devices.inference_context():
|
||||
model.eval()
|
||||
partitioner = Partitioner()
|
||||
compiled_model = partitioner.make_partitions(model)
|
||||
|
||||
if executor_parameters is not None and 'model_hash_str' in executor_parameters:
|
||||
# Check if the model is fully supported.
|
||||
fully_supported = partitioner.check_fully_supported(compiled_model)
|
||||
if fully_supported:
|
||||
executor_parameters["model_hash_str"] += "_fs"
|
||||
|
||||
def _call(*args):
|
||||
res = execute(compiled_model, *args, executor="openvino",
|
||||
executor_parameters=executor_parameters, file_name=maybe_fs_cached_name)
|
||||
return res
|
||||
return _call
|
||||
|
||||
+41
-22
@@ -1,3 +1,4 @@
|
||||
import time
|
||||
import diffusers
|
||||
import diffusers.models.lora as diffusers_lora
|
||||
# from modules import shared
|
||||
@@ -7,13 +8,15 @@ import modules.shared as shared
|
||||
lora_state = { # TODO Lora state for Diffusers
|
||||
'multiplier': [],
|
||||
'active': False,
|
||||
'loaded': 0,
|
||||
'all_loras': []
|
||||
'loaded': [],
|
||||
'all_loras': [],
|
||||
}
|
||||
def unload_diffusers_lora():
|
||||
try:
|
||||
pipe = shared.sd_model
|
||||
if shared.opts.diffusers_lora_loader == "diffusers default":
|
||||
if shared.opts.diffusers_lora_loader == "diffusers":
|
||||
if len(lora_state['loaded']) > 1 and hasattr(pipe, "unfuse_lora"):
|
||||
pipe.unfuse_lora()
|
||||
pipe.unload_lora_weights()
|
||||
pipe._remove_text_encoder_monkey_patch() # pylint: disable=W0212
|
||||
proc_cls_name = next(iter(pipe.unet.attn_processors.values())).__class__.__name__
|
||||
@@ -29,22 +32,32 @@ def unload_diffusers_lora():
|
||||
if shared.opts.diffusers_lora_loader == "sequential apply":
|
||||
lora_network.unapply_to()
|
||||
lora_state['active'] = False
|
||||
lora_state['loaded'] = 0
|
||||
lora_state['loaded'].clear()
|
||||
lora_state['all_loras'] = []
|
||||
lora_state['multiplier'] = []
|
||||
|
||||
except Exception as e:
|
||||
shared.log.error(f"Diffusers LoRA unloading failed: {e}")
|
||||
shared.log.error(f"LoRA unload failed: {e}")
|
||||
|
||||
|
||||
def load_diffusers_lora(name, lora, strength = 1.0):
|
||||
def load_diffusers_lora(name, lora, strength = 1.0, num_loras = 1):
|
||||
if f'{lora.filename}:{strength}' in lora_state['loaded']:
|
||||
shared.log.info(f'LoRA cached: {name} strength={strength}')
|
||||
return
|
||||
try:
|
||||
t0 = time.time()
|
||||
pipe = shared.sd_model
|
||||
lora_state['active'] = True
|
||||
lora_state['loaded'] += 1
|
||||
lora_state['multiplier'].append(strength)
|
||||
if shared.opts.diffusers_lora_loader == "diffusers default":
|
||||
pipe.load_lora_weights(lora.filename, cache_dir=shared.opts.diffusers_dir, local_files_only=True, lora_scale=strength)
|
||||
fuse = 0
|
||||
if shared.opts.diffusers_lora_loader.startswith("diffusers"):
|
||||
pipe.load_lora_weights(lora.filename, cache_dir=shared.opts.diffusers_dir, local_files_only=True, lora_scale=strength, low_cpu_mem_usage=True)
|
||||
if num_loras > 1 and hasattr(pipe, "fuse_lora"):
|
||||
t2 = time.time()
|
||||
pipe.fuse_lora(lora_scale=strength)
|
||||
fuse = time.time() - t2
|
||||
lora_state['loaded'].append(f'{lora.filename}:{strength}')
|
||||
if shared.compiled_model_state is not None: #filename breaks caching
|
||||
shared.compiled_model_state.lora_model.append(f'{name}:{strength}')
|
||||
else:
|
||||
from safetensors.torch import load_file
|
||||
lora_sd = load_file(lora.filename)
|
||||
@@ -60,20 +73,26 @@ def load_diffusers_lora(name, lora, strength = 1.0):
|
||||
lora_network.to(shared.device, dtype=pipe.unet.dtype)
|
||||
lora_network.apply_to(multiplier=strength)
|
||||
lora_state['all_loras'].append(lora_network)
|
||||
shared.log.info(f"LoRA loaded: {name} strength={strength} loader={shared.opts.diffusers_lora_loader}")
|
||||
lora_state['loaded'].append(f'{lora.filename}:{strength}')
|
||||
if shared.compiled_model_state is not None: #filename breaks caching
|
||||
shared.compiled_model_state.lora_model.append(f'{name}:{strength}')
|
||||
t1 = time.time()
|
||||
fuse = f'fuse={fuse:.2f}s' if fuse > 0 else ''
|
||||
shared.log.info(f'LoRA loaded: {name} strength={strength} loader="{shared.opts.diffusers_lora_loader}" lora={t1-t0:.2f}s {fuse}')
|
||||
except Exception as e:
|
||||
shared.log.error(f"LoRA loading failed: {name} {e}")
|
||||
lines = str(e).splitlines()
|
||||
shared.log.error(f'LoRA loading failed: {name} loader="{shared.opts.diffusers_lora_loader}" {lines[0]}')
|
||||
|
||||
|
||||
# Diffusersで動くLoRA。このファイル単独で完結する。
|
||||
# LoRA module for Diffusers. This file works independently.
|
||||
import bisect
|
||||
import math
|
||||
from typing import Any, Dict, List, Mapping, Optional, Union
|
||||
from diffusers import UNet2DConditionModel
|
||||
from tqdm import tqdm
|
||||
from transformers import CLIPTextModel
|
||||
import torch
|
||||
import bisect # pylint: disable=wrong-import-order
|
||||
import math # pylint: disable=wrong-import-order
|
||||
from typing import Any, Dict, List, Mapping, Optional, Union # pylint: disable=wrong-import-order
|
||||
from diffusers import UNet2DConditionModel # pylint: disable=wrong-import-order
|
||||
from tqdm import tqdm # pylint: disable=wrong-import-order
|
||||
from transformers import CLIPTextModel # pylint: disable=wrong-import-order
|
||||
import torch # pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
def make_unet_conversion_map() -> Dict[str, str]:
|
||||
@@ -496,7 +515,7 @@ class LoRANetwork(torch.nn.Module): # pylint: disable=abstract-method
|
||||
for lora in tqdm(self.text_encoder_loras + self.unet_loras):
|
||||
lora.restore_from(multiplier)
|
||||
|
||||
def load_state_dict(self, state_dict: Mapping[str, Any], strict: bool = True):
|
||||
def load_state_dict(self, state_dict: Mapping[str, Any], strict: bool = True): # pylint: disable=arguments-differ
|
||||
# convert SDXL Stability AI's state dict to Diffusers' based state dict
|
||||
map_keys = list(UNET_CONVERSION_MAP.keys()) # prefix of U-Net modules
|
||||
map_keys.sort()
|
||||
@@ -514,8 +533,8 @@ class LoRANetwork(torch.nn.Module): # pylint: disable=abstract-method
|
||||
# because V2 LoRA is based on U-Net created by use_linear_projection=False
|
||||
my_state_dict = self.state_dict()
|
||||
for key in state_dict.keys():
|
||||
if state_dict[key].size() != my_state_dict[key].size():
|
||||
if state_dict[key].size() != my_state_dict[key].size(): # pylint: disable=unsubscriptable-object
|
||||
# print(f"convert {key} from {state_dict[key].size()} to {my_state_dict[key].size()}")
|
||||
state_dict[key] = state_dict[key].view(my_state_dict[key].size())
|
||||
state_dict[key] = state_dict[key].view(my_state_dict[key].size()) # pylint: disable=unsubscriptable-object
|
||||
|
||||
return super().load_state_dict(state_dict, strict)
|
||||
|
||||
+3
-1
@@ -68,10 +68,12 @@ def create_paths(opts, log=None):
|
||||
fullpath = os.path.join(data_path, tgt)
|
||||
if len(data_path) > 0 and os.path.isabs(data_path):
|
||||
return fullpath
|
||||
if os.path.isabs(fullpath) and os.path.exists(fullpath):
|
||||
return fullpath
|
||||
try:
|
||||
relpath = os.path.relpath(fullpath, script_path)
|
||||
opts.data[folder] = relpath
|
||||
except:
|
||||
except Exception:
|
||||
opts.data[folder] = fullpath
|
||||
return opts.data[folder]
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp
|
||||
outputs = []
|
||||
params = {}
|
||||
if extras_mode == 1:
|
||||
shared.log.debug(f'process: mode=batch folder={image_folder}')
|
||||
for img in image_folder:
|
||||
if isinstance(img, Image.Image):
|
||||
image = img
|
||||
@@ -29,6 +30,7 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp
|
||||
image_names.append(fn)
|
||||
image_ext.append(ext)
|
||||
elif extras_mode == 2:
|
||||
shared.log.debug(f'process: mode=folder folder={input_dir}')
|
||||
assert not shared.cmd_opts.hide_ui_dir_config, '--hide-ui-dir-config option must be disabled'
|
||||
assert input_dir, 'input directory not selected'
|
||||
image_list = shared.listfiles(input_dir)
|
||||
@@ -50,6 +52,7 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp
|
||||
else:
|
||||
outpath = opts.outdir_samples or opts.outdir_extras_samples
|
||||
for image, name, ext in zip(image_data, image_names, image_ext):
|
||||
shared.log.debug(f'process: image={image} {args}')
|
||||
infotext = ''
|
||||
if shared.state.interrupted:
|
||||
shared.log.debug('Postprocess interrupted')
|
||||
|
||||
+11
-7
@@ -462,7 +462,8 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su
|
||||
if all_negative_prompts is None:
|
||||
all_negative_prompts = p.all_negative_prompts
|
||||
comment = ', '.join(comments) if comments is not None and type(comments) is list else None
|
||||
|
||||
ops = list(set(p.ops))
|
||||
ops.reverse()
|
||||
args = {
|
||||
# basic
|
||||
"Steps": p.steps,
|
||||
@@ -488,7 +489,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su
|
||||
"Backend": 'Diffusers' if shared.backend == shared.Backend.DIFFUSERS else 'Original',
|
||||
"Version": git_commit,
|
||||
"Comment": comment,
|
||||
"Operations": '; '.join(p.ops).replace('"', '') if len(p.ops) > 0 else 'none',
|
||||
"Operations": '; '.join(ops).replace('"', '') if len(p.ops) > 0 else 'none',
|
||||
}
|
||||
if 'txt2img' in p.ops:
|
||||
pass
|
||||
@@ -572,6 +573,8 @@ def print_profile(profile, msg: str):
|
||||
def process_images(p: StableDiffusionProcessing) -> Processed:
|
||||
if not hasattr(p.sd_model, 'sd_checkpoint_info'):
|
||||
return None
|
||||
if p.scripts is not None:
|
||||
p.scripts.before_process(p)
|
||||
stored_opts = {}
|
||||
for k, v in p.override_settings.copy().items():
|
||||
orig = shared.opts.data.get(k, None) or shared.opts.data_labels[k].default
|
||||
@@ -686,8 +689,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
|
||||
p.all_subseeds = subseed
|
||||
else:
|
||||
p.all_subseeds = [int(subseed) + x for x in range(len(p.all_prompts))]
|
||||
if os.path.exists(shared.opts.embeddings_dir) and not p.do_not_reload_embeddings:
|
||||
modules.sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings()
|
||||
if os.path.exists(shared.opts.embeddings_dir) and not p.do_not_reload_embeddings and shared.backend == shared.Backend.ORIGINAL:
|
||||
modules.sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=False)
|
||||
if p.scripts is not None:
|
||||
p.scripts.process(p)
|
||||
infotexts = []
|
||||
@@ -984,12 +987,14 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
|
||||
info = create_infotext(self, self.all_prompts, self.all_seeds, self.all_subseeds, [], iteration=self.iteration, position_in_batch=index)
|
||||
self.extra_generation_params = orig1
|
||||
self.restore_faces = orig2
|
||||
images.save_image(image, self.outpath_samples, "", seeds[index], prompts[index], shared.opts.samples_format, info=info, suffix="-before-highres-fix")
|
||||
images.save_image(image, self.outpath_samples, "", seeds[index], prompts[index], shared.opts.samples_format, info=info, suffix="-before-hires", index=index+1)
|
||||
|
||||
if shared.backend == shared.Backend.DIFFUSERS:
|
||||
modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.TEXT_2_IMAGE)
|
||||
|
||||
latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None")
|
||||
if latent_scale_mode is not None:
|
||||
self.hr_force = False # no need to force anything
|
||||
if self.enable_hr and (latent_scale_mode is None or self.hr_force):
|
||||
if len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) == 0:
|
||||
shared.log.warning(f"Cannot find upscaler for hires: {self.hr_upscaler}")
|
||||
@@ -1013,11 +1018,10 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
|
||||
decoded_samples = decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae))
|
||||
lowres_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0)
|
||||
batch_images = []
|
||||
for i, x_sample in enumerate(lowres_samples):
|
||||
for _i, x_sample in enumerate(lowres_samples):
|
||||
x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2)
|
||||
x_sample = validate_sample(x_sample)
|
||||
image = Image.fromarray(x_sample)
|
||||
save_intermediate(image, i)
|
||||
image = images.resize_image(1, image, target_width, target_height, upscaler_name=self.hr_upscaler)
|
||||
image = np.array(image).astype(np.float32) / 255.0
|
||||
image = np.moveaxis(image, 2, 0)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import time
|
||||
import math
|
||||
import inspect
|
||||
import typing
|
||||
import torch
|
||||
import torchvision.transforms.functional as TF
|
||||
import diffusers
|
||||
import modules.devices as devices
|
||||
import modules.shared as shared
|
||||
import modules.sd_samplers as sd_samplers
|
||||
@@ -15,12 +17,6 @@ from modules.processing import StableDiffusionProcessing
|
||||
import modules.prompt_parser_diffusers as prompt_parser_diffusers
|
||||
|
||||
|
||||
try:
|
||||
import diffusers
|
||||
except Exception as ex:
|
||||
shared.log.error(f'Failed to import diffusers: {ex}')
|
||||
|
||||
|
||||
def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_prompts):
|
||||
results = []
|
||||
if p.enable_hr and p.hr_upscaler != 'None' and p.denoising_strength > 0 and len(getattr(p, 'init_images', [])) == 0:
|
||||
@@ -78,8 +74,8 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
|
||||
model.vae.to(devices.device)
|
||||
latents.to(model.vae.device)
|
||||
|
||||
needs_upcasting = model.vae.dtype == torch.float16 and model.vae.config.force_upcast
|
||||
if needs_upcasting: # this is done by diffusers automatically if output_type != 'latent'
|
||||
upcast = (model.vae.dtype == torch.float16) and model.vae.config.force_upcast and hasattr(model, 'upcast_vae')
|
||||
if upcast: # this is done by diffusers automatically if output_type != 'latent'
|
||||
model.upcast_vae()
|
||||
latents = latents.to(next(iter(model.vae.post_quant_conv.parameters())).dtype)
|
||||
|
||||
@@ -87,7 +83,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
|
||||
if shared.opts.diffusers_move_unet and not model.has_accelerate:
|
||||
model.unet.to(unet_device)
|
||||
t1 = time.time()
|
||||
shared.log.debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)} images={latents.shape[0]} latents={latents.shape} time={round(t1-t0, 3)}s')
|
||||
shared.log.debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={upcast} images={latents.shape[0]} latents={latents.shape} time={round(t1-t0, 3)}s')
|
||||
return decoded
|
||||
|
||||
def full_vae_encode(image, model):
|
||||
@@ -171,6 +167,8 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
|
||||
return prompts, negative_prompts, prompts_2, negative_prompts_2
|
||||
|
||||
def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: typing.Optional[list]=None, negative_prompts_2: typing.Optional[list]=None, desc:str='', **kwargs):
|
||||
if hasattr(model, 'embedding_db'):
|
||||
del model.embedding_db
|
||||
try:
|
||||
is_refiner = model.text_encoder.__class__.__name__ != 'CLIPTextModel'
|
||||
except Exception:
|
||||
@@ -187,8 +185,13 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
|
||||
negative_embed = None
|
||||
negative_pooled = None
|
||||
prompts, negative_prompts, prompts_2, negative_prompts_2 = fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2)
|
||||
if shared.opts.prompt_attention in {'Compel parser', 'Full parser'} and 'StableDiffusion' in model.__class__.__name__:
|
||||
prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompts(model, prompts, negative_prompts, prompts_2, negative_prompts_2, is_refiner, kwargs.pop("clip_skip", None))
|
||||
parser = 'Fixed attention'
|
||||
if shared.opts.prompt_attention != 'Fixed attention' and 'StableDiffusion' in model.__class__.__name__:
|
||||
try:
|
||||
prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompts(model, prompts, negative_prompts, prompts_2, negative_prompts_2, is_refiner, kwargs.pop("clip_skip", None))
|
||||
parser = shared.opts.prompt_attention
|
||||
except Exception as e:
|
||||
shared.log.error(f'Prompt parser: {e}')
|
||||
if 'prompt' in possible:
|
||||
if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None:
|
||||
if type(pooled) == list:
|
||||
@@ -244,7 +247,10 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
|
||||
if 'negative_pooled_prompt_embeds' in clean:
|
||||
clean['negative_pooled_prompt_embeds'] = clean['negative_pooled_prompt_embeds'].shape if torch.is_tensor(clean['negative_pooled_prompt_embeds']) else type(clean['negative_pooled_prompt_embeds'])
|
||||
clean['generator'] = generator_device
|
||||
clean['parser'] = parser
|
||||
shared.log.debug(f'Diffuser pipeline: {model.__class__.__name__} task={sd_models.get_diffusers_task(model)} set={clean}')
|
||||
# components = [{ k: getattr(v, 'device', None) } for k, v in model.components.items()]
|
||||
# shared.log.debug(f'Diffuser pipeline components: {components}')
|
||||
return args
|
||||
|
||||
def recompile_model(hires=False):
|
||||
@@ -252,10 +258,12 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
|
||||
if shared.opts.cuda_compile_backend == "openvino_fx":
|
||||
compile_height = p.height if not hires else p.hr_upscale_to_y
|
||||
compile_width = p.width if not hires else p.hr_upscale_to_x
|
||||
if (not hasattr(shared.sd_model, "compiled_model_state") or (not shared.sd_model.compiled_model_state.first_pass
|
||||
and (shared.sd_model.compiled_model_state.height != compile_height or shared.sd_model.compiled_model_state.width != compile_width
|
||||
or shared.sd_model.compiled_model_state.batch_size != p.batch_size))):
|
||||
shared.log.info("OpenVINO: Resolution change detected")
|
||||
if (shared.compiled_model_state is None or
|
||||
(not shared.compiled_model_state.first_pass
|
||||
and (shared.compiled_model_state.height != compile_height
|
||||
or shared.compiled_model_state.width != compile_width
|
||||
or shared.compiled_model_state.batch_size != p.batch_size))):
|
||||
shared.log.info("OpenVINO: Parameter change detected")
|
||||
shared.log.info("OpenVINO: Recompiling base model")
|
||||
sd_models.unload_model_weights(op='model')
|
||||
sd_models.reload_model_weights(op='model')
|
||||
@@ -263,19 +271,20 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
|
||||
shared.log.info("OpenVINO: Recompiling refiner")
|
||||
sd_models.unload_model_weights(op='refiner')
|
||||
sd_models.reload_model_weights(op='refiner')
|
||||
shared.sd_model.compiled_model_state.height = compile_height
|
||||
shared.sd_model.compiled_model_state.width = compile_width
|
||||
shared.sd_model.compiled_model_state.batch_size = p.batch_size
|
||||
shared.sd_model.compiled_model_state.first_pass = False
|
||||
shared.compiled_model_state.height = compile_height
|
||||
shared.compiled_model_state.width = compile_width
|
||||
shared.compiled_model_state.batch_size = p.batch_size
|
||||
shared.compiled_model_state.first_pass = False
|
||||
else:
|
||||
pass #Can be implemented for TensorRT or Olive
|
||||
else:
|
||||
pass #Do nothing if compile is disabled
|
||||
|
||||
recompile_model()
|
||||
|
||||
is_karras_compatible = shared.sd_model.__class__.__init__.__annotations__.get("scheduler", None) == diffusers.schedulers.scheduling_utils.KarrasDiffusionSchedulers
|
||||
use_sampler = p.sampler_name if not p.is_hr_pass else p.latent_sampler
|
||||
if (not hasattr(shared.sd_model.scheduler, 'name')) or (shared.sd_model.scheduler.name != use_sampler) and (use_sampler != 'Default') and is_karras_compatible:
|
||||
sampler = sd_samplers.all_samplers_map.get(use_sampler, None)
|
||||
if ((not hasattr(shared.sd_model.scheduler, 'name')) or (p.sampler_name == 'DPM SDE') or (shared.sd_model.scheduler.name != p.sampler_name)) and (p.sampler_name != 'Default') and is_karras_compatible:
|
||||
sampler = sd_samplers.all_samplers_map.get(p.sampler_name, None)
|
||||
if sampler is None:
|
||||
sampler = sd_samplers.all_samplers_map.get("UniPC")
|
||||
sd_samplers.create_sampler(sampler.name, shared.sd_model) # TODO(Patrick): For wrapped pipelines this is currently a no-op
|
||||
@@ -297,20 +306,18 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
|
||||
task_specific_kwargs={}
|
||||
if sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.TEXT_2_IMAGE:
|
||||
p.ops.append('txt2img')
|
||||
task_specific_kwargs = {"height": p.height, "width": p.width}
|
||||
task_specific_kwargs = {"height": 8 * math.ceil(p.height / 8), "width": 8 * math.ceil(p.width / 8)}
|
||||
elif sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.IMAGE_2_IMAGE:
|
||||
p.ops.append('img2img')
|
||||
task_specific_kwargs = {"image": p.init_images, "strength": p.denoising_strength}
|
||||
elif sd_models.get_diffusers_task(shared.sd_model) == sd_models.DiffusersTaskType.INPAINTING:
|
||||
p.ops.append('inpaint')
|
||||
task_specific_kwargs = {"image": p.init_images, "mask_image": p.mask, "strength": p.denoising_strength, "height": p.height, "width": p.width}
|
||||
task_specific_kwargs = {"image": p.init_images, "mask_image": p.mask, "strength": p.denoising_strength, "height": 8 * math.ceil(p.height / 8), "width": 8 * math.ceil(p.width / 8)}
|
||||
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
unload_diffusers_lora()
|
||||
return results
|
||||
|
||||
recompile_model()
|
||||
|
||||
if shared.opts.diffusers_move_base and not shared.sd_model.has_accelerate:
|
||||
shared.sd_model.to(devices.device)
|
||||
|
||||
@@ -373,6 +380,11 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
|
||||
if latent_scale_mode is not None or p.hr_force:
|
||||
p.ops.append('hires')
|
||||
recompile_model(hires=True)
|
||||
if ((not hasattr(shared.sd_model.scheduler, 'name')) or (p.latent_sampler == 'DPM SDE') or (shared.sd_model.scheduler.name != p.latent_sampler)) and (p.latent_sampler != 'Default') and is_karras_compatible:
|
||||
sampler = sd_samplers.all_samplers_map.get(p.latent_sampler, None)
|
||||
if sampler is None:
|
||||
sampler = sd_samplers.all_samplers_map.get("UniPC")
|
||||
sd_samplers.create_sampler(sampler.name, shared.sd_model) # TODO(Patrick): For wrapped pipelines this is currently a no-op
|
||||
sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE)
|
||||
hires_args = set_pipeline_args(
|
||||
model=shared.sd_model,
|
||||
@@ -404,7 +416,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
|
||||
shared.sd_model.to(devices.cpu)
|
||||
devices.torch_gc()
|
||||
|
||||
if (not hasattr(shared.sd_refiner.scheduler, 'name')) or (shared.sd_refiner.scheduler.name != p.latent_sampler) and (p.sampler_name != 'Default'):
|
||||
if ((not hasattr(shared.sd_refiner.scheduler, 'name')) or (p.latent_sampler == 'DPM SDE') or (shared.sd_refiner.scheduler.name != p.latent_sampler)) and (p.latent_sampler != 'Default'):
|
||||
sampler = sd_samplers.all_samplers_map.get(p.latent_sampler, None)
|
||||
if sampler is None:
|
||||
sampler = sd_samplers.all_samplers_map.get("UniPC")
|
||||
|
||||
@@ -179,6 +179,7 @@ def compel_encode_prompt(
|
||||
return prompt_embed, positive_pooled, negative_embed, negative_pooled
|
||||
|
||||
# neither base+sdxl nor refiner+sdxl
|
||||
positive, negative = compel_te1(prompt), compel_te1(negative_prompt)
|
||||
positive = compel_te1(prompt)
|
||||
negative = compel_te1(negative_prompt)
|
||||
[prompt_embed, negative_embed] = compel_te1.pad_conditioning_tensors_to_same_length([positive, negative])
|
||||
return prompt_embed, None, negative_embed, None
|
||||
|
||||
+14
-19
@@ -1,12 +1,9 @@
|
||||
import os
|
||||
import sys
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from basicsr.utils.download_util import load_file_from_url
|
||||
from modules.upscaler import Upscaler, UpscalerData
|
||||
from modules.shared import opts, device
|
||||
from modules.shared import opts, device, log
|
||||
from modules import modelloader
|
||||
import modules.errors as errors
|
||||
|
||||
|
||||
class UpscalerRealESRGAN(Upscaler):
|
||||
@@ -16,8 +13,7 @@ class UpscalerRealESRGAN(Upscaler):
|
||||
super().__init__()
|
||||
try:
|
||||
from basicsr.archs.rrdbnet_arch import RRDBNet # pylint: disable=unused-import
|
||||
from realesrgan import RealESRGANer # pylint: disable=unused-import
|
||||
from realesrgan.archs.srvgg_arch import SRVGGNetCompact # pylint: disable=unused-import
|
||||
from modules.realesrgan_model_arch import RealESRGANer, SRVGGNetCompact # pylint: disable=unused-import
|
||||
self.enable = True
|
||||
self.scalers = []
|
||||
scalers = self.load_models(path)
|
||||
@@ -28,11 +24,9 @@ class UpscalerRealESRGAN(Upscaler):
|
||||
local_model_candidates = [local_model for local_model in local_model_paths if local_model.endswith(f"{filename}.pth")]
|
||||
if local_model_candidates:
|
||||
scaler.local_data_path = local_model_candidates[0]
|
||||
if scaler.name in opts.realesrgan_enabled_models:
|
||||
self.scalers.append(scaler)
|
||||
|
||||
self.scalers.append(scaler)
|
||||
except Exception as e:
|
||||
errors.display(e, 'real-esrgan')
|
||||
log.error(f"Error loading Real-ESRGAN: model={path} {e}")
|
||||
self.enable = False
|
||||
self.scalers = []
|
||||
|
||||
@@ -41,14 +35,13 @@ class UpscalerRealESRGAN(Upscaler):
|
||||
return img
|
||||
|
||||
try:
|
||||
from realesrgan import RealESRGANer
|
||||
from modules.realesrgan_model_arch import RealESRGANer
|
||||
except Exception:
|
||||
print("Error importing Real-ESRGAN:", file=sys.stderr)
|
||||
log.error("Error importing Real-ESRGAN:")
|
||||
return img
|
||||
|
||||
info = self.load_model(selected_model)
|
||||
if not os.path.exists(info.local_data_path):
|
||||
print(f"Unable to load RealESRGAN model: {info.name}")
|
||||
if info is None or not os.path.exists(info.local_data_path):
|
||||
return img
|
||||
|
||||
upsampler = RealESRGANer(
|
||||
@@ -70,13 +63,15 @@ class UpscalerRealESRGAN(Upscaler):
|
||||
try:
|
||||
info = next(iter([scaler for scaler in self.scalers if scaler.data_path == path]), None)
|
||||
if info is None:
|
||||
print(f"Unable to find model info: {path}")
|
||||
log.error(f"Model failed loading: type=R-ESRGAN model={info.name}")
|
||||
return None
|
||||
if info.local_data_path.startswith("http"):
|
||||
from modules.modelloader import load_file_from_url
|
||||
info.local_data_path = load_file_from_url(url=info.data_path, model_dir=self.model_download_path, progress=True)
|
||||
log.info(f"Model loaded: type=R-ESRGAN model={info.name}")
|
||||
return info
|
||||
except Exception as e:
|
||||
errors.display(e, 'real-esrgan model list')
|
||||
log.error(f"Model failed loading: type=R-ESRGAN model={info.name} {e}")
|
||||
return None
|
||||
|
||||
def load_models(self, _):
|
||||
@@ -86,7 +81,7 @@ class UpscalerRealESRGAN(Upscaler):
|
||||
def get_realesrgan_models(scaler):
|
||||
try:
|
||||
from basicsr.archs.rrdbnet_arch import RRDBNet
|
||||
from realesrgan.archs.srvgg_arch import SRVGGNetCompact
|
||||
from modules.realesrgan_model_arch import SRVGGNetCompact # pylint: disable=unused-import
|
||||
models = [
|
||||
UpscalerData(
|
||||
name="R-ESRGAN General 4xV3",
|
||||
@@ -132,6 +127,6 @@ def get_realesrgan_models(scaler):
|
||||
),
|
||||
]
|
||||
return models
|
||||
except Exception:
|
||||
print("Error creating Real-ESRGAN models list", file=sys.stderr)
|
||||
except Exception as e:
|
||||
log.error(f'Error creating Real-ESRGAN models list: {e}')
|
||||
return []
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
import os
|
||||
import math
|
||||
import queue
|
||||
import threading
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch.nn import functional as F
|
||||
from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn
|
||||
from modules.shared import log, console
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
class RealESRGANer():
|
||||
"""A helper class for upsampling images with RealESRGAN.
|
||||
|
||||
Args:
|
||||
scale (int): Upsampling scale factor used in the networks. It is usually 2 or 4.
|
||||
model_path (str): The path to the pretrained model. It can be urls (will first download it automatically).
|
||||
model (nn.Module): The defined network. Default: None.
|
||||
tile (int): As too large images result in the out of GPU memory issue, so this tile option will first crop
|
||||
input images into tiles, and then process each of them. Finally, they will be merged into one image.
|
||||
0 denotes for do not use tile. Default: 0.
|
||||
tile_pad (int): The pad size for each tile, to remove border artifacts. Default: 10.
|
||||
pre_pad (int): Pad the input images to avoid border artifacts. Default: 10.
|
||||
half (float): Whether to use half precision during inference. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
scale,
|
||||
model_path,
|
||||
dni_weight=None,
|
||||
model=None,
|
||||
tile=0,
|
||||
tile_pad=10,
|
||||
pre_pad=10,
|
||||
half=False,
|
||||
device=None,
|
||||
gpu_id=None):
|
||||
self.scale = scale
|
||||
self.tile_size = tile
|
||||
self.tile_pad = tile_pad
|
||||
self.pre_pad = pre_pad
|
||||
self.mod_scale = None
|
||||
self.half = half
|
||||
|
||||
# initialize model
|
||||
if gpu_id:
|
||||
self.device = torch.device(
|
||||
f'cuda:{gpu_id}' if torch.cuda.is_available() else 'cpu') if device is None else device
|
||||
else:
|
||||
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') if device is None else device
|
||||
|
||||
if isinstance(model_path, list):
|
||||
# dni
|
||||
assert len(model_path) == len(dni_weight), 'model_path and dni_weight should have the save length.'
|
||||
loadnet = self.dni(model_path[0], model_path[1], dni_weight)
|
||||
else:
|
||||
# if the model_path starts with https, it will first download models to the folder: weights
|
||||
if model_path.startswith('https://'):
|
||||
from modules.modelloader import load_file_from_url
|
||||
model_path = load_file_from_url(url=model_path, model_dir=os.path.join(ROOT_DIR, 'weights'), progress=True, file_name=None)
|
||||
loadnet = torch.load(model_path, map_location=torch.device('cpu'))
|
||||
|
||||
# prefer to use params_ema
|
||||
if 'params_ema' in loadnet:
|
||||
keyname = 'params_ema'
|
||||
else:
|
||||
keyname = 'params'
|
||||
model.load_state_dict(loadnet[keyname], strict=True)
|
||||
|
||||
model.eval()
|
||||
self.model = model.to(self.device)
|
||||
if self.half:
|
||||
self.model = self.model.half()
|
||||
|
||||
def dni(self, net_a, net_b, dni_weight, key='params', loc='cpu'):
|
||||
"""Deep network interpolation.
|
||||
|
||||
``Paper: Deep Network Interpolation for Continuous Imagery Effect Transition``
|
||||
"""
|
||||
net_a = torch.load(net_a, map_location=torch.device(loc))
|
||||
net_b = torch.load(net_b, map_location=torch.device(loc))
|
||||
for k, v_a in net_a[key].items():
|
||||
net_a[key][k] = dni_weight[0] * v_a + dni_weight[1] * net_b[key][k]
|
||||
return net_a
|
||||
|
||||
def pre_process(self, img):
|
||||
"""Pre-process, such as pre-pad and mod pad, so that the images can be divisible
|
||||
"""
|
||||
img = torch.from_numpy(np.transpose(img, (2, 0, 1))).float()
|
||||
self.img = img.unsqueeze(0).to(self.device)
|
||||
if self.half:
|
||||
self.img = self.img.half()
|
||||
|
||||
# pre_pad
|
||||
if self.pre_pad != 0:
|
||||
self.img = F.pad(self.img, (0, self.pre_pad, 0, self.pre_pad), 'reflect')
|
||||
# mod pad for divisible borders
|
||||
if self.scale == 2:
|
||||
self.mod_scale = 2
|
||||
elif self.scale == 1:
|
||||
self.mod_scale = 4
|
||||
if self.mod_scale is not None:
|
||||
self.mod_pad_h, self.mod_pad_w = 0, 0
|
||||
_, _, h, w = self.img.size()
|
||||
if (h % self.mod_scale != 0):
|
||||
self.mod_pad_h = (self.mod_scale - h % self.mod_scale)
|
||||
if (w % self.mod_scale != 0):
|
||||
self.mod_pad_w = (self.mod_scale - w % self.mod_scale)
|
||||
self.img = F.pad(self.img, (0, self.mod_pad_w, 0, self.mod_pad_h), 'reflect')
|
||||
|
||||
def process(self):
|
||||
# model inference
|
||||
self.output = self.model(self.img)
|
||||
|
||||
def tile_process(self):
|
||||
"""It will first crop input images to tiles, and then process each tile.
|
||||
Finally, all the processed tiles are merged into one images.
|
||||
|
||||
Modified from: https://github.com/ata4/esrgan-launcher
|
||||
"""
|
||||
batch, channel, height, width = self.img.shape
|
||||
output_height = height * self.scale
|
||||
output_width = width * self.scale
|
||||
output_shape = (batch, channel, output_height, output_width)
|
||||
|
||||
# start with black image
|
||||
self.output = self.img.new_zeros(output_shape)
|
||||
tiles_x = math.ceil(width / self.tile_size)
|
||||
tiles_y = math.ceil(height / self.tile_size)
|
||||
|
||||
# loop over all tiles
|
||||
with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn(), console=console) as progress:
|
||||
task = progress.add_task(description="Upscaling", total=tiles_y * tiles_x)
|
||||
with torch.no_grad():
|
||||
for y in range(tiles_y):
|
||||
for x in range(tiles_x):
|
||||
# extract tile from input image
|
||||
ofs_x = x * self.tile_size
|
||||
ofs_y = y * self.tile_size
|
||||
# input tile area on total image
|
||||
input_start_x = ofs_x
|
||||
input_end_x = min(ofs_x + self.tile_size, width)
|
||||
input_start_y = ofs_y
|
||||
input_end_y = min(ofs_y + self.tile_size, height)
|
||||
|
||||
# input tile area on total image with padding
|
||||
input_start_x_pad = max(input_start_x - self.tile_pad, 0)
|
||||
input_end_x_pad = min(input_end_x + self.tile_pad, width)
|
||||
input_start_y_pad = max(input_start_y - self.tile_pad, 0)
|
||||
input_end_y_pad = min(input_end_y + self.tile_pad, height)
|
||||
|
||||
# input tile dimensions
|
||||
input_tile_width = input_end_x - input_start_x
|
||||
input_tile_height = input_end_y - input_start_y
|
||||
tile_idx = y * tiles_x + x + 1
|
||||
input_tile = self.img[:, :, input_start_y_pad:input_end_y_pad, input_start_x_pad:input_end_x_pad]
|
||||
|
||||
# upscale tile
|
||||
try:
|
||||
output_tile = self.model(input_tile)
|
||||
except Exception as e:
|
||||
log.error(f'Upscale error: type=R-ESRGAN {e}')
|
||||
|
||||
# output tile area on total image
|
||||
output_start_x = input_start_x * self.scale
|
||||
output_end_x = input_end_x * self.scale
|
||||
output_start_y = input_start_y * self.scale
|
||||
output_end_y = input_end_y * self.scale
|
||||
|
||||
# output tile area without padding
|
||||
output_start_x_tile = (input_start_x - input_start_x_pad) * self.scale
|
||||
output_end_x_tile = output_start_x_tile + input_tile_width * self.scale
|
||||
output_start_y_tile = (input_start_y - input_start_y_pad) * self.scale
|
||||
output_end_y_tile = output_start_y_tile + input_tile_height * self.scale
|
||||
|
||||
# put tile into output image
|
||||
self.output[:, :, output_start_y:output_end_y,
|
||||
output_start_x:output_end_x] = output_tile[:, :, output_start_y_tile:output_end_y_tile,
|
||||
output_start_x_tile:output_end_x_tile]
|
||||
progress.update(task, advance=1, description="Upscaling")
|
||||
|
||||
def post_process(self):
|
||||
# remove extra pad
|
||||
if self.mod_scale is not None:
|
||||
_, _, h, w = self.output.size()
|
||||
self.output = self.output[:, :, 0:h - self.mod_pad_h * self.scale, 0:w - self.mod_pad_w * self.scale]
|
||||
# remove prepad
|
||||
if self.pre_pad != 0:
|
||||
_, _, h, w = self.output.size()
|
||||
self.output = self.output[:, :, 0:h - self.pre_pad * self.scale, 0:w - self.pre_pad * self.scale]
|
||||
return self.output
|
||||
|
||||
@torch.no_grad()
|
||||
def enhance(self, img, outscale=None, alpha_upsampler='realesrgan'):
|
||||
h_input, w_input = img.shape[0:2]
|
||||
# img: numpy
|
||||
img = img.astype(np.float32)
|
||||
if np.max(img) > 256: # 16-bit image
|
||||
max_range = 65535
|
||||
else:
|
||||
max_range = 255
|
||||
img = img / max_range
|
||||
if len(img.shape) == 2: # gray image
|
||||
img_mode = 'L'
|
||||
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
|
||||
elif img.shape[2] == 4: # RGBA image with alpha channel
|
||||
img_mode = 'RGBA'
|
||||
alpha = img[:, :, 3]
|
||||
img = img[:, :, 0:3]
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
if alpha_upsampler == 'realesrgan':
|
||||
alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2RGB)
|
||||
else:
|
||||
img_mode = 'RGB'
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# ------------------- process image (without the alpha channel) ------------------- #
|
||||
self.pre_process(img)
|
||||
if self.tile_size > 0:
|
||||
self.tile_process()
|
||||
else:
|
||||
self.process()
|
||||
output_img = self.post_process()
|
||||
output_img = output_img.data.squeeze().float().cpu().clamp_(0, 1).numpy()
|
||||
output_img = np.transpose(output_img[[2, 1, 0], :, :], (1, 2, 0))
|
||||
if img_mode == 'L':
|
||||
output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# ------------------- process the alpha channel if necessary ------------------- #
|
||||
if img_mode == 'RGBA':
|
||||
if alpha_upsampler == 'realesrgan':
|
||||
self.pre_process(alpha)
|
||||
if self.tile_size > 0:
|
||||
self.tile_process()
|
||||
else:
|
||||
self.process()
|
||||
output_alpha = self.post_process()
|
||||
output_alpha = output_alpha.data.squeeze().float().cpu().clamp_(0, 1).numpy()
|
||||
output_alpha = np.transpose(output_alpha[[2, 1, 0], :, :], (1, 2, 0))
|
||||
output_alpha = cv2.cvtColor(output_alpha, cv2.COLOR_BGR2GRAY)
|
||||
else: # use the cv2 resize for alpha channel
|
||||
h, w = alpha.shape[0:2]
|
||||
output_alpha = cv2.resize(alpha, (w * self.scale, h * self.scale), interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
# merge the alpha channel
|
||||
output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2BGRA)
|
||||
output_img[:, :, 3] = output_alpha
|
||||
|
||||
# ------------------------------ return ------------------------------ #
|
||||
if max_range == 65535: # 16-bit image
|
||||
output = (output_img * 65535.0).round().astype(np.uint16)
|
||||
else:
|
||||
output = (output_img * 255.0).round().astype(np.uint8)
|
||||
|
||||
if outscale is not None and outscale != float(self.scale):
|
||||
output = cv2.resize(
|
||||
output, (
|
||||
int(w_input * outscale),
|
||||
int(h_input * outscale),
|
||||
), interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
return output, img_mode
|
||||
|
||||
|
||||
class PrefetchReader(threading.Thread):
|
||||
"""Prefetch images.
|
||||
|
||||
Args:
|
||||
img_list (list[str]): A image list of image paths to be read.
|
||||
num_prefetch_queue (int): Number of prefetch queue.
|
||||
"""
|
||||
|
||||
def __init__(self, img_list, num_prefetch_queue):
|
||||
super().__init__()
|
||||
self.que = queue.Queue(num_prefetch_queue)
|
||||
self.img_list = img_list
|
||||
|
||||
def run(self):
|
||||
for img_path in self.img_list:
|
||||
img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
|
||||
self.que.put(img)
|
||||
|
||||
self.que.put(None)
|
||||
|
||||
def __next__(self):
|
||||
next_item = self.que.get()
|
||||
if next_item is None:
|
||||
raise StopIteration
|
||||
return next_item
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
|
||||
class IOConsumer(threading.Thread):
|
||||
|
||||
def __init__(self, opt, que, qid):
|
||||
super().__init__()
|
||||
self._queue = que
|
||||
self.qid = qid
|
||||
self.opt = opt
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
msg = self._queue.get()
|
||||
if isinstance(msg, str) and msg == 'quit':
|
||||
break
|
||||
|
||||
output = msg['output']
|
||||
save_path = msg['save_path']
|
||||
cv2.imwrite(save_path, output)
|
||||
|
||||
from basicsr.utils.registry import ARCH_REGISTRY
|
||||
from torch import nn as nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
|
||||
class SRVGGNetCompact(nn.Module):
|
||||
"""A compact VGG-style network structure for super-resolution.
|
||||
|
||||
It is a compact network structure, which performs upsampling in the last layer and no convolution is
|
||||
conducted on the HR feature space.
|
||||
|
||||
Args:
|
||||
num_in_ch (int): Channel number of inputs. Default: 3.
|
||||
num_out_ch (int): Channel number of outputs. Default: 3.
|
||||
num_feat (int): Channel number of intermediate features. Default: 64.
|
||||
num_conv (int): Number of convolution layers in the body network. Default: 16.
|
||||
upscale (int): Upsampling factor. Default: 4.
|
||||
act_type (str): Activation type, options: 'relu', 'prelu', 'leakyrelu'. Default: prelu.
|
||||
"""
|
||||
|
||||
def __init__(self, num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=16, upscale=4, act_type='prelu'):
|
||||
super(SRVGGNetCompact, self).__init__()
|
||||
self.num_in_ch = num_in_ch
|
||||
self.num_out_ch = num_out_ch
|
||||
self.num_feat = num_feat
|
||||
self.num_conv = num_conv
|
||||
self.upscale = upscale
|
||||
self.act_type = act_type
|
||||
|
||||
self.body = nn.ModuleList()
|
||||
# the first conv
|
||||
self.body.append(nn.Conv2d(num_in_ch, num_feat, 3, 1, 1))
|
||||
# the first activation
|
||||
if act_type == 'relu':
|
||||
activation = nn.ReLU(inplace=True)
|
||||
elif act_type == 'prelu':
|
||||
activation = nn.PReLU(num_parameters=num_feat)
|
||||
elif act_type == 'leakyrelu':
|
||||
activation = nn.LeakyReLU(negative_slope=0.1, inplace=True)
|
||||
self.body.append(activation)
|
||||
|
||||
# the body structure
|
||||
for _ in range(num_conv):
|
||||
self.body.append(nn.Conv2d(num_feat, num_feat, 3, 1, 1))
|
||||
# activation
|
||||
if act_type == 'relu':
|
||||
activation = nn.ReLU(inplace=True)
|
||||
elif act_type == 'prelu':
|
||||
activation = nn.PReLU(num_parameters=num_feat)
|
||||
elif act_type == 'leakyrelu':
|
||||
activation = nn.LeakyReLU(negative_slope=0.1, inplace=True)
|
||||
self.body.append(activation)
|
||||
|
||||
# the last conv
|
||||
self.body.append(nn.Conv2d(num_feat, num_out_ch * upscale * upscale, 3, 1, 1))
|
||||
# upsample
|
||||
self.upsampler = nn.PixelShuffle(upscale)
|
||||
|
||||
def forward(self, x):
|
||||
out = x
|
||||
for i in range(0, len(self.body)):
|
||||
out = self.body[i](out)
|
||||
|
||||
out = self.upsampler(out)
|
||||
# add the nearest upsampled image, so that the network learns the residual
|
||||
base = F.interpolate(x, scale_factor=self.upscale, mode='nearest')
|
||||
out += base
|
||||
return out
|
||||
|
||||
+26
-1
@@ -67,6 +67,20 @@ class Script:
|
||||
"""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def setup(self, p, *args):
|
||||
"""For AlwaysVisible scripts, this function is called when the processing object is set up, before any processing starts.
|
||||
args contains all values returned by components from ui().
|
||||
"""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def before_process(self, p, *args):
|
||||
"""
|
||||
This function is called very early during processing begins for AlwaysVisible scripts.
|
||||
You can modify the processing object (p) here, inject hooks, etc.
|
||||
args contains all values returned by components from ui()
|
||||
"""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def process(self, p, *args):
|
||||
"""
|
||||
This function is called before processing begins for AlwaysVisible scripts.
|
||||
@@ -398,7 +412,7 @@ class ScriptRunner:
|
||||
|
||||
dropdown.init_field = init_field
|
||||
dropdown.change(fn=select_script, inputs=[dropdown], outputs=[script.group for script in self.selectable_scripts])
|
||||
|
||||
|
||||
def onload_script_visibility(params):
|
||||
title = params.get('Script', None)
|
||||
if title:
|
||||
@@ -437,6 +451,17 @@ class ScriptRunner:
|
||||
s.report()
|
||||
return processed
|
||||
|
||||
def before_process(self, p, **kwargs):
|
||||
s = ScriptSummary('before-process')
|
||||
for script in self.alwayson_scripts:
|
||||
try:
|
||||
script_args = p.script_args[script.args_from:script.args_to]
|
||||
script.before_process(p, *script_args, **kwargs)
|
||||
except Exception as e:
|
||||
errors.display(e, f"Error running before process: {script.filename}")
|
||||
s.record(script.title())
|
||||
s.report()
|
||||
|
||||
def process(self, p, **kwargs):
|
||||
s = ScriptSummary('process')
|
||||
for script in self.alwayson_scripts:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import os
|
||||
import gradio as gr
|
||||
|
||||
from modules import errors, shared
|
||||
|
||||
|
||||
@@ -15,15 +14,9 @@ class ScriptPostprocessing:
|
||||
controls = None
|
||||
args_from = None
|
||||
args_to = None
|
||||
|
||||
order = 1000
|
||||
"""scripts will be ordred by this value in postprocessing UI"""
|
||||
|
||||
name = None
|
||||
"""this function should return the title of the script."""
|
||||
|
||||
group = None
|
||||
"""A gr.Group component that has all script's UI inside it"""
|
||||
order = 1000 # scripts will be ordred by this value in postprocessing UI
|
||||
name = None # this function should return the title of the script
|
||||
group = None # A gr.Group component that has all script's UI inside it
|
||||
|
||||
def ui(self):
|
||||
"""
|
||||
@@ -61,25 +54,19 @@ class ScriptPostprocessingRunner:
|
||||
|
||||
def initialize_scripts(self, scripts_data):
|
||||
self.scripts = []
|
||||
|
||||
for script_class, path, _basedir, _script_module in scripts_data:
|
||||
script: ScriptPostprocessing = script_class()
|
||||
script.filename = path
|
||||
|
||||
if script.name == "Simple Upscale":
|
||||
continue
|
||||
|
||||
self.scripts.append(script)
|
||||
|
||||
def create_script_ui(self, script, inputs):
|
||||
script.args_from = len(inputs)
|
||||
script.args_to = len(inputs)
|
||||
|
||||
script.controls = wrap_call(script.ui, script.filename, "ui")
|
||||
|
||||
for control in script.controls.values():
|
||||
control.custom_script_source = os.path.basename(script.filename)
|
||||
|
||||
inputs += list(script.controls.values())
|
||||
script.args_to = len(inputs)
|
||||
|
||||
@@ -87,56 +74,46 @@ class ScriptPostprocessingRunner:
|
||||
if self.scripts is None:
|
||||
import modules.scripts
|
||||
self.initialize_scripts(modules.scripts.postprocessing_scripts_data)
|
||||
|
||||
scripts_order = shared.opts.postprocessing_operation_order
|
||||
|
||||
def script_score(name):
|
||||
for i, possible_match in enumerate(scripts_order):
|
||||
if possible_match == name:
|
||||
return i
|
||||
|
||||
return len(self.scripts)
|
||||
|
||||
script_scores = {script.name: (script_score(script.name), script.order, script.name, original_index) for original_index, script in enumerate(self.scripts)}
|
||||
|
||||
return sorted(self.scripts, key=lambda x: script_scores[x.name])
|
||||
|
||||
def setup_ui(self):
|
||||
inputs = []
|
||||
|
||||
for script in self.scripts_in_preferred_order():
|
||||
with gr.Row() as group:
|
||||
self.create_script_ui(script, inputs)
|
||||
|
||||
script.group = group
|
||||
|
||||
self.ui_created = True
|
||||
return inputs
|
||||
|
||||
def run(self, pp: PostprocessedImage, args):
|
||||
for script in self.scripts_in_preferred_order():
|
||||
shared.state.job = script.name
|
||||
|
||||
script_args = args[script.args_from:script.args_to]
|
||||
|
||||
process_args = {}
|
||||
for (name, _component), value in zip(script.controls.items(), script_args):
|
||||
process_args[name] = value
|
||||
|
||||
shared.log.debug(f'postprocess: script={script.name} args={process_args}')
|
||||
script.process(pp, **process_args)
|
||||
|
||||
def create_args_for_run(self, scripts_args):
|
||||
if not self.ui_created:
|
||||
with gr.Blocks(analytics_enabled=False):
|
||||
self.setup_ui()
|
||||
|
||||
scripts = self.scripts_in_preferred_order()
|
||||
args = [None] * max([x.args_to for x in scripts])
|
||||
|
||||
for script in scripts:
|
||||
script_args_dict = scripts_args.get(script.name, None)
|
||||
if script_args_dict is not None:
|
||||
|
||||
for i, name in enumerate(script.controls):
|
||||
args[script.args_from + i] = script_args_dict.get(name, None)
|
||||
|
||||
|
||||
+81
-60
@@ -118,10 +118,13 @@ class CheckpointInfo:
|
||||
#Used by OpenVINO, can be used with TensorRT or Olive
|
||||
class CompiledModelState:
|
||||
def __init__(self):
|
||||
self.first_pass = True
|
||||
self.height = 512
|
||||
self.width = 512
|
||||
self.batch_size = 1
|
||||
self.first_pass = True
|
||||
self.partition_id = 0
|
||||
self.cn_model = []
|
||||
self.lora_model = []
|
||||
|
||||
|
||||
class NoWatermark:
|
||||
@@ -146,6 +149,7 @@ def checkpoint_tiles(use_short=False): # pylint: disable=unused-argument
|
||||
|
||||
def list_models():
|
||||
t0 = time.time()
|
||||
global checkpoints_list # pylint: disable=global-statement
|
||||
checkpoints_list.clear()
|
||||
checkpoint_aliases.clear()
|
||||
if shared.opts.sd_disable_ckpt or shared.backend == shared.Backend.DIFFUSERS:
|
||||
@@ -172,6 +176,7 @@ def list_models():
|
||||
shared.log.warning(f"Checkpoint not found: {shared.cmd_opts.ckpt}")
|
||||
shared.log.info(f'Available models: {shared.opts.ckpt_dir} items={len(checkpoints_list)} time={time.time()-t0:.2f}s')
|
||||
|
||||
checkpoints_list = dict(sorted(checkpoints_list.items(), key=lambda cp: cp[1].filename))
|
||||
if len(checkpoints_list) == 0:
|
||||
if not shared.cmd_opts.no_download:
|
||||
key = input('Download the default model? (y/N) ')
|
||||
@@ -190,6 +195,7 @@ def list_models():
|
||||
if checkpoint_info.name is not None:
|
||||
checkpoint_info.register()
|
||||
|
||||
|
||||
def update_model_hashes():
|
||||
txt = []
|
||||
lst = [ckpt for ckpt in checkpoints_list.values() if ckpt.hash is None]
|
||||
@@ -586,8 +592,12 @@ model_data = ModelData()
|
||||
|
||||
|
||||
def change_backend():
|
||||
shared.log.info(f'Pipeline changed: {shared.backend}')
|
||||
unload_model_weights()
|
||||
shared.log.info(f'Backend changed: {shared.backend}')
|
||||
if shared.backend == shared.Backend.ORIGINAL:
|
||||
change_from = shared.Backend.DIFFUSERS
|
||||
else:
|
||||
change_from = shared.Backend.ORIGINAL
|
||||
unload_model_weights(change_from=change_from)
|
||||
checkpoints_loaded.clear()
|
||||
from modules.sd_samplers import list_samplers
|
||||
list_samplers(shared.backend)
|
||||
@@ -654,6 +664,49 @@ def detect_pipeline(f: str, op: str = 'model'):
|
||||
pipeline = None, None
|
||||
return pipeline, guess
|
||||
|
||||
def compile_diffusers(sd_model):
|
||||
try:
|
||||
if shared.opts.ipex_optimize:
|
||||
import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import
|
||||
sd_model.unet.training = False
|
||||
sd_model.unet = ipex.optimize(sd_model.unet, dtype=devices.dtype_unet, inplace=True, weights_prepack=False) # pylint: disable=attribute-defined-outside-init
|
||||
if hasattr(sd_model, 'vae'):
|
||||
sd_model.vae.training = False
|
||||
sd_model.vae = ipex.optimize(sd_model.vae, dtype=devices.dtype_vae, inplace=True, weights_prepack=False) # pylint: disable=attribute-defined-outside-init
|
||||
if hasattr(sd_model, 'movq'):
|
||||
sd_model.movq.training = False
|
||||
sd_model.movq = ipex.optimize(sd_model.movq, dtype=devices.dtype_vae, inplace=True, weights_prepack=False) # pylint: disable=attribute-defined-outside-init
|
||||
shared.log.info("Applied IPEX Optimize.")
|
||||
except Exception as err:
|
||||
shared.log.warning(f"IPEX Optimize not supported: {err}")
|
||||
|
||||
try:
|
||||
if shared.opts.cuda_compile and shared.opts.cuda_compile_backend != 'none':
|
||||
shared.log.info(f"Compiling pipeline={sd_model.__class__.__name__} shape={8 * sd_model.unet.config.sample_size} mode={shared.opts.cuda_compile_backend}")
|
||||
import torch._dynamo # pylint: disable=unused-import,redefined-outer-name
|
||||
if shared.opts.cuda_compile_backend == "openvino_fx":
|
||||
torch._dynamo.reset() # pylint: disable=protected-access
|
||||
from modules.intel.openvino import openvino_fx, openvino_clear_caches # pylint: disable=unused-import
|
||||
openvino_clear_caches()
|
||||
torch._dynamo.eval_frame.check_if_dynamo_supported = lambda: True # pylint: disable=protected-access
|
||||
if shared.compiled_model_state is None:
|
||||
shared.compiled_model_state = CompiledModelState()
|
||||
shared.compiled_model_state.first_pass = True if not shared.opts.cuda_compile_precompile else False
|
||||
log_level = logging.WARNING if shared.opts.cuda_compile_verbose else logging.CRITICAL # pylint: disable=protected-access
|
||||
if hasattr(torch, '_logging'):
|
||||
torch._logging.set_logs(dynamo=log_level, aot=log_level, inductor=log_level) # pylint: disable=protected-access
|
||||
torch._dynamo.config.verbose = shared.opts.cuda_compile_verbose # pylint: disable=protected-access
|
||||
torch._dynamo.config.suppress_errors = shared.opts.cuda_compile_errors # pylint: disable=protected-access
|
||||
sd_model.unet = torch.compile(sd_model.unet, mode=shared.opts.cuda_compile_mode, backend=shared.opts.cuda_compile_backend, fullgraph=shared.opts.cuda_compile_fullgraph) # pylint: disable=attribute-defined-outside-init
|
||||
if hasattr(sd_model, 'vae'):
|
||||
sd_model.vae.decode = torch.compile(sd_model.vae.decode, mode=shared.opts.cuda_compile_mode, backend=shared.opts.cuda_compile_backend, fullgraph=shared.opts.cuda_compile_fullgraph) # pylint: disable=attribute-defined-outside-init
|
||||
if hasattr(sd_model, 'movq'):
|
||||
sd_model.movq.decode = torch.compile(sd_model.movq.decode, mode=shared.opts.cuda_compile_mode, backend=shared.opts.cuda_compile_backend, fullgraph=shared.opts.cuda_compile_fullgraph) # pylint: disable=attribute-defined-outside-init
|
||||
if shared.opts.cuda_compile_precompile:
|
||||
sd_model("dummy prompt")
|
||||
shared.log.info("Complilation done.")
|
||||
except Exception as err:
|
||||
shared.log.warning(f"Model compile not supported: {err}")
|
||||
|
||||
def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=None, op='model'): # pylint: disable=unused-argument
|
||||
import torch # pylint: disable=reimported,redefined-outer-name
|
||||
@@ -733,7 +786,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
|
||||
shared.log.error(f'Failed loading {op}: {checkpoint_info.path} {e}')
|
||||
return
|
||||
else:
|
||||
diffusers_load_config["local_files_only "] = True
|
||||
diffusers_load_config["local_files_only"] = True
|
||||
diffusers_load_config["extract_ema"] = shared.opts.diffusers_extract_ema
|
||||
pipeline, model_type = detect_pipeline(checkpoint_info.path, op)
|
||||
if pipeline is None:
|
||||
@@ -762,7 +815,9 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
|
||||
diffusers_load_config.pop('safety_checker', None)
|
||||
diffusers_load_config.pop('requires_safety_checker', None)
|
||||
diffusers_load_config.pop('load_safety_checker', None)
|
||||
shared.log.debug(f'Model {op}: pipeline={sd_model.__class__.__name__} config={diffusers_load_config}') # pylint: disable=protected-access
|
||||
diffusers_load_config.pop('config_files', None)
|
||||
diffusers_load_config.pop('local_files_only', None)
|
||||
shared.log.debug(f'Setting {op}: pipeline={sd_model.__class__.__name__} config={diffusers_load_config}') # pylint: disable=protected-access
|
||||
except Exception as e:
|
||||
shared.log.error(f'Diffusers failed loading model using pipeline: {checkpoint_info.path} {shared.opts.diffusers_pipeline} {e}')
|
||||
return
|
||||
@@ -773,8 +828,8 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
|
||||
sd_model.scheduler.name = 'DDIM'
|
||||
|
||||
if (shared.opts.diffusers_model_cpu_offload or shared.cmd_opts.medvram) and (shared.opts.diffusers_seq_cpu_offload or shared.cmd_opts.lowvram):
|
||||
shared.log.warning(f'Model {op}: Model CPU offload (--medvram) and Sequential CPU offload (--lowvram) are not compatible')
|
||||
shared.log.debug(f'Model {op}: disabling model CPU offload and --medvram')
|
||||
shared.log.warning(f'Setting {op}: Model CPU offload and Sequential CPU offload are not compatible')
|
||||
shared.log.debug(f'Setting {op}: disabling model CPU offload')
|
||||
shared.opts.diffusers_model_cpu_offload=False
|
||||
shared.cmd_opts.medvram=False
|
||||
|
||||
@@ -783,7 +838,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
|
||||
sd_model.has_accelerate = False
|
||||
if hasattr(sd_model, "enable_model_cpu_offload"):
|
||||
if (shared.cmd_opts.medvram and devices.backend != "directml") or shared.opts.diffusers_model_cpu_offload:
|
||||
shared.log.debug(f'Model {op}: enable model CPU offload')
|
||||
shared.log.debug(f'Setting {op}: enable model CPU offload')
|
||||
if shared.opts.diffusers_move_base or shared.opts.diffusers_move_unet or shared.opts.diffusers_move_refiner:
|
||||
shared.opts.diffusers_move_base = False
|
||||
shared.opts.diffusers_move_unet = False
|
||||
@@ -793,7 +848,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
|
||||
sd_model.has_accelerate = True
|
||||
if hasattr(sd_model, "enable_sequential_cpu_offload"):
|
||||
if shared.cmd_opts.lowvram or shared.opts.diffusers_seq_cpu_offload:
|
||||
shared.log.debug(f'Model {op}: enable sequential CPU offload')
|
||||
shared.log.debug(f'Setting {op}: enable sequential CPU offload')
|
||||
if shared.opts.diffusers_move_base or shared.opts.diffusers_move_unet or shared.opts.diffusers_move_refiner:
|
||||
shared.opts.diffusers_move_base = False
|
||||
shared.opts.diffusers_move_unet = False
|
||||
@@ -803,19 +858,19 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
|
||||
sd_model.has_accelerate = True
|
||||
if hasattr(sd_model, "enable_vae_slicing"):
|
||||
if shared.cmd_opts.lowvram or shared.opts.diffusers_vae_slicing:
|
||||
shared.log.debug(f'Model {op}: enable VAE slicing')
|
||||
shared.log.debug(f'Setting {op}: enable VAE slicing')
|
||||
sd_model.enable_vae_slicing()
|
||||
else:
|
||||
sd_model.disable_vae_slicing()
|
||||
if hasattr(sd_model, "enable_vae_tiling"):
|
||||
if shared.cmd_opts.lowvram or shared.opts.diffusers_vae_tiling:
|
||||
shared.log.debug(f'Model {op}: enable VAE tiling')
|
||||
shared.log.debug(f'Setting {op}: enable VAE tiling')
|
||||
sd_model.enable_vae_tiling()
|
||||
else:
|
||||
sd_model.disable_vae_tiling()
|
||||
if hasattr(sd_model, "enable_attention_slicing"):
|
||||
if shared.cmd_opts.lowvram or shared.opts.diffusers_attention_slicing:
|
||||
shared.log.debug(f'Model {op}: enable attention slicing')
|
||||
shared.log.debug(f'Setting {op}: enable attention slicing')
|
||||
sd_model.enable_attention_slicing()
|
||||
else:
|
||||
sd_model.disable_attention_slicing()
|
||||
@@ -824,19 +879,19 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
|
||||
sd_model.vae = vae
|
||||
if shared.opts.diffusers_vae_upcast != 'default':
|
||||
if shared.opts.diffusers_vae_upcast == 'true':
|
||||
sd_model.vae.config["force_upcast"] = True
|
||||
# sd_model.vae.config["force_upcast"] = True
|
||||
sd_model.vae.config.force_upcast = True
|
||||
else:
|
||||
sd_model.vae.config["force_upcast"] = False
|
||||
# sd_model.vae.config["force_upcast"] = False
|
||||
sd_model.vae.config.force_upcast = False
|
||||
if shared.opts.no_half_vae:
|
||||
devices.dtype_vae = torch.float32
|
||||
sd_model.vae.to(devices.dtype_vae)
|
||||
shared.log.debug(f'Model {op} VAE: name={sd_vae.loaded_vae_file} upcast={sd_model.vae.config.get("force_upcast", None)}')
|
||||
shared.log.debug(f'Setting {op} VAE: name={sd_vae.loaded_vae_file} upcast={sd_model.vae.config.get("force_upcast", None)}')
|
||||
if shared.opts.cross_attention_optimization == "xFormers" and hasattr(sd_model, 'enable_xformers_memory_efficient_attention'):
|
||||
sd_model.enable_xformers_memory_efficient_attention()
|
||||
if shared.opts.opt_channelslast:
|
||||
shared.log.debug(f'Model {op}: enable channels last')
|
||||
shared.log.debug(f'Setting {op}: enable channels last')
|
||||
sd_model.unet.to(memory_format=torch.channels_last)
|
||||
|
||||
base_sent_to_cpu=False
|
||||
@@ -864,45 +919,8 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
|
||||
base_sent_to_cpu=True
|
||||
elif not sd_model.has_accelerate:
|
||||
sd_model.to(devices.device)
|
||||
try:
|
||||
if shared.opts.ipex_optimize:
|
||||
sd_model.unet.training = False
|
||||
sd_model.unet = torch.xpu.optimize(sd_model.unet, dtype=devices.dtype_unet, inplace=True, weights_prepack=False) # pylint: disable=attribute-defined-outside-init
|
||||
if hasattr(sd_model, 'vae'):
|
||||
sd_model.vae.training = False
|
||||
sd_model.vae = torch.xpu.optimize(sd_model.vae, dtype=devices.dtype_vae, inplace=True, weights_prepack=False) # pylint: disable=attribute-defined-outside-init
|
||||
if hasattr(sd_model, 'movq'):
|
||||
sd_model.movq.training = False
|
||||
sd_model.movq = torch.xpu.optimize(sd_model.movq, dtype=devices.dtype_vae, inplace=True, weights_prepack=False) # pylint: disable=attribute-defined-outside-init
|
||||
shared.log.info("Applied IPEX Optimize.")
|
||||
except Exception as err:
|
||||
shared.log.warning(f"IPEX Optimize not supported: {err}")
|
||||
try:
|
||||
if shared.opts.cuda_compile and shared.opts.cuda_compile_backend != 'none':
|
||||
shared.log.info(f"Compiling pipeline={sd_model.__class__.__name__} shape={8 * sd_model.unet.config.sample_size} mode={shared.opts.cuda_compile_backend}")
|
||||
import torch._dynamo # pylint: disable=unused-import,redefined-outer-name
|
||||
if shared.opts.cuda_compile_backend == "openvino_fx":
|
||||
torch._dynamo.reset() # pylint: disable=protected-access
|
||||
from modules.intel.openvino import openvino_fx, openvino_clear_caches # pylint: disable=unused-import
|
||||
openvino_clear_caches()
|
||||
torch._dynamo.eval_frame.check_if_dynamo_supported = lambda: True # pylint: disable=protected-access
|
||||
sd_model.compiled_model_state = CompiledModelState()
|
||||
sd_model.compiled_model_state.first_pass = True if not shared.opts.cuda_compile_precompile else False
|
||||
log_level = logging.WARNING if shared.opts.cuda_compile_verbose else logging.CRITICAL # pylint: disable=protected-access
|
||||
if hasattr(torch, '_logging'):
|
||||
torch._logging.set_logs(dynamo=log_level, aot=log_level, inductor=log_level) # pylint: disable=protected-access
|
||||
torch._dynamo.config.verbose = shared.opts.cuda_compile_verbose # pylint: disable=protected-access
|
||||
torch._dynamo.config.suppress_errors = shared.opts.cuda_compile_errors # pylint: disable=protected-access
|
||||
sd_model.unet = torch.compile(sd_model.unet, mode=shared.opts.cuda_compile_mode, backend=shared.opts.cuda_compile_backend, fullgraph=shared.opts.cuda_compile_fullgraph) # pylint: disable=attribute-defined-outside-init
|
||||
if hasattr(sd_model, 'vae'):
|
||||
sd_model.vae.decode = torch.compile(sd_model.vae.decode, mode=shared.opts.cuda_compile_mode, backend=shared.opts.cuda_compile_backend, fullgraph=shared.opts.cuda_compile_fullgraph) # pylint: disable=attribute-defined-outside-init
|
||||
if hasattr(sd_model, 'movq'):
|
||||
sd_model.movq.decode = torch.compile(sd_model.movq.decode, mode=shared.opts.cuda_compile_mode, backend=shared.opts.cuda_compile_backend, fullgraph=shared.opts.cuda_compile_fullgraph) # pylint: disable=attribute-defined-outside-init
|
||||
if shared.opts.cuda_compile_precompile:
|
||||
sd_model("dummy prompt")
|
||||
shared.log.info("Complilation done.")
|
||||
except Exception as err:
|
||||
shared.log.warning(f"Model compile not supported: {err}")
|
||||
|
||||
compile_diffusers(sd_model)
|
||||
|
||||
if sd_model is None:
|
||||
shared.log.error('Diffuser model not loaded')
|
||||
@@ -1162,11 +1180,11 @@ def disable_offload(sd_model):
|
||||
remove_hook_from_module(model, recurse=True)
|
||||
|
||||
|
||||
def unload_model_weights(op='model'):
|
||||
from modules import sd_hijack
|
||||
def unload_model_weights(op='model', change_from='none'):
|
||||
if op == 'model' or op == 'dict':
|
||||
if model_data.sd_model:
|
||||
if shared.backend == shared.Backend.ORIGINAL:
|
||||
if (shared.backend == shared.Backend.ORIGINAL and change_from != shared.Backend.DIFFUSERS) or change_from == shared.Backend.ORIGINAL:
|
||||
from modules import sd_hijack
|
||||
model_data.sd_model.to(devices.cpu)
|
||||
sd_hijack.model_hijack.undo_hijack(model_data.sd_model)
|
||||
else:
|
||||
@@ -1176,7 +1194,8 @@ def unload_model_weights(op='model'):
|
||||
shared.log.debug(f'Unload weights {op}: {memory_stats()}')
|
||||
else:
|
||||
if model_data.sd_refiner:
|
||||
if shared.backend == shared.Backend.ORIGINAL:
|
||||
if (shared.backend == shared.Backend.ORIGINAL and change_from != shared.Backend.DIFFUSERS) or change_from == shared.Backend.ORIGINAL:
|
||||
from modules import sd_hijack
|
||||
model_data.sd_model.to(devices.cpu)
|
||||
sd_hijack.model_hijack.undo_hijack(model_data.sd_refiner)
|
||||
else:
|
||||
@@ -1208,5 +1227,7 @@ def apply_token_merging(sd_model, token_merging_ratio=0):
|
||||
)
|
||||
shared.log.debug(f'Applying token merging: ratio={token_merging_ratio}')
|
||||
sd_model.applied_token_merged_ratio = token_merging_ratio
|
||||
except:
|
||||
except Exception:
|
||||
shared.log.warning(f'Token merging not supported: pipeline={sd_model.__class__.__name__}')
|
||||
else:
|
||||
sd_model.applied_token_merged_ratio = 0
|
||||
|
||||
@@ -45,7 +45,6 @@ class VanillaStableDiffusionSampler:
|
||||
def launch_sampling(self, steps, func):
|
||||
state.sampling_steps = steps
|
||||
state.sampling_step = 0
|
||||
|
||||
try:
|
||||
return func()
|
||||
except sd_samplers_common.InterruptedException:
|
||||
@@ -53,11 +52,8 @@ class VanillaStableDiffusionSampler:
|
||||
|
||||
def p_sample_ddim_hook(self, x_dec, cond, ts, unconditional_conditioning, *args, **kwargs):
|
||||
x_dec, ts, cond, unconditional_conditioning = self.before_sample(x_dec, ts, cond, unconditional_conditioning)
|
||||
|
||||
res = self.orig_p_sample_ddim(x_dec, cond, ts, *args, unconditional_conditioning=unconditional_conditioning, **kwargs)
|
||||
|
||||
x_dec, ts, cond, unconditional_conditioning, res = self.after_sample(x_dec, ts, cond, unconditional_conditioning, res)
|
||||
|
||||
return res
|
||||
|
||||
def before_sample(self, x, ts, cond, unconditional_conditioning):
|
||||
|
||||
@@ -8,6 +8,7 @@ try:
|
||||
DEISMultistepScheduler,
|
||||
DPMSolverMultistepScheduler,
|
||||
DPMSolverSinglestepScheduler,
|
||||
DPMSolverSDEScheduler,
|
||||
EulerAncestralDiscreteScheduler,
|
||||
EulerDiscreteScheduler,
|
||||
HeunDiscreteScheduler,
|
||||
@@ -30,6 +31,7 @@ config = {
|
||||
'DEIS': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "deis", 'solver_type': "logrho", 'lower_order_final': True },
|
||||
'DPM 1S': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False },
|
||||
'DPM 2M': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False },
|
||||
'DPM SDE': { 'use_karras_sigmas': False },
|
||||
'Euler a': { },
|
||||
'Euler': { 'interpolation_type': "linear", 'use_karras_sigmas': False },
|
||||
'Heun': { 'use_karras_sigmas': False },
|
||||
@@ -52,6 +54,7 @@ samplers_data_diffusers = [
|
||||
sd_samplers_common.SamplerData('KDPM2 a', lambda model: DiffusionSampler('KDPM2 a', KDPM2AncestralDiscreteScheduler, model), [], {}),
|
||||
sd_samplers_common.SamplerData('DPM 1S', lambda model: DiffusionSampler('DPM++ 1S', DPMSolverSinglestepScheduler, model), [], {}),
|
||||
sd_samplers_common.SamplerData('DPM 2M', lambda model: DiffusionSampler('DPM++ 2M', DPMSolverMultistepScheduler, model), [], {}),
|
||||
sd_samplers_common.SamplerData('DPM SDE', lambda model: DiffusionSampler('DPM SDE', DPMSolverSDEScheduler, model), [], {}),
|
||||
sd_samplers_common.SamplerData('Euler', lambda model: DiffusionSampler('Euler', EulerDiscreteScheduler, model), [], {}),
|
||||
sd_samplers_common.SamplerData('Euler a', lambda model: DiffusionSampler('Euler a', EulerAncestralDiscreteScheduler, model), [], {}),
|
||||
sd_samplers_common.SamplerData('Heun', lambda model: DiffusionSampler('Heun', HeunDiscreteScheduler, model), [], {}),
|
||||
|
||||
+34
-49
@@ -9,10 +9,9 @@ import urllib.request
|
||||
from urllib.parse import urlparse
|
||||
from enum import Enum
|
||||
import gradio as gr
|
||||
import tqdm
|
||||
import fasteners
|
||||
from rich.console import Console
|
||||
from modules import errors, ui_components, shared_items, cmd_args
|
||||
from modules import errors, shared_items, cmd_args, ui_components
|
||||
from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611
|
||||
from modules.dml import memory_providers, default_memory_provider, directml_do_hijack
|
||||
import modules.interrogate
|
||||
@@ -38,6 +37,7 @@ interrogator = modules.interrogate.InterrogateModels("interrogate")
|
||||
sd_upscalers = []
|
||||
face_restorers = []
|
||||
tab_names = []
|
||||
extra_networks = []
|
||||
options_templates = {}
|
||||
hypernetworks = {}
|
||||
loaded_hypernetworks = []
|
||||
@@ -72,21 +72,12 @@ restricted_opts = {
|
||||
compatibility_opts = ['clip_skip', 'uni_pc_lower_order_final', 'uni_pc_order']
|
||||
console = Console(log_time=True, log_time_format='%H:%M:%S-%f')
|
||||
|
||||
def is_url(string):
|
||||
parsed_url = urlparse(string)
|
||||
return all([parsed_url.scheme, parsed_url.netloc])
|
||||
|
||||
|
||||
class Backend(Enum):
|
||||
ORIGINAL = 1
|
||||
DIFFUSERS = 2
|
||||
|
||||
|
||||
def reload_hypernetworks():
|
||||
from modules.hypernetworks import hypernetwork
|
||||
global hypernetworks # pylint: disable=W0603
|
||||
hypernetworks = hypernetwork.list_hypernetworks(opts.hypernetwork_dir)
|
||||
|
||||
|
||||
class State:
|
||||
skipped = False
|
||||
@@ -174,7 +165,7 @@ class State:
|
||||
"""sets self.current_image from self.current_latent if enough sampling steps have been made after the last call to this"""
|
||||
if not parallel_processing_allowed:
|
||||
return
|
||||
if self.sampling_step - self.current_image_sampling_step >= opts.show_progress_every_n_steps and opts.live_previews_enable and opts.show_progress_every_n_steps != -1:
|
||||
if abs(self.sampling_step - self.current_image_sampling_step) >= opts.show_progress_every_n_steps and opts.live_previews_enable and opts.show_progress_every_n_steps > 0:
|
||||
self.do_set_current_image()
|
||||
|
||||
def do_set_current_image(self):
|
||||
@@ -193,6 +184,7 @@ class State:
|
||||
self.current_image = image
|
||||
self.id_live_preview += 1
|
||||
|
||||
|
||||
state = State()
|
||||
state.server_start = time.time()
|
||||
if not hasattr(cmd_opts, "use_openvino"):
|
||||
@@ -252,6 +244,17 @@ def list_checkpoint_tiles():
|
||||
default_checkpoint = list_checkpoint_tiles()[0] if len(list_checkpoint_tiles()) > 0 else "model.ckpt"
|
||||
|
||||
|
||||
def is_url(string):
|
||||
parsed_url = urlparse(string)
|
||||
return all([parsed_url.scheme, parsed_url.netloc])
|
||||
|
||||
|
||||
def reload_hypernetworks():
|
||||
from modules.hypernetworks import hypernetwork
|
||||
global hypernetworks # pylint: disable=W0603
|
||||
hypernetworks = hypernetwork.list_hypernetworks(opts.hypernetwork_dir)
|
||||
|
||||
|
||||
def refresh_checkpoints():
|
||||
import modules.sd_models # pylint: disable=W0621
|
||||
return modules.sd_models.list_models()
|
||||
@@ -435,10 +438,10 @@ options_templates.update(options_section(('diffusers', "Diffusers Settings"), {
|
||||
"diffusers_vae_upcast": OptionInfo("default", "VAE upcasting", gr.Radio, lambda: {"choices": ['default', 'true', 'false']}),
|
||||
"diffusers_vae_slicing": OptionInfo(True, "Enable VAE slicing"),
|
||||
"diffusers_vae_tiling": OptionInfo(False if cmd_opts.use_openvino else True, "Enable VAE tiling"),
|
||||
"diffusers_attention_slicing": OptionInfo(False, "Enable attention slicing"),
|
||||
"diffusers_attention_slicing": OptionInfo(True if devices.backend == "ipex" else False, "Enable attention slicing"),
|
||||
"diffusers_model_load_variant": OptionInfo("default", "Diffusers model loading variant", gr.Radio, lambda: {"choices": ['default', 'fp32', 'fp16']}),
|
||||
"diffusers_vae_load_variant": OptionInfo("default", "Diffusers VAE loading variant", gr.Radio, lambda: {"choices": ['default', 'fp32', 'fp16']}),
|
||||
"diffusers_lora_loader": OptionInfo("diffusers default" if cmd_opts.use_openvino else "sequential apply", "Diffusers LoRA loading variant", gr.Radio, lambda: {"choices": ['sequential apply', 'merge and apply', 'diffusers default']}),
|
||||
"diffusers_lora_loader": OptionInfo("sequential apply", "Diffusers LoRA loading variant", gr.Radio, lambda: {"choices": ['diffusers', 'sequential apply', 'merge and apply']}),
|
||||
"diffusers_force_zeros": OptionInfo(True, "Force zeros for prompts when empty"),
|
||||
"diffusers_aesthetics_score": OptionInfo(False, "Require aesthetics score"),
|
||||
}))
|
||||
@@ -487,14 +490,13 @@ options_templates.update(options_section(('saving-images', "Image Options"), {
|
||||
"n_rows": OptionInfo(-1, "Grid row count", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}),
|
||||
|
||||
"save_sep_options": OptionInfo("<h2>Intermediate Image Saving</h2>", "", gr.HTML),
|
||||
"save_init_img": OptionInfo(True, "Save copy of img2img init images"),
|
||||
"save_images_before_highres_fix": OptionInfo(False, "Save copy of image before applying highres fix"),
|
||||
"save_init_img": OptionInfo(False, "Save copy of img2img init images"),
|
||||
"save_images_before_highres_fix": OptionInfo(False, "Save copy of image before applying hires"),
|
||||
"save_images_before_refiner": OptionInfo(False, "Save copy of image before running refiner"),
|
||||
"save_images_before_face_restoration": OptionInfo(False, "Save copy of image before doing face restoration"),
|
||||
"save_images_before_color_correction": OptionInfo(False, "Save copy of image before applying color correction"),
|
||||
"save_mask": OptionInfo(False, "Save copy of the inpainting greyscale mask"),
|
||||
"save_mask_composite": OptionInfo(False, "Save copy of inpainting masked composite"),
|
||||
|
||||
}))
|
||||
|
||||
options_templates.update(options_section(('saving-paths', "Image Naming & Paths"), {
|
||||
@@ -522,7 +524,6 @@ options_templates.update(options_section(('saving-paths', "Image Naming & Paths"
|
||||
"outdir_grids": OptionInfo("", "Output directory for grids", component_args=hide_dirs, folder=True),
|
||||
"outdir_txt2img_grids": OptionInfo("outputs/grids", 'Output directory for txt2img grids', component_args=hide_dirs, folder=True),
|
||||
"outdir_img2img_grids": OptionInfo("outputs/grids", 'Output directory for img2img grids', component_args=hide_dirs, folder=True),
|
||||
|
||||
}))
|
||||
|
||||
options_templates.update(options_section(('ui', "User Interface"), {
|
||||
@@ -558,7 +559,7 @@ options_templates.update(options_section(('live-preview', "Live Previews"), {
|
||||
}))
|
||||
|
||||
options_templates.update(options_section(('sampler-params', "Sampler Settings"), {
|
||||
"show_samplers": OptionInfo(["Default", "Euler a", "UniPC", "DEIS", "DDIM", "DPM 1S", "DPM 2M", "DPM++ 2M SDE", "DPM++ 2M SDE Karras", "DPM2 Karras", "DPM++ 2M Karras"], "Show samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in list_samplers() if x.name != "PLMS"]}),
|
||||
"show_samplers": OptionInfo(["Default", "Euler a", "UniPC", "DEIS", "DDIM", "DPM 1S", "DPM 2M", "DPM SDE", "DPM++ 2M SDE", "DPM++ 2M SDE Karras", "DPM2 Karras", "DPM++ 2M Karras"], "Show samplers in user interface", gr.CheckboxGroup, lambda: {"choices": [x.name for x in list_samplers() if x.name != "PLMS"]}),
|
||||
'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "bh2", "vary_coeff"]}),
|
||||
'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}),
|
||||
'eta_noise_seed_delta': OptionInfo(0, "Noise seed delta (eta)", gr.Number, {"precision": 0}),
|
||||
@@ -570,7 +571,7 @@ options_templates.update(options_section(('sampler-params', "Sampler Settings"),
|
||||
"schedulers_use_karras": OptionInfo(True, "Samplers use Karras sigmas where applicable"),
|
||||
"schedulers_use_loworder": OptionInfo(True, "Samplers use simplified solvers in final steps where applicable"),
|
||||
"schedulers_use_thresholding": OptionInfo(False, "Samplers use dynamic thresholding where applicable"),
|
||||
"schedulers_dpm_solver": OptionInfo("sde-dpmsolver++", "Samplers DPM solver algorithm", gr.Radio, lambda: {"choices": ['dpmsolver', 'dpmsolver++', 'sde-dpmsolver++']}),
|
||||
"schedulers_dpm_solver": OptionInfo("sde-dpmsolver++", "Samplers DPM solver algorithm", gr.Radio, lambda: {"choices": ['dpmsolver', 'dpmsolver++', 'sde-dpmsolver', 'sde-dpmsolver++']}),
|
||||
"schedulers_beta_schedule": OptionInfo("default", "Samplers override beta schedule", gr.Radio, lambda: {"choices": ['default', 'linear', 'scaled_linear', 'squaredcos_cap_v2']}),
|
||||
'schedulers_beta_start': OptionInfo(0, "Samplers override beta start", gr.Number, {}),
|
||||
'schedulers_beta_end': OptionInfo(0, "Samplers override beta end", gr.Number, {}),
|
||||
@@ -609,7 +610,7 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), {
|
||||
"postprocessing_sep_upscalers": OptionInfo("<h2>Upscaling</h2>", "", gr.HTML),
|
||||
'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
|
||||
"upscaler_for_img2img": OptionInfo("None", "Default upscaler for image resize operations", gr.Dropdown, lambda: {"choices": [x.name for x in sd_upscalers]}),
|
||||
"realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}),
|
||||
# "realesrgan_enabled_models": OptionInfo(["R-ESRGAN 4x+", "R-ESRGAN 4x+ Anime6B"], "Real-ESRGAN available models", gr.CheckboxGroup, lambda: {"choices": shared_items.realesrgan_models_names()}),
|
||||
"ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
|
||||
"ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap in pixels for ESRGAN upscalers", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}),
|
||||
"SCUNET_tile": OptionInfo(256, "Tile size for SCUNET upscalers", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
|
||||
@@ -647,6 +648,7 @@ options_templates.update(options_section(('interrogate', "Interrogate"), {
|
||||
}))
|
||||
|
||||
options_templates.update(options_section(('extra_networks', "Extra Networks"), {
|
||||
"extra_networks": OptionInfo(["All"], "Extra networks", ui_components.DropdownMulti, lambda: {"choices": ['All'] + [en.title for en in extra_networks]}),
|
||||
"extra_networks_card_cover": OptionInfo("sidebar", "UI position", gr.Radio, lambda: {"choices": ["cover", "inline", "sidebar"]}),
|
||||
"extra_networks_height": OptionInfo(53, "UI height (%)", gr.Slider, {"minimum": 10, "maximum": 100, "step": 1}),
|
||||
"extra_networks_sidebar_width": OptionInfo(35, "UI sidebar width (%)", gr.Slider, {"minimum": 10, "maximum": 80, "step": 1}),
|
||||
@@ -656,8 +658,8 @@ options_templates.update(options_section(('extra_networks', "Extra Networks"), {
|
||||
"extra_networks_card_fit": OptionInfo("cover", "UI image contain method", gr.Radio, lambda: {"choices": ["contain", "cover", "fill"]}),
|
||||
"extra_network_skip_indexing": OptionInfo(False, "Do not automatically build extra network pages", gr.Checkbox),
|
||||
"lyco_patch_lora": OptionInfo(False, "Use LyCoris handler for all LoRA types", gr.Checkbox),
|
||||
"lora_functional": OptionInfo(False, "Use Kohya method for handling multiple LoRA", gr.Checkbox),
|
||||
"extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}),
|
||||
"lora_functional": OptionInfo(False, "Use Kohya method for handling multiple LoRA", gr.Checkbox, { "visible": False }),
|
||||
"extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01, "visible": False}),
|
||||
"sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: { "choices": ["None"] + list(hypernetworks.keys()), "visible": False }, refresh=reload_hypernetworks),
|
||||
}))
|
||||
|
||||
@@ -832,6 +834,7 @@ else:
|
||||
opts.data['sd_backend'] = 'diffusers' if backend == Backend.DIFFUSERS else 'original'
|
||||
opts.data['uni_pc_lower_order_final'] = opts.schedulers_use_loworder
|
||||
opts.data['uni_pc_order'] = opts.schedulers_solver_order
|
||||
# opts.data['diffusers_lora_loader'] = 'diffusers' # TODO broken in diffusers=0.21
|
||||
log.info(f'Engine: backend={backend} compute={devices.backend} mode={devices.inference_context.__name__} device={devices.get_optimal_device_name()}')
|
||||
log.info(f'Device: {print_dict(devices.get_gpu_info())}')
|
||||
|
||||
@@ -885,37 +888,18 @@ def reload_gradio_theme(theme_name=None):
|
||||
log.info(f'Loading UI theme: name={theme_name} style={opts.theme_style}')
|
||||
|
||||
|
||||
class TotalTQDM:
|
||||
class TotalTQDM: # compatibility with previous global-tqdm
|
||||
# import tqdm
|
||||
def __init__(self):
|
||||
self._tqdm = None
|
||||
|
||||
pass
|
||||
def reset(self):
|
||||
self._tqdm = tqdm.tqdm(
|
||||
desc="Total",
|
||||
total=state.job_count * state.sampling_steps,
|
||||
position=1,
|
||||
)
|
||||
|
||||
pass
|
||||
def update(self):
|
||||
if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars:
|
||||
return
|
||||
if self._tqdm is None:
|
||||
self.reset()
|
||||
self._tqdm.update()
|
||||
|
||||
pass
|
||||
def updateTotal(self, new_total):
|
||||
if not opts.multiple_tqdm or cmd_opts.disable_console_progressbars:
|
||||
return
|
||||
if self._tqdm is None:
|
||||
self.reset()
|
||||
self._tqdm.total = new_total
|
||||
|
||||
pass
|
||||
def clear(self):
|
||||
if self._tqdm is not None:
|
||||
self._tqdm.refresh()
|
||||
self._tqdm.close()
|
||||
self._tqdm = None
|
||||
|
||||
pass
|
||||
total_tqdm = TotalTQDM()
|
||||
|
||||
|
||||
@@ -1067,4 +1051,5 @@ sd_model = None
|
||||
sd_refiner = None
|
||||
sd_model_type = ''
|
||||
sd_refiner_type = ''
|
||||
compiled_model_state = None
|
||||
sys.modules[__name__].__class__ = Shared
|
||||
|
||||
+4
-2
@@ -1,7 +1,8 @@
|
||||
# We need this so Python doesn't complain about the unknown StableDiffusionProcessing-typehint at runtime
|
||||
from __future__ import annotations
|
||||
import csv
|
||||
import re
|
||||
import os
|
||||
import csv
|
||||
import json
|
||||
from installer import log
|
||||
from modules import paths
|
||||
@@ -9,7 +10,7 @@ from modules import paths
|
||||
|
||||
class Style():
|
||||
def __init__(self, name: str, prompt: str = "", negative_prompt: str = "", extra: str = "", filename: str = "", preview: str = ""):
|
||||
self.name = name
|
||||
self.name = re.sub(r'[\t\r\n]', '', name).strip()
|
||||
self.prompt = prompt
|
||||
self.negative_prompt = negative_prompt
|
||||
self.extra = extra
|
||||
@@ -73,6 +74,7 @@ class StyleDatabase:
|
||||
list_folder(fn)
|
||||
|
||||
list_folder(self.path)
|
||||
self.styles = dict(sorted(self.styles.items(), key=lambda style: style[1].filename))
|
||||
log.debug(f'Loaded styles: folder={self.path} items={len(self.styles.keys())}')
|
||||
|
||||
def get_style_prompts(self, styles):
|
||||
|
||||
@@ -159,11 +159,6 @@ class EmbeddingDatabase:
|
||||
self.register_embedding(embedding, shared.sd_model)
|
||||
except Exception:
|
||||
self.skipped_embeddings[name] = embedding
|
||||
try:
|
||||
text_inv_tokens = pipe.tokenizer.added_tokens_encoder.keys()
|
||||
text_inv_tokens = [t for t in text_inv_tokens if not (len(t.split("_")) > 1 and t.split("_")[-1].isdigit())]
|
||||
except Exception:
|
||||
text_inv_tokens = []
|
||||
|
||||
def load_from_file(self, path, filename):
|
||||
name, ext = os.path.splitext(filename)
|
||||
|
||||
+6
-1
@@ -937,7 +937,7 @@ def create_ui(startup_timer = None):
|
||||
elif info.folder is not None:
|
||||
with FormRow():
|
||||
res = comp(label=info.label, value=fun(), elem_id=elem_id, elem_classes="folder-selector", **args)
|
||||
ui_common.create_browse_button(res, f"folder_{key}")
|
||||
# ui_common.create_browse_button(res, f"folder_{key}")
|
||||
else:
|
||||
try:
|
||||
res = comp(label=info.label, value=fun(), elem_id=elem_id, **args)
|
||||
@@ -1076,6 +1076,11 @@ def create_ui(startup_timer = None):
|
||||
loadsave.create_ui()
|
||||
create_dirty_indicator("tab_defaults", [], interactive=False)
|
||||
|
||||
with gr.TabItem("Change log", id="change_log", elem_id="system_tab_changelog"):
|
||||
with open('CHANGELOG.md', 'r', encoding='utf-8') as f:
|
||||
md = f.read()
|
||||
gr.Markdown(md)
|
||||
|
||||
with gr.TabItem("Licenses", id="system_licenses", elem_id="system_tab_licenses"):
|
||||
gr.HTML(modules.shared.html("licenses.html"), elem_id="licenses", elem_classes="licenses")
|
||||
create_dirty_indicator("tab_licenses", [], interactive=False)
|
||||
|
||||
@@ -110,7 +110,7 @@ def save_files(js_data, images, html_info, index):
|
||||
fullfns.append(fullfn)
|
||||
destination = shared.opts.outdir_save
|
||||
if shared.opts.use_save_to_dirs_for_ui:
|
||||
namegen = modules.images.FilenameGenerator(p, seed=p.all_seeds[i], prompt=p.all_prompts[i], image=None) # pylint: disable=no-member
|
||||
namegen = modules.images.FilenameGenerator(p, seed=p.all_seeds[i], prompt=p.all_prompts[i], image=None, index=image_index) # pylint: disable=no-member
|
||||
dirname = namegen.apply(shared.opts.directories_filename_pattern or "[prompt_words]").lstrip(' ').rstrip('\\ /')
|
||||
destination = os.path.join(destination, dirname)
|
||||
os.makedirs(destination, exist_ok = True)
|
||||
|
||||
@@ -16,10 +16,10 @@ from modules.ui_components import ToolButton
|
||||
import modules.ui_symbols as symbols
|
||||
|
||||
|
||||
extra_pages = []
|
||||
allowed_dirs = []
|
||||
dir_cache = {} # key=path, value=(mtime, listdir(path))
|
||||
refresh_time = None
|
||||
extra_pages = shared.extra_networks
|
||||
|
||||
|
||||
def listdir(path):
|
||||
@@ -37,9 +37,9 @@ def listdir(path):
|
||||
|
||||
def register_page(page):
|
||||
# registers extra networks page for the UI; recommend doing it in on_before_ui() callback for extensions
|
||||
extra_pages.append(page)
|
||||
shared.extra_networks.append(page)
|
||||
allowed_dirs.clear()
|
||||
for page in extra_pages:
|
||||
for page in shared.extra_networks:
|
||||
for folder in page.allowed_directories_for_previews():
|
||||
if folder not in allowed_dirs:
|
||||
allowed_dirs.append(os.path.abspath(folder))
|
||||
@@ -58,7 +58,7 @@ def fetch_file(filename: str = ""):
|
||||
|
||||
|
||||
def get_metadata(page: str = "", item: str = ""):
|
||||
page = next(iter([x for x in extra_pages if x.name == page]), None)
|
||||
page = next(iter([x for x in shared.extra_networks if x.name == page]), None)
|
||||
if page is None:
|
||||
return JSONResponse({ 'metadata': 'none' })
|
||||
metadata = page.metadata.get(item, 'none')
|
||||
@@ -69,7 +69,7 @@ def get_metadata(page: str = "", item: str = ""):
|
||||
|
||||
|
||||
def get_info(page: str = "", item: str = ""):
|
||||
page = next(iter([x for x in extra_pages if x.name == page]), None)
|
||||
page = next(iter([x for x in shared.extra_networks if x.name == page]), None)
|
||||
if page is None:
|
||||
return JSONResponse({ 'info': 'none' })
|
||||
info = page.info.get(item, 'none')
|
||||
@@ -128,7 +128,7 @@ class ExtraNetworksPage:
|
||||
shared.log.error(f'Cannot evaluate extra network prompt: {item["prompt"]} {e}')
|
||||
|
||||
if not any(self.title in x.label for x in xyz_grid.axis_options):
|
||||
if self.title == 'Checkpoints':
|
||||
if self.title == 'Model':
|
||||
return
|
||||
opt = xyz_grid.AxisOption(f"[Network] {self.title}", str, add_prompt, choices=lambda: [x["name"] for x in self.items])
|
||||
xyz_grid.axis_options.append(opt)
|
||||
@@ -308,7 +308,7 @@ class ExtraNetworksPage:
|
||||
shared.log.error(f'Extra network save preview: {filename} {e}')
|
||||
return
|
||||
is_allowed = False
|
||||
for page in extra_pages:
|
||||
for page in shared.extra_networks:
|
||||
if any(path_is_parent(x, filename) for x in page.allowed_directories_for_previews()):
|
||||
is_allowed = True
|
||||
break
|
||||
@@ -339,7 +339,7 @@ class ExtraNetworksPage:
|
||||
|
||||
|
||||
def initialize():
|
||||
extra_pages.clear()
|
||||
shared.extra_networks.clear()
|
||||
|
||||
|
||||
def register_pages():
|
||||
@@ -353,6 +353,21 @@ def register_pages():
|
||||
register_page(ExtraNetworksPageHypernetworks())
|
||||
|
||||
|
||||
def get_pages():
|
||||
pages = []
|
||||
if 'All' in shared.opts.extra_networks:
|
||||
pages = shared.extra_networks
|
||||
else:
|
||||
titles = [page.title for page in shared.extra_networks]
|
||||
for page in shared.opts.extra_networks:
|
||||
try:
|
||||
idx = titles.index(page)
|
||||
except ValueError:
|
||||
continue
|
||||
pages.append(shared.extra_networks[idx])
|
||||
return pages
|
||||
|
||||
|
||||
class ExtraNetworksUi:
|
||||
def __init__(self):
|
||||
self.pages = None
|
||||
@@ -384,28 +399,28 @@ def create_ui(container, button, tabname, skip_indexing = False):
|
||||
if ui.tabname == 'txt2img': # refresh only once
|
||||
global refresh_time # pylint: disable=global-statement
|
||||
refresh_time = time.time()
|
||||
for page in extra_pages:
|
||||
for page in get_pages():
|
||||
page.create_page(ui.tabname, skip_indexing)
|
||||
with gr.Tab(page.title, id=page.title.lower().replace(" ", "_"), elem_classes="extra-networks-tab"):
|
||||
page_elem = gr.HTML(page.html, elem_id=f'{tabname}{page.name}_extra_page', elem_classes="extra-networks-page")
|
||||
page_elem.change(fn=lambda: None, _js=f'() => refreshExtraNetworks("{tabname}")', inputs=[], outputs=[])
|
||||
ui.pages.append(page_elem)
|
||||
hmtl = gr.HTML(page.html, elem_id=f'{tabname}{page.name}_extra_page', elem_classes="extra-networks-page")
|
||||
# hmtl.change(fn=lambda: None, _js=f'() => refreshExtraNetworks("{tabname}")', inputs=[], outputs=[])
|
||||
ui.pages.append(hmtl)
|
||||
|
||||
def toggle_visibility(is_visible):
|
||||
is_visible = not is_visible
|
||||
return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary"))
|
||||
|
||||
def en_refresh(title):
|
||||
res = []
|
||||
for page in extra_pages:
|
||||
pages = []
|
||||
for page in get_pages():
|
||||
if title is None or title == '' or title == page.title or len(page.html) == 0:
|
||||
page.refresh()
|
||||
page.refresh_time = None
|
||||
page.create_page(ui.tabname)
|
||||
shared.log.debug(f"Refreshing Extra networks: page='{page.title}' items={len(page.items)} tab={ui.tabname}")
|
||||
res.append(page.html)
|
||||
pages.append(page.html)
|
||||
ui.search.update(value = ui.search.value)
|
||||
return res
|
||||
return pages
|
||||
|
||||
state_visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated
|
||||
button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button])
|
||||
@@ -423,15 +438,13 @@ def path_is_parent(parent_path, child_path):
|
||||
def setup_ui(ui, gallery):
|
||||
|
||||
def save_preview(pagename, index, images, filename):
|
||||
res = []
|
||||
for page in extra_pages:
|
||||
pages = []
|
||||
for page in get_pages():
|
||||
if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0:
|
||||
page.save_preview(index, images, filename)
|
||||
res.append(page.create_page(ui.tabname))
|
||||
else:
|
||||
res.append(page.html)
|
||||
return res
|
||||
|
||||
page.create_page(ui.tabname)
|
||||
pages.append(page.html)
|
||||
return pages
|
||||
|
||||
ui.button_save_preview.click(
|
||||
fn=save_preview,
|
||||
@@ -441,14 +454,13 @@ def setup_ui(ui, gallery):
|
||||
)
|
||||
|
||||
def save_description(pagename, filename, desc):
|
||||
res = []
|
||||
for page in extra_pages:
|
||||
pages = []
|
||||
for page in get_pages():
|
||||
if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0:
|
||||
page.save_description(filename, desc)
|
||||
res.append(page.create_page(ui.tabname))
|
||||
else:
|
||||
res.append(page.html)
|
||||
return res
|
||||
page.create_page(ui.tabname)
|
||||
pages.append(page.html)
|
||||
return pages
|
||||
|
||||
ui.button_save_description.click(
|
||||
fn=save_description,
|
||||
|
||||
@@ -7,7 +7,7 @@ from modules import shared, ui_extra_networks, sd_models
|
||||
|
||||
class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage):
|
||||
def __init__(self):
|
||||
super().__init__('Checkpoints')
|
||||
super().__init__('Model')
|
||||
|
||||
def refresh(self):
|
||||
shared.refresh_checkpoints()
|
||||
@@ -18,6 +18,7 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage):
|
||||
path, _ext = os.path.splitext(checkpoint.filename)
|
||||
yield {
|
||||
"name": checkpoint.name_for_extra,
|
||||
"title": checkpoint.title,
|
||||
"filename": path,
|
||||
"fullname": checkpoint.filename,
|
||||
"hash": checkpoint.shorthash,
|
||||
|
||||
@@ -5,7 +5,7 @@ from modules import shared, ui_extra_networks
|
||||
|
||||
class ExtraNetworksPageHypernetworks(ui_extra_networks.ExtraNetworksPage):
|
||||
def __init__(self):
|
||||
super().__init__('Hypernetworks')
|
||||
super().__init__('Hypernetwork')
|
||||
|
||||
def refresh(self):
|
||||
shared.reload_hypernetworks()
|
||||
|
||||
@@ -6,7 +6,7 @@ from modules import shared, ui_extra_networks
|
||||
|
||||
class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage):
|
||||
def __init__(self):
|
||||
super().__init__('Styles')
|
||||
super().__init__('Style')
|
||||
|
||||
def refresh(self):
|
||||
shared.prompt_styles.reload()
|
||||
|
||||
@@ -7,7 +7,7 @@ from modules.textual_inversion.textual_inversion import Embedding
|
||||
|
||||
class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage):
|
||||
def __init__(self):
|
||||
super().__init__('Textual Inversion')
|
||||
super().__init__('Embedding')
|
||||
self.allow_negative_prompt = True
|
||||
|
||||
def refresh(self):
|
||||
@@ -39,6 +39,7 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage):
|
||||
embeddings = list(sd_models.model_data.sd_model.embedding_db.word_embeddings.values())
|
||||
else:
|
||||
embeddings = []
|
||||
embeddings = sorted(embeddings, key=lambda emb: emb.filename)
|
||||
for embedding in embeddings:
|
||||
path, _ext = os.path.splitext(embedding.filename)
|
||||
tags = {}
|
||||
|
||||
+1
-2
@@ -27,7 +27,6 @@ opencv-contrib-python-headless
|
||||
piexif
|
||||
psutil
|
||||
pyyaml
|
||||
realesrgan
|
||||
resize-right
|
||||
rich
|
||||
safetensors
|
||||
@@ -47,7 +46,7 @@ requests==2.31.0
|
||||
tqdm==4.66.1
|
||||
accelerate==0.20.3
|
||||
opencv-python-headless==4.7.0.72
|
||||
diffusers==0.21.1
|
||||
diffusers==0.20.2
|
||||
einops==0.4.1
|
||||
gradio==3.43.2
|
||||
huggingface_hub==0.17.1
|
||||
|
||||
@@ -28,7 +28,7 @@ if %ERRORLEVEL% == 0 goto :activate_venv
|
||||
|
||||
for /f "delims=" %%i in ('CALL %PYTHON% -c "import sys; print(sys.executable)"') do set PYTHON_FULLNAME="%%i"
|
||||
echo Using python: %PYTHON_FULLNAME%
|
||||
echo Creating VENV: %VENV_DIR%
|
||||
echo Creating VENV: %VENV_DIR%
|
||||
%PYTHON_FULLNAME% -m venv "%VENV_DIR%" >tmp/stdout.txt 2>tmp/stderr.txt
|
||||
if %ERRORLEVEL% == 0 goto :activate_venv
|
||||
echo Failed creating VENV: "%VENV_DIR%"
|
||||
|
||||
+1
-1
Submodule wiki updated: fea51bf38c...d43376f66f
Reference in New Issue
Block a user