Merge pull request #2221 from vladmandic/master

update dev
This commit is contained in:
Vladimir Mandic
2023-09-18 15:33:02 -04:00
committed by GitHub
47 changed files with 1265 additions and 435 deletions
+10 -2
View File
@@ -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:
+23 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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':
+32 -8
View File
@@ -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'));
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+7 -9
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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)
+2 -1
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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:
+39 -15
View File
@@ -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
+337 -81
View File
@@ -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
View File
@@ -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
View File
@@ -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]
+3
View File
@@ -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
View File
@@ -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)
+39 -27
View File
@@ -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")
+2 -1
View File
@@ -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
View File
@@ -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 []
+384
View File
@@ -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
View File
@@ -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:
+4 -27
View File
@@ -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
View File
@@ -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
-4
View File
@@ -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):
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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)
+41 -29
View File
@@ -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,
+2 -1
View File
@@ -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,
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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