mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 17:24:32 +02:00
Merge branch 'dev' into lora-refactor
This commit is contained in:
+7
-3
@@ -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",
|
||||
|
||||
+61
-9
@@ -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: <https://civitai.com/models/646328?modelVersionId=1040235>
|
||||
- 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
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma
|
||||
- SD35 IPAdapter: <https://github.com/huggingface/diffusers/issues/9966>
|
||||
- SD35 LoRA: <https://github.com/huggingface/diffusers/issues/9950>
|
||||
- Flux IPAdapter: <https://github.com/huggingface/diffusers/issues/9825>
|
||||
- Flux Fill/ControlNet/Redux: <https://github.com/huggingface/diffusers/pull/9985>
|
||||
- Flux NF4: <https://github.com/huggingface/diffusers/issues/9996>
|
||||
- SANA: <https://github.com/huggingface/diffusers/pull/9982>
|
||||
- LTX-Video: <https://huggingface.co/Lightricks/LTX-Video> <https://huggingface.co/spaces/Lightricks/LTX-Video-Playground/tree/main>
|
||||
|
||||
## Other
|
||||
|
||||
|
||||
Executable
+30
@@ -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();
|
||||
+17
-7
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
Submodule extensions-builtin/sdnext-modernui updated: 4647bd7f86...3008cee4b6
+3
-3
@@ -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 ''
|
||||
|
||||
@@ -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 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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; }
|
||||
|
||||
+17
-14
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = `<td>${dateToStr(l.created)}</td>${level}<td>${l.facility}</td>${module}<td>${l.msg}</td>`;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+27
-23
@@ -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');
|
||||
}
|
||||
|
||||
+44
-43
@@ -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);
|
||||
}
|
||||
|
||||
+1
-15
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-14
@@ -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;}
|
||||
|
||||
+4
-4
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
+2
-1
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
+11
-1
@@ -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():
|
||||
|
||||
@@ -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 = " | <p class='vram'>"
|
||||
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 += "</p>"
|
||||
if isinstance(res, list):
|
||||
res[-1] += f"<div class='performance'><p class='time'>Time: {elapsed_text}</p>{vram_html}</div>"
|
||||
res[-1] += f"<div class='performance'><p>Time: {elapsed_text} | {summary}{vram_html}</p></div>"
|
||||
return tuple(res)
|
||||
return f
|
||||
|
||||
@@ -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'<p>Ready {image_txt}</p>'
|
||||
html_txt = f'<p>Ready {image_txt}</p>' if image_txt != '' else ''
|
||||
if len(info_txt) > 0:
|
||||
html_txt = html_txt + infotext_to_html(info_txt[0])
|
||||
if is_generator:
|
||||
|
||||
@@ -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',
|
||||
|
||||
+1
-1
@@ -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):
|
||||
|
||||
+7
-7
@@ -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
|
||||
|
||||
+4
-4
@@ -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
|
||||
|
||||
+10
-2
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+17
-10
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+9
-7
@@ -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("<h2>Img2Img & Inpainting</h2>", "", gr.HTML),
|
||||
"postprocessing_sep_img2img": OptionInfo("<h2>Inpaint</h2>", "", 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("<h2>Detailer</h2>", "", gr.HTML),
|
||||
"postprocessing_sep_detailer": OptionInfo("<h2>Detailer</h2>", "", 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("<h2>Face restore</h2>", "", 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("<h2>Upscaling</h2>", "", gr.HTML),
|
||||
"upscaler_unload": OptionInfo(False, "Unload upscaler after processing"),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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 = []
|
||||
@@ -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:
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+11
-3
@@ -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])
|
||||
|
||||
+44
-17
@@ -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"<div class='performance'><p>{elapsed_text} {summary} {vram_html}</p></div>"
|
||||
|
||||
|
||||
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('<span id="control-input-button">Control input</p>')
|
||||
with gr.Column(scale=9, elem_id='control-input-column', visible=True) as column_input:
|
||||
gr.HTML('<span id="control-input-button">Input</p>')
|
||||
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('<span id="control-output-button">Output</p>')
|
||||
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('<span id="control-preview-button">Preview</p>')
|
||||
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,
|
||||
|
||||
@@ -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('\\', '/'))
|
||||
|
||||
+33
-38
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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('<a href="https://blackforestlabs.ai/flux-1-tools/">  Flux.1 Redux</a><br>')
|
||||
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()
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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('<a href="https://github.com/google/style-aligned">  Style Aligned Image Generation</a><br><br>')
|
||||
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
|
||||
@@ -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")),
|
||||
|
||||
+18
-22
@@ -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()
|
||||
|
||||
+1
-1
Submodule wiki updated: 30f3265bb0...f57cdb49d8
Reference in New Issue
Block a user