Merge pull request #2194 from vladmandic/master

update dev
This commit is contained in:
Vladimir Mandic
2023-09-14 16:25:12 -04:00
committed by GitHub
93 changed files with 1648 additions and 1094 deletions
+1
View File
@@ -3,6 +3,7 @@ __pycache__
.ruff_cache
/cache.json
/*.json
/*.yaml
/params.txt
/styles.csv
/user.css
+46
View File
@@ -1,5 +1,51 @@
# Change Log for SD.Next
## Update for 2023-09-13
Started as a mostly a service release with quite a few fixes, but then...
Major changes how **hires** works as well as support for a very interesting new model [Wuerstchen](https://huggingface.co/blog/wuertschen)
- tons of fixes
- changes to **hires**
- enable non-latent upscale modes (standard upscalers)
- when using latent upscale, hires pass is run automatically
- when using non-latent upscalers, hires pass is skipped by default
enabled using **force hires** option in ui
hires was not designed to work with standard upscalers, but i understand this is a common workflow
- when using refiner, upscale/hires runs before refiner pass
- second pass can now also utilize full/quick vae quality
- note that when combining non-latent upscale, hires and refiner output quality is maximum,
but operations are really resource intensive as it includes: *base->decode->upscale->encode->hires->refine*
- all combinations of: decode full/quick + upscale none/latent/non-latent + hires on/off + refiner on/off
should be supported, but given the number of combinations, issues are possible
- all operations are captured in image medata
- diffusers:
- allow loading of sd/sdxl models from safetensors without online connectivity
- support for new model: [wuerstchen](https://huggingface.co/warp-ai/wuerstchen)
its a high-resolution model (1024px+) that nearly doubls performance of sd-xl with much lower resource requirements
go to *models -> huggingface -> search "warp-ai/wuerstchen" -> download*
its nearly 12gb in size, so be patient :)
- minor re-layout of the main ui
- update **ui hints**
- updated **models -> civitai**
- search and download loras
- find previews for already downloaded models or loras
- new option **inference mode**
- default is standard `torch.no_grad`
new option is `torch.inference_only` which is slightly faster and uses less vram, but only works on some gpus
- new cmdline param `--no-metadata`
skips reading metadata from models that are not already cached
- updated **gradio**
- **styles** support for subfolders
- **css** optimizations
- clean-up **logging**
- capture system info in startup log
- better diagnostic output
- capture extension output
- capture ldm output
- cleaner server restart
- custom exception handling
## Update for 2023-09-06
One week later, another large update!
+10 -7
View File
@@ -46,20 +46,23 @@ All Individual features are not listed here, instead check [ChangeLog](CHANGELOG
- **Original**: Based on [LDM](https://github.com/Stability-AI/stablediffusion) reference implementation and significantly expanded on by [A1111](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
This is the default backend and it is fully compatible with all existing functionality and extensions
It supports **SD 1.x** and **SD 2.x** models
- **Diffusers**: Based on new [Huggingface Diffusers](https://huggingface.co/docs/diffusers/index) implementation
It is also the only backend that supports **Stable Diffusion XL** model
It supports All models listed below
It is also the *only backend* that supports **Stable Diffusion XL** model
See [wiki article](https://github.com/vladmandic/automatic/wiki/Diffusers) for more information
## Model support
Additional models will be added as they become available and there is public interest in them
- Stable Diffusion 1.x and 2.x *(all variants)*
- Stable Diffusion XL
- Kandinsky 2.1 and 2.2
- DeepFloyd IF
- UniDiffusion
- SD-Distilled *(all variants)*
- [Stable Diffusion](https://github.com/Stability-AI/stablediffusion/) 1.x and 2.x *(all variants)*
- [Stable Diffusion XL](https://github.com/Stability-AI/generative-models)
- [Kandinsky](https://github.com/ai-forever/Kandinsky-2) 2.1 and 2.2
- [DeepFloyd IF](https://github.com/deep-floyd/IF)
- [UniDiffusion](https://github.com/thu-ml/unidiffuser)
- [SD-Distilled](https://huggingface.co/blog/sd_distillation) *(all variants)*
- [Wuerstchen](https://huggingface.co/blog/wuertschen)
## Platform support
+4 -6
View File
@@ -22,7 +22,6 @@ Stuff to be added, in no particular order...
- Port **A1111** stuff
- Port `p.all_hr_prompts`
- Import core repos to reduce dependencies
- Parse StabilityAI `modelspec` metadata
- Non-technical:
- Update Wiki
- Get more high-quality upscalers
@@ -30,14 +29,15 @@ Stuff to be added, in no particular order...
- [Localization](https://app.transifex.com/signup/open-source/)
- New Minor
- Prompt padding for positive/negative
- Add EN provider for VAEs
- XYZ grid upscalers
- Built-in `motd`-style notifications
- Docker PR
- New Major
- Style editor (use json format instead of csv)
- Profile manager (for config.json and ui-config.json)
- Style editor
- Profile manager (for `config.json` and `ui-config.json`)
- Multi-user support
- Add [SAG](https://huggingface.co/docs/diffusers/v0.19.3/en/api/pipelines/self_attention_guidance),(https://github.com/ashen-sensored/sd_webui_SAG)
- Add [SAG](https://huggingface.co/docs/diffusers/v0.19.3/en/api/pipelines/self_attention_guidance), [SAG](https://github.com/ashen-sensored/sd_webui_SAG)
- Image phash and hdash using `imagehash`
- Model merge using `git-rebasin`
- Enable refiner-style workflow for `ldm` backend
@@ -49,11 +49,9 @@ Stuff to be added, in no particular order...
- Templates for SD-XL training
- Lora train UI
- Redesign
- Extensions reporting framework
- New UI
- New inpainting canvas controls (move from backend to purely frontend)
- New image browser (move from backend to purely frontend)
- New extra networks (move from backend to purely frontend)
- Change workflows from static/legacy to steps-based
## Investigate
+4
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env python
import os
import sys
import json
from rich import print # pylint: disable=redefined-builtin
@@ -7,6 +8,9 @@ from rich import print # pylint: disable=redefined-builtin
if __name__ == "__main__":
sys.argv.pop(0)
fn = sys.argv[0] if len(sys.argv) > 0 else 'locale_en.json'
if not os.path.isfile(fn):
print(f'File not found: {fn}')
sys.exit(1)
with open(fn, 'r', encoding="utf-8") as f:
data = json.load(f)
keys = []
+1 -10
View File
@@ -77,19 +77,16 @@ class LoraOnDisk:
self.filename = filename
self.metadata = {}
self.is_safetensors = os.path.splitext(filename)[1].lower() == ".safetensors"
if self.is_safetensors:
try:
self.metadata = sd_models.read_metadata_from_safetensors(filename)
except Exception as e:
errors.display(e, f"reading lora metadata: {filename}")
if self.metadata:
m = {}
for k, v in sorted(self.metadata.items(), key=lambda x: metadata_tags_order.get(x[0], 999)):
m[k] = v
self.metadata = m
self.ssmd_cover_images = self.metadata.pop('ssmd_cover_images', None) # those are cover images and they are too big to display in UI as text
self.alias = self.metadata.get('ss_output_name', self.name)
self.hash = None
@@ -98,7 +95,7 @@ class LoraOnDisk:
def set_hash(self, v):
self.hash = v
self.shorthash = self.hash[0:12]
self.shorthash = self.hash[0:10]
if self.shorthash:
available_lora_hash_lookup[self.shorthash] = self
@@ -442,19 +439,13 @@ def list_available_loras():
forbidden_lora_aliases.clear()
available_lora_hash_lookup.clear()
forbidden_lora_aliases.update({"none": 1, "Addams": 1})
os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True)
for filename in sorted([*filter(extension_filter(['.PT', '.CKPT', '.SAFETENSORS']), directory_files(shared.cmd_opts.lora_dir))], key=str.lower):
name = os.path.splitext(os.path.basename(filename))[0]
entry = LoraOnDisk(name, filename)
available_loras[name] = entry
if entry.alias in available_lora_aliases:
forbidden_lora_aliases[entry.alias.lower()] = 1
available_lora_aliases[name] = entry
available_lora_aliases[entry.alias] = entry
@@ -13,32 +13,28 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
lora.list_available_loras()
def list_items(self):
for name, lora_on_disk in lora.available_loras.items():
path, _ext = os.path.splitext(lora_on_disk.filename)
alias = lora_on_disk.get_alias()
for name, l in lora.available_loras.items():
path, _ext = os.path.splitext(l.filename)
alias = l.get_alias()
prompt = f" <lora:{alias}:{shared.opts.extra_networks_default_multiplier}>"
prompt = json.dumps(prompt)
metadata = json.dumps(lora_on_disk.metadata, indent=4) if lora_on_disk.metadata else None
possible_tags = lora_on_disk.metadata.get('ss_tag_frequency', {}) if lora_on_disk.metadata is not None else {}
metadata = json.dumps(l.metadata, indent=4) if l.metadata else None
possible_tags = l.metadata.get('ss_tag_frequency', {}) if l.metadata is not None else {}
if isinstance(possible_tags, str):
possible_tags = {}
shared.log.debug(f'Lora has invalid metadata: {path}')
tags = {}
for tag in possible_tags.keys():
if '_' not in tag:
tag = f'0_{tag}'
words = tag.split('_', 1)
for k, v in possible_tags.items():
words = k.split('_', 1) if '_' in k else [v, k]
tags[' '.join(words[1:])] = words[0]
# shared.log.debug(f'Lora: {path}: name={name} alias={alias} tags={tags}')
yield {
"name": name,
"filename": path,
"fullname": lora_on_disk.filename,
"hash": lora_on_disk.shorthash,
"fullname": l.filename,
"hash": l.shorthash,
"preview": self.find_preview(path),
"description": self.find_description(path),
"info": self.find_info(path),
"search_term": self.search_terms_from_path(lora_on_disk.filename) + ' '.join(tags.keys()),
"search_term": self.search_terms_from_path(l.filename) + ' '.join(tags.keys()),
"prompt": prompt,
"local_preview": f"{path}.{shared.opts.samples_format}",
"metadata": metadata,
+107 -82
View File
@@ -9,7 +9,17 @@
{"id":"","label":"⏫","localized":"","hint":"Fill"},
{"id":"","label":"🎲️","localized":"","hint":"Use random seed"},
{"id":"","label":"♻️","localized":"","hint":"Reuse previous seed"},
{"id":"","label":"⇅","localized":"","hint":"Swap values"}
{"id":"","label":"⇅","localized":"","hint":"Swap values"},
{"id":"","label":"⇦","localized":"","hint":"Read generation parameters from prompt or last generation if prompt is empty into user interface"},
{"id":"","label":"⊗","localized":"","hint":"Clear prompt"},
{"id":"","label":"🗁","localized":"","hint":"Show/hide extra networks"},
{"id":"","label":"⇰","localized":"","hint":"Apply selected styles to current prompt"},
{"id":"","label":"⇩","localized":"","hint":"Save current prompt as style template"},
{"id":"","label":"⟲","localized":"","hint":"Refresh"},
{"id":"","label":"🗙","localized":"","hint":"Close"},
{"id":"","label":"⊜","localized":"","hint":"Fill"},
{"id":"","label":"📐","localized":"","hint":"Measure"},
{"id":"","label":"🔍","localized":"","hint":"Search"}
],
"prompts": [
{"id":"","label":"Prompt","localized":"","hint":"Type what you want to see in the image"},
@@ -31,11 +41,13 @@
{"id":"","label":"Train","localized":"","hint":"Run training or model merging"},
{"id":"","label":"Models","localized":"","hint":"Convert or merge your models"},
{"id":"","label":"Interrogator","localized":"","hint":"Run interrogate to get description of your image"},
{"id":"","label":"System Info","localized":"","hint":"System information and benchmarking"},
{"id":"","label":"System Info","localized":"","hint":"System information"},
{"id":"","label":"Agent Scheduler","localized":"","hint":"Enqueue your generate requests and run them in the background"},
{"id":"","label":"Image Browser","localized":"","hint":"Browse through your generated image database"},
{"id":"","label":"System","localized":"","hint":"System settings and information"},
{"id":"","label":"Settings","localized":"","hint":"Application settings"},
{"id":"","label":"Extensions","localized":"","hint":"Application extensions"}
{"id":"","label":"Extensions","localized":"","hint":"Application extensions"},
{"id":"","label":"Script","localized":"","hint":"Addtional scripts to be used"}
],
"action panel": [
{"id":"","label":"Generate","localized":"","hint":"Start processing"},
@@ -48,8 +60,10 @@
{"id":"","label":"Interrogate\nDeepBooru","localized":"","hint":"Run interrogate using DeepBooru model"}
],
"extra networks": [
{"id":"","label":"Extra networks tab order","localized":"","hint":"Comma-separated list of tab names; tabs listed here will appear in the extra networks UI first and in order listed"},
{"id":"","label":"UI position","localized":"","hint":""},
{"id":"","label":"UI position","localized":"","hint":"Location of extra networks"},
{"id":"","label":"cover","localized":"","hint":"cover full area"},
{"id":"","label":"inline","localized":"","hint":"inline with all additional elelemtns (scrollable)"},
{"id":"","label":"sidebar","localized":"","hint":"sidebar on the right side of the screen"},
{"id":"","label":"UI height (%)","localized":"","hint":""},
{"id":"","label":"UI sidebar width (%)","localized":"","hint":""},
{"id":"","label":"UI card preview lazy loading","localized":"","hint":""},
@@ -58,20 +72,19 @@
{"id":"","label":"UI image contain method","localized":"","hint":""},
{"id":"","label":"Do not automatically build extra network pages","localized":"","hint":""},
{"id":"","label":"Use LyCoris handler for all Lora types","localized":"","hint":""},
{"id":"","label":"Disable built-in Lora handler","localized":"","hint":""},
{"id":"","label":"Use Kohya method for handling multiple Loras","localized":"","hint":""},
{"id":"","label":"Use Kohya method for handling multiple LoRA","localized":"","hint":""},
{"id":"","label":"Multiplier for extra networks","localized":"","hint":"When adding extra network such as Hypernetwork or Lora to prompt, use this multiplier for it"},
{"id":"","label":"Add hypernetwork to prompt","localized":"","hint":""},
{"id":"","label":"Add Lora to prompt","localized":"","hint":""},
{"id":"","label":"shuffle tags by ',' when creating prompts.","localized":"","hint":""},
{"id":"","label":"extra text to add before <...> when adding extra network to prompt","localized":"","hint":""},
{"id":"","label":"When adding to prompt, refer to Lora by","localized":"","hint":""},
{"id":"","label":"add lora hashes to infotext","localized":"","hint":""},
{"id":"","label":"Checkpoints","localized":"","hint":""},
{"id":"","label":"Lora","localized":"","hint":""},
{"id":"","label":"LyCORIS","localized":"","hint":""},
{"id":"","label":"Textual Inversion","localized":"","hint":""},
{"id":"","label":"Hypernetworks","localized":"","hint":""},
{"id":"","label":"Checkpoints","localized":"","hint":"Trained model checkpoints"},
{"id":"","label":"Styles","localized":"","hint":"Additional styles to be applied on selected generation paramters"},
{"id":"","label":"Lora","localized":"","hint":"LoRA: Low-Rank Adaptation. Fine-tuned model that is applied on top of a loaded model"},
{"id":"","label":"LyCORIS","localized":"","hint":"LyCORIS: Lora beYond Conventional methods. Fine-tuned model that is applied on top of a loaded model"},
{"id":"","label":"Textual Inversion","localized":"","hint":"Textual inversion embedding is a trained embedded information about the subject"},
{"id":"","label":"Hypernetworks","localized":"","hint":"Small trained neural network that modifies behavior of the loaded model"},
{"id":"","label":"Save preview","localized":"","hint":"Save current image as extra network preview"},
{"id":"","label":"Save description","localized":"","hint":"Save current text as extra network description"},
{"id":"","label":"Read description","localized":"","hint":"Read stored extra network description"}
@@ -102,26 +115,37 @@
{"id":"","label":"Apply changes & restart server","localized":"","hint":"Apply all changes and restart server"},
{"id":"","label":"install","localized":"","hint":"install this extension"},
{"id":"","label":"uninstall","localized":"","hint":"uninstall this extension"},
{"id":"","label":"User interface defaults","localized":"","hint":"Review and set current values as default values for the user interface"},
{"id":"","label":"UI Config","localized":"","hint":"Review and set current values as default values for the user interface"},
{"id":"","label":"View changes","localized":"","hint":"Review changes between default user interface values and current values"},
{"id":"","label":"Set new defaults","localized":"","hint":"Set current values as default values for the user interface"},
{"id":"","label":"Benchmark","localized":"","hint":"Run benchmarks"},
{"id":"","label":"Models & Networks","localized":"","hint":"View lists of all available models and networks"},
{"id":"","label":"Restore system defaults","localized":"","hint":"Restore default user interface values"}
],
"txt2img tab": [
{"id":"","label":"Batch","localized":"","hint":"Additional batching options"},
{"id":"","label":"Seed details","localized":"","hint":"Additional options regarding initial seed used to produce images"},
{"id":"","label":"Advanced","localized":"","hint":"Additional advanced options"},
{"id":"","label":"Sampling method","localized":"","hint":"Which algorithm to use to produce the image"},
{"id":"","label":"Sampling steps","localized":"","hint":"How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results"},
{"id":"","label":"Restore faces","localized":"","hint":"Use a pre-trained model to correct the generated faces. See GFPGAN or Codeformer."},
{"id":"","label":"Tiling","localized":"","hint":"Produce an image that can be tiled"},
{"id":"","label":"Hires fix","localized":"","hint":"Use a similar process as image to image to upscale and add detail to the final image."},
{"id":"","label":"full quality","localized":"","hint":"Use full quality VAE to decode latent samples"},
{"id":"","label":"face restore","localized":"","hint":"Run processed image through additional face restoration model"},
{"id":"","label":"denoise","localized":"","hint":"Denoising details for img2img"},
{"id":"","label":"remove background","localized":"","hint":"Run processed image through additional background removal model"},
{"id":"","label":"Second pass","localized":"","hint":"Use a similar process as image to image to upscale and/or add detail to the final image. Optionally uses refiner model to enhance image details."},
{"id":"","label":"Denoising strength","localized":"","hint":"Determines how little respect the algorithm should have for image's content. At 0, nothing will change, and at 1 you'll get an unrelated image. With values below 1.0, processing will take less steps than the Sampling Steps slider specifies"},
{"id":"","label":"Denoise start","localized":"","hint":"Override denoise strength by stating how early base model should finish and when refiner should start. Only applicable to refiner usage. If set to 0 or 1, denoising strength will be used"},
{"id":"","label":"Hires steps","localized":"","hint":"Number of sampling steps for upscaled picture. If 0, uses same as for original"},
{"id":"","label":"Upscaler","localized":"","hint":"Which pre-trained model to use for the upscaling process."},
{"id":"","label":"Upscale by","localized":"","hint":"Adjusts the size of the image by multiplying the original width and height by the selected value. Ignored if either Resize width to or Resize height to are non-zero"},
{"id":"","label":"Force Hires","localized":"","hint":"Hires runs automatically when Latent upscale is selected, but its skipped when using non-latent upscalers. Enable force hires to run hires with non-latent upscalers"},
{"id":"","label":"Resize width to","localized":"","hint":"Resizes image to this width. If 0, width is inferred from either of two nearby sliders"},
{"id":"","label":"Resize height to","localized":"","hint":"Resizes image to this height. If 0, height is inferred from either of two nearby sliders"},
{"id":"","label":"Secondary sampler","localized":"","hint":"Use specific sampler as fallback sampler if primary is not supported for specific operation"},
{"id":"","label":"Secondary steps","localized":"","hint":"Number of steps to use for second pass"},
{"id":"","label":"Refiner start","localized":"","hint":"Refiner pass will start when base model is this much complete (set to 0 or 1 to run after full base model run)"},
{"id":"","label":"Refiner steps","localized":"","hint":"Number of steps to use for refiner pass"},
{"id":"","label":"Secondary CFG Scale","localized":"","hint":"CFG scale used for refiner pass"},
{"id":"","label":"Guidance rescale","localized":"","hint":"Rescale CFG generated noise to avoid overexposed images"},
{"id":"","label":"Secondary Prompt","localized":"","hint":"Prompt used for both second encoder in base model (if it exists) and for refiner pass (if enabled)"},
@@ -130,7 +154,7 @@
{"id":"","label":"Height","localized":"","hint":"Image height"},
{"id":"","label":"Batch count","localized":"","hint":"How many batches of images to create (has no impact on generation performance or VRAM usage)"},
{"id":"","label":"Batch size","localized":"","hint":"How many image to create in a single batch (increases generation performance at cost of higher VRAM usage)"},
{"id":"","label":"CFG Scale","localized":"","hint":"Classifier Free Guidance scale: how strongly the image should conform to prompt. Lower values produce more creative results, higher values make it follow the prompt more strictly; recommended values between 5-10"},
{"id":"","label":"cfg scale","localized":"","hint":"Classifier Free Guidance scale: how strongly the image should conform to prompt. Lower values produce more creative results, higher values make it follow the prompt more strictly; recommended values between 5-10"},
{"id":"","label":"CLIP skip","localized":"","hint":"Clip skip is a feature that allows users to control the level of specificity of the prompt, the higher the CLIP skip value, the less deep the prompt will be interpreted. CLIP Skip 1 is typical while some anime models produce better results at CLIP skip 2"},
{"id":"","label":"Seed","localized":"","hint":"A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result"},
{"id":"","label":"Extra","localized":"","hint":"Show additional options"},
@@ -149,7 +173,7 @@
{"id":"","label":"Input directory","localized":"","hint":"Folder where the images are that you want to process"},
{"id":"","label":"Output directory","localized":"","hint":"Folder where the processed images should be saved to"},
{"id":"","label":"Show result images","localized":"","hint":"Enable to show the processed images in the image pane"},
{"id":"","label":"Resize","localized":"","hint":"Factor for resizing 1x mean no upscale, 4x means 4 times upscale, high values might lead to memory issues on small graphics cards"},
{"id":"","label":"Resize","localized":"","hint":"Resizing details. Higher resolutions require additional processing memory."},
{"id":"","label":"Crop to fit","localized":"","hint":"If the dimensions of your source image (e.g. 512x510) deviate from your target dimensions (e.g. 1024x768) this function will fit your upscaled image into your target size image. Excess will be cropped"},
{"id":"","label":"Secondary Upscaler","localized":"","hint":"Select secondary upscaler to run after initial upscaler"},
{"id":"","label":"Upscaler 2 visibility","localized":"","hint":"Strength of the secondary upscaler"},
@@ -167,24 +191,22 @@
{"id":"sett_reload_sd_model","label":"Reload checkpoint","localized":"","hint":"Reload currently selected model checkpoint"}
],
"settings sections": [
{"id":"","label":"Stable Diffusion","localized":"","hint":""},
{"id":"","label":"execution & models","localized":"","hint":""},
{"id":"","label":"Optimizations","localized":"","hint":""},
{"id":"","label":"Compute Settings","localized":"","hint":""},
{"id":"","label":"Diffusers Settings","localized":"","hint":""},
{"id":"","label":"System Paths","localized":"","hint":""},
{"id":"","label":"Image Options","localized":"","hint":""},
{"id":"","label":"Image Processing","localized":"","hint":""},
{"id":"","label":"Image Paths","localized":"","hint":""},
{"id":"","label":"image naming & paths","localized":"","hint":""},
{"id":"","label":"User Interface","localized":"","hint":""},
{"id":"","label":"Live Previews","localized":"","hint":""},
{"id":"","label":"Sampler Settings","localized":"","hint":""},
{"id":"","label":"Postprocessing","localized":"","hint":""},
{"id":"","label":"Training","localized":"","hint":""},
{"id":"","label":"Interrogate","localized":"","hint":""},
{"id":"","label":"Upscaling","localized":"","hint":""},
{"id":"","label":"Extra Networks","localized":"","hint":""},
{"id":"","label":"Licenses","localized":"","hint":""},
{"id":"","label":"Show all pages","localized":"","hint":""},
{"id":"","label":"Licenses","localized":"","hint":"View licenses of all additional included libraries"},
{"id":"","label":"Show all pages","localized":"","hint":"Show all settings pages"},
{"id":"","label":"Request browser notifications","localized":"","hint":""}
],
"img2img tabs": [
@@ -192,8 +214,7 @@
{"id":"","label":"Sketch","localized":"","hint":""},
{"id":"","label":"Inpaint","localized":"","hint":""},
{"id":"","label":"Inpaint sketch","localized":"","hint":""},
{"id":"","label":"Inpaint upload","localized":"","hint":""},
{"id":"","label":"Batch","localized":"","hint":""}
{"id":"","label":"Inpaint upload","localized":"","hint":""}
],
"img2img tab": [
{"id":"","label":"Inpaint batch input directory","localized":"","hint":""},
@@ -207,7 +228,7 @@
{"id":"","label":"Mask transparency","localized":"","hint":""},
{"id":"","label":"Inpaint masked","localized":"","hint":""},
{"id":"","label":"Inpaint not masked","localized":"","hint":""},
{"id":"","label":"fill","localized":"","hint":"fill it with colors of the image"},
{"id":"","label":"fill","localized":"","hint":"fill"},
{"id":"","label":"original","localized":"","hint":"keep whatever was there originally"},
{"id":"","label":"latent noise","localized":"","hint":"fill it with latent space noise"},
{"id":"","label":"latent nothing","localized":"","hint":"fill it with latent space zeroes"},
@@ -318,20 +339,20 @@
{"id":"","label":"Original model","localized":"","hint":""}
],
"settings": [
{"id":"","label":"Stable Diffusion checkpoint","localized":"","hint":""},
{"id":"","label":"Stable Diffusion refiner","localized":"","hint":""},
{"id":"","label":"Stable Diffusion checkpoint autoload on server start","localized":"","hint":""},
{"id":"","label":"stable diffusion checkpoint dict","localized":"","hint":""},
{"id":"","label":"disallow usage of checkpoints in ckpt format","localized":"","hint":""},
{"id":"","label":"base model","localized":"","hint":"Main model used for all operations"},
{"id":"","label":"refiner model","localized":"","hint":"Refiner model used for second-pass operations"},
{"id":"","label":"model autoload on server start","localized":"","hint":""},
{"id":"","label":"use baseline data from a different model","localized":"","hint":""},
{"id":"","label":"Disallow usage of models in ckpt format","localized":"","hint":""},
{"id":"","label":"model compile fullgraph","localized":"","hint":""},
{"id":"","label":"create zip archive when downloading multiple images","localized":"","hint":""},
{"id":"","label":"samplers solver order where applicable","localized":"","hint":""},
{"id":"","label":"samplers should use karras sigmas where applicable","localized":"","hint":""},
{"id":"","label":"samplers should use use lower-order solvers in the final steps where applicable","localized":"","hint":""},
{"id":"","label":"samplers should use dynamic thresholding where applicable","localized":"","hint":""},
{"id":"","label":"Number of cached model checkpoints","localized":"","hint":"The amount of models to store in RAM for quick access"},
{"id":"","label":"Number of cached VAE checkpoints","localized":"","hint":"The amount of VAE files to store in RAM for quick access"},
{"id":"","label":"Select VAE","localized":"","hint":"VAE helps with fine details in the final image and may also alter colors"},
{"id":"","label":"samplers use karras sigmas where applicable","localized":"","hint":""},
{"id":"","label":"samplers use simplified solvers in final steps where applicable","localized":"","hint":""},
{"id":"","label":"samplers use dynamic thresholding where applicable","localized":"","hint":""},
{"id":"","label":"Number of cached models","localized":"","hint":"The amount of models to store in RAM for quick access"},
{"id":"","label":"Number of cached VAEs","localized":"","hint":"The amount of VAE files to store in RAM for quick access"},
{"id":"","label":"VAE model","localized":"","hint":"VAE helps with fine details in the final image and may also alter colors"},
{"id":"","label":"Enable splitting of hires batch processing","localized":"","hint":"Reduces VRAM usage when using hires fix on batches of images"},
{"id":"","label":"Load models using stream loading method","localized":"","hint":"When loading models attempt stream loading optimized for slow or network storage"},
{"id":"","label":"When loading models attempt to reuse previous model dictionary","localized":"","hint":""},
@@ -355,14 +376,13 @@
{"id":"","label":"Disable conditional batching enabled on low memory systems","localized":"","hint":""},
{"id":"","label":"Enable samplers quantization for sharper and cleaner results","localized":"","hint":""},
{"id":"","label":"Prompt padding for long prompts","localized":"","hint":"Increase coherency by padding from the last comma within n tokens when using more than 75 tokens"},
{"id":"","label":"Original","localized":"","hint":""},
{"id":"","label":"Diffusers","localized":"","hint":""},
{"id":"","label":"VRAM usage polls per second during generation","localized":"","hint":""},
{"id":"","label":"Autocast","localized":"","hint":""},
{"id":"","label":"Full","localized":"","hint":""},
{"id":"","label":"FP32","localized":"","hint":""},
{"id":"","label":"FP16","localized":"","hint":""},
{"id":"","label":"BF16","localized":"","hint":""},
{"id":"","label":"Original","localized":"","hint":"Original LDM backend"},
{"id":"","label":"Diffusers","localized":"","hint":"Diffusers backend"},
{"id":"","label":"Autocast","localized":"","hint":"Automatically determine precision during runtime"},
{"id":"","label":"Full","localized":"","hint":"Always use full precision"},
{"id":"","label":"FP32","localized":"","hint":"Use 32-bit floating point precision for calculations"},
{"id":"","label":"FP16","localized":"","hint":"Use 16-bit floating point precision for calculations"},
{"id":"","label":"BF16","localized":"","hint":"Use modified 16-bit floating point precision for calculations"},
{"id":"","label":"Use full precision for model (--no-half)","localized":"","hint":"Uses FP32 for the model. May produce better results while using more VRAM and slower generation"},
{"id":"","label":"Use full precision for VAE (--no-half-vae)","localized":"","hint":"Uses FP32 for the VAE. May produce better results while using more VRAM and slower generation"},
{"id":"","label":"Enable upcast sampling","localized":"","hint":"Usually produces similar results to --no-half with better performance while using less memory"},
@@ -371,9 +391,7 @@
{"id":"","label":"Attempt VAE roll back when produced NaN values (experimental)","localized":"","hint":"Requires Torch 2.1 and NaN check enabled"},
{"id":"","label":"Use channels last as torch memory format","localized":"","hint":""},
{"id":"","label":"Enable full-depth cuDNN benchmark feature","localized":"","hint":""},
{"id":"","label":"Allow TF32 math ops","localized":"","hint":""},
{"id":"","label":"Allow TF16 reduced precision math ops","localized":"","hint":""},
{"id":"","label":"Enable model compile (experimental)","localized":"","hint":""},
{"id":"","label":"Enable model compile","localized":"","hint":""},
{"id":"","label":"inductor","localized":"","hint":""},
{"id":"","label":"cudagraphs","localized":"","hint":""},
{"id":"","label":"aot_ts_nvfuser","localized":"","hint":""},
@@ -381,26 +399,25 @@
{"id":"","label":"ipex","localized":"","hint":""},
{"id":"","label":"Model compile verbose mode","localized":"","hint":""},
{"id":"","label":"Model compile suppress errors","localized":"","hint":""},
{"id":"","label":"Disable Torch memory garbage collection","localized":"","hint":"Disable Torch memory garbage collection on each generation. CG will still run before & after model load as well when low GPU memory threshold is reached."},
{"id":"","label":"Directory for temporary images; leave empty for default","localized":"","hint":""},
{"id":"","label":"Enable IPEX Optimize for Intel GPUs","localized":"","hint":""},
{"id":"","label":"Cleanup non-default temporary directory when starting webui","localized":"","hint":""},
{"id":"","label":"Path to directory with stable diffusion checkpoints","localized":"","hint":""},
{"id":"","label":"Path to directory with stable diffusion diffusers","localized":"","hint":""},
{"id":"","label":"Path to directory with VAE files","localized":"","hint":""},
{"id":"","label":"Embeddings directory for textual inversion","localized":"","hint":""},
{"id":"","label":"Hypernetwork directory","localized":"","hint":""},
{"id":"","label":"Path to directory with codeformer model file(s)","localized":"","hint":""},
{"id":"","label":"Path to directory with GFPGAN model file(s)","localized":"","hint":""},
{"id":"","label":"Path to directory with ESRGAN model file(s)","localized":"","hint":""},
{"id":"","label":"Path to directory with BSRGAN model file(s)","localized":"","hint":""},
{"id":"","label":"Path to directory with RealESRGAN model file(s)","localized":"","hint":""},
{"id":"","label":"Path to directory with ScuNET model file(s)","localized":"","hint":""},
{"id":"","label":"Path to directory with SwinIR model file(s)","localized":"","hint":""},
{"id":"","label":"Path to directory with LDSR model file(s)","localized":"","hint":""},
{"id":"","label":"Path to directory with CLIP model file(s)","localized":"","hint":""},
{"id":"","label":"Path to directory with Lora network(s)","localized":"","hint":""},
{"id":"","label":"Path to directory with LyCORIS network(s)","localized":"","hint":""},
{"id":"","label":"Folder with stable diffusion models","localized":"","hint":""},
{"id":"","label":"Folder with stable diffusion diffusers","localized":"","hint":""},
{"id":"","label":"Folder with VAE files","localized":"","hint":""},
{"id":"","label":"Folder with textual inversion embeddings","localized":"","hint":""},
{"id":"","label":"Folder with Hypernetwork models","localized":"","hint":""},
{"id":"","label":"Folder with codeformer Folder","localized":"","hint":""},
{"id":"","label":"Folder with GFPGAN models","localized":"","hint":""},
{"id":"","label":"Folder with ESRGAN models","localized":"","hint":""},
{"id":"","label":"Folder with BSRGAN models","localized":"","hint":""},
{"id":"","label":"Folder with RealESRGAN models","localized":"","hint":""},
{"id":"","label":"Folder with ScuNET models","localized":"","hint":""},
{"id":"","label":"Folder with SwinIR models","localized":"","hint":""},
{"id":"","label":"Folder with LDSR models","localized":"","hint":""},
{"id":"","label":"Folder with CLIP models","localized":"","hint":""},
{"id":"","label":"Folder with Lora networks","localized":"","hint":""},
{"id":"","label":"Folder with LyCORIS networks","localized":"","hint":""},
{"id":"","label":"Path to user-defined styles file","localized":"","hint":""},
{"id":"","label":"Always save all generated images","localized":"","hint":""},
{"id":"","label":"File format for generated images","localized":"","hint":"Select file format for images"},
@@ -410,8 +427,6 @@
{"id":"","label":"Always save all generated image grids","localized":"","hint":""},
{"id":"","label":"File format for grids","localized":"","hint":""},
{"id":"","label":"Add extended info (seed, prompt) to filename when saving grid","localized":"","hint":""},
{"id":"","label":"Do not save grids consisting of one picture","localized":"","hint":""},
{"id":"","label":"Prevent empty spots in grid (when set to autodetect)","localized":"","hint":""},
{"id":"","label":"Grid row count","localized":"","hint":"Use -1 for autodetect and 0 for it to be same as batch size"},
{"id":"","label":"Create text file next to every image with generation parameters","localized":"","hint":""},
{"id":"","label":"Create JSON log file for each saved image","localized":"","hint":"Save image information to a JSON file"},
@@ -420,7 +435,7 @@
{"id":"","label":"Save copy of image before applying color correction","localized":"","hint":""},
{"id":"","label":"Save copy of the inpainting greyscale mask","localized":"","hint":""},
{"id":"","label":"Save copy of inpainting masked composite","localized":"","hint":""},
{"id":"","label":"Save copy of processing init images","localized":"","hint":""},
{"id":"","label":"Save copy of img2img init images","localized":"","hint":""},
{"id":"","label":"Quality for saved jpeg images","localized":"","hint":""},
{"id":"","label":"Use lossless compression for webp images","localized":"","hint":""},
{"id":"","label":"Maximum allowed image size in megapixels","localized":"","hint":""},
@@ -462,10 +477,7 @@
{"id":"","label":"Ctrl+up/down precision when editing <extra networks:0.9>","localized":"","hint":""},
{"id":"","label":"Ctrl+up/down word delimiters","localized":"","hint":""},
{"id":"","label":"Quicksettings list","localized":"","hint":"List of setting names, separated by commas, for settings that should go to the quick access bar at the top instead the setting tab"},
{"id":"","label":"Hidden UI tabs","localized":"","hint":""},
{"id":"","label":"UI tabs order","localized":"","hint":""},
{"id":"","label":"UI scripts order","localized":"","hint":""},
{"id":"","label":"txt2img/img2img UI item order","localized":"","hint":""},
{"id":"","label":"Show progressbar","localized":"","hint":""},
{"id":"","label":"Show live previews of the created image","localized":"","hint":""},
{"id":"","label":"Show previews of all images generated in a batch as a grid","localized":"","hint":""},
@@ -477,7 +489,7 @@
{"id":"","label":"Approximate simple","localized":"","hint":"Very cheap approximation. Very fast compared to VAE, but produces pictures with 8 times smaller horizontal/vertical resolution and extremely low quality"},
{"id":"","label":"TAESD","localized":"","hint":""},
{"id":"","label":"Combined","localized":"","hint":""},
{"id":"","label":"Progressbar/preview update period, in milliseconds","localized":"","hint":""},
{"id":"","label":"Progress update period","localized":"","hint":"Update period for UI progress bar and preview checks, in miliseconds"},
{"id":"","label":"Euler a","localized":"","hint":"Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps higher than 30-40 does not help"},
{"id":"","label":"Euler","localized":"","hint":""},
{"id":"","label":"LMS","localized":"","hint":""},
@@ -499,7 +511,6 @@
{"id":"","label":"DPM++ 2M SDE Karras","localized":"","hint":""},
{"id":"","label":"DDIM","localized":"","hint":"Denoising Diffusion Implicit Models - best at inpainting"},
{"id":"","label":"UniPC","localized":"","hint":"Unified Predictor-Corrector Framework for Fast Sampling of Diffusion Models"},
{"id":"","label":"Force latent upscaler sampler","localized":"","hint":"Force specific sampler for second pass operations"},
{"id":"","label":"Noise multiplier for ancestral samplers (eta)","localized":"","hint":""},
{"id":"","label":"Noise multiplier for DDIM (eta)","localized":"","hint":""},
{"id":"","label":"uniform","localized":"","hint":""},
@@ -553,7 +564,6 @@
{"id":"","label":"Tile overlap in pixels for ESRGAN upscalers","localized":"","hint":"Low values = visible seam"},
{"id":"","label":"Tile size for SCUNET upscalers","localized":"","hint":"0 = no tiling"},
{"id":"","label":"Tile overlap for SCUNET upscalers","localized":"","hint":" Low values = visible seam"},
{"id":"","label":"Hires fix uses width & height to set final resolution","localized":"","hint":"Hires fix uses width & height to set final resolution rather than first pass"},
{"id":"","label":"Do not fix prompt schedule for second order samplers","localized":"","hint":""},
{"id":"","label":"CodeFormer","localized":"","hint":""},
{"id":"","label":"GFPGAN","localized":"","hint":"Restore low quality faces using GFPGAN neural network"},
@@ -562,30 +572,45 @@
{"id":"","label":"Token merging ratio","localized":"","hint":"Enable redundant token merging via tomesd for speed and memory improvements, 0=disabled"},
{"id":"","label":"Token merging ratio for img2img","localized":"","hint":"Enable redundant token merging for img2img via tomesd for speed and memory improvements, 0=disabled"},
{"id":"","label":"Token merging ratio for hires pass","localized":"","hint":"Enable redundant token merging for hires pass via tomesd for speed and memory improvements, 0=disabled"},
{"id":"","label":"Select diffuser pipeline when loading from safetensors","localized":"","hint":""},
{"id":"","label":"Diffusers pipeline","localized":"","hint":"If autodetect does not detect model automatically, select model type before loading a model"},
{"id":"","label":"Move base model to CPU when using refiner","localized":"","hint":""},
{"id":"","label":"Move base model to CPU when using VAE","localized":"","hint":""},
{"id":"","label":"Move refiner model to CPU when not in use","localized":"","hint":""},
{"id":"","label":"Move UNet to CPU while VAE decoding","localized":"","hint":""},
{"id":"","label":"Use model EMA weights when possible","localized":"","hint":""},
{"id":"","label":"Generator device","localized":"","hint":""},
{"id":"","label":"Enable sequential CPU offload","localized":"","hint":"Reduces GPU memory usage by transferring weights to the CPU. Increases inference time approximately 10%"},
{"id":"","label":"Enable model CPU offload","localized":"","hint":"Transferring of entire models to the CPU, negligible impact on inference time while still providing some memory savings"},
{"id":"","label":"Enable sequential CPU offload (--lowvram)","localized":"","hint":"Reduces GPU memory usage by transferring weights to the CPU. Increases inference time approximately 10%"},
{"id":"","label":"Enable model CPU offload (--medvram)","localized":"","hint":"Transferring of entire models to the CPU, negligible impact on inference time while still providing some memory savings"},
{"id":"","label":"Enable VAE slicing","localized":"","hint":"Decodes batch latents one image at a time with limited VRAM. Small performance boost in VAE decode on multi-image batches"},
{"id":"","label":"Enable VAE tiling","localized":"","hint":"Divide large images into overlapping tiles with limited VRAM. Results in a minor increase in processing time"},
{"id":"","label":"Enable attention slicing","localized":"","hint":"Performs attention computation in steps instead of all at once. Slower inference times, but greatly reduced memory usage"},
{"id":"","label":"Diffusers model loading variant","localized":"","hint":""},
{"id":"","label":"Diffusers VAE loading variant","localized":"","hint":""},
{"id":"","label":"Diffusers LoRA loading variant","localized":"","hint":"'sequential apply' loads and applies each LoRA in order of appearance, 'merge and apply' loads all LoRAs and merges them in-memory before applying to model, 'diffusers default' uses single LoRA loading method"}
{"id":"","label":"Diffusers LoRA loading variant","localized":"","hint":"'sequential apply' loads and applies each LoRA in order of appearance, 'merge and apply' loads all LoRAs and merges them in-memory before applying to model, 'diffusers default' uses single LoRA loading method"},
{"id":"","label":"Torch inference mode","localized":"","hint":"Use torch inference mode"},
{"id":"","label":"inference-mode","localized":"","hint":"Use torch.inference_mode"},
{"id":"","label":"no-grad","localized":"","hint":"Use torch.no_grad"},
{"id":"","label":"vae slicing (original)","localized":"","hint":"Run VAE on sliced samples to reduce memory requirements when processing high resolution images"},
{"id":"","label":"use fixed unet precision","localized":"","hint":""},
{"id":"","label":"enable model compile","localized":"","hint":"Enable usage of torch.compile"},
{"id":"","label":"reduce-overhead","localized":"","hint":""},
{"id":"","label":"max-autotune","localized":"","hint":""},
{"id":"","label":"model compile precompile","localized":"","hint":"Run model compile immediately on model load instead of first use"},
{"id":"","label":"sequential apply","localized":"","hint":"When loading multiple LoRAs, apply each in order of loading"},
{"id":"","label":"merge and apply","localized":"","hint":"When loading multiple LoRAs, load all and merge them before applying to model"},
{"id":"","label":"force zeros for prompts when empty","localized":"","hint":"Force full zero tensor when prompt is empty to remove any residual noise"},
{"id":"","label":"require aesthetics score","localized":"","hint":"Automatically guide model towards higher-pleasing results, applicable only to refiner model"},
{"id":"","label":"include watermark in saved images","localized":"","hint":"Add invisible watermark to image by altering some pixel values"},
{"id":"","label":"image watermark string","localized":"","hint":"Watermark string to add to image. Keep very short to avoid image corruption."},
{"id":"","label":"show log view","localized":"","hint":"Show log view at the bottom of the main window"},
{"id":"","label":"Log view update period","localized":"","hint":"Log view update period, in miliseconds"}
],
"scripts": [
{"id":"","label":"Script","localized":"","hint":""},
{"id":"","label":"Swap X/Y","localized":"","hint":""},
{"id":"","label":"Swap Y/Z","localized":"","hint":""},
{"id":"","label":"Swap X/Z","localized":"","hint":""},
{"id":"","label":"Resize to","localized":"","hint":""},
{"id":"","label":"Resize by","localized":"","hint":""},
{"id":"","label":"Use via API","localized":"","hint":""},
{"id":"","label":"Styles","localized":"","hint":""},
{"id":"","label":"Put variable parts at start of prompt","localized":"","hint":""},
{"id":"","label":"Use different seed for each picture","localized":"","hint":""},
{"id":"","label":"positive","localized":"","hint":""},
+1 -1
View File
@@ -518,7 +518,7 @@
{"id":"","label":"logSNR","localized":"","hint":""},
{"id":"","label":"UniPC order (must be < sampling steps)","localized":"","hint":""},
{"id":"","label":"UniPC lower order final","localized":"","hint":""},
{"id":"","label":"Enable addtional postprocessing operations","localized":"추가 후처리 작업","hint":""},
{"id":"","label":"Enable additional postprocessing operations","localized":"추가 후처리 작업","hint":""},
{"id":"","label":"Postprocessing operation order","localized":"후처리 작업 순서","hint":""},
{"id":"","label":"Maximum number of images in upscaling cache","localized":"","hint":""},
{"id":"","label":"Move VAE and CLIP to RAM when training if possible","localized":"가능하다면 학습 시 VAE와 CLIP 모델을 램으로 이동","hint":""},
+65 -6
View File
@@ -108,6 +108,22 @@ def setup_logging():
# logging.getLogger("DeepSpeed").handlers = log.handlers
def custom_excepthook(exc_type, exc_value, exc_traceback):
import traceback
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
log.error(f"Uncaught exception occurred: type={exc_type} value={exc_value}")
if exc_traceback:
format_exception = traceback.format_tb(exc_traceback)
for line in format_exception:
log.error(repr(line))
def print_dict(d):
return ' '.join([f'{k}={v}' for k, v in d.items()])
def print_profile(profile: cProfile.Profile, msg: str):
try:
from rich import print # pylint: disable=redefined-builtin
@@ -265,6 +281,26 @@ def clone(url, folder, commithash=None):
git(f'-C "{folder}" checkout {commithash}')
def get_platform():
try:
if platform.system() == 'Windows':
release = platform.platform(aliased = True, terse = False)
else:
release = platform.release()
return {
# 'host': platform.node(),
'arch': platform.machine(),
'cpu': platform.processor(),
'system': platform.system(),
'release': release,
# 'platform': platform.platform(aliased = True, terse = False),
# 'version': platform.version(),
'python': platform.python_version(),
}
except Exception as e:
return { 'error': e }
# check python version
def check_python():
supported_minors = [9, 10, 11]
@@ -580,7 +616,7 @@ def list_extensions_folder(folder, quiet=False):
disabled_extensions = opts.get('disabled_extensions', [])
enabled_extensions = [x for x in os.listdir(folder) if x not in disabled_extensions and not x.startswith('.')]
if not quiet:
log.info(f'Enabled {name}: {enabled_extensions}')
log.info(f'Extensions: enabled={enabled_extensions} {name}')
return enabled_extensions
@@ -713,9 +749,9 @@ def check_extensions():
extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir]
disabled_extensions_all = opts.get('disable_all_extensions', 'none')
if disabled_extensions_all != 'none':
log.info(f'Disabled extensions: {disabled_extensions_all}')
log.info(f'Extensions: disabled={disabled_extensions_all}')
else:
log.info(f'Disabled extensions: {opts.get("disabled_extensions", [])}')
log.info(f'Extensions: disabled={opts.get("disabled_extensions", [])}')
for folder in extension_folders:
if not os.path.isdir(folder):
continue
@@ -736,6 +772,28 @@ def check_extensions():
return round(newest_all)
def get_version():
version = None
if version is None:
try:
res = subprocess.run('git log --pretty=format:"%h %ad" -1 --date=short', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True)
ver = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ' '
githash, updated = ver.split(' ')
res = subprocess.run('git remote get-url origin', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True)
origin = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ''
res = subprocess.run('git rev-parse --abbrev-ref HEAD', stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True, check=True)
branch_name = res.stdout.decode(encoding = 'utf8', errors='ignore') if len(res.stdout) > 0 else ''
version = {
'app': 'sd.next',
'updated': updated,
'hash': githash,
'url': origin.replace('\n', '') + '/tree/' + branch_name.replace('\n', '')
}
except Exception:
version = { 'app': 'sd.next', 'version': 'unknown' }
return version
# check version of the main repo and optionally upgrade it
def check_version(offline=False, reset=True): # pylint: disable=unused-argument
if args.skip_all:
@@ -744,8 +802,7 @@ def check_version(offline=False, reset=True): # pylint: disable=unused-argument
log.error('Not a git repository')
if not args.ignore:
sys.exit(1)
ver = git('log -1 --pretty=format:"%h %ad"')
log.info(f'Version: {ver}')
log.info(f'Version: {print_dict(get_version())}')
if args.version or args.skip_git:
return
commit = git('rev-parse HEAD')
@@ -873,11 +930,13 @@ def extensions_preload(parser):
from modules.script_loading import preload_extensions
from modules.paths_internal import extensions_builtin_dir, extensions_dir
extension_folders = [extensions_builtin_dir] if args.safe else [extensions_builtin_dir, extensions_dir]
preload_time = {}
for ext_dir in extension_folders:
t0 = time.time()
preload_extensions(ext_dir, parser)
t1 = time.time()
log.info(f'Extension preload: {round(t1 - t0, 1)}s {ext_dir}')
preload_time[ext_dir] = round(t1 - t0, 2)
log.info(f'Extension preload: {preload_time}')
except Exception:
log.error('Error running extension preloading')
if args.profile:
-1
View File
@@ -95,7 +95,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
#img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; }
#interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; }
#quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; }
#quicksettings > div, #quicksettings > fieldset { min-width: 24em; max-width: 26em; line-height: 1.6em; margin-top: 0.4em; }
#open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; }
#save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; }
#script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; }
-1
View File
@@ -95,7 +95,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
#img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; }
#interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; }
#quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; }
#quicksettings > div, #quicksettings > fieldset { line-height: 1.4em; margin-top: 0.4em; }
#open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; }
#save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; }
#script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; }
+6 -6
View File
@@ -77,13 +77,14 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.py-6 { padding-bottom: 0; }
.tabs { background-color: var(--background-color); }
.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; }
.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
.tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
.label-wrap { margin: 16px 0px 8px 0px; }
.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; }
.gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); }
#tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; }
#tab_extensions table { width: 96vw }
#tab_extensions table thead { background-color: var(--neutral-700); }
#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: none; padding: 0.5em; }
#tab_extensions table, #tab_config table { width: 96vw }
#tab_extensions table thead, #tab_config table thead { background-color: var(--neutral-700); }
#tab_extensions table, #tab_config table { background-color: #222222; }
/* automatic style classes */
.progressDiv { border-radius: var(--radius-sm) !important; position: fixed; top: 44px; right: 26px; max-width: 262px; height: 48px; z-index: 99; box-shadow: var(--button-shadow); }
@@ -93,6 +94,7 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.extra-networks { border-left: 2px solid var(--highlight-color) !important; padding-left: 4px; }
.image-buttons { gap: 10px !important; justify-content: center; }
.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: 190px; }
/* gradio elements overrides */
@@ -103,12 +105,10 @@ svg.feather.feather-image, .feather .feather-image { display: none }
#img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; }
#interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; }
#quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; }
#quicksettings > div, #quicksettings > fieldset { line-height: 1.4em; margin-top: 0.4em; }
#open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; }
#save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; }
#script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; }
#settings > div.flex-wrap { width: 15em; }
#tab_extensions table { background-color: #222222; }
#txt2img_cfg_scale { min-width: 200px; }
#txt2img_checkboxes, #img2img_checkboxes { background-color: transparent; }
#txt2img_checkboxes, #img2img_checkboxes { margin-bottom: 0.2em; }
+26 -15
View File
@@ -153,24 +153,35 @@ function setupExtraNetworksForTab(tabname) {
tabs.appendChild(div);
div.appendChild(search);
div.appendChild(description);
let searchTimer = null;
search.addEventListener('input', (evt) => {
const searchTerm = search.value.toLowerCase();
gradioApp().querySelectorAll(`#${tabname}_extra_tabs div.card`).forEach((elem) => {
let text = `${elem.querySelector('.name').textContent.toLowerCase()} ${elem.querySelector('.search_term').textContent.toLowerCase()}`;
text = text.replace('models--', 'Diffusers');
elem.style.display = text.indexOf(searchTerm) === -1 ? 'none' : '';
});
if (searchTimer) clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
const searchTerm = search.value.toLowerCase();
gradioApp().querySelectorAll(`#${tabname}_extra_tabs div.card`).forEach((elem) => {
let text = `${elem.querySelector('.name').textContent.toLowerCase()} ${elem.querySelector('.search_term').textContent.toLowerCase()}`;
text = text.replace('models--', 'Diffusers');
elem.style.display = text.indexOf(searchTerm) === -1 ? 'none' : '';
});
searchTimer = null;
}, 100);
});
let hoverTimer = null;
gradioApp().getElementById(`${tabname}_extra_tabs`).onmouseover = (e) => {
const el = e?.target?.parentElement;
if (!el?.classList?.contains('card')) return;
if (el.title === previousCard) return;
readCardDescription(el.dataset.filename, el.dataset.description);
readCardTags(el, el.dataset.tags);
e.stopPropagation();
e.preventDefault();
previousCard = el.title;
const el = e.target.closest('.card'); // bubble-up to card
if (!el || (el.title === previousCard)) return;
if (!hoverTimer) {
hoverTimer = setTimeout(() => {
readCardDescription(el.dataset.filename, el.dataset.description);
readCardTags(el, el.dataset.tags);
previousCard = el.title;
}, 300);
}
el.onmouseout = () => {
clearTimeout(hoverTimer);
hoverTimer = null;
};
};
const intersectionObserver = new IntersectionObserver((entries) => {
@@ -278,7 +289,7 @@ function extraNetworksSearchButton(event) {
updateInput(searchTextarea);
}
function extraNetworksRefreshButton() {
function getENActivePage() {
const tabname = getENActiveTab();
const page = gradioApp().querySelector(`#${tabname}_extra_networks > .tabs > .tab-nav > .selected`);
return page ? page.innerText : '';
+1 -2
View File
@@ -77,7 +77,7 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.py-6 { padding-bottom: 0; }
.tabs { background-color: var(--background-color); }
.block.token-counter span { background-color: var(--input-background-fill) !important; box-shadow: 2px 2px 2px #111; border: none !important; font-size: 0.8rem; }
.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
.tab-nav { zoom: 120%; margin-top: 10px; margin-bottom: 10px; border-bottom: 2px solid var(--highlight-color) !important; padding-bottom: 2px; }
.label-wrap { margin: 16px 0px 8px 0px; }
.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; height: 20px; }
.gradio-button.tool { border: none; background: none; box-shadow: none; filter: hue-rotate(340deg) saturate(0.5); }
@@ -103,7 +103,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
#img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; }
#interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; }
#quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; }
#quicksettings > div, #quicksettings > fieldset { line-height: 1.4em; margin-top: 0.4em; }
#open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; }
#save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; }
#script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; }
-1
View File
@@ -95,7 +95,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
#img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; }
#interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; }
#quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; }
#quicksettings > div, #quicksettings > fieldset { line-height: 1.4em; margin-top: 0.4em; }
#open_folder_extras, #footer, #style_pos_col, #style_neg_col, #roll_col, #extras_upscaler_2, #extras_upscaler_2_visibility, #txt2img_seed_resize_from_w, #txt2img_seed_resize_from_h { display: none; }
#save-animation { border-radius: var(--radius-sm) !important; margin-bottom: 16px; background-color: #111111; }
#script_list { padding: 4px; margin-top: 20px; margin-bottom: 20px; }
@@ -3,15 +3,17 @@
// Counts open and closed brackets (round, square, curly) in the prompt and negative prompt text boxes in the txt2img and img2img tabs.
// If there's a mismatch, the keyword counter turns red and if you hover on it, a tooltip tells you what's wrong.
let promptCheckerInitialized = false;
function checkBrackets(textArea, counterElt) {
const counts = {};
(textArea.value.match(/[(){}[\]]/g) || []).forEach((bracket) => { counts[bracket] = (counts[bracket] || 0) + 1; });
const errors = [];
function checkPair(open, close, kind) {
if (counts[open] !== counts[close]) errors.push(`${open}...${close} - Detected ${counts[open] || 0} opening and ${counts[close] || 0} closing ${kind}.`);
}
(textArea.value.match(/[(){}[\]]/g) || []).forEach((bracket) => { counts[bracket] = (counts[bracket] || 0) + 1; });
checkPair('(', ')', 'round brackets');
checkPair('[', ']', 'square brackets');
checkPair('{', '}', 'curly brackets');
@@ -22,10 +24,14 @@ function checkBrackets(textArea, counterElt) {
function setupBracketChecking(idPrompt, idCounter) {
const textarea = gradioApp().querySelector(`#${idPrompt} > label > textarea`);
const counter = gradioApp().getElementById(idCounter);
if (textarea && counter) textarea.addEventListener('input', () => checkBrackets(textarea, counter));
if (!textarea || !counter) return;
if (!promptCheckerInitialized) log('initPromptChecker');
promptCheckerInitialized = true;
textarea.addEventListener('input', () => checkBrackets(textarea, counter));
}
onAfterUiUpdate(() => {
if (promptCheckerInitialized) return;
setupBracketChecking('txt2img_prompt', 'txt2img_token_counter');
setupBracketChecking('txt2img_neg_prompt', 'txt2img_negative_token_counter');
setupBracketChecking('img2img_prompt', 'img2img_token_counter');
+3 -2
View File
@@ -52,6 +52,7 @@ async function setHints() {
const res = await fetch('/file=html/locale_en.json');
const json = await res.json();
localeData.data = Object.values(json).flat();
for (const e of localeData.data) e.label = e.label.toLowerCase().trim();
}
const elements = [
...Array.from(gradioApp().querySelectorAll('button')),
@@ -65,7 +66,7 @@ async function setHints() {
localeData.finished = true;
const t0 = performance.now();
for (const el of elements) {
const found = localeData.data.find((l) => l.label === el.textContent.trim());
const found = localeData.data.find((l) => l.label === el.textContent.toLowerCase().trim());
if (found?.localized?.length > 0) {
localized++;
el.textContent = found.localized;
@@ -87,7 +88,7 @@ async function setHints() {
log('setHints', { type: localeData.type, elements: elements.length, localized, hints, data: localeData.data.length, time: t1 - t0 });
// sortUIElements();
removeSplash();
// validateHints(elements, localeData.data)
// validateHints(elements, localeData.data);
}
onAfterUiUpdate(async () => {
+26 -20
View File
@@ -6,7 +6,7 @@ div.tabitem { padding: 0 !important; }
div.form { border-width: 0; box-shadow: none; background: transparent; overflow: visible; gap: 0.5em 1em; flex-grow: 1 !important; }
div.compact{ gap: 1em; }
div.gradio-html.min{ min-height: 0; }
.block.gradio-checkbox { margin: 0.75em 1.5em 0 0; }
.block.gradio-checkbox { margin: 0.75em 1.5em 0 0; align-self: center; }
.block.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;}
.block.padded:not(.gradio-accordion) { padding: 0 !important; margin-right: 0; min-width: 100px !important; }
.compact{ background: transparent !important; padding: 0 !important; }
@@ -19,21 +19,22 @@ div.gradio-html.min{ min-height: 0; }
.gradio-dropdown label span:not(.has-info), .gradio-textbox label span:not(.has-info), .gradio-number label span:not(.has-info) { margin-bottom: 0; }
.gradio-dropdown ul.options li.item { padding: 0.05em 0; }
.gradio-dropdown ul.options li.item:not(:has(.hide)) { background-color: var(--neutral-100); }
.gradio-dropdown ul.options{ z-index: 3000; min-width: fit-content; max-width: inherit; white-space: nowrap; }
.gradio-dropdown ul.options { z-index: 3000; min-width: fit-content; max-height: 25vh !important; white-space: nowrap; }
.gradio-dropdown:not(.multiselect) .wrap-inner.wrap-inner.wrap-inner{ flex-wrap: unset; }
.gradio-dropdown.multiselect .token-remove.remove-all.remove-all{ display: flex; }
.gradio-dropdown.multiselect div.wrap-inner { overflow-x: hidden; overflow-y: auto; max-height: 50vh; overflow-wrap: anywhere; }
.gradio-html div.wrap{ height: 100%; }
.gradio-slider input[type="number"]{ width: 6em; margin-left: 0.5em; }
.gradio-accordion { padding-top: var(--spacing-md) !important; padding-right: 0 !important; padding-bottom: 0 !important; color: var(--body-text-color); }
.hidden { display: none; }
footer { display: none; }
td { border-bottom: none !important; }
/* general styled components */
.gradio-button.tool{ max-width: 1em; min-width: 1em !important; align-self: end; font-size: 1.4em }
.gradio-button.secondary-down{ background: var(--button-secondary-background-fill); color: var(--button-secondary-text-color); }
.gradio-button.secondary-down, .gradio-button.secondary-down:hover{ box-shadow: 1px 1px 1px rgba(0,0,0,0.25) inset, 0px 0px 3px rgba(0,0,0,0.15) inset; }
.gradio-button.secondary-down:hover{ background: var(--button-secondary-background-fill-hover); color: var(--button-secondary-text-color-hover); }
.gradio-button.tool { max-width: min-content; min-width: min-content !important; align-self: end; font-size: 1.4em; color: var(--body-text-color) !important; }
.gradio-button.secondary-down { background: var(--button-secondary-background-fill); color: var(--button-secondary-text-color); }
.gradio-button.secondary-down, .gradio-button.secondary-down:hover { box-shadow: 1px 1px 1px rgba(0,0,0,0.25) inset, 0px 0px 3px rgba(0,0,0,0.15) inset; }
.gradio-button.secondary-down:hover { background: var(--button-secondary-background-fill-hover); color: var(--button-secondary-text-color-hover); }
.checkboxes-row { margin-bottom: 1em; gap: 0 !important; justify-content: space-around; flex-wrap: unset !important; }
.checkboxes-row > div{ flex: 0; white-space: nowrap; min-width: auto; }
@@ -75,7 +76,7 @@ button.custom-button{
#txt2img_generate_line2, #img2img_generate_line2 { display: flex; }
#txt2img_generate_line2 > button, #img2img_generate_line2 > button, #extras_generate_box > button { height: 2.2em; line-height: 0; min-width: unset; display: block !important; }
#txt2img_tools > div, #img2img_tools > div { justify-content: space-around; margin-top: 0.5em; margin-bottom: 0em; }
#txt2img_tools > div > button, #img2img_tools > div > button { scale: 120%; }
#txt2img_tools > div > button, #img2img_tools > div > button { scale: 120%; min-width: 1em !important; min-height: 1em !important; }
#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { display: contents; }
.interrogate-col{ min-width: 0 !important; max-width: fit-content; gap: 0.5em; }
.interrogate-col > button{ flex: 1; }
@@ -115,12 +116,11 @@ div#extras_scale_to_tab div.form{ flex-direction: row; }
/* settings */
#si-sparkline-memo, #si-sparkline-load { background-color: #111; }
.licenses { display: block !important; }
#quicksettings { width: fit-content; align-items: end; }
#quicksettings > div, #quicksettings > fieldset{ max-width: 20em; min-width: 24em; padding: 0; border: none; box-shadow: none; background: none; }
#quicksettings > button { margin-left: -0.5em; }
#quicksettings { width: fit-content; margin-top: 1em; }
#quicksettings > button { padding: 0 1em 0 0 }
#settings { display: flex; gap: var(--layout-gap); }
#settings div { border: none; justify-content: normal; gap: 0.5em; }
#settings div { border: none; gap: 0.5em; }
#settings > div.tab-content { flex: 10 0 75%; display: grid; }
#settings > div.tab-content > div { border: none; padding: 0; }
@@ -142,7 +142,7 @@ 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: 300; background-color: transparent; width: -webkit-fill-available; }
.livePreview { position: absolute; z-index: 300; 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; }
@@ -150,7 +150,6 @@ div#extras_scale_to_tab div.form{ flex-direction: row; }
.global-popup-close:before { content: "×"; }
.global-popup-close{ position: fixed; right: 0.5em; top: 0; cursor: pointer; color: white; font-size: 32pt; }
.global-popup-inner{ display: inline-block; margin: auto; padding: 2em; }
.ui-defaults-none{ color: #aaa !important; }
/* fullpage image viewer */
@@ -211,8 +210,8 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt
.context-menu-items a:hover { background: #a55000; }
/* extensions */
#tab_extensions table{ border-collapse: collapse; }
#tab_extensions table td, #tab_extensions table th { border: 1px solid #ccc; padding: 0.25em 0.5em; }
#tab_extensions table, #tab_config table{ border-collapse: collapse; }
#tab_extensions table td, #tab_extensions table th, #tab_config table td, #tab_config table th { border: 1px solid #ccc; padding: 0.25em 0.5em; }
#tab_extensions table input[type="checkbox"] { margin-right: 0.5em; appearance: checkbox; }
#tab_extensions button{ max-width: 16em; }
#tab_extensions input[disabled="disabled"]{ opacity: 0.5; }
@@ -226,20 +225,20 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt
/* extra networks */
.extra-networks > div { margin: 0; gap: 0.2em; border-bottom: none !important; }
.extra-networks .second-line { display: flex; width: -webkit-fill-available; gap: 0.3em; box-shadow: var(--input-shadow); }
.extra-networks .second-line { display: flex; width: -moz-available; width: -webkit-fill-available; gap: 0.3em; box-shadow: var(--input-shadow); }
.extra-networks .search { flex: 1; }
.extra-networks .description { flex: 3; }
.extra-networks .tab-nav > button { margin-right: 0; height: 24px; padding: 2px 4px 2px 4px; }
.extra-networks-tab { padding: 0 !important; }
.extra-network-subdirs { background: var(--input-background-fill); overflow-x: hidden; overflow-y: auto; min-width: 120px; padding-top: 0.5em; }
.extra-network-subdirs { background: var(--input-background-fill); overflow-x: hidden; overflow-y: auto; min-width: max(20%, 120px); padding-top: 0.5em; }
.extra-networks-page { display: flex }
.extra-networks .custom-button { width: 120px; width: 100%; background: none; justify-content: left; text-align: left; padding: 2px 8px 2px 16px; text-indent: -8px; box-shadow: none; line-break: auto; }
.extra-networks .custom-button:hover { background: var(--button-primary-background-fill) }
.extra-network-cards { display: flex; flex-wrap: wrap; overflow-y: auto; overflow-x: hidden; align-content: flex-start; width: -webkit-fill-available; }
.extra-network-cards { display: flex; flex-wrap: wrap; overflow-y: auto; overflow-x: hidden; align-content: flex-start; width: -moz-available; width: -webkit-fill-available; }
.extra-network-cards .card { height: fit-content; margin: 0 0 0.5em 0.5em; position: relative; scroll-snap-align: start; scroll-margin-top: 0; }
.extra-network-cards .card .overlay { position: absolute; bottom: 0; padding: 0.2em; z-index: 10; width: 100%; background: none; }
.extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); }
.extra-network-cards .card .overlay .name { font-size: 1.1em; font-weight: bold; text-shadow: 1px 1px black; color: white; }
.extra-network-cards .card .overlay .name { font-size: 1.1em; font-weight: bold; text-shadow: 1px 1px black; color: white; overflow-wrap: break-word; }
.extra-network-cards .card .overlay .tags { margin: 4px; display: none; overflow-wrap: break-word; }
.extra-network-cards .card .overlay .tag { padding: 2px; margin: 2px; background: var(--neutral-700); cursor: pointer; display: inline-block; }
.extra-network-cards .card .overlay .actions { font-size: 2.2em; display: none; text-align-last: center; cursor: pointer; font-variant: unicase; height: 0.8em }
@@ -267,7 +266,11 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri
/* specific elements */
#modelmerger_interp_description { margin-top: 1em; margin-bottom: 1em; }
#scripts_alwayson_txt2img, #scripts_alwayson_img2img { display: grid }
#scripts_alwayson_txt2img, #scripts_alwayson_img2img { display: grid; padding: 0 }
#scripts_alwayson_txt2img > .label-wrap, #scripts_alwayson_img2img > .label-wrap { background: var(--input-background-fill); padding: 0; margin: 0; border-radius: var(--radius-lg); }
#scripts_alwayson_txt2img > .label-wrap > span, #scripts_alwayson_img2img > .label-wrap > span { padding: var(--spacing-xxl); }
#script_txt2img_agent_scheduler { display: none; }
#extras_generate, #extras_interrupt, #extras_skip { display: block !important; position: relative; height: 36px; }
#extras_upscale { margin-top: 10px }
#refresh_tac_refreshTempFiles { display: none; }
@@ -278,6 +281,9 @@ div.controlnet_main_options { display: grid; grid-template-columns: 1fr 1fr; gri
.log-monitor { display: none; justify-content: unset !important; overflow: hidden; padding: 0; margin-top: auto; font-family: monospace; font-size: 0.85em; }
.log-monitor td, .log-monitor th { padding-left: 1em; }
/* custom component */
.folder-selector textarea { height: 2em !important; padding: 6px !important; }
/* Workaround for Gradio dropdowns capturing clicks during and after fadeout */
.gradio-dropdown > label > div > div:first-child:not(.showOptions) ~ ul.options { pointer-events: none; }
+20 -12
View File
@@ -361,19 +361,27 @@ function create_theme_element() {
return el;
}
async function preview_theme() {
function previewTheme() {
let name = gradioApp().getElementById('setting_gradio_theme').querySelectorAll('input')?.[0].value || '';
const res = await fetch('/file=html/themes.json');
const themes = await res.json();
const theme = themes.find((t) => t.id === name);
if (theme) {
window.open(theme.subdomain, '_blank');
} else {
const el = document.getElementById('theme-preview') || create_theme_element();
el.style.display = el.style.display === 'block' ? 'none' : 'block';
name = name.replace('/', '-');
el.src = `/file=html/${name}.jpg`;
}
fetch('/file=html/themes.json').then((res) => {
res.json().then((themes) => {
const theme = themes.find((t) => t.id === name);
if (theme) {
window.open(theme.subdomain, '_blank');
} else {
const el = document.getElementById('theme-preview') || create_theme_element();
el.style.display = el.style.display === 'block' ? 'none' : 'block';
name = name.replace('/', '-');
el.src = `/file=html/${name}.jpg`;
}
});
});
}
async function browseFolder() {
const f = await window.showDirectoryPicker();
if (f && f.kind === 'directory') return f.name;
return null;
}
async function reconnectUI() {
+18 -13
View File
@@ -40,7 +40,7 @@ def get_custom_args():
current = getattr(args, arg)
if current != default:
custom[arg] = getattr(args, arg)
installer.log.info(f'Command line args: {custom}')
installer.log.info(f'Command line args: {sys.argv[1:]} {installer.print_dict(custom)}')
@lru_cache()
@@ -121,7 +121,7 @@ def get_memory_stats():
process = psutil.Process(os.getpid())
res = process.memory_info()
ram_total = 100 * res.rss / process.memory_percent()
return f'used: {gb(res.rss)} total: {gb(ram_total)}'
return f'used={gb(res.rss)} total={gb(ram_total)}'
def start_server(immediate=True, server=None):
@@ -137,25 +137,26 @@ def start_server(immediate=True, server=None):
collected = gc.collect()
if not immediate:
time.sleep(3)
installer.log.debug(f'Memory {get_memory_stats()} Collected {collected}')
if collected > 0:
installer.log.debug(f'Memory {get_memory_stats()} Collected {collected}')
module_spec = importlib.util.spec_from_file_location('webui', 'webui.py')
# installer.log.debug(f'Loading module: {module_spec}')
server = importlib.util.module_from_spec(module_spec)
installer.log.debug(f'Starting module: {server}')
installer.log.info(f"Server arguments: {sys.argv[1:]}")
get_custom_args()
module_spec.loader.exec_module(server)
uvicorn = None
if args.test:
installer.log.info("Test only")
server.wants_restart = False
else:
if args.api_only:
server = server.api_only()
uvicorn = server.api_only()
else:
server = server.webui(restart=not immediate)
uvicorn = server.webui(restart=not immediate)
if args.profile:
installer.print_profile(pr, 'WebUI')
return server
return uvicorn, server
if __name__ == "__main__":
@@ -164,6 +165,7 @@ if __name__ == "__main__":
installer.args = args
installer.setup_logging()
installer.log.info('Starting SD.Next')
sys.excepthook = installer.custom_excepthook
installer.read_options()
if args.skip_all:
args.quick = True
@@ -173,6 +175,7 @@ if __name__ == "__main__":
if args.skip_git:
installer.log.info('Skipping GIT operations')
installer.check_version()
installer.log.info(f'Platform: {installer.print_dict(installer.get_platform())}')
installer.set_environment()
installer.check_torch()
installer.check_modified_files()
@@ -207,20 +210,22 @@ if __name__ == "__main__":
# installer.log.debug(f"Args: {vars(args)}")
logging.disable(logging.NOTSET if args.debug else logging.DEBUG)
instance = start_server(immediate=True, server=None)
uv, instance = start_server(immediate=True, server=None)
while True:
try:
alive = instance.thread.is_alive()
requests = instance.server_state.total_requests if hasattr(instance, 'server_state') else 0
alive = uv.thread.is_alive()
requests = uv.server_state.total_requests if hasattr(uv, 'server_state') else 0
except Exception:
alive = False
requests = 0
if round(time.time()) % 120 == 0:
installer.log.debug(f'Server alive={alive} requests={requests} memory {get_memory_stats()} ')
state = f'job="{instance.state.job}" {instance.state.job_no}/{instance.state.job_count}' if instance.state.job != '' or instance.state.job_no != 0 or instance.state.job_count != 0 else 'idle'
uptime = round(time.time() - instance.state.server_start)
installer.log.debug(f'Server alive={alive} jobs={instance.state.total_jobs} requests={requests} uptime={uptime}s memory {get_memory_stats()} {state}')
if not alive:
if instance.wants_restart:
if uv is not None and uv.wants_restart:
installer.log.info('Server restarting...')
instance = start_server(immediate=False, server=instance)
uv, instance = start_server(immediate=False, server=instance)
else:
installer.log.info('Exiting...')
break
+9 -14
View File
@@ -131,7 +131,7 @@ class Api:
self.add_api_route("/sdapi/v1/hypernetworks", self.get_hypernetworks, methods=["GET"], response_model=List[models.HypernetworkItem])
self.add_api_route("/sdapi/v1/face-restorers", self.get_face_restorers, methods=["GET"], response_model=List[models.FaceRestorerItem])
self.add_api_route("/sdapi/v1/realesrgan-models", self.get_realesrgan_models, methods=["GET"], response_model=List[models.RealesrganItem])
self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=List[models.PromptStyleItem])
self.add_api_route("/sdapi/v1/prompt-styles", self.get_prompt_styles, methods=["GET"], response_model=List[models.StyleItem])
self.add_api_route("/sdapi/v1/embeddings", self.get_embeddings, methods=["GET"], response_model=models.EmbeddingsResponse)
self.add_api_route("/sdapi/v1/refresh-checkpoints", self.refresh_checkpoints, methods=["POST"])
self.add_api_route("/sdapi/v1/sd-vae", self.get_sd_vaes, methods=["GET"], response_model=List[models.SDVaeItem])
@@ -263,7 +263,7 @@ class Api:
p.scripts = script_runner
p.outpath_grids = shared.opts.outdir_grids or shared.opts.outdir_txt2img_grids
p.outpath_samples = shared.opts.outdir_samples or shared.opts.outdir_txt2img_samples
shared.state.begin()
shared.state.begin('api-txt2img')
script_args = self.init_script_args(p, txt2imgreq, self.default_script_arg_txt2img, selectable_scripts, selectable_script_idx, script_runner)
if selectable_scripts is not None:
processed = scripts.scripts_txt2img.run(p, *script_args) # Need to pass args as list here
@@ -311,7 +311,7 @@ class Api:
p.scripts = script_runner
p.outpath_grids = shared.opts.outdir_img2img_grids
p.outpath_samples = shared.opts.outdir_img2img_samples
shared.state.begin()
shared.state.begin('api-img2img')
script_args = self.init_script_args(p, img2imgreq, self.default_script_arg_img2img, selectable_scripts, selectable_script_idx, script_runner)
if selectable_scripts is not None:
processed = scripts.scripts_img2img.run(p, *script_args) # Need to pass args as list here
@@ -478,12 +478,7 @@ class Api:
return [{"name":x.name,"path":x.data_path, "scale":x.scale} for x in get_realesrgan_models(None)]
def get_prompt_styles(self):
styleList = []
for k in shared.prompt_styles.styles:
style = shared.prompt_styles.styles[k]
styleList.append({"name":style[0], "prompt": style[1], "negative_prompt": style[2]})
return styleList
return [{ 'name': v.name, 'prompt': v.prompt, 'negative_prompt': v.negative_prompt, 'extra': v.extra, 'filename': v.filename, 'preview': v.preview} for v in shared.prompt_styles.styles.values()]
def get_embeddings(self):
db = sd_hijack.model_hijack.embedding_db
@@ -513,7 +508,7 @@ class Api:
def create_embedding(self, args: dict):
try:
shared.state.begin()
shared.state.begin('api-create-embedding')
filename = create_embedding(**args) # create empty embedding
sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings() # reload embeddings so new one can be immediately used
shared.state.end()
@@ -524,7 +519,7 @@ class Api:
def create_hypernetwork(self, args: dict):
try:
shared.state.begin()
shared.state.begin('api-create-hypernetwork')
filename = create_hypernetwork(**args) # create empty embedding # pylint: disable=E1111
shared.state.end()
return models.CreateResponse(info = f"create hypernetwork filename: {filename}")
@@ -534,7 +529,7 @@ class Api:
def preprocess(self, args: dict):
try:
shared.state.begin()
shared.state.begin('api-preprocess')
preprocess(**args) # quick operation unless blip/booru interrogation is enabled
shared.state.end()
return models.PreprocessResponse(info = 'preprocess complete')
@@ -550,7 +545,7 @@ class Api:
def train_embedding(self, args: dict):
try:
shared.state.begin()
shared.state.begin('api-train-embedding')
apply_optimizations = False
error = None
filename = ''
@@ -571,7 +566,7 @@ class Api:
def train_hypernetwork(self, args: dict):
try:
shared.state.begin()
shared.state.begin('api-train-hypernetwork')
shared.loaded_hypernetworks = []
apply_optimizations = False
error = None
+4 -1
View File
@@ -264,10 +264,13 @@ class RealesrganItem(BaseModel):
path: Optional[str] = Field(title="Path")
scale: Optional[int] = Field(title="Scale")
class PromptStyleItem(BaseModel):
class StyleItem(BaseModel):
name: str = Field(title="Name")
prompt: Optional[str] = Field(title="Prompt")
negative_prompt: Optional[str] = Field(title="Negative Prompt")
extra: Optional[str] = Field(title="Extra")
filename: Optional[str] = Field(title="Filename")
preview: Optional[str] = Field(title="Preview")
class ArtistItem(BaseModel):
name: str = Field(title="Name")
+6 -9
View File
@@ -19,6 +19,7 @@ def wrap_queued_call(func):
def wrap_gradio_gpu_call(func, extra_outputs=None):
name = func.__name__
def f(*args, **kwargs):
# if the first argument is a string that says "task(...)", it is treated as a job id
if len(args) > 0 and type(args[0]) == str and args[0][0:5] == "task(" and args[0][-1] == ")":
@@ -27,7 +28,6 @@ def wrap_gradio_gpu_call(func, extra_outputs=None):
else:
id_task = None
with queue_lock:
shared.state.begin()
progress.start_task(id_task)
res = [None, '', '', '']
try:
@@ -42,13 +42,15 @@ def wrap_gradio_gpu_call(func, extra_outputs=None):
progress.finish_task(id_task)
shared.state.end()
return res
return wrap_gradio_call(f, extra_outputs=extra_outputs, add_stats=True)
return wrap_gradio_call(f, extra_outputs=extra_outputs, add_stats=True, name=name)
def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
def wrap_gradio_call(func, extra_outputs=None, add_stats=False, name=None):
job_name = name if name is not None else func.__name__
def f(*args, extra_outputs_array=extra_outputs, **kwargs):
t = time.perf_counter()
shared.mem_mon.reset()
shared.state.begin(job_name)
try:
if shared.cmd_opts.profile:
pr = cProfile.Profile()
@@ -67,15 +69,10 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
print('Profile Exec:', s.getvalue())
except Exception as e:
errors.display(e, 'gradio call')
shared.state.job = ""
shared.state.job_count = 0
if extra_outputs_array is None:
extra_outputs_array = [None, '']
res = extra_outputs_array + [f"<div class='error'>{html.escape(type(e).__name__+': '+str(e))}</div>"]
shared.state.skipped = False
shared.state.interrupted = False
shared.state.paused = False
shared.state.job_count = 0
shared.state.end()
if not add_stats:
return tuple(res)
elapsed = time.perf_counter() - t
+1
View File
@@ -36,6 +36,7 @@ group.add_argument("--tls-certfile", type=str, help="Enable TLS and specify cert
group.add_argument("--tls-selfsign", action="store_true", help="Enable TLS with self-signed certificates, default: %(default)s", default=None)
group.add_argument("--server-name", type=str, help="Sets hostname of server, default: %(default)s", default=None)
group.add_argument("--no-hashing", action='store_true', help="Disable hashing of checkpoints, default: %(default)s", default=False)
group.add_argument("--no-metadata", action='store_true', help="Disable reading of metadata from models, default: %(default)s", default=False)
group.add_argument("--no-download", action='store_true', help="Disable download of default model, default: %(default)s", default=False)
group.add_argument("--profile", action='store_true', help="Run profiler, default: %(default)s")
group.add_argument("--disable-queue", action='store_true', help="Disable queues, default: %(default)s")
+1 -1
View File
@@ -97,7 +97,7 @@ def setup_model(dirname):
cropped_face_t = cropped_face_t.unsqueeze(0).to(devices.device_codeformer)
try:
with torch.no_grad():
with devices.inference_context():
output = self.net(cropped_face_t, w=w if w is not None else shared.opts.code_former_weight, adain=True)[0]
restored_face = tensor2img(output, rgb2bgr=True, min_max=(-1, 1))
del output
+1 -1
View File
@@ -56,7 +56,7 @@ class DeepDanbooru:
pic = images.resize_image(2, pil_image.convert("RGB"), 512, 512)
a = np.expand_dims(np.array(pic, dtype=np.float32), 0) / 255
with torch.no_grad(), devices.autocast():
with devices.inference_context(), devices.autocast():
x = torch.from_numpy(a).to(devices.device)
y = self.model(x)[0].detach().cpu().numpy()
+61 -8
View File
@@ -17,6 +17,51 @@ def has_mps() -> bool:
return mac_specific.has_mps
def get_gpu_info():
def get_driver():
import os
import subprocess
if torch.cuda.is_available() and torch.version.cuda:
try:
result = subprocess.run('nvidia-smi --query-gpu=driver_version --format=csv,noheader', shell=True, check=False, env=os.environ, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
version = result.stdout.decode(encoding="utf8", errors="ignore").strip()
return version
except Exception:
return ''
else:
return ''
if not torch.cuda.is_available():
return {}
else:
try:
if torch.version.cuda:
return {
'device': f'{torch.cuda.get_device_name(torch.cuda.current_device())} n={torch.cuda.device_count()} arch={torch.cuda.get_arch_list()[-1]} cap={torch.cuda.get_device_capability(device)}',
'cuda': torch.version.cuda,
'cudnn': torch.backends.cudnn.version(),
'driver': get_driver(),
}
elif torch.version.hip:
return {
'device': f'{torch.cuda.get_device_name(torch.cuda.current_device())} n={torch.cuda.device_count()}',
'hip': torch.version.hip,
}
else:
try:
import intel_extension_for_pytorch as ipex# pylint: disable=import-error, unused-import
return {
'device': f'{torch.xpu.get_device_name(torch.xpu.current_device())} n={torch.xpu.device_count()}',
'ipex': ipex.__version__,
}
except Exception:
return {
'device': 'unknown'
}
except Exception as ex:
return { 'error': ex }
def extract_device_id(args, name): # pylint: disable=redefined-outer-name
for x in range(len(args)):
if name in args[x]:
@@ -93,10 +138,9 @@ def test_fp16():
x = torch.tensor([[1.5,.0,.0,.0]]).to(device).half()
layerNorm = torch.nn.LayerNorm(4, eps=0.00001, elementwise_affine=True, dtype=torch.float16, device=device)
_y = layerNorm(x)
shared.log.debug('Torch FP16 test passed')
return True
except Exception as e:
shared.log.warning(f'Torch FP16 test failed: Forcing FP32 operations: {e}')
except Exception as ex:
shared.log.warning(f'Torch FP16 test failed: Forcing FP32 operations: {ex}')
shared.opts.cuda_dtype = 'FP32'
shared.opts.no_half = True
shared.opts.no_half_vae = True
@@ -116,7 +160,7 @@ def test_bf16():
def set_cuda_params():
shared.log.debug('Verifying Torch settings')
# shared.log.debug('Verifying Torch settings')
if cuda_ok:
try:
torch.backends.cuda.matmul.allow_tf32 = True
@@ -133,7 +177,7 @@ def set_cuda_params():
torch.backends.cudnn.allow_tf32 = True
except Exception:
pass
global dtype, dtype_vae, dtype_unet, unet_needs_upcast # pylint: disable=global-statement
global dtype, dtype_vae, dtype_unet, unet_needs_upcast, inference_context # pylint: disable=global-statement
if shared.opts.cuda_dtype == 'FP32':
dtype = torch.float32
dtype_vae = torch.float32
@@ -143,13 +187,15 @@ def set_cuda_params():
dtype = torch.bfloat16 if bf16_ok else torch.float16
dtype_vae = torch.bfloat16 if bf16_ok else torch.float16
dtype_unet = torch.bfloat16 if bf16_ok else torch.float16
else:
bf16_ok = False
if shared.opts.cuda_dtype == 'FP16' or dtype == torch.float16:
fp16_ok = test_fp16()
dtype = torch.float16 if fp16_ok else torch.float32
dtype_vae = torch.float16 if fp16_ok else torch.float32
dtype_unet = torch.float16 if fp16_ok else torch.float32
else:
pass
fp16_ok = False
if shared.opts.no_half:
shared.log.info('Torch override dtype: no-half set')
dtype = torch.float32
@@ -159,12 +205,18 @@ def set_cuda_params():
shared.log.info('Torch override VAE dtype: no-half set')
dtype_vae = torch.float32
unet_needs_upcast = shared.opts.upcast_sampling
if shared.opts.inference_mode == 'inference-mode':
inference_context = torch.inference_mode
elif shared.opts.inference_mode == 'none':
inference_context = contextlib.nullcontext
else:
inference_context = torch.no_grad
shared.log.debug(f'Desired Torch parameters: dtype={shared.opts.cuda_dtype} no-half={shared.opts.no_half} no-half-vae={shared.opts.no_half_vae} upscast={shared.opts.upcast_sampling}')
shared.log.info(f'Setting Torch parameters: dtype={dtype} vae={dtype_vae} unet={dtype_unet}')
shared.log.debug(f'Torch default device: {torch.device(get_optimal_device_name())}')
shared.log.info(f'Setting Torch parameters: device={torch.device(get_optimal_device_name())} dtype={dtype} vae={dtype_vae} unet={dtype_unet} context={inference_context.__name__} fp16={fp16_ok} bf16={bf16_ok}')
args = cmd_args.parser.parse_args()
backend = 'not set'
if args.use_ipex or (hasattr(torch, 'xpu') and torch.xpu.is_available()):
backend = 'ipex'
from modules.intel.ipex import ipex_init
@@ -188,6 +240,7 @@ elif sys.platform == 'darwin':
else:
backend = 'cpu'
inference_context = torch.no_grad
cuda_ok = torch.cuda.is_available()
cpu = torch.device("cpu")
device = device_interrogate = device_gfpgan = device_esrgan = device_codeformer = None
+7 -7
View File
@@ -1,7 +1,7 @@
import torch
from tqdm.auto import tqdm
from k_diffusion import sampling
from modules.shared import device
import modules.devices as devices
def dpm_solver_adaptive(self, x, t_start, t_end, order=3, rtol=0.05, atol=0.0078, h_init=0.05, pcoeff=0., icoeff=1., dcoeff=0., accept_safety=0.81, eta=0., s_noise=1., noise_sampler=None):
@@ -12,8 +12,8 @@ def dpm_solver_adaptive(self, x, t_start, t_end, order=3, rtol=0.05, atol=0.0078
if not forward and eta:
raise ValueError('eta must be 0 for reverse sampling')
h_init = abs(h_init) * (1 if forward else -1)
atol = torch.tensor(atol, device=device)
rtol = torch.tensor(rtol, device=device)
atol = torch.tensor(atol, device=devices.device)
rtol = torch.tensor(rtol, device=devices.device)
s = t_start
x_prev = x
accept = True
@@ -58,7 +58,7 @@ def dpm_solver_adaptive(self, x, t_start, t_end, order=3, rtol=0.05, atol=0.0078
return x, info
@torch.no_grad()
@devices.inference_context()
def sample_dpm_fast(model, x, sigma_min, sigma_max, n, extra_args=None, callback=None, disable=None, eta=0., s_noise=1., noise_sampler=None):
"""DPM-Solver-Fast (fixed step size). See https://arxiv.org/abs/2206.00927."""
if sigma_min <= 0 or sigma_max <= 0:
@@ -67,10 +67,10 @@ def sample_dpm_fast(model, x, sigma_min, sigma_max, n, extra_args=None, callback
dpm_solver = sampling.DPMSolver(model, extra_args, eps_callback=pbar.update)
if callback is not None:
dpm_solver.info_callback = lambda info: callback({'sigma': dpm_solver.sigma(info['t']), 'sigma_hat': dpm_solver.sigma(info['t_up']), **info})
return dpm_solver.dpm_solver_fast(x, dpm_solver.t(torch.tensor(sigma_max, device=device)), dpm_solver.t(torch.tensor(sigma_min, device=device)), n, eta, s_noise, noise_sampler)
return dpm_solver.dpm_solver_fast(x, dpm_solver.t(torch.tensor(sigma_max, device=devices.device)), dpm_solver.t(torch.tensor(sigma_min, device=devices.device)), n, eta, s_noise, noise_sampler)
@torch.no_grad()
@devices.inference_context()
def sample_dpm_adaptive(model, x, sigma_min, sigma_max, extra_args=None, callback=None, disable=None, order=3, rtol=0.05, atol=0.0078, h_init=0.05, pcoeff=0., icoeff=1., dcoeff=0., accept_safety=0.81, eta=0., s_noise=1., noise_sampler=None, return_info=False):
"""DPM-Solver-12 and 23 (adaptive step size). See https://arxiv.org/abs/2206.00927."""
if sigma_min <= 0 or sigma_max <= 0:
@@ -79,7 +79,7 @@ def sample_dpm_adaptive(model, x, sigma_min, sigma_max, extra_args=None, callbac
dpm_solver = sampling.DPMSolver(model, extra_args, eps_callback=pbar.update)
if callback is not None:
dpm_solver.info_callback = lambda info: callback({'sigma': dpm_solver.sigma(info['t']), 'sigma_hat': dpm_solver.sigma(info['t_up']), **info})
x, info = dpm_solver.dpm_solver_adaptive(x, dpm_solver.t(torch.tensor(sigma_max, device=device)), dpm_solver.t(torch.tensor(sigma_min, device=device)), order, rtol, atol, h_init, pcoeff, icoeff, dcoeff, accept_safety, eta, s_noise, noise_sampler)
x, info = dpm_solver.dpm_solver_adaptive(x, dpm_solver.t(torch.tensor(sigma_max, device=devices.device)), dpm_solver.t(torch.tensor(sigma_min, device=devices.device)), order, rtol, atol, h_init, pcoeff, icoeff, dcoeff, accept_safety, eta, s_noise, noise_sampler)
if return_info:
return x, info
return x
+2 -3
View File
@@ -1,11 +1,10 @@
import torch
from ldm.models.diffusion.ddim import noise_like
import modules.sd_hijack_inpainting as plms_hijack
import modules.devices as devices
@torch.no_grad()
@devices.inference_context()
def p_sample_plms(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False,
temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None,
unconditional_guidance_scale=1., unconditional_conditioning=None, old_eps=None, t_next=None, dynamic_threshold=None):
-1
View File
@@ -1,6 +1,5 @@
import math
import torch
from realesrgan import RealESRGANer
+3 -2
View File
@@ -1,9 +1,10 @@
import torch
from ldm.models.diffusion.ddim import DDIMSampler
from ldm.modules.diffusionmodules.util import noise_like
import modules.devices as devices
@torch.no_grad()
@devices.inference_context()
def p_sample_ddim(self, x, c, t, index, repeat_noise=False, use_original_steps=False, quantize_denoised=False,
temperature=1., noise_dropout=0., score_corrector=None, corrector_kwargs=None,
unconditional_guidance_scale=1., unconditional_conditioning=None,
+2 -2
View File
@@ -199,9 +199,9 @@ def upscale_without_tiling(model, img):
img = np.ascontiguousarray(np.transpose(img, (2, 0, 1))) / 255
img = torch.from_numpy(img).float()
img = img.unsqueeze(0).to(devices.device_esrgan)
with torch.no_grad():
with devices.inference_context():
output = model(img)
output = output.squeeze().float().cpu().clamp_(0, 1).numpy()
output = output.squeeze().float().cpu().clamp_(0, 1).detach().numpy()
output = 255. * np.moveaxis(output, 0, 2)
output = output.astype(np.uint8)
output = output[:, :, ::-1]
+2 -6
View File
@@ -54,9 +54,7 @@ def to_half(tensor, enable):
def run_modelmerger(id_task, primary_model_name, secondary_model_name, tertiary_model_name, interp_method, multiplier, save_as_half, custom_name, checkpoint_format, config_source, bake_in_vae, discard_weights, save_metadata): # pylint: disable=unused-argument
shared.state.begin()
shared.state.job = 'model-merge'
shared.state.begin('model-merge')
save_as_half = save_as_half == 0
def fail(message):
@@ -321,9 +319,7 @@ def run_modelconvert(model, checkpoint_formats, precision, conv_type, custom_nam
"vae": vae_conv,
"other": others_conv
}
shared.state.begin()
shared.state.job = 'model-convert'
shared.state.begin('model-convert')
model_info = sd_models.checkpoints_list[model]
shared.state.textinfo = f"Loading {model_info.filename}..."
shared.log.info(f"Model convert loading: {model_info.filename}")
+10 -5
View File
@@ -21,12 +21,17 @@ def cache(subsection):
return s
def calculate_sha256(filename):
def calculate_sha256(filename, quiet=False):
hash_sha256 = hashlib.sha256()
blksize = 1024 * 1024
with progress.open(filename, 'rb', description=f'Calculating model hash: [cyan]{filename}', auto_refresh=True) as f:
for chunk in iter(lambda: f.read(blksize), b""):
hash_sha256.update(chunk)
if not quiet:
with progress.open(filename, 'rb', description=f'Calculating model hash: [cyan]{filename}', auto_refresh=True, console=shared.console) as f:
for chunk in iter(lambda: f.read(blksize), b""):
hash_sha256.update(chunk)
else:
with open(filename, 'rb') as f:
for chunk in iter(lambda: f.read(blksize), b""):
hash_sha256.update(chunk)
return hash_sha256.hexdigest()
@@ -52,7 +57,7 @@ def sha256(filename, title, use_addnet_hash=False):
if not os.path.isfile(filename):
return None
if use_addnet_hash:
with progress.open(filename, 'rb', description=f'Calculating model hash: [cyan]{filename}', auto_refresh=True) as f:
with progress.open(filename, 'rb', description=f'Calculating model hash: [cyan]{filename}', auto_refresh=True, console=shared.console) as f:
sha256_value = addnet_hash_safetensors(f)
else:
sha256_value = calculate_sha256(filename)
+11 -58
View File
@@ -1,5 +1,4 @@
import datetime
import glob
import html
import os
from collections import deque
@@ -35,19 +34,14 @@ class HypernetworkModule(torch.nn.Module):
def __init__(self, dim, state_dict=None, layer_structure=None, activation_func=None, weight_init='Normal',
add_layer_norm=False, activate_output=False, dropout_structure=None):
super().__init__()
self.multiplier = 1.0
assert layer_structure is not None, "layer_structure must not be None"
assert layer_structure[0] == 1, "Multiplier Sequence should start with size 1!"
assert layer_structure[-1] == 1, "Multiplier Sequence should end with size 1!"
linears = []
for i in range(len(layer_structure) - 1):
# Add a fully-connected layer
linears.append(torch.nn.Linear(int(dim * layer_structure[i]), int(dim * layer_structure[i+1])))
# Add an activation func except last layer
if activation_func == "linear" or activation_func is None or (i >= len(layer_structure) - 2 and not activate_output):
pass
@@ -55,20 +49,16 @@ class HypernetworkModule(torch.nn.Module):
linears.append(self.activation_dict[activation_func]())
else:
raise RuntimeError(f'hypernetwork uses an unsupported activation function: {activation_func}')
# Add layer normalization
if add_layer_norm:
linears.append(torch.nn.LayerNorm(int(dim * layer_structure[i+1])))
# Everything should be now parsed into dropout structure, and applied here.
# Since we only have dropouts after layers, dropout structure should start with 0 and end with 0.
if dropout_structure is not None and dropout_structure[i+1] > 0:
assert 0 < dropout_structure[i+1] < 1, "Dropout probability should be 0 or float between 0 and 1!"
linears.append(torch.nn.Dropout(p=dropout_structure[i+1]))
# Code explanation : [1, 2, 1] -> dropout is missing when last_layer_dropout is false. [1, 2, 2, 1] -> [0, 0.3, 0, 0], when its True, [0, 0.3, 0.3, 0].
self.linear = torch.nn.Sequential(*linears)
if state_dict is not None:
self.fix_old_state_dict(state_dict)
self.load_state_dict(state_dict)
@@ -102,12 +92,10 @@ class HypernetworkModule(torch.nn.Module):
'linear2.bias': 'linear.1.bias',
'linear2.weight': 'linear.1.weight',
}
for fr, to in changes.items():
x = state_dict.get(fr, None)
if x is None:
continue
del state_dict[fr]
state_dict[to] = x
@@ -162,7 +150,6 @@ class Hypernetwork:
self.optimizer_name = None
self.optimizer_state_dict = None
self.optional_info = None
for size in enable_sizes or []:
self.layers[size] = (
HypernetworkModule(size, None, self.layer_structure, self.activation_func, self.weight_init,
@@ -210,10 +197,8 @@ class Hypernetwork:
def save(self, filename):
state_dict = {}
optimizer_saved_dict = {}
for k, v in self.layers.items():
state_dict[k] = (v[0].state_dict(), v[1].state_dict())
state_dict['step'] = self.step
state_dict['name'] = self.name
state_dict['layer_structure'] = self.layer_structure
@@ -227,10 +212,8 @@ class Hypernetwork:
state_dict['dropout_structure'] = self.dropout_structure
state_dict['last_layer_dropout'] = (self.dropout_structure[-2] != 0) if self.dropout_structure is not None else self.last_layer_dropout
state_dict['optional_info'] = self.optional_info if self.optional_info else None
if self.optimizer_name is not None:
optimizer_saved_dict['optimizer_name'] = self.optimizer_name
torch.save(state_dict, filename)
if shared.opts.save_optimizer_state and self.optimizer_state_dict:
optimizer_saved_dict['hash'] = self.shorthash()
@@ -241,10 +224,8 @@ class Hypernetwork:
self.filename = filename
if self.name is None:
self.name = os.path.splitext(os.path.basename(filename))[0]
with progress.open(filename, 'rb', description=f'Loading hypernetwork: [cyan]{filename}', auto_refresh=True) as f:
with progress.open(filename, 'rb', description=f'Loading hypernetwork: [cyan]{filename}', auto_refresh=True, console=shared.console) as f:
state_dict = torch.load(f, map_location='cpu')
self.layer_structure = state_dict.get('layer_structure', [1, 2, 1])
self.optional_info = state_dict.get('optional_info', None)
self.activation_func = state_dict.get('activation_func', None)
@@ -257,11 +238,9 @@ class Hypernetwork:
# Dropout structure should have same length as layer structure, Every digits should be in [0,1), and last digit must be 0.
if self.dropout_structure is None:
self.dropout_structure = parse_dropout_structure(self.layer_structure, self.use_dropout, self.last_layer_dropout)
if shared.opts.print_hypernet_extra:
if self.optional_info is not None:
print(f" INFO:\n {self.optional_info}\n")
print(f" Layer structure: {self.layer_structure}")
print(f" Activation function: {self.activation_func}")
print(f" Weight initialization: {self.weight_init}")
@@ -269,9 +248,7 @@ class Hypernetwork:
print(f" Dropout usage: {self.use_dropout}" )
print(f" Activate last layer: {self.activate_output}")
print(f" Dropout structure: {self.dropout_structure}")
optimizer_saved_dict = torch.load(self.filename + '.optim', map_location='cpu') if os.path.exists(self.filename + '.optim') else {}
if self.shorthash() == optimizer_saved_dict.get('hash', None):
self.optimizer_state_dict = optimizer_saved_dict.get('optimizer_state_dict', None)
else:
@@ -285,7 +262,6 @@ class Hypernetwork:
self.optimizer_name = "AdamW"
if shared.opts.print_hypernet_extra:
print("No saved optimizer exists in checkpoint")
for size, sd in state_dict.items():
if type(size) == int:
self.layers[size] = (
@@ -294,7 +270,6 @@ class Hypernetwork:
HypernetworkModule(size, sd[1], self.layer_structure, self.activation_func, self.weight_init,
self.add_layer_norm, self.activate_output, self.dropout_structure),
)
self.name = state_dict.get('name', self.name)
self.step = state_dict.get('step', 0)
self.sd_checkpoint = state_dict.get('sd_checkpoint', None)
@@ -303,54 +278,49 @@ class Hypernetwork:
def shorthash(self):
sha256 = hashes.sha256(self.filename, f'hypernet/{self.name}')
return sha256[0:10] if sha256 else None
def list_hypernetworks(path):
res = {}
for filename in sorted(glob.iglob(os.path.join(path, '**/*.pt'), recursive=True), key=str.lower):
name = os.path.splitext(os.path.basename(filename))[0]
# Prevent a hypothetical "None.pt" from being listed.
if name != "None":
res[name] = filename
def list_folder(folder):
for filename in os.listdir(folder):
fn = os.path.join(folder, filename)
if os.path.isfile(fn) and fn.lower().endswith(".pt"):
name = os.path.splitext(os.path.basename(fn))[0]
res[name] = filename
elif os.path.isdir(fn) and not fn.startswith('.'):
list_folder(fn)
list_folder(path)
return res
def load_hypernetwork(name):
path = shared.hypernetworks.get(name, None)
if path is None:
return None
hypernetwork = Hypernetwork()
try:
hypernetwork.load(path)
except Exception as e:
errors.display(e, f'hypernetwork load: {path}')
return None
return hypernetwork
def load_hypernetworks(names, multipliers=None):
already_loaded = {}
for hypernetwork in shared.loaded_hypernetworks:
if hypernetwork.name in names:
already_loaded[hypernetwork.name] = hypernetwork
shared.loaded_hypernetworks.clear()
for i, name in enumerate(names):
hypernetwork = already_loaded.get(name, None)
if hypernetwork is None:
hypernetwork = load_hypernetwork(name)
if hypernetwork is None:
continue
hypernetwork.set_multiplier(multipliers[i] if multipliers else 1.0)
shared.loaded_hypernetworks.append(hypernetwork)
@@ -368,14 +338,11 @@ def find_closest_hypernetwork_name(search: str):
def apply_single_hypernetwork(hypernetwork, context_k, context_v, layer=None):
hypernetwork_layers = (hypernetwork.layers if hypernetwork is not None else {}).get(context_k.shape[2], None)
if hypernetwork_layers is None:
return context_k, context_v
if layer is not None:
layer.hyper_k = hypernetwork_layers[0]
layer.hyper_v = hypernetwork_layers[1]
context_k = devices.cond_cast_unet(hypernetwork_layers[0](devices.cond_cast_float(context_k)))
context_v = devices.cond_cast_unet(hypernetwork_layers[1](devices.cond_cast_float(context_v)))
return context_k, context_v
@@ -386,33 +353,25 @@ def apply_hypernetworks(hypernetworks, context, layer=None):
context_v = context
for hypernetwork in hypernetworks:
context_k, context_v = apply_single_hypernetwork(hypernetwork, context_k, context_v, layer)
return context_k, context_v
def attention_CrossAttention_forward(self, x, context=None, mask=None):
h = self.heads
q = self.to_q(x)
context = default(context, x)
context_k, context_v = apply_hypernetworks(shared.loaded_hypernetworks, context, self)
k = self.to_k(context_k)
v = self.to_v(context_v)
q, k, v = (rearrange(t, 'b n (h d) -> (b h) n d', h=h) for t in (q, k, v))
sim = einsum('b i d, b j d -> b i j', q, k) * self.scale
if mask is not None:
mask = rearrange(mask, 'b ... -> b (...)')
max_neg_value = -torch.finfo(sim.dtype).max
mask = repeat(mask, 'b j -> (b h) () j', h=h)
sim.masked_fill_(~mask, max_neg_value)
# attention, what we cannot get enough of
attn = sim.softmax(dim=-1)
out = einsum('b i j, b j d -> b i d', attn, v)
out = rearrange(out, '(b h) n d -> b n (h d)', h=h)
return self.to_out(out)
@@ -421,7 +380,6 @@ def attention_CrossAttention_forward(self, x, context=None, mask=None):
def stack_conds(conds):
if len(conds) == 1:
return torch.stack(conds)
# same as in reconstruct_multicond_batch
token_count = max([x.shape[0] for x in conds])
for i in range(len(conds)):
@@ -429,7 +387,6 @@ def stack_conds(conds):
last_vector = conds[i][-1:]
last_vector_repeated = last_vector.repeat([token_count - conds[i].shape[0], 1])
conds[i] = torch.vstack([conds[i], last_vector_repeated])
return torch.stack(conds)
@@ -464,19 +421,15 @@ def create_hypernetwork(name, enable_sizes, overwrite_old, layer_structure=None,
# Remove illegal characters from name.
name = "".join( x for x in name if (x.isalnum() or x in "._- "))
assert name, "Name cannot be empty!"
fn = os.path.join(shared.opts.hypernetwork_dir, f"{name}.pt")
if not overwrite_old:
assert not os.path.exists(fn), f"file {fn} already exists"
if type(layer_structure) == str:
layer_structure = [float(x.strip()) for x in layer_structure.split(",")]
if use_dropout and dropout_structure and type(dropout_structure) == str:
dropout_structure = [float(x.strip()) for x in dropout_structure.split(",")]
else:
dropout_structure = [0] * len(layer_structure)
hypernet = modules.hypernetworks.hypernetwork.Hypernetwork(
name=name,
enable_sizes=[int(x) for x in enable_sizes],
+10 -10
View File
@@ -44,13 +44,10 @@ def image_grid(imgs, batch_size=1, rows=None):
rows = shared.opts.n_rows
elif shared.opts.n_rows == 0:
rows = batch_size
elif shared.opts.grid_prevent_empty_spots:
else:
rows = math.floor(math.sqrt(len(imgs)))
while len(imgs) % rows != 0:
rows -= 1
else:
rows = math.sqrt(len(imgs))
rows = round(rows)
if rows > len(imgs):
rows = len(imgs)
cols = math.ceil(len(imgs) / rows)
@@ -204,7 +201,7 @@ def draw_prompt_matrix(im, width, height, all_prompts, margin=0):
return draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin)
def resize_image(resize_mode, im, width, height, upscaler_name=None):
def resize_image(resize_mode, im, width, height, upscaler_name=None, output_type='image'):
"""
Resizes an image with the specified resize_mode, width, and height.
Args:
@@ -264,6 +261,8 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None):
fill_width = width // 2 - src_w // 2
res.paste(resized.resize((fill_width, height), box=(0, 0, 0, height)), box=(0, 0))
res.paste(resized.resize((fill_width, height), box=(resized.width, 0, resized.width, height)), box=(fill_width + src_w, 0))
if output_type == 'np':
return np.array(res)
return res
@@ -292,7 +291,7 @@ def sanitize_filename_part(text, replace_spaces=True):
class FilenameGenerator:
replacements = {
'batch_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.batch_size == 1 else self.p.batch_index + 1,
'batch_number': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p is None or self.p.batch_size == 1 else self.p.batch_index + 1,
'cfg': lambda self: self.p and self.p.cfg_scale,
'clip_skip': lambda self: self.p and self.p.clip_skip,
'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'),
@@ -385,8 +384,6 @@ class FilenameGenerator:
def apply(self, x):
res = ''
if self.p is None:
return res
for m in re_pattern.finditer(x):
text, pattern = m.groups()
if pattern is None:
@@ -512,7 +509,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', i
The base filename which will be applied to `filename pattern`.
seed, prompt, short_filename,
extension (`str`):
Image file extension, default is `png`.
Image file extension, default is `jpg`.
pngsectionname (`str`):
Specify the name of the section which `info` will be saved in.
info (`str` or `PngImagePlugin.iTXt`):
@@ -553,7 +550,10 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='jpg', i
file_decoration = shared.opts.samples_filename_pattern
else:
file_decoration = "[seq]-[prompt_words]"
file_decoration = namegen.apply(file_decoration).strip(' ').strip('-') + suffix
file_decoration = namegen.apply(file_decoration).strip(' ').strip('-')
if len(file_decoration) == 0:
file_decoration = namegen.apply('[seq]').strip(' ').strip('-')
file_decoration += suffix
if shared.opts.save_images_add_number:
if '[seq]' not in file_decoration:
file_decoration = f"[seq]-{file_decoration}"
+1 -1
View File
@@ -83,7 +83,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s
shared.log.warning('Model not loaded')
return [], '', '', 'Error: model not loaded'
shared.log.debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}|args={args}')
shared.log.debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}')
if init_img is None:
shared.log.debug('Init image not set')
+19 -24
View File
@@ -2,22 +2,14 @@ import os
import sys
import contextlib
import torch
import intel_extension_for_pytorch as ipex
from modules import shared
from .diffusers import ipex_diffusers
import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import
from .hijacks import ipex_hijacks
from .attention import attention_init
from .diffusers import ipex_diffusers
#ControlNet depth_leres++
class DummyDataParallel(torch.nn.Module):
def __new__(cls, module, device_ids=None, output_device=None, dim=0):
if type(device_ids) is list and len(device_ids) > 1:
shared.log.warning("IPEX backend doesn't support DataParallel on multiple XPU devices")
return module.to(shared.device)
# pylint: disable=protected-access, missing-function-docstring, line-too-long
def return_null_context(*args, **kwargs):
return contextlib.nullcontext()
def ipex_init():
def ipex_init(): # pylint: disable=too-many-statements
try:
#Replace cuda with xpu:
torch.cuda.current_device = torch.xpu.current_device
@@ -140,10 +132,13 @@ def ipex_init():
torch.cuda.amp.common.amp_definitely_not_available = lambda: False
try:
torch.cuda.amp.GradScaler = torch.xpu.amp.GradScaler
except Exception:
from .gradscaler import gradscaler_init
gradscaler_init()
torch.cuda.amp.GradScaler = torch.xpu.amp.GradScaler
except Exception: # pylint: disable=broad-exception-caught
try:
from .gradscaler import gradscaler_init # pylint: disable=import-outside-toplevel, import-error
gradscaler_init()
torch.cuda.amp.GradScaler = torch.xpu.amp.GradScaler
except Exception: # pylint: disable=broad-exception-caught
torch.cuda.amp.GradScaler = ipex.cpu.autocast._grad_scaler.GradScaler
#C
torch._C._cuda_getCurrentRawStream = ipex._C._getCurrentStream
@@ -152,20 +147,20 @@ def ipex_init():
#Fix functions with ipex:
torch.cuda.mem_get_info = lambda device=None: [(torch.xpu.get_device_properties(device).total_memory - torch.xpu.memory_allocated(device)), torch.xpu.get_device_properties(device).total_memory]
torch._utils._get_available_device_type = lambda: "xpu" # pylint: disable=protected-access
torch._utils._get_available_device_type = lambda: "xpu"
torch.has_cuda = True
torch.cuda.has_half = True
torch.cuda.is_bf16_supported = True
torch.cuda.is_bf16_supported = lambda *args, **kwargs: True
torch.cuda.is_fp16_supported = lambda *args, **kwargs: True
#torch.version.cuda = "11.7" #Breaks System Info
torch.cuda.get_device_capability = lambda: [11,7]
torch.cuda.get_device_capability = lambda *args, **kwargs: [11,7]
torch.cuda.get_device_properties.major = 11
torch.cuda.get_device_properties.minor = 7
torch.backends.cuda.sdp_kernel = return_null_context
torch.nn.DataParallel = DummyDataParallel
torch.cuda.ipc_collect = lambda: None
torch.cuda.utilization = lambda: 0
torch.cuda.ipc_collect = lambda *args, **kwargs: None
torch.cuda.utilization = lambda *args, **kwargs: 0
ipex_hijacks()
attention_init()
ipex_diffusers()
except Exception as e:
return False, e
+128
View File
@@ -0,0 +1,128 @@
import torch
import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import
# pylint: disable=protected-access, missing-function-docstring, line-too-long
original_torch_bmm = torch.bmm
def torch_bmm(input, mat2, *, out=None):
if input.dtype != mat2.dtype:
mat2 = mat2.to(input.dtype)
#ARC GPUs can't allocate more than 4GB to a single block, Slice it:
batch_size_attention, input_tokens, mat2_shape = input.shape[0], input.shape[1], mat2.shape[2]
block_multiply = 2.4 if input.dtype == torch.float32 else 1.2
block_size = (batch_size_attention * input_tokens * mat2_shape) / 1024 * block_multiply #MB
split_slice_size = batch_size_attention
if block_size >= 4000:
do_split = True
#Find something divisible with the input_tokens
while ((split_slice_size * input_tokens * mat2_shape) / 1024 * block_multiply) > 4000:
split_slice_size = split_slice_size // 2
if split_slice_size <= 1:
split_slice_size = 1
break
else:
do_split = False
split_block_size = (split_slice_size * input_tokens * mat2_shape) / 1024 * block_multiply #MB
split_2_slice_size = input_tokens
if split_block_size >= 4000:
do_split_2 = True
#Find something divisible with the input_tokens
while ((split_slice_size * split_2_slice_size * mat2_shape) / 1024 * block_multiply) > 4000:
split_2_slice_size = split_2_slice_size // 2
if split_2_slice_size <= 1:
split_2_slice_size = 1
break
else:
do_split_2 = False
if do_split:
hidden_states = torch.zeros(input.shape[0], input.shape[1], mat2.shape[2], device=input.device, dtype=input.dtype)
for i in range(batch_size_attention // split_slice_size):
start_idx = i * split_slice_size
end_idx = (i + 1) * split_slice_size
if do_split_2:
for i2 in range(input_tokens // split_2_slice_size): # pylint: disable=invalid-name
start_idx_2 = i2 * split_2_slice_size
end_idx_2 = (i2 + 1) * split_2_slice_size
hidden_states[start_idx:end_idx, start_idx_2:end_idx_2] = original_torch_bmm(
input[start_idx:end_idx, start_idx_2:end_idx_2],
mat2[start_idx:end_idx, start_idx_2:end_idx_2],
out=out
)
else:
hidden_states[start_idx:end_idx] = original_torch_bmm(
input[start_idx:end_idx],
mat2[start_idx:end_idx],
out=out
)
else:
return original_torch_bmm(input, mat2, out=out)
return hidden_states
original_scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention
def scaled_dot_product_attention(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False):
#ARC GPUs can't allocate more than 4GB to a single block, Slice it:
shape_one, batch_size_attention, query_tokens, shape_four = query.shape
block_multiply = 2.4 if query.dtype == torch.float32 else 1.2
block_size = (shape_one * batch_size_attention * query_tokens * shape_four) / 1024 * block_multiply #MB
split_slice_size = batch_size_attention
if block_size >= 4000:
do_split = True
#Find something divisible with the shape_one
while ((shape_one * split_slice_size * query_tokens * shape_four) / 1024 * block_multiply) > 4000:
split_slice_size = split_slice_size // 2
if split_slice_size <= 1:
split_slice_size = 1
break
else:
do_split = False
split_block_size = (shape_one * split_slice_size * query_tokens * shape_four) / 1024 * block_multiply #MB
split_2_slice_size = query_tokens
if split_block_size >= 4000:
do_split_2 = True
#Find something divisible with the batch_size_attention
while ((shape_one * split_slice_size * split_2_slice_size * shape_four) / 1024 * block_multiply) > 4000:
split_2_slice_size = split_2_slice_size // 2
if split_2_slice_size <= 1:
split_2_slice_size = 1
break
else:
do_split_2 = False
if do_split:
hidden_states = torch.zeros(query.shape, device=query.device, dtype=query.dtype)
for i in range(batch_size_attention // split_slice_size):
start_idx = i * split_slice_size
end_idx = (i + 1) * split_slice_size
if do_split_2:
for i2 in range(query_tokens // split_2_slice_size): # pylint: disable=invalid-name
start_idx_2 = i2 * split_2_slice_size
end_idx_2 = (i2 + 1) * split_2_slice_size
hidden_states[:, start_idx:end_idx, start_idx_2:end_idx_2] = original_scaled_dot_product_attention(
query[:, start_idx:end_idx, start_idx_2:end_idx_2],
key[:, start_idx:end_idx, start_idx_2:end_idx_2],
value[:, start_idx:end_idx, start_idx_2:end_idx_2],
attn_mask=attn_mask[:, start_idx:end_idx, start_idx_2:end_idx_2] if attn_mask is not None else attn_mask,
dropout_p=dropout_p, is_causal=is_causal
)
else:
hidden_states[:, start_idx:end_idx] = original_scaled_dot_product_attention(
query[:, start_idx:end_idx],
key[:, start_idx:end_idx],
value[:, start_idx:end_idx],
attn_mask=attn_mask[:, start_idx:end_idx] if attn_mask is not None else attn_mask,
dropout_p=dropout_p, is_causal=is_causal
)
else:
return original_scaled_dot_product_attention(
query, key, value, attn_mask=attn_mask, dropout_p=dropout_p, is_causal=is_causal
)
return hidden_states
def attention_init():
#ARC GPUs can't allocate more than 4GB to a single block:
torch.bmm = torch_bmm
torch.nn.functional.scaled_dot_product_attention = scaled_dot_product_attention
+7 -148
View File
@@ -1,11 +1,11 @@
import torch
import intel_extension_for_pytorch as ipex
import torch.nn.functional as F
import diffusers #0.20.2
import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import
import diffusers #0.21.1 # pylint: disable=import-error
from diffusers.models.attention_processor import Attention
Attention = diffusers.models.attention_processor.Attention
# pylint: disable=protected-access, missing-function-docstring, line-too-long
class SlicedAttnProcessor:
class SlicedAttnProcessor: # pylint: disable=too-few-public-methods
r"""
Processor for implementing sliced attention.
@@ -18,7 +18,7 @@ class SlicedAttnProcessor:
def __init__(self, slice_size):
self.slice_size = slice_size
def __call__(self, attn: Attention, hidden_states, encoder_hidden_states=None, attention_mask=None):
def __call__(self, attn: Attention, hidden_states, encoder_hidden_states=None, attention_mask=None): # pylint: disable=too-many-statements, too-many-locals, too-many-branches
residual = hidden_states
input_ndim = hidden_states.ndim
@@ -74,7 +74,7 @@ class SlicedAttnProcessor:
end_idx = (i + 1) * self.slice_size
if do_split_2:
for i2 in range(query_tokens // split_2_slice_size):
for i2 in range(query_tokens // split_2_slice_size): # pylint: disable=invalid-name
start_idx_2 = i2 * split_2_slice_size
end_idx_2 = (i2 + 1) * split_2_slice_size
@@ -114,147 +114,6 @@ class SlicedAttnProcessor:
return hidden_states
class AttnProcessor2_0:
r"""
Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0).
"""
def __init__(self):
if not hasattr(F, "scaled_dot_product_attention"):
raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.")
def __call__(
self,
attn: Attention,
hidden_states,
encoder_hidden_states=None,
attention_mask=None,
temb=None,
):
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
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)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
key = attn.to_k(encoder_hidden_states)
value = attn.to_v(encoder_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)
#ARC GPUs can't allocate more than 4GB to a single block, Slice it:
shape_one, batch_size_attention, query_tokens, shape_four = query.shape
block_multiply = 2.4 if query.dtype == torch.float32 else 1.2
block_size = (shape_one * batch_size_attention * query_tokens * shape_four) / 1024 * block_multiply #MB
split_slice_size = batch_size_attention
if block_size >= 4000:
do_split = True
#Find something divisible with the shape_one
while ((shape_one * split_slice_size * query_tokens * shape_four) / 1024 * block_multiply) > 4000:
split_slice_size = split_slice_size // 2
if split_slice_size <= 1:
split_slice_size = 1
break
else:
do_split = False
split_block_size = (shape_one * split_slice_size * query_tokens * shape_four) / 1024 * block_multiply #MB
split_2_slice_size = query_tokens
if split_block_size >= 4000:
do_split_2 = True
#Find something divisible with the batch_size_attention
while ((shape_one * split_slice_size * split_2_slice_size * shape_four) / 1024 * block_multiply) > 4000:
split_2_slice_size = split_2_slice_size // 2
if split_2_slice_size <= 1:
split_2_slice_size = 1
break
else:
do_split_2 = False
if do_split:
hidden_states = torch.zeros(query.shape, device=query.device, dtype=query.dtype)
for i in range(batch_size_attention // split_slice_size):
start_idx = i * split_slice_size
end_idx = (i + 1) * split_slice_size
if do_split_2:
for i2 in range(query_tokens // split_2_slice_size):
start_idx_2 = i2 * split_2_slice_size
end_idx_2 = (i2 + 1) * split_2_slice_size
query_slice = query[:, start_idx:end_idx, start_idx_2:end_idx_2]
key_slice = key[:, start_idx:end_idx, start_idx_2:end_idx_2]
attn_mask_slice = attention_mask[:, start_idx:end_idx, start_idx_2:end_idx_2] if attention_mask is not None else None
attn_slice = F.scaled_dot_product_attention(
query_slice, key_slice, value[:, start_idx:end_idx, start_idx_2:end_idx_2],
attn_mask=attn_mask_slice, dropout_p=0.0, is_causal=False
)
hidden_states[:, start_idx:end_idx, start_idx_2:end_idx_2] = attn_slice
else:
query_slice = query[:, start_idx:end_idx]
key_slice = key[:, start_idx:end_idx]
attn_mask_slice = attention_mask[:, start_idx:end_idx] if attention_mask is not None else None
attn_slice = F.scaled_dot_product_attention(
query_slice, key_slice, value[:, start_idx:end_idx],
attn_mask=attn_mask_slice, dropout_p=0.0, is_causal=False
)
hidden_states[:, start_idx:end_idx] = attn_slice
else:
hidden_states = F.scaled_dot_product_attention(
query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False
)
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 ipex_diffusers():
#ARC GPUs can't allocate more than 4GB to a single block:
diffusers.models.attention_processor.SlicedAttnProcessor = SlicedAttnProcessor
diffusers.models.attention_processor.AttnProcessor2_0 = AttnProcessor2_0
+9 -9
View File
@@ -1,14 +1,15 @@
import torch
from collections import defaultdict
import intel_extension_for_pytorch as ipex
import intel_extension_for_pytorch._C as core
from modules import shared
import torch
import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import
import intel_extension_for_pytorch._C as core # pylint: disable=import-error, unused-import
# pylint: disable=protected-access, missing-function-docstring, line-too-long
OptState = ipex.cpu.autocast._grad_scaler.OptState
_MultiDeviceReplicator = ipex.cpu.autocast._grad_scaler._MultiDeviceReplicator
_refresh_per_optimizer_state = ipex.cpu.autocast._grad_scaler._refresh_per_optimizer_state
def _unscale_grads_(self, optimizer, inv_scale, found_inf, allow_fp16):
def _unscale_grads_(self, optimizer, inv_scale, found_inf, allow_fp16): # pylint: disable=unused-argument
per_device_inv_scale = _MultiDeviceReplicator(inv_scale)
per_device_found_inf = _MultiDeviceReplicator(found_inf)
@@ -40,7 +41,7 @@ def _unscale_grads_(self, optimizer, inv_scale, found_inf, allow_fp16):
else:
to_unscale = param.grad
# TODO: is there a way to split by device and dtype without appending in the inner loop?
# -: is there a way to split by device and dtype without appending in the inner loop?
to_unscale = to_unscale.to("cpu")
per_device_and_dtype_grads[to_unscale.device][
to_unscale.dtype
@@ -86,7 +87,7 @@ def unscale_(self, optimizer):
optimizer_state = self._per_optimizer_states[id(optimizer)]
if optimizer_state["stage"] is OptState.UNSCALED:
if optimizer_state["stage"] is OptState.UNSCALED: # pylint: disable=no-else-raise
raise RuntimeError(
"unscale_() has already been called on this optimizer since the last update()."
)
@@ -175,5 +176,4 @@ def gradscaler_init():
torch.xpu.amp.GradScaler._unscale_grads_ = _unscale_grads_
torch.xpu.amp.GradScaler.unscale_ = unscale_
torch.xpu.amp.GradScaler.update = update
return torch.xpu.amp.GradScaler
+91 -35
View File
@@ -1,19 +1,63 @@
import contextlib
import torch
import intel_extension_for_pytorch as ipex
from modules import devices
import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import
from modules.sd_hijack_utils import CondFunc
from modules import devices
# pylint: disable=protected-access, missing-function-docstring, line-too-long, unnecessary-lambda, no-else-return
def _shutdown_workers(self):
if torch.utils.data._utils is None or torch.utils.data._utils.python_exit_status is True or torch.utils.data._utils.python_exit_status is None:
return
if hasattr(self, "_shutdown") and not self._shutdown:
self._shutdown = True
try:
if hasattr(self, '_pin_memory_thread'):
self._pin_memory_thread_done_event.set()
self._worker_result_queue.put((None, None))
self._pin_memory_thread.join()
self._worker_result_queue.cancel_join_thread()
self._worker_result_queue.close()
self._workers_done_event.set()
for worker_id in range(len(self._workers)):
if self._persistent_workers or self._workers_status[worker_id]:
self._mark_worker_as_unavailable(worker_id, shutdown=True)
for w in self._workers: # pylint: disable=invalid-name
w.join(timeout=torch.utils.data._utils.MP_STATUS_CHECK_INTERVAL)
for q in self._index_queues: # pylint: disable=invalid-name
q.cancel_join_thread()
q.close()
finally:
if self._worker_pids_set:
torch.utils.data._utils.signal_handling._remove_worker_pids(id(self))
self._worker_pids_set = False
for w in self._workers: # pylint: disable=invalid-name
if w.is_alive():
w.terminate()
class DummyDataParallel(torch.nn.Module): # pylint: disable=missing-class-docstring, unused-argument, too-few-public-methods
def __new__(cls, module, device_ids=None, output_device=None, dim=0): # pylint: disable=unused-argument
if isinstance(device_ids, list) and len(device_ids) > 1:
print("IPEX backend doesn't support DataParallel on multiple XPU devices")
return module.to(devices.device)
def return_null_context(*args, **kwargs): # pylint: disable=unused-argument
return contextlib.nullcontext()
def check_device(device):
return bool((isinstance(device, torch.device) and device.type == "cuda") or (isinstance(device, str) and "cuda" in device) or isinstance(device, int))
def ipex_no_cuda(orig_func, *args, **kwargs): # pylint: disable=redefined-outer-name
def return_xpu(device):
return f"xpu:{device.split(':')[-1]}" if isinstance(device, str) and ":" in device else f"xpu:{device}" if isinstance(device, int) else torch.device(devices.device) if isinstance(device, torch.device) else devices.device
def ipex_no_cuda(orig_func, *args, **kwargs):
torch.cuda.is_available = lambda: False
orig_func(*args, **kwargs)
torch.cuda.is_available = torch.xpu.is_available
original_autocast = torch.autocast
def ipex_autocast(*args, **kwargs):
if args[0] == "cuda" or args[0] == "xpu":
if len(args) > 0 and args[0] == "cuda" or args[0] == "xpu":
if "dtype" in kwargs:
return original_autocast("xpu", *args[1:], **kwargs)
else:
@@ -23,66 +67,75 @@ def ipex_autocast(*args, **kwargs):
#Embedding BF16
original_torch_cat = torch.cat
def torch_cat(input, *args, **kwargs):
if len(input) == 3 and (input[0].dtype != input[1].dtype or input[2].dtype != input[1].dtype):
return original_torch_cat([input[0].to(input[1].dtype), input[1], input[2].to(input[1].dtype)], *args, **kwargs)
def torch_cat(tensor, *args, **kwargs):
if len(tensor) == 3 and (tensor[0].dtype != tensor[1].dtype or tensor[2].dtype != tensor[1].dtype):
return original_torch_cat([tensor[0].to(tensor[1].dtype), tensor[1], tensor[2].to(tensor[1].dtype)], *args, **kwargs)
else:
return original_torch_cat(input, *args, **kwargs)
return original_torch_cat(tensor, *args, **kwargs)
#Latent antialias:
original_interpolate = torch.nn.functional.interpolate
def interpolate(input, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False):
if antialias:
return original_interpolate(input.to("cpu", dtype=torch.float32), size=size, scale_factor=scale_factor, mode=mode,
align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias).to(devices.device, dtype=devices.dtype)
def interpolate(tensor, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False): # pylint: disable=too-many-arguments
if antialias or align_corners is not None:
return_device = tensor.device
return_dtype = tensor.dtype
return original_interpolate(tensor.to("cpu", dtype=torch.float32), size=size, scale_factor=scale_factor, mode=mode,
align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias).to(return_device, dtype=return_dtype)
else:
return original_interpolate(input, size=size, scale_factor=scale_factor, mode=mode,
return original_interpolate(tensor, size=size, scale_factor=scale_factor, mode=mode,
align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias)
original_linalg_solve = torch.linalg.solve
def linalg_solve(A, B, *args, **kwargs): # pylint: disable=invalid-name
if A.device != torch.device("cpu") or B.device != torch.device("cpu"):
return_device = A.device
return original_linalg_solve(A.to("cpu"), B.to("cpu"), *args, **kwargs).to(return_device)
else:
return original_linalg_solve(A, B, *args, **kwargs)
def ipex_hijacks():
CondFunc('torch.Tensor.to',
lambda orig_func, self, device=None, *args, **kwargs: orig_func(self, devices.device, *args, **kwargs),
lambda orig_func, self, device=None, *args, **kwargs: orig_func(self, return_xpu(device), *args, **kwargs),
lambda orig_func, self, device=None, *args, **kwargs: check_device(device))
CondFunc('torch.Tensor.cuda',
lambda orig_func, self, device=None, *args, **kwargs: orig_func(self, devices.device, *args, **kwargs),
lambda orig_func, self, device=None, *args, **kwargs: orig_func(self, return_xpu(device), *args, **kwargs),
lambda orig_func, self, device=None, *args, **kwargs: check_device(device))
CondFunc('torch.empty',
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs),
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs),
lambda orig_func, *args, device=None, **kwargs: check_device(device))
CondFunc('torch.load',
lambda orig_func, *args, map_location=None, **kwargs: orig_func(*args, devices.device, **kwargs),
lambda orig_func, *args, map_location=None, **kwargs: orig_func(*args, return_xpu(map_location), **kwargs),
lambda orig_func, *args, map_location=None, **kwargs: map_location is None or check_device(map_location))
CondFunc('torch.randn',
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs),
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs),
lambda orig_func, *args, device=None, **kwargs: check_device(device))
CondFunc('torch.ones',
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs),
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs),
lambda orig_func, *args, device=None, **kwargs: check_device(device))
CondFunc('torch.zeros',
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs),
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs),
lambda orig_func, *args, device=None, **kwargs: check_device(device))
CondFunc('torch.tensor',
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs),
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs),
lambda orig_func, *args, device=None, **kwargs: check_device(device))
CondFunc('torch.linspace',
lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=return_xpu(device), **kwargs),
lambda orig_func, *args, device=None, **kwargs: check_device(device))
CondFunc('torch.Generator',
lambda orig_func, device: torch.xpu.Generator(device),
lambda orig_func, device: device != torch.device("cpu") and device != "cpu")
#Crashes the GPU:
CondFunc('torch.linalg.solve',
lambda orig_func, A, B, *args, **kwargs: orig_func(A.to("cpu"), B.to("cpu"), *args, **kwargs).to(devices.device),
lambda orig_func, A, B, *args, **kwargs: A.device != torch.device("cpu") or B.device != torch.device("cpu"))
lambda orig_func, device=None: torch.xpu.Generator(device),
lambda orig_func, device=None: device is not None and device != torch.device("cpu") and device != "cpu")
#TiledVAE and ControlNet:
CondFunc('torch.batch_norm',
lambda orig_func, input, weight, bias, *args, **kwargs: orig_func(input,
weight if weight is not None else torch.ones(input.size()[1], device=devices.device),
bias if bias is not None else torch.zeros(input.size()[1], device=devices.device), *args, **kwargs),
weight if weight is not None else torch.ones(input.size()[1], device=input.device),
bias if bias is not None else torch.zeros(input.size()[1], device=input.device), *args, **kwargs),
lambda orig_func, input, *args, **kwargs: input.device != torch.device("cpu"))
CondFunc('torch.instance_norm',
lambda orig_func, input, weight, bias, *args, **kwargs: orig_func(input,
weight if weight is not None else torch.ones(input.size()[1], device=devices.device),
bias if bias is not None else torch.zeros(input.size()[1], device=devices.device), *args, **kwargs),
weight if weight is not None else torch.ones(input.size()[1], device=input.device),
bias if bias is not None else torch.zeros(input.size()[1], device=input.device), *args, **kwargs),
lambda orig_func, input, *args, **kwargs: input.device != torch.device("cpu"))
#Functions with dtype errors:
@@ -94,10 +147,9 @@ def ipex_hijacks():
CondFunc('torch.nn.modules.linear.Linear.forward',
lambda orig_func, self, input: orig_func(self, input.to(self.weight.data.dtype)),
lambda orig_func, self, input: input.dtype != self.weight.data.dtype)
#Embedding FP32:
CondFunc('torch.bmm',
lambda orig_func, input, mat2, *args, **kwargs: orig_func(input, mat2.to(input.dtype), *args, **kwargs),
lambda orig_func, input, mat2, *args, **kwargs: input.dtype != mat2.dtype)
CondFunc('torch.nn.modules.conv.Conv2d.forward',
lambda orig_func, self, input: orig_func(self, input.to(self.weight.data.dtype)),
lambda orig_func, self, input: input.dtype != self.weight.data.dtype)
#BF16:
CondFunc('torch.nn.functional.layer_norm',
lambda orig_func, input, normalized_shape=None, weight=None, *args, **kwargs:
@@ -118,6 +170,10 @@ def ipex_hijacks():
lambda orig_func, *args, **kwargs: True)
#Functions that make compile mad with CondFunc:
torch.utils.data.dataloader._MultiProcessingDataLoaderIter._shutdown_workers = _shutdown_workers
torch.nn.DataParallel = DummyDataParallel
torch.autocast = ipex_autocast
torch.cat = torch_cat
torch.linalg.solve = linalg_solve
torch.nn.functional.interpolate = interpolate
torch.backends.cuda.sdp_kernel = return_null_context
+2 -2
View File
@@ -8,7 +8,7 @@ from torch._dynamo.backends.registry import register_backend
from torch.fx.experimental.proxy_tensor import make_fx
from torch._inductor.compile_fx import compile_fx
from hashlib import sha256
from modules import shared
from modules import shared, devices
@register_backend
@fake_tensor_unsupported
@@ -89,7 +89,7 @@ def openvino_fx(subgraph, example_inputs):
else:
example_inputs.reverse()
model = make_fx(subgraph)(*example_inputs)
with torch.no_grad():
with devices.inference_context():
model.eval()
partitioner = Partitioner()
compiled_model = partitioner.make_partitions(model)
+3 -4
View File
@@ -174,15 +174,14 @@ class InterrogateModels:
transforms.Normalize((0.48145466, 0.4578275, 0.40821073), (0.26862954, 0.26130258, 0.27577711))
])(pil_image).unsqueeze(0).type(self.dtype).to(devices.device_interrogate)
with torch.no_grad():
with devices.inference_context():
caption = self.blip_model.generate(gpu_image, sample=False, num_beams=shared.opts.interrogate_clip_num_beams, min_length=shared.opts.interrogate_clip_min_length, max_length=shared.opts.interrogate_clip_max_length)
return caption[0]
def interrogate(self, pil_image):
res = ""
shared.state.begin()
shared.state.job = 'interrogate'
shared.state.begin('interrogate')
try:
if shared.cmd_opts.lowvram or shared.cmd_opts.medvram:
lowvram.send_everything_to_cpu()
@@ -198,7 +197,7 @@ class InterrogateModels:
clip_image = self.clip_preprocess(pil_image).unsqueeze(0).type(self.dtype).to(devices.device_interrogate)
with torch.no_grad(), devices.autocast():
with devices.inference_context(), devices.autocast():
image_features = self.clip_model.encode_image(clip_image).type(self.dtype)
image_features /= image_features.norm(dim=-1, keepdim=True)
+1 -3
View File
@@ -8,7 +8,6 @@ from modules import timer, errors
initialized = False
logging.getLogger("DeepSpeed").disabled = True
import torch # pylint: disable=C0411
errors.log.debug(f'Loaded Torch=={torch.__version__}')
try:
import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import
errors.log.debug(f'Loaded IPEX=={ipex.__version__}')
@@ -29,10 +28,9 @@ timer.startup.record("torch")
from fastapi import FastAPI # pylint: disable=W0611,C0411
import gradio # pylint: disable=W0611,C0411
errors.log.debug(f'Loaded Gradio=={gradio.__version__}')
timer.startup.record("gradio")
errors.install([gradio])
import diffusers # pylint: disable=W0611,C0411
errors.log.debug(f'Loaded Diffusers=={diffusers.__version__}')
timer.startup.record("diffusers")
errors.log.debug(f'Loaded packages: torch={torch.__version__} diffusers={diffusers.__version__} gradio={gradio.__version__}')
+63 -27
View File
@@ -4,6 +4,7 @@ import shutil
import importlib
from typing import Dict
from urllib.parse import urlparse
import PIL.Image as Image
from modules import shared
from modules.upscaler import Upscaler, UpscalerLanczos, UpscalerNearest, UpscalerNone
from modules.paths import script_path, models_path
@@ -59,17 +60,17 @@ def download_civit_preview(model_path: str, preview_url: str):
import rich.progress as p
_, ext = os.path.splitext(preview_url)
model_name, _ = os.path.splitext(os.path.basename(model_path))
preview_file = os.path.splitext(model_path)[0] + ext
preview_file = f'{os.path.splitext(model_path)[0]}{ext}' if '.safetensors' in model_path.lower() else f'{model_path}{ext}'
res = f'CivitAI download: name={model_name} url={preview_url}'
req = requests.get(preview_url, stream=True, timeout=30)
total_size = int(req.headers.get('content-length', 0))
block_size = 16384 # 16KB blocks
written = 0
shared.state.begin()
shared.state.job = 'download preview'
img = None
shared.state.begin('civitai-download-preview')
try:
with open(preview_file, 'wb') as f:
with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn()) as progress:
with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn(), console=shared.console) as progress:
task = progress.add_task(description="Download starting", total=total_size)
for data in req.iter_content(block_size):
written = written + len(data)
@@ -78,13 +79,14 @@ def download_civit_preview(model_path: str, preview_url: str):
if written < 1024: # min threshold
os.remove(preview_file)
raise ValueError(f'removed invalid download: bytes={written}')
img = Image.open(preview_file)
except Exception as e:
shared.log.error(f'CivitAI download error: name={model_name} url={preview_url} {e}')
if total_size == written:
shared.log.info(f'{res} size={total_size}')
else:
shared.log.error(f'{res} size={total_size} written={written}')
shared.state.end()
if img is None:
return res
shared.log.info(f'{res} size={total_size} image={img.size}')
img.close()
return res
@@ -105,18 +107,17 @@ def download_civit_model(model_url: str, model_name: str, model_path: str, model
total_size = int(req.headers.get('content-length', 0))
block_size = 16384 # 16KB blocks
written = 0
shared.state.begin()
shared.state.job = 'download model'
shared.state.begin('civitai-download-model')
try:
with open(model_file, 'wb') as f:
with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn()) as progress:
with p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn(), console=shared.console) as progress:
task = progress.add_task(description="Download starting", total=total_size)
# for data in tqdm(req.iter_content(block_size), total=total_size//1024, unit='KB', unit_scale=False):
for data in req.iter_content(block_size):
written = written + len(data)
f.write(data)
progress.update(task, advance=block_size, description="Downloading")
if written < 1024 * 1024 * 1024: # min threshold
if written < 1024 * 1024: # min threshold
os.remove(model_file)
raise ValueError(f'removed invalid download: bytes={written}')
if preview is not None:
@@ -136,8 +137,7 @@ def download_civit_model(model_url: str, model_name: str, model_path: str, model
def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config: Dict[str, str] = None, token = None, variant = None, revision = None, mirror = None):
from diffusers import DiffusionPipeline
import huggingface_hub as hf
shared.state.begin()
shared.state.job = 'download model'
shared.state.begin('huggingface-download-model')
if download_config is None:
download_config = {
"force_download": False,
@@ -157,26 +157,28 @@ def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config
if token is not None and len(token) > 2:
shared.log.debug(f"Diffusers authentication: {token}")
hf.login(token)
pipeline_dir = DiffusionPipeline.download(hub_id, **download_config)
pipeline_dir = None
try:
model_info_dict = hf.model_info(hub_id).cardData # pylint: disable=no-member # TODO Diffusers is this real error?
pipeline_dir = DiffusionPipeline.download(hub_id, **download_config)
except Exception as e:
shared.log.error(f"Diffusers download error: {hub_id} {e}")
try:
model_info_dict = hf.model_info(hub_id).cardData if pipeline_dir is not None else None # pylint: disable=no-member # TODO Diffusers is this real error?
except Exception:
model_info_dict = None
# some checkpoints need to be downloaded as "hidden" as they just serve as pre- or post-pipelines of other pipelines
if model_info_dict is not None and "prior" in model_info_dict:
if model_info_dict is not None and "prior" in model_info_dict: # some checkpoints need to be downloaded as "hidden" as they just serve as pre- or post-pipelines of other pipelines
download_dir = DiffusionPipeline.download(model_info_dict["prior"][0], **download_config)
model_info_dict["prior"] = download_dir
# mark prior as hidden
with open(os.path.join(download_dir, "hidden"), "w", encoding="utf-8") as f:
with open(os.path.join(download_dir, "hidden"), "w", encoding="utf-8") as f: # mark prior as hidden
f.write("True")
shared.writefile(model_info_dict, os.path.join(pipeline_dir, "model_info.json"))
if pipeline_dir is not None:
shared.writefile(model_info_dict, os.path.join(pipeline_dir, "model_info.json"))
shared.state.end()
return pipeline_dir
def load_diffusers_models(model_path: str, command_path: str = None):
t0 = time.time()
import huggingface_hub as hf
places = []
places.append(model_path)
if command_path is not None and command_path != model_path:
@@ -187,12 +189,35 @@ def load_diffusers_models(model_path: str, command_path: str = None):
if not os.path.isdir(place):
continue
try:
"""
import huggingface_hub as hf
res = hf.scan_cache_dir(cache_dir=place)
for r in list(res.repos):
cache_path = os.path.join(r.repo_path, "snapshots", list(r.revisions)[-1].commit_hash)
diffuser_repos.append({ 'name': r.repo_id, 'filename': r.repo_id, 'path': cache_path, 'size': r.size_on_disk, 'mtime': r.last_modified, 'hash': list(r.revisions)[-1].commit_hash, 'model_info': str(os.path.join(cache_path, "model_info.json")) })
if not os.path.isfile(os.path.join(cache_path, "hidden")):
output.append(str(r.repo_id))
"""
for folder in os.listdir(place):
try:
if "--" not in folder:
continue
_, name = folder.split("--", maxsplit=1)
name = name.replace("--", "/")
snapshots = os.listdir(os.path.join(place, folder, "snapshots"))
if len(snapshots) == 0:
shared.log.warning(f"Diffusers folder has no snapshots: location={place} folder={folder} name={name}")
continue
commit = snapshots[-1]
folder = os.path.join(place, folder, 'snapshots', commit)
mtime = os.path.getmtime(folder)
info = os.path.join(folder, "model_info.json")
diffuser_repos.append({ 'name': name, 'filename': name, 'path': folder, 'hash': commit, 'mtime': mtime, 'model_info': info })
if os.path.exists(os.path.join(folder, 'hidden')):
continue
output.append(name)
except Exception as e:
shared.log.error(f"Error analyzing diffusers model: {place}/{folder} {e}")
except Exception as e:
shared.log.error(f"Error listing diffusers: {place} {e}")
shared.log.debug(f'Scanning diffusers cache: {model_path} {command_path} items={len(output)} time={time.time()-t0:.2f}s')
@@ -300,6 +325,19 @@ def extension_filter(ext_filter=None, ext_blacklist=None):
return (not ext_filter or any(fp.upper().endswith(ew) for ew in ext_filter)) and (not ext_blacklist or not any(fp.upper().endswith(ew) for ew in ext_blacklist))
return filter
def load_file_from_url(url: str, *, model_dir: str, progress: bool = True, file_name = None):
"""Download a file from url into model_dir, using the file present if possible. Returns the path to the downloaded file."""
os.makedirs(model_dir, exist_ok=True)
if not file_name:
parts = urlparse(url)
file_name = os.path.basename(parts.path)
cached_file = os.path.abspath(os.path.join(model_dir, file_name))
if not os.path.exists(cached_file):
shared.log.info(f'Downloading: url="{url}" file={cached_file}')
from torch.hub import download_url_to_file
download_url_to_file(url, cached_file, progress=progress)
return cached_file
def load_models(model_path: str, model_url: str = None, command_path: str = None, ext_filter=None, download_name=None, ext_blacklist=None) -> list:
"""
@@ -317,8 +355,7 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None
output:list = [*filter(extension_filter(ext_filter, ext_blacklist), directory_files(*places))]
if model_url is not None and len(output) == 0:
if download_name is not None:
from basicsr.utils.download_util import load_file_from_url
dl = load_file_from_url(model_url, places[0], True, download_name)
dl = load_file_from_url(model_url, model_dir=places[0], progress=True, file_name=download_name)
output.append(dl)
else:
output.append(model_url)
@@ -388,7 +425,6 @@ def move_files(src_path: str, dest_path: str, ext_filter: str = None):
pass
def load_upscalers():
# We can only do this 'magic' method to dynamically load upscalers if they are referenced, so we'll try to import any _model.py files before looking in __subclasses__
modules_dir = os.path.join(shared.script_path, "modules")
@@ -418,6 +454,6 @@ def load_upscalers():
datas += scaler.scalers
shared.sd_upscalers = sorted(
datas,
# Special case for UpscalerNone keeps it at the beginning of the list.
key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else ""
key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else "" # Special case for UpscalerNone keeps it at the beginning of the list.
)
shared.log.debug(f"Loaded upscalers: items={len(shared.sd_upscalers)}")
+2 -2
View File
@@ -21,7 +21,7 @@ class UniPCSampler(object):
# persist steps so we can eventually find denoising strength
self.inflated_steps = ddim_num_steps
@torch.no_grad()
@devices.inference_context()
def stochastic_encode(self, x0, t, use_original_steps=False, noise=None):
if noise is None:
noise = torch.randn_like(x0)
@@ -119,7 +119,7 @@ class UniPCSampler(object):
self.after_sample = after_sample
self.after_update = after_update
@torch.no_grad()
@devices.inference_context()
def sample(self,
S,
batch_size,
+3 -3
View File
@@ -3,7 +3,7 @@ import torch.nn.functional as F
import math
import time
from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn, TimeElapsedColumn
from modules import shared
from modules import shared, devices
class NoiseScheduleVP:
@@ -757,10 +757,10 @@ class UniPC:
#print(f"Running UniPC Sampling with {timesteps.shape[0]} timesteps, order {order}")
assert steps >= order, "UniPC order must be < sampling steps"
assert timesteps.shape[0] - 1 == steps
with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn()) as progress:
with Progress(TextColumn('[cyan]{task.description}'), BarColumn(), TaskProgressColumn(), TimeRemainingColumn(), TimeElapsedColumn(), console=shared.console) as progress:
task = progress.add_task(description="Initializing", total=steps)
t = time.time()
with torch.no_grad():
with devices.inference_context():
vec_t = timesteps[0].expand((x.shape[0]))
model_prev_list = [self.model_fn(x, vec_t)]
t_prev_list = [vec_t]
+11 -6
View File
@@ -62,13 +62,18 @@ def create_paths(opts, log=None):
def fix_path(folder):
tgt = opts.data.get(folder, None) or opts.data_labels[folder].default
if tgt is None or tgt == '':
return
if os.path.isabs(tgt) or (len(data_path) > 0 and tgt.startswith(data_path)) and not tgt.startswith(script_path):
return
return tgt
if len(data_path) > 0 and tgt.startswith(data_path): # path is already relative to data_path
return tgt
fullpath = os.path.join(data_path, tgt)
relpath = os.path.relpath(fullpath, script_path)
opts.data[folder] = relpath
return
if len(data_path) > 0 and os.path.isabs(data_path):
return fullpath
try:
relpath = os.path.relpath(fullpath, script_path)
opts.data[folder] = relpath
except:
opts.data[folder] = fullpath
return opts.data[folder]
create_path(data_path)
create_path(script_path)
+1 -1
View File
@@ -18,4 +18,4 @@ cmd_opts_pre = parser_pre.parse_known_args()[0]
data_path = cmd_opts_pre.data_dir
models_path = cmd_opts_pre.models_dir if os.path.isabs(cmd_opts_pre.models_dir) else os.path.join(data_path, cmd_opts_pre.models_dir)
extensions_dir = os.path.join(data_path, "extensions")
extensions_builtin_dir = os.path.join(script_path, "extensions-builtin")
extensions_builtin_dir = "extensions-builtin"
+1 -2
View File
@@ -10,8 +10,7 @@ from modules.shared import opts
def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemporaryFile], input_dir, output_dir, show_extras_results, *args, save_output: bool = True):
devices.torch_gc()
shared.state.begin()
shared.state.job = 'extras'
shared.state.begin('extras')
image_data = []
image_names = []
image_ext = []
+43 -38
View File
@@ -158,6 +158,7 @@ class StableDiffusionProcessing:
self.clip_skip = clip_skip
self.iteration = 0
self.is_hr_pass = False
self.hr_force = False
self.enable_hr = None
self.refiner_steps = 5
self.refiner_start = 0
@@ -211,7 +212,6 @@ class StableDiffusionProcessing:
conditioning_mask = np.array(image_mask.convert("L"))
conditioning_mask = conditioning_mask.astype(np.float32) / 255.0
conditioning_mask = torch.from_numpy(conditioning_mask[None, None])
# Inpainting model uses a discretized mask as input, so we round to either 1.0 or 0.0
conditioning_mask = torch.round(conditioning_mask)
else:
@@ -488,11 +488,11 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su
"Backend": 'Diffusers' if shared.backend == shared.Backend.DIFFUSERS else 'Original',
"Version": git_commit,
"Comment": comment,
"Operations": ', '.join(list(set(p.ops))).replace('"', '') if len(p.ops) > 0 else None,
"Operations": '; '.join(p.ops).replace('"', '') if len(p.ops) > 0 else 'none',
}
if 'txt2img' in p.ops:
pass
if 'hires' in p.ops:
if 'hires' in p.ops or 'upscale' in p.ops:
args["Hires steps"] = p.hr_second_pass_steps
args["Hires upscaler"] = p.hr_upscaler
args["Hires upscale"] = p.hr_scale
@@ -707,7 +707,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
return ''
ema_scope_context = p.sd_model.ema_scope if shared.backend == shared.Backend.ORIGINAL else nullcontext
with torch.no_grad(), ema_scope_context():
with devices.inference_context(), ema_scope_context():
t0 = time.time()
with devices.autocast():
p.init(p.all_prompts, p.all_seeds, p.all_subseeds)
@@ -800,8 +800,13 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
for i, x_sample in enumerate(x_samples_ddim):
p.batch_index = i
x_sample = 255. * (np.moveaxis(x_sample.cpu().numpy(), 0, 2) if shared.backend == shared.Backend.ORIGINAL else x_sample)
x_sample = validate_sample(x_sample)
if type(x_sample) == Image.Image:
image = x_sample
x_sample = np.array(x_sample)
else:
x_sample = 255. * (np.moveaxis(x_sample.cpu().numpy(), 0, 2) if shared.backend == shared.Backend.ORIGINAL else x_sample)
x_sample = validate_sample(x_sample)
image = Image.fromarray(x_sample)
if p.restore_faces:
if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_face_restoration:
orig = p.restore_faces
@@ -811,7 +816,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
images.save_image(Image.fromarray(x_sample), path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix="-before-face-restoration")
p.ops.append('face')
x_sample = modules.face_restoration.restore_faces(x_sample)
image = Image.fromarray(x_sample)
image = Image.fromarray(x_sample)
if p.scripts is not None:
pp = modules.scripts.PostprocessImageArgs(image)
p.scripts.postprocess_image(p, pp)
@@ -853,8 +858,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
p.color_corrections = None
index_of_first_image = 0
unwanted_grid_because_of_img_count = len(output_images) < 2 and shared.opts.grid_only_if_multiple
if (shared.opts.return_grid or shared.opts.grid_save) and not p.do_not_save_grid and not unwanted_grid_because_of_img_count:
if (shared.opts.return_grid or shared.opts.grid_save) and not p.do_not_save_grid and len(output_images) > 1:
if images.check_grid_size(output_images):
grid = images.image_grid(output_images, p.batch_size)
if shared.opts.return_grid:
@@ -895,15 +899,15 @@ def old_hires_fix_first_pass_dimensions(width, height):
class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
sampler = None
def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, firstphase_width: int = 0, firstphase_height: int = 0, hr_scale: float = 2.0, hr_upscaler: str = None, hr_second_pass_steps: int = 0, hr_resize_x: int = 0, hr_resize_y: int = 0, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs):
def __init__(self, enable_hr: bool = False, denoising_strength: float = 0.75, firstphase_width: int = 0, firstphase_height: int = 0, hr_scale: float = 2.0, hr_force: bool = False, hr_upscaler: str = None, hr_second_pass_steps: int = 0, hr_resize_x: int = 0, hr_resize_y: int = 0, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs):
super().__init__(**kwargs)
self.enable_hr = enable_hr
self.denoising_strength = denoising_strength
self.hr_scale = hr_scale
self.hr_upscaler = hr_upscaler
self.hr_force = hr_force
self.hr_second_pass_steps = hr_second_pass_steps
self.hr_resize_x = hr_resize_x
self.hr_resize_y = hr_resize_y
@@ -921,11 +925,11 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
self.refiner_start = refiner_start
self.refiner_prompt = refiner_prompt
self.refiner_negative = refiner_negative
self.sampler = None
def init(self, all_prompts, all_seeds, all_subseeds):
if shared.backend == shared.Backend.DIFFUSERS:
modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.TEXT_2_IMAGE)
self.width = self.width or 512
self.height = self.height or 512
@@ -985,13 +989,14 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
if shared.backend == shared.Backend.DIFFUSERS:
modules.sd_models.set_diffuser_pipe(self.sd_model, modules.sd_models.DiffusersTaskType.TEXT_2_IMAGE)
latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None")
if self.enable_hr and (latent_scale_mode is None or self.hr_force):
if len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) == 0:
shared.log.warning(f"Cannot find upscaler for hires: {self.hr_upscaler}")
self.enable_hr = False
self.ops.append('txt2img')
self.sampler = modules.sd_samplers.create_sampler(self.sampler_name, self.sd_model)
latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None")
if self.enable_hr and latent_scale_mode is None:
if len([x for x in shared.sd_upscalers if x.name == self.hr_upscaler]) == 0:
shared.log.warning("Could not find upscaler to use with hrfix")
self.enable_hr = False
x = create_random_tensors([4, self.height // 8, self.width // 8], seeds=seeds, subseeds=subseeds, subseed_strength=self.subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self)
samples = self.sampler.sample(self, x, conditioning, unconditional_conditioning, image_conditioning=self.txt2img_image_conditioning(x))
if not self.enable_hr or shared.state.interrupted or shared.state.skipped:
@@ -1002,16 +1007,9 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
self.ops.append('hires')
target_width = self.hr_upscale_to_x
target_height = self.hr_upscale_to_y
if latent_scale_mode is not None:
for i in range(samples.shape[0]):
save_intermediate(samples, i)
samples = torch.nn.functional.interpolate(samples, size=(target_height // 8, target_width // 8), mode=latent_scale_mode["mode"], antialias=latent_scale_mode["antialias"])
if getattr(self, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) < 1.0:
image_conditioning = self.img2img_image_conditioning(decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae)), samples)
else:
image_conditioning = self.txt2img_image_conditioning(samples.to(dtype=devices.dtype_vae))
else:
for i in range(samples.shape[0]):
save_intermediate(samples, i)
if latent_scale_mode is None or self.hr_force: # non-latent upscaling
decoded_samples = decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae))
lowres_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0)
batch_images = []
@@ -1036,24 +1034,30 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
else:
samples = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(decoded_samples))
image_conditioning = self.img2img_image_conditioning(decoded_samples, samples)
shared.state.nextjob()
if self.latent_sampler == "PLMS":
self.latent_sampler = 'UniPC'
self.sampler = modules.sd_samplers.create_sampler(self.latent_sampler or self.sampler_name, self.sd_model)
samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2]
noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=subseed_strength, p=self)
else:
samples = torch.nn.functional.interpolate(samples, size=(target_height // 8, target_width // 8), mode=latent_scale_mode["mode"], antialias=latent_scale_mode["antialias"])
if getattr(self, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) < 1.0:
image_conditioning = self.img2img_image_conditioning(decode_first_stage(self.sd_model, samples.to(dtype=devices.dtype_vae)), samples)
else:
image_conditioning = self.txt2img_image_conditioning(samples.to(dtype=devices.dtype_vae))
if self.latent_sampler == "PLMS":
self.latent_sampler = 'UniPC'
if self.hr_force or latent_scale_mode is not None:
devices.torch_gc() # GC now before running the next img2img to prevent running out of memory
self.sampler = modules.sd_samplers.create_sampler(self.latent_sampler or self.sampler_name, self.sd_model)
samples = samples[:, :, self.truncate_y//2:samples.shape[2]-(self.truncate_y+1)//2, self.truncate_x//2:samples.shape[3]-(self.truncate_x+1)//2]
noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=subseed_strength, p=self)
modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True))
samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning)
modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio())
x = None
devices.torch_gc() # GC now before running the next img2img to prevent running out of memory
modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True))
samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.hr_second_pass_steps or self.steps, image_conditioning=image_conditioning)
modules.sd_models.apply_token_merging(self.sd_model, self.get_token_merging_ratio())
shared.state.nextjob()
self.is_hr_pass = False
return samples
class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
sampler = None
def __init__(self, init_images: list = None, resize_mode: int = 0, denoising_strength: float = 0.3, image_cfg_scale: float = None, mask: Any = None, mask_blur: int = 4, inpainting_fill: int = 0, inpaint_full_res: bool = True, inpaint_full_res_padding: int = 0, inpainting_mask_invert: int = 0, initial_noise_multiplier: float = None, refiner_steps: int = 5, refiner_start: float = 0, refiner_prompt: str = '', refiner_negative: str = '', **kwargs):
super().__init__(**kwargs)
@@ -1081,6 +1085,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
self.enable_hr = None
self.is_batch = False
self.scale_by = 1.0
self.sampler = None
def init(self, all_prompts, all_seeds, all_subseeds):
if shared.backend == shared.Backend.DIFFUSERS and self.image_mask is None:
+90 -35
View File
@@ -2,6 +2,7 @@ import time
import inspect
import typing
import torch
import torchvision.transforms.functional as TF
import modules.devices as devices
import modules.shared as shared
import modules.sd_samplers as sd_samplers
@@ -24,20 +25,24 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
results = []
if p.enable_hr and p.hr_upscaler != 'None' and p.denoising_strength > 0 and len(getattr(p, 'init_images', [])) == 0:
p.is_hr_pass = True
is_refiner_enabled = p.enable_hr and p.refiner_steps > 0 and shared.sd_refiner is not None
is_refiner_enabled = p.enable_hr and p.refiner_steps > 0 and p.refiner_start > 0 and p.refiner_start < 1 and shared.sd_refiner is not None
def hires_resize(latents): # input=latents output=pil
latent_upscaler = shared.latent_upscale_modes.get(p.hr_upscaler, None)
shared.log.info(f'Hires: upscaler={p.hr_upscaler} width={p.hr_upscale_to_x} height={p.hr_upscale_to_y} images={latents.shape[0]}')
if latent_upscaler is not None:
latents = torch.nn.functional.interpolate(latents, size=(p.hr_upscale_to_y // 8, p.hr_upscale_to_x // 8), mode=latent_upscaler["mode"], antialias=latent_upscaler["antialias"])
first_pass_images = vae_decode(latents=latents, model=shared.sd_model, full_quality=True, output_type='pil')
first_pass_images = vae_decode(latents=latents, model=shared.sd_model, full_quality=p.full_quality, output_type='pil')
p.init_images = []
for first_pass_image in first_pass_images:
init_image = images.resize_image(1, first_pass_image, p.hr_upscale_to_x, p.hr_upscale_to_y, upscaler_name=p.hr_upscaler) if latent_upscaler is None else first_pass_image
if latent_upscaler is None:
init_image = images.resize_image(1, first_pass_image, p.hr_upscale_to_x, p.hr_upscale_to_y, upscaler_name=p.hr_upscaler)
else:
init_image = first_pass_image
# if is_refiner_enabled:
# init_image = vae_encode(init_image, model=shared.sd_model, full_quality=p.full_quality)
p.init_images.append(init_image)
p.width = p.hr_upscale_to_x
p.height = p.hr_upscale_to_y
return p.init_images
def save_intermediate(latents, suffix):
for i in range(len(latents)):
@@ -63,7 +68,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
time.sleep(0.1)
def full_vae_decode(latents, model):
shared.log.debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)} images={latents.shape[0]}')
t0 = time.time()
if shared.opts.diffusers_move_unet and not model.has_accelerate:
shared.log.debug('Moving to CPU: model=UNet')
unet_device = model.unet.device
@@ -72,18 +77,47 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
if not shared.cmd_opts.lowvram and not shared.opts.diffusers_seq_cpu_offload:
model.vae.to(devices.device)
latents.to(model.vae.device)
needs_upcasting = model.vae.dtype == torch.float16 and model.vae.config.force_upcast
if needs_upcasting: # this is done by diffusers automatically if output_type != 'latent'
model.upcast_vae()
latents = latents.to(next(iter(model.vae.post_quant_conv.parameters())).dtype)
decoded = model.vae.decode(latents / model.vae.config.scaling_factor, return_dict=False)[0]
if shared.opts.diffusers_move_unet and not model.has_accelerate:
model.unet.to(unet_device)
t1 = time.time()
shared.log.debug(f'VAE decode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)} images={latents.shape[0]} latents={latents.shape} time={round(t1-t0, 3)}s')
return decoded
def full_vae_encode(image, model):
shared.log.debug(f'VAE encode: name={sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "baked"} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)}')
if shared.opts.diffusers_move_unet and not model.has_accelerate:
shared.log.debug('Moving to CPU: model=UNet')
unet_device = model.unet.device
model.unet.to(devices.cpu)
devices.torch_gc()
if not shared.cmd_opts.lowvram and not shared.opts.diffusers_seq_cpu_offload:
model.vae.to(devices.device)
encoded = model.vae.encode(image.to(model.vae.device, model.vae.dtype))
if shared.opts.diffusers_move_unet and not model.has_accelerate:
model.unet.to(unet_device)
return encoded
def taesd_vae_decode(latents):
shared.log.debug(f'VAE decode: name=TAESD images={latents.shape[0]}')
decoded = torch.zeros((len(latents), 3, p.height, p.width), dtype=devices.dtype_vae, device=devices.device)
shared.log.debug(f'VAE decode: name=TAESD images={len(latents)} latents={latents.shape}')
if len(latents) == 0:
return []
decoded = torch.zeros((len(latents), 3, latents.shape[2] * 8, latents.shape[3] * 8), dtype=devices.dtype_vae, device=devices.device)
for i in range(len(output.images)):
decoded[i] = (sd_vae_taesd.decode(latents[i]) * 2.0) - 1.0
return decoded
def taesd_vae_encode(image):
shared.log.debug(f'VAE encode: name=TAESD image={image.shape}')
encoded = sd_vae_taesd.encode(image)
return encoded
def vae_decode(latents, model, output_type='np', full_quality=True):
if not torch.is_tensor(latents): # already decoded
return latents
@@ -104,6 +138,19 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
imgs = model.image_processor.postprocess(decoded, output_type=output_type)
return imgs
def vae_encode(image, model, full_quality=True): # pylint: disable=unused-variable
if shared.state.interrupted or shared.state.skipped:
return []
if not hasattr(model, 'vae'):
shared.log.error('VAE not found in model')
return []
tensor = TF.to_tensor(image.convert("RGB")).unsqueeze(0).to(devices.device, devices.dtype_vae)
if full_quality:
latents = full_vae_encode(image=tensor, model=shared.sd_model)
else:
latents = taesd_vae_encode(image=tensor)
return latents
def fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2):
if type(prompts) is str:
prompts = [prompts]
@@ -129,7 +176,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
except Exception:
is_refiner = False
if hasattr(model, "set_progress_bar_config"):
model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} '+desc, ncols=80, colour='#327fba')
model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + desc, ncols=80, colour='#327fba')
args = {}
signature = inspect.signature(type(model).__call__)
possible = signature.parameters.keys()
@@ -140,7 +187,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
negative_embed = None
negative_pooled = None
prompts, negative_prompts, prompts_2, negative_prompts_2 = fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2)
if shared.opts.prompt_attention in {'Compel parser', 'Full parser'}:
if shared.opts.prompt_attention in {'Compel parser', 'Full parser'} and 'StableDiffusion' in model.__class__.__name__:
prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompts(model, prompts, negative_prompts, prompts_2, negative_prompts_2, is_refiner, kwargs.pop("clip_skip", None))
if 'prompt' in possible:
if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None:
@@ -315,35 +362,38 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
return results
# optional hires pass
latent_scale_mode = shared.latent_upscale_modes.get(p.hr_upscaler, None) if (hasattr(p, "hr_upscaler") and p.hr_upscaler is not None) else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "None")
if p.is_hr_pass:
p.init_hr()
recompile_model(hires=True)
if p.width != p.hr_upscale_to_x or p.height != p.hr_upscale_to_y:
p.ops.append('upscale')
if shared.opts.save and not p.do_not_save_samples and shared.opts.save_images_before_highres_fix and hasattr(shared.sd_model, 'vae'):
save_intermediate(latents=output.images, suffix="-before-hires")
hires_resize(latents=output.images)
sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE)
p.ops.append('hires')
hires_args = set_pipeline_args(
model=shared.sd_model,
prompts=prompts,
negative_prompts=negative_prompts,
prompts_2=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts,
negative_prompts_2=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts,
num_inference_steps=int(p.hr_second_pass_steps // p.denoising_strength + 1),
eta=shared.opts.eta_ddim,
guidance_scale=p.image_cfg_scale if p.image_cfg_scale is not None else p.cfg_scale,
guidance_rescale=p.diffusers_guidance_rescale,
output_type='latent' if hasattr(shared.sd_model, 'vae') else 'np',
clip_skip=p.clip_skip,
image=p.init_images,
strength=p.denoising_strength,
desc='Hires',
)
try:
output = shared.sd_model(**hires_args) # pylint: disable=not-callable
except AssertionError as e:
shared.log.info(e)
output.images = hires_resize(latents=output.images)
if latent_scale_mode is not None or p.hr_force:
p.ops.append('hires')
recompile_model(hires=True)
sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE)
hires_args = set_pipeline_args(
model=shared.sd_model,
prompts=prompts,
negative_prompts=negative_prompts,
prompts_2=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts,
negative_prompts_2=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts,
num_inference_steps=int(p.hr_second_pass_steps // p.denoising_strength + 1),
eta=shared.opts.eta_ddim,
guidance_scale=p.image_cfg_scale if p.image_cfg_scale is not None else p.cfg_scale,
guidance_rescale=p.diffusers_guidance_rescale,
output_type='latent' if hasattr(shared.sd_model, 'vae') else 'np',
clip_skip=p.clip_skip,
image=p.init_images,
strength=p.denoising_strength,
desc='Hires',
)
try:
output = shared.sd_model(**hires_args) # pylint: disable=not-callable
except AssertionError as e:
shared.log.info(e)
# optional refiner pass or decode
if is_refiner_enabled:
@@ -368,6 +418,11 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
refiner_is_sdxl = bool("StableDiffusionXL" in shared.sd_refiner.__class__.__name__)
p.ops.append('refine')
for i in range(len(output.images)):
image = output.images[i]
# if (image.shape[2] == 3) and (image.shape[0] % 8 != 0 or image.shape[1] % 8 != 0):
# shared.log.warning(f'Refiner requires image size to be divisible by 8: {image.shape}')
# results.append(image)
# return results
refiner_args = set_pipeline_args(
model=shared.sd_refiner,
prompts=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts[i],
@@ -379,7 +434,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro
guidance_rescale=p.diffusers_guidance_rescale,
denoising_start=p.refiner_start if p.refiner_start > 0 and p.refiner_start < 1 else None,
denoising_end=1 if p.refiner_start > 0 and p.refiner_start < 1 else None,
image=output.images[i],
image=image,
output_type='latent' if hasattr(shared.sd_refiner, 'vae') else 'np',
clip_skip=p.clip_skip,
desc='Refiner',
+2 -1
View File
@@ -62,7 +62,8 @@ class DiffusersTextualInversionManager(BaseTextualInversionManager):
replacement += f" {token}_{i}"
i += 1
prompt = prompt.replace(token, replacement)
self.pipe.embedding_db.embeddings_used = list(set(self.pipe.embedding_db.embeddings_used))
if hasattr(self.pipe, 'embedding_db'):
self.pipe.embedding_db.embeddings_used = list(set(self.pipe.embedding_db.embeddings_used))
return prompt
def expand_textual_inversion_token_ids_if_necessary(self, token_ids: typing.List[int]) -> typing.List[int]:
+19
View File
@@ -98,6 +98,7 @@ callback_map = dict(
callbacks_ui_settings=[],
callbacks_before_image_saved=[],
callbacks_image_saved=[],
callbacks_image_save_btn=[],
callbacks_cfg_denoiser=[],
callbacks_cfg_denoised=[],
callbacks_cfg_after_cfg=[],
@@ -205,6 +206,16 @@ def image_saved_callback(params: ImageSaveParams):
report_exception(e, c, 'image_saved_callback')
def image_save_btn_callback(filename: str):
for c in callback_map['callbacks_image_save_btn']:
try:
t0 = time.time()
c.callback(filename)
timer(t0, c.script, 'image_save_btn')
except Exception as e:
report_exception(e, c, 'image_save_btn_callback')
def cfg_denoiser_callback(params: CFGDenoiserParams):
for c in callback_map['callbacks_cfg_denoiser']:
try:
@@ -376,6 +387,14 @@ def on_image_saved(callback):
add_callback(callback_map['callbacks_image_saved'], callback)
def on_image_save_btn(callback):
"""register a function to be called after an image save button is pressed.
The callback is called with one argument:
- params: ImageSaveParams - parameters the image was saved with. Changing fields in this object does nothing.
"""
add_callback(callback_map['callbacks_image_save_btn'], callback)
def on_cfg_denoiser(callback):
"""register a function to be called in the kdiffussion cfg_denoiser method after building the inner model inputs.
The callback is called with one argument:
+10 -2
View File
@@ -1,6 +1,9 @@
import io
import os
import contextlib
import importlib.util
import modules.errors as errors
from installer import setup_logging
preloaded = []
@@ -10,13 +13,18 @@ def load_module(path):
module_spec = importlib.util.spec_from_file_location(os.path.basename(path), path)
module = importlib.util.module_from_spec(module_spec)
try:
module_spec.loader.exec_module(module)
# stdout = io.StringIO()
with contextlib.redirect_stdout(io.StringIO()) as stdout:
module_spec.loader.exec_module(module)
setup_logging() # reset since scripts can hijaack logging
for line in stdout.getvalue().splitlines():
if len(line) > 0:
errors.log.info(f"Extension: script='{os.path.relpath(path)}' {line.strip()}")
except Exception as e:
errors.display(e, f'Module load: {path}')
return module
def preload_extensions(extensions_dir, parser):
if not os.path.isdir(extensions_dir):
return
+15 -15
View File
@@ -343,7 +343,7 @@ class ScriptRunner:
def setup_ui(self):
import modules.api.models as api_models
self.titles = [wrap_call(script.title, script.filename, "title") or f"{script.filename} [error]" for script in self.selectable_scripts]
inputs = [None]
inputs = []
inputs_alwayson = [True]
def create_script_ui(script, inputs, inputs_alwayson):
@@ -377,38 +377,28 @@ class ScriptRunner:
inputs_alwayson += [script.alwayson for _ in controls]
script.args_to = len(inputs)
with gr.Group(elem_id='scripts_alwayson_img2img' if self.is_img2img else 'scripts_alwayson_txt2img'):
for script in self.alwayson_scripts:
t0 = time.time()
elem_id = f'script_{"txt2img" if script.is_txt2img else "img2img"}_{script.title().lower().replace(" ", "_")}'
with gr.Group(elem_id=elem_id) as group:
create_script_ui(script, inputs, inputs_alwayson)
script.group = group
time_setup[script.title()] = time_setup.get(script.title(), 0) + (time.time()-t0)
dropdown = gr.Dropdown(label="Script", elem_id="script_list", choices=["None"] + self.titles, value="None", type="index")
inputs[0] = dropdown
inputs.insert(0, dropdown)
for script in self.selectable_scripts:
with gr.Group(visible=False) as group:
t0 = time.time()
create_script_ui(script, inputs, inputs_alwayson)
time_setup[script.title()] = time_setup.get(script.title(), 0) + (time.time()-t0)
script.group = group
script.group = group
def select_script(script_index):
selected_script = self.selectable_scripts[script_index - 1] if script_index > 0 else None
return [gr.update(visible=selected_script == s) for s in self.selectable_scripts]
def init_field(title):
"""called when an initial value is set from ui-config.json to show script's UI components"""
if title == 'None':
if title == 'None': # called when an initial value is set from ui-config.json to show script's UI components
return
script_index = self.titles.index(title)
self.selectable_scripts[script_index].group.visible = True
dropdown.init_field = init_field
dropdown.change(fn=select_script, inputs=[dropdown], outputs=[script.group for script in self.selectable_scripts])
def onload_script_visibility(params):
title = params.get('Script', None)
if title:
@@ -419,6 +409,16 @@ class ScriptRunner:
else:
return gr.update(visible=False)
# with gr.Group(elem_id='scripts_alwayson_img2img' if self.is_img2img else 'scripts_alwayson_txt2img'):
with gr.Accordion(label="Extensions", elem_id='scripts_alwayson_img2img' if self.is_img2img else 'scripts_alwayson_txt2img'):
for script in self.alwayson_scripts:
t0 = time.time()
elem_id = f'script_{"txt2img" if script.is_txt2img else "img2img"}_{script.title().lower().replace(" ", "_")}'
with gr.Group(elem_id=elem_id) as group:
create_script_ui(script, inputs, inputs_alwayson)
script.group = group
time_setup[script.title()] = time_setup.get(script.title(), 0) + (time.time()-t0)
self.infotext_fields.append( (dropdown, lambda x: gr.update(value=x.get('Script', 'None'))) )
self.infotext_fields.extend( [(script.group, onload_script_visibility) for script in self.selectable_scripts] )
return inputs
+1 -1
View File
@@ -179,7 +179,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
used_embeddings[embedding.name] = embedding
z = self.process_tokens(tokens, multipliers)
zs.append(z)
self.hijack.embedding_db.embeddings_used = [name for name, embedding in used_embeddings.items()]
self.hijack.embedding_db.embeddings_used = list(used_embeddings)
return torch.hstack(zs)
def process_tokens(self, remade_batch_tokens, batch_multipliers):
+7 -4
View File
@@ -1,8 +1,11 @@
import io
import contextlib
import torch
import ldm.models.diffusion.ddpm
import ldm.models.diffusion.ddim
import ldm.models.diffusion.plms
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
import ldm.models.diffusion.ddpm
import ldm.models.diffusion.ddim
import ldm.models.diffusion.plms
from ldm.models.diffusion.ddpm import LatentDiffusion # pylint: disable=unused-import
from ldm.models.diffusion.plms import PLMSSampler # pylint: disable=unused-import
+87 -31
View File
@@ -1,5 +1,3 @@
import collections
import os.path
import re
import io
import sys
@@ -7,6 +5,9 @@ import json
import time
import logging
import threading
import contextlib
import collections
import os.path
from os import mkdir
from urllib import request
from enum import Enum
@@ -130,7 +131,7 @@ class NoWatermark:
def setup_model():
if not os.path.exists(model_path):
os.makedirs(model_path)
os.makedirs(model_path, exist_ok=True)
list_models()
enable_midas_autodownload()
@@ -256,7 +257,7 @@ def select_checkpoint(op='model'):
if checkpoint_info is not None:
shared.log.debug(f'Select checkpoint: {op} {checkpoint_info.title if checkpoint_info is not None else None}')
return checkpoint_info
if len(checkpoints_list) == 0:
if len(checkpoints_list) == 0 and not shared.cmd_opts.no_download:
shared.log.error("Cannot run without a checkpoint")
shared.log.error("Use --ckpt <path-to-checkpoint> to force using existing checkpoint")
return None
@@ -309,6 +310,20 @@ def write_metadata():
sd_metadata_pending = 0
def scrub_dict(dict_obj, keys):
for key in list(dict_obj.keys()):
if not isinstance(dict_obj, dict):
continue
elif key in keys:
dict_obj.pop(key, None)
elif isinstance(dict_obj[key], dict):
scrub_dict(dict_obj[key], keys)
elif isinstance(dict_obj[key], list):
for item in dict_obj[key]:
scrub_dict(item, keys)
return
def read_metadata_from_safetensors(filename):
global sd_metadata # pylint: disable=global-statement
if sd_metadata is None:
@@ -320,6 +335,8 @@ def read_metadata_from_safetensors(filename):
if res is not None:
return res
res = {}
if shared.cmd_opts.no_metadata:
return {}
try:
t0 = time.time()
with open(filename, mode="rb") as file:
@@ -331,15 +348,29 @@ def read_metadata_from_safetensors(filename):
json_data = json_start + file.read(metadata_len-2)
json_obj = json.loads(json_data)
for k, v in json_obj.get("__metadata__", {}).items():
if v.startswith("data:"):
v = 'data'
if k == 'format' and v == 'pt':
continue
if isinstance(v, str) and v[0:1] == '{':
large = True if len(v) > 2048 else False
if large and k == 'ss_datasets':
continue
if large and k == 'workflow':
continue
if large and k == 'prompt':
continue
if large and k == 'ss_bucket_info':
continue
if v[0:1] == '{':
try:
res[k] = json.loads(v)
v = json.loads(v)
if large and k == 'ss_tag_frequency':
v = { i: len(j) for i, j in v.items() }
if large and k == 'sd_merge_models':
scrub_dict(v, ['sd_merge_recipe'])
except Exception:
pass
else:
res[k] = v
res[k] = v
sd_metadata[filename] = res
global sd_metadata_pending # pylint: disable=global-statement
sd_metadata_pending += 1
@@ -356,7 +387,7 @@ def read_state_dict(checkpoint_file, map_location=None): # pylint: disable=unuse
return None
try:
pl_sd = None
with progress.open(checkpoint_file, 'rb', description=f'[cyan]Loading weights: [yellow]{checkpoint_file}', auto_refresh=True) as f:
with progress.open(checkpoint_file, 'rb', description=f'[cyan]Loading weights: [yellow]{checkpoint_file}', auto_refresh=True, console=shared.console) as f:
_, extension = os.path.splitext(checkpoint_file)
if extension.lower() == ".ckpt" and shared.opts.sd_disable_ckpt:
shared.log.warning(f"Checkpoint loading disabled: {checkpoint_file}")
@@ -688,13 +719,19 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
if vae is not None:
diffusers_load_config["vae"] = vae
shared.log.info(f'Loading diffuser {op}: {checkpoint_info.filename}')
# shared.log.info(f'Loading diffuser {op}: {checkpoint_info.filename}')
if not os.path.isfile(checkpoint_info.path):
try:
# os.environ.setdefault('HUGGINGFACE_HUB_CACHE', shared.opts.diffusers_dir) # evalulated only on initial diffusers load
# diffusers_load_config["cache_dir "] = shared.opts.diffusers_dir # ignored for connected pipelines such as kandinsky-prior
# diffusers.utils.constants.DIFFUSERS_CACHE = shared.opts.diffusers_dir
# shared.log.debug(f'Diffusers load {op} config: {diffusers_load_config}')
sd_model = diffusers.DiffusionPipeline.from_pretrained(checkpoint_info.path, **diffusers_load_config)
# sd_model = diffusers.DiffusionPipeline.from_pretrained(checkpoint_info.path, **diffusers_load_config)
sd_model = diffusers.AutoPipelineForText2Image.from_pretrained(checkpoint_info.path, cache_dir=shared.opts.diffusers_dir, **diffusers_load_config)
sd_model.model_type = sd_model.__class__.__name__
except Exception as e:
shared.log.error(f'Failed loading model {op}: {checkpoint_info.path} {e}')
shared.log.error(f'Failed loading {op}: {checkpoint_info.path} {e}')
return
else:
diffusers_load_config["local_files_only "] = True
diffusers_load_config["extract_ema"] = shared.opts.diffusers_extract_ema
@@ -705,7 +742,13 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
try:
if model_type.startswith('Stable Diffusion'):
diffusers_load_config['force_zeros_for_empty_prompt '] = shared.opts.diffusers_force_zeros
diffusers_load_config['requires_aesthetics_score '] = shared.opts.diffusers_aesthetics_score
diffusers_load_config['requires_aesthetics_score'] = shared.opts.diffusers_aesthetics_score
diffusers_load_config['config_files'] = {
'v1': 'configs/v1-inference.yaml',
'v2': 'configs/v2-inference-768-v.yaml',
'xl': 'configs/sd_xl_base.yaml',
'xl_refiner': 'configs/sd_xl_refiner.yaml',
}
if hasattr(pipeline, 'from_single_file'):
diffusers_load_config['use_safetensors'] = True
sd_model = pipeline.from_single_file(checkpoint_info.path, **diffusers_load_config)
@@ -786,6 +829,9 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No
else:
sd_model.vae.config["force_upcast"] = False
sd_model.vae.config.force_upcast = False
if shared.opts.no_half_vae:
devices.dtype_vae = torch.float32
sd_model.vae.to(devices.dtype_vae)
shared.log.debug(f'Model {op} VAE: name={sd_vae.loaded_vae_file} upcast={sd_model.vae.config.get("force_upcast", None)}')
if shared.opts.cross_attention_optimization == "xFormers" and hasattr(sd_model, 'enable_xformers_memory_efficient_attention'):
sd_model.enable_xformers_memory_efficient_attention()
@@ -990,13 +1036,17 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None, timer=None,
timer.record("config")
shared.log.debug(f'Model config loaded: {memory_stats()}')
sd_model = None
# shared.log.debug(f'Model config: {sd_config.model.get("params", dict())}')
try:
clip_is_included_into_sd = sd1_clip_weight in state_dict or sd2_clip_weight in state_dict
with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd):
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
try:
clip_is_included_into_sd = sd1_clip_weight in state_dict or sd2_clip_weight in state_dict
with sd_disable_initialization.DisableInitialization(disable_clip=clip_is_included_into_sd):
sd_model = instantiate_from_config(sd_config.model)
except Exception:
sd_model = instantiate_from_config(sd_config.model)
except Exception:
sd_model = instantiate_from_config(sd_config.model)
for line in stdout.getvalue().splitlines():
if len(line) > 0:
shared.log.info(f'LDM: {line.strip()}')
shared.log.debug(f"Model created from config: {checkpoint_config}")
sd_model.used_config = checkpoint_config
timer.record("create")
@@ -1141,16 +1191,22 @@ def apply_token_merging(sd_model, token_merging_ratio=0):
current_token_merging_ratio = getattr(sd_model, 'applied_token_merged_ratio', 0)
if token_merging_ratio is None or current_token_merging_ratio is None or current_token_merging_ratio == token_merging_ratio:
return
if current_token_merging_ratio > 0:
tomesd.remove_patch(sd_model)
try:
if current_token_merging_ratio > 0:
tomesd.remove_patch(sd_model)
except Exception:
pass
if token_merging_ratio > 0:
shared.log.debug(f'Applying token merging: ratio={token_merging_ratio}')
tomesd.apply_patch(
sd_model,
ratio=token_merging_ratio,
use_rand=False, # can cause issues with some samplers
merge_attn=True,
merge_crossattn=False,
merge_mlp=False
)
sd_model.applied_token_merged_ratio = token_merging_ratio
try:
tomesd.apply_patch(
sd_model,
ratio=token_merging_ratio,
use_rand=False, # can cause issues with some samplers
merge_attn=True,
merge_crossattn=False,
merge_mlp=False
)
shared.log.debug(f'Applying token merging: ratio={token_merging_ratio}')
sd_model.applied_token_merged_ratio = token_merging_ratio
except:
shared.log.warning(f'Token merging not supported: pipeline={sd_model.__class__.__name__}')
+2 -5
View File
@@ -2,7 +2,7 @@ import os
import torch
from modules import paths, sd_disable_initialization
from modules import paths, sd_disable_initialization, devices
sd_repo_configs_path = os.path.join(paths.paths['Stable Diffusion'], "configs", "stable-diffusion")
config_default = paths.sd_default_config
@@ -21,12 +21,9 @@ def is_using_v_parameterization_for_sd2(state_dict):
"""
Detects whether unet in state_dict is using v-parameterization. Returns True if it is. You're welcome.
"""
import ldm.modules.diffusionmodules.openaimodel
from modules import devices
device = devices.cpu
with sd_disable_initialization.DisableInitialization():
unet = ldm.modules.diffusionmodules.openaimodel.UNetModel(
use_checkpoint=True,
@@ -47,7 +44,7 @@ def is_using_v_parameterization_for_sd2(state_dict):
)
unet.eval()
with torch.no_grad():
with devices.inference_context():
unet_sd = {k.replace("model.diffusion_model.", ""): v for k, v in state_dict.items() if "model.diffusion_model." in k}
unet.load_state_dict(unet_sd, strict=True)
unet.to(device=device, dtype=torch.float)
+1 -1
View File
@@ -23,7 +23,7 @@ def list_samplers(backend_name = shared.backend):
samplers = all_samplers
samplers_for_img2img = all_samplers
samplers_map = {}
shared.log.debug(f'Available samplers: {[x.name for x in all_samplers]}')
# shared.log.debug(f'Available samplers: {[x.name for x in all_samplers]}')
def find_sampler_config(name):
+10
View File
@@ -142,6 +142,16 @@ class CFGDenoiser(torch.nn.Module):
else:
cond_in = torch.cat([tensor, uncond])
"""
adjusted_cond_scale = cond_scale # Adjusted cond_scale for uncond
last_uncond_steps = max(0, state.sampling_steps - 2) # Determine the last two steps before uncond stops
if self.step >= last_uncond_steps: # Check if we're in the last two steps before uncond stops
adjusted_cond_scale *= 1.5 # Apply uncond with 150% cond_scale
else:
if (self.step - last_uncond_steps) % 3 == 0: # Check if it's one of every three steps after uncond stops
adjusted_cond_scale *= 1.5 # Apply uncond with 150% cond_scale
"""
if shared.batch_cond_uncond:
x_out = self.inner_model(x_in, sigma_in, cond=make_condition_dict([cond_in], image_cond_in))
else:
+2
View File
@@ -232,6 +232,8 @@ def reload_vae_weights(sd_model=None, vae_file=unspecified):
from modules import lowvram, sd_hijack
if not sd_model:
sd_model = shared.sd_model
if sd_model is None:
return
global checkpoint_info # pylint: disable=global-statement
checkpoint_info = sd_model.sd_checkpoint_info
checkpoint_file = checkpoint_info.filename
+110 -88
View File
@@ -1,14 +1,17 @@
import io
import os
import sys
import time
import json
import datetime
import contextlib
import urllib.request
from urllib.parse import urlparse
from enum import Enum
import gradio as gr
import tqdm
import fasteners
from rich.console import Console
from modules import errors, ui_components, shared_items, cmd_args
from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # pylint: disable=W0611
from modules.dml import memory_providers, default_memory_provider, directml_do_hijack
@@ -17,6 +20,7 @@ import modules.memmon
import modules.styles
import modules.devices as devices # pylint: disable=R0402
import modules.paths_internal as paths
from installer import print_dict
from installer import log as central_logger # pylint: disable=E0611
@@ -66,7 +70,7 @@ restricted_opts = {
"outdir_init_images"
}
compatibility_opts = ['clip_skip', 'uni_pc_lower_order_final', 'uni_pc_order']
console = Console(log_time=True, log_time_format='%H:%M:%S-%f')
def is_url(string):
parsed_url = urlparse(string)
@@ -91,6 +95,7 @@ class State:
job = ""
job_no = 0
job_count = 0
total_jobs = 0
processing_has_refined_job_count = False
job_timestamp = '0'
sampling_step = 0
@@ -137,19 +142,21 @@ class State:
}
return obj
def begin(self):
self.sampling_step = 0
self.job_count = -1
self.processing_has_refined_job_count = False
self.job_no = 0
self.job_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
self.current_latent = None
def begin(self, title=""):
self.total_jobs += 1
self.current_image = None
self.current_image_sampling_step = 0
self.current_latent = None
self.id_live_preview = 0
self.skipped = False
self.interrupted = False
self.job = title
self.job_count = -1
self.job_no = 0
self.job_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
self.paused = False
self.processing_has_refined_job_count = False
self.sampling_step = 0
self.skipped = False
self.textinfo = None
self.time_start = time.time()
devices.torch_gc()
@@ -157,7 +164,10 @@ class State:
def end(self):
self.job = ""
self.job_count = 0
self.job_no = 0
self.paused = False
self.interrupted = False
self.skipped = False
devices.torch_gc()
def set_current_image(self):
@@ -175,8 +185,9 @@ class State:
image = modules.sd_samplers.samples_to_image_grid(self.current_latent) if opts.show_progress_grid else modules.sd_samplers.sample_to_image(self.current_latent)
self.assign_current_image(image)
self.current_image_sampling_step = self.sampling_step
except Exception as e:
log.error(f'Error setting current image: step={self.sampling_step} {e}')
except Exception:
# log.error(f'Error setting current image: step={self.sampling_step} {e}')
pass
def assign_current_image(self, image):
self.current_image = image
@@ -194,7 +205,7 @@ else:
class OptionInfo:
def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, submit=None, comment_before='', comment_after=''):
def __init__(self, default=None, label="", component=None, component_args=None, onchange=None, section=None, refresh=None, folder=None, submit=None, comment_before='', comment_after=''):
self.default = default
self.label = label
self.component = component
@@ -202,6 +213,7 @@ class OptionInfo:
self.onchange = onchange
self.section = section
self.refresh = refresh
self.folder = folder
self.comment_before = comment_before # HTML text that will be added after label in UI
self.comment_after = comment_after # HTML text that will be added before label in UI
self.submit = submit
@@ -244,19 +256,38 @@ def refresh_checkpoints():
import modules.sd_models # pylint: disable=W0621
return modules.sd_models.list_models()
def refresh_vaes():
import modules.sd_vae # pylint: disable=W0621
modules.sd_vae.refresh_vae_list()
def list_samplers():
import modules.sd_samplers # pylint: disable=W0621
modules.sd_samplers.set_samplers()
return modules.sd_samplers.all_samplers
def temp_disable_extensions():
disabled = []
if backend == Backend.DIFFUSERS:
for ext in ['sd-webui-controlnet', 'multidiffusion-upscaler-for-automatic1111', 'a1111-sd-webui-lycoris']:
if ext not in opts.disabled_extensions:
disabled.append(ext)
log.info(f'Diffusers disabling uncompatible extensions: {disabled}')
if opts.lyco_patch_lora and backend != Backend.DIFFUSERS:
cmd_opts.lyco_dir = opts.lora_dir
if 'Lora' not in opts.disabled_extensions:
disabled.append('Lora')
cmd_opts.controlnet_loglevel = 'WARNING'
return disabled
def list_builtin_themes():
files = [os.path.splitext(f)[0] for f in os.listdir('javascript') if f.endswith('.css')]
return files
def list_themes():
fn = os.path.join('html', 'themes.json')
if not os.path.exists(fn):
@@ -266,25 +297,14 @@ def list_themes():
res = json.loads(f.read())
else:
res = []
list_builtin_themes()
builtin = list_builtin_themes() + ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"]
themes = sorted(builtin) + sorted({x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()}, key=str.casefold)
builtin = list_builtin_themes()
default = ["gradio/default", "gradio/base", "gradio/glass", "gradio/monochrome", "gradio/soft"]
external = {x['id'] for x in res if x['status'] == 'RUNNING' and 'test' not in x['id'].lower()}
log.info(f'Themes: builtin={len(builtin)} default={len(default)} external={len(external)}')
themes = sorted(builtin) + sorted(default) + sorted(external, key=str.casefold)
return themes
def temp_disable_extensions():
disabled = []
if backend == Backend.DIFFUSERS:
for ext in ['sd-webui-controlnet', 'multidiffusion-upscaler-for-automatic1111', 'a1111-sd-webui-lycoris']:
if ext not in opts.disabled_extensions:
disabled.append(ext)
log.warning(f'Diffusers disabling uncompatible extensions: {disabled}')
if opts.lyco_patch_lora and backend != Backend.DIFFUSERS:
if 'Lora' not in opts.disabled_extensions:
disabled.append('Lora')
return disabled
def refresh_themes():
import requests
try:
@@ -292,8 +312,8 @@ def refresh_themes():
if req.status_code == 200:
res = req.json()
fn = os.path.join('html', 'themes.json')
with open(fn, mode='w', encoding='utf=8') as f:
f.write(json.dumps(res))
writefile(res, fn)
list_themes()
else:
log.error('Error refreshing UI themes')
except Exception:
@@ -339,7 +359,7 @@ if devices.backend == "cpu":
elif devices.backend == "mps":
cross_attention_optimization_default = "Doggettx's"
elif devices.backend == "ipex":
cross_attention_optimization_default = "Sub-quadratic"
cross_attention_optimization_default = "Scaled-Dot-Product"
elif devices.backend == "directml":
cross_attention_optimization_default = "Sub-quadratic"
elif devices.backend == "rocm":
@@ -347,6 +367,7 @@ elif devices.backend == "rocm":
else: # cuda
cross_attention_optimization_default ="Scaled-Dot-Product"
options_templates.update(options_section(('sd', "Execution & Models"), {
"sd_backend": OptionInfo("diffusers" if cmd_opts.use_openvino else "original", "Execution backend", gr.Radio, lambda: {"choices": ["original", "diffusers"] }),
"sd_checkpoint_autoload": OptionInfo(True, "Model autoload on server start"),
@@ -355,7 +376,7 @@ options_templates.update(options_section(('sd', "Execution & Models"), {
"sd_checkpoint_cache": OptionInfo(0, "Number of cached models", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
"sd_vae_checkpoint_cache": OptionInfo(0, "Number of cached VAEs", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}),
"sd_vae": OptionInfo("Automatic", "VAE model", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list),
"sd_model_dict": OptionInfo('None', "Use dict from model", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_tiles()}, refresh=refresh_checkpoints),
"sd_model_dict": OptionInfo('None', "Use baseline data from a different model", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_tiles()}, refresh=refresh_checkpoints),
"stream_load": OptionInfo(False, "Load models using stream loading method"),
"model_reuse_dict": OptionInfo(False, "When loading models attempt to reuse previous model dictionary"),
"prompt_attention": OptionInfo("Full parser", "Prompt attention parser", gr.Radio, lambda: {"choices": ["Full parser", "Compel parser", "A1111 parser", "Fixed attention"] }),
@@ -373,6 +394,7 @@ options_templates.update(options_section(('optimizations', "Optimizations"), {
"token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}),
"token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}),
"token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for hires pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}),
"inference_mode": OptionInfo("no-grad", "Torch inference mode", gr.Radio, lambda: {"choices": ["no-grad", "inference-mode", "none"]}),
"sd_vae_sliced_encode": OptionInfo(False, "VAE Slicing (original)"),
}))
@@ -389,8 +411,9 @@ options_templates.update(options_section(('cuda', "Compute Settings"), {
"rollback_vae": OptionInfo(False, "Attempt VAE roll back when produced NaN values (experimental)"),
"opt_channelslast": OptionInfo(False, "Use channels last as torch memory format "),
"cudnn_benchmark": OptionInfo(False, "Enable full-depth cuDNN benchmark feature"),
# "cuda_allow_tf32": OptionInfo(True, "Allow TF32 math ops"),
# "cuda_allow_tf16_reduced": OptionInfo(True, "Allow TF16 reduced precision math ops"),
"ipex_optimize": OptionInfo(True if devices.backend == "ipex" else False, "Enable IPEX Optimize for Intel GPUs"),
"directml_memory_provider": OptionInfo(default_memory_provider, 'DirectML memory stats provider', gr.Radio, lambda: {"choices": memory_providers}),
"cuda_compile_sep": OptionInfo("<h2>Model Compile</h2>", "", gr.HTML),
"cuda_compile": OptionInfo(True if cmd_opts.use_openvino else False, "Enable model compile"),
"cuda_compile_backend": OptionInfo("openvino_fx" if cmd_opts.use_openvino else "none", "Model compile backend", gr.Radio, lambda: {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex', 'openvino_fx']}),
"cuda_compile_mode": OptionInfo("default", "Model compile mode", gr.Radio, lambda: {"choices": ['default', 'reduce-overhead', 'max-autotune']}),
@@ -398,8 +421,6 @@ options_templates.update(options_section(('cuda', "Compute Settings"), {
"cuda_compile_precompile": OptionInfo(False, "Model compile precompile"),
"cuda_compile_verbose": OptionInfo(False, "Model compile verbose mode"),
"cuda_compile_errors": OptionInfo(True, "Model compile suppress errors"),
"ipex_optimize": OptionInfo(True if devices.backend == "ipex" else False, "Enable IPEX Optimize for Intel GPUs"),
"directml_memory_provider": OptionInfo(default_memory_provider, 'DirectML memory stats provider', gr.Dropdown, lambda: {"choices": memory_providers}),
}))
options_templates.update(options_section(('diffusers', "Diffusers Settings"), {
@@ -423,26 +444,26 @@ options_templates.update(options_section(('diffusers', "Diffusers Settings"), {
}))
options_templates.update(options_section(('system-paths', "System Paths"), {
"temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"),
"temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default", folder=True),
"clean_temp_dir_at_start": OptionInfo(True, "Cleanup non-default temporary directory when starting webui"),
"ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Path to directory with stable diffusion checkpoints"),
"diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Path to directory with stable diffusion diffusers"),
"vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Path to directory with VAE files"),
"sd_lora": OptionInfo("", "Add LoRA to prompt", gr.CheckboxGroup, {"choices": [], "visible": False}),
"lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Path to directory with LoRA network(s)"),
"lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Path to directory with LyCORIS network(s)"),
"styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "Path to user-defined styles file"),
"embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Embeddings directory for textual inversion"),
"hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Hypernetwork directory"),
"codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Path to directory with codeformer model file(s)"),
"gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Path to directory with GFPGAN model file(s)"),
"esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Path to directory with ESRGAN model file(s)"),
"bsrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'BSRGAN'), "Path to directory with BSRGAN model file(s)"),
"realesrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'RealESRGAN'), "Path to directory with RealESRGAN model file(s)"),
"scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'ScuNET'), "Path to directory with ScuNET model file(s)"),
"swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Path to directory with SwinIR model file(s)"),
"ldsr_models_path": OptionInfo(os.path.join(paths.models_path, 'LDSR'), "Path to directory with LDSR model file(s)"),
"clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Path to directory with CLIP model file(s)"),
"ckpt_dir": OptionInfo(os.path.join(paths.models_path, 'Stable-diffusion'), "Folder with stable diffusion models", folder=True),
"diffusers_dir": OptionInfo(os.path.join(paths.models_path, 'Diffusers'), "Folder with Hugggingface models", folder=True),
"vae_dir": OptionInfo(os.path.join(paths.models_path, 'VAE'), "Folder with VAE files", folder=True),
"sd_lora": OptionInfo("", "Add LoRA to prompt", gr.Textbox, {"visible": False}),
"lora_dir": OptionInfo(os.path.join(paths.models_path, 'Lora'), "Folder with LoRA network(s)", folder=True),
"lyco_dir": OptionInfo(os.path.join(paths.models_path, 'LyCORIS'), "Folder with LyCORIS network(s)", folder=True),
"styles_dir": OptionInfo(os.path.join(paths.data_path, 'styles.csv'), "File or Folder with user-defined styles", folder=True),
"embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Folder with textual inversion embeddings", folder=True),
"hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Folder with Hypernetwork models", folder=True),
"codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Folder with codeformer models", folder=True),
"gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Folder with GFPGAN models", folder=True),
"esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Folder with ESRGAN models", folder=True),
"bsrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'BSRGAN'), "Folder with BSRGAN models", folder=True),
"realesrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'RealESRGAN'), "Folder with RealESRGAN models", folder=True),
"scunet_models_path": OptionInfo(os.path.join(paths.models_path, 'ScuNET'), "Folder with ScuNET models", folder=True),
"swinir_models_path": OptionInfo(os.path.join(paths.models_path, 'SwinIR'), "Folder with SwinIR models", folder=True),
"ldsr_models_path": OptionInfo(os.path.join(paths.models_path, 'LDSR'), "Folder with LDSR models", folder=True),
"clip_models_path": OptionInfo(os.path.join(paths.models_path, 'CLIP'), "Folder with CLIP models", folder=True),
}))
options_templates.update(options_section(('saving-images', "Image Options"), {
@@ -464,11 +485,9 @@ options_templates.update(options_section(('saving-images', "Image Options"), {
"grid_save": OptionInfo(True, "Always save all generated image grids"),
"grid_format": OptionInfo('jpg', 'File format for grids', gr.Dropdown, lambda: {"choices": ["jpg", "png", "webp", "tiff", "jp2"]}),
"n_rows": OptionInfo(-1, "Grid row count", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}),
"grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"),
"grid_prevent_empty_spots": OptionInfo(True, "Prevent empty spots in grid (when set to autodetect)"),
"save_sep_options": OptionInfo("<h2>Intermediate Image Saving</h2>", "", gr.HTML),
"save_init_img": OptionInfo(True, "Save copy of img2img init images (helps track workflow)"),
"save_init_img": OptionInfo(True, "Save copy of img2img init images"),
"save_images_before_highres_fix": OptionInfo(False, "Save copy of image before applying highres fix"),
"save_images_before_refiner": OptionInfo(False, "Save copy of image before running refiner"),
"save_images_before_face_restoration": OptionInfo(False, "Save copy of image before doing face restoration"),
@@ -490,19 +509,19 @@ options_templates.update(options_section(('saving-paths', "Image Naming & Paths"
"use_save_to_dirs_for_ui": OptionInfo(False, "Save images to a subdirectory when using Save button"),
"directories_filename_pattern": OptionInfo("[date]", "Directory name pattern", component_args=hide_dirs),
"directories_max_prompt_words": OptionInfo(8, "Max prompt words for [prompt_words] pattern", gr.Slider, {"minimum": 1, "maximum": 99, "step": 1, **hide_dirs}),
"outdir_samples": OptionInfo("", "Output directory for images", component_args=hide_dirs),
"outdir_txt2img_samples": OptionInfo("outputs/text", 'Output directory for txt2img images', component_args=hide_dirs),
"outdir_img2img_samples": OptionInfo("outputs/image", 'Output directory for img2img images', component_args=hide_dirs),
"outdir_extras_samples": OptionInfo("outputs/extras", 'Output directory for images from extras tab', component_args=hide_dirs),
"outdir_save": OptionInfo("outputs/save", "Directory for saving images using the Save button", component_args=hide_dirs),
"outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs),
"outdir_samples": OptionInfo("", "Output directory for images", component_args=hide_dirs, folder=True),
"outdir_txt2img_samples": OptionInfo("outputs/text", 'Output directory for txt2img images', component_args=hide_dirs, folder=True),
"outdir_img2img_samples": OptionInfo("outputs/image", 'Output directory for img2img images', component_args=hide_dirs, folder=True),
"outdir_extras_samples": OptionInfo("outputs/extras", 'Output directory for images from extras tab', component_args=hide_dirs, folder=True),
"outdir_save": OptionInfo("outputs/save", "Directory for saving images using the Save button", component_args=hide_dirs, folder=True),
"outdir_init_images": OptionInfo("outputs/init-images", "Directory for saving init images when using img2img", component_args=hide_dirs, folder=True),
"outdir_sep_grids": OptionInfo("<h2>Grids</h2>", "", gr.HTML),
"grid_extended_filename": OptionInfo(True, "Add extended info (seed, prompt) to filename when saving grid"),
"grid_save_to_dirs": OptionInfo(False, "Save grids to a subdirectory"),
"outdir_grids": OptionInfo("", "Output directory for grids", component_args=hide_dirs),
"outdir_txt2img_grids": OptionInfo("outputs/grids", 'Output directory for txt2img grids', component_args=hide_dirs),
"outdir_img2img_grids": OptionInfo("outputs/grids", 'Output directory for img2img grids', component_args=hide_dirs),
"outdir_grids": OptionInfo("", "Output directory for grids", component_args=hide_dirs, folder=True),
"outdir_txt2img_grids": OptionInfo("outputs/grids", 'Output directory for txt2img grids', component_args=hide_dirs, folder=True),
"outdir_img2img_grids": OptionInfo("outputs/grids", 'Output directory for img2img grids', component_args=hide_dirs, folder=True),
}))
@@ -529,13 +548,13 @@ options_templates.update(options_section(('live-preview', "Live Previews"), {
"live_previews_enable": OptionInfo(True, "Show live previews of the created image"),
"show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"),
"notification_audio_enable": OptionInfo(False, "Play a sound when images are finished generating"),
"notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs),
"notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs, folder=True),
"show_progress_every_n_steps": OptionInfo(1, "Live preview display period", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}),
"show_progress_type": OptionInfo("Approximate NN", "Live preview method", gr.Radio, {"choices": ["Full VAE", "Approximate NN", "Approximate simple", "TAESD"]}),
"live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}),
"live_preview_refresh_period": OptionInfo(500, "Progressbar/preview update period, in milliseconds", gr.Slider, {"minimum": 0, "maximum": 5000, "step": 25}),
"live_preview_refresh_period": OptionInfo(500, "Progress update period", gr.Slider, {"minimum": 0, "maximum": 5000, "step": 25}),
"logmonitor_show": OptionInfo(True, "Show log view"),
"logmonitor_refresh_period": OptionInfo(5000, "Log view update period, in milliseconds", gr.Slider, {"minimum": 0, "maximum": 30000, "step": 25}),
"logmonitor_refresh_period": OptionInfo(5000, "Log view update period", gr.Slider, {"minimum": 0, "maximum": 30000, "step": 25}),
}))
options_templates.update(options_section(('sampler-params', "Sampler Settings"), {
@@ -548,9 +567,9 @@ options_templates.update(options_section(('sampler-params', "Sampler Settings"),
"schedulers_sep_diffusers": OptionInfo("<h2>Diffusers specific config</h2>", "", gr.HTML),
"schedulers_prediction_type": OptionInfo("default", "Samplers override model prediction type", gr.Radio, lambda: {"choices": ['default', 'epsilon', 'sample', 'v-prediction']}),
"schedulers_use_karras": OptionInfo(True, "Samplers should use Karras sigmas where applicable"),
"schedulers_use_loworder": OptionInfo(True, "Samplers should use use lower-order solvers in the final steps where applicable"),
"schedulers_use_thresholding": OptionInfo(False, "Samplers should use dynamic thresholding where applicable"),
"schedulers_use_karras": OptionInfo(True, "Samplers use Karras sigmas where applicable"),
"schedulers_use_loworder": OptionInfo(True, "Samplers use simplified solvers in final steps where applicable"),
"schedulers_use_thresholding": OptionInfo(False, "Samplers use dynamic thresholding where applicable"),
"schedulers_dpm_solver": OptionInfo("sde-dpmsolver++", "Samplers DPM solver algorithm", gr.Radio, lambda: {"choices": ['dpmsolver', 'dpmsolver++', 'sde-dpmsolver++']}),
"schedulers_beta_schedule": OptionInfo("default", "Samplers override beta schedule", gr.Radio, lambda: {"choices": ['default', 'linear', 'scaled_linear', 'squaredcos_cap_v2']}),
'schedulers_beta_start': OptionInfo(0, "Samplers override beta start", gr.Number, {}),
@@ -572,11 +591,8 @@ options_templates.update(options_section(('sampler-params', "Sampler Settings"),
}))
options_templates.update(options_section(('postprocessing', "Postprocessing"), {
'postprocessing_enable_in_main_ui': OptionInfo([], "Enable addtional postprocessing operations", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}),
'postprocessing_enable_in_main_ui': OptionInfo([], "Enable additional postprocessing operations", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}),
'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}),
# "use_old_hires_fix_width_height": OptionInfo(False, "Hires fix uses width & height to set final resolution"),
# "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers"),
"postprocessing_sep_img2img": OptionInfo("<h2>Img2Img & Inpainting</h2>", "", gr.HTML),
"img2img_color_correction": OptionInfo(False, "Apply color correction to match original colors"),
"img2img_fix_steps": OptionInfo(False, "For image processing do exact number of steps as specified"),
@@ -607,7 +623,7 @@ options_templates.update(options_section(('training', "Training"), {
"save_training_settings_to_txt": OptionInfo(True, "Save training settings to a text file on training start"),
"dataset_filename_word_regex": OptionInfo("", "Filename word regex"),
"dataset_filename_join_string": OptionInfo(" ", "Filename join string"),
"embeddings_templates_dir": OptionInfo(os.path.join(paths.script_path, 'train', 'templates'), "Embeddings train templates directory"),
"embeddings_templates_dir": OptionInfo(os.path.join(paths.script_path, 'train', 'templates'), "Embeddings train templates directory", folder=True),
"training_image_repeats_per_epoch": OptionInfo(1, "Number of repeats for a single input image per epoch", gr.Number, {"precision": 0}),
"training_write_csv_every": OptionInfo(0, "Save CSV file containing the loss to log directory"),
"training_enable_tensorboard": OptionInfo(False, "Enable tensorboard logging"),
@@ -622,7 +638,7 @@ options_templates.update(options_section(('interrogate', "Interrogate"), {
"interrogate_clip_min_length": OptionInfo(32, "Interrogate: minimum description length", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}),
"interrogate_clip_max_length": OptionInfo(192, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}),
"interrogate_clip_dict_limit": OptionInfo(2048, "CLIP: maximum number of lines in text file"),
"interrogate_clip_skip_categories": OptionInfo(["artists", "movements", "flavors"], "CLIP: skip inquire categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types),
"interrogate_clip_skip_categories": OptionInfo(["artists", "movements", "flavors"], "Interrogate: skip categories", gr.CheckboxGroup, lambda: {"choices": modules.interrogate.category_types()}, refresh=modules.interrogate.category_types),
"interrogate_deepbooru_score_threshold": OptionInfo(0.65, "Interrogate: deepbooru score threshold", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}),
"deepbooru_sort_alpha": OptionInfo(False, "Interrogate: deepbooru sort alphabetically"),
"deepbooru_use_spaces": OptionInfo(False, "Use spaces for tags in deepbooru"),
@@ -805,7 +821,6 @@ class Options:
value = expected_type(value)
return value
opts = Options()
config_filename = cmd_opts.config
opts.load(config_filename)
@@ -817,7 +832,8 @@ else:
opts.data['sd_backend'] = 'diffusers' if backend == Backend.DIFFUSERS else 'original'
opts.data['uni_pc_lower_order_final'] = opts.schedulers_use_loworder
opts.data['uni_pc_order'] = opts.schedulers_solver_order
log.info(f'Engine: backend={backend}')
log.info(f'Engine: backend={backend} compute={devices.backend} mode={devices.inference_context.__name__} device={devices.get_optimal_device_name()}')
log.info(f'Device: {print_dict(devices.get_gpu_info())}')
prompt_styles = modules.styles.StyleDatabase(opts)
cmd_opts.disable_extension_access = (cmd_opts.share or cmd_opts.listen or (cmd_opts.server_name or False)) and not cmd_opts.insecure
@@ -906,14 +922,20 @@ total_tqdm = TotalTQDM()
def restart_server(restart=True):
if demo is None:
return
log.info('Server shutdown requested')
log.warning('Server shutdown requested')
try:
demo.server.wants_restart = restart
demo.server.should_exit = True
demo.server.force_exit = True
demo.close(verbose=False)
demo.server.close()
demo.fns = []
sys.tracebacklimit = 0
stdout = io.StringIO()
stderr = io.StringIO()
with contextlib.redirect_stdout(stdout), contextlib.redirect_stdout(stderr):
demo.server.wants_restart = restart
demo.server.should_exit = True
demo.server.force_exit = True
demo.close(verbose=False)
demo.server.close()
demo.fns = []
time.sleep(1)
sys.tracebacklimit = 100
# os._exit(0)
except (Exception, BaseException) as e:
log.error(f'Server shutdown error: {e}')
+45 -38
View File
@@ -3,22 +3,18 @@ from __future__ import annotations
import csv
import os
import json
import shutil
import typing
from installer import log
from modules import paths
if typing.TYPE_CHECKING:
# Only import this when code is being type-checked, it doesn't have any effect at runtime
from .processing import StableDiffusionProcessing
class PromptStyle(typing.NamedTuple):
name: str
prompt: str
negative_prompt: str
extra: str = ""
class Style():
def __init__(self, name: str, prompt: str = "", negative_prompt: str = "", extra: str = "", filename: str = "", preview: str = ""):
self.name = name
self.prompt = prompt
self.negative_prompt = negative_prompt
self.extra = extra
self.filename = filename
self.preview = preview
def merge_prompts(style_prompt: str, prompt: str) -> str:
@@ -43,36 +39,40 @@ def apply_styles_to_prompt(prompt, styles):
class StyleDatabase:
def __init__(self, opts):
self.no_style = PromptStyle("None", "", "")
self.no_style = Style("None")
self.styles = {}
self.path = opts.styles_dir
if os.path.isfile(opts.styles_dir):
if os.path.isfile(opts.styles_dir) or opts.styles_dir.endswith(".csv"):
legacy_file = opts.styles_dir
self.load_csv(legacy_file)
opts.styles_dir = os.path.join(paths.models_path, "styles")
self.path = opts.styles_dir
self.mkdir()
os.makedirs(opts.styles_dir, exist_ok=True)
self.save_styles(opts.styles_dir, verbose=True)
log.debug(f'Migrated styles: file={legacy_file} folder={self.path}')
self.mkdir()
self.reload()
def mkdir(self):
if not os.path.isdir(self.path):
os.makedirs(self.path, exist_ok=True)
log.debug(f'Created styles: folder={self.path}')
log.debug(f'Migrated styles: file={legacy_file} folder={opts.styles_dir}')
self.reload()
if not os.path.isdir(opts.styles_dir):
opts.styles_dir = os.path.join(paths.models_path, "styles")
self.path = opts.styles_dir
os.makedirs(opts.styles_dir, exist_ok=True)
def reload(self):
self.styles.clear()
for fn in os.listdir(self.path):
if not fn.endswith(".json"):
continue
with open(os.path.join(self.path, fn), 'r', encoding='utf-8') as f:
try:
style = json.load(f)
self.styles[style["name"]] = PromptStyle(style["name"], style["prompt"], style["negative"], style["extra"])
except Exception as e:
log.error(f'Failed to load style: file={fn} error={e}')
def list_folder(folder):
for filename in os.listdir(folder):
fn = os.path.join(folder, filename)
if os.path.isfile(fn) and fn.lower().endswith(".json"):
with open(fn, 'r', encoding='utf-8') as f:
try:
style = json.load(f)
fn = os.path.splitext(os.path.relpath(fn, self.path))[0]
self.styles[style["name"]] = Style(style["name"], style.get("prompt", ""), style.get("negative", ""), style.get("extra", ""), fn, style.get("preview", ""))
except Exception as e:
log.error(f'Failed to load style: file={fn} error={e}')
elif os.path.isdir(fn) and not fn.startswith('.'):
list_folder(fn)
list_folder(self.path)
log.debug(f'Loaded styles: folder={self.path} items={len(self.styles.keys())}')
def get_style_prompts(self, styles):
@@ -94,8 +94,11 @@ class StyleDatabase:
"prompt": self.styles[name].prompt,
"negative": self.styles[name].negative_prompt,
"extra": "",
"preview": "",
}
fn = os.path.join(path, name + ".json")
keepcharacters = (' ','.','_')
fn = "".join(c for c in name if c.isalnum() or c in keepcharacters).rstrip()
fn = os.path.join(path, fn + ".json")
try:
with open(fn, 'w', encoding='utf-8') as f:
json.dump(style, f, indent=2)
@@ -103,20 +106,23 @@ class StyleDatabase:
log.debug(f'Saved style: name={name} file={fn}')
except Exception as e:
log.error(f'Failed to save style: name={name} file={path} error={e}')
log.debug(f'Saved styles: {path} {len(self.styles.keys())}')
count = len(list(self.styles))
if count > 0:
log.debug(f'Saved styles: {path} {count}')
def load_csv(self, legacy_file):
if not os.path.isfile(legacy_file):
return
with open(legacy_file, "r", encoding="utf-8-sig", newline='') as file:
reader = csv.DictReader(file, skipinitialspace=True)
for row in reader:
try:
prompt = row["prompt"] if "prompt" in row else row["text"]
negative_prompt = row.get("negative_prompt", "")
self.styles[row["name"]] = PromptStyle(row["name"], prompt, negative_prompt)
self.styles[row["name"]] = Style(row["name"], row["prompt"] if "prompt" in row else row["text"], row.get("negative_prompt", ""))
except Exception:
log.error(f'Styles error: file={legacy_file} row={row}')
log.debug(f'Loaded legacy styles: file={legacy_file} items={len(self.styles.keys())}')
"""
def save_csv(self, path: str) -> None:
import tempfile
basedir = os.path.dirname(path)
@@ -124,8 +130,9 @@ class StyleDatabase:
os.makedirs(basedir, exist_ok=True)
fd, temp_path = tempfile.mkstemp(".csv")
with os.fdopen(fd, "w", encoding="utf-8-sig", newline='') as file:
writer = csv.DictWriter(file, fieldnames=PromptStyle._fields)
writer = csv.DictWriter(file, fieldnames=Style._fields)
writer.writeheader()
writer.writerows(style._asdict() for k, style in self.styles.items())
log.debug(f'Saved legacy styles: {path} {len(self.styles.keys())}')
shutil.move(temp_path, path)
"""
+19
View File
@@ -61,3 +61,22 @@ def decode(latents):
enc = latents.unsqueeze(0).to(devices.device, devices.dtype_vae)
image = vae.decoder(enc).clamp(0, 1).detach()
return image[0]
def encode(image):
from modules import shared
model_class = shared.sd_model_type
if model_class == 'ldm':
model_class = 'sd'
if 'sd' not in model_class:
shared.log.warning(f'TAESD unsupported model type: {model_class}')
return Image.new('RGB', (8, 8), color = (0, 0, 0))
vae = taesd_models[f'{model_class}-encoder']
if vae is None:
model_path = os.path.join(paths_internal.models_path, "TAESD", f"tae{model_class}_encoder.pth")
download_model(model_path)
if os.path.exists(model_path):
taesd_models[f'{model_class}-encoder'] = TAESD(encoder_path=model_path, decoder_path=None)
vae = taesd_models[f'{model_class}-encoder']
vae.to(devices.device, devices.dtype_vae)
latents = vae.encoder(image).detach()
return latents
+3 -1
View File
@@ -5,6 +5,8 @@ Tiny AutoEncoder for Stable Diffusion
"""
import torch
import torch.nn as nn
from modules import devices
def conv(n_in, n_out, **kwargs):
return nn.Conv2d(n_in, n_out, 3, padding=1, **kwargs)
@@ -65,7 +67,7 @@ class TAESD(nn.Module):
return x.sub(TAESD.latent_shift).mul(2 * TAESD.latent_magnitude)
@torch.no_grad()
@devices.inference_context()
def main():
from PIL import Image
import sys
+1 -3
View File
@@ -295,10 +295,8 @@ def is_square(w, h):
def download_and_cache_models(dirname):
download_url = 'https://github.com/opencv/opencv_zoo/blob/91fb0290f50896f38a0ab1e558b74b16bc009428/models/face_detection_yunet/face_detection_yunet_2022mar.onnx?raw=true'
model_file_name = 'face_detection_yunet.onnx'
if not os.path.exists(dirname):
os.makedirs(dirname)
os.makedirs(dirname, exist_ok=True)
cache_file = os.path.join(dirname, model_file_name)
if not os.path.exists(cache_file):
print(f"downloading face detection model from '{download_url}' to '{cache_file}'")
+8 -16
View File
@@ -21,13 +21,10 @@ textual_inversion_templates = {}
def list_textual_inversion_templates():
textual_inversion_templates.clear()
for root, _dirs, fns in os.walk(shared.opts.embeddings_templates_dir):
for fn in fns:
path = os.path.join(root, fn)
textual_inversion_templates[fn] = TextualInversionTemplate(fn, path)
return textual_inversion_templates
@@ -35,6 +32,7 @@ class Embedding:
def __init__(self, vec, name, step=None):
self.vec = vec
self.name = name
self.tag = name
self.step = step
self.shape = None
self.vectors = 0
@@ -81,13 +79,11 @@ class DirWithTextualInversionEmbeddings:
def has_changed(self):
if not os.path.isdir(self.path):
return False
return directory_mtime(self.path) != self.mtime
def update(self):
if not os.path.isdir(self.path):
return
self.mtime = directory_mtime(self.path)
@@ -148,9 +144,9 @@ class EmbeddingDatabase:
embeddings_dict[k] = f.get_tensor(k)
for i in range(len(embeddings_dict["clip_l"])):
if i == 0:
token = name
token = name.lower()
else:
token = f"{name}_{i}"
token = f"{name.lower()}_{i}"
pipe.tokenizer.add_tokens(token)
token_id = pipe.tokenizer.convert_tokens_to_ids(token)
pipe.text_encoder.resize_token_embeddings(len(pipe.tokenizer))
@@ -177,19 +173,14 @@ class EmbeddingDatabase:
return
if ext in ['.PNG', '.WEBP', '.JXL', '.AVIF']:
_, second_ext = os.path.splitext(name)
if second_ext.upper() == '.PREVIEW':
if '.preview' in filename.lower():
return
embed_image = Image.open(path)
if hasattr(embed_image, 'text') and 'sd-ti-embedding' in embed_image.text:
data = embedding_from_b64(embed_image.text['sd-ti-embedding'])
name = data.get('name', name)
else:
data = extract_image_data_embed(embed_image)
if data:
name = data.get('name', name)
else:
# if data is None, means this is not an embeding, just a preview image
if not data: # if data is None, means this is not an embeding, just a preview image
return
elif ext in ['.BIN', '.PT']:
data = torch.load(path, map_location="cpu")
@@ -207,17 +198,18 @@ class EmbeddingDatabase:
# diffuser concepts
elif type(data) == dict and type(next(iter(data.values()))) == torch.Tensor:
if len(data.keys()) != 1:
# shared.log.warning(f"Skipping embedding: {filename} multiple keys found")
self.skipped_embeddings[name] = Embedding(None, name)
return
emb = next(iter(data.values()))
if len(emb.shape) == 1:
emb = emb.unsqueeze(0)
else:
raise RuntimeError(f"Couldn't identify {filename} as neither textual inversion embedding nor diffuser concept.")
raise RuntimeError(f"Couldn't identify {filename} as textual inversion embedding")
vec = emb.detach().to(devices.device, dtype=torch.float32)
# name = data.get('name', name)
embedding = Embedding(vec, name)
embedding.tag = data.get('name', None)
embedding.step = data.get('step', None)
embedding.sd_checkpoint = data.get('sd_checkpoint', None)
embedding.sd_checkpoint_name = data.get('sd_checkpoint_name', None)
+3 -2
View File
@@ -4,9 +4,9 @@ from modules.generation_parameters_copypaste import create_override_settings_dic
from modules.ui import plaintext_to_html
def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, latent_index: int, full_quality: bool, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, clip_skip: int, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, refiner_steps: int, refiner_start: int, refiner_prompt: str, refiner_negative: str, override_settings_texts, *args): # pylint: disable=unused-argument
def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, latent_index: int, full_quality: bool, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, clip_skip: int, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_force: bool, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, refiner_steps: int, refiner_start: int, refiner_prompt: str, refiner_negative: str, override_settings_texts, *args): # pylint: disable=unused-argument
shared.log.debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}||height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}||refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings_texts={override_settings_texts}|args={args}')
shared.log.debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|latent_index={latent_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}||height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_force={hr_force}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}|refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings_texts={override_settings_texts}')
if shared.sd_model is None:
shared.log.warning('Model not loaded')
@@ -49,6 +49,7 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step
denoising_strength=denoising_strength,
hr_scale=hr_scale,
hr_upscaler=hr_upscaler,
hr_force=hr_force,
hr_second_pass_steps=hr_second_pass_steps,
hr_resize_x=hr_resize_x,
hr_resize_y=hr_resize_y,
+46 -41
View File
@@ -73,7 +73,7 @@ def send_gradio_gallery_to_image(x):
def add_style(name: str, prompt: str, negative_prompt: str):
if name is None:
return [gr_show() for x in range(4)]
style = modules.styles.PromptStyle(name, prompt, negative_prompt)
style = modules.styles.Style(name, prompt, negative_prompt)
modules.shared.prompt_styles.styles[style.name] = style
modules.shared.prompt_styles.save_styles(modules.shared.opts.styles_dir)
return [gr.Dropdown.update(visible=True, choices=list(modules.shared.prompt_styles.styles)) for _ in range(2)]
@@ -235,11 +235,11 @@ def create_toprow(is_img2img):
with gr.Row():
with gr.Column(scale=80):
with gr.Row():
prompt = gr.Textbox(label="Prompt", elem_id=f"{id_part}_prompt", show_label=False, lines=3, placeholder="Prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"])
prompt = gr.Textbox(elem_id=f"{id_part}_prompt", label="Prompt", show_label=False, lines=3, placeholder="Prompt", elem_classes=["prompt"])
with gr.Row():
with gr.Column(scale=80):
with gr.Row():
negative_prompt = gr.Textbox(label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"])
negative_prompt = gr.Textbox(elem_id=f"{id_part}_neg_prompt", label="Negative prompt", show_label=False, lines=3, placeholder="Negative prompt", elem_classes=["prompt"])
button_interrogate = None
button_deepbooru = None
if is_img2img:
@@ -270,7 +270,7 @@ def create_toprow(is_img2img):
negative_token_button = gr.Button(visible=False, elem_id=f"{id_part}_negative_token_button")
with gr.Row(elem_id=f"{id_part}_styles_row"):
prompt_styles = gr.Dropdown(label="Styles", elem_id=f"{id_part}_styles", choices=[k for k, v in modules.shared.prompt_styles.styles.items()], value=[], multiselect=True)
create_refresh_button(prompt_styles, modules.shared.prompt_styles.reload, lambda: {"choices": [k for k, v in modules.shared.prompt_styles.styles.items()]}, f"refresh_{id_part}_styles")
# create_refresh_button(prompt_styles, modules.shared.prompt_styles.reload, lambda: {"choices": [k for k, v in modules.shared.prompt_styles.styles.items()]}, f"refresh_{id_part}_styles")
prompt_styles_btn = gr.Button('Apply', elem_id=f"{id_part}_styles_select", visible=False)
prompt_styles_btn.click(_js="applyStyles", fn=parse_style, inputs=[prompt_styles], outputs=[prompt_styles])
return prompt, prompt_styles, negative_prompt, submit, button_interrogate, button_deepbooru, prompt_style_apply, save_style, paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button
@@ -389,35 +389,36 @@ def create_ui(startup_timer = None):
tiling = gr.Checkbox(label='Tiling', value=False, elem_id="txt2img_tiling")
with FormGroup(visible=show_second_pass.value, elem_id="txt2img_second_pass") as second_pass_group:
with FormRow(elem_id="sampler_selection_txt2img_alt_row1"):
latent_index = gr.Dropdown(label='Secondary sampler', elem_id="txt2img_sampling_alt", choices=[x.name for x in modules.sd_samplers.samplers], value='Default', type="index")
denoising_strength = gr.Slider(minimum=0.05, maximum=1.0, step=0.01, label='Denoising strength', value=0.3, elem_id="txt2img_denoising_strength")
with FormRow(elem_id="txt2img_hires_finalres", variant="compact"):
hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False)
with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"):
hr_upscaler = gr.Dropdown(label="Upscaler", elem_id="txt2img_hr_upscaler", choices=[*modules.shared.latent_upscale_modes, *[x.name for x in modules.shared.sd_upscalers]], value=modules.shared.latent_upscale_default_mode)
hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id="txt2img_steps_alt", value=20)
with FormRow(elem_id="txt2img_hires_fix_row2", variant="compact"):
hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale")
with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact"):
hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x")
hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y")
with FormRow():
hr_refiner = FormHTML(value="Refiner", elem_id="txtimg_hr_refiner", interactive=False)
with FormRow(elem_id="txt2img_refiner_row1", variant="compact"):
refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Refiner start', value=0.8, elem_id="txt2img_refiner_start")
refiner_steps = gr.Slider(minimum=0, maximum=99, step=1, label="Refiner steps", elem_id="txt2img_refiner_steps", value=5)
with FormRow(elem_id="txt2img_refiner_row3", variant="compact"):
refiner_prompt = gr.Textbox(value='', label='Secondary Prompt')
with FormRow(elem_id="txt2img_refiner_row4", variant="compact"):
refiner_negative = gr.Textbox(value='', label='Secondary negative prompt')
with FormGroup():
with FormRow(elem_id="sampler_selection_txt2img_alt_row1"):
latent_index = gr.Dropdown(label='Secondary sampler', elem_id="txt2img_sampling_alt", choices=[x.name for x in modules.sd_samplers.samplers], value='Default', type="index")
denoising_strength = gr.Slider(minimum=0.05, maximum=1.0, step=0.01, label='Denoising strength', value=0.5, elem_id="txt2img_denoising_strength")
with FormRow(elem_id="txt2img_hires_finalres", variant="compact"):
hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False)
with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"):
hr_upscaler = gr.Dropdown(label="Upscaler", elem_id="txt2img_hr_upscaler", choices=[*modules.shared.latent_upscale_modes, *[x.name for x in modules.shared.sd_upscalers]], value=modules.shared.latent_upscale_default_mode)
hr_force = gr.Checkbox(label='Force Hires', value=False, elem_id="txt2img_hr_force")
with FormRow(elem_id="txt2img_hires_fix_row2", variant="compact"):
hr_second_pass_steps = gr.Slider(minimum=0, maximum=99, step=1, label='Hires steps', elem_id="txt2img_steps_alt", value=20)
hr_scale = gr.Slider(minimum=1.0, maximum=4.0, step=0.05, label="Upscale by", value=2.0, elem_id="txt2img_hr_scale")
with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact"):
hr_resize_x = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize width to", value=0, elem_id="txt2img_hr_resize_x")
hr_resize_y = gr.Slider(minimum=0, maximum=4096, step=8, label="Resize height to", value=0, elem_id="txt2img_hr_resize_y")
with FormGroup(visible=modules.shared.backend == modules.shared.Backend.DIFFUSERS):
with FormRow():
hr_refiner = FormHTML(value="Refiner", elem_id="txtimg_hr_refiner", interactive=False)
with FormRow(elem_id="txt2img_refiner_row1", variant="compact"):
refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Refiner start', value=0.8, elem_id="txt2img_refiner_start")
refiner_steps = gr.Slider(minimum=0, maximum=99, step=1, label="Refiner steps", elem_id="txt2img_refiner_steps", value=5)
with FormRow(elem_id="txt2img_refiner_row3", variant="compact"):
refiner_prompt = gr.Textbox(value='', label='Secondary Prompt')
with FormRow(elem_id="txt2img_refiner_row4", variant="compact"):
refiner_negative = gr.Textbox(value='', label='Secondary negative prompt')
with FormRow(elem_id="txt2img_override_settings_row") as row:
override_settings = create_override_settings_dropdown('txt2img', row)
with FormGroup(elem_id="txt2img_script_container"):
custom_inputs = modules.scripts.scripts_txt2img.setup_ui()
custom_inputs = modules.scripts.scripts_txt2img.setup_ui()
hr_resolution_preview_inputs = [show_second_pass, width, height, hr_scale, hr_resize_x, hr_resize_y, hr_upscaler]
for preview_input in hr_resolution_preview_inputs:
@@ -450,7 +451,7 @@ def create_ui(startup_timer = None):
seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w,
height, width,
show_second_pass, denoising_strength,
hr_scale, hr_upscaler, hr_second_pass_steps, hr_resize_x, hr_resize_y,
hr_scale, hr_upscaler, hr_force, hr_second_pass_steps, hr_resize_x, hr_resize_y,
refiner_steps, refiner_start, refiner_prompt, refiner_negative,
override_settings,
] + custom_inputs,
@@ -465,13 +466,14 @@ def create_ui(startup_timer = None):
txt2img_prompt.submit(**txt2img_args)
submit.click(**txt2img_args)
def enable_hr_change(visible: bool):
return {"visible": visible, "__type__": "update"}, f'Refiner: {"disabled" if modules.shared.opts.sd_model_refiner == "None" else "enabled"}'
def enable_hr_change(visible: bool, refiner_start):
enabled = modules.shared.opts.sd_model_refiner != "None" and refiner_start > 0 and refiner_start < 1
return {"visible": visible, "__type__": "update"}, f'Refiner: {"enabled" if enabled else "disabled"}'
res_switch_btn.click(lambda w, h: (h, w), inputs=[width, height], outputs=[width, height], show_progress=False)
batch_switch_btn.click(lambda w, h: (h, w), inputs=[batch_count, batch_size], outputs=[batch_count, batch_size], show_progress=False)
txt_prompt_img.change(fn=modules.images.image_data, inputs=[txt_prompt_img], outputs=[txt2img_prompt, txt_prompt_img])
show_second_pass.change(enable_hr_change, inputs=[show_second_pass], outputs=[second_pass_group, hr_refiner], show_progress = False)
show_second_pass.change(enable_hr_change, inputs=[show_second_pass, refiner_start], outputs=[second_pass_group, hr_refiner], show_progress = False)
show_seed.change(gr_show, inputs=[show_seed], outputs=[seed_group], show_progress = False)
show_batch.change(gr_show, inputs=[show_batch], outputs=[batch_group], show_progress = False)
show_advanced.change(gr_show, inputs=[show_advanced], outputs=[advanced_group], show_progress = False)
@@ -522,7 +524,7 @@ def create_ui(startup_timer = None):
negative_token_button.click(fn=wrap_queued_call(update_token_counter), inputs=[txt2img_negative_prompt, steps], outputs=[negative_token_counter])
ui_extra_networks.setup_ui(extra_networks_ui, txt2img_gallery)
log.debug(f'UI interface: tab=txt2img batch={show_batch.value} seed={show_seed.value} advanced={show_advanced.value} second_pass={show_second_pass.value}')
# log.debug(f'UI interface: tab=txt2img batch={show_batch.value} seed={show_seed.value} advanced={show_advanced.value} second_pass={show_second_pass.value}')
timer.startup.record("ui-txt2img")
@@ -882,8 +884,7 @@ def create_ui(startup_timer = None):
parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding(
paste_button=img2img_paste, tabname="img2img", source_text_component=img2img_prompt, source_image_component=None,
))
log.debug(f'UI interface: tab=img2img seed={show_seed.value} resize={show_resize.value} batch={show_batch.value} denoise={show_denoise.value} advanced={show_advanced.value}')
# log.debug(f'UI interface: tab=img2img seed={show_seed.value} resize={show_resize.value} batch={show_batch.value} denoise={show_denoise.value} advanced={show_advanced.value}')
timer.startup.record("ui-img2img")
@@ -928,11 +929,15 @@ def create_ui(startup_timer = None):
if info.refresh is not None:
if is_quicksettings:
res = comp(label=info.label, value=fun(), elem_id=elem_id, **args)
create_refresh_button(res, info.refresh, args, f"refresh_{key}")
ui_common.create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
else:
with FormRow():
res = comp(label=info.label, value=fun(), elem_id=elem_id, **args)
create_refresh_button(res, info.refresh, args, f"refresh_{key}")
ui_common.create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
elif info.folder is not None:
with FormRow():
res = comp(label=info.label, value=fun(), elem_id=elem_id, elem_classes="folder-selector", **args)
ui_common.create_browse_button(res, f"folder_{key}")
else:
try:
res = comp(label=info.label, value=fun(), elem_id=elem_id, **args)
@@ -1067,7 +1072,7 @@ def create_ui(startup_timer = None):
with gr.TabItem("Show all pages", variant='primary', elem_id="settings_show_all_pages"):
create_dirty_indicator("show_all_pages", [], interactive=False)
with gr.TabItem("UI Config", id="system_config", elem_id="system_config_tab"):
with gr.TabItem("UI Config", id="system_config", elem_id="tab_config"):
loadsave.create_ui()
create_dirty_indicator("tab_defaults", [], interactive=False)
@@ -1085,7 +1090,7 @@ def create_ui(startup_timer = None):
unload_sd_model.click(fn=unload_sd_weights, inputs=[], outputs=[])
reload_sd_model.click(fn=reload_sd_weights, inputs=[], outputs=[])
request_notifications.click(fn=lambda: None, inputs=[], outputs=[], _js='function(){}')
preview_theme.click(fn=None, _js='preview_theme', inputs=[dummy_component], outputs=[dummy_component])
preview_theme.click(fn=None, _js='previewTheme', inputs=[], outputs=[])
timer.startup.record("ui-settings")
@@ -1154,7 +1159,7 @@ def create_ui(startup_timer = None):
show_progress=info.refresh is not None,
)
button_set_checkpoint = gr.Button('Change checkpoint', elem_id='change_checkpoint', visible=False)
button_set_checkpoint = gr.Button('Change model', elem_id='change_checkpoint', visible=False)
button_set_checkpoint.click(
fn=lambda value, _: run_settings_single(value, key='sd_model_checkpoint'),
_js="function(v){ var res = desiredCheckpointName; desiredCheckpointName = ''; return [res || v, null]; }",
+18
View File
@@ -9,6 +9,7 @@ from modules import call_queue, shared
from modules.generation_parameters_copypaste import image_from_url_text
import modules.ui_symbols as symbols
import modules.images
import modules.script_callbacks
def update_generation_info(generation_info, html_info, img_index):
@@ -115,6 +116,8 @@ def save_files(js_data, images, html_info, index):
os.makedirs(destination, exist_ok = True)
shutil.copy(fullfn, destination)
shared.log.info(f"Copying image: {fullfn} -> {destination}")
tgt_filename = os.path.join(destination, os.path.basename(fullfn))
modules.script_callbacks.image_save_btn_callback(tgt_filename)
else:
image = image_from_url_text(filedata)
info = p.infotexts[i + 1] if len(p.infotexts) > len(p.all_seeds) else p.infotexts[i] # infotexts may be offset by 1 because the first image is the grid
@@ -127,6 +130,7 @@ def save_files(js_data, images, html_info, index):
if txt_fullfn:
filenames.append(os.path.basename(txt_fullfn))
fullfns.append(txt_fullfn)
modules.script_callbacks.image_save_btn_callback(filename)
if shared.opts.samples_save_zip and len(fullfns) > 1:
zip_filepath = os.path.join(shared.opts.outdir_save, "images.zip")
from zipfile import ZipFile
@@ -225,3 +229,17 @@ def create_refresh_button(refresh_component, refresh_method, refreshed_args, ele
refresh_button = ToolButton(value=symbols.refresh, elem_id=elem_id)
refresh_button.click(fn=refresh, inputs=[], outputs=[refresh_component])
return refresh_button
def create_browse_button(browse_component, elem_id):
def browse(folder):
# import subprocess
if folder is not None:
return gr.update(value = folder)
return gr.update()
from modules.ui_components import ToolButton
browse_button = ToolButton(value=symbols.folder, elem_id=elem_id)
browse_button.click(fn=browse, _js="async () => await browseFolder()", inputs=[browse_component], outputs=[browse_component])
# browse_button.click(fn=browse, inputs=[browse_component], outputs=[browse_component])
return browse_button
+2 -2
View File
@@ -34,7 +34,7 @@ def update_extension_list():
try:
with open(os.path.join(paths.script_path, "html", "extensions.json"), "r", encoding="utf-8") as f:
extensions_list = json.loads(f.read())
shared.log.debug(f'Extensions list loaded: {os.path.join(paths.script_path, "html", "extensions.json")}')
# shared.log.debug(f'Extensions list loaded: {os.path.join(paths.script_path, "html", "extensions.json")}')
except Exception:
shared.log.debug(f'Extensions list failed to load: {os.path.join(paths.script_path, "html", "extensions.json")}')
found = []
@@ -290,7 +290,7 @@ def refresh_extensions_list_from_data(search_text, sort_column):
<th>Current version</th>
<th></th>
</tr>
</thead>
</thead>
<tbody>"""
if len(extensions_list) == 0:
update_extension_list()
+78 -60
View File
@@ -64,7 +64,7 @@ def get_metadata(page: str = "", item: str = ""):
metadata = page.metadata.get(item, 'none')
if metadata is None:
metadata = ''
shared.log.debug(f'Extra networks metadata: page={page} item={item} len={len(metadata)}')
shared.log.debug(f"Extra networks metadata: page='{page}' item={item} len={len(metadata)}")
return JSONResponse({"metadata": metadata})
@@ -75,7 +75,7 @@ def get_info(page: str = "", item: str = ""):
info = page.info.get(item, 'none')
if info is None:
info = ''
shared.log.debug(f'Extra networks info: page={page} item={item} len={len(info)}')
shared.log.debug(f"Extra networks info: page='{page}' item={item} len={len(info)}")
return JSONResponse({"info": info})
@@ -150,7 +150,7 @@ class ExtraNetworksPage:
def is_empty(self, folder):
for f in listdir(folder):
_fn, ext = os.path.splitext(f)
if ext.lower() in ['.ckpt', '.safetensors', '.pt'] or os.path.isdir(os.path.join(folder, f)):
if ext.lower() in ['.ckpt', '.safetensors', '.pt', '.json'] or os.path.isdir(os.path.join(folder, f)):
return False
return True
@@ -164,21 +164,20 @@ class ExtraNetworksPage:
continue
try:
img = Image.open(f)
if img.width > 1024 or img.height > 1024 or os.path.getsize(f) > 70000:
if img.width > 1024 or img.height > 1024 or os.path.getsize(f) > 65536:
img = img.convert('RGB')
img.thumbnail((512, 512), Image.HAMMING)
img.save(fn)
img.save(fn, quality=50)
img.close()
created += 1
except Exception as e:
shared.log.error(f'Extra network error creating thumbnail: {f} {e}')
if created > 0:
shared.log.info(f"Extra network created thumbnails: {self.name} {created}")
shared.log.info(f"Extra network thumbnails: {self.name} created={created}")
self.missing_thumbs.clear()
def create_page(self, tabname, skip = False):
if self.refresh_time is not None and self.refresh_time > refresh_time:
# shared.log.debug(f'Extra networks: {self.name} items={len(self.items)} tab={tabname} cached')
if self.refresh_time is not None and self.refresh_time > refresh_time: # cached page
return self.html
t0 = time.time()
self_name_id = self.name.replace(" ", "_")
@@ -219,7 +218,7 @@ class ExtraNetworksPage:
else:
return ''
t1 = time.time()
shared.log.debug(f'Extra networks: {self.name} items={len(self.items)} subdirs={len(subdirs)} tab={tabname} time={round(t1-t0, 2)}')
shared.log.debug(f"Extra networks: page='{self.name}' items={len(self.items)} subdirs={len(subdirs)} tab={tabname} dirs={self.allowed_directories_for_previews()} time={round(t1-t0, 2)}")
threading.Thread(target=self.create_thumb).start()
def list_items(self):
@@ -302,12 +301,48 @@ class ExtraNetworksPage:
pass
return ''
def save_preview(self, index, images, filename):
try:
image = image_from_url_text(images[int(index)])
except Exception as e:
shared.log.error(f'Extra network save preview: {filename} {e}')
return
is_allowed = False
for page in extra_pages:
if any(path_is_parent(x, filename) for x in page.allowed_directories_for_previews()):
is_allowed = True
break
if not is_allowed:
shared.log.error(f'Extra network save preview: {filename} not allowed')
return
if image.width > 512 or image.height > 512:
image = image.convert('RGB')
image.thumbnail((512, 512), Image.HAMMING)
image.save(filename, quality=50)
fn, _ext = os.path.splitext(filename)
thumb = fn + '.thumb.jpg'
if os.path.exists(thumb):
shared.log.debug(f'Extra network delete thumbnail: {thumb}')
os.remove(thumb)
shared.log.info(f'Extra network save preview: {filename}')
def save_description(self, filename, desc):
lastDotIndex = filename.rindex('.')
filename = filename[0:lastDotIndex]+".txt"
if desc != "":
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(desc)
shared.log.info(f'Extra network save description: {filename} {desc}')
except Exception as e:
shared.log.error(f'Extra network save description: {filename} {e}')
def initialize():
extra_pages.clear()
def register_default_pages():
def register_pages():
from modules.ui_extra_networks_textual_inversion import ExtraNetworksPageTextualInversion
from modules.ui_extra_networks_hypernets import ExtraNetworksPageHypernetworks
from modules.ui_extra_networks_checkpoints import ExtraNetworksPageCheckpoints
@@ -360,22 +395,22 @@ def create_ui(container, button, tabname, skip_indexing = False):
is_visible = not is_visible
return is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary"))
state_visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated
button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button])
button_close.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container])
def refresh(title):
def en_refresh(title):
res = []
for page in extra_pages:
if title == '' or title == page.title or len(page.html) == 0:
if title is None or title == '' or title == page.title or len(page.html) == 0:
page.refresh()
page.refresh_time = None
page.create_page(ui.tabname)
shared.log.debug(f"Refreshing Extra networks: page={page.title} items={len(page.items)} tab={ui.tabname}")
shared.log.debug(f"Refreshing Extra networks: page='{page.title}' items={len(page.items)} tab={ui.tabname}")
res.append(page.html)
ui.search.update(value = ui.search.value)
return res
button_refresh.click(_js='extraNetworksRefreshButton', fn=refresh, inputs=[ui.search], outputs=ui.pages)
state_visible = gr.State(value=False) # pylint: disable=abstract-class-instantiated
button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container, button])
button_close.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container])
button_refresh.click(_js='getENActivePage', fn=en_refresh, inputs=[ui.search], outputs=ui.pages)
return ui
@@ -387,54 +422,37 @@ def path_is_parent(parent_path, child_path):
def setup_ui(ui, gallery):
def save_preview(index, images, filename):
if len(images) == 0:
for page in extra_pages:
page.create_page(ui.tabname)
return [page.html for page in extra_pages]
index = int(index)
index = 0 if index < 0 else index
index = len(images) - 1 if index >= len(images) else index
img_info = images[index if index >= 0 else 0]
image = image_from_url_text(img_info)
is_allowed = False
for extra_page in extra_pages:
if any(path_is_parent(x, filename) for x in extra_page.allowed_directories_for_previews()):
is_allowed = True
break
assert is_allowed, f'writing to {filename} is not allowed'
image.save(filename)
fn, _ext = os.path.splitext(filename)
thumb = fn + '.thumb.jpg'
if os.path.exists(thumb):
shared.log.debug(f'Extra network delete thumbnail: {thumb}')
os.remove(thumb)
shared.log.info(f'Extra network save preview: {filename}')
return [page.create_page(ui.tabname) for page in extra_pages]
def save_preview(pagename, index, images, filename):
res = []
for page in extra_pages:
if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0:
page.save_preview(index, images, filename)
res.append(page.create_page(ui.tabname))
else:
res.append(page.html)
return res
ui.button_save_preview.click(
fn=save_preview,
_js="function(x, y, z) {return [selected_gallery_index(), y, z]}",
inputs=[ui.preview_target_filename, gallery, ui.preview_target_filename],
outputs=[*ui.pages]
_js="function(t, i, y, z) {return [getENActivePage(), selected_gallery_index(), y, z]}",
inputs=[ui.search, ui.preview_target_filename, gallery, ui.preview_target_filename],
outputs=ui.pages
)
# write description to a file
def save_description(filename, desc):
lastDotIndex = filename.rindex('.')
filename = filename[0:lastDotIndex]+".txt"
if desc != "":
try:
with open(filename,'w', encoding='utf-8') as f:
f.write(desc)
shared.log.info(f'Extra network save description: {filename} {desc}')
except Exception as e:
shared.log.error(f'Extra network save description: {filename} {e}')
return [page.create_page(ui.tabname) for page in extra_pages]
def save_description(pagename, filename, desc):
res = []
for page in extra_pages:
if pagename is None or pagename == '' or pagename == page.title or len(page.html) == 0:
page.save_description(filename, desc)
res.append(page.create_page(ui.tabname))
else:
res.append(page.html)
return res
ui.button_save_description.click(
fn=save_description,
_js="function(x, y) { return [x, y] }",
inputs=[ui.description_target_filename, ui.description],
outputs=[*ui.pages]
_js="function(t, x, y) { return [getENActivePage(), x, y] }",
inputs=[ui.search, ui.description_target_filename, ui.description],
outputs=ui.pages
)
+44 -14
View File
@@ -1,7 +1,6 @@
import os
import html
import json
from modules import shared, ui_extra_networks
@@ -12,22 +11,53 @@ class ExtraNetworksPageStyles(ui_extra_networks.ExtraNetworksPage):
def refresh(self):
shared.prompt_styles.reload()
"""
import io
import base64
from PIL import Image
def image2str(image):
buff = io.BytesIO()
image.save(buff, format="JPEG", quality=80)
encoded = base64.b64encode(buff.getvalue())
return encoded
def str2image(data):
buff = io.BytesIO(base64.b64decode(data))
return Image.open(buff)
def save_preview(self, index, images, filename):
from modules.generation_parameters_copypaste import image_from_url_text
try:
image = image_from_url_text(images[int(index)])
except Exception:
shared.log.error(f'Extra network save preview: {filename} no image')
return
if image.width > 512 or image.height > 512:
image = image.convert('RGB').thumbnail((512, 512), Image.HAMMING)
for k in shared.prompt_styles.styles.keys():
if k == filename:
shared.prompt_styles.styles[k].preview = image2str(image)
break
def save_description(self, filename, desc):
pass
"""
def list_items(self):
styles = list(shared.prompt_styles.styles)
for style in styles:
path = os.path.join(shared.opts.styles_dir, style)
txt = f'Prompt: {shared.prompt_styles.styles[style].prompt}'
negative = shared.prompt_styles.styles[style].negative_prompt
if negative is not None and len(negative) > 0:
txt += f'\nNegative: {negative}'
for k, v in shared.prompt_styles.styles.items():
fn = os.path.join(shared.opts.styles_dir, v.filename)
txt = f'Prompt: {v.prompt}'
if len(v.negative_prompt) > 0:
txt += f'\nNegative: {v.negative_prompt}'
yield {
"name": style,
"search_term": path,
"filename": path,
"preview": self.find_preview(path),
"name": v.name,
"search_term": f'{txt} /{v.filename}',
"filename": v.filename,
"preview": self.find_preview(fn),
"description": txt,
"onclick": '"' + html.escape(f"""return selectStyle({json.dumps(style)})""") + '"',
"local_preview": f"{path}.{shared.opts.samples_format}",
"onclick": '"' + html.escape(f"""return selectStyle({json.dumps(k)})""") + '"',
"local_preview": f"{fn}.{shared.opts.samples_format}",
}
def allowed_directories_for_previews(self):
+15 -5
View File
@@ -21,12 +21,18 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage):
def list_items(self):
if sd_models.model_data.sd_model is None:
embeddings = []
for root, _dirs, fns in os.walk(shared.opts.embeddings_dir, followlinks=True):
for fn in fns:
if fn.lower().endswith(".pt") or fn.lower().endswith(".safetensors"):
embedding = Embedding(0, fn)
embedding.filename = os.path.join(root, fn)
def list_folder(folder):
for filename in os.listdir(folder):
fn = os.path.join(folder, filename)
if os.path.isfile(fn) and (fn.lower().endswith(".pt") or fn.lower().endswith(".safetensors")):
embedding = Embedding(0, os.path.basename(fn))
embedding.filename = fn
embeddings.append(embedding)
elif os.path.isdir(fn) and not fn.startswith('.'):
list_folder(fn)
list_folder(shared.opts.embeddings_dir)
elif shared.backend == shared.Backend.ORIGINAL:
embeddings = list(sd_hijack.model_hijack.embedding_db.word_embeddings.values())
elif hasattr(sd_models.model_data.sd_model, 'embedding_db'):
@@ -35,6 +41,9 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage):
embeddings = []
for embedding in embeddings:
path, _ext = os.path.splitext(embedding.filename)
tags = {}
if embedding.tag is not None:
tags[embedding.tag]=1
yield {
"name": os.path.splitext(embedding.name)[0],
"filename": embedding.filename,
@@ -44,6 +53,7 @@ class ExtraNetworksPageTextualInversion(ui_extra_networks.ExtraNetworksPage):
"search_term": self.search_terms_from_path(embedding.filename),
"prompt": json.dumps(os.path.splitext(embedding.name)[0]),
"local_preview": f"{path}.preview.{shared.opts.samples_format}",
"tags": tags,
}
def allowed_directories_for_previews(self):
+34 -13
View File
@@ -112,7 +112,6 @@ class UiLoadsave:
self.write_to_file(self.ui_settings)
def iter_changes(self, values):
from modules.shared import log
"""
given a dictionary with defaults from a file and current values from gradio elements, returns
an iterator over tuples of values that are not the same between the file and the current;
@@ -138,27 +137,49 @@ class UiLoadsave:
continue
if (new_value == default_value) and (old_value is None):
continue
log.debug(f'Settings: name={name} component={component} old={old_value} default={default_value} new={new_value}')
yield name, old_value, new_value, default_value
return []
def ui_view(self, *values):
text = ['<table style="width: -webkit-fill-available"><thead style="font-size: 110%; border-style: solid; border-bottom: 1px var(--button-primary-border-color) solid"><tr><th>Variable</th><th>User value</th><th>New value</th><th>Default value</th></thead><tbody>']
for path, old_value, new_value, default_value in self.iter_changes(values):
text = """
<table id="ui-defauls">
<colgroup>
<col style="width: 20%; background: var(--table-border-color)">
<col style="width: 10%; background: var(--panel-background-fill)">
<col style="width: 10%; background: var(--panel-background-fill)">
<col style="width: 10%; background: var(--panel-background-fill)">
</colgroup>
<thead style="font-size: 110%; border-style: solid; border-bottom: 1px var(--button-primary-border-color) solid">
<tr>
<th>Name</th>
<th>Saved value</th>
<th>New value</th>
<th>Default value</th>
</tr>
</thead>
<tbody>"""
changed = 0
for name, old_value, new_value, default_value in self.iter_changes(values):
changed += 1
if old_value is None:
old_value = "<span class='ui-defaults-none'>None</span>"
text.append(f"<tr><td>{path}</td><td>{old_value}</td><td>{new_value}</td><td>{default_value}</td></tr>")
if len(text) == 1:
text.append("<tr><td colspan=3>No changes</td></tr>")
text.append("</tbody>")
return "".join(text)
old_value = "None"
text += f"<tr><td>{name}</td><td>{old_value}</td><td>{new_value}</td><td>{default_value}</td></tr>"
text += "</tbody></table>"
if changed == 0:
text = '<h2>No changes</h2>'
else:
text = f'<h2>Changed values: {changed}</h2>' + text
return text
def ui_apply(self, *values):
from modules.shared import log
num_changed = 0
current_ui_settings = self.read_from_file()
for path, _, new_value, _ in self.iter_changes(values):
for name, old_value, new_value, default_value in self.iter_changes(values):
component = self.component_mapping[name]
log.debug(f'Settings: name={name} component={component} old={old_value} default={default_value} new={new_value}')
num_changed += 1
current_ui_settings[path] = new_value
current_ui_settings[name] = new_value
if num_changed == 0:
return "No changes"
self.write_to_file(current_ui_settings)
@@ -173,12 +194,12 @@ class UiLoadsave:
def create_ui(self):
"""creates ui elements for editing defaults UI, without adding any logic to them"""
gr.HTML(f"Review changed values and apply them as new user interface defaults<br>Config file: {self.filename}")
with gr.Row(elem_id="config_row"):
self.ui_defaults_view = gr.Button(value='View changes', elem_id="ui_defaults_view", variant="secondary")
self.ui_defaults_apply = gr.Button(value='Set new defaults', elem_id="ui_defaults_apply", variant="primary")
self.ui_defaults_restore = gr.Button(value='Restore system defaults', elem_id="ui_defaults_restore", variant="primary")
self.ui_defaults_review = gr.HTML("")
gr.HTML(f"Review changed values and apply them as new user interface defaults<br><br>Config file: {self.filename}")
def setup_ui(self):
"""adds logic to elements created with create_ui; all add_block class must be made before this"""
+27 -18
View File
@@ -8,6 +8,7 @@ from modules.ui_common import create_refresh_button
from modules.call_queue import wrap_gradio_gpu_call
from modules.shared import opts, log
import modules.errors
import modules.hashes
def create_ui():
@@ -201,19 +202,15 @@ def create_ui():
def hf_download_model(hub_id: str, token, variant, revision, mirror):
from modules.modelloader import download_diffusers_model
try:
download_diffusers_model(hub_id, cache_dir=opts.diffusers_dir, token=token, variant=variant, revision=revision, mirror=mirror)
except Exception as e:
log.error(f"Diffuser model downloaded error: model={hub_id} {e}")
return f"Diffuser model downloaded error: model={hub_id} {e}"
download_diffusers_model(hub_id, cache_dir=opts.diffusers_dir, token=token, variant=variant, revision=revision, mirror=mirror)
from modules.sd_models import list_models # pylint: disable=W0621
list_models()
log.info(f"Diffuser model downloaded: model={hub_id}")
return f'Diffuser model downloaded: model={hub_id}'
log.info(f'Diffuser model downloaded: model="{hub_id}"')
return f'Diffuser model downloaded: model="{hub_id}"'
with gr.Column(scale=6):
with gr.Row():
hf_search_text = gr.Textbox('', label = 'Seach models', placeholder='search huggingface models')
hf_search_text = gr.Textbox('', label = 'Search models', placeholder='search huggingface models')
hf_search_btn = ToolButton(value="🔍", label="Search")
with gr.Row():
with gr.Column(scale=2):
@@ -252,7 +249,7 @@ def create_ui():
if tag is not None and len(tag) > 0:
url += f'&tag={tag}'
r = requests.get(url, timeout=60, headers=headers)
log.debug(f'CivitAI search: name={name} tag={tag} status={r.status_code}')
log.debug(f'CivitAI search: name="{name}" tag={tag or "none"} status={r.status_code}')
if r.status_code != 200:
return [], [], []
body = r.json()
@@ -261,6 +258,8 @@ def create_ui():
data1 = []
for model in data:
found = 0
if model_type == 'LoRA' and model['type'] == 'LORA':
found += 1
for variant in model['modelVersions']:
if model_type == 'SD 1.5':
if 'SD 1.' in variant['baseModel']:
@@ -297,7 +296,7 @@ def create_ui():
d['baseModel'],
d['createdAt'],
])
log.debug(f'CivitAI select: model={in_data[evt.index[0]]} versions={len(data2)}')
log.debug(f'CivitAI select: model="{in_data[evt.index[0]]}" versions={len(data2)}')
return data2, preview_img
def civit_select2(evt: gr.SelectData, in_data):
@@ -315,7 +314,7 @@ def create_ui():
json.dumps(f['metadata']),
f['downloadUrl'],
])
log.debug(f'CivitAI select: model={in_data[evt.index[0]]} files={len(data3)}')
log.debug(f'CivitAI select: model="{in_data[evt.index[0]]}" files={len(data3)}')
return data3
def civit_select3(evt: gr.SelectData, in_data):
@@ -336,7 +335,7 @@ def create_ui():
list_models()
return res
def civit_download_previews():
def civit_download_previews(civit_previews_rehash):
import requests
from modules.ui_extra_networks import extra_pages
from modules.modelloader import download_civit_preview
@@ -347,17 +346,26 @@ def create_ui():
if item.get('fullname', None) is None:
continue
if 'card-no-preview.png' in item['preview'] and os.path.isfile(item['fullname']):
sha = item.get('hash', None)
if item.get('hash', None) is None:
log.debug(f'CivitAI skipping item without hash: name={item["name"]}')
log.debug(f'CivitAI skipping item without hash: name="{item["name"]}"')
continue
url = f'https://civitai.com/api/v1/model-versions/by-hash/{item["hash"]}'
r = requests.get(url, timeout=5, headers=headers)
log.debug(f'CivitAI search: name={item["name"]} hash={item["hash"]} status={r.status_code}')
r = requests.get(f'https://civitai.com/api/v1/model-versions/by-hash/{sha}', timeout=5, headers=headers)
log.debug(f'CivitAI search: name="{item["name"]}" hash={sha} status={r.status_code}')
if r.status_code == 200:
d = r.json()
if d.get('images') is not None and len(d['images']) > 0 and len(d['images'][0]['url']) > 0:
preview_url = d['images'][0]['url']
res += download_civit_preview(item['filename'], preview_url) + '<br>'
elif civit_previews_rehash and os.stat(item['fullname']).st_size < (1024 * 1024 * 1024):
sha = modules.hashes.calculate_sha256(item['fullname'], quiet=True)
r = requests.get(f'https://civitai.com/api/v1/model-versions/by-hash/{sha}', timeout=5, headers=headers)
log.debug(f'CivitAI search: name="{item["name"]}" hash={sha} status={r.status_code}')
if r.status_code == 200:
d = r.json()
if d.get('images') is not None and len(d['images']) > 0 and len(d['images'][0]['url']) > 0:
preview_url = d['images'][0]['url']
res += download_civit_preview(item['filename'], preview_url) + '<br>'
return res
with gr.Row():
@@ -365,7 +373,7 @@ def create_ui():
civit_model_type = gr.Dropdown(label='Model type', choices=['SD 1.5', 'SD XL', 'LoRA', 'Other'], value='LoRA')
with gr.Column(scale=15):
with gr.Row():
civit_search_text = gr.Textbox('', label = 'Seach models', placeholder='keyword')
civit_search_text = gr.Textbox('', label = 'Search models', placeholder='keyword')
civit_search_tag = gr.Textbox('', label = '', placeholder='tags')
civit_search_btn = ToolButton(value="🔍", label="Search", interactive=False)
with gr.Row():
@@ -389,6 +397,7 @@ def create_ui():
civit_results1 = gr.DataFrame(value = None, label = 'Search results', show_label = True, interactive = False, wrap = True, overflow_row_behaviour = 'paginate', max_rows = 10, headers = civit_headers1, datatype = civit_types1, type='array')
with gr.Row():
civit_previews_btn = gr.Button(value="Fetch previews for existing models", variant='primary')
civit_previews_rehash = gr.Checkbox(value=False, label="Check alternative hash")
civit_search_text.submit(fn=civit_search, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3])
civit_search_tag.submit(fn=civit_search, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3])
@@ -397,4 +406,4 @@ def create_ui():
civit_results2.select(fn=civit_select2, inputs=[civit_results2], outputs=[civit_results3])
civit_results3.select(fn=civit_select3, inputs=[civit_results3], outputs=[civit_selected, civit_name, civit_search_btn])
civit_download_model_btn.click(fn=civit_download_model, inputs=[civit_selected, civit_name, civit_path, civit_model_type, models_image], outputs=[models_outcome])
civit_previews_btn.click(fn=civit_download_previews, inputs=[], outputs=[models_outcome])
civit_previews_btn.click(fn=civit_download_previews, inputs=[civit_previews_rehash], outputs=[models_outcome])
+13 -1
View File
@@ -1,3 +1,14 @@
refresh = ''
close = '🗙'
load = ''
save = ''
apply = ''
clear = ''
fill = ''
networks = '🗁'
paste = ''
"""
refresh = '🔄'
close = '🛗'
load = '⬆️'
@@ -6,9 +17,10 @@ apply = '⏩'
clear = '🚮'
fill = ''
networks = '🌐'
paste = '📘'
"""
switch = ''
detect = '📐'
folder = '📂'
random = '🎲️'
reuse = '♻️'
paste = '📘'
+4 -4
View File
@@ -44,13 +44,13 @@ fasteners
typing-extensions==4.7.1
antlr4-python3-runtime==4.9.3
requests==2.31.0
tqdm==4.65.0
tqdm==4.66.1
accelerate==0.20.3
opencv-python-headless==4.7.0.72
diffusers==0.20.2
diffusers==0.21.1
einops==0.4.1
gradio==3.41.2
huggingface_hub==0.16.4
gradio==3.43.2
huggingface_hub==0.17.1
numexpr==2.8.4
numpy==1.24.4
numba==0.57.1
+2 -3
View File
@@ -264,8 +264,7 @@ class Script(scripts.Script):
all_images = all_processed_images
combined_grid_image = images.image_grid(all_processed_images)
unwanted_grid_because_of_img_count = len(all_processed_images) < 2 and opts.grid_only_if_multiple
if opts.return_grid and not unwanted_grid_because_of_img_count:
if opts.return_grid and len(all_processed_images) > 1:
all_images = [combined_grid_image] + all_processed_images
res = Processed(p, all_images, initial_seed_and_info[0], initial_seed_and_info[1])
@@ -274,7 +273,7 @@ class Script(scripts.Script):
for img in all_processed_images:
images.save_image(img, p.outpath_samples, "", res.seed, p.prompt, opts.samples_format, info=res.info, p=p)
if opts.grid_save and not unwanted_grid_because_of_img_count:
if opts.grid_save and len(all_processed_images) > 1:
images.save_image(combined_grid_image, p.outpath_grids, "grid", res.seed, p.prompt, opts.samples_format, info=res.info, short_filename=not opts.grid_extended_filename, grid=True, p=p)
return res
+2 -2
View File
@@ -39,7 +39,7 @@ def draw_xy_grid(xs, ys, x_label, y_label, cell):
class Script(scripts.Script):
def title(self):
return "Prompt matrix"
return "Prompt Matrix"
def ui(self, is_img2img):
gr.HTML('<br />')
@@ -92,7 +92,7 @@ class Script(scripts.Script):
p.prompt = all_prompts
else:
p.negative_prompt = all_prompts
p.seed = [p.seed + (i if different_seeds else 0) for i in range(len(all_prompts))]
p.seed = [int(p.seed + (i if different_seeds else 0)) for i in range(len(all_prompts))]
p.prompt_for_display = positive_prompt
processed = process_images(p)
+1 -1
View File
@@ -101,7 +101,7 @@ def load_prompt_file(file):
class Script(scripts.Script):
def title(self):
return "Prompts from file"
return "Prompts from File"
def ui(self, is_img2img):
checkbox_iterate = gr.Checkbox(label="Iterate seed every line", value=False, elem_id=self.elem_id("checkbox_iterate"))
+5 -5
View File
@@ -375,7 +375,7 @@ re_range_count_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+
class Script(scripts.Script):
def title(self):
return "X/Y/Z grid"
return "X/Y/Z Grid"
def ui(self, is_img2img):
self.current_axis_options = [x for x in axis_options if type(x) == AxisOption or x.is_img2img == is_img2img]
@@ -400,10 +400,10 @@ class Script(scripts.Script):
fill_z_button = ToolButton(value=symbols.fill, elem_id="xyz_grid_fill_z_tool_button", visible=False)
with gr.Row(variant="compact", elem_id="axis_options"):
draw_legend = gr.Checkbox(label='Draw legend', value=True, elem_id=self.elem_id("draw_legend"))
no_fixed_seeds = gr.Checkbox(label='Keep random for seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"))
no_grid = gr.Checkbox(label='Do not create grid', value=False, elem_id=self.elem_id("no_xyz_grid"))
include_lone_images = gr.Checkbox(label='Include Sub Images', value=False, elem_id=self.elem_id("include_lone_images"))
include_sub_grids = gr.Checkbox(label='Include Sub Grids', value=False, elem_id=self.elem_id("include_sub_grids"))
no_fixed_seeds = gr.Checkbox(label='Keep random seeds', value=False, elem_id=self.elem_id("no_fixed_seeds"))
no_grid = gr.Checkbox(label='Skip grid', value=False, elem_id=self.elem_id("no_xyz_grid"))
include_lone_images = gr.Checkbox(label='Include sub images', value=False, elem_id=self.elem_id("include_lone_images"))
include_sub_grids = gr.Checkbox(label='Include sub grids', value=False, elem_id=self.elem_id("include_sub_grids"))
with gr.Row(variant="compact", elem_id="axis_options"):
margin_size = gr.Slider(label="Grid margins", minimum=0, maximum=500, value=0, step=2, elem_id=self.elem_id("margin_size"))
with gr.Row(variant="compact", elem_id="swap_axes"):
+7 -7
View File
@@ -7,7 +7,7 @@ mkdir tmp 2>NUL
%PYTHON% -c "" >tmp/stdout.txt 2>tmp/stderr.txt
if %ERRORLEVEL% == 0 goto :check_pip
echo Couldn't launch python
echo Cannot launch python
goto :show_stdout_stderr
:check_pip
@@ -16,7 +16,7 @@ if %ERRORLEVEL% == 0 goto :start_venv
if "%PIP_INSTALLER_LOCATION%" == "" goto :show_stdout_stderr
%PYTHON% "%PIP_INSTALLER_LOCATION%" >tmp/stdout.txt 2>tmp/stderr.txt
if %ERRORLEVEL% == 0 goto :start_venv
echo Couldn't install pip
echo Cannot install pip
goto :show_stdout_stderr
:start_venv
@@ -27,10 +27,11 @@ dir "%VENV_DIR%\Scripts\Python.exe" >tmp/stdout.txt 2>tmp/stderr.txt
if %ERRORLEVEL% == 0 goto :activate_venv
for /f "delims=" %%i in ('CALL %PYTHON% -c "import sys; print(sys.executable)"') do set PYTHON_FULLNAME="%%i"
echo Creating venv in directory %VENV_DIR% using python %PYTHON_FULLNAME%
echo Using python: %PYTHON_FULLNAME%
echo Creating VENV: %VENV_DIR%
%PYTHON_FULLNAME% -m venv "%VENV_DIR%" >tmp/stdout.txt 2>tmp/stderr.txt
if %ERRORLEVEL% == 0 goto :activate_venv
echo Unable to create venv in directory "%VENV_DIR%"
echo Failed creating VENV: "%VENV_DIR%"
goto :show_stdout_stderr
:activate_venv
@@ -42,7 +43,6 @@ if [%ACCELERATE%] == ["True"] goto :accelerate
goto :launch
:accelerate
echo Checking for accelerate: %ACCELERATE%
set ACCELERATE="%VENV_DIR%\Scripts\accelerate.exe"
if EXIST %ACCELERATE% goto :accelerate_launch
@@ -52,7 +52,7 @@ pause
exit /b
:accelerate_launch
echo Accelerating
echo Using accelerate
%ACCELERATE% launch --num_cpu_threads_per_process=6 launch.py %*
pause
exit /b
@@ -78,5 +78,5 @@ type tmp\stderr.txt
:endofscript
echo.
echo Launch unsuccessful. Exiting.
echo Launch Failed
pause
+38 -36
View File
@@ -1,3 +1,4 @@
import io
import os
import sys
import glob
@@ -5,19 +6,17 @@ import signal
import asyncio
import logging
import importlib
import contextlib
from threading import Thread
import modules.loader
import torch # pylint: disable=wrong-import-order
from modules import timer, errors, paths # pylint: disable=unused-import
local_url = None
if not modules.loader.initialized:
errors.log.debug('Loading modules')
from installer import log, setup_logging, git_commit
from installer import log, git_commit, custom_excepthook
import ldm.modules.encoders.modules # pylint: disable=W0611,C0411,E0401
from modules.call_queue import queue_lock, wrap_queued_call, wrap_gradio_gpu_call # pylint: disable=W0611,C0411,C0412
from modules import shared, extensions, extra_networks, ui_tempdir, ui_extra_networks, modelloader # pylint: disable=ungrouped-imports
from modules.paths import create_paths
from modules import shared, extensions, extra_networks, ui_tempdir, ui_extra_networks, modelloader
from modules.call_queue import queue_lock, wrap_queued_call, wrap_gradio_gpu_call # pylint: disable=W0611,C0411,C0412
import modules.devices
import modules.sd_samplers
import modules.upscaler
@@ -39,9 +38,11 @@ from modules.shared import cmd_opts, opts
import modules.hypernetworks.hypernetwork
from modules.middleware import setup_middleware
sys.excepthook = custom_excepthook
state = shared.state
if not modules.loader.initialized:
timer.startup.record("libraries")
log.info('Loaded librareis')
log.setLevel(logging.DEBUG if cmd_opts.debug else logging.INFO)
logging.disable(logging.NOTSET if cmd_opts.debug else logging.DEBUG)
if cmd_opts.server_name:
@@ -63,6 +64,7 @@ fastapi_args = {
}
modules.loader.initialized = True
def check_rollback_vae():
if shared.cmd_opts.rollback_vae:
if not torch.cuda.is_available():
@@ -77,7 +79,7 @@ def check_rollback_vae():
def initialize():
log.debug('Entering initialize')
log.debug('Initializing')
check_rollback_vae()
@@ -104,7 +106,6 @@ def initialize():
t_timer, t_total = modules.scripts.load_scripts()
timer.startup.record("extensions")
timer.startup.records["extensions"] = t_total # scripts can reset the time
setup_logging() # reset since scripts can hijaack logging
log.info(f'Extensions time: {t_timer.summary()}')
modelloader.load_upscalers()
@@ -117,9 +118,10 @@ def initialize():
modules.textual_inversion.textual_inversion.list_textual_inversion_templates()
shared.reload_hypernetworks()
shared.prompt_styles.reload()
ui_extra_networks.initialize()
ui_extra_networks.register_default_pages()
ui_extra_networks.register_pages()
extra_networks.initialize()
extra_networks.register_default_extra_networks()
timer.startup.record("extra-networks")
@@ -152,8 +154,7 @@ def initialize():
def load_model():
if opts.sd_checkpoint_autoload:
shared.state.begin()
shared.state.job = 'load model'
shared.state.begin('load model')
thread_model = Thread(target=lambda: shared.sd_model)
thread_model.start()
thread_refiner = Thread(target=lambda: shared.sd_refiner)
@@ -198,20 +199,18 @@ def async_policy():
super().__init__()
self.loop = self.get_event_loop()
self.loop.set_exception_handler(self.handle_exception)
log.debug(f"Event loop: {self.loop}")
# log.debug(f"Event loop: {self.loop}")
asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())
def start_common():
log.debug('Entering start sequence')
if cmd_opts.debug and hasattr(shared, 'get_version'):
log.debug(f'Version: {shared.get_version()}')
logging.disable(logging.NOTSET if cmd_opts.debug else logging.DEBUG)
if shared.cmd_opts.data_dir is not None and len(shared.cmd_opts.data_dir) > 0:
log.info(f'Using data path: {shared.cmd_opts.data_dir}')
if shared.cmd_opts.models_dir is not None and len(shared.cmd_opts.models_dir) > 0:
log.info(f'Using models path: {shared.cmd_opts.data_dir}')
if shared.cmd_opts.models_dir is not None and len(shared.cmd_opts.models_dir) > 0 and shared.cmd_opts.models_dir != 'models':
log.info(f'Using models path: {shared.cmd_opts.models_dir}')
create_paths(opts, log)
async_policy()
initialize()
@@ -244,28 +243,30 @@ def start_ui():
gradio_auth_creds += [x.strip() for x in line.split(',') if x.strip()]
global local_url # pylint: disable=global-statement
app, local_url, share_url = shared.demo.launch( # app is FastAPI(Starlette) instance
share=cmd_opts.share,
server_name=server_name,
server_port=cmd_opts.port if cmd_opts.port != 7860 else None,
ssl_keyfile=cmd_opts.tls_keyfile,
ssl_certfile=cmd_opts.tls_certfile,
ssl_verify=not cmd_opts.tls_selfsign,
debug=False,
auth=[tuple(cred.split(':')) for cred in gradio_auth_creds] if gradio_auth_creds else None,
prevent_thread_lock=True,
max_threads=64,
show_api=False,
quiet=True,
favicon_path='html/logo.ico',
allowed_paths=[os.path.dirname(__file__), cmd_opts.data_dir],
app_kwargs=fastapi_args,
)
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
app, local_url, share_url = shared.demo.launch( # app is FastAPI(Starlette) instance
share=cmd_opts.share,
server_name=server_name,
server_port=cmd_opts.port if cmd_opts.port != 7860 else None,
ssl_keyfile=cmd_opts.tls_keyfile,
ssl_certfile=cmd_opts.tls_certfile,
ssl_verify=not cmd_opts.tls_selfsign,
debug=False,
auth=[tuple(cred.split(':')) for cred in gradio_auth_creds] if gradio_auth_creds else None,
prevent_thread_lock=True,
max_threads=64,
show_api=False,
quiet=True,
favicon_path='html/logo.ico',
allowed_paths=[os.path.dirname(__file__), cmd_opts.data_dir],
app_kwargs=fastapi_args,
)
if cmd_opts.data_dir is not None:
ui_tempdir.register_tmp_file(shared.demo, os.path.join(cmd_opts.data_dir, 'x'))
shared.log.info(f'Local URL: {local_url}')
if cmd_opts.docs:
shared.log.info(f'API Docs: {local_url[:-1]}/docs') # {local_url[:-1]}?view=api
shared.log.info(f'API Docs: {local_url[:-1]}/docs') # pylint: disable=unsubscriptable-object
if share_url is not None:
shared.log.info(f'Share URL: {share_url}')
shared.log.debug(f'Gradio registered functions: {len(shared.demo.fns)}')
@@ -291,7 +292,8 @@ def start_ui():
time_setup = [f'{k}:{round(v,3)}s' for (k,v) in modules.scripts.time_setup.items() if v > 0.005]
shared.log.debug(f'Scripts setup: {time_setup}')
time_component = [f'{k}:{round(v,3)}s' for (k,v) in modules.scripts.time_component.items() if v > 0.005]
shared.log.debug(f'Scripts components: {time_component}')
if len(time_component) > 0:
shared.log.debug(f'Scripts components: {time_component}')
def webui(restart=False):
+1 -1
Submodule wiki updated: de31c082f8...fea51bf38c