diff --git a/.eslintrc.json b/.eslintrc.json
index 62feb13a5..c86dbb749 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -37,14 +37,19 @@
"object-curly-newline":"off",
"prefer-rest-params":"off",
"prefer-destructuring":"off",
- "radix":"off"
+ "radix":"off",
+ "node/shebang": "off"
},
"globals": {
// asssets
"panzoom": "readonly",
- // script.js
+ // logger.js
"log": "readonly",
"debug": "readonly",
+ "error": "readonly",
+ "xhrGet": "readonly",
+ "xhrPost": "readonly",
+ // script.js
"gradioApp": "readonly",
"executeCallbacks": "readonly",
"onAfterUiUpdate": "readonly",
@@ -87,7 +92,6 @@
// settings.js
"registerDragDrop": "readonly",
// extraNetworks.js
- "requestGet": "readonly",
"getENActiveTab": "readonly",
"quickApplyStyle": "readonly",
"quickSaveStyle": "readonly",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc6cd163b..919041bde 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,21 +1,73 @@
# Change Log for SD.Next
-## Update for 2024-11-22
+## Update for 2024-11-28
-- Model loader improvements:
+### New models and integrations
+
+- [Flux Tools](https://blackforestlabs.ai/flux-1-tools/)
+ **Redux** is actually a tool, **Fill** is inpaint/outpaint optimized version of *Flux-dev*
+ **Canny** & **Depth** are optimized versions of *Flux-dev* for their respective tasks: they are *not* ControlNets that work on top of a model
+ to use, go to image or control interface and select *Flux Tools* in scripts
+ all models are auto-downloaded on first use
+ *note*: All models are [gated](https://github.com/vladmandic/automatic/wiki/Gated) and require acceptance of terms and conditions via web page
+ *recommended*: Enable on-the-fly [quantization](https://github.com/vladmandic/automatic/wiki/Quantization) or [compression](https://github.com/vladmandic/automatic/wiki/NNCF-Compression) to reduce resource usage
+ *todo*: support for Canny/Depth LoRAs
+ - [Redux](https://huggingface.co/black-forest-labs/FLUX.1-Redux-dev): ~0.1GB
+ works together with existing model and basically uses input image to analyze it and use that instead of prompt
+ *recommended*: low denoise strength levels result in more variety
+ - [Fill](https://huggingface.co/black-forest-labs/FLUX.1-Fill-dev): ~23.8GB, replaces currently loaded model
+ *note*: can be used in inpaint/outpaint mode only
+ - [Canny](https://huggingface.co/black-forest-labs/FLUX.1-Canny-dev): ~23.8GB, replaces currently loaded model
+ *recommended*: guidance scale 30
+ - [Depth](https://huggingface.co/black-forest-labs/FLUX.1-Depth-dev): ~23.8GB, replaces currently loaded model
+ *recommended*: guidance scale 10
+- [StabilityAI SD35 ControlNets]([sd3_medium](https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets))
+ - In addition to previously released `InstantX` and `Alimama`, we now have *official* ones from StabilityAI
+- [Style Aligned Image Generation](https://style-aligned-gen.github.io/)
+ enable in scripts, compatible with sd-xl
+ enter multiple prompts in prompt field separated by new line
+ style-aligned applies selected attention layers uniformly to all images to achive consistency
+ can be used with or without input image in which case first prompt is used to establish baseline
+ *note:* all prompts are processes as a single batch, so vram is limiting factor
+
+### UI and workflow improvements
+
+- **Model loader** improvements:
- detect model components on model load fail
+ - allow passing absolute path to model loader
- Flux, SD35: force unload model
- Flux: apply `bnb` quant when loading *unet/transformer*
- Flux: all-in-one safetensors
example:
- Flux: do not recast quants
-- Sampler improvements
- - update DPM FlowMatch samplers
-- Fixes:
- - update `diffusers`
- - fix README links
- - fix sdxl controlnet single-file loader
- - relax settings validator
+- **UI**:
+ - improved stats on generate completion
+ - improved live preview display and performance
+ - improved accordion behavior
+ - auto-size networks height for sidebar
+ - control: hide preview column by default
+ - control: optionn to hide input column
+ - control: add stats
+ - browser -> server logging framework
+ - add addtional themes: `black-reimagined`
+- **Sampler** improvements
+ - Euler FlowMatch: add sigma methods (*karras/exponential/betas*)
+ - DPM FlowMatch: update all and add sigma methods
+
+### Fixes
+
+- update `diffusers`
+- fix README links
+- fix sdxl controlnet single-file loader
+- relax settings validator
+- improve js progress calls resiliency
+- fix text-to-video pipeline
+- avoid live-preview if vae-decode is running
+- allow xyz-grid with multi-axis s&r
+- fix xyz-grid with lora
+- fix api script callbacks
+- fix gpu memory monitoring
+- simplify img2img/inpaint/sketch canvas handling
## Update for 2024-11-21
diff --git a/TODO.md b/TODO.md
index 973e062dc..73008039d 100644
--- a/TODO.md
+++ b/TODO.md
@@ -7,9 +7,9 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma
- SD35 IPAdapter:
- SD35 LoRA:
- Flux IPAdapter:
-- Flux Fill/ControlNet/Redux:
- Flux NF4:
- SANA:
+- LTX-Video:
## Other
diff --git a/cli/api-model.js b/cli/api-model.js
new file mode 100755
index 000000000..e2ce5344a
--- /dev/null
+++ b/cli/api-model.js
@@ -0,0 +1,30 @@
+#!/usr/bin/env node
+
+const sd_url = process.env.SDAPI_URL || 'http://127.0.0.1:7860';
+const sd_username = process.env.SDAPI_USR;
+const sd_password = process.env.SDAPI_PWD;
+const models = [
+ '/mnt/models/stable-diffusion/sd15/lyriel_v16.safetensors',
+ '/mnt/models/stable-diffusion/flux/flux-finesse_v2-f1h-fp8.safetensors',
+ '/mnt/models/stable-diffusion/sdxl/TempestV0.1-Artistic.safetensors',
+];
+
+async function options(data) {
+ const method = 'POST';
+ const headers = new Headers();
+ const body = JSON.stringify(data);
+ headers.set('Content-Type', 'application/json');
+ if (sd_username && sd_password) headers.set({ Authorization: `Basic ${btoa('sd_username:sd_password')}` });
+ const res = await fetch(`${sd_url}/sdapi/v1/options`, { method, headers, body });
+ return res;
+}
+
+async function main() {
+ for (const model of models) {
+ console.log('model:', model);
+ const res = await options({ sd_model_checkpoint: model });
+ console.log('result:', res);
+ }
+}
+
+main();
diff --git a/cli/api-pulid.js b/cli/api-pulid.js
index fde0ae43b..033824e9b 100755
--- a/cli/api-pulid.js
+++ b/cli/api-pulid.js
@@ -10,12 +10,13 @@ const argparse = require('argparse');
const sd_url = process.env.SDAPI_URL || 'http://127.0.0.1:7860';
const sd_username = process.env.SDAPI_USR;
const sd_password = process.env.SDAPI_PWD;
+let args = {};
function b64(file) {
const data = fs.readFileSync(file);
- const b64 = Buffer.from(data).toString('base64');
+ const b64str = Buffer.from(data).toString('base64');
const ext = path.extname(file).replace('.', '');
- str = `data:image/${ext};base64,${b64}`;
+ const str = `data:image/${ext};base64,${b64str}`;
// console.log('b64:', ext, b64.length);
return str;
}
@@ -39,7 +40,16 @@ function options() {
if (args.pulid) {
const b64image = b64(args.pulid);
opt.script_name = 'pulid';
- opt.script_args = [b64image, 0.9];
+ opt.script_args = [
+ b64image, // b64 encoded image, required param
+ 0.9, // strength, optional
+ 20, // zero, optional
+ 'dpmpp_sde', // sampler, optional
+ 'v2', // ortho, optional
+ true, // restore (disable pulid after run), optional
+ true, // offload, optional
+ 'v1.1', // version, optional
+ ];
}
// console.log('options:', opt);
return opt;
@@ -53,8 +63,8 @@ function init() {
parser.add_argument('--height', { type: 'int', help: 'height' });
parser.add_argument('--pulid', { type: 'str', help: 'pulid init image' });
parser.add_argument('--output', { type: 'str', help: 'output path' });
- const args = parser.parse_args();
- return args
+ const parsed = parser.parse_args();
+ return parsed;
}
async function main() {
@@ -73,12 +83,12 @@ async function main() {
console.log('result:', json.info);
for (const i in json.images) { // eslint-disable-line guard-for-in
const file = args.output || `/tmp/test-${i}.jpg`;
- const data = atob(json.images[i])
+ const data = atob(json.images[i]);
fs.writeFileSync(file, data, 'binary');
console.log('image saved:', file);
}
}
}
-const args = init();
+args = init();
main();
diff --git a/cli/full-test.sh b/cli/full-test.sh
index e410528ad..912dc3a5b 100755
--- a/cli/full-test.sh
+++ b/cli/full-test.sh
@@ -1,5 +1,8 @@
#!/usr/bin/env bash
+node cli/api-txt2img.js
+node cli/api-pulid.js
+
source venv/bin/activate
echo image-exif
python cli/api-info.py --input html/logo-bg-0.jpg
diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py
index db617ee5b..fd6287c62 100644
--- a/extensions-builtin/Lora/networks.py
+++ b/extensions-builtin/Lora/networks.py
@@ -88,7 +88,7 @@ def assign_network_names_to_compvis_modules(sd_model):
network_name = name.replace(".", "_")
network_layer_mapping[network_name] = module
module.network_layer_name = network_name
- shared.sd_model.network_layer_mapping = network_layer_mapping
+ sd_model.network_layer_mapping = network_layer_mapping
def load_diffusers(name, network_on_disk, lora_scale=shared.opts.extra_networks_default_multiplier) -> network.Network:
@@ -141,7 +141,7 @@ def load_network(name, network_on_disk) -> network.Network:
sd = sd_models.read_state_dict(network_on_disk.filename, what='network')
if shared.sd_model_type == 'f1': # if kohya flux lora, convert state_dict
sd = lora_convert._convert_kohya_flux_lora_to_diffusers(sd) or sd # pylint: disable=protected-access
- assign_network_names_to_compvis_modules(shared.sd_model) # this should not be needed but is here as an emergency fix for an unknown error people are experiencing in 1.2.0
+ assign_network_names_to_compvis_modules(shared.sd_model)
keys_failed_to_match = {}
matched_networks = {}
bundle_embeddings = {}
diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui
index 4647bd7f8..3008cee4b 160000
--- a/extensions-builtin/sdnext-modernui
+++ b/extensions-builtin/sdnext-modernui
@@ -1 +1 @@
-Subproject commit 4647bd7f86be9d2783a9ba1f38acaa9bcec942d2
+Subproject commit 3008cee4b67bb00f8f1a4fe4510ec27ba92aa418
diff --git a/installer.py b/installer.py
index 0b64c3616..37202552d 100644
--- a/installer.py
+++ b/installer.py
@@ -212,7 +212,7 @@ def installed(package, friendly: str = None, reload = False, quiet = False):
if friendly:
pkgs = friendly.split()
else:
- pkgs = [p for p in package.split() if not p.startswith('-') and not p.startswith('=')]
+ pkgs = [p for p in package.split() if not p.startswith('-') and not p.startswith('=') and not p.startswith('git+')]
pkgs = [p.split('/')[-1] for p in pkgs] # get only package name if installing from url
for pkg in pkgs:
if '!=' in pkg:
@@ -295,7 +295,7 @@ def install(package, friendly: str = None, ignore: bool = False, reinstall: bool
quick_allowed = False
if args.reinstall or reinstall or not installed(package, friendly, quiet=quiet):
deps = '' if not no_deps else '--no-deps '
- res = pip(f"install{' --upgrade' if not args.uv else ''} {deps}{package}", ignore=ignore, uv=package != "uv")
+ res = pip(f"install{' --upgrade' if not args.uv else ''} {deps}{package}", ignore=ignore, uv=package != "uv" and not package.startswith('git+'))
try:
import importlib # pylint: disable=deprecated-module
importlib.reload(pkg_resources)
@@ -459,7 +459,7 @@ def check_python(supported_minors=[9, 10, 11, 12], reason=None):
def check_diffusers():
if args.skip_all or args.skip_requirements:
return
- sha = 'b5fd6f13f5434d69d919cc8cedf0b11db664cf06'
+ sha = '069186fac510d6f6f88a5e435523b235c823a8a0'
pkg = pkg_resources.working_set.by_key.get('diffusers', None)
minor = int(pkg.version.split('.')[1] if pkg is not None else 0)
cur = opts.get('diffusers_version', '') if minor > 0 else ''
diff --git a/javascript/base.css b/javascript/base.css
index 7daa8b2bd..6c18ad7c5 100644
--- a/javascript/base.css
+++ b/javascript/base.css
@@ -25,7 +25,6 @@
.progressDiv .progress { width: 0%; height: 20px; background: #0060df; color: white; font-weight: bold; line-height: 20px; padding: 0 8px 0 0; text-align: right; overflow: visible; white-space: nowrap; padding: 0 0.5em; }
.livePreview { position: absolute; z-index: 50; background-color: transparent; width: -moz-available; width: -webkit-fill-available; }
.livePreview img { position: absolute; object-fit: contain; width: 100%; height: 100%; }
-.dark .livePreview { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); }
.popup-metadata { color: white; background: #0000; display: inline-block; white-space: pre-wrap; font-size: 0.75em; }
/* fullpage image viewer */
diff --git a/javascript/black-teal-reimagined.css b/javascript/black-teal-reimagined.css
new file mode 100644
index 000000000..b7567ce75
--- /dev/null
+++ b/javascript/black-teal-reimagined.css
@@ -0,0 +1,1072 @@
+/* Generic HTML Tags */
+@font-face {
+ font-family: 'NotoSans';
+ font-display: swap;
+ font-style: normal;
+ font-weight: 100;
+ src: local('NotoSans'), url('notosans-nerdfont-regular.ttf');
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+:root,
+.light,
+.dark {
+ --font: 'NotoSans';
+ --font-mono: 'ui-monospace', 'Consolas', monospace;
+ --font-size: 16px;
+
+ /* Primary Colors */
+ --primary-50: #7dffff;
+ --primary-100: #72e8e8;
+ --primary-200: #67d2d2;
+ --primary-300: #5dbcbc;
+ --primary-400: #52a7a7;
+ --primary-500: #489292;
+ --primary-600: #3e7d7d;
+ --primary-700: #356969;
+ --primary-800: #2b5656;
+ --primary-900: #224444;
+ --primary-950: #193232;
+
+ /* Neutral Colors */
+ --neutral-50: #f0f0f0;
+ --neutral-100: #e0e0e0;
+ --neutral-200: #d0d0d0;
+ --neutral-300: #b0b0b0;
+ --neutral-400: #909090;
+ --neutral-500: #707070;
+ --neutral-600: #606060;
+ --neutral-700: #404040;
+ --neutral-800: #303030;
+ --neutral-900: #202020;
+ --neutral-950: #101010;
+
+ /* Highlight and Inactive Colors */
+ --highlight-color: var(--primary-200);
+ --inactive-color: var(--primary-800);
+
+ /* Text Colors */
+ --body-text-color: var(--neutral-100);
+ --body-text-color-subdued: var(--neutral-300);
+
+ /* Background Colors */
+ --background-color: var(--neutral-950);
+ --background-fill-primary: var(--neutral-700);
+ --input-background-fill: var(--neutral-800);
+
+ /* Padding and Borders */
+ --input-padding: 4px;
+ --input-shadow: none;
+ --button-primary-text-color: var(--neutral-100);
+ --button-primary-background-fill: var(--primary-600);
+ --button-primary-background-fill-hover: var(--primary-800);
+ --button-secondary-text-color: var(--neutral-100);
+ --button-secondary-background-fill: var(--neutral-900);
+ --button-secondary-background-fill-hover: var(--neutral-600);
+
+ /* Border Radius */
+ --radius-xs: 2px;
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+ --radius-xl: 10px;
+ --radius-xxl: 15px;
+ --radius-xxxl: 20px;
+
+ /* Shadows */
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.1);
+ --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1);
+ --shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.1);
+ --shadow-xl: 0 8px 16px rgba(0, 0, 0, 0.1);
+
+ /* Animation */
+ --transition: all 0.3s ease;
+
+ /* Scrollbar */
+ --scrollbar-bg: var(--neutral-800);
+ --scrollbar-thumb: var(--highlight-color);
+}
+
+html {
+ font-size: var(--font-size);
+ font-family: var(--font);
+}
+
+body,
+button,
+input,
+select,
+textarea {
+ font-family: var(--font);
+ color: var(--body-text-color);
+ transition: var(--transition);
+}
+
+button {
+ max-width: 400px;
+ white-space: nowrap;
+ padding: 8px 12px;
+ border: none;
+ border-radius: var(--radius-md);
+ background-color: var(--button-primary-background-fill);
+ color: var(--button-primary-text-color);
+ cursor: pointer;
+ box-shadow: var(--shadow-sm);
+ transition: transform 0.2s ease, background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: var(--button-primary-background-fill-hover);
+ transform: scale(1.05);
+}
+
+/* Range Input Styles */
+.slider-container {
+ width: 100%;
+ /* Ensures the container takes full width */
+ max-width: 100%;
+ /* Prevents overflow */
+ padding: 0 10px;
+ /* Adds padding for aesthetic spacing */
+ box-sizing: border-box;
+ /* Ensures padding doesn't affect width */
+}
+
+input[type='range'] {
+ display: block;
+ margin: 0;
+ padding: 0;
+ height: 1em;
+ background-color: transparent;
+ overflow: hidden;
+ cursor: pointer;
+ box-shadow: none;
+ -webkit-appearance: none;
+ opacity: 0.7;
+ appearance: none;
+ width: 100%;
+ /* Makes the slider responsive */
+}
+
+input[type='range'] {
+ opacity: 1;
+}
+
+input[type='range']::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ height: 1em;
+ width: 1em;
+ background-color: var(--highlight-color);
+ border-radius: var(--radius-xs);
+ box-shadow: var(--shadow-md);
+ cursor: pointer;
+ /* Ensures the thumb is clickable */
+}
+
+input[type='range']::-webkit-slider-runnable-track {
+ -webkit-appearance: none;
+ height: 6px;
+ background: var(--input-background-fill);
+ border-radius: var(--radius-md);
+}
+
+input[type='range']::-moz-range-thumb {
+ height: 1em;
+ width: 1em;
+ background-color: var(--highlight-color);
+ border-radius: var(--radius-xs);
+ box-shadow: var(--shadow-md);
+ cursor: pointer;
+ /* Ensures the thumb is clickable */
+}
+
+input[type='range']::-moz-range-track {
+ height: 6px;
+ background: var(--input-background-fill);
+ border-radius: var(--radius-md);
+}
+
+@media (max-width: 768px) {
+ .slider-container {
+ width: 100%;
+ /* Adjust width for smaller screens */
+ }
+
+ .networks-menu,
+ .styles-menu {
+ width: 100%;
+ /* Ensure menus are full width */
+ margin: 0;
+ /* Reset margins for smaller screens */
+ }
+}
+
+/* Scrollbar Styles */
+:root {
+ scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-bg);
+}
+
+::-webkit-scrollbar {
+ width: 12px;
+ height: 12px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--scrollbar-bg);
+ border-radius: var(--radius-lg);
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: var(--scrollbar-thumb);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-sm);
+}
+
+/* Tab Navigation Styles */
+.tab-nav {
+ display: flex;
+ /* Use flexbox for layout */
+ justify-content: space-evenly;
+ /* Space out the tabs evenly */
+ align-items: center;
+ /* Center items vertically */
+ background: var(--background-color);
+ /* Background color */
+ border-bottom: 1px dashed var(--highlight-color) !important;
+ /* Bottom border for separation */
+ box-shadow: var(--shadow-md);
+ /* Shadow for depth */
+ margin-bottom: 5px;
+ /* Add some space between the tab nav and the content */
+ padding-bottom: 5px;
+ /* Add space between buttons and border */
+}
+
+/* Individual Tab Styles */
+.tab-nav>button {
+ background: var(--neutral-900);
+ /* No background for default state */
+ color: var(--text-color);
+ /* Text color */
+ border: 1px solid var(--highlight-color);
+ /* No border */
+ border-radius: var(--radius-xxl);
+ /* Rounded corners */
+ cursor: pointer;
+ /* Pointer cursor */
+ transition: background 0.3s ease, color 0.3s ease;
+ /* Smooth transition */
+ padding-top: 5px;
+ padding-bottom: 5px;
+ padding-right: 10px;
+ padding-left: 10px;
+ margin-bottom: 3px;
+}
+
+/* Active Tab Style */
+.tab-nav>button.selected {
+ background: var(--primary-100);
+ /* Highlight active tab */
+ color: var(--background-color);
+ /* Change text color for active tab */
+}
+
+/* Hover State for Tabs */
+.tab-nav>button:hover {
+ background: var(--highlight-color);
+ /* Background on hover */
+ color: var(--background-color);
+ /* Change text color on hover */
+}
+
+/* Responsive Styles */
+@media (max-width: 768px) {
+ .tab-nav {
+ flex-direction: column;
+ /* Stack tabs vertically on smaller screens */
+ align-items: stretch;
+ /* Stretch tabs to full width */
+ }
+
+ .tab-nav>button {
+ width: 100%;
+ /* Full width for buttons */
+ text-align: left;
+ /* Align text to the left */
+ }
+}
+
+/* Quick Settings Panel Styles */
+#quicksettings {
+ background: var(--background-color);
+ /* Background color */
+ box-shadow: var(--shadow-lg);
+ /* Shadow for depth */
+ border-radius: var(--radius-lg);
+ /* Rounded corners */
+ padding: 1em;
+ /* Padding for spacing */
+ z-index: 200;
+ /* Ensure it stays on top */
+}
+
+/* Quick Settings Header */
+#quicksettings .header {
+ font-size: var(--text-lg);
+ /* Font size for header */
+ font-weight: bold;
+ /* Bold text */
+ margin-bottom: 0.5em;
+ /* Space below header */
+}
+
+/* Quick Settings Options */
+#quicksettings .option {
+ display: flex;
+ /* Flexbox for layout */
+ justify-content: space-between;
+ /* Space between label and toggle */
+ align-items: center;
+ /* Center items vertically */
+ padding: 0.5em 0;
+ /* Padding for each option */
+ border-bottom: 1px solid var(--neutral-600);
+ /* Separator line */
+}
+
+/* Option Label Styles */
+#quicksettings .option label {
+ color: var(--text-color);
+ /* Text color */
+}
+
+/* Toggle Switch Styles */
+#quicksettings .option input[type="checkbox"] {
+ cursor: pointer;
+ /* Pointer cursor */
+}
+
+/* Quick Settings Footer */
+#quicksettings .footer {
+ margin-top: 1em;
+ /* Space above footer */
+ text-align: right;
+ /* Align text to the right */
+}
+
+/* Close Button Styles */
+#quicksettings .footer button {
+ background: var(--button-primary-background-fill);
+ /* Button background */
+ color: var(--button-primary-text-color);
+ /* Button text color */
+ border: none;
+ /* No border */
+ border-radius: var(--radius-md);
+ /* Rounded corners */
+ padding: 0.5em 1em;
+ /* Padding for button */
+ cursor: pointer;
+ /* Pointer cursor */
+ transition: 0.3s ease;
+ /* Smooth transition */
+}
+
+/* Close Button Hover State */
+#quicksettings .footer button:hover {
+ background: var(--highlight-color);
+ /* Change background on hover */
+}
+
+/* Responsive Styles */
+@media (max-width: 768px) {
+ #quicksettings {
+ right: 10px;
+ /* Adjust position for smaller screens */
+ width: 90%;
+ /* Full width on smaller screens */
+ }
+}
+
+/* Form Styles */
+div.form {
+ border-width: 0;
+ box-shadow: var(--shadow-md);
+ background: var(--background-fill-primary);
+ border-bottom: 3px solid var(--highlight-color);
+ padding: 3px;
+ border-radius: var(--radius-md);
+ margin: 1px;
+}
+
+/* Gradio Style Classes */
+fieldset .gr-block.gr-box,
+label.block span {
+ padding: 0;
+ margin-top: -4px;
+}
+
+.border-2 {
+ border-width: 0;
+}
+
+.border-b-2 {
+ border-bottom-width: 2px;
+ border-color: var(--highlight-color) !important;
+ padding-bottom: 2px;
+ margin-bottom: 8px;
+}
+
+.bg-white {
+ color: lightyellow;
+ background-color: var(--inactive-color);
+}
+
+.gr-box {
+ border-radius: var(--radius-sm) !important;
+ background-color: var(--neutral-950) !important;
+ box-shadow: var(--shadow-md);
+ border-width: 0;
+ padding: 4px;
+ margin: 12px 0;
+}
+
+.gr-button {
+ font-weight: normal;
+ box-shadow: var(--shadow-sm);
+ font-size: 0.8rem;
+ min-width: 32px;
+ min-height: 32px;
+ padding: 3px;
+ margin: 3px;
+ transition: var(--transition);
+}
+
+.gr-button:hover {
+ background-color: var(--highlight-color);
+}
+
+.gr-check-radio {
+ background-color: var(--inactive-color);
+ border-width: 0;
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-sm);
+}
+
+.gr-check-radio:checked {
+ background-color: var(--highlight-color);
+}
+
+.gr-compact {
+ background-color: var(--background-color);
+}
+
+.gr-form {
+ border-width: 0;
+}
+
+.gr-input {
+ background-color: var(--neutral-800) !important;
+ padding: 4px;
+ margin: 4px;
+ border-radius: var(--radius-md);
+ transition: var(--transition);
+}
+
+.gr-input:hover {
+ background-color: var(--neutral-700);
+}
+
+.gr-input-label {
+ color: lightyellow;
+ border-width: 0;
+ background: transparent;
+ padding: 2px !important;
+}
+
+.gr-panel {
+ background-color: var(--background-color);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-md);
+}
+
+.eta-bar {
+ display: none !important;
+}
+
+.gradio-slider {
+ max-width: 200px;
+}
+
+.gradio-slider input[type="number"] {
+ background: var(--neutral-950);
+ margin-top: 2px;
+}
+
+.gradio-image {
+ height: unset !important;
+}
+
+svg.feather.feather-image,
+.feather .feather-image {
+ display: none;
+}
+
+.gap-2 {
+ padding-top: 8px;
+}
+
+.gr-box>div>div>input.gr-text-input {
+ right: 0;
+ width: 4em;
+ padding: 0;
+ top: -12px;
+ border: none;
+ max-height: 20px;
+}
+
+.output-html {
+ line-height: 1.2 rem;
+ overflow-x: hidden;
+}
+
+.output-html>div {
+ margin-bottom: 8px;
+}
+
+.overflow-hidden .flex .flex-col .relative col .gap-4 {
+ min-width: var(--left-column);
+ max-width: var(--left-column);
+}
+
+.p-2 {
+ padding: 0;
+}
+
+.px-4 {
+ padding-left: 1rem;
+ padding-right: 1rem;
+}
+
+.py-6 {
+ padding-bottom: 0;
+}
+
+.tabs {
+ background-color: var(--background-color);
+}
+
+.block.token-counter span {
+ background-color: var(--input-background-fill) !important;
+ box-shadow: 2px 2px 2px #111;
+ border: none !important;
+ font-size: 0.7rem;
+}
+
+.label-wrap {
+ margin: 8px 0px 4px 0px;
+}
+
+.gradio-button.tool {
+ border: none;
+ background: none;
+ box-shadow: none;
+ filter: hue-rotate(340deg) saturate(0.5);
+}
+
+#tab_extensions table td,
+#tab_extensions table th,
+#tab_config table td,
+#tab_config table th {
+ border: none;
+}
+
+#tab_extensions table tr:hover,
+#tab_config table tr:hover {
+ background-color: var(--neutral-500) !important;
+}
+
+#tab_extensions table,
+#tab_config table {
+ width: 96vw;
+}
+
+#tab_extensions table thead,
+#tab_config table thead {
+ background-color: var(--neutral-700);
+}
+
+#tab_extensions table,
+#tab_config table {
+ background-color: var(--neutral-900);
+}
+
+/* Automatic Style Classes */
+.progressDiv {
+ border-radius: var(--radius-sm) !important;
+ position: fixed;
+ top: 44px;
+ right: 26px;
+ max-width: 262px;
+ height: 48px;
+ z-index: 99;
+ box-shadow: var(--button-shadow);
+}
+
+.progressDiv .progress {
+ border-radius: var(--radius-lg) !important;
+ background: var(--highlight-color);
+ line-height: 3rem;
+ height: 48px;
+}
+
+.gallery-item {
+ box-shadow: none !important;
+}
+
+.performance {
+ color: #888;
+}
+
+.image-buttons {
+ justify-content: center;
+ gap: 0 !important;
+}
+
+.image-buttons>button {
+ max-width: 160px;
+}
+
+.tooltip {
+ background: var(--primary-300);
+ color: black;
+ border: none;
+ border-radius: var(--radius-lg);
+}
+
+#system_row>button,
+#settings_row>button,
+#config_row>button {
+ max-width: 10em;
+}
+
+/* Gradio Elements Overrides */
+#div.gradio-container {
+ overflow-x: hidden;
+}
+
+#img2img_label_copy_to_img2img {
+ font-weight: normal;
+}
+
+#txt2img_styles,
+#img2img_styles,
+#control_styles {
+ padding: 0;
+ margin-top: 2px;
+}
+
+#txt2img_styles_refresh,
+#img2img_styles_refresh,
+#control_styles_refresh {
+ padding: 0;
+ margin-top: 1em;
+}
+
+#img2img_settings {
+ min-width: calc(2 * var(--left-column));
+ max-width: calc(2 * var(--left-column));
+ background-color: var(--neutral-950);
+ padding-top: 16px;
+}
+
+#interrogate,
+#deepbooru {
+ margin: 0 0px 10px 0px;
+ max-width: 80px;
+ max-height: 80px;
+ font-weight: normal;
+ font-size: 0.95em;
+}
+
+#quicksettings .gr-button-tool {
+ font-size: 1.6rem;
+ box-shadow: none;
+ margin-left: -20px;
+ margin-top: -2px;
+ height: 2.4em;
+}
+
+#footer,
+#style_pos_col,
+#style_neg_col,
+#roll_col,
+#extras_upscaler_2,
+#extras_upscaler_2_visibility,
+#txt2img_seed_resize_from_w,
+#txt2img_seed_resize_from_h {
+ display: none;
+}
+
+#save-animation {
+ border-radius: var(--radius-sm) !important;
+ margin-bottom: 16px;
+ background-color: var(--neutral-950);
+}
+
+#script_list {
+ padding: 4px;
+ margin-top: 16px;
+ margin-bottom: 8px;
+}
+
+#settings>div.flex-wrap {
+ width: 15em;
+}
+
+#txt2img_cfg_scale {
+ min-width: 200px;
+}
+
+#txt2img_checkboxes,
+#img2img_checkboxes,
+#control_checkboxes {
+ background-color: transparent;
+ margin-bottom: 0.2em;
+}
+
+#extras_upscale {
+ margin-top: 10px;
+}
+
+#txt2img_progress_row>div {
+ min-width: var(--left-column);
+ max-width: var(--left-column);
+}
+
+#txt2img_settings {
+ min-width: var(--left-column);
+ max-width: var(--left-column);
+ background-color: var(--neutral-950);
+}
+
+#pnginfo_html2_info {
+ margin-top: -18px;
+ background-color: var(--input-background-fill);
+ padding: var(--input-padding);
+}
+
+#txt2img_styles_row,
+#img2img_styles_row,
+#control_styles_row {
+ margin-top: -6px;
+}
+
+.block>span {
+ margin-bottom: 0 !important;
+ margin-top: var(--spacing-lg);
+}
+
+/* Extra Networks Container */
+#extra_networks_root {
+ z-index: 100;
+ background: var(--background-color);
+ box-shadow: var(--shadow-md);
+ border-radius: var(--radius-lg);
+ transform: translateX(100%);
+ animation: slideIn 0.5s forwards;
+ overflow: hidden;
+ /* Prevents overflow of content */
+}
+
+@keyframes slideIn {
+ to {
+ transform: translateX(0);
+ }
+}
+
+/* Extra Networks Styles */
+.extra-networks {
+ border-left: 2px solid var(--highlight-color) !important;
+ padding-left: 4px;
+}
+
+.extra-networks .tab-nav>button:hover {
+ background: var(--highlight-color);
+}
+
+/* Network tab search and description important fix, dont remove */
+#txt2img_description,
+#txt2img_extra_search,
+#img2img_description,
+#img2img_extra_search,
+#control_description,
+#control_extra_search {
+ margin-top: 50px;
+}
+
+.extra-networks .buttons>button:hover {
+ background: var(--highlight-color);
+}
+
+/* Network Cards Container */
+.extra-network-cards {
+ display: flex;
+ flex-wrap: wrap;
+ overflow-y: auto;
+ overflow-x: hidden;
+ align-content: flex-start;
+ padding-top: 20px;
+ justify-content: center;
+ width: 100%;
+ /* Ensures it takes full width */
+}
+
+/* Individual Card Styles */
+.extra-network-cards .card {
+ height: fit-content;
+ margin: 0 0 0.5em 0.5em;
+ position: relative;
+ scroll-snap-align: start;
+ scroll-margin-top: 0;
+ background: var(--neutral-800);
+ /* Background for cards */
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-md);
+ transition: var(--transition);
+}
+
+/* Overlay Styles */
+.extra-network-cards .card .overlay {
+ z-index: 10;
+ width: 100%;
+ background: none;
+ border-radius: var(--radius-md);
+}
+
+/* Overlay Name Styles */
+.extra-network-cards .card .overlay .name {
+ font-size: var(--text-lg);
+ font-weight: bold;
+ text-shadow: 1px 1px black;
+ color: white;
+ overflow-wrap: anywhere;
+ position: absolute;
+ bottom: 0;
+ padding: 0.2em;
+ z-index: 10;
+}
+
+/* Preview Styles */
+.extra-network-cards .card .preview {
+ box-shadow: var(--button-shadow);
+ min-height: 30px;
+ border-radius: var(--radius-md);
+ z-index: 9999;
+}
+
+/* Hover Effects */
+.extra-network-cards .card:hover {
+ transform: scale(1.3);
+ z-index: 9999; /* Use a high value to ensure it appears on top */
+ transition: transform 0.3s ease, z-index 0s; /* Smooth transition */
+}
+
+.extra-network-cards .card:hover .overlay {
+ z-index: 10000; /* Ensure overlay is also on top */
+}
+
+.extra-network-cards .card:hover .preview {
+ box-shadow: none;
+ filter: grayscale(0%);
+}
+
+/* Tags Styles */
+.extra-network-cards .card .overlay .tags {
+ display: none;
+ overflow-wrap: anywhere;
+ position: absolute;
+ top: 100%;
+ z-index: 20;
+ background: var(--body-background-fill);
+ overflow-x: hidden;
+ overflow-y: auto;
+ max-height: 333px;
+}
+
+/* Individual Tag Styles */
+.extra-network-cards .card .overlay .tag {
+ padding: 2px;
+ margin: 2px;
+ background: rgba(70, 70, 70, 0.60);
+ font-size: var(--text-md);
+ cursor: pointer;
+ display: inline-block;
+}
+
+/* Actions Styles */
+.extra-network-cards .card .actions>span {
+ padding: 4px;
+ font-size: 34px !important;
+}
+
+.extra-network-cards .card .actions {
+ background: none;
+}
+
+.extra-network-cards .card .actions .details {
+ bottom: 50px;
+ background-color: var(--neutral-800);
+}
+
+.extra-network-cards .card .actions>span:hover {
+ color: var(--highlight-color);
+}
+
+/* Version Styles */
+.extra-network-cards .card .version {
+ position: absolute;
+ top: 0;
+ left: 0;
+ padding: 2px;
+ font-weight: bolder;
+ text-shadow: 1px 1px black;
+ text-transform: uppercase;
+ background: gray;
+ opacity: 75%;
+ margin: 4px;
+ line-height: 0.9rem;
+}
+
+/* Hover Actions */
+.extra-network-cards .card:hover .actions {
+ display: block;
+}
+
+.extra-network-cards .card:hover .overlay .tags {
+ display: block;
+}
+
+/* No Preview Card Styles */
+.extra-network-cards .card:has(>img[src*="card-no-preview.png"])::before {
+ content: '';
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ mix-blend-mode: multiply;
+ background-color: var(--data-color);
+}
+
+/* Card List Styles */
+.extra-network-cards .card-list {
+ display: flex;
+ margin: 0.3em;
+ padding: 0.3em;
+ background: var(--input-background-fill);
+ cursor: pointer;
+ border-radius: var(--button-large-radius);
+}
+
+.extra-network-cards .card-list .tag {
+ color: var(--primary-500);
+ margin-left: 0.8em;
+}
+
+/* Correction color picker styling */
+#txt2img_hdr_color_picker label input {
+ width: 100%;
+ height: 100%;
+}
+
+/* Token counters styling */
+
+#txt2img_token_counter, #txt2img_negative_token_counter {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-evenly;
+ padding: 5px;
+}
+
+#txt2img_prompt_container {
+ margin: 5px;
+ padding: 0px;
+}
+
+#text2img_prompt label, #text2img_neg_prompt label {
+ margin: 0px;
+}
+
+/* Based on Gradio Built-in Dark Theme */
+:root,
+.light,
+.dark {
+ --body-background-fill: var(--background-color);
+ --color-accent-soft: var(--neutral-700);
+ --background-fill-secondary: none;
+ --border-color-accent: var(--background-color);
+ --border-color-primary: var(--background-color);
+ --link-text-color-active: var(--primary-500);
+ --link-text-color: var(--secondary-500);
+ --link-text-color-hover: var(--secondary-400);
+ --link-text-color-visited: var(--secondary-600);
+ --shadow-spread: 1px;
+ --block-background-fill: none;
+ --block-border-color: var(--border-color-primary);
+ --block_border_width: none;
+ --block-info-text-color: var(--body-text-color-subdued);
+ --block-label-background-fill: var(--background-fill-secondary);
+ --block-label-border-color: var(--border-color-primary);
+ --block_label_border_width: none;
+ --block-label-text-color: var(--neutral-200);
+ --block-shadow: none;
+ --block-title-background-fill: none;
+ --block-title-border-color: none;
+ --block-title-border-width: 0px;
+ --block-title-padding: 0;
+ --block-title-radius: none;
+ --block-title-text-size: var(--text-md);
+ --block-title-text-weight: 400;
+ --container-radius: var(--radius-lg);
+ --form-gap-width: 1px;
+ --layout-gap: var(--spacing-xxl);
+ --panel-border-width: 0;
+ --section-header-text-size: var(--text-md);
+ --section-header-text-weight: 400;
+ --checkbox-border-radius: var(--radius-sm);
+ --checkbox-label-gap: 2px;
+ --checkbox-label-padding: var(--spacing-md);
+ --checkbox-label-shadow: var(--shadow-drop);
+ --checkbox-label-text-size: var(--text-md);
+ --checkbox-label-text-weight: 400;
+ --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
+ --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
+ --checkbox-shadow: var(--input-shadow);
+ --error-border-width: 1px;
+ --input-border-width: 0;
+ --input-radius: var(--radius-lg);
+ --input-text-size: var(--text-md);
+ --input-text-weight: 400;
+ --loader-color: var(--color-accent);
+ --prose-text-size: var(--text-md);
+ --prose-text-weight: 400;
+ --prose-header-text-weight: 400;
+ --slider-color: var(--neutral-900);
+ --table-radius: var(--radius-lg);
+ --button-large-padding: 2px 6px;
+ --button-large-radius: var(--radius-lg);
+ --button-large-text-size: var(--text-lg);
+ --button-large-text-weight: 400;
+ --button-shadow: none;
+ --button-shadow-active: none;
+ --button-shadow-hover: none;
+ --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm));
+ --button-small-radius: var(--radius-lg);
+ --button-small-text-size: var(--text-md);
+ --button-small-text-weight: 400;
+ --button-transition: none;
+ --size-9: 64px;
+ --size-14: 64px;
+}
\ No newline at end of file
diff --git a/javascript/black-teal.css b/javascript/black-teal.css
index c6f266c54..2ebf32e96 100644
--- a/javascript/black-teal.css
+++ b/javascript/black-teal.css
@@ -134,7 +134,7 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.gallery-item { box-shadow: none !important; }
.performance { color: #888; }
.extra-networks { border-left: 2px solid var(--highlight-color) !important; padding-left: 4px; }
-.image-buttons { gap: 10px !important; justify-content: center; }
+.image-buttons { justify-content: center; gap: 0 !important; }
.image-buttons > button { max-width: 160px; }
.tooltip { background: var(--primary-300); color: black; border: none; border-radius: var(--radius-lg) }
#system_row > button, #settings_row > button, #config_row > button { max-width: 10em; }
diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js
index 77fe125f3..1d1bcfb24 100644
--- a/javascript/extraNetworks.js
+++ b/javascript/extraNetworks.js
@@ -3,19 +3,6 @@ let sortVal = -1;
// helpers
-const requestGet = (url, data, handler) => {
- const xhr = new XMLHttpRequest();
- const args = Object.keys(data).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&');
- xhr.open('GET', `${url}?${args}`, true);
- xhr.onreadystatechange = () => {
- if (xhr.readyState === 4) {
- if (xhr.status === 200) handler(JSON.parse(xhr.responseText));
- else console.error(`Request: url=${url} status=${xhr.status} err`);
- }
- };
- xhr.send(JSON.stringify(data));
-};
-
const getENActiveTab = () => {
let tabName = '';
if (gradioApp().getElementById('tab_txt2img').style.display === 'block') tabName = 'txt2img';
@@ -98,7 +85,7 @@ function readCardTags(el, tags) {
}
function readCardDescription(page, item) {
- requestGet('/sd_extra_networks/description', { page, item }, (data) => {
+ xhrGet('/sd_extra_networks/description', { page, item }, (data) => {
const tabname = getENActiveTab();
const description = gradioApp().querySelector(`#${tabname}_description > label > textarea`);
description.value = data?.description?.trim() || '';
@@ -447,6 +434,22 @@ function setupExtraNetworksForTab(tabname) {
};
}
+ // auto-resize networks sidebar
+ const resizeObserver = new ResizeObserver((entries) => {
+ for (const entry of entries) {
+ for (const el of Array.from(gradioApp().getElementById(`${tabname}_extra_tabs`).querySelectorAll('.extra-networks-page'))) {
+ const h = Math.trunc(entry.contentRect.height);
+ if (h <= 0) return;
+ if (window.opts.extra_networks_card_cover === 'sidebar' && window.opts.theme_type === 'Standard') el.style.height = `max(55vh, ${h - 90}px)`;
+ // log(`${tabname} height: ${entry.target.id}=${h} ${el.id}=${el.clientHeight}`);
+ }
+ }
+ });
+ const settingsEl = gradioApp().getElementById(`${tabname}_settings`);
+ const interfaceEl = gradioApp().getElementById(`${tabname}_interface`);
+ if (settingsEl) resizeObserver.observe(settingsEl);
+ if (interfaceEl) resizeObserver.observe(interfaceEl);
+
// en style
if (!en) return;
let lastView;
diff --git a/javascript/gallery.js b/javascript/gallery.js
index 1f3afd148..05e594e4c 100644
--- a/javascript/gallery.js
+++ b/javascript/gallery.js
@@ -94,14 +94,14 @@ async function delayFetchThumb(fn) {
outstanding++;
const res = await fetch(`/sdapi/v1/browser/thumb?file=${encodeURI(fn)}`, { priority: 'low' });
if (!res.ok) {
- console.error(res.statusText);
+ error(`fetchThumb: ${res.statusText}`);
outstanding--;
return undefined;
}
const json = await res.json();
outstanding--;
if (!res || !json || json.error || Object.keys(json).length === 0) {
- if (json.error) console.error(json.error);
+ if (json.error) error(`fetchThumb: ${json.error}`);
return undefined;
}
return json;
diff --git a/javascript/imageMaskFix.js b/javascript/imageMaskFix.js
deleted file mode 100644
index fd37caf90..000000000
--- a/javascript/imageMaskFix.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * temporary fix for https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/668
- * @see https://github.com/gradio-app/gradio/issues/1721
- */
-function imageMaskResize() {
- const canvases = gradioApp().querySelectorAll('#img2maskimg .touch-none canvas');
- if (!canvases.length) {
- window.removeEventListener('resize', imageMaskResize);
- return;
- }
- const wrapper = canvases[0].closest('.touch-none');
- const previewImage = wrapper.previousElementSibling;
- if (!previewImage.complete) {
- previewImage.addEventListener('load', imageMaskResize);
- return;
- }
- const w = previewImage.width;
- const h = previewImage.height;
- const nw = previewImage.naturalWidth;
- const nh = previewImage.naturalHeight;
- const portrait = nh > nw;
- const wW = Math.min(w, portrait ? h / nh * nw : w / nw * nw);
- const wH = Math.min(h, portrait ? h / nh * nh : w / nw * nh);
- wrapper.style.width = `${wW}px`;
- wrapper.style.height = `${wH}px`;
- wrapper.style.left = '0px';
- wrapper.style.top = '0px';
- canvases.forEach((c) => {
- c.style.width = '';
- c.style.height = '';
- c.style.maxWidth = '100%';
- c.style.maxHeight = '100%';
- c.style.objectFit = 'contain';
- });
-}
-
-onAfterUiUpdate(imageMaskResize);
-window.addEventListener('resize', imageMaskResize);
diff --git a/javascript/loader.js b/javascript/loader.js
index f3c7fe60f..8cd4811bf 100644
--- a/javascript/loader.js
+++ b/javascript/loader.js
@@ -20,7 +20,7 @@ async function preloadImages() {
try {
await Promise.all(imagePromises);
} catch (error) {
- console.error('Error preloading images:', error);
+ error(`preloadImages: ${error}`);
}
}
@@ -43,14 +43,16 @@ async function createSplash() {
const motdEl = document.getElementById('motd');
if (motdEl) motdEl.innerHTML = text.replace(/["]+/g, '');
})
- .catch((err) => console.error('getMOTD:', err));
+ .catch((err) => error(`getMOTD: ${err}`));
}
async function removeSplash() {
const splash = document.getElementById('splash');
if (splash) splash.remove();
log('removeSplash');
- log('startupTime', Math.round(performance.now() - appStartTime) / 1000);
+ const t = Math.round(performance.now() - appStartTime) / 1000;
+ log('startupTime', t);
+ xhrPost('/sdapi/v1/log', { message: `ready time=${t}` });
}
window.onload = createSplash;
diff --git a/javascript/logMonitor.js b/javascript/logMonitor.js
index e4fe99a7f..9b915e6da 100644
--- a/javascript/logMonitor.js
+++ b/javascript/logMonitor.js
@@ -2,6 +2,7 @@ let logMonitorEl = null;
let logMonitorStatus = true;
let logWarnings = 0;
let logErrors = 0;
+let logConnected = false;
function dateToStr(ts) {
const dt = new Date(1000 * ts);
@@ -29,8 +30,7 @@ async function logMonitor() {
row.innerHTML = `| ${dateToStr(l.created)} | ${level}${l.facility} | ${module}${l.msg} | `;
logMonitorEl.appendChild(row);
} catch (e) {
- // console.log('logMonitor', e);
- console.error('logMonitor line', line);
+ error(`logMonitor: ${line}`);
}
};
@@ -46,6 +46,7 @@ async function logMonitor() {
if (logMonitorStatus) setTimeout(logMonitor, opts.logmonitor_refresh_period);
else setTimeout(logMonitor, 10 * 1000); // on failure try to reconnect every 10sec
+
if (!opts.logmonitor_show) return;
logMonitorStatus = false;
if (!logMonitorEl) {
@@ -64,14 +65,20 @@ async function logMonitor() {
const lines = await res.json();
if (logMonitorEl && lines?.length > 0) logMonitorEl.parentElement.parentElement.style.display = opts.logmonitor_show ? 'block' : 'none';
for (const line of lines) addLogLine(line);
+ if (!logConnected) {
+ logConnected = true;
+ xhrPost('/sdapi/v1/log', { debug: 'connected' });
+ }
} else {
- addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Failed to fetch log: ${res?.status} ${res?.statusText}" }`);
+ logConnected = false;
logErrors++;
+ addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Failed to fetch log: ${res?.status} ${res?.statusText}" }`);
}
cleanupLog(atBottom);
} catch (err) {
- addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Failed to fetch log: server unreachable" }`);
+ logConnected = false;
logErrors++;
+ addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Failed to fetch log: server unreachable" }`);
cleanupLog(atBottom);
}
}
diff --git a/javascript/logger.js b/javascript/logger.js
new file mode 100644
index 000000000..1677fa537
--- /dev/null
+++ b/javascript/logger.js
@@ -0,0 +1,66 @@
+const log = async (...msg) => {
+ const dt = new Date();
+ const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
+ if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
+ console.log(ts, ...msg); // eslint-disable-line no-console
+};
+
+const debug = async (...msg) => {
+ const dt = new Date();
+ const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
+ if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
+ console.debug(ts, ...msg); // eslint-disable-line no-console
+};
+
+const error = async (...msg) => {
+ const dt = new Date();
+ const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
+ if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
+ console.error(ts, ...msg); // eslint-disable-line no-console
+ // const txt = msg.join(' ');
+ // if (!txt.includes('asctime') && !txt.includes('xhr.')) xhrPost('/sdapi/v1/log', { error: txt }); // eslint-disable-line no-use-before-define
+};
+
+const xhrInternal = (xhrObj, data, handler = undefined, errorHandler = undefined, ignore = false, serverTimeout = 5000) => {
+ const err = (msg) => {
+ if (!ignore) {
+ error(`${msg}: state=${xhrObj.readyState} status=${xhrObj.status} response=${xhrObj.responseText}`);
+ if (errorHandler) errorHandler(xhrObj);
+ }
+ };
+
+ xhrObj.setRequestHeader('Content-Type', 'application/json');
+ xhrObj.timeout = serverTimeout;
+ xhrObj.ontimeout = () => err('xhr.ontimeout');
+ xhrObj.onerror = () => err('xhr.onerror');
+ xhrObj.onabort = () => err('xhr.onabort');
+ xhrObj.onreadystatechange = () => {
+ if (xhrObj.readyState === 4) {
+ if (xhrObj.status === 200) {
+ try {
+ const json = JSON.parse(xhrObj.responseText);
+ if (handler) handler(json);
+ } catch (e) {
+ error(`xhr.onreadystatechange: ${e}`);
+ }
+ } else {
+ err(`xhr.onreadystatechange: state=${xhrObj.readyState} status=${xhrObj.status} response=${xhrObj.responseText}`);
+ }
+ }
+ };
+ const req = JSON.stringify(data);
+ xhrObj.send(req);
+};
+
+const xhrGet = (url, data, handler = undefined, errorHandler = undefined, ignore = false, serverTimeout = 5000) => {
+ const xhr = new XMLHttpRequest();
+ const args = Object.keys(data).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&');
+ xhr.open('GET', `${url}?${args}`, true);
+ xhrInternal(xhr, data, handler, errorHandler, ignore, serverTimeout);
+};
+
+function xhrPost(url, data, handler = undefined, errorHandler = undefined, ignore = false, serverTimeout = 5000) {
+ const xhr = new XMLHttpRequest();
+ xhr.open('POST', url, true);
+ xhrInternal(xhr, data, handler, errorHandler, ignore, serverTimeout);
+}
diff --git a/javascript/notification.js b/javascript/notification.js
index 33e8d1c55..c702c90e7 100644
--- a/javascript/notification.js
+++ b/javascript/notification.js
@@ -4,28 +4,32 @@ let lastHeadImg = null;
let notificationButton = null;
async function sendNotification() {
- if (!notificationButton) {
- notificationButton = gradioApp().getElementById('request_notifications');
- if (notificationButton) notificationButton.addEventListener('click', (evt) => Notification.requestPermission(), true);
+ try {
+ if (!notificationButton) {
+ notificationButton = gradioApp().getElementById('request_notifications');
+ if (notificationButton) notificationButton.addEventListener('click', (evt) => Notification.requestPermission(), true);
+ }
+ if (document.hasFocus()) return; // window is in focus so don't send notifications
+ let galleryPreviews = gradioApp().querySelectorAll('div[id^="tab_"][style*="display: block"] div[id$="_results"] .thumbnail-item > img');
+ if (!galleryPreviews || galleryPreviews.length === 0) galleryPreviews = gradioApp().querySelectorAll('.thumbnail-item > img');
+ if (!galleryPreviews || galleryPreviews.length === 0) return;
+ const headImg = galleryPreviews[0]?.src;
+ if (!headImg || headImg === lastHeadImg || headImg.includes('logo-bg-')) return;
+ const audioNotification = gradioApp().querySelector('#audio_notification audio');
+ if (audioNotification) audioNotification.play();
+ lastHeadImg = headImg;
+ const imgs = new Set(Array.from(galleryPreviews).map((img) => img.src)); // Multiple copies of the images are in the DOM when one is selected
+ const notification = new Notification('SD.Next', {
+ body: `Generated ${imgs.size > 1 ? imgs.size - opts.return_grid : 1} image${imgs.size > 1 ? 's' : ''}`,
+ icon: headImg,
+ image: headImg,
+ });
+ notification.onclick = () => {
+ parent.focus();
+ this.close();
+ };
+ log('sendNotifications');
+ } catch (e) {
+ error(`sendNotification: ${e}`);
}
- if (document.hasFocus()) return; // window is in focus so don't send notifications
- let galleryPreviews = gradioApp().querySelectorAll('div[id^="tab_"][style*="display: block"] div[id$="_results"] .thumbnail-item > img');
- if (!galleryPreviews || galleryPreviews.length === 0) galleryPreviews = gradioApp().querySelectorAll('.thumbnail-item > img');
- if (!galleryPreviews || galleryPreviews.length === 0) return;
- const headImg = galleryPreviews[0]?.src;
- if (!headImg || headImg === lastHeadImg || headImg.includes('logo-bg-')) return;
- const audioNotification = gradioApp().querySelector('#audio_notification audio');
- if (audioNotification) audioNotification.play();
- lastHeadImg = headImg;
- const imgs = new Set(Array.from(galleryPreviews).map((img) => img.src)); // Multiple copies of the images are in the DOM when one is selected
- const notification = new Notification('SD.Next', {
- body: `Generated ${imgs.size > 1 ? imgs.size - opts.return_grid : 1} image${imgs.size > 1 ? 's' : ''}`,
- icon: headImg,
- image: headImg,
- });
- notification.onclick = () => {
- parent.focus();
- this.close();
- };
- log('sendNotifications');
}
diff --git a/javascript/progressBar.js b/javascript/progressBar.js
index a9ecb31e9..c385fe5db 100644
--- a/javascript/progressBar.js
+++ b/javascript/progressBar.js
@@ -1,28 +1,5 @@
let lastState = {};
-function request(url, data, handler, errorHandler) {
- const xhr = new XMLHttpRequest();
- xhr.open('POST', url, true);
- xhr.setRequestHeader('Content-Type', 'application/json');
- xhr.onreadystatechange = () => {
- if (xhr.readyState === 4) {
- if (xhr.status === 200) {
- try {
- const js = JSON.parse(xhr.responseText);
- handler(js);
- } catch (error) {
- console.error(error);
- errorHandler();
- }
- } else {
- errorHandler();
- }
- }
- };
- const js = JSON.stringify(data);
- xhr.send(js);
-}
-
function pad2(x) {
return x < 10 ? `0${x}` : x;
}
@@ -35,8 +12,10 @@ function formatTime(secs) {
function checkPaused(state) {
lastState.paused = state ? !state : !lastState.paused;
- document.getElementById('txt2img_pause').innerText = lastState.paused ? 'Resume' : 'Pause';
- document.getElementById('img2img_pause').innerText = lastState.paused ? 'Resume' : 'Pause';
+ const t_el = document.getElementById('txt2img_pause');
+ const i_el = document.getElementById('img2img_pause');
+ if (t_el) t_el.innerText = lastState.paused ? 'Resume' : 'Pause';
+ if (i_el) i_el.innerText = lastState.paused ? 'Resume' : 'Pause';
}
function setProgress(res) {
@@ -89,28 +68,38 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres
let img;
const initLivePreview = () => {
+ if (!parentGallery) return;
+ const footers = Array.from(gradioApp().querySelectorAll('.gallery_footer'));
+ for (const footer of footers) footer.style.display = 'none'; // remove all footers
+ const galleries = Array.from(gradioApp().querySelectorAll('.gallery_main'));
+ for (const gallery of galleries) gallery.style.display = 'none'; // remove all footers
+
+ livePreview = document.createElement('div');
+ livePreview.className = 'livePreview';
+ parentGallery.insertBefore(livePreview, galleryEl);
img = new Image();
- if (parentGallery) {
- livePreview = document.createElement('div');
- livePreview.className = 'livePreview';
- parentGallery.insertBefore(livePreview, galleryEl);
- const rect = galleryEl.getBoundingClientRect();
- if (rect.width) {
- livePreview.style.width = `${rect.width}px`;
- livePreview.style.height = `${rect.height}px`;
- }
- img.onload = () => {
- livePreview.appendChild(img);
- if (livePreview.childElementCount > 2) livePreview.removeChild(livePreview.firstElementChild);
- };
- }
+ img.id = 'livePreviewImage';
+ livePreview.appendChild(img);
+ img.onload = () => {
+ img.style.width = `min(100%, max(${img.naturalWidth}px, 512px))`;
+ parentGallery.style.minHeight = `${img.height}px`;
+ };
};
const done = () => {
debug('taskEnd:', id_task);
localStorage.removeItem('task');
setProgress();
- if (parentGallery && livePreview) parentGallery.removeChild(livePreview);
+ const footers = Array.from(gradioApp().querySelectorAll('.gallery_footer'));
+ for (const footer of footers) footer.style.display = 'flex'; // restore all footers
+ const galleries = Array.from(gradioApp().querySelectorAll('.gallery_main'));
+ for (const gallery of galleries) gallery.style.display = 'flex'; // remove all galleries
+ try {
+ if (parentGallery && livePreview) {
+ parentGallery.removeChild(livePreview);
+ parentGallery.style.minHeight = 'unset';
+ }
+ } catch { /* ignore */ }
checkPaused(true);
sendNotification();
if (atEnd) atEnd();
@@ -118,20 +107,32 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres
const start = (id_task, id_live_preview) => { // eslint-disable-line no-shadow
if (!opts.live_previews_enable || opts.live_preview_refresh_period === 0 || opts.show_progress_every_n_steps === 0) return;
- request('./internal/progress', { id_task, id_live_preview }, (res) => {
+
+ const onProgressHandler = (res) => {
+ // debug('onProgress', res);
lastState = res;
const elapsedFromStart = (new Date() - dateStart) / 1000;
hasStarted |= res.active;
if (res.completed || (!res.active && (hasStarted || once)) || (elapsedFromStart > 30 && !res.queued && res.progress === prevProgress)) {
+ debug('onProgressEnd', res);
done();
return;
}
setProgress(res);
if (res.live_preview && !livePreview) initLivePreview();
- if (res.live_preview && galleryEl) img.src = res.live_preview;
+ if (res.live_preview && galleryEl) {
+ if (img.src !== res.live_preview) img.src = res.live_preview;
+ }
if (onProgress) onProgress(res);
setTimeout(() => start(id_task, id_live_preview), opts.live_preview_refresh_period || 500);
- }, done);
+ };
+
+ const onProgressErrorHandler = (err) => {
+ error(`onProgressError: ${err}`);
+ done();
+ };
+
+ xhrPost('./internal/progress', { id_task, id_live_preview }, onProgressHandler, onProgressErrorHandler, false, 5000);
};
start(id_task, 0);
}
diff --git a/javascript/script.js b/javascript/script.js
index 104567dd7..250e90ba2 100644
--- a/javascript/script.js
+++ b/javascript/script.js
@@ -1,17 +1,3 @@
-const log = (...msg) => {
- const dt = new Date();
- const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
- if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
- console.log(ts, ...msg); // eslint-disable-line no-console
-};
-
-const debug = (...msg) => {
- const dt = new Date();
- const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`;
- if (window.logger) window.logger.innerHTML += window.logPrettyPrint(...msg);
- console.debug(ts, ...msg); // eslint-disable-line no-console
-};
-
async function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); // eslint-disable-line no-promise-executor-return
}
@@ -82,7 +68,7 @@ function executeCallbacks(queue, arg) {
try {
callback(arg);
} catch (e) {
- console.error('error running callback', callback, ':', e);
+ error(`executeCallbacks: ${callback} ${e}`);
}
}
}
diff --git a/javascript/sdnext.css b/javascript/sdnext.css
index 08fae2eb8..c5145c973 100644
--- a/javascript/sdnext.css
+++ b/javascript/sdnext.css
@@ -16,7 +16,7 @@ tr { border-bottom: none !important; padding: 0 0.5em !important; }
td > div > span { overflow-y: auto; max-height: 3em; overflow-x: hidden; }
textarea { overflow-y: auto !important; }
span { font-size: var(--text-md) !important; }
-button { font-size: var(--text-lg) !important; }
+button { font-size: var(--text-lg) !important; min-width: unset !important; }
input[type='color'] { width: 64px; height: 32px; }
input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { margin-left: 4px; }
@@ -30,6 +30,19 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { margin-left
.hidden { display: none; }
.tabitem { padding: 0 !important; }
+/* gradio image/canvas elements */
+.image-container { overflow: auto; }
+/*
+.gradio-image { min-height: fit-content; }
+.gradio-image img { object-fit: contain; }
+*/
+/*
+.gradio-image { min-height: 200px !important; }
+.image-container { height: unset !important; }
+.control-image { height: unset !important; }
+#img2img_sketch, #img2maskimg, #inpaint_sketch { overflow: overlay !important; resize: auto; background: var(--panel-background-fill); z-index: 5; }
+*/
+
/* color elements */
.gradio-dropdown, .block.gradio-slider, .block.gradio-checkbox, .block.gradio-textbox, .block.gradio-radio, .block.gradio-checkboxgroup, .block.gradio-number, .block.gradio-colorpicker { border-width: 0 !important; box-shadow: none !important;}
.gradio-accordion { padding-top: var(--spacing-md) !important; padding-right: 0 !important; padding-bottom: 0 !important; color: var(--body-text-color); }
@@ -83,13 +96,12 @@ button.custom-button { border-radius: var(--button-large-radius); padding: var(-
.block.token-counter div{ display: inline; }
.block.token-counter span{ padding: 0.1em 0.75em; }
.performance { font-size: var(--text-xs); color: #444; }
-.performance p { display: inline-block; color: var(--body-text-color-subdued) !important }
+.performance p { display: inline-block; color: var(--primary-500) !important }
.performance .time { margin-right: 0; }
.thumbnails { background: var(--body-background-fill); }
-.control-image { height: calc(100vw/3) !important; }
.prompt textarea { resize: vertical; }
+.grid-wrap { overflow-y: auto !important; }
#control_results { margin: 0; padding: 0; }
-#control_gallery { height: calc(100vw/3 + 60px); }
#txt2img_gallery, #img2img_gallery { height: 50vh; }
#control-result { background: var(--button-secondary-background-fill); padding: 0.2em; }
#control-inputs { margin-top: 1em; }
@@ -105,7 +117,6 @@ button.custom-button { border-radius: var(--button-large-radius); padding: var(-
#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt, #control_prompt, #control_neg_prompt { display: contents; }
#txt2img_actions_column, #img2img_actions_column, #control_actions { flex-flow: wrap; justify-content: space-between; }
-
.interrogate-clip { position: absolute; right: 6em; top: 8px; max-width: fit-content; background: none !important; z-index: 50; }
.interrogate-blip { position: absolute; right: 4em; top: 8px; max-width: fit-content; background: none !important; z-index: 50; }
.interrogate-col { min-width: 0 !important; max-width: fit-content; margin-right: var(--spacing-xxl); }
@@ -118,11 +129,9 @@ div#extras_scale_to_tab div.form { flex-direction: row; }
#img2img_unused_scale_by_slider { visibility: hidden; width: 0.5em; max-width: 0.5em; min-width: 0.5em; }
.inactive{ opacity: 0.5; }
div#extras_scale_to_tab div.form { flex-direction: row; }
-#mode_img2img .gradio-image>div.fixed-height, #mode_img2img .gradio-image>div.fixed-height img{ height: 480px !important; max-height: 480px !important; min-height: 480px !important; }
-#img2img_sketch, #img2maskimg, #inpaint_sketch { overflow: overlay !important; resize: auto; background: var(--panel-background-fill); z-index: 5; }
.image-buttons button { min-width: auto; }
.infotext { overflow-wrap: break-word; line-height: 1.5em; font-size: 0.95em !important; }
-.infotext > p { padding-left: 1em; text-indent: -1em; white-space: pre-wrap; color: var(--block-info-text-color) !important; }
+.infotext > p { white-space: pre-wrap; color: var(--block-info-text-color) !important; }
.tooltip { display: block; position: fixed; top: 1em; right: 1em; padding: 0.5em; background: var(--input-background-fill); color: var(--body-text-color); border: 1pt solid var(--button-primary-border-color);
width: 22em; min-height: 1.3em; font-size: var(--text-xs); transition: opacity 0.2s ease-in; pointer-events: none; opacity: 0; z-index: 999; }
.tooltip-show { opacity: 0.9; }
@@ -158,11 +167,10 @@ div#extras_scale_to_tab div.form { flex-direction: row; }
.progressDiv { position: relative; height: 20px; background: #b4c0cc; margin-bottom: -3px; }
.dark .progressDiv { background: #424c5b; }
.progressDiv .progress { width: 0%; height: 20px; background: #0060df; color: white; font-weight: bold; line-height: 20px; padding: 0 8px 0 0; text-align: right; overflow: visible; white-space: nowrap; padding: 0 0.5em; }
-.livePreview { position: absolute; z-index: 50; background-color: transparent; width: -moz-available; width: -webkit-fill-available; }
-.livePreview img { position: absolute; object-fit: contain; width: 100%; height: 100%; }
-.dark .livePreview { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); }
+.livePreview { position: absolute; z-index: 50; width: -moz-available; width: -webkit-fill-available; height: 100%; background-color: var(--background-color); }
+.livePreview img { object-fit: contain; width: 100%; justify-self: center; }
.popup-metadata { color: white; background: #0000; display: inline-block; white-space: pre-wrap; font-size: var(--text-xxs); }
-
+.generating { animation: unset !important; border: unset !important; }
/* fullpage image viewer */
#lightboxModal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; overflow: hidden; background-color: rgba(20, 20, 20, 0.75); backdrop-filter: blur(6px);
user-select: none; -webkit-user-select: none; flex-direction: row; font-family: 'NotoSans';}
@@ -380,8 +388,6 @@ div:has(>#tab-gallery-folders) { flex-grow: 0 !important; background-color: var(
#img2img_actions_column { display: flex; min-width: fit-content !important; flex-direction: row;justify-content: space-evenly; align-items: center;}
#txt2img_generate_box, #img2img_generate_box, #txt2img_enqueue_wrapper,#img2img_enqueue_wrapper {display: flex;flex-direction: column;height: 4em !important;align-items: stretch;justify-content: space-evenly;}
#img2img_interface, #img2img_results, #img2img_footer p { text-wrap: wrap; min-width: 100% !important; max-width: 100% !important;} /* maintain single column for from image operations on larger mobile devices */
- #img2img_sketch, #img2maskimg, #inpaint_sketch {display: flex; overflow: auto !important; resize: none !important; } /* fix inpaint image display being too large for mobile displays */
- #img2maskimg canvas { width: auto !important; max-height: 100% !important; height: auto !important; }
#txt2img_sampler, #txt2img_batch, #txt2img_seed_group, #txt2img_advanced, #txt2img_second_pass, #img2img_sampling_group, #img2img_resize_group, #img2img_batch_group, #img2img_seed_group, #img2img_denoise_group, #img2img_advanced_group { width: 100% !important; } /* fix from text/image UI elements to prevent them from moving around within the UI */
#img2img_resize_group .gradio-radio>div { display: flex; flex-direction: column; width: unset !important; }
#inpaint_controls div { display:flex;flex-direction: row;}
diff --git a/javascript/ui.js b/javascript/ui.js
index 8808f1c8b..3e3f14390 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -28,7 +28,7 @@ function clip_gallery_urls(gallery) {
const files = gallery.map((v) => v.data);
navigator.clipboard.writeText(JSON.stringify(files)).then(
() => log('clipboard:', files),
- (err) => console.error('clipboard:', files, err),
+ (err) => error(`clipboard: ${files} ${err}`),
);
}
@@ -139,7 +139,7 @@ function switch_to_inpaint(...args) {
return Array.from(arguments);
}
-function switch_to_inpaint_sketch(...args) {
+function switch_to_composite(...args) {
switchToTab('Image');
switch_to_img2img_tab(3);
return Array.from(arguments);
@@ -493,9 +493,9 @@ function previewTheme() {
el.src = `/file=html/${name}.jpg`;
}
})
- .catch((e) => console.error('previewTheme:', e));
+ .catch((e) => error(`previewTheme: ${e}`));
})
- .catch((e) => console.error('previewTheme:', e));
+ .catch((e) => error(`previewTheme: ${e}`));
}
async function browseFolder() {
diff --git a/launch.py b/launch.py
index f944a7e54..e00da58c7 100755
--- a/launch.py
+++ b/launch.py
@@ -55,9 +55,11 @@ def get_custom_args():
if 'PS1' in env:
del env['PS1']
installer.log.trace(f'Environment: {installer.print_dict(env)}')
- else:
- env = [f'{k}={v}' for k, v in os.environ.items() if k.startswith('SD_')]
- installer.log.debug(f'Env flags: {env}')
+ env = [f'{k}={v}' for k, v in os.environ.items() if k.startswith('SD_')]
+ installer.log.debug(f'Env flags: {env}')
+ ldd = os.environ.get('LD_PRELOAD', None)
+ if ldd is not None:
+ installer.log.debug(f'Linker flags: "{ldd}"')
@lru_cache()
diff --git a/modules/api/api.py b/modules/api/api.py
index f8346995d..d48cbf521 100644
--- a/modules/api/api.py
+++ b/modules/api/api.py
@@ -35,7 +35,8 @@ class Api:
# server api
self.add_api_route("/sdapi/v1/motd", server.get_motd, methods=["GET"], response_model=str)
- self.add_api_route("/sdapi/v1/log", server.get_log_buffer, methods=["GET"], response_model=List[str])
+ self.add_api_route("/sdapi/v1/log", server.get_log, methods=["GET"], response_model=List[str])
+ self.add_api_route("/sdapi/v1/log", server.post_log, methods=["POST"])
self.add_api_route("/sdapi/v1/start", self.get_session_start, methods=["GET"])
self.add_api_route("/sdapi/v1/version", server.get_version, methods=["GET"])
self.add_api_route("/sdapi/v1/status", server.get_status, methods=["GET"], response_model=models.ResStatus)
diff --git a/modules/api/control.py b/modules/api/control.py
index 29c5a77f1..345930341 100644
--- a/modules/api/control.py
+++ b/modules/api/control.py
@@ -159,6 +159,8 @@ class APIControl():
output_images = []
output_processed = []
output_info = ''
+ # TODO control script process
+ # init script args, call scripts.script_control.run, call scripts.script_control.after
run.control_set({ 'do_not_save_grid': not req.save_images, 'do_not_save_samples': not req.save_images, **self.prepare_ip_adapter(req) })
run.control_set(getattr(req, "extra", {}))
res = run.control_run(**args)
diff --git a/modules/api/generate.py b/modules/api/generate.py
index b8ee645a4..9b409a14b 100644
--- a/modules/api/generate.py
+++ b/modules/api/generate.py
@@ -116,6 +116,8 @@ class APIGenerate():
processed = scripts.scripts_txt2img.run(p, *script_args) # Need to pass args as list here
else:
processed = process_images(p)
+ processed = scripts.scripts_txt2img.after(p, processed, *script_args)
+ p.close()
shared.state.end(api=False)
if processed is None or processed.images is None or len(processed.images) == 0:
b64images = []
@@ -166,6 +168,8 @@ class APIGenerate():
processed = scripts.scripts_img2img.run(p, *script_args) # Need to pass args as list here
else:
processed = process_images(p)
+ processed = scripts.scripts_img2img.after(p, processed, *script_args)
+ p.close()
shared.state.end(api=False)
if processed is None or processed.images is None or len(processed.images) == 0:
b64images = []
diff --git a/modules/api/models.py b/modules/api/models.py
index e68ebf081..39bcbe383 100644
--- a/modules/api/models.py
+++ b/modules/api/models.py
@@ -286,10 +286,16 @@ class ResImageInfo(BaseModel):
items: dict = Field(title="Items", description="A dictionary containing all the other fields the image had")
parameters: dict = Field(title="Parameters", description="A dictionary with parsed generation info fields")
-class ReqLog(BaseModel):
+class ReqGetLog(BaseModel):
lines: int = Field(default=100, title="Lines", description="How many lines to return")
clear: bool = Field(default=False, title="Clear", description="Should the log be cleared after returning the lines?")
+
+class ReqPostLog(BaseModel):
+ message: Optional[str] = Field(title="Message", description="The info message to log")
+ debug: Optional[str] = Field(title="Debug message", description="The debug message to log")
+ error: Optional[str] = Field(title="Error message", description="The error message to log")
+
class ReqProgress(BaseModel):
skip_current_image: bool = Field(default=False, title="Skip current image", description="Skip current image serialization")
diff --git a/modules/api/server.py b/modules/api/server.py
index 939e19c86..dabbe634c 100644
--- a/modules/api/server.py
+++ b/modules/api/server.py
@@ -37,12 +37,22 @@ def get_platform():
from modules.loader import get_packages as loader_get_packages
return { **installer_get_platform(), **loader_get_packages() }
-def get_log_buffer(req: models.ReqLog = Depends()):
+def get_log(req: models.ReqGetLog = Depends()):
lines = shared.log.buffer[:req.lines] if req.lines > 0 else shared.log.buffer.copy()
if req.clear:
shared.log.buffer.clear()
return lines
+def post_log(req: models.ReqPostLog):
+ if req.message is not None:
+ shared.log.info(f'UI: {req.message}')
+ if req.debug is not None:
+ shared.log.debug(f'UI: {req.debug}')
+ if req.error is not None:
+ shared.log.error(f'UI: {req.error}')
+ return {}
+
+
def get_config():
options = {}
for k in shared.opts.data.keys():
diff --git a/modules/call_queue.py b/modules/call_queue.py
index 4065d13d9..cdc2fe1f7 100644
--- a/modules/call_queue.py
+++ b/modules/call_queue.py
@@ -2,7 +2,7 @@ import html
import threading
import time
import cProfile
-from modules import shared, progress, errors
+from modules import shared, progress, errors, timer
queue_lock = threading.Lock()
@@ -73,15 +73,16 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False, name=None):
elapsed_m = int(elapsed // 60)
elapsed_s = elapsed % 60
elapsed_text = f"{elapsed_m}m {elapsed_s:.2f}s" if elapsed_m > 0 else f"{elapsed_s:.2f}s"
+ summary = timer.process.summary(min_time=0.1, total=False).replace('=', ' ')
vram_html = ''
if not shared.mem_mon.disabled:
vram = {k: -(v//-(1024*1024)) for k, v in shared.mem_mon.read().items()}
+ used = round(100 * vram['used'] / (vram['total'] + 0.001))
if vram.get('active_peak', 0) > 0:
- vram_html = " | "
- vram_html += f"GPU active {max(vram['active_peak'], vram['reserved_peak'])} MB reserved {vram['reserved']} | used {vram['used']} MB free {vram['free']} MB total {vram['total']} MB"
+ vram_html = " | "
+ vram_html += f"GPU {max(vram['active_peak'], vram['reserved_peak'])} MB {used}%"
vram_html += f" | retries {vram['retries']} oom {vram['oom']}" if vram.get('retries', 0) > 0 or vram.get('oom', 0) > 0 else ''
- vram_html += "
"
if isinstance(res, list):
- res[-1] += f""
+ res[-1] += f""
return tuple(res)
return f
diff --git a/modules/control/run.py b/modules/control/run.py
index 5d6343c98..2fe13dd73 100644
--- a/modules/control/run.py
+++ b/modules/control/run.py
@@ -87,7 +87,6 @@ def control_run(state: str = '',
u.process.override = u.override
global instance, pipe, original_pipeline # pylint: disable=global-statement
- t_start = time.time()
debug(f'Control: type={unit_type} input={inputs} init={inits} type={input_type}')
if inputs is None or (type(inputs) is list and len(inputs) == 0):
inputs = [None]
@@ -717,14 +716,11 @@ def control_run(state: str = '',
shared.log.error(f'Control pipeline failed: type={unit_type} units={len(active_model)} error={e}')
errors.display(e, 'Control')
- t_end = time.time()
-
if len(output_images) == 0:
output_images = None
image_txt = '| Images None'
else:
- image_str = [f'{image.width}x{image.height}' for image in output_images]
- image_txt = f'| Time {t_end-t_start:.2f}s | Images {len(output_images)} | Size {" ".join(image_str)}'
+ image_txt = ''
p.init_images = output_images # may be used for hires
if video_type != 'None' and isinstance(output_images, list):
@@ -738,7 +734,7 @@ def control_run(state: str = '',
restore_pipeline()
debug(f'Ready: {image_txt}')
- html_txt = f'Ready {image_txt}
'
+ html_txt = f'Ready {image_txt}
' if image_txt != '' else ''
if len(info_txt) > 0:
html_txt = html_txt + infotext_to_html(info_txt[0])
if is_generator:
diff --git a/modules/control/units/controlnet.py b/modules/control/units/controlnet.py
index 20b99412a..3f68a4896 100644
--- a/modules/control/units/controlnet.py
+++ b/modules/control/units/controlnet.py
@@ -85,6 +85,9 @@ predefined_f1 = {
"XLabs-AI HED": 'XLabs-AI/flux-controlnet-hed-diffusers'
}
predefined_sd3 = {
+ "StabilityAI Canny": 'diffusers-internal-dev/sd35-controlnet-canny-8b',
+ "StabilityAI Depth": 'diffusers-internal-dev/sd35-controlnet-depth-8b',
+ "StabilityAI Blur": 'diffusers-internal-dev/sd35-controlnet-blur-8b',
"InstantX Canny": 'InstantX/SD3-Controlnet-Canny',
"InstantX Pose": 'InstantX/SD3-Controlnet-Pose',
"InstantX Depth": 'InstantX/SD3-Controlnet-Depth',
diff --git a/modules/devices.py b/modules/devices.py
index 56ac50091..9ca1863a5 100644
--- a/modules/devices.py
+++ b/modules/devices.py
@@ -471,7 +471,7 @@ def set_cuda_params():
device_name = get_raw_openvino_device()
else:
device_name = torch.device(get_optimal_device_name())
- log.info(f'Torch parameters: backend={backend} device={device_name} config={opts.cuda_dtype} dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__} nohalf={opts.no_half} nohalfvae={opts.no_half_vae} upscast={opts.upcast_sampling} deterministic={opts.cudnn_deterministic} test-fp16={fp16_ok} test-bf16={bf16_ok} optimization="{opts.cross_attention_optimization}"')
+ log.info(f'Torch parameters: backend={backend} device={device_name} config={opts.cuda_dtype} dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__} nohalf={opts.no_half} nohalfvae={opts.no_half_vae} upcast={opts.upcast_sampling} deterministic={opts.cudnn_deterministic} test-fp16={fp16_ok} test-bf16={bf16_ok} optimization="{opts.cross_attention_optimization}"')
def cond_cast_unet(tensor):
diff --git a/modules/img2img.py b/modules/img2img.py
index 8274386cc..077df1259 100644
--- a/modules/img2img.py
+++ b/modules/img2img.py
@@ -164,12 +164,7 @@ def img2img(id_task: str, state: str, mode: int,
return [], '', '', 'Error: init image not provided'
image = init_img.convert("RGB")
mask = None
- elif mode == 1: # img2img sketch
- if sketch is None:
- return [], '', '', 'Error: sketch image not provided'
- image = sketch.convert("RGB")
- mask = None
- elif mode == 2: # inpaint
+ elif mode == 1: # inpaint
if init_img_with_mask is None:
return [], '', '', 'Error: init image with mask not provided'
image = init_img_with_mask["image"]
@@ -177,7 +172,12 @@ def img2img(id_task: str, state: str, mode: int,
alpha_mask = ImageOps.invert(image.split()[-1]).convert('L').point(lambda x: 255 if x > 0 else 0, mode='1')
mask = ImageChops.lighter(alpha_mask, mask.convert('L')).convert('L')
image = image.convert("RGB")
- elif mode == 3: # inpaint sketch
+ elif mode == 2: # sketch
+ if sketch is None:
+ return [], '', '', 'Error: sketch image not provided'
+ image = sketch.convert("RGB")
+ mask = None
+ elif mode == 3: # composite
if inpaint_color_sketch is None:
return [], '', '', 'Error: color sketch image not provided'
image = inpaint_color_sketch
diff --git a/modules/memmon.py b/modules/memmon.py
index 6887e1e1c..d9fa3963d 100644
--- a/modules/memmon.py
+++ b/modules/memmon.py
@@ -42,14 +42,14 @@ class MemUsageMonitor():
if not self.disabled:
try:
self.data["free"], self.data["total"] = torch.cuda.mem_get_info(self.device.index if self.device.index is not None else torch.cuda.current_device())
+ self.data["used"] = self.data["total"] - self.data["free"]
torch_stats = torch.cuda.memory_stats(self.device)
- self.data["active"] = torch_stats["active.all.current"]
+ self.data["active"] = torch_stats.get("active.all.current", torch_stats["active_bytes.all.current"])
self.data["active_peak"] = torch_stats["active_bytes.all.peak"]
self.data["reserved"] = torch_stats["reserved_bytes.all.current"]
self.data["reserved_peak"] = torch_stats["reserved_bytes.all.peak"]
- self.data['retries'] = torch_stats["num_alloc_retries"]
- self.data['oom'] = torch_stats["num_ooms"]
- self.data["used"] = self.data["total"] - self.data["free"]
+ self.data['retries'] = torch_stats.get("num_alloc_retries", -1)
+ self.data['oom'] = torch_stats.get("num_ooms", -1)
except Exception:
self.disabled = True
return self.data
diff --git a/modules/model_flux.py b/modules/model_flux.py
index 17234d9a4..324e50b36 100644
--- a/modules/model_flux.py
+++ b/modules/model_flux.py
@@ -306,9 +306,17 @@ def load_flux(checkpoint_info, diffusers_load_config): # triggered by opts.sd_ch
model_te.loaded_te = shared.opts.sd_text_encoder
if vae is not None:
kwargs['vae'] = vae
- shared.log.debug(f'Load model: type=FLUX preloaded={list(kwargs)}')
if repo_id == 'sayakpaul/flux.1-dev-nf4':
repo_id = 'black-forest-labs/FLUX.1-dev' # workaround since sayakpaul model is missing model_index.json
+ if 'Fill' in repo_id:
+ cls = diffusers.FluxFillPipeline
+ elif 'Canny' in repo_id:
+ cls = diffusers.FluxControlPipeline
+ elif 'Depth' in repo_id:
+ cls = diffusers.FluxControlPipeline
+ else:
+ cls = diffusers.FluxPipeline
+ shared.log.debug(f'Load model: type=FLUX cls={cls.__name__} preloaded={list(kwargs)} revision={diffusers_load_config.get("revision", None)}')
for c in kwargs:
if kwargs[c].dtype == torch.float32 and devices.dtype != torch.float32:
shared.log.warning(f'Load model: type=FLUX component={c} dtype={kwargs[c].dtype} cast dtype={devices.dtype} recast')
@@ -319,7 +327,7 @@ def load_flux(checkpoint_info, diffusers_load_config): # triggered by opts.sd_ch
if checkpoint_info.path.endswith('.safetensors') and os.path.isfile(checkpoint_info.path):
pipe = diffusers.FluxPipeline.from_single_file(checkpoint_info.path, cache_dir=shared.opts.diffusers_dir, **kwargs, **diffusers_load_config)
else:
- pipe = diffusers.FluxPipeline.from_pretrained(repo_id, cache_dir=shared.opts.diffusers_dir, **kwargs, **diffusers_load_config)
+ pipe = cls.from_pretrained(repo_id, cache_dir=shared.opts.diffusers_dir, **kwargs, **diffusers_load_config)
# release memory
transformer = None
diff --git a/modules/model_quant.py b/modules/model_quant.py
index 0e7bdd4b3..9482fe898 100644
--- a/modules/model_quant.py
+++ b/modules/model_quant.py
@@ -5,6 +5,7 @@ from installer import install, log
bnb = None
quanto = None
+ao = None
def create_bnb_config(kwargs = None, allow_bnb: bool = True):
@@ -12,6 +13,8 @@ def create_bnb_config(kwargs = None, allow_bnb: bool = True):
if len(shared.opts.bnb_quantization) > 0 and allow_bnb:
if 'Model' in shared.opts.bnb_quantization:
load_bnb()
+ if bnb is None:
+ return kwargs
bnb_config = diffusers.BitsAndBytesConfig(
load_in_8bit=shared.opts.bnb_quantization_type in ['fp8'],
load_in_4bit=shared.opts.bnb_quantization_type in ['nf4', 'fp4'],
@@ -28,6 +31,44 @@ def create_bnb_config(kwargs = None, allow_bnb: bool = True):
return kwargs
+def create_ao_config(kwargs = None, allow_ao: bool = True):
+ from modules import shared
+ if len(shared.opts.torchao_quantization) > 0 and shared.opts.torchao_quantization_mode == 'pre' and allow_ao:
+ if 'Model' in shared.opts.torchao_quantization:
+ load_torchao()
+ if ao is None:
+ return kwargs
+ ao_config = {}
+ # ao_config = diffusers.TorchAoConfig("int8wo") # TODO torchao
+ shared.log.debug(f'Quantization: module=all type=bnb dtype={shared.opts.torchao_quantization_type}')
+ if kwargs is None:
+ return ao_config
+ else:
+ kwargs['quantization_config'] = ao_config
+ return kwargs
+ return kwargs
+
+
+def load_torchao(msg='', silent=False):
+ global ao # pylint: disable=global-statement
+ if ao is not None:
+ return ao
+ install('torchao', quiet=True)
+ try:
+ import torchao
+ ao = torchao
+ fn = f'{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access
+ log.debug(f'Quantization: type=quanto version={ao.__version__} fn={fn}') # pylint: disable=protected-access
+ return ao
+ except Exception as e:
+ if len(msg) > 0:
+ log.error(f"{msg} failed to import optimum.quanto: {e}")
+ ao = None
+ if not silent:
+ raise
+ return None
+
+
def load_bnb(msg='', silent=False):
global bnb # pylint: disable=global-statement
if bnb is not None:
diff --git a/modules/model_sd3.py b/modules/model_sd3.py
index b9d579085..ba036760a 100644
--- a/modules/model_sd3.py
+++ b/modules/model_sd3.py
@@ -150,6 +150,7 @@ def load_sd3(checkpoint_info, cache_dir=None, config=None):
shared.log.debug(f'Load model: type=SD3 kwargs={list(kwargs)} repo="{repo_id}"')
kwargs = model_quant.create_bnb_config(kwargs)
+ kwargs = model_quant.create_ao_config(kwargs)
pipe = loader(
repo_id,
torch_dtype=devices.dtype,
diff --git a/modules/modelloader.py b/modules/modelloader.py
index ce36a739b..b1b3930d6 100644
--- a/modules/modelloader.py
+++ b/modules/modelloader.py
@@ -326,6 +326,9 @@ def find_diffuser(name: str, full=False):
return [repo[0]['name']]
hf_api = hf.HfApi()
models = list(hf_api.list_models(model_name=name, library=['diffusers'], full=True, limit=20, sort="downloads", direction=-1))
+ if len(models) == 0:
+ models = list(hf_api.list_models(model_name=name, full=True, limit=20, sort="downloads", direction=-1)) # widen search
+ models = [m for m in models if m.id.startswith(name)] # filter exact
shared.log.debug(f'Searching diffusers models: {name} {len(models) > 0}')
if len(models) > 0:
if not full:
diff --git a/modules/postprocess/yolo.py b/modules/postprocess/yolo.py
index f42b6bb9f..5deab1282 100644
--- a/modules/postprocess/yolo.py
+++ b/modules/postprocess/yolo.py
@@ -72,7 +72,7 @@ class YoloRestorer(Detailer):
imgsz: int = 640,
half: bool = True,
device = devices.device,
- augment: bool = True,
+ augment: bool = shared.opts.detailer_augment,
agnostic: bool = False,
retina: bool = False,
mask: bool = True,
diff --git a/modules/processing.py b/modules/processing.py
index 0d557e64e..16e7a9213 100644
--- a/modules/processing.py
+++ b/modules/processing.py
@@ -323,7 +323,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
processed = p.scripts.process_images(p)
if processed is not None:
samples = processed.images
- infotexts = processed.infotexts
+ infotexts += processed.infotexts
if samples is None:
if not shared.native:
from modules.processing_original import process_original
@@ -393,11 +393,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
if shared.opts.mask_apply_overlay:
image = apply_overlay(image, p.paste_to, i, p.overlay_images)
- if len(infotexts) > i:
- info = infotexts[i]
- else:
- info = create_infotext(p, p.prompts, p.seeds, p.subseeds, index=i, all_negative_prompts=p.negative_prompts)
- infotexts.append(info)
+ info = create_infotext(p, p.prompts, p.seeds, p.subseeds, index=i, all_negative_prompts=p.negative_prompts)
+ infotexts.append(info)
image.info["parameters"] = info
output_images.append(image)
if shared.opts.samples_save and not p.do_not_save_samples and p.outpath_samples is not None:
diff --git a/modules/processing_args.py b/modules/processing_args.py
index ff766ec04..a716b685e 100644
--- a/modules/processing_args.py
+++ b/modules/processing_args.py
@@ -135,7 +135,7 @@ def set_pipeline_args(p, model, prompts: list, negative_prompts: list, prompts_2
prompts = [p.replace('|image|', '<|image_1|>') for p in prompts]
if hasattr(model, 'text_encoder') and hasattr(model, 'tokenizer') and 'prompt_embeds' in possible and prompt_parser_diffusers.embedder is not None:
args['prompt_embeds'] = prompt_parser_diffusers.embedder('prompt_embeds')
- if 'StableCascade' in model.__class__.__name__ and len(getattr(p, 'negative_pooleds', [])) > 0:
+ if 'StableCascade' in model.__class__.__name__ and prompt_parser_diffusers.embedder is not None:
args['prompt_embeds_pooled'] = prompt_parser_diffusers.embedder('positive_pooleds').unsqueeze(0)
elif 'XL' in model.__class__.__name__ and prompt_parser_diffusers.embedder is not None:
args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds')
diff --git a/modules/processing_callbacks.py b/modules/processing_callbacks.py
index f6e3c0672..0bb94abad 100644
--- a/modules/processing_callbacks.py
+++ b/modules/processing_callbacks.py
@@ -7,7 +7,8 @@ from modules import shared, processing_correction, extra_networks, timer, prompt
from modules.lora.networks import network_load
p = None
-debug_callback = shared.log.trace if os.environ.get('SD_CALLBACK_DEBUG', None) is not None else lambda *args, **kwargs: None
+debug = os.environ.get('SD_CALLBACK_DEBUG', None) is not None
+debug_callback = shared.log.trace if debug else lambda *args, **kwargs: None
def set_callbacks_p(processing):
@@ -51,7 +52,8 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {}
if p is None:
return kwargs
latents = kwargs.get('latents', None)
- debug_callback(f'Callback: step={step} timestep={timestep} latents={latents.shape if latents is not None else None} kwargs={list(kwargs)}')
+ if debug:
+ debug_callback(f'Callback: step={step} timestep={timestep} latents={latents.shape if latents is not None else None} kwargs={list(kwargs)}')
order = getattr(pipe.scheduler, "order", 1) if hasattr(pipe, 'scheduler') else 1
shared.state.sampling_step = step // order
if shared.state.interrupted or shared.state.skipped:
@@ -69,7 +71,7 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {}
return kwargs
elif shared.opts.nan_skip:
assert not torch.isnan(latents[..., 0, 0]).all(), f'NaN detected at step {step}: Skipping...'
- if len(getattr(p, 'ip_adapter_names', [])) > 0:
+ if len(getattr(p, 'ip_adapter_names', [])) > 0 and p.ip_adapter_names[0] != 'None':
ip_adapter_scales = list(p.ip_adapter_scales)
ip_adapter_starts = list(p.ip_adapter_starts)
ip_adapter_ends = list(p.ip_adapter_ends)
@@ -80,7 +82,7 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {}
debug_callback(f"Callback: IP Adapter scales={ip_adapter_scales}")
pipe.set_ip_adapter_scale(ip_adapter_scales)
if step != getattr(pipe, 'num_timesteps', 0):
- kwargs = processing_correction.correction_callback(p, timestep, kwargs)
+ kwargs = processing_correction.correction_callback(p, timestep, kwargs, initial=step == 0)
kwargs = prompt_callback(step, kwargs) # monkey patch for diffusers callback issues
if step == int(getattr(pipe, 'num_timesteps', 100) * p.cfg_end) and 'prompt_embeds' in kwargs and 'negative_prompt_embeds' in kwargs:
if "PAG" in shared.sd_model.__class__.__name__:
@@ -107,7 +109,5 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {}
if shared.cmd_opts.profile and shared.profiler is not None:
shared.profiler.step()
t1 = time.time()
- if 'callback' not in timer.process.records:
- timer.process.records['callback'] = 0
- timer.process.records['callback'] += t1 - t0
+ timer.process.add('callback', t1 - t0)
return kwargs
diff --git a/modules/processing_class.py b/modules/processing_class.py
index 79f51576f..21e86c1b0 100644
--- a/modules/processing_class.py
+++ b/modules/processing_class.py
@@ -31,8 +31,8 @@ class StableDiffusionProcessing:
n_iter: int = 1,
steps: int = 50,
clip_skip: int = 1,
- width: int = 512,
- height: int = 512,
+ width: int = 1024,
+ height: int = 1024,
# samplers
sampler_index: int = None, # pylint: disable=unused-argument # used only to set sampler_name
sampler_name: str = None,
diff --git a/modules/processing_correction.py b/modules/processing_correction.py
index e715d8c49..050fae889 100644
--- a/modules/processing_correction.py
+++ b/modules/processing_correction.py
@@ -7,9 +7,11 @@ import os
import torch
from modules import shared, sd_vae_taesd, devices
+
debug_enabled = os.environ.get('SD_HDR_DEBUG', None) is not None
debug = shared.log.trace if debug_enabled else lambda *args, **kwargs: None
debug('Trace: HDR')
+skip_correction = False
def sharpen_tensor(tensor, ratio=0):
@@ -116,8 +118,15 @@ def correction(p, timestep, latent):
return latent
-def correction_callback(p, timestep, kwargs):
- if not any([p.hdr_clamp, p.hdr_mode, p.hdr_maximize, p.hdr_sharpen, p.hdr_color, p.hdr_brightness, p.hdr_tint_ratio]):
+def correction_callback(p, timestep, kwargs, initial: bool = False):
+ global skip_correction # pylint: disable=global-statement
+ if initial:
+ if not any([p.hdr_clamp, p.hdr_mode, p.hdr_maximize, p.hdr_sharpen, p.hdr_color, p.hdr_brightness, p.hdr_tint_ratio]):
+ skip_correction = True
+ return kwargs
+ else:
+ skip_correction = False
+ elif skip_correction:
return kwargs
latents = kwargs["latents"]
if debug_enabled:
diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py
index 83d3b1b69..2e8fb357c 100644
--- a/modules/processing_diffusers.py
+++ b/modules/processing_diffusers.py
@@ -77,7 +77,6 @@ def process_base(p: processing.StableDiffusionProcessing):
clip_skip=p.clip_skip,
desc='Base',
)
- timer.process.record('args')
shared.state.sampling_steps = base_args.get('prior_num_inference_steps', None) or p.steps or base_args.get('num_inference_steps', None)
if shared.opts.scheduler_eta is not None and shared.opts.scheduler_eta > 0 and shared.opts.scheduler_eta < 1:
p.extra_generation_params["Sampler Eta"] = shared.opts.scheduler_eta
@@ -233,7 +232,8 @@ def process_hires(p: processing.StableDiffusionProcessing, output):
output = shared.sd_model(**hires_args) # pylint: disable=not-callable
if isinstance(output, dict):
output = SimpleNamespace(**output)
- shared.history.add(output.images, info=processing.create_infotext(p), ops=p.ops)
+ if hasattr(output, 'images'):
+ shared.history.add(output.images, info=processing.create_infotext(p), ops=p.ops)
sd_models_compile.check_deepcache(enable=False)
sd_models_compile.openvino_post_compile(op="base")
except AssertionError as e:
@@ -315,7 +315,8 @@ def process_refine(p: processing.StableDiffusionProcessing, output):
output = shared.sd_refiner(**refiner_args) # pylint: disable=not-callable
if isinstance(output, dict):
output = SimpleNamespace(**output)
- shared.history.add(output.images, info=processing.create_infotext(p), ops=p.ops)
+ if hasattr(output, 'images'):
+ shared.history.add(output.images, info=processing.create_infotext(p), ops=p.ops)
sd_models_compile.openvino_post_compile(op="refiner")
except AssertionError as e:
shared.log.info(e)
@@ -353,7 +354,7 @@ def process_decode(p: processing.StableDiffusionProcessing, output):
if not hasattr(model, 'vae'):
if hasattr(model, 'pipe') and hasattr(model.pipe, 'vae'):
model = model.pipe
- if hasattr(model, "vae") and output.images is not None and len(output.images) > 0:
+ if (hasattr(model, "vae") or hasattr(model, "vqgan")) and output.images is not None and len(output.images) > 0:
if p.hr_resize_mode > 0 and (p.hr_upscaler != 'None' or p.hr_resize_mode == 5):
width = max(getattr(p, 'width', 0), getattr(p, 'hr_upscale_to_x', 0))
height = max(getattr(p, 'height', 0), getattr(p, 'hr_upscale_to_y', 0))
diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py
index ec7fbf048..22acf296c 100644
--- a/modules/processing_helpers.py
+++ b/modules/processing_helpers.py
@@ -561,7 +561,9 @@ def save_intermediate(p, latents, suffix):
def update_sampler(p, sd_model, second_pass=False):
sampler_selection = p.hr_sampler_name if second_pass else p.sampler_name
if hasattr(sd_model, 'scheduler'):
- if sampler_selection is None or sampler_selection == 'None':
+ if sampler_selection == 'None':
+ return
+ if sampler_selection is None:
sampler = sd_samplers.all_samplers_map.get("UniPC")
else:
sampler = sd_samplers.all_samplers_map.get(sampler_selection, None)
diff --git a/modules/processing_vae.py b/modules/processing_vae.py
index 3c0357c81..1c4a45f07 100644
--- a/modules/processing_vae.py
+++ b/modules/processing_vae.py
@@ -33,6 +33,62 @@ def create_latents(image, p, dtype=None, device=None):
return latents
+def full_vqgan_decode(latents, model):
+ t0 = time.time()
+ if model is None or not hasattr(model, 'vqgan'):
+ shared.log.error('VQGAN not found in model')
+ return []
+ if debug:
+ devices.torch_gc(force=True)
+ shared.mem_mon.reset()
+
+ base_device = None
+ if shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False):
+ base_device = sd_models.move_base(model, devices.cpu)
+
+ if shared.opts.diffusers_offload_mode == "balanced":
+ shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model)
+ elif shared.opts.diffusers_offload_mode != "sequential":
+ sd_models.move_model(model.vqgan, devices.device)
+
+ latents = latents.to(devices.device, dtype=model.vqgan.dtype)
+
+ #normalize latents
+ scaling_factor = model.vqgan.config.get("scale_factor", None)
+ if scaling_factor:
+ latents = latents * scaling_factor
+
+ vae_name = os.path.splitext(os.path.basename(sd_vae.loaded_vae_file))[0] if sd_vae.loaded_vae_file is not None else "default"
+ vae_stats = f'name="{vae_name}" dtype={model.vqgan.dtype} device={model.vqgan.device}'
+ latents_stats = f'shape={latents.shape} dtype={latents.dtype} device={latents.device}'
+ stats = f'vae {vae_stats} latents {latents_stats}'
+
+ log_debug(f'VAE config: {model.vqgan.config}')
+ try:
+ decoded = model.vqgan.decode(latents).sample.clamp(0, 1)
+ except Exception as e:
+ shared.log.error(f'VAE decode: {stats} {e}')
+ errors.display(e, 'VAE decode')
+ decoded = []
+
+ # delete vae after OpenVINO compile
+ if 'VAE' in shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx" and shared.compiled_model_state.first_pass_vae:
+ shared.compiled_model_state.first_pass_vae = False
+ if not shared.opts.openvino_disable_memory_cleanup and hasattr(shared.sd_model, "vqgan"):
+ model.vqgan.apply(sd_models.convert_to_faketensors)
+ devices.torch_gc(force=True)
+
+ if shared.opts.diffusers_offload_mode == "balanced":
+ shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model)
+ elif shared.opts.diffusers_move_unet and not getattr(model, 'has_accelerate', False) and base_device is not None:
+ sd_models.move_base(model, base_device)
+ t1 = time.time()
+ if debug:
+ log_debug(f'VAE memory: {shared.mem_mon.read()}')
+ shared.log.debug(f'VAE decode: {stats} time={round(t1-t0, 3)}')
+ return decoded
+
+
def full_vae_decode(latents, model):
t0 = time.time()
if not hasattr(model, 'vae') and hasattr(model, 'pipe'):
@@ -161,7 +217,7 @@ def vae_decode(latents, model, output_type='np', full_quality=True, width=None,
return []
if shared.state.interrupted or shared.state.skipped:
return []
- if not hasattr(model, 'vae'):
+ if not hasattr(model, 'vae') and not hasattr(model, 'vqgan'):
shared.log.error('VAE not found in model')
return []
@@ -176,12 +232,18 @@ def vae_decode(latents, model, output_type='np', full_quality=True, width=None,
decoded = latents.float().cpu().numpy()
elif full_quality and hasattr(model, "vae"):
decoded = full_vae_decode(latents=latents, model=model)
+ elif hasattr(model, "vqgan"):
+ decoded = full_vqgan_decode(latents=latents, model=model)
else:
decoded = taesd_vae_decode(latents=latents)
if torch.is_tensor(decoded):
if hasattr(model, 'image_processor'):
imgs = model.image_processor.postprocess(decoded, output_type=output_type)
+ elif hasattr(model, "vqgan"):
+ imgs = decoded.permute(0, 2, 3, 1).cpu().float().numpy()
+ if output_type == "pil":
+ imgs = model.numpy_to_pil(imgs)
else:
import diffusers
model.image_processor = diffusers.image_processor.VaeImageProcessor()
diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py
index 234272907..2edef4bf5 100644
--- a/modules/prompt_parser_diffusers.py
+++ b/modules/prompt_parser_diffusers.py
@@ -162,7 +162,7 @@ class PromptEmbedder:
def encode(self, pipe, positive_prompt, negative_prompt, batchidx):
self.attention = shared.opts.prompt_attention
- if self.attention == "xhinker" or 'Flux' in pipe.__class__.__name__:
+ if self.attention == "xhinker":
prompt_embed, positive_pooled, negative_embed, negative_pooled = get_xhinker_text_embeddings(pipe, positive_prompt, negative_prompt, self.clip_skip)
else:
prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, positive_prompt, negative_prompt, self.clip_skip)
@@ -583,15 +583,15 @@ def get_xhinker_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", cl
te1_device, te2_device, te3_device = None, None, None
if hasattr(pipe, "text_encoder") and pipe.text_encoder.device != devices.device:
te1_device = pipe.text_encoder.device
- sd_models.move_model(pipe.text_encoder, devices.device)
+ sd_models.move_model(pipe.text_encoder, devices.device, force=True)
if hasattr(pipe, "text_encoder_2") and pipe.text_encoder_2.device != devices.device:
te2_device = pipe.text_encoder_2.device
- sd_models.move_model(pipe.text_encoder_2, devices.device)
+ sd_models.move_model(pipe.text_encoder_2, devices.device, force=True)
if hasattr(pipe, "text_encoder_3") and pipe.text_encoder_3.device != devices.device:
te3_device = pipe.text_encoder_3.device
- sd_models.move_model(pipe.text_encoder_3, devices.device)
+ sd_models.move_model(pipe.text_encoder_3, devices.device, force=True)
- if is_sd3:
+ if 'StableDiffusion3' in pipe.__class__.__name__:
prompt_embed, negative_embed, positive_pooled, negative_pooled = get_weighted_text_embeddings_sd3(pipe=pipe, prompt=prompt, neg_prompt=neg_prompt, use_t5_encoder=bool(pipe.text_encoder_3))
elif 'Flux' in pipe.__class__.__name__:
prompt_embed, positive_pooled = get_weighted_text_embeddings_flux1(pipe=pipe, prompt=prompt, prompt2=prompt_2, device=devices.device)
@@ -601,10 +601,10 @@ def get_xhinker_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", cl
prompt_embed, negative_embed = get_weighted_text_embeddings_sd15(pipe=pipe, prompt=prompt, neg_prompt=neg_prompt, clip_skip=clip_skip)
if te1_device is not None:
- sd_models.move_model(pipe.text_encoder, te1_device)
+ sd_models.move_model(pipe.text_encoder, te1_device, force=True)
if te2_device is not None:
- sd_models.move_model(pipe.text_encoder_2, te1_device)
+ sd_models.move_model(pipe.text_encoder_2, te1_device, force=True)
if te3_device is not None:
- sd_models.move_model(pipe.text_encoder_3, te1_device)
+ sd_models.move_model(pipe.text_encoder_3, te1_device, force=True)
return prompt_embed, positive_pooled, negative_embed, negative_pooled
diff --git a/modules/sd_checkpoint.py b/modules/sd_checkpoint.py
index afc5842e4..e035fc3db 100644
--- a/modules/sd_checkpoint.py
+++ b/modules/sd_checkpoint.py
@@ -168,7 +168,10 @@ def update_model_hashes():
def get_closet_checkpoint_match(s: str):
if s.startswith('https://huggingface.co/'):
- s = s.replace('https://huggingface.co/', '')
+ model_name = s.replace('https://huggingface.co/', '')
+ checkpoint_info = CheckpointInfo(model_name) # create a virutal model info
+ checkpoint_info.type = 'huggingface'
+ return checkpoint_info
if s.startswith('huggingface/'):
model_name = s.replace('huggingface/', '')
checkpoint_info = CheckpointInfo(model_name) # create a virutal model info
@@ -185,6 +188,11 @@ def get_closet_checkpoint_match(s: str):
if found and len(found) == 1:
return found[0]
+ # absolute path
+ if s.endswith('.safetensors') and os.path.isfile(s):
+ checkpoint_info = CheckpointInfo(s)
+ return checkpoint_info
+
# reference search
"""
found = sorted([info for info in shared.reference_models.values() if os.path.basename(info['path']).lower().startswith(s.lower())], key=lambda x: len(x['path']))
@@ -198,8 +206,9 @@ def get_closet_checkpoint_match(s: str):
if shared.opts.sd_checkpoint_autodownload and s.count('/') == 1:
modelloader.hf_login()
found = modelloader.find_diffuser(s, full=True)
+ found = [f for f in found if f == s]
shared.log.info(f'HF search: model="{s}" results={found}')
- if found is not None and len(found) == 1 and found[0] == s:
+ if found is not None and len(found) == 1:
checkpoint_info = CheckpointInfo(s)
checkpoint_info.type = 'huggingface'
return checkpoint_info
diff --git a/modules/sd_models.py b/modules/sd_models.py
index cf1921a36..68446bdd3 100644
--- a/modules/sd_models.py
+++ b/modules/sd_models.py
@@ -16,7 +16,7 @@ import safetensors.torch
from omegaconf import OmegaConf
from ldm.util import instantiate_from_config
from modules import paths, shared, shared_state, modelloader, devices, script_callbacks, sd_vae, sd_unet, errors, sd_models_config, sd_models_compile, sd_hijack_accelerate, sd_detect
-from modules.timer import Timer
+from modules.timer import Timer, process as process_timer
from modules.memstats import memory_stats
from modules.modeldata import model_data
from modules.sd_checkpoint import CheckpointInfo, select_checkpoint, list_models, checkpoints_list, checkpoint_titles, get_closet_checkpoint_match, model_hash, update_model_hashes, setup_model, write_metadata, read_metadata_from_safetensors # pylint: disable=unused-import
@@ -279,7 +279,7 @@ def set_diffuser_options(sd_model, vae = None, op: str = 'model', offload=True):
model.eval()
return model
sd_model = sd_models_compile.apply_compile_to_model(sd_model, eval_model, ["Model", "VAE", "Text Encoder"], op="eval")
- if len(shared.opts.torchao_quantization) > 0:
+ if len(shared.opts.torchao_quantization) > 0 and shared.opts.torchao_quantization_mode != 'post':
sd_model = sd_models_compile.torchao_quantization(sd_model)
if shared.opts.opt_channelslast and hasattr(sd_model, 'unet'):
@@ -319,12 +319,12 @@ def set_diffuser_offload(sd_model, op: str = 'model'):
if not (hasattr(sd_model, "has_accelerate") and sd_model.has_accelerate):
sd_model.has_accelerate = False
if hasattr(sd_model, 'maybe_free_model_hooks') and shared.opts.diffusers_offload_mode == "none":
- shared.log.debug(f'Setting {op}: offload={shared.opts.diffusers_offload_mode}')
+ shared.log.debug(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} limit={shared.opts.cuda_mem_fraction}')
sd_model.maybe_free_model_hooks()
sd_model.has_accelerate = False
if hasattr(sd_model, "enable_model_cpu_offload") and shared.opts.diffusers_offload_mode == "model":
try:
- shared.log.debug(f'Setting {op}: offload={shared.opts.diffusers_offload_mode}')
+ shared.log.debug(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} limit={shared.opts.cuda_mem_fraction}')
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
@@ -339,7 +339,7 @@ def set_diffuser_offload(sd_model, op: str = 'model'):
shared.log.error(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} {e}')
if hasattr(sd_model, "enable_sequential_cpu_offload") and shared.opts.diffusers_offload_mode == "sequential":
try:
- shared.log.debug(f'Setting {op}: offload={shared.opts.diffusers_offload_mode}')
+ shared.log.debug(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} limit={shared.opts.cuda_mem_fraction}')
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
@@ -359,7 +359,7 @@ def set_diffuser_offload(sd_model, op: str = 'model'):
shared.log.error(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} {e}')
if shared.opts.diffusers_offload_mode == "balanced":
try:
- shared.log.debug(f'Setting {op}: offload={shared.opts.diffusers_offload_mode}')
+ shared.log.debug(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} threshold={shared.opts.diffusers_offload_max_gpu_memory} limit={shared.opts.cuda_mem_fraction}')
sd_model = apply_balanced_offload(sd_model)
except Exception as e:
shared.log.error(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} {e}')
@@ -512,7 +512,10 @@ def move_model(model, device=None, force=False):
except Exception as e1:
t1 = time.time()
shared.log.error(f'Model move: device={device} {e1}')
- if os.environ.get('SD_MOVE_DEBUG', None) or (t1-t0) > 0.1:
+ if 'move' not in process_timer.records:
+ process_timer.records['move'] = 0
+ process_timer.records['move'] += t1 - t0
+ if os.environ.get('SD_MOVE_DEBUG', None) or (t1-t0) > 1:
shared.log.debug(f'Model move: device={device} class={model.__class__.__name__} accelerate={getattr(model, "has_accelerate", False)} fn={fn} time={t1-t0:.2f}') # pylint: disable=protected-access
devices.torch_gc()
@@ -771,7 +774,7 @@ def load_diffuser_file(model_type, pipeline, checkpoint_info, diffusers_load_con
return sd_model
-def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=None, op='model'): # pylint: disable=unused-argument
+def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=None, op='model', revision=None): # pylint: disable=unused-argument
if timer is None:
timer = Timer()
logging.getLogger("diffusers").setLevel(logging.ERROR)
@@ -784,6 +787,8 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
"requires_safety_checker": False, # sd15 specific but we cant know ahead of time
# "use_safetensors": True,
}
+ if revision is not None:
+ diffusers_load_config['revision'] = revision
if shared.opts.diffusers_model_load_variant != 'default':
diffusers_load_config['variant'] = shared.opts.diffusers_model_load_variant
if shared.opts.diffusers_pipeline == 'Custom Diffusers Pipeline' and len(shared.opts.custom_diffusers_pipeline) > 0:
@@ -1077,6 +1082,8 @@ def set_diffuser_pipe(pipe, new_pipe_type):
'OmniGenPipeline',
'StableDiffusion3ControlNetPipeline',
'InstantIRPipeline',
+ 'FluxFillPipeline',
+ 'FluxControlPipeline',
]
n = getattr(pipe.__class__, '__name__', '')
@@ -1345,7 +1352,7 @@ def reload_text_encoder(initial=False):
set_t5(pipe=shared.sd_model, module='text_encoder_3', t5=shared.opts.sd_text_encoder, cache_dir=shared.opts.diffusers_dir)
-def reload_model_weights(sd_model=None, info=None, reuse_dict=False, op='model', force=False):
+def reload_model_weights(sd_model=None, info=None, reuse_dict=False, op='model', force=False, revision=None):
load_dict = shared.opts.sd_model_dict != model_data.sd_dict
from modules import lowvram, sd_hijack
checkpoint_info = info or select_checkpoint(op=op) # are we selecting model or dictionary
@@ -1390,7 +1397,7 @@ def reload_model_weights(sd_model=None, info=None, reuse_dict=False, op='model',
load_model(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer, op=op)
model_data.sd_dict = shared.opts.sd_model_dict
else:
- load_diffuser(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer, op=op)
+ load_diffuser(checkpoint_info, already_loaded_state_dict=state_dict, timer=timer, op=op, revision=revision)
if load_dict and next_checkpoint_info is not None:
model_data.sd_dict = shared.opts.sd_model_dict
shared.opts.data["sd_model_checkpoint"] = next_checkpoint_info.title
diff --git a/modules/sd_models_compile.py b/modules/sd_models_compile.py
index 91ed84ded..38d3ef57f 100644
--- a/modules/sd_models_compile.py
+++ b/modules/sd_models_compile.py
@@ -535,7 +535,6 @@ def torchao_quantization(sd_model):
if hasattr(sd_model, 'transformer') and 'Model' in shared.opts.torchao_quantization:
modules.append('transformer')
q.quantize_(sd_model.transformer, fn(), device=devices.device)
- # sd_model.transformer = q.autoquant(sd_model.transformer, error_on_unseen=False)
if hasattr(sd_model, 'vae') and 'VAE' in shared.opts.torchao_quantization:
modules.append('vae')
q.quantize_(sd_model.vae, fn(), device=devices.device)
diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py
index e560744dd..d8416e5d9 100644
--- a/modules/sd_samplers.py
+++ b/modules/sd_samplers.py
@@ -47,6 +47,8 @@ def visible_sampler_names():
def create_sampler(name, model):
+ if name is None or name == 'None':
+ return model.scheduler
try:
current = model.scheduler.__class__.__name__
except Exception:
diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py
index a487fe9b7..f6f6c18d5 100644
--- a/modules/sd_samplers_common.py
+++ b/modules/sd_samplers_common.py
@@ -1,9 +1,10 @@
+import time
import threading
from collections import namedtuple
import torch
import torchvision.transforms as T
from PIL import Image
-from modules import shared, devices, processing, images, sd_vae_approx, sd_vae_taesd, sd_vae_stablecascade, sd_samplers
+from modules import shared, devices, processing, images, sd_vae_approx, sd_vae_taesd, sd_vae_stablecascade, sd_samplers, timer
SamplerData = namedtuple('SamplerData', ['name', 'constructor', 'aliases', 'options'])
@@ -33,6 +34,7 @@ def setup_img2img_steps(p, steps=None):
def single_sample_to_image(sample, approximation=None):
with queue_lock:
+ t0 = time.time()
sd_cascade = False
if approximation is None:
approximation = approximation_indexes.get(shared.opts.show_progress_type, None)
@@ -84,6 +86,8 @@ def single_sample_to_image(sample, approximation=None):
except Exception as e:
warn_once(f'Preview: {e}')
image = Image.new(mode="RGB", size=(512, 512))
+ t1 = time.time()
+ timer.process.add('preview', t1 - t0)
return image
diff --git a/modules/sd_samplers_diffusers.py b/modules/sd_samplers_diffusers.py
index 60c75b64e..4672df92e 100644
--- a/modules/sd_samplers_diffusers.py
+++ b/modules/sd_samplers_diffusers.py
@@ -69,7 +69,7 @@ config = {
'Euler a': { 'steps_offset': 0, 'rescale_betas_zero_snr': False, 'timestep_spacing': 'linspace' },
'Euler SGM': { 'steps_offset': 0, 'interpolation_type': "linear", 'rescale_betas_zero_snr': False, 'final_sigmas_type': 'zero', 'timestep_spacing': 'trailing', 'use_beta_sigmas': False, 'use_exponential_sigmas': False, 'use_karras_sigmas': False, 'prediction_type': "sample" },
'Euler EDM': { 'sigma_schedule': "karras" },
- 'Euler FlowMatch': { 'timestep_spacing': "linspace", 'shift': 1, 'use_dynamic_shifting': False },
+ 'Euler FlowMatch': { 'timestep_spacing': "linspace", 'shift': 1, 'use_dynamic_shifting': False, 'use_karras_sigmas': False, 'use_exponential_sigmas': False, 'use_beta_sigmas': False },
'DPM++': { 'solver_order': 2, 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False, 'use_exponential_sigmas': False, 'use_beta_sigmas': False, 'final_sigmas_type': 'sigma_min' },
'DPM++ 1S': { 'thresholding': False, 'sample_max_value': 1.0, 'algorithm_type': "dpmsolver++", 'solver_type': "midpoint", 'lower_order_final': True, 'use_karras_sigmas': False, 'use_exponential_sigmas': False, 'use_beta_sigmas': False, 'use_lu_lambdas': False, 'final_sigmas_type': 'zero', 'timestep_spacing': 'linspace', 'solver_order': 1 },
@@ -80,13 +80,13 @@ config = {
'DPM++ Cosine': { 'solver_order': 2, 'sigma_schedule': "exponential", 'prediction_type': "v-prediction" },
'DPM SDE': { 'use_karras_sigmas': False, 'use_exponential_sigmas': False, 'use_beta_sigmas': False, 'noise_sampler_seed': None, 'timestep_spacing': 'linspace', 'steps_offset': 0, },
- 'DPM2 FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver2', 'use_noise_sampler': True },
- 'DPM2a FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver2A', 'use_noise_sampler': True },
- 'DPM2++ 2M FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver++2M', 'use_noise_sampler': True },
- 'DPM2++ 2S FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver++2S', 'use_noise_sampler': True },
- 'DPM2++ SDE FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver++sde', 'use_noise_sampler': True },
- 'DPM2++ 2M SDE FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver++2Msde', 'use_noise_sampler': True },
- 'DPM2++ 3M SDE FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 3, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver++3Msde', 'use_noise_sampler': True },
+ 'DPM2 FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver2', 'use_noise_sampler': True, 'beta_start': 0.00085, 'beta_end': 0.012 },
+ 'DPM2a FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver2A', 'use_noise_sampler': True, 'beta_start': 0.00085, 'beta_end': 0.012 },
+ 'DPM2++ 2M FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver++2M', 'use_noise_sampler': True, 'beta_start': 0.00085, 'beta_end': 0.012 },
+ 'DPM2++ 2S FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver++2S', 'use_noise_sampler': True, 'beta_start': 0.00085, 'beta_end': 0.012 },
+ 'DPM2++ SDE FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver++sde', 'use_noise_sampler': True, 'beta_start': 0.00085, 'beta_end': 0.012 },
+ 'DPM2++ 2M SDE FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 2, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver++2Msde', 'use_noise_sampler': True, 'beta_start': 0.00085, 'beta_end': 0.012 },
+ 'DPM2++ 3M SDE FlowMatch': { 'shift': 1, 'use_dynamic_shifting': False, 'solver_order': 3, 'sigma_schedule': None, 'use_beta_sigmas': False, 'algorithm_type': 'dpmsolver++3Msde', 'use_noise_sampler': True, 'beta_start': 0.00085, 'beta_end': 0.012 },
'Heun': { 'use_beta_sigmas': False, 'use_karras_sigmas': False, 'use_exponential_sigmas': False, 'timestep_spacing': 'linspace' },
'Heun FlowMatch': { 'timestep_spacing': "linspace", 'shift': 1 },
@@ -200,16 +200,16 @@ class DiffusionSampler:
timesteps = re.split(',| ', shared.opts.schedulers_timesteps)
timesteps = [int(x) for x in timesteps if x.isdigit()]
if len(timesteps) == 0:
- if 'use_beta_sigmas' in self.config:
- self.config['use_beta_sigmas'] = shared.opts.schedulers_sigma == 'beta'
- if 'use_karras_sigmas' in self.config:
- self.config['use_karras_sigmas'] = shared.opts.schedulers_sigma == 'karras'
- if 'use_exponential_sigmas' in self.config:
- self.config['use_exponential_sigmas'] = shared.opts.schedulers_sigma == 'exponential'
- if 'use_lu_lambdas' in self.config:
- self.config['use_lu_lambdas'] = shared.opts.schedulers_sigma == 'lambdas'
if 'sigma_schedule' in self.config:
self.config['sigma_schedule'] = shared.opts.schedulers_sigma if shared.opts.schedulers_sigma != 'default' else None
+ if shared.opts.schedulers_sigma == 'betas' and 'use_beta_sigmas' in self.config:
+ self.config['use_beta_sigmas'] = True
+ elif shared.opts.schedulers_sigma == 'karras' and 'use_karras_sigmas' in self.config:
+ self.config['use_karras_sigmas'] = True
+ elif shared.opts.schedulers_sigma == 'exponential' and 'use_exponential_sigmas' in self.config:
+ self.config['use_exponential_sigmas'] = True
+ elif shared.opts.schedulers_sigma == 'lambdas' and 'use_lu_lambdas' in self.config:
+ self.config['use_lu_lambdas'] = True
else:
pass # timesteps are set using set_timesteps in set_pipeline_args
@@ -236,7 +236,7 @@ class DiffusionSampler:
if 'use_dynamic_shifting' in self.config:
if 'Flux' in model.__class__.__name__:
self.config['use_dynamic_shifting'] = shared.opts.schedulers_dynamic_shift
- if 'use_beta_sigmas' in self.config:
+ if 'use_beta_sigmas' in self.config and 'sigma_schedule' in self.config:
self.config['use_beta_sigmas'] = 'StableDiffusion3' in model.__class__.__name__
if 'rescale_betas_zero_snr' in self.config:
self.config['rescale_betas_zero_snr'] = shared.opts.schedulers_rescale_betas
diff --git a/modules/shared.py b/modules/shared.py
index a5af83f5e..72af37500 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -477,7 +477,7 @@ options_templates.update(options_section(('sd', "Execution & Models"), {
"sd_checkpoint_autoload": OptionInfo(True, "Model autoload on start"),
"sd_checkpoint_autodownload": OptionInfo(True, "Model auto-download on demand"),
"sd_textencoder_cache": OptionInfo(True, "Cache text encoder results", gr.Checkbox, {"visible": False}),
- "sd_textencoder_cache_size": OptionInfo(4, "Text encoder results LRU cache size", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
+ "sd_textencoder_cache_size": OptionInfo(4, "Text encoder cache size", gr.Slider, {"minimum": 0, "maximum": 16, "step": 1}),
"stream_load": OptionInfo(False, "Load models using stream loading method", gr.Checkbox, {"visible": not native }),
"prompt_mean_norm": OptionInfo(False, "Prompt attention normalization", gr.Checkbox),
"comma_padding_backtrack": OptionInfo(20, "Prompt padding", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1, "visible": not native }),
@@ -559,8 +559,8 @@ options_templates.update(options_section(('diffusers', "Diffusers Settings"), {
"diffusers_extract_ema": OptionInfo(False, "Use model EMA weights when possible"),
"diffusers_generator_device": OptionInfo("GPU", "Generator device", gr.Radio, {"choices": ["GPU", "CPU", "Unset"]}),
"diffusers_offload_mode": OptionInfo(startup_offload_mode, "Model offload mode", gr.Radio, {"choices": ['none', 'balanced', 'model', 'sequential']}),
- "diffusers_offload_max_gpu_memory": OptionInfo(round(gpu_memory * 0.75, 1), "Max GPU memory for balanced offload mode in GB", gr.Slider, {"minimum": 0, "maximum": gpu_memory, "step": 0.01,}),
- "diffusers_offload_max_cpu_memory": OptionInfo(round(cpu_memory * 0.75, 1), "Max CPU memory for balanced offload mode in GB", gr.Slider, {"minimum": 0, "maximum": cpu_memory, "step": 0.01,}),
+ "diffusers_offload_max_gpu_memory": OptionInfo(round(gpu_memory * 0.75, 1), "Max GPU memory before balanced offload", gr.Slider, {"minimum": 0, "maximum": gpu_memory, "step": 0.01, "visible": True }),
+ "diffusers_offload_max_cpu_memory": OptionInfo(round(cpu_memory * 0.75, 1), "Max CPU memory before balanced offload", gr.Slider, {"minimum": 0, "maximum": cpu_memory, "step": 0.01, "visible": False }),
"diffusers_vae_upcast": OptionInfo("default", "VAE upcasting", gr.Radio, {"choices": ['default', 'true', 'false']}),
"diffusers_vae_slicing": OptionInfo(True, "VAE slicing"),
"diffusers_vae_tiling": OptionInfo(cmd_opts.lowvram or cmd_opts.medvram, "VAE tiling"),
@@ -590,6 +590,7 @@ options_templates.update(options_section(('quantization', "Quantization Settings
"optimum_quanto_weights_type": OptionInfo("qint8", "Optimum.quanto quantization type", gr.Radio, {"choices": ['qint8', 'qfloat8_e4m3fn', 'qfloat8_e5m2', 'qint4', 'qint2'], "visible": native}),
"optimum_quanto_activations_type": OptionInfo("none", "Optimum.quanto quantization activations ", gr.Radio, {"choices": ['none', 'qint8', 'qfloat8_e4m3fn', 'qfloat8_e5m2'], "visible": native}),
"torchao_quantization": OptionInfo([], "TorchAO quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "VAE", "Text Encoder"], "visible": native}),
+ "torchao_quantization_mode": OptionInfo("pre", "TorchAO quantization mode", gr.Radio, {"choices": ['pre', 'post'], "visible": native}),
"torchao_quantization_type": OptionInfo("int8", "TorchAO quantization type", gr.Radio, {"choices": ["int8+act", "int8", "int4", "fp8+act", "fp8", "fpx"], "visible": native}),
"nncf_compress_weights": OptionInfo([], "NNCF compression enabled", gr.CheckboxGroup, {"choices": ["Model", "VAE", "Text Encoder", "ControlNet"], "visible": native}),
"nncf_compress_weights_mode": OptionInfo("INT8", "NNCF compress mode", gr.Radio, {"choices": ['INT8', 'INT8_SYM', 'INT4_ASYM', 'INT4_SYM', 'NF4'] if cmd_opts.use_openvino else ['INT8']}),
@@ -823,7 +824,7 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), {
'postprocessing_enable_in_main_ui': OptionInfo([], "Additional postprocessing operations", gr.Dropdown, lambda: {"multiselect":True, "choices": [x.name for x in shared_items.postprocessing_scripts()]}),
'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", gr.Dropdown, lambda: {"multiselect":True, "choices": [x.name for x in shared_items.postprocessing_scripts()]}),
- "postprocessing_sep_img2img": OptionInfo("Img2Img & Inpainting
", "", gr.HTML),
+ "postprocessing_sep_img2img": OptionInfo("Inpaint
", "", gr.HTML),
"img2img_color_correction": OptionInfo(False, "Apply color correction"),
"mask_apply_overlay": OptionInfo(True, "Apply mask as overlay"),
"img2img_background_color": OptionInfo("#ffffff", "Image transparent color fill", gr.ColorPicker, {}),
@@ -831,7 +832,7 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), {
"initial_noise_multiplier": OptionInfo(1.0, "Noise multiplier for image processing", gr.Slider, {"minimum": 0.1, "maximum": 1.5, "step": 0.01, "visible": not native}),
"img2img_extra_noise": OptionInfo(0.0, "Extra noise multiplier for img2img", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01, "visible": not native}),
- # "postprocessing_sep_detailer": OptionInfo("Detailer
", "", gr.HTML),
+ "postprocessing_sep_detailer": OptionInfo("Detailer
", "", gr.HTML),
"detailer_model": OptionInfo("Detailer", "Detailer model", gr.Radio, lambda: {"choices": [x.name() for x in detailers], "visible": False}),
"detailer_classes": OptionInfo("", "Detailer classes", gr.Textbox, { "visible": False}),
"detailer_conf": OptionInfo(0.6, "Min confidence", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.05, "visible": False}),
@@ -843,11 +844,12 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), {
"detailer_blur": OptionInfo(10, "Item edge blur", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1, "visible": False}),
"detailer_strength": OptionInfo(0.5, "Detailer strength", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01, "visible": False}),
"detailer_models": OptionInfo(['face-yolo8n'], "Detailer models", gr.Dropdown, lambda: {"multiselect":True, "choices": list(yolo.list), "visible": False}),
- "code_former_weight": OptionInfo(0.2, "CodeFormer weight parameter", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01, "visible": False}),
"detailer_unload": OptionInfo(False, "Move detailer model to CPU when complete"),
+ "detailer_augment": OptionInfo(True, "Detailer use model augment"),
"postprocessing_sep_face_restore": OptionInfo("Face restore
", "", gr.HTML),
- "face_restoration_model": OptionInfo("Face restorer", "Face restoration", gr.Radio, lambda: {"choices": ['None'] + [x.name() for x in face_restorers]}),
+ "face_restoration_model": OptionInfo("None", "Face restoration", gr.Radio, lambda: {"choices": ['None'] + [x.name() for x in face_restorers]}),
+ "code_former_weight": OptionInfo(0.2, "CodeFormer weight parameter", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}),
"postprocessing_sep_upscalers": OptionInfo("Upscaling
", "", gr.HTML),
"upscaler_unload": OptionInfo(False, "Unload upscaler after processing"),
diff --git a/modules/shared_state.py b/modules/shared_state.py
index 7def42b8c..51d33f9ed 100644
--- a/modules/shared_state.py
+++ b/modules/shared_state.py
@@ -28,6 +28,9 @@ class State:
oom = False
debug_output = os.environ.get('SD_STATE_DEBUG', None)
+ def __str__(self) -> str:
+ return f'State: job={self.job} {self.job_no}/{self.job_count} step={self.sampling_step}/{self.sampling_steps} skipped={self.skipped} interrupted={self.interrupted} paused={self.paused} info={self.textinfo}'
+
def skip(self):
log.debug('Requested skip')
self.skipped = True
@@ -135,6 +138,8 @@ class State:
modules.devices.torch_gc()
def set_current_image(self):
+ if self.job == 'VAE': # avoid generating preview while vae is running
+ return
from modules.shared import opts, cmd_opts
"""sets self.current_image from self.current_latent if enough sampling steps have been made after the last call to this"""
if cmd_opts.lowvram or self.api:
diff --git a/modules/style_aligned/inversion.py b/modules/style_aligned/inversion.py
new file mode 100644
index 000000000..8c91cc02a
--- /dev/null
+++ b/modules/style_aligned/inversion.py
@@ -0,0 +1,124 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import annotations
+from typing import Callable, TYPE_CHECKING
+from diffusers import StableDiffusionXLPipeline
+import torch
+from tqdm import tqdm
+if TYPE_CHECKING:
+ import numpy as np
+
+
+T = torch.Tensor
+TN = T
+InversionCallback = Callable[[StableDiffusionXLPipeline, int, T, dict[str, T]], dict[str, T]]
+
+
+def _get_text_embeddings(prompt: str, tokenizer, text_encoder, device):
+ # Tokenize text and get embeddings
+ text_inputs = tokenizer(prompt, padding='max_length', max_length=tokenizer.model_max_length, truncation=True, return_tensors='pt')
+ text_input_ids = text_inputs.input_ids
+
+ with torch.no_grad():
+ prompt_embeds = text_encoder(
+ text_input_ids.to(device),
+ output_hidden_states=True,
+ )
+
+ pooled_prompt_embeds = prompt_embeds[0]
+ prompt_embeds = prompt_embeds.hidden_states[-2]
+ if prompt == '':
+ negative_prompt_embeds = torch.zeros_like(prompt_embeds)
+ negative_pooled_prompt_embeds = torch.zeros_like(pooled_prompt_embeds)
+ return negative_prompt_embeds, negative_pooled_prompt_embeds
+ return prompt_embeds, pooled_prompt_embeds
+
+
+def _encode_text_sdxl(model: StableDiffusionXLPipeline, prompt: str) -> tuple[dict[str, T], T]:
+ device = model._execution_device # pylint: disable=protected-access
+ prompt_embeds, pooled_prompt_embeds, = _get_text_embeddings(prompt, model.tokenizer, model.text_encoder, device) # pylint: disable=unused-variable
+ prompt_embeds_2, pooled_prompt_embeds2, = _get_text_embeddings( prompt, model.tokenizer_2, model.text_encoder_2, device)
+ prompt_embeds = torch.cat((prompt_embeds, prompt_embeds_2), dim=-1)
+ text_encoder_projection_dim = model.text_encoder_2.config.projection_dim
+ add_time_ids = model._get_add_time_ids((1024, 1024), (0, 0), (1024, 1024), model.text_encoder.dtype, # pylint: disable=protected-access
+ text_encoder_projection_dim).to(device)
+ added_cond_kwargs = {"text_embeds": pooled_prompt_embeds2, "time_ids": add_time_ids}
+ return added_cond_kwargs, prompt_embeds
+
+
+def _encode_text_sdxl_with_negative(model: StableDiffusionXLPipeline, prompt: str) -> tuple[dict[str, T], T]:
+ added_cond_kwargs, prompt_embeds = _encode_text_sdxl(model, prompt)
+ added_cond_kwargs_uncond, prompt_embeds_uncond = _encode_text_sdxl(model, "")
+ prompt_embeds = torch.cat((prompt_embeds_uncond, prompt_embeds, ))
+ added_cond_kwargs = {"text_embeds": torch.cat((added_cond_kwargs_uncond["text_embeds"], added_cond_kwargs["text_embeds"])),
+ "time_ids": torch.cat((added_cond_kwargs_uncond["time_ids"], added_cond_kwargs["time_ids"])),}
+ return added_cond_kwargs, prompt_embeds
+
+
+def _encode_image(model: StableDiffusionXLPipeline, image: np.ndarray) -> T:
+ image = torch.from_numpy(image).float() / 255.
+ image = (image * 2 - 1).permute(2, 0, 1).unsqueeze(0)
+ latent = model.vae.encode(image.to(model.vae.device, model.vae.dtype))['latent_dist'].mean * model.vae.config.scaling_factor
+ return latent
+
+
+def _next_step(model: StableDiffusionXLPipeline, model_output: T, timestep: int, sample: T) -> T:
+ timestep, next_timestep = min(timestep - model.scheduler.config.num_train_timesteps // model.scheduler.num_inference_steps, 999), timestep
+ alpha_prod_t = model.scheduler.alphas_cumprod[int(timestep)] if timestep >= 0 else model.scheduler.final_alpha_cumprod
+ alpha_prod_t_next = model.scheduler.alphas_cumprod[int(next_timestep)]
+ beta_prod_t = 1 - alpha_prod_t
+ next_original_sample = (sample - beta_prod_t ** 0.5 * model_output) / alpha_prod_t ** 0.5
+ next_sample_direction = (1 - alpha_prod_t_next) ** 0.5 * model_output
+ next_sample = alpha_prod_t_next ** 0.5 * next_original_sample + next_sample_direction
+ return next_sample
+
+
+def _get_noise_pred(model: StableDiffusionXLPipeline, latent: T, t: T, context: T, guidance_scale: float, added_cond_kwargs: dict[str, T]):
+ latents_input = torch.cat([latent] * 2)
+ noise_pred = model.unet(latents_input, t, encoder_hidden_states=context, added_cond_kwargs=added_cond_kwargs)["sample"]
+ noise_pred_uncond, noise_prediction_text = noise_pred.chunk(2)
+ noise_pred = noise_pred_uncond + guidance_scale * (noise_prediction_text - noise_pred_uncond)
+ # latents = next_step(model, noise_pred, t, latent)
+ return noise_pred
+
+
+def _ddim_loop(model: StableDiffusionXLPipeline, z0, prompt, guidance_scale) -> T:
+ all_latent = [z0]
+ added_cond_kwargs, text_embedding = _encode_text_sdxl_with_negative(model, prompt)
+ latent = z0.clone().detach().to(model.text_encoder.dtype)
+ for i in tqdm(range(model.scheduler.num_inference_steps)):
+ t = model.scheduler.timesteps[len(model.scheduler.timesteps) - i - 1]
+ noise_pred = _get_noise_pred(model, latent, t, text_embedding, guidance_scale, added_cond_kwargs)
+ latent = _next_step(model, noise_pred, t, latent)
+ all_latent.append(latent)
+ return torch.cat(all_latent).flip(0)
+
+
+def make_inversion_callback(zts, offset: int = 0):
+
+ def callback_on_step_end(pipeline: StableDiffusionXLPipeline, i: int, t: T, callback_kwargs: dict[str, T]) -> dict[str, T]: # pylint: disable=unused-argument
+ latents = callback_kwargs['latents']
+ latents[0] = zts[max(offset + 1, i + 1)].to(latents.device, latents.dtype)
+ return {'latents': latents}
+ return zts[offset], callback_on_step_end
+
+
+@torch.no_grad()
+def ddim_inversion(model: StableDiffusionXLPipeline, x0: np.ndarray, prompt: str, num_inference_steps: int, guidance_scale,) -> T:
+ z0 = _encode_image(model, x0)
+ model.scheduler.set_timesteps(num_inference_steps, device=z0.device)
+ zs = _ddim_loop(model, z0, prompt, guidance_scale)
+ return zs
diff --git a/modules/style_aligned/sa_handler.py b/modules/style_aligned/sa_handler.py
new file mode 100644
index 000000000..ee4b1ca79
--- /dev/null
+++ b/modules/style_aligned/sa_handler.py
@@ -0,0 +1,281 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import annotations
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+ from diffusers import StableDiffusionXLPipeline
+from dataclasses import dataclass
+import torch
+import torch.nn as nn
+from torch.nn import functional as nnf
+from diffusers.models import attention_processor # pylint: disable=ungrouped-imports
+import einops
+
+T = torch.Tensor
+
+
+@dataclass(frozen=True)
+class StyleAlignedArgs:
+ share_group_norm: bool = True
+ share_layer_norm: bool = True
+ share_attention: bool = True
+ adain_queries: bool = True
+ adain_keys: bool = True
+ adain_values: bool = False
+ full_attention_share: bool = False
+ shared_score_scale: float = 1.
+ shared_score_shift: float = 0.
+ only_self_level: float = 0.
+
+
+def expand_first(feat: T, scale=1.,) -> T:
+ b = feat.shape[0]
+ feat_style = torch.stack((feat[0], feat[b // 2])).unsqueeze(1)
+ if scale == 1:
+ feat_style = feat_style.expand(2, b // 2, *feat.shape[1:])
+ else:
+ feat_style = feat_style.repeat(1, b // 2, 1, 1, 1)
+ feat_style = torch.cat([feat_style[:, :1], scale * feat_style[:, 1:]], dim=1)
+ return feat_style.reshape(*feat.shape)
+
+
+def concat_first(feat: T, dim=2, scale=1.) -> T:
+ feat_style = expand_first(feat, scale=scale)
+ return torch.cat((feat, feat_style), dim=dim)
+
+
+def calc_mean_std(feat, eps: float = 1e-5) -> tuple[T, T]:
+ feat_std = (feat.var(dim=-2, keepdims=True) + eps).sqrt()
+ feat_mean = feat.mean(dim=-2, keepdims=True)
+ return feat_mean, feat_std
+
+
+def adain(feat: T) -> T:
+ feat_mean, feat_std = calc_mean_std(feat)
+ feat_style_mean = expand_first(feat_mean)
+ feat_style_std = expand_first(feat_std)
+ feat = (feat - feat_mean) / feat_std
+ feat = feat * feat_style_std + feat_style_mean
+ return feat
+
+
+class DefaultAttentionProcessor(nn.Module):
+
+ def __init__(self):
+ super().__init__()
+ self.processor = attention_processor.AttnProcessor2_0()
+
+ def __call__(self, attn: attention_processor.Attention, hidden_states, encoder_hidden_states=None,
+ attention_mask=None, **kwargs):
+ return self.processor(attn, hidden_states, encoder_hidden_states, attention_mask)
+
+
+class SharedAttentionProcessor(DefaultAttentionProcessor):
+
+ def shifted_scaled_dot_product_attention(self, attn: attention_processor.Attention, query: T, key: T, value: T) -> T:
+ logits = torch.einsum('bhqd,bhkd->bhqk', query, key) * attn.scale
+ logits[:, :, :, query.shape[2]:] += self.shared_score_shift
+ probs = logits.softmax(-1)
+ return torch.einsum('bhqk,bhkd->bhqd', probs, value)
+
+ def shared_call( # pylint: disable=unused-argument
+ self,
+ attn: attention_processor.Attention,
+ hidden_states,
+ encoder_hidden_states=None,
+ attention_mask=None,
+ **kwargs
+ ):
+
+ residual = hidden_states
+ input_ndim = hidden_states.ndim
+ if input_ndim == 4:
+ batch_size, channel, height, width = hidden_states.shape
+ hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2)
+ batch_size, sequence_length, _ = (
+ hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
+ )
+
+ if attention_mask is not None:
+ attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
+ # scaled_dot_product_attention expects attention_mask shape to be
+ # (batch, heads, source_length, target_length)
+ attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1])
+
+ if attn.group_norm is not None:
+ hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
+
+ query = attn.to_q(hidden_states)
+ key = attn.to_k(hidden_states)
+ value = attn.to_v(hidden_states)
+ inner_dim = key.shape[-1]
+ head_dim = inner_dim // attn.heads
+
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
+ # if self.step >= self.start_inject:
+ if self.adain_queries:
+ query = adain(query)
+ if self.adain_keys:
+ key = adain(key)
+ if self.adain_values:
+ value = adain(value)
+ if self.share_attention:
+ key = concat_first(key, -2, scale=self.shared_score_scale)
+ value = concat_first(value, -2)
+ if self.shared_score_shift != 0:
+ hidden_states = self.shifted_scaled_dot_product_attention(attn, query, key, value,)
+ else:
+ hidden_states = nnf.scaled_dot_product_attention(
+ query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False
+ )
+ else:
+ hidden_states = nnf.scaled_dot_product_attention(
+ query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False
+ )
+ # hidden_states = adain(hidden_states)
+ hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
+ hidden_states = hidden_states.to(query.dtype)
+
+ # linear proj
+ hidden_states = attn.to_out[0](hidden_states)
+ # dropout
+ hidden_states = attn.to_out[1](hidden_states)
+
+ if input_ndim == 4:
+ hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)
+
+ if attn.residual_connection:
+ hidden_states = hidden_states + residual
+
+ hidden_states = hidden_states / attn.rescale_output_factor
+ return hidden_states
+
+ def __call__(self, attn: attention_processor.Attention, hidden_states, encoder_hidden_states=None,
+ attention_mask=None, **kwargs):
+ if self.full_attention_share:
+ _b, n, _d = hidden_states.shape
+ hidden_states = einops.rearrange(hidden_states, '(k b) n d -> k (b n) d', k=2)
+ hidden_states = super().__call__(attn, hidden_states, encoder_hidden_states=encoder_hidden_states,
+ attention_mask=attention_mask, **kwargs)
+ hidden_states = einops.rearrange(hidden_states, 'k (b n) d -> (k b) n d', n=n)
+ else:
+ hidden_states = self.shared_call(attn, hidden_states, hidden_states, attention_mask, **kwargs)
+
+ return hidden_states
+
+ def __init__(self, style_aligned_args: StyleAlignedArgs):
+ super().__init__()
+ self.share_attention = style_aligned_args.share_attention
+ self.adain_queries = style_aligned_args.adain_queries
+ self.adain_keys = style_aligned_args.adain_keys
+ self.adain_values = style_aligned_args.adain_values
+ self.full_attention_share = style_aligned_args.full_attention_share
+ self.shared_score_scale = style_aligned_args.shared_score_scale
+ self.shared_score_shift = style_aligned_args.shared_score_shift
+
+
+def _get_switch_vec(total_num_layers, level):
+ if level <= 0:
+ return torch.zeros(total_num_layers, dtype=torch.bool)
+ if level >= 1:
+ return torch.ones(total_num_layers, dtype=torch.bool)
+ to_flip = level > .5
+ if to_flip:
+ level = 1 - level
+ num_switch = int(level * total_num_layers)
+ vec = torch.arange(total_num_layers)
+ vec = vec % (total_num_layers // num_switch)
+ vec = vec == 0
+ if to_flip:
+ vec = ~vec
+ return vec
+
+
+def init_attention_processors(pipeline: StableDiffusionXLPipeline, style_aligned_args: StyleAlignedArgs | None = None):
+ attn_procs = {}
+ unet = pipeline.unet
+ number_of_self, number_of_cross = 0, 0
+ num_self_layers = len([name for name in unet.attn_processors.keys() if 'attn1' in name])
+ if style_aligned_args is None:
+ only_self_vec = _get_switch_vec(num_self_layers, 1)
+ else:
+ only_self_vec = _get_switch_vec(num_self_layers, style_aligned_args.only_self_level)
+ for i, name in enumerate(unet.attn_processors.keys()):
+ is_self_attention = 'attn1' in name
+ if is_self_attention:
+ number_of_self += 1
+ if style_aligned_args is None or only_self_vec[i // 2]:
+ attn_procs[name] = DefaultAttentionProcessor()
+ else:
+ attn_procs[name] = SharedAttentionProcessor(style_aligned_args)
+ else:
+ number_of_cross += 1
+ attn_procs[name] = DefaultAttentionProcessor()
+
+ unet.set_attn_processor(attn_procs)
+
+
+def register_shared_norm(pipeline: StableDiffusionXLPipeline,
+ share_group_norm: bool = True,
+ share_layer_norm: bool = True,
+ ):
+ def register_norm_forward(norm_layer: nn.GroupNorm | nn.LayerNorm) -> nn.GroupNorm | nn.LayerNorm:
+ if not hasattr(norm_layer, 'orig_forward'):
+ setattr(norm_layer, 'orig_forward', norm_layer.forward) # noqa
+ orig_forward = norm_layer.orig_forward
+
+ def forward_(hidden_states: T) -> T:
+ n = hidden_states.shape[-2]
+ hidden_states = concat_first(hidden_states, dim=-2)
+ hidden_states = orig_forward(hidden_states)
+ return hidden_states[..., :n, :]
+
+ norm_layer.forward = forward_
+ return norm_layer
+
+ def get_norm_layers(pipeline_, norm_layers_: dict[str, list[nn.GroupNorm | nn.LayerNorm]]):
+ if isinstance(pipeline_, nn.LayerNorm) and share_layer_norm:
+ norm_layers_['layer'].append(pipeline_)
+ if isinstance(pipeline_, nn.GroupNorm) and share_group_norm:
+ norm_layers_['group'].append(pipeline_)
+ else:
+ for layer in pipeline_.children():
+ get_norm_layers(layer, norm_layers_)
+
+ norm_layers = {'group': [], 'layer': []}
+ get_norm_layers(pipeline.unet, norm_layers)
+ return [register_norm_forward(layer) for layer in norm_layers['group']] + [register_norm_forward(layer) for layer in
+ norm_layers['layer']]
+
+
+class Handler:
+
+ def register(self, style_aligned_args: StyleAlignedArgs):
+ self.norm_layers = register_shared_norm(self.pipeline, style_aligned_args.share_group_norm,
+ style_aligned_args.share_layer_norm)
+ init_attention_processors(self.pipeline, style_aligned_args)
+
+ def remove(self):
+ for layer in self.norm_layers:
+ layer.forward = layer.orig_forward
+ self.norm_layers = []
+ init_attention_processors(self.pipeline, None)
+
+ def __init__(self, pipeline: StableDiffusionXLPipeline):
+ self.pipeline = pipeline
+ self.norm_layers = []
diff --git a/modules/timer.py b/modules/timer.py
index 8a5db726d..7657ac8e8 100644
--- a/modules/timer.py
+++ b/modules/timer.py
@@ -15,6 +15,12 @@ class Timer:
self.start = end
return res
+ def add(self, name, t):
+ if name not in self.records:
+ self.records[name] = t
+ else:
+ self.records[name] += t
+
def record(self, category=None, extra_time=0, reset=True):
e = self.elapsed(reset)
if category is None:
diff --git a/modules/txt2img.py b/modules/txt2img.py
index 2f0e2f4b3..e82c744a2 100644
--- a/modules/txt2img.py
+++ b/modules/txt2img.py
@@ -88,7 +88,7 @@ def txt2img(id_task, state,
p.scripts = scripts.scripts_txt2img
p.script_args = args
p.state = state
- processed = scripts.scripts_txt2img.run(p, *args)
+ processed: processing.Processed = scripts.scripts_txt2img.run(p, *args)
if processed is None:
processed = processing.process_images(p)
processed = scripts.scripts_txt2img.after(p, processed, *args)
diff --git a/modules/ui_common.py b/modules/ui_common.py
index 9c4bb5cdc..3e7c68bec 100644
--- a/modules/ui_common.py
+++ b/modules/ui_common.py
@@ -245,10 +245,18 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None):
gr.HTML(value="", elem_id="main_info", visible=False, elem_classes=["main-info"])
# columns are for <576px, <768px, <992px, <1200px, <1400px, >1400px
result_gallery = gr.Gallery(value=[],
- label='Output', show_label=False, show_download_button=True, allow_preview=True, container=False, preview=preview,
- columns=4, object_fit='scale-down', height=height,
+ label='Output',
+ show_label=False,
+ show_download_button=True,
+ allow_preview=True,
+ container=False,
+ preview=preview,
+ columns=4,
+ object_fit='scale-down',
+ height=height,
elem_id=f"{tabname}_gallery",
- )
+ elem_classes=["gallery_main"],
+ )
if prompt is not None:
interrogate_clip_btn, interrogate_booru_btn = ui_sections.create_interrogate_buttons('control')
interrogate_clip_btn.click(fn=interrogate_clip, inputs=[result_gallery], outputs=[prompt])
diff --git a/modules/ui_control.py b/modules/ui_control.py
index 0bf070036..072d9b9c9 100644
--- a/modules/ui_control.py
+++ b/modules/ui_control.py
@@ -9,11 +9,11 @@ from modules.control.units import xs # vislearn ControlNet-XS
from modules.control.units import lite # vislearn ControlNet-XS
from modules.control.units import t2iadapter # TencentARC T2I-Adapter
from modules.control.units import reference # reference pipeline
-from modules import errors, shared, progress, ui_components, ui_symbols, ui_common, ui_sections, generation_parameters_copypaste, call_queue, scripts, masking, images, processing_vae # pylint: disable=ungrouped-imports
+from modules import errors, shared, progress, ui_components, ui_symbols, ui_common, ui_sections, generation_parameters_copypaste, call_queue, scripts, masking, images, processing_vae, timer # pylint: disable=ungrouped-imports
from modules import ui_control_helpers as helpers
-gr_height = None
+gr_height = 512
max_units = shared.opts.control_max_units
units: list[unit.Unit] = [] # main state variable
controls: list[gr.component] = [] # list of gr controls
@@ -21,13 +21,36 @@ debug = shared.log.trace if os.environ.get('SD_CONTROL_DEBUG', None) is not None
debug('Trace: CONTROL')
-def return_controls(res):
+def return_stats(t: float = None):
+ if t is None:
+ elapsed_text = ''
+ else:
+ elapsed = time.perf_counter() - t
+ elapsed_m = int(elapsed // 60)
+ elapsed_s = elapsed % 60
+ elapsed_text = f"Time: {elapsed_m}m {elapsed_s:.2f}s |" if elapsed_m > 0 else f"Time: {elapsed_s:.2f}s |"
+ summary = timer.process.summary(min_time=0.1, total=False).replace('=', ' ')
+ vram_html = ''
+ if not shared.mem_mon.disabled:
+ vram = {k: -(v//-(1024*1024)) for k, v in shared.mem_mon.read().items()}
+ used = round(100 * vram['used'] / (vram['total'] + 0.001))
+ if vram.get('active_peak', 0) > 0:
+ vram_html += f"| GPU {max(vram['active_peak'], vram['reserved_peak'])} MB {used}%"
+ vram_html += f" | retries {vram['retries']} oom {vram['oom']}" if vram.get('retries', 0) > 0 or vram.get('oom', 0) > 0 else ''
+ return f""
+
+
+def return_controls(res, t: float = None):
# return preview, image, video, gallery, text
debug(f'Control received: type={type(res)} {res}')
+ if t is None:
+ perf = ''
+ else:
+ perf = return_stats(t)
if res is None: # no response
- return [None, None, None, None, '']
+ return [None, None, None, None, '', perf]
elif isinstance(res, str): # error response
- return [None, None, None, None, res]
+ return [None, None, None, None, res, perf]
elif isinstance(res, tuple): # standard response received as tuple via control_run->yield(output_images, process_image, result_txt)
preview_image = res[1] # may be None
output_image = res[0][0] if isinstance(res[0], list) else res[0] # may be image or list of images
@@ -37,9 +60,9 @@ def return_controls(res):
output_gallery = [res[0]] if res[0] is not None else [] # must return list, but can receive single image
result_txt = res[2] if len(res) > 2 else '' # do we have a message
output_video = res[3] if len(res) > 3 else None # do we have a video filename
- return [preview_image, output_image, output_video, output_gallery, result_txt]
+ return [preview_image, output_image, output_video, output_gallery, result_txt, perf]
else: # unexpected
- return [None, None, None, None, f'Control: Unexpected response: {type(res)}']
+ return [None, None, None, None, f'Control: Unexpected response: {type(res)}', perf]
def get_units(*values):
@@ -67,17 +90,18 @@ def generate_click(job_id: str, state: str, active_tab: str, *args):
shared.state.begin('Generate')
progress.add_task_to_queue(job_id)
with call_queue.queue_lock:
- yield [None, None, None, None, 'Control: starting']
+ yield [None, None, None, None, 'Control: starting', '']
shared.mem_mon.reset()
progress.start_task(job_id)
try:
+ t = time.perf_counter()
for results in control_run(state, units, helpers.input_source, helpers.input_init, helpers.input_mask, active_tab, True, *args):
progress.record_results(job_id, results)
- yield return_controls(results)
+ yield return_controls(results, t)
except Exception as e:
shared.log.error(f"Control exception: {e}")
errors.display(e, 'Control')
- yield [None, None, None, None, f'Control: Exception: {e}']
+ yield [None, None, None, None, f'Control: Exception: {e}', '']
progress.finish_task(job_id)
shared.state.end()
@@ -106,11 +130,12 @@ def create_ui(_blocks: gr.Blocks=None):
with gr.Accordion(open=False, label="Input", elem_id="control_input", elem_classes=["small-accordion"]):
with gr.Row():
- show_preview = gr.Checkbox(label="Show preview", value=True, elem_id="control_show_preview")
+ show_input = gr.Checkbox(label="Show input", value=True, elem_id="control_show_input")
+ show_preview = gr.Checkbox(label="Show preview", value=False, elem_id="control_show_preview")
with gr.Row():
input_type = gr.Radio(label="Input type", choices=['Control only', 'Init image same as control', 'Separate init image'], value='Control only', type='index', elem_id='control_input_type')
with gr.Row():
- denoising_strength = gr.Slider(minimum=0.01, maximum=1.0, step=0.01, label='Denoising strength', value=0.50, elem_id="control_input_denoising_strength")
+ denoising_strength = gr.Slider(minimum=0.01, maximum=1.0, step=0.01, label='Denoising strength', value=0.30, elem_id="control_input_denoising_strength")
with gr.Accordion(open=False, label="Size", elem_id="control_size", elem_classes=["small-accordion"]):
with gr.Tabs():
@@ -153,13 +178,13 @@ def create_ui(_blocks: gr.Blocks=None):
override_settings = ui_common.create_override_inputs('control')
with gr.Row(variant='compact', elem_id="control_extra_networks", elem_classes=["extra_networks_root"], visible=False) as extra_networks_ui:
- from modules import timer, ui_extra_networks
+ from modules import ui_extra_networks
extra_networks_ui = ui_extra_networks.create_ui(extra_networks_ui, btn_extra, 'control', skip_indexing=shared.opts.extra_network_skip_indexing)
timer.startup.record('ui-networks')
with gr.Row(elem_id='control-inputs'):
- with gr.Column(scale=9, elem_id='control-input-column', visible=True) as _column_input:
- gr.HTML('Control input
')
+ with gr.Column(scale=9, elem_id='control-input-column', visible=True) as column_input:
+ gr.HTML('Input')
with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-input'):
with gr.Tab('Image', id='in-image') as tab_image:
input_mode = gr.Label(value='select', visible=False)
@@ -190,12 +215,12 @@ def create_ui(_blocks: gr.Blocks=None):
gr.HTML('Output')
with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-output') as output_tabs:
with gr.Tab('Gallery', id='out-gallery'):
- output_gallery, _output_gen_info, _output_html_info, _output_html_info_formatted, _output_html_log = ui_common.create_output_panel("control", preview=True, prompt=prompt, height=gr_height)
+ output_gallery, _output_gen_info, _output_html_info, _output_html_info_formatted, output_html_log = ui_common.create_output_panel("control", preview=True, prompt=prompt, height=gr_height)
with gr.Tab('Image', id='out-image'):
output_image = gr.Image(label="Output", show_label=False, type="pil", interactive=False, tool="editor", height=gr_height, elem_id='control_output_image', elem_classes=['control-image'])
with gr.Tab('Video', id='out-video'):
output_video = gr.Video(label="Output", show_label=False, height=gr_height, elem_id='control_output_video', elem_classes=['control-image'])
- with gr.Column(scale=9, elem_id='control-preview-column', visible=True) as column_preview:
+ with gr.Column(scale=9, elem_id='control-preview-column', visible=False) as column_preview:
gr.HTML('Preview')
with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-preview'):
with gr.Tab('Preview', id='preview-image') as _tab_preview:
@@ -498,6 +523,7 @@ def create_ui(_blocks: gr.Blocks=None):
btn_update = gr.Button('Update', interactive=True, visible=False, elem_id='control_update')
btn_update.click(fn=get_units, inputs=controls, outputs=[], show_progress=True, queue=False)
+ show_input.change(fn=lambda x: gr.update(visible=x), inputs=[show_input], outputs=[column_input])
show_preview.change(fn=lambda x: gr.update(visible=x), inputs=[show_preview], outputs=[column_preview])
input_type.change(fn=lambda x: gr.update(visible=x == 2), inputs=[input_type], outputs=[column_init])
btn_prompt_counter.click(fn=call_queue.wrap_queued_call(ui_common.update_token_counter), inputs=[prompt, steps], outputs=[prompt_counter])
@@ -550,6 +576,7 @@ def create_ui(_blocks: gr.Blocks=None):
output_video,
output_gallery,
result_txt,
+ output_html_log,
]
control_dict = dict(
fn=generate_click,
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index f6e6cee97..c326219df 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -16,7 +16,7 @@ from collections import OrderedDict
import gradio as gr
from PIL import Image
from starlette.responses import FileResponse, JSONResponse
-from modules import paths, shared, scripts, files_cache, errors, infotext
+from modules import paths, shared, files_cache, errors, infotext
from modules.ui_components import ToolButton
import modules.ui_symbols as symbols
@@ -135,6 +135,7 @@ class ExtraNetworksPage:
return text.replace('~tabname', tabname)
def create_xyz_grid(self):
+ """
xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "xyz_grid.py"][0].module
def add_prompt(p, opt, x):
@@ -150,6 +151,7 @@ class ExtraNetworksPage:
opt = xyz_grid.AxisOption(f"[Network] {self.title}", str, add_prompt, choices=lambda: [x["name"] for x in self.items])
if opt not in xyz_grid.axis_options:
xyz_grid.axis_options.append(opt)
+ """
def link_preview(self, filename):
quoted_filename = urllib.parse.quote(filename.replace('\\', '/'))
diff --git a/modules/ui_img2img.py b/modules/ui_img2img.py
index 22c89dac8..3c3d63656 100644
--- a/modules/ui_img2img.py
+++ b/modules/ui_img2img.py
@@ -1,7 +1,6 @@
import os
from PIL import Image
import gradio as gr
-import numpy as np
from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call
from modules import timer, shared, ui_common, ui_sections, generation_parameters_copypaste, processing_vae
@@ -56,7 +55,7 @@ def create_ui():
def add_copy_image_controls(tab_name, elem):
with gr.Row(variant="compact", elem_id=f"img2img_copy_to_{tab_name}"):
- for title, name in zip(['➠ Image', '➠ Sketch', '➠ Inpaint', '➠ Composite'], ['img2img', 'sketch', 'inpaint', 'inpaint_sketch']):
+ for title, name in zip(['➠ Image', '➠ Inpaint', '➠ Sketch', '➠ Composite'], ['img2img', 'sketch', 'inpaint', 'composite']):
if name == tab_name:
gr.Button(title, elem_id=f'copy_to_{name}', interactive=False)
copy_image_destinations[name] = elem
@@ -67,33 +66,36 @@ def create_ui():
with gr.Tabs(elem_id="mode_img2img"):
img2img_selected_tab = gr.State(0) # pylint: disable=abstract-class-instantiated
state = gr.Textbox(value='', visible=False)
- with gr.TabItem('Image', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img:
- init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA", height=512)
+ with gr.TabItem('Image', id='img2img_image', elem_id="img2img_image_tab") as tab_img2img:
+ img_init = gr.Image(label="", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA", height=512)
interrogate_clip, interrogate_booru = ui_sections.create_interrogate_buttons('img2img')
- add_copy_image_controls('img2img', init_img)
+ add_copy_image_controls('img2img', img_init)
- with gr.TabItem('Sketch', id='img2img_sketch', elem_id="img2img_img2img_sketch_tab") as tab_sketch:
- sketch = gr.Image(label="Image for img2img", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512)
- add_copy_image_controls('sketch', sketch)
+ with gr.TabItem('Inpaint', id='img2img_inpaint', elem_id="img2img_inpaint_tab") as tab_inpaint:
+ img_inpaint = gr.Image(label="", elem_id="img2img_inpaint", show_label=False, source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA", height=512)
+ add_copy_image_controls('inpaint', img_inpaint)
- with gr.TabItem('Inpaint', id='inpaint', elem_id="img2img_inpaint_tab") as tab_inpaint:
- init_img_with_mask = gr.Image(label="Image for inpainting with mask", show_label=False, elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA", height=512)
- add_copy_image_controls('inpaint', init_img_with_mask)
+ with gr.TabItem('Sketch', id='img2img_sketch', elem_id="img2img_sketch_tab") as tab_sketch:
+ img_sketch = gr.Image(label="", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512)
+ add_copy_image_controls('sketch', img_sketch)
- with gr.TabItem('Composite', id='inpaint_sketch', elem_id="img2img_inpaint_sketch_tab") as tab_inpaint_color:
- inpaint_color_sketch = gr.Image(label="Color sketch inpainting", show_label=False, elem_id="inpaint_sketch", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512)
- inpaint_color_sketch_orig = gr.State(None) # pylint: disable=abstract-class-instantiated
- add_copy_image_controls('inpaint_sketch', inpaint_color_sketch)
+ with gr.TabItem('Composite', id='img2img_composite', elem_id="img2img_composite_tab") as tab_inpaint_color:
+ img_composite = gr.Image(label="", show_label=False, elem_id="img2img_composite", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGBA", height=512)
+ img_composite_orig = gr.State(None) # pylint: disable=abstract-class-instantiated
+ img_composite_orig_update = False
- def update_orig(image, state):
- if image is not None:
- same_size = state is not None and state.size == image.size
- has_exact_match = np.any(np.all(np.array(image) == np.array(state), axis=-1))
- edited = same_size and has_exact_match
- return image if not edited or state is None else state
- return state
+ def fn_img_composite_upload():
+ nonlocal img_composite_orig_update
+ img_composite_orig_update = True
+ def fn_img_composite_change(img, img_composite):
+ nonlocal img_composite_orig_update
+ res = img if img_composite_orig_update else img_composite
+ img_composite_orig_update = False
+ return res
- inpaint_color_sketch.change(update_orig, [inpaint_color_sketch, inpaint_color_sketch_orig], inpaint_color_sketch_orig)
+ img_composite.upload(fn=fn_img_composite_upload, inputs=[], outputs=[])
+ img_composite.change(fn=fn_img_composite_change, inputs=[img_composite, img_composite_orig], outputs=[img_composite_orig])
+ add_copy_image_controls('composite', img_composite)
with gr.TabItem('Upload', id='inpaint_upload', elem_id="img2img_inpaint_upload_tab") as tab_inpaint_upload:
init_img_inpaint = gr.Image(label="Image for img2img", show_label=False, source="upload", interactive=True, type="pil", elem_id="img_inpaint_base")
@@ -120,13 +122,13 @@ def create_ui():
with gr.Accordion(open=False, label="Sampler", elem_classes=["small-accordion"], elem_id="img2img_sampler_group"):
steps, sampler_index = ui_sections.create_sampler_and_steps_selection(None, "img2img")
ui_sections.create_sampler_options('img2img')
- resize_mode, resize_name, resize_context, width, height, scale_by, selected_scale_tab = ui_sections.create_resize_inputs('img2img', [init_img, sketch], latent=True, non_zero=False)
+ resize_mode, resize_name, resize_context, width, height, scale_by, selected_scale_tab = ui_sections.create_resize_inputs('img2img', [img_init, img_sketch], latent=True, non_zero=False)
batch_count, batch_size = ui_sections.create_batch_inputs('img2img', accordion=True)
seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = ui_sections.create_seed_inputs('img2img')
with gr.Accordion(open=False, label="Denoise", elem_classes=["small-accordion"], elem_id="img2img_denoise_group"):
with gr.Row():
- denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="img2img_denoising_strength")
+ denoising_strength = gr.Slider(minimum=0.0, maximum=0.99, step=0.01, label='Denoising strength', value=0.30, elem_id="img2img_denoising_strength")
refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoise start', value=0.0, elem_id="img2img_refiner_start")
full_quality, tiling, hidiffusion, cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, pag_scale, pag_adaptive, cfg_end = ui_sections.create_advanced_inputs('img2img')
@@ -167,13 +169,8 @@ def create_ui():
img2img_args = [
dummy_component1, state, dummy_component2,
img2img_prompt, img2img_negative_prompt, img2img_prompt_styles,
- init_img,
- sketch,
- init_img_with_mask,
- inpaint_color_sketch,
- inpaint_color_sketch_orig,
- init_img_inpaint,
- init_mask_inpaint,
+ img_init, img_sketch, img_inpaint, img_composite, img_composite_orig,
+ init_img_inpaint, init_mask_inpaint,
steps,
sampler_index,
mask_blur, mask_alpha,
@@ -225,10 +222,7 @@ def create_ui():
img2img_batch_files,
img2img_batch_input_dir,
img2img_batch_output_dir,
- init_img,
- sketch,
- init_img_with_mask,
- inpaint_color_sketch,
+ img_init, img_sketch, img_inpaint, img_composite,
init_img_inpaint,
],
outputs=[img2img_prompt, dummy_component],
@@ -285,7 +279,8 @@ def create_ui():
(seed_resize_from_h, "Seed resize from-2"),
*modules.scripts.scripts_img2img.infotext_fields
]
- generation_parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings)
- generation_parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings)
+ generation_parameters_copypaste.add_paste_fields("img2img", img_init, img2img_paste_fields, override_settings)
+ generation_parameters_copypaste.add_paste_fields("sketch", img_sketch, img2img_paste_fields, override_settings)
+ generation_parameters_copypaste.add_paste_fields("inpaint", img_inpaint, img2img_paste_fields, override_settings)
img2img_bindings = generation_parameters_copypaste.ParamBinding(paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None)
generation_parameters_copypaste.register_paste_params_button(img2img_bindings)
diff --git a/scripts/animatediff.py b/scripts/animatediff.py
index 4c50f9cf6..91db60915 100644
--- a/scripts/animatediff.py
+++ b/scripts/animatediff.py
@@ -189,7 +189,7 @@ def set_free_noise(frames):
class Script(scripts.Script):
def title(self):
- return 'Video AnimateDiff'
+ return 'Video: AnimateDiff'
def show(self, is_img2img):
# return scripts.AlwaysVisible if shared.native else False
diff --git a/scripts/cogvideo.py b/scripts/cogvideo.py
index 7f2c7225e..c988c05c4 100644
--- a/scripts/cogvideo.py
+++ b/scripts/cogvideo.py
@@ -22,7 +22,7 @@ debug = (os.environ.get('SD_LOAD_DEBUG', None) is not None) or (os.environ.get('
class Script(scripts.Script):
def title(self):
- return 'Video CogVideoX'
+ return 'Video: CogVideoX'
def show(self, is_img2img):
return shared.native
diff --git a/scripts/flux_tools.py b/scripts/flux_tools.py
new file mode 100644
index 000000000..e5fe443b7
--- /dev/null
+++ b/scripts/flux_tools.py
@@ -0,0 +1,123 @@
+# https://github.com/huggingface/diffusers/pull/9985
+
+import time
+import gradio as gr
+import diffusers
+from modules import scripts, processing, shared, devices, sd_models
+from installer import install
+
+
+redux_pipe: diffusers.FluxPriorReduxPipeline = None
+processor_canny = None
+processor_depth = None
+title = 'Flux Tools'
+
+
+class Script(scripts.Script):
+ def title(self):
+ return f'{title}'
+
+ def show(self, is_img2img):
+ return is_img2img if shared.native else False
+
+ def ui(self, _is_img2img): # ui elements
+ with gr.Row():
+ gr.HTML('  Flux.1 Redux
')
+ with gr.Row():
+ tool = gr.Dropdown(label='Tool', choices=['None', 'Redux', 'Fill', 'Canny', 'Depth'], value='None')
+ with gr.Row():
+ process = gr.Checkbox(label='Preprocess input images', value=True)
+ strength = gr.Checkbox(label='Override denoise strength', value=True)
+ return [tool, strength, process]
+
+ def run(self, p: processing.StableDiffusionProcessing, tool: str = 'None', strength: bool = True, process: bool = True): # pylint: disable=arguments-differ
+ global redux_pipe, processor_canny, processor_depth # pylint: disable=global-statement
+ if tool is None or tool == 'None':
+ return
+ supported_model_list = ['f1']
+ if shared.sd_model_type not in supported_model_list:
+ shared.log.warning(f'{title}: class={shared.sd_model.__class__.__name__} model={shared.sd_model_type} required={supported_model_list}')
+ return None
+ image = getattr(p, 'init_images', None)
+ if image is None or len(image) == 0:
+ shared.log.error(f'{title}: tool={tool} no init_images')
+ return None
+ else:
+ image = image[0] if isinstance(image, list) else image
+
+ shared.log.info(f'{title}: tool={tool} init')
+
+ t0 = time.time()
+ if tool == 'Redux':
+ # pipe_prior_redux = FluxPriorReduxPipeline.from_pretrained("black-forest-labs/FLUX.1-Redux-dev", revision="refs/pr/8", torch_dtype=torch.bfloat16).to("cuda")
+ if redux_pipe is None:
+ redux_pipe = diffusers.FluxPriorReduxPipeline.from_pretrained(
+ "black-forest-labs/FLUX.1-Redux-dev",
+ revision="refs/pr/8",
+ torch_dtype=devices.dtype,
+ cache_dir=shared.opts.hfcache_dir
+ ).to(devices.device)
+ redux_output = redux_pipe(image)
+ for k, v in redux_output.items():
+ p.task_args[k] = v
+ else:
+ if redux_pipe is not None:
+ shared.log.debug(f'{title}: tool=Redux unload')
+ redux_pipe = None
+
+ if tool == 'Fill':
+ # pipe = FluxFillPipeline.from_pretrained("black-forest-labs/FLUX.1-Fill-dev", torch_dtype=torch.bfloat16, revision="refs/pr/4").to("cuda")
+ if p.image_mask is None:
+ shared.log.error(f'{title}: tool={tool} no image_mask')
+ return None
+ if shared.sd_model.__class__.__name__ != 'FluxFillPipeline':
+ shared.opts.data["sd_model_checkpoint"] = "black-forest-labs/FLUX.1-Fill-dev"
+ sd_models.reload_model_weights(op='model', revision="refs/pr/4")
+ p.task_args['image'] = image
+ p.task_args['mask_image'] = p.image_mask
+
+ if tool == 'Canny':
+ # pipe = FluxControlPipeline.from_pretrained("black-forest-labs/FLUX.1-Canny-dev", torch_dtype=torch.bfloat16, revision="refs/pr/1").to("cuda")
+ install('controlnet-aux')
+ install('timm==0.9.16')
+ if shared.sd_model.__class__.__name__ != 'FluxControlPipeline' or 'Canny' not in shared.opts.sd_model_checkpoint:
+ shared.opts.data["sd_model_checkpoint"] = "black-forest-labs/FLUX.1-Canny-dev"
+ sd_models.reload_model_weights(op='model', revision="refs/pr/1")
+ if processor_canny is None:
+ from controlnet_aux import CannyDetector
+ processor_canny = CannyDetector()
+ if process:
+ control_image = processor_canny(image, low_threshold=50, high_threshold=200, detect_resolution=1024, image_resolution=1024)
+ p.task_args['control_image'] = control_image
+ else:
+ p.task_args['control_image'] = image
+ if strength:
+ p.task_args['strength'] = None
+ else:
+ if processor_canny is not None:
+ shared.log.debug(f'{title}: tool=Canny unload processor')
+ processor_canny = None
+
+ if tool == 'Depth':
+ # pipe = FluxControlPipeline.from_pretrained("black-forest-labs/FLUX.1-Depth-dev", torch_dtype=torch.bfloat16, revision="refs/pr/1").to("cuda")
+ install('git+https://github.com/asomoza/image_gen_aux.git', 'image_gen_aux')
+ if shared.sd_model.__class__.__name__ != 'FluxControlPipeline' or 'Depth' not in shared.opts.sd_model_checkpoint:
+ shared.opts.data["sd_model_checkpoint"] = "black-forest-labs/FLUX.1-Depth-dev"
+ sd_models.reload_model_weights(op='model', revision="refs/pr/1")
+ if processor_depth is None:
+ from image_gen_aux import DepthPreprocessor
+ processor_depth = DepthPreprocessor.from_pretrained("LiheYoung/depth-anything-large-hf")
+ if process:
+ control_image = processor_depth(image)[0].convert("RGB")
+ p.task_args['control_image'] = control_image
+ else:
+ p.task_args['control_image'] = image
+ if strength:
+ p.task_args['strength'] = None
+ else:
+ if processor_depth is not None:
+ shared.log.debug(f'{title}: tool=Depth unload processor')
+ processor_depth = None
+
+ shared.log.debug(f'{title}: tool={tool} ready time={time.time() - t0:.2f}')
+ devices.torch_gc()
diff --git a/scripts/image2video.py b/scripts/image2video.py
index 876ed3193..5e08922ee 100644
--- a/scripts/image2video.py
+++ b/scripts/image2video.py
@@ -13,7 +13,7 @@ MODELS = [
class Script(scripts.Script):
def title(self):
- return 'Video VGen Image-to-Video'
+ return 'Video: VGen Image-to-Video'
def show(self, is_img2img):
return is_img2img if shared.native else False
diff --git a/scripts/pulid_ext.py b/scripts/pulid_ext.py
index 676fa79f3..ee08e348b 100644
--- a/scripts/pulid_ext.py
+++ b/scripts/pulid_ext.py
@@ -164,11 +164,13 @@ class Script(scripts.Script):
p.batch_size = 1
sdp = shared.opts.cross_attention_optimization == "Scaled-Dot-Product"
+ sampler_fn = getattr(self.pulid.sampling, f'sample_{sampler}', None)
strength = getattr(p, 'pulid_strength', strength)
zero = getattr(p, 'pulid_zero', zero)
ortho = getattr(p, 'pulid_ortho', ortho)
sampler = getattr(p, 'pulid_sampler', sampler)
- sampler_fn = getattr(self.pulid.sampling, f'sample_{sampler}', None)
+ restore = getattr(p, 'pulid_restore', restore)
+ p.pulid_restore = restore
if sampler_fn is None:
sampler_fn = self.pulid.sampling.sample_dpmpp_2m_sde
@@ -199,7 +201,7 @@ class Script(scripts.Script):
return None
shared.sd_model.sampler = sampler_fn
- shared.log.info(f'PuLID: class={shared.sd_model.__class__.__name__} version="{version}" sdp={sdp} strength={strength} zero={zero} ortho={ortho} sampler={sampler_fn} images={[i.shape for i in images]} offload={offload}')
+ shared.log.info(f'PuLID: class={shared.sd_model.__class__.__name__} version="{version}" sdp={sdp} strength={strength} zero={zero} ortho={ortho} sampler={sampler_fn} images={[i.shape for i in images]} offload={offload} restore={restore}')
self.pulid.attention.NUM_ZERO = zero
self.pulid.attention.ORTHO = ortho == 'v1'
self.pulid.attention.ORTHO_v2 = ortho == 'v2'
diff --git a/scripts/style_aligned.py b/scripts/style_aligned.py
new file mode 100644
index 000000000..25feb49bc
--- /dev/null
+++ b/scripts/style_aligned.py
@@ -0,0 +1,117 @@
+import gradio as gr
+import torch
+import numpy as np
+import diffusers
+from modules import scripts, processing, shared, devices
+
+
+handler = None
+zts = None
+supported_model_list = ['sdxl']
+orig_prompt_attention = None
+
+
+class Script(scripts.Script):
+ def title(self):
+ return 'Style Aligned Image Generation'
+
+ def show(self, is_img2img):
+ return shared.native
+
+ def reset(self):
+ global handler, zts # pylint: disable=global-statement
+ handler = None
+ zts = None
+ shared.log.info('SA: image upload')
+
+ def preset(self, preset):
+ if preset == 'text':
+ return [['attention', 'adain_queries', 'adain_keys'], 1.0, 0, 0.0]
+ if preset == 'image':
+ return [['group_norm', 'layer_norm', 'attention', 'adain_queries', 'adain_keys'], 1.0, 2, 0.0]
+ if preset == 'all':
+ return [['group_norm', 'layer_norm', 'attention', 'adain_queries', 'adain_keys', 'adain_values', 'full_attention_share'], 1.0, 1, 0.5]
+
+ def ui(self, _is_img2img): # ui elements
+ with gr.Row():
+ gr.HTML('  Style Aligned Image Generation
')
+ with gr.Row():
+ preset = gr.Dropdown(label="Preset", choices=['text', 'image', 'all'], value='text')
+ scheduler = gr.Checkbox(label="Override scheduler", value=False)
+ with gr.Row():
+ shared_opts = gr.Dropdown(label="Shared options",
+ multiselect=True,
+ choices=['group_norm', 'layer_norm', 'attention', 'adain_queries', 'adain_keys', 'adain_values', 'full_attention_share'],
+ value=['attention', 'adain_queries', 'adain_keys'],
+ )
+ with gr.Row():
+ shared_score_scale = gr.Slider(label="Scale", minimum=0.0, maximum=2.0, step=0.01, value=1.0)
+ shared_score_shift = gr.Slider(label="Shift", minimum=0, maximum=10, step=1, value=0)
+ only_self_level = gr.Slider(label="Level", minimum=0.0, maximum=1.0, step=0.01, value=0.0)
+ with gr.Row():
+ prompt = gr.Textbox(lines=1, label='Optional image description', placeholder='use the style from the image')
+ with gr.Row():
+ image = gr.Image(label='Optional image', source='upload', type='pil')
+
+ image.change(self.reset)
+ preset.change(self.preset, inputs=[preset], outputs=[shared_opts, shared_score_scale, shared_score_shift, only_self_level])
+
+ return [image, prompt, scheduler, shared_opts, shared_score_scale, shared_score_shift, only_self_level]
+
+ def run(self, p: processing.StableDiffusionProcessing, image, prompt, scheduler, shared_opts, shared_score_scale, shared_score_shift, only_self_level): # pylint: disable=arguments-differ
+ global handler, zts, orig_prompt_attention # pylint: disable=global-statement
+ if shared.sd_model_type not in supported_model_list:
+ shared.log.warning(f'SA: class={shared.sd_model.__class__.__name__} model={shared.sd_model_type} required={supported_model_list}')
+ return None
+
+ from modules.style_aligned import sa_handler, inversion
+
+ handler = sa_handler.Handler(shared.sd_model)
+ sa_args = sa_handler.StyleAlignedArgs(
+ share_group_norm='group_norm' in shared_opts,
+ share_layer_norm='layer_norm' in shared_opts,
+ share_attention='attention' in shared_opts,
+ adain_queries='adain_queries' in shared_opts,
+ adain_keys='adain_keys' in shared_opts,
+ adain_values='adain_values' in shared_opts,
+ full_attention_share='full_attention_share' in shared_opts,
+ shared_score_scale=float(shared_score_scale),
+ shared_score_shift=np.log(shared_score_shift) if shared_score_shift > 0 else 0,
+ only_self_level=1 if only_self_level else 0,
+ )
+ handler.register(sa_args)
+
+ if scheduler:
+ shared.sd_model.scheduler = diffusers.DDIMScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", clip_sample=False, set_alpha_to_one=False)
+ p.sampler_name = 'None'
+
+ if image is not None and zts is None:
+ shared.log.info(f'SA: inversion image={image} prompt="{prompt}"')
+ image = image.resize((1024, 1024))
+ x0 = np.array(image).astype(np.float32) / 255.0
+ shared.sd_model.scheduler = diffusers.DDIMScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", clip_sample=False, set_alpha_to_one=False)
+ zts = inversion.ddim_inversion(shared.sd_model, x0, prompt, num_inference_steps=50, guidance_scale=2)
+
+ p.prompt = p.prompt.splitlines()
+ p.batch_size = len(p.prompt)
+ orig_prompt_attention = shared.opts.prompt_attention
+ shared.opts.data['prompt_attention'] = 'fixed' # otherwise need to deal with class_tokens_mask
+
+ if zts is not None:
+ processing.fix_seed(p)
+ zT, inversion_callback = inversion.make_inversion_callback(zts, offset=0)
+ generator = torch.Generator(device='cpu')
+ generator.manual_seed(p.seed)
+ latents = torch.randn(p.batch_size, 4, 128, 128, device='cpu', generator=generator, dtype=devices.dtype,).to(devices.device)
+ latents[0] = zT
+ p.task_args['latents'] = latents
+ p.task_args['callback_on_step_end'] = inversion_callback
+
+ shared.log.info(f'SA: batch={p.batch_size} type={"image" if zts is not None else "text"} config={sa_args.__dict__}')
+
+ def after(self, p: processing.StableDiffusionProcessing, *args): # pylint: disable=unused-argument
+ global handler # pylint: disable=global-statement
+ if handler is not None:
+ handler.remove()
+ handler = None
+ shared.opts.data['prompt_attention'] = orig_prompt_attention
diff --git a/scripts/xyz_grid_classes.py b/scripts/xyz_grid_classes.py
index b80b9f13c..cc70d68f8 100644
--- a/scripts/xyz_grid_classes.py
+++ b/scripts/xyz_grid_classes.py
@@ -1,4 +1,4 @@
-from scripts.xyz_grid_shared import apply_field, apply_task_args, apply_setting, apply_prompt, apply_order, apply_sampler, apply_hr_sampler_name, confirm_samplers, apply_checkpoint, apply_refiner, apply_unet, apply_dict, apply_clip_skip, apply_vae, list_lora, apply_lora, apply_te, apply_styles, apply_upscaler, apply_context, apply_detailer, apply_override, apply_processing, apply_options, apply_seed, format_value_add_label, format_value, format_value_join_list, do_nothing, format_nothing, str_permutations # pylint: disable=no-name-in-module, unused-import
+from scripts.xyz_grid_shared import apply_field, apply_task_args, apply_setting, apply_prompt, apply_order, apply_sampler, apply_hr_sampler_name, confirm_samplers, apply_checkpoint, apply_refiner, apply_unet, apply_dict, apply_clip_skip, apply_vae, list_lora, apply_lora, apply_lora_strength, apply_te, apply_styles, apply_upscaler, apply_context, apply_detailer, apply_override, apply_processing, apply_options, apply_seed, format_value_add_label, format_value, format_value_join_list, do_nothing, format_nothing, str_permutations # pylint: disable=no-name-in-module, unused-import
from modules import shared, shared_items, sd_samplers, ipadapter, sd_models, sd_vae, sd_unet
@@ -97,7 +97,7 @@ axis_options = [
AxisOption("[Prompt] Prompt order", str_permutations, apply_order, fmt=format_value_join_list),
AxisOption("[Prompt] Prompt parser", str, apply_setting("prompt_attention"), choices=lambda: ["native", "compel", "xhinker", "a1111", "fixed"]),
AxisOption("[Network] LoRA", str, apply_lora, cost=0.5, choices=list_lora),
- AxisOption("[Network] LoRA strength", float, apply_setting('extra_networks_default_multiplier')),
+ AxisOption("[Network] LoRA strength", float, apply_lora_strength, cost=0.6),
AxisOption("[Network] Styles", str, apply_styles, choices=lambda: [s.name for s in shared.prompt_styles.styles.values()]),
AxisOption("[Param] Width", int, apply_field("width")),
AxisOption("[Param] Height", int, apply_field("height")),
diff --git a/scripts/xyz_grid_shared.py b/scripts/xyz_grid_shared.py
index d3ee0a864..82387fab8 100644
--- a/scripts/xyz_grid_shared.py
+++ b/scripts/xyz_grid_shared.py
@@ -63,28 +63,15 @@ def apply_seed(p, x, xs):
def apply_prompt(p, x, xs):
- if not hasattr(p, 'orig_prompt'):
- p.orig_prompt = p.prompt
- p.orig_negative = p.negative_prompt
- if xs[0] not in p.orig_prompt and xs[0] not in p.orig_negative:
- shared.log.warning(f'XYZ grid: prompt S/R string="{xs[0]}" not found')
- else:
- p.prompt = p.orig_prompt.replace(xs[0], x)
- p.negative_prompt = p.orig_negative.replace(xs[0], x)
- p.all_prompts = None
- p.all_negative_prompts = None
- """
- if p.all_prompts is not None:
- for i in range(len(p.all_prompts)):
- for j in range(len(xs)):
- p.all_prompts[i] = p.all_prompts[i].replace(xs[j], x)
- p.negative_prompt = p.negative_prompt.replace(xs[0], x)
- if p.all_negative_prompts is not None:
- for i in range(len(p.all_negative_prompts)):
- for j in range(len(xs)):
- p.all_negative_prompts[i] = p.all_negative_prompts[i].replace(xs[j], x)
- """
- shared.log.debug(f'XYZ grid apply prompt: "{xs[0]}"="{x}"')
+ for s in xs:
+ if s in p.prompt:
+ shared.log.debug(f'XYZ grid apply prompt: "{s}"="{x}"')
+ p.prompt = p.prompt.replace(s, x)
+ if s in p.negative_prompt:
+ shared.log.debug(f'XYZ grid apply negative: "{s}"="{x}"')
+ p.negative_prompt = p.negative_prompt.replace(s, x)
+ p.all_prompts = None
+ p.all_negative_prompts = None
def apply_order(p, x, xs):
@@ -220,6 +207,15 @@ def apply_lora(p, x, xs):
shared.log.debug(f'XYZ grid apply LoRA: "{x}"')
+def apply_lora_strength(p, x, xs):
+ shared.log.debug(f'XYZ grid apply LoRA strength: "{x}"')
+ p.prompt = p.prompt.replace(':1.0>', '>')
+ p.prompt = p.prompt.replace(f':{shared.opts.extra_networks_default_multiplier}>', '>')
+ p.all_prompts = None
+ p.all_negative_prompts = None
+ shared.opts.data['extra_networks_default_multiplier'] = x
+
+
def apply_te(p, x, xs):
shared.opts.data["sd_text_encoder"] = x
sd_models.reload_text_encoder()
diff --git a/wiki b/wiki
index 30f3265bb..f57cdb49d 160000
--- a/wiki
+++ b/wiki
@@ -1 +1 @@
-Subproject commit 30f3265bb06ac738e4467f58be4df3fc4b49c08b
+Subproject commit f57cdb49d8ca928024b43525897d1c1379eab4c4