From 397d7ea6de6f9dc6642aa89e3b63336f6a8ebad1 Mon Sep 17 00:00:00 2001 From: AI-Casanova <54461896+AI-Casanova@users.noreply.github.com> Date: Sun, 30 Jul 2023 22:26:55 -0500 Subject: [PATCH 01/72] Add Compel Parsing for SDXL --- modules/processing_diffusers.py | 45 ++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 7c80909de..01b676e40 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -7,6 +7,7 @@ import modules.sd_models as sd_models import modules.images as images from modules.lora_diffusers import lora_state, unload_diffusers_lora from modules.processing import StableDiffusionProcessing +from compel import Compel, ReturnedEmbeddingsType try: @@ -23,6 +24,28 @@ def encode_prompt(encoder, prompt): shared.log.debug(f'Diffuser encoder: {encoder.__class__.__name__} dict={getattr(cfg, "vocab_size", None)} layers={getattr(cfg, "num_hidden_layers", None)} tokens={getattr(cfg, "max_position_embeddings", None)}') embeds = prompt return embeds + +def compel_encode_prompt(pipeline, prompt, negative_prompt): + compel = Compel( + truncate_long_prompts=True, + tokenizer=[ + pipeline.tokenizer, + pipeline.tokenizer_2 + ], + text_encoder=[ + pipeline.text_encoder, + pipeline.text_encoder_2 + ], + returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, + requires_pooled=[ + False, + True + ] + ) + prompt_embed, pooled = compel(prompt) + negative_embed, negative_pooled = compel(negative_prompt) + [prompt_embed, negative_embed] = compel.pad_conditioning_tensors_to_same_length([prompt_embed, negative_embed]) + return prompt_embed, pooled, negative_embed, negative_pooled def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_prompts): @@ -58,16 +81,22 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro possible = signature.parameters.keys() generator_device = devices.cpu if shared.opts.diffusers_generator_device == "cpu" else shared.device generator = [torch.Generator(generator_device).manual_seed(s) for s in seeds] + prompt_embed = None + pooled = None + negative_embed = None + negative_pooled = None + if shared.opts.data['prompt_attention'] == 'Compel parser' and (shared.opts.diffusers_pipeline == shared.pipelines[1] or shared.opts.diffusers_pipeline == shared.pipelines[7]): #Gated for SDXL only + prompt_embed, pooled, negative_embed, negative_pooled = compel_encode_prompt(model, prompt, negative_prompt) if 'prompt' in possible: - if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible: - # args['prompt_embeds'] = encode_prompt(model, prompt) - args['prompt'] = prompt + if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None: + args['prompt_embeds'] = prompt_embed + args['pooled_prompt_embeds'] = pooled else: args['prompt'] = prompt if 'negative_prompt' in possible: - if hasattr(model, 'text_encoder') and 'negative_prompt_embeds' in possible: - # args['negative_prompt_embeds'] = encode_prompt(model, negative_prompt) - args['negative_prompt'] = negative_prompt + if hasattr(model, 'text_encoder') and 'negative_prompt_embeds' in possible and negative_embed is not None: + args['negative_prompt_embeds'] = negative_embed + args['negative_pooled_prompt_embeds'] = negative_pooled else: args['negative_prompt'] = negative_prompt if 'num_inference_steps' in possible: @@ -89,6 +118,10 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro args[arg] = kwargs[arg] else: pass + if prompt_embed is not None: #Cannot pass prompts when passing embeds + del args['prompt_2'] + del args['negative_prompt_2'] + # shared.log.debug(f'Diffuser not supported: pipeline={pipeline.__class__.__name__} task={sd_models.get_diffusers_task(model)} arg={arg}') # shared.log.debug(f'Diffuser pipeline: {pipeline.__class__.__name__} possible={possible}') clean = args.copy() From b166dcbfad89b75fb54270edc9a444177f0faeb3 Mon Sep 17 00:00:00 2001 From: AI-Casanova Date: Tue, 1 Aug 2023 02:46:53 +0000 Subject: [PATCH 02/72] Add prompt_parser_diffusers.py --- modules/processing_diffusers.py | 42 +++++++----------------------- modules/prompt_parser_diffusers.py | 36 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 modules/prompt_parser_diffusers.py diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 01b676e40..9dcfdc954 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -7,8 +7,7 @@ import modules.sd_models as sd_models import modules.images as images from modules.lora_diffusers import lora_state, unload_diffusers_lora from modules.processing import StableDiffusionProcessing -from compel import Compel, ReturnedEmbeddingsType - +import modules.prompt_parser_diffusers as prompt_parser_diffusers try: import diffusers @@ -24,28 +23,6 @@ def encode_prompt(encoder, prompt): shared.log.debug(f'Diffuser encoder: {encoder.__class__.__name__} dict={getattr(cfg, "vocab_size", None)} layers={getattr(cfg, "num_hidden_layers", None)} tokens={getattr(cfg, "max_position_embeddings", None)}') embeds = prompt return embeds - -def compel_encode_prompt(pipeline, prompt, negative_prompt): - compel = Compel( - truncate_long_prompts=True, - tokenizer=[ - pipeline.tokenizer, - pipeline.tokenizer_2 - ], - text_encoder=[ - pipeline.text_encoder, - pipeline.text_encoder_2 - ], - returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, - requires_pooled=[ - False, - True - ] - ) - prompt_embed, pooled = compel(prompt) - negative_embed, negative_pooled = compel(negative_prompt) - [prompt_embed, negative_embed] = compel.pad_conditioning_tensors_to_same_length([prompt_embed, negative_embed]) - return prompt_embed, pooled, negative_embed, negative_pooled def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_prompts): @@ -74,7 +51,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro return latents - def set_pipeline_args(model, prompt, negative_prompt, **kwargs): + def set_pipeline_args(model, prompt, negative_prompt, prompt_2=None, negative_prompt_2=None, refiner=False, **kwargs): args = {} pipeline = model signature = inspect.signature(type(pipeline).__call__) @@ -85,18 +62,20 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro pooled = None negative_embed = None negative_pooled = None - if shared.opts.data['prompt_attention'] == 'Compel parser' and (shared.opts.diffusers_pipeline == shared.pipelines[1] or shared.opts.diffusers_pipeline == shared.pipelines[7]): #Gated for SDXL only - prompt_embed, pooled, negative_embed, negative_pooled = compel_encode_prompt(model, prompt, negative_prompt) + if shared.opts.data['prompt_attention'] == 'Compel parser': + prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompt(model, prompt, negative_prompt, prompt_2, negative_prompt_2, refiner) if 'prompt' in possible: if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None: args['prompt_embeds'] = prompt_embed args['pooled_prompt_embeds'] = pooled + args['prompt_2'] = None #Cannot pass prompts when passing embeds else: args['prompt'] = prompt if 'negative_prompt' in possible: if hasattr(model, 'text_encoder') and 'negative_prompt_embeds' in possible and negative_embed is not None: args['negative_prompt_embeds'] = negative_embed args['negative_pooled_prompt_embeds'] = negative_pooled + args['negative_prompt_2'] = None else: args['negative_prompt'] = negative_prompt if 'num_inference_steps' in possible: @@ -118,10 +97,6 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro args[arg] = kwargs[arg] else: pass - if prompt_embed is not None: #Cannot pass prompts when passing embeds - del args['prompt_2'] - del args['negative_prompt_2'] - # shared.log.debug(f'Diffuser not supported: pipeline={pipeline.__class__.__name__} task={sd_models.get_diffusers_task(model)} arg={arg}') # shared.log.debug(f'Diffuser pipeline: {pipeline.__class__.__name__} possible={possible}') clean = args.copy() @@ -182,6 +157,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro denoising_start=0 if refiner_enabled and p.refiner_start > 0 and p.refiner_start < 1 else None, denoising_end=p.refiner_start if refiner_enabled and p.refiner_start > 0 and p.refiner_start < 1 else None, output_type='latent' if hasattr(shared.sd_model, 'vae') else 'np', + refiner=False, **task_specific_kwargs ) output = shared.sd_model(**pipe_args) # pylint: disable=not-callable @@ -235,6 +211,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro denoising_end=1 if p.refiner_start > 0 and p.refiner_start < 1 else None, image=output.images[i], output_type='latent' if hasattr(shared.sd_refiner, 'vae') else 'np', + refiner=True ) refiner_output = shared.sd_refiner(**pipe_args) # pylint: disable=not-callable if not shared.state.interrupted and not shared.state.skipped: @@ -244,6 +221,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro if shared.opts.diffusers_move_refiner and not shared.sd_refiner.has_accelerate: shared.log.debug('Diffusers: Moving refiner model to CPU') shared.sd_refiner.to(devices.cpu) + devices.torch_gc() else: results = output.images @@ -252,4 +230,4 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro - return results + return results \ No newline at end of file diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py new file mode 100644 index 000000000..10c1bc3db --- /dev/null +++ b/modules/prompt_parser_diffusers.py @@ -0,0 +1,36 @@ +import torch +import modules.shared as shared +from compel import Compel, ReturnedEmbeddingsType + +def compel_encode_prompt(pipeline, prompt, negative_prompt, prompt_2=None, negative_prompt_2=None, refiner=False): + if "XL" not in pipeline.__class__.__name__: + print(f"Compel parser is not configured for: {pipeline.__class__.__name__}") + return None, None, None, None + compel_te1 = Compel( + tokenizer=pipeline.tokenizer, + text_encoder=pipeline.text_encoder, + returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, + requires_pooled=False, + ) + + compel_te2 = Compel( + tokenizer=pipeline.tokenizer_2, + text_encoder=pipeline.text_encoder_2, + returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, + requires_pooled=True, + ) + if not refiner: + positive_te1 = compel_te1(prompt) + positive_te2, pooled = compel_te2(prompt_2) + positive = torch.cat((positive_te1, positive_te2), dim=-1) + + negative_te1 = compel_te1(negative_prompt) + negative_te2, negative_pooled = compel_te2(negative_prompt_2) + negative = torch.cat((negative_te1, negative_te2), dim=-1) + if refiner: + positive, pooled = compel_te2(prompt) + negative, negative_pooled = compel_te2(negative_prompt) + + + [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length([positive, negative]) + return prompt_embed, pooled, negative_embed, negative_pooled \ No newline at end of file From fdbafbd71302c2674baea9249ff2302c8461ddae Mon Sep 17 00:00:00 2001 From: root Date: Fri, 4 Aug 2023 15:12:37 +0000 Subject: [PATCH 03/72] Clean up code. --- modules/prompt_parser_diffusers.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 10c1bc3db..fd50ce14e 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -1,11 +1,16 @@ import torch import modules.shared as shared from compel import Compel, ReturnedEmbeddingsType +import diffusers +import typing -def compel_encode_prompt(pipeline, prompt, negative_prompt, prompt_2=None, negative_prompt_2=None, refiner=False): - if "XL" not in pipeline.__class__.__name__: - print(f"Compel parser is not configured for: {pipeline.__class__.__name__}") - return None, None, None, None +def compel_encode_prompt(pipeline: typing.Any, *args, **kwargs): + compel_encode_fn = COMPEL_ENCODE_FN_DICT.get(type(pipeline), None) + if compel_encode_fn is None: + raise TypeError(f"Compel encoding not yet supported for {type(pipeline).__name__}.") + return compel_encode_fn(pipeline, *args, **kwargs) + +def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str]=None, negative_prompt_2: typing.Optional[str]=None, refiner=False): compel_te1 = Compel( tokenizer=pipeline.tokenizer, text_encoder=pipeline.text_encoder, @@ -19,7 +24,7 @@ def compel_encode_prompt(pipeline, prompt, negative_prompt, prompt_2=None, negat returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, requires_pooled=True, ) - if not refiner: + if refiner is None: positive_te1 = compel_te1(prompt) positive_te2, pooled = compel_te2(prompt_2) positive = torch.cat((positive_te1, positive_te2), dim=-1) @@ -27,10 +32,12 @@ def compel_encode_prompt(pipeline, prompt, negative_prompt, prompt_2=None, negat negative_te1 = compel_te1(negative_prompt) negative_te2, negative_pooled = compel_te2(negative_prompt_2) negative = torch.cat((negative_te1, negative_te2), dim=-1) - if refiner: + else: positive, pooled = compel_te2(prompt) negative, negative_pooled = compel_te2(negative_prompt) [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length([positive, negative]) - return prompt_embed, pooled, negative_embed, negative_pooled \ No newline at end of file + return prompt_embed, pooled, negative_embed, negative_pooled + +COMPEL_ENCODE_FN_DICT = {diffusers.StableDiffusionXLPipeline: compel_encode_prompt_sdxl} \ No newline at end of file From 076acf0664f5ab9a206ef699c0f56a8477a6dca6 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 4 Aug 2023 15:16:13 +0000 Subject: [PATCH 04/72] Linting fixes. --- extensions-builtin/sd-dynamic-thresholding | 2 +- extensions-builtin/sd-webui-agent-scheduler | 2 +- extensions-builtin/stable-diffusion-webui-images-browser | 2 +- modules/processing_diffusers.py | 4 ++-- modules/prompt_parser_diffusers.py | 6 +++--- wiki | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/extensions-builtin/sd-dynamic-thresholding b/extensions-builtin/sd-dynamic-thresholding index 639e40970..5349f0087 160000 --- a/extensions-builtin/sd-dynamic-thresholding +++ b/extensions-builtin/sd-dynamic-thresholding @@ -1 +1 @@ -Subproject commit 639e40970f9b88c019866a6babb34f3294465d39 +Subproject commit 5349f008721480a572ab9a917533afdd0dae7b9e diff --git a/extensions-builtin/sd-webui-agent-scheduler b/extensions-builtin/sd-webui-agent-scheduler index 1bb04d1fb..ea470d752 160000 --- a/extensions-builtin/sd-webui-agent-scheduler +++ b/extensions-builtin/sd-webui-agent-scheduler @@ -1 +1 @@ -Subproject commit 1bb04d1fbe00201cc28638fef09c25bea002b1e2 +Subproject commit ea470d75242ef7ae6fed6019b6ba227a4926b671 diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser index b984cdd16..a3aeb93fd 160000 --- a/extensions-builtin/stable-diffusion-webui-images-browser +++ b/extensions-builtin/stable-diffusion-webui-images-browser @@ -1 +1 @@ -Subproject commit b984cdd1692f46006333ab92ef463cc35879f455 +Subproject commit a3aeb93fd7387cfe58aabf431b2dbbd1796bffed diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 9dcfdc954..facd97292 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -62,7 +62,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro pooled = None negative_embed = None negative_pooled = None - if shared.opts.data['prompt_attention'] == 'Compel parser': + if shared.opts.data['prompt_attention'] == 'Compel parser': prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompt(model, prompt, negative_prompt, prompt_2, negative_prompt_2, refiner) if 'prompt' in possible: if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None: @@ -230,4 +230,4 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro - return results \ No newline at end of file + return results diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index fd50ce14e..ba2f2dc08 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -35,9 +35,9 @@ def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, pro else: positive, pooled = compel_te2(prompt) negative, negative_pooled = compel_te2(negative_prompt) - - + + [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length([positive, negative]) return prompt_embed, pooled, negative_embed, negative_pooled -COMPEL_ENCODE_FN_DICT = {diffusers.StableDiffusionXLPipeline: compel_encode_prompt_sdxl} \ No newline at end of file +COMPEL_ENCODE_FN_DICT = {diffusers.StableDiffusionXLPipeline: compel_encode_prompt_sdxl} diff --git a/wiki b/wiki index ec18e358a..f76cc3a9a 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit ec18e358a4523567164170e38a90c74eb4deba0f +Subproject commit f76cc3a9ac124882f58f35ba3dfe930744109456 From df18ad4eb142acaedb0ee952e9ff2992bdfc407a Mon Sep 17 00:00:00 2001 From: AI-Casanova Date: Sat, 5 Aug 2023 14:54:50 +0000 Subject: [PATCH 05/72] Enable side loading of multiple Kohya-style LoRA for SDXL --- modules/lora_diffusers.py | 527 +++++++++++++++++++++++++++++++- modules/processing_diffusers.py | 4 +- modules/shared.py | 1 + 3 files changed, 515 insertions(+), 17 deletions(-) diff --git a/modules/lora_diffusers.py b/modules/lora_diffusers.py index 537416180..3ad4884f5 100644 --- a/modules/lora_diffusers.py +++ b/modules/lora_diffusers.py @@ -1,35 +1,532 @@ import diffusers -from modules import shared +# from modules import shared +import modules.shared as shared + lora_state = { # TODO Lora state for Diffusers - 'multiplier': 1.0, + 'multiplier': [], 'active': False, 'loaded': 0, + 'all_loras': [] } - def unload_diffusers_lora(): try: pipe = shared.sd_model - pipe.unload_lora_weights() + if shared.opts.diffusers_lora_loader == "Diffusers": + pipe.unload_lora_weights() + pipe._remove_text_encoder_monkey_patch() # pylint: disable=W0212 + proc_cls_name = next(iter(pipe.unet.attn_processors.values())).__class__.__name__ + non_lora_proc_cls = getattr(diffusers.models.attention_processor, proc_cls_name)#[len("LORA"):]) + pipe.unet.set_attn_processor(non_lora_proc_cls()) + # shared.log.debug('Diffusers LoRA unloaded') + else: + lora_state['all_loras'].reverse() + lora_state['multiplier'].reverse() + for i, lora_network in enumerate(lora_state['all_loras']): + if shared.opts.diffusers_lora_loader == "kohya-merge": + lora_network.restore_from(multiplier=lora_state['multiplier'][i]) + if shared.opts.diffusers_lora_loader == "kohya-apply": + lora_network.unapply_to() lora_state['active'] = False lora_state['loaded'] = 0 - pipe._remove_text_encoder_monkey_patch() # pylint: disable=W0212 - proc_cls_name = next(iter(pipe.unet.attn_processors.values())).__class__.__name__ - non_lora_proc_cls = getattr(diffusers.models.attention_processor, proc_cls_name[len("LORA"):]) - pipe.unet.set_attn_processor(non_lora_proc_cls()) - # shared.log.debug('Diffusers LoRA unloaded') - except Exception: - pass + lora_state['all_loras'] = [] + lora_state['multiplier'] = [] + + except Exception as e: + shared.log.error(f"Diffusers LoRA unloading failed: {e}") def load_diffusers_lora(name, lora, strength = 1.0): try: pipe = shared.sd_model - pipe.load_lora_weights(lora.filename, cache_dir=shared.opts.diffusers_dir, local_files_only=True, lora_scale=strength) lora_state['active'] = True lora_state['loaded'] += 1 - lora_state['multiplier'] = strength - # pipe.unet.load_attn_procs("pcuenq/pokemon-lora") - shared.log.info(f"Diffusers LoRA loaded: {name} {lora_state['multiplier']}") + lora_state['multiplier'].append(strength) + if shared.opts.diffusers_lora_loader == "Diffusers": + pipe.load_lora_weights(lora.filename, cache_dir=shared.opts.diffusers_dir, local_files_only=True, lora_scale=strength) + shared.log.info(f"Diffusers LoRA loaded: {name} {lora_state['multiplier']}") + else: + from safetensors.torch import load_file + lora_sd = load_file(lora.filename) + if "XL" in pipe.__class__.__name__: + text_encoders = [pipe.text_encoder, pipe.text_encoder_2] + else: + text_encoders = pipe.text_encoder + lora_network: LoRANetwork = create_network_from_weights(text_encoders, pipe.unet, lora_sd, multiplier=strength) + lora_network.load_state_dict(lora_sd) + if shared.opts.diffusers_lora_loader == "kohya-merge": + lora_network.merge_to(multiplier=strength) + if shared.opts.diffusers_lora_loader == "kohya-apply": + lora_network.to(pipe.device, dtype=pipe.unet.dtype) + lora_network.apply_to(multiplier=strength) + lora_state['all_loras'].append(lora_network) + shared.log.info(f"Diffusers LoRA loaded: {name} {strength}") except Exception as e: shared.log.error(f"Diffusers LoRA loading failed: {name} {e}") + + +# Diffusersで動くLoRA。このファイル単独で完結する。 +# LoRA module for Diffusers. This file works independently. + +import bisect +import math +# import random +from typing import Any, Dict, List, Mapping, Optional, Union +from diffusers import UNet2DConditionModel +# import numpy as np +from tqdm import tqdm +from transformers import CLIPTextModel +import torch + + +def make_unet_conversion_map() -> Dict[str, str]: + unet_conversion_map_layer = [] + + for i in range(3): # num_blocks is 3 in sdxl + # loop over downblocks/upblocks + for j in range(2): + # loop over resnets/attentions for downblocks + hf_down_res_prefix = f"down_blocks.{i}.resnets.{j}." + sd_down_res_prefix = f"input_blocks.{3*i + j + 1}.0." + unet_conversion_map_layer.append((sd_down_res_prefix, hf_down_res_prefix)) + + if i < 3: + # no attention layers in down_blocks.3 + hf_down_atn_prefix = f"down_blocks.{i}.attentions.{j}." + sd_down_atn_prefix = f"input_blocks.{3*i + j + 1}.1." + unet_conversion_map_layer.append((sd_down_atn_prefix, hf_down_atn_prefix)) + + for j in range(3): + # loop over resnets/attentions for upblocks + hf_up_res_prefix = f"up_blocks.{i}.resnets.{j}." + sd_up_res_prefix = f"output_blocks.{3*i + j}.0." + unet_conversion_map_layer.append((sd_up_res_prefix, hf_up_res_prefix)) + + # if i > 0: commentout for sdxl + # no attention layers in up_blocks.0 + hf_up_atn_prefix = f"up_blocks.{i}.attentions.{j}." + sd_up_atn_prefix = f"output_blocks.{3*i + j}.1." + unet_conversion_map_layer.append((sd_up_atn_prefix, hf_up_atn_prefix)) + + if i < 3: + # no downsample in down_blocks.3 + hf_downsample_prefix = f"down_blocks.{i}.downsamplers.0.conv." + sd_downsample_prefix = f"input_blocks.{3*(i+1)}.0.op." + unet_conversion_map_layer.append((sd_downsample_prefix, hf_downsample_prefix)) + + # no upsample in up_blocks.3 + hf_upsample_prefix = f"up_blocks.{i}.upsamplers.0." + sd_upsample_prefix = f"output_blocks.{3*i + 2}.{2}." # change for sdxl + unet_conversion_map_layer.append((sd_upsample_prefix, hf_upsample_prefix)) + + hf_mid_atn_prefix = "mid_block.attentions.0." + sd_mid_atn_prefix = "middle_block.1." + unet_conversion_map_layer.append((sd_mid_atn_prefix, hf_mid_atn_prefix)) + + for j in range(2): + hf_mid_res_prefix = f"mid_block.resnets.{j}." + sd_mid_res_prefix = f"middle_block.{2*j}." + unet_conversion_map_layer.append((sd_mid_res_prefix, hf_mid_res_prefix)) + + unet_conversion_map_resnet = [ + # (stable-diffusion, HF Diffusers) + ("in_layers.0.", "norm1."), + ("in_layers.2.", "conv1."), + ("out_layers.0.", "norm2."), + ("out_layers.3.", "conv2."), + ("emb_layers.1.", "time_emb_proj."), + ("skip_connection.", "conv_shortcut."), + ] + + unet_conversion_map = [] + for sd, hf in unet_conversion_map_layer: + if "resnets" in hf: + for sd_res, hf_res in unet_conversion_map_resnet: + unet_conversion_map.append((sd + sd_res, hf + hf_res)) + else: + unet_conversion_map.append((sd, hf)) + + for j in range(2): + hf_time_embed_prefix = f"time_embedding.linear_{j+1}." + sd_time_embed_prefix = f"time_embed.{j*2}." + unet_conversion_map.append((sd_time_embed_prefix, hf_time_embed_prefix)) + + for j in range(2): + hf_label_embed_prefix = f"add_embedding.linear_{j+1}." + sd_label_embed_prefix = f"label_emb.0.{j*2}." + unet_conversion_map.append((sd_label_embed_prefix, hf_label_embed_prefix)) + + unet_conversion_map.append(("input_blocks.0.0.", "conv_in.")) + unet_conversion_map.append(("out.0.", "conv_norm_out.")) + unet_conversion_map.append(("out.2.", "conv_out.")) + + sd_hf_conversion_map = {sd.replace(".", "_")[:-1]: hf.replace(".", "_")[:-1] for sd, hf in unet_conversion_map} + return sd_hf_conversion_map + + +UNET_CONVERSION_MAP = make_unet_conversion_map() + + +class LoRAModule(torch.nn.Module): + """ + replaces forward method of the original Linear, instead of replacing the original Linear module. + """ + + def __init__( + self, + lora_name, + org_module: torch.nn.Module, + multiplier=1.0, + lora_dim=4, + alpha=1, + ): + """if alpha == 0 or None, alpha is rank (no scaling).""" + super().__init__() + self.lora_name = lora_name + + if org_module.__class__.__name__ == "LoRACompatibleConv": #Modified to support Diffusers>=0.19.2 + in_dim = org_module.in_channels + out_dim = org_module.out_channels + else: + in_dim = org_module.in_features + out_dim = org_module.out_features + + self.lora_dim = lora_dim + + if org_module.__class__.__name__ == "LoRACompatibleConv": #Modified to support Diffusers>=0.19.2 + kernel_size = org_module.kernel_size + stride = org_module.stride + padding = org_module.padding + self.lora_down = torch.nn.Conv2d(in_dim, self.lora_dim, kernel_size, stride, padding, bias=False) + self.lora_up = torch.nn.Conv2d(self.lora_dim, out_dim, (1, 1), (1, 1), bias=False) + else: + self.lora_down = torch.nn.Linear(in_dim, self.lora_dim, bias=False) + self.lora_up = torch.nn.Linear(self.lora_dim, out_dim, bias=False) + + if type(alpha) == torch.Tensor: + alpha = alpha.detach().float().numpy() # without casting, bf16 causes error + alpha = self.lora_dim if alpha is None or alpha == 0 else alpha + self.scale = alpha / self.lora_dim + self.register_buffer("alpha", torch.tensor(alpha)) # 勾配計算に含めない / not included in gradient calculation + + # same as microsoft's + torch.nn.init.kaiming_uniform_(self.lora_down.weight, a=math.sqrt(5)) + torch.nn.init.zeros_(self.lora_up.weight) + + self.multiplier = multiplier + self.org_module = [org_module] + self.enabled = True + self.network: LoRANetwork = None + self.org_forward = None + + # override org_module's forward method + def apply_to(self, multiplier=None): + if multiplier is not None: + self.multiplier = multiplier + if self.org_forward is None: + self.org_forward = self.org_module[0].forward + self.org_module[0].forward = self.forward + + # restore org_module's forward method + def unapply_to(self): + if self.org_forward is not None: + self.org_module[0].forward = self.org_forward + + # forward with lora + def forward(self, x): + if not self.enabled: + return self.org_forward(x) + return self.org_forward(x) + self.lora_up(self.lora_down(x)) * self.multiplier * self.scale + + def set_network(self, network): + self.network = network + + # merge lora weight to org weight + def merge_to(self, multiplier=1.0): + # get lora weight + lora_weight = self.get_weight(multiplier) + + # get org weight + org_sd = self.org_module[0].state_dict() + org_weight = org_sd["weight"] + weight = org_weight + lora_weight.to(org_weight.device, dtype=org_weight.dtype) + + # set weight to org_module + org_sd["weight"] = weight + self.org_module[0].load_state_dict(org_sd) + + # restore org weight from lora weight + def restore_from(self, multiplier=1.0): + # get lora weight + lora_weight = self.get_weight(multiplier) + + # get org weight + org_sd = self.org_module[0].state_dict() + org_weight = org_sd["weight"] + weight = org_weight - lora_weight.to(org_weight.device, dtype=org_weight.dtype) + + # set weight to org_module + org_sd["weight"] = weight + self.org_module[0].load_state_dict(org_sd) + + # return lora weight + def get_weight(self, multiplier=None): + if multiplier is None: + multiplier = self.multiplier + + # get up/down weight from module + up_weight = self.lora_up.weight.to(torch.float) + down_weight = self.lora_down.weight.to(torch.float) + + # pre-calculated weight + if len(down_weight.size()) == 2: + # linear + weight = self.multiplier * (up_weight @ down_weight) * self.scale + elif down_weight.size()[2:4] == (1, 1): + # conv2d 1x1 + weight = ( + self.multiplier + * (up_weight.squeeze(3).squeeze(2) @ down_weight.squeeze(3).squeeze(2)).unsqueeze(2).unsqueeze(3) + * self.scale + ) + else: + # conv2d 3x3 + conved = torch.nn.functional.conv2d(down_weight.permute(1, 0, 2, 3), up_weight).permute(1, 0, 2, 3) + weight = self.multiplier * conved * self.scale + + return weight + + +# Create network from weights for inference, weights are not loaded here +def create_network_from_weights( + text_encoder: Union[CLIPTextModel, List[CLIPTextModel]], unet: UNet2DConditionModel, weights_sd: Dict, multiplier: float = 1.0 +): + # get dim/alpha mapping + modules_dim = {} + modules_alpha = {} + for key, value in weights_sd.items(): + if "." not in key: + continue + + lora_name = key.split(".")[0] + if "alpha" in key: + modules_alpha[lora_name] = value + elif "lora_down" in key: + dim = value.size()[0] + modules_dim[lora_name] = dim + # print(lora_name, value.size(), dim) + + # support old LoRA without alpha + for key in modules_dim.keys(): + if key not in modules_alpha: + modules_alpha[key] = modules_dim[key] + + return LoRANetwork(text_encoder, unet, multiplier=multiplier, modules_dim=modules_dim, modules_alpha=modules_alpha) + + +def merge_lora_weights(pipe, weights_sd: Dict, multiplier: float = 1.0): + text_encoders = [pipe.text_encoder, pipe.text_encoder_2] if hasattr(pipe, "text_encoder_2") else [pipe.text_encoder] + unet = pipe.unet + + lora_network = create_network_from_weights(text_encoders, unet, weights_sd, multiplier=multiplier) + lora_network.load_state_dict(weights_sd) + lora_network.merge_to(multiplier=multiplier) + + +# block weightや学習に対応しない簡易版 / simple version without block weight and training +class LoRANetwork(torch.nn.Module): + UNET_TARGET_REPLACE_MODULE = ["Transformer2DModel"] + UNET_TARGET_REPLACE_MODULE_CONV2D_3X3 = ["ResnetBlock2D", "Downsample2D", "Upsample2D"] + TEXT_ENCODER_TARGET_REPLACE_MODULE = ["CLIPAttention", "CLIPMLP"] + LORA_PREFIX_UNET = "lora_unet" + LORA_PREFIX_TEXT_ENCODER = "lora_te" + + # SDXL: must starts with LORA_PREFIX_TEXT_ENCODER + LORA_PREFIX_TEXT_ENCODER1 = "lora_te1" + LORA_PREFIX_TEXT_ENCODER2 = "lora_te2" + + def __init__( + self, + text_encoder: Union[List[CLIPTextModel], CLIPTextModel], + unet: UNet2DConditionModel, + multiplier: float = 1.0, + modules_dim: Optional[Dict[str, int]] = None, + modules_alpha: Optional[Dict[str, int]] = None, + varbose: Optional[bool] = False, + ) -> None: + super().__init__() + self.multiplier = multiplier + + shared.log.debug(f"create LoRA network from weights") + + # convert SDXL Stability AI's U-Net modules to Diffusers + converted = self.convert_unet_modules(modules_dim, modules_alpha) + if converted: + shared.log.debug(f"converted {converted} Stability AI's U-Net LoRA modules to Diffusers (SDXL)") + + # create module instances + def create_modules( + is_unet: bool, + text_encoder_idx: Optional[int], # None, 1, 2 + root_module: torch.nn.Module, + target_replace_modules: List[torch.nn.Module], + ) -> List[LoRAModule]: + prefix = ( + self.LORA_PREFIX_UNET + if is_unet + else ( + self.LORA_PREFIX_TEXT_ENCODER + if text_encoder_idx is None + else (self.LORA_PREFIX_TEXT_ENCODER1 if text_encoder_idx == 1 else self.LORA_PREFIX_TEXT_ENCODER2) + ) + ) + loras = [] + skipped = [] + for name, module in root_module.named_modules(): + if module.__class__.__name__ in target_replace_modules: + for child_name, child_module in module.named_modules(): + is_linear = child_module.__class__.__name__ == "Linear" or "LoRACompatibleLinear" #Modified to support Diffusers>=0.19.2 + is_conv2d = child_module.__class__.__name__ == "Conv2d" or "LoRACompatibleConv" #Modified to support Diffusers>=0.19.2 + + if is_linear or is_conv2d: + lora_name = prefix + "." + name + "." + child_name + lora_name = lora_name.replace(".", "_") + + if lora_name not in modules_dim: + # print(f"skipped {lora_name} (not found in modules_dim)") + skipped.append(lora_name) + continue + + dim = modules_dim[lora_name] + alpha = modules_alpha[lora_name] + lora = LoRAModule( + lora_name, + child_module, + self.multiplier, + dim, + alpha, + ) + loras.append(lora) + return loras, skipped + + text_encoders = text_encoder if type(text_encoder) == list else [text_encoder] + + # create LoRA for text encoder + # 毎回すべてのモジュールを作るのは無駄なので要検討 / it is wasteful to create all modules every time, need to consider + self.text_encoder_loras: List[LoRAModule] = [] + skipped_te = [] + for i, text_encoder in enumerate(text_encoders): + if len(text_encoders) > 1: + index = i + 1 + else: + index = None + + text_encoder_loras, skipped = create_modules(False, index, text_encoder, LoRANetwork.TEXT_ENCODER_TARGET_REPLACE_MODULE) + self.text_encoder_loras.extend(text_encoder_loras) + skipped_te += skipped + shared.log.debug(f"create LoRA for Text Encoder: {len(self.text_encoder_loras)} modules.") + if len(skipped_te) > 0: + shared.log.debug(f"skipped {len(skipped_te)} modules because of missing weight.") + + # extend U-Net target modules to include Conv2d 3x3 + target_modules = LoRANetwork.UNET_TARGET_REPLACE_MODULE + LoRANetwork.UNET_TARGET_REPLACE_MODULE_CONV2D_3X3 + + self.unet_loras: List[LoRAModule] + self.unet_loras, skipped_un = create_modules(True, None, unet, target_modules) + shared.log.debug(f"create LoRA for U-Net: {len(self.unet_loras)} modules.") + if len(skipped_un) > 0: + shared.log.debug(f"skipped {len(skipped_un)} modules because of missing weight.") + + # assertion + names = set() + for lora in self.text_encoder_loras + self.unet_loras: + names.add(lora.lora_name) + for lora_name in modules_dim.keys(): + assert lora_name in names, f"{lora_name} is not found in created LoRA modules." + + # make to work load_state_dict + for lora in self.text_encoder_loras + self.unet_loras: + self.add_module(lora.lora_name, lora) + + # SDXL: convert SDXL Stability AI's U-Net modules to Diffusers + def convert_unet_modules(self, modules_dim, modules_alpha): + converted_count = 0 + not_converted_count = 0 + + map_keys = list(UNET_CONVERSION_MAP.keys()) + map_keys.sort() + + for key in list(modules_dim.keys()): + if key.startswith(LoRANetwork.LORA_PREFIX_UNET + "_"): + search_key = key.replace(LoRANetwork.LORA_PREFIX_UNET + "_", "") + position = bisect.bisect_right(map_keys, search_key) + map_key = map_keys[position - 1] + if search_key.startswith(map_key): + new_key = key.replace(map_key, UNET_CONVERSION_MAP[map_key]) + modules_dim[new_key] = modules_dim[key] + modules_alpha[new_key] = modules_alpha[key] + del modules_dim[key] + del modules_alpha[key] + converted_count += 1 + else: + not_converted_count += 1 + assert ( + converted_count == 0 or not_converted_count == 0 + ), f"some modules are not converted: {converted_count} converted, {not_converted_count} not converted" + return converted_count + + def set_multiplier(self, multiplier): + self.multiplier = multiplier + for lora in self.text_encoder_loras + self.unet_loras: + lora.multiplier = self.multiplier + + def apply_to(self, multiplier=1.0, apply_text_encoder=True, apply_unet=True): + if apply_text_encoder: + shared.log.debug("enable LoRA for text encoder") + for lora in self.text_encoder_loras: + lora.apply_to(multiplier) + if apply_unet: + shared.log.debug("enable LoRA for U-Net") + for lora in self.unet_loras: + lora.apply_to(multiplier) + + def unapply_to(self): + for lora in self.text_encoder_loras + self.unet_loras: + lora.unapply_to() + + def merge_to(self, multiplier=1.0): + shared.log.debug("merge LoRA weights to original weights") + for lora in tqdm(self.text_encoder_loras + self.unet_loras): + lora.merge_to(multiplier) + shared.log.debug(f"weights are merged") + + def restore_from(self, multiplier=1.0): + shared.log.debug("restore LoRA weights from original weights") + for lora in tqdm(self.text_encoder_loras + self.unet_loras): + lora.restore_from(multiplier) + shared.log.debug(f"weights are restored") + + def load_state_dict(self, state_dict: Mapping[str, Any], strict: bool = True): + # convert SDXL Stability AI's state dict to Diffusers' based state dict + map_keys = list(UNET_CONVERSION_MAP.keys()) # prefix of U-Net modules + map_keys.sort() + for key in list(state_dict.keys()): + if key.startswith(LoRANetwork.LORA_PREFIX_UNET + "_"): + search_key = key.replace(LoRANetwork.LORA_PREFIX_UNET + "_", "") + position = bisect.bisect_right(map_keys, search_key) + map_key = map_keys[position - 1] + if search_key.startswith(map_key): + new_key = key.replace(map_key, UNET_CONVERSION_MAP[map_key]) + state_dict[new_key] = state_dict[key] + del state_dict[key] + + # in case of V2, some weights have different shape, so we need to convert them + # because V2 LoRA is based on U-Net created by use_linear_projection=False + my_state_dict = self.state_dict() + for key in state_dict.keys(): + if state_dict[key].size() != my_state_dict[key].size(): + # print(f"convert {key} from {state_dict[key].size()} to {my_state_dict[key].size()}") + state_dict[key] = state_dict[key].view(my_state_dict[key].size()) + + return super().load_state_dict(state_dict, strict) + diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 18c86116b..15364b739 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -82,8 +82,8 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro args['callback_steps'] = 1 if 'callback' in possible: args['callback'] = diffusers_callback - if 'cross_attention_kwargs' in possible and lora_state['active']: - args['cross_attention_kwargs'] = { 'scale': lora_state['multiplier']} + if 'cross_attention_kwargs' in possible and lora_state['active'] and shared.opts.diffusers_lora_loader == "Diffusers": + args['cross_attention_kwargs'] = { 'scale': lora_state['multiplier'][0]} for arg in kwargs: if arg in possible: args[arg] = kwargs[arg] diff --git a/modules/shared.py b/modules/shared.py index 8b0ff3109..35d06ad68 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -410,6 +410,7 @@ options_templates.update(options_section(('diffusers', "Diffusers Settings"), { "diffusers_attention_slicing": OptionInfo(False, "Enable attention slicing"), "diffusers_model_load_variant": OptionInfo("default", "Diffusers model loading variant", gr.Radio, lambda: {"choices": ['default', 'fp32', 'fp16']}), "diffusers_vae_load_variant": OptionInfo("default", "Diffusers VAE loading variant", gr.Radio, lambda: {"choices": ['default', 'fp32', 'fp16']}), + "diffusers_lora_loader": OptionInfo("default", "Diffusers LoRA loading variant", gr.Radio, lambda: {"choices": ['kohya-apply', 'kohya-merge', 'Diffusers']}), # "diffusers_force_zeros": OptionInfo(False, "Force zeros for prompts when empty"), # "diffusers_aesthetics_score": OptionInfo(6.0, "Require aesthetic score", gr.Slider, {"minimum": 0, "maximum": 10, "step": 0.1}), })) From 5a3d92883d638e7cb66b6a1324893b7a3e96ba83 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sat, 5 Aug 2023 18:33:11 +0000 Subject: [PATCH 06/72] Modify code a bit. --- modules/lora_diffusers.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/modules/lora_diffusers.py b/modules/lora_diffusers.py index 3ad4884f5..c5e2ea980 100644 --- a/modules/lora_diffusers.py +++ b/modules/lora_diffusers.py @@ -1,4 +1,5 @@ import diffusers +import diffusers.models.lora as diffusers_lora # from modules import shared import modules.shared as shared @@ -67,13 +68,10 @@ def load_diffusers_lora(name, lora, strength = 1.0): # Diffusersで動くLoRA。このファイル単独で完結する。 # LoRA module for Diffusers. This file works independently. - import bisect import math -# import random from typing import Any, Dict, List, Mapping, Optional, Union from diffusers import UNet2DConditionModel -# import numpy as np from tqdm import tqdm from transformers import CLIPTextModel import torch @@ -184,7 +182,7 @@ class LoRAModule(torch.nn.Module): super().__init__() self.lora_name = lora_name - if org_module.__class__.__name__ == "LoRACompatibleConv": #Modified to support Diffusers>=0.19.2 + if isinstance(org_module, diffusers_lora.LoRACompatibleConv): #Modified to support Diffusers>=0.19.2 in_dim = org_module.in_channels out_dim = org_module.out_channels else: @@ -193,7 +191,7 @@ class LoRAModule(torch.nn.Module): self.lora_dim = lora_dim - if org_module.__class__.__name__ == "LoRACompatibleConv": #Modified to support Diffusers>=0.19.2 + if isinstance(org_module, diffusers_lora.LoRACompatibleConv): #Modified to support Diffusers>=0.19.2 kernel_size = org_module.kernel_size stride = org_module.stride padding = org_module.padding @@ -203,7 +201,7 @@ class LoRAModule(torch.nn.Module): self.lora_down = torch.nn.Linear(in_dim, self.lora_dim, bias=False) self.lora_up = torch.nn.Linear(self.lora_dim, out_dim, bias=False) - if type(alpha) == torch.Tensor: + if isinstance(alpha, torch.Tensor): alpha = alpha.detach().float().numpy() # without casting, bf16 causes error alpha = self.lora_dim if alpha is None or alpha == 0 else alpha self.scale = alpha / self.lora_dim @@ -385,8 +383,8 @@ class LoRANetwork(torch.nn.Module): for name, module in root_module.named_modules(): if module.__class__.__name__ in target_replace_modules: for child_name, child_module in module.named_modules(): - is_linear = child_module.__class__.__name__ == "Linear" or "LoRACompatibleLinear" #Modified to support Diffusers>=0.19.2 - is_conv2d = child_module.__class__.__name__ == "Conv2d" or "LoRACompatibleConv" #Modified to support Diffusers>=0.19.2 + is_linear = isinstance(child_module, (torch.nn.Linear, diffusers_lora.LoRACompatibleLinear)) #Modified to support Diffusers>=0.19.2 + is_conv2d = isinstance(child_module, (torch.nn.Conv2d, diffusers_lora.LoRACompatibleConv)) #Modified to support Diffusers>=0.19.2 if is_linear or is_conv2d: lora_name = prefix + "." + name + "." + child_name From 2172f7c3f080334029e4a0a9d248c1999166050f Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sat, 5 Aug 2023 18:35:18 +0000 Subject: [PATCH 07/72] Linting fix. --- modules/lora_diffusers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/lora_diffusers.py b/modules/lora_diffusers.py index c5e2ea980..3df991c77 100644 --- a/modules/lora_diffusers.py +++ b/modules/lora_diffusers.py @@ -32,7 +32,7 @@ def unload_diffusers_lora(): lora_state['loaded'] = 0 lora_state['all_loras'] = [] lora_state['multiplier'] = [] - + except Exception as e: shared.log.error(f"Diffusers LoRA unloading failed: {e}") @@ -355,7 +355,7 @@ class LoRANetwork(torch.nn.Module): super().__init__() self.multiplier = multiplier - shared.log.debug(f"create LoRA network from weights") + shared.log.debug("create LoRA network from weights") # convert SDXL Stability AI's U-Net modules to Diffusers converted = self.convert_unet_modules(modules_dim, modules_alpha) @@ -496,13 +496,13 @@ class LoRANetwork(torch.nn.Module): shared.log.debug("merge LoRA weights to original weights") for lora in tqdm(self.text_encoder_loras + self.unet_loras): lora.merge_to(multiplier) - shared.log.debug(f"weights are merged") + shared.log.debug("weights are merged") def restore_from(self, multiplier=1.0): shared.log.debug("restore LoRA weights from original weights") for lora in tqdm(self.text_encoder_loras + self.unet_loras): lora.restore_from(multiplier) - shared.log.debug(f"weights are restored") + shared.log.debug("weights are restored") def load_state_dict(self, state_dict: Mapping[str, Any], strict: bool = True): # convert SDXL Stability AI's state dict to Diffusers' based state dict From e65e959eefa61a0c28d6d604a439b53747044ef7 Mon Sep 17 00:00:00 2001 From: AI-Casanova Date: Sun, 6 Aug 2023 02:52:03 +0000 Subject: [PATCH 08/72] Enable A1111 and Full parsing conversion --- modules/processing_diffusers.py | 4 ++-- modules/prompt_parser_diffusers.py | 26 ++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index facd97292..a305b9530 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -62,7 +62,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro pooled = None negative_embed = None negative_pooled = None - if shared.opts.data['prompt_attention'] == 'Compel parser': + if shared.opts.data['prompt_attention'] != 'Fixed attention': prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompt(model, prompt, negative_prompt, prompt_2, negative_prompt_2, refiner) if 'prompt' in possible: if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None: @@ -230,4 +230,4 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro - return results + return results \ No newline at end of file diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index ba2f2dc08..a23660f5c 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -1,9 +1,25 @@ import torch import modules.shared as shared +import modules.prompt_parser as prompt_parser from compel import Compel, ReturnedEmbeddingsType import diffusers import typing +def convert_to_compel(prompt: str): + if prompt is None: + return None + all_schedules = prompt_parser.get_learned_conditioning_prompt_schedules(prompt, 100)[0] #100 should be steps, but doesn't actually matter because we can't schedule yet + output_list = prompt_parser.parse_prompt_attention(all_schedules[0][1]) + converted_prompt = [] + for subprompt, weight in output_list: + if subprompt != " ": + if weight == 1: + converted_prompt.append(subprompt) + else: + converted_prompt.append(f"({subprompt}){weight}") + converted_prompt = " ".join(converted_prompt) + return converted_prompt + def compel_encode_prompt(pipeline: typing.Any, *args, **kwargs): compel_encode_fn = COMPEL_ENCODE_FN_DICT.get(type(pipeline), None) if compel_encode_fn is None: @@ -11,6 +27,12 @@ def compel_encode_prompt(pipeline: typing.Any, *args, **kwargs): return compel_encode_fn(pipeline, *args, **kwargs) def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str]=None, negative_prompt_2: typing.Optional[str]=None, refiner=False): + if shared.opts.data['prompt_attention'] != 'Compel parser': + prompt = convert_to_compel(prompt) + negative_prompt = convert_to_compel(negative_prompt) + prompt_2 = convert_to_compel(prompt_2) + negative_prompt_2 = convert_to_compel(negative_prompt_2) + compel_te1 = Compel( tokenizer=pipeline.tokenizer, text_encoder=pipeline.text_encoder, @@ -24,7 +46,7 @@ def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, pro returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, requires_pooled=True, ) - if refiner is None: + if refiner is False: positive_te1 = compel_te1(prompt) positive_te2, pooled = compel_te2(prompt_2) positive = torch.cat((positive_te1, positive_te2), dim=-1) @@ -36,7 +58,7 @@ def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, pro positive, pooled = compel_te2(prompt) negative, negative_pooled = compel_te2(negative_prompt) - + shared.log.debug(compel_te1.parse_prompt_string(prompt)) [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length([positive, negative]) return prompt_embed, pooled, negative_embed, negative_pooled From bb66b5ce0e4aa48077c14afd0f1477df0c6e9d98 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sun, 6 Aug 2023 07:37:45 +0200 Subject: [PATCH 09/72] Ruff fix and refiner->is_refiner. --- modules/api/api.py | 2 +- modules/processing_diffusers.py | 11 ++++++----- modules/prompt_parser_diffusers.py | 10 +++++----- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/modules/api/api.py b/modules/api/api.py index 0a35a8fcf..ce5bda675 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -448,7 +448,7 @@ class Api: def get_samplers(self): return [{"name": sampler[0], "aliases":sampler[2], "options":sampler[3]} for sampler in sd_samplers.all_samplers] - + def get_sd_vaes(self): return [{"model_name": x, "filename": vae_dict[x]} for x in vae_dict.keys()] diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index a305b9530..ffe3fe871 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -8,6 +8,7 @@ import modules.images as images from modules.lora_diffusers import lora_state, unload_diffusers_lora from modules.processing import StableDiffusionProcessing import modules.prompt_parser_diffusers as prompt_parser_diffusers +import typing try: import diffusers @@ -51,7 +52,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro return latents - def set_pipeline_args(model, prompt, negative_prompt, prompt_2=None, negative_prompt_2=None, refiner=False, **kwargs): + def set_pipeline_args(model, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str] =None, negative_prompt_2: typing.Optional[str] = None, is_refiner: bool = False, **kwargs): args = {} pipeline = model signature = inspect.signature(type(pipeline).__call__) @@ -63,7 +64,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro negative_embed = None negative_pooled = None if shared.opts.data['prompt_attention'] != 'Fixed attention': - prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompt(model, prompt, negative_prompt, prompt_2, negative_prompt_2, refiner) + prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompt(model, prompt, negative_prompt, prompt_2, negative_prompt_2, is_refiner) if 'prompt' in possible: if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None: args['prompt_embeds'] = prompt_embed @@ -157,7 +158,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro denoising_start=0 if refiner_enabled and p.refiner_start > 0 and p.refiner_start < 1 else None, denoising_end=p.refiner_start if refiner_enabled and p.refiner_start > 0 and p.refiner_start < 1 else None, output_type='latent' if hasattr(shared.sd_model, 'vae') else 'np', - refiner=False, + is_refiner=False, **task_specific_kwargs ) output = shared.sd_model(**pipe_args) # pylint: disable=not-callable @@ -211,7 +212,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro denoising_end=1 if p.refiner_start > 0 and p.refiner_start < 1 else None, image=output.images[i], output_type='latent' if hasattr(shared.sd_refiner, 'vae') else 'np', - refiner=True + is_refiner=True ) refiner_output = shared.sd_refiner(**pipe_args) # pylint: disable=not-callable if not shared.state.interrupted and not shared.state.skipped: @@ -230,4 +231,4 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro - return results \ No newline at end of file + return results diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index a23660f5c..1f21c0dee 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -26,7 +26,7 @@ def compel_encode_prompt(pipeline: typing.Any, *args, **kwargs): raise TypeError(f"Compel encoding not yet supported for {type(pipeline).__name__}.") return compel_encode_fn(pipeline, *args, **kwargs) -def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str]=None, negative_prompt_2: typing.Optional[str]=None, refiner=False): +def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str]=None, negative_prompt_2: typing.Optional[str]=None, is_refiner: bool = False): if shared.opts.data['prompt_attention'] != 'Compel parser': prompt = convert_to_compel(prompt) negative_prompt = convert_to_compel(negative_prompt) @@ -46,20 +46,20 @@ def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, pro returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, requires_pooled=True, ) - if refiner is False: + if not is_refiner: positive_te1 = compel_te1(prompt) - positive_te2, pooled = compel_te2(prompt_2) + positive_te2, positive_pooled = compel_te2(prompt_2) positive = torch.cat((positive_te1, positive_te2), dim=-1) negative_te1 = compel_te1(negative_prompt) negative_te2, negative_pooled = compel_te2(negative_prompt_2) negative = torch.cat((negative_te1, negative_te2), dim=-1) else: - positive, pooled = compel_te2(prompt) + positive, positive_pooled = compel_te2(prompt) negative, negative_pooled = compel_te2(negative_prompt) shared.log.debug(compel_te1.parse_prompt_string(prompt)) [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length([positive, negative]) - return prompt_embed, pooled, negative_embed, negative_pooled + return prompt_embed, positive_pooled, negative_embed, negative_pooled COMPEL_ENCODE_FN_DICT = {diffusers.StableDiffusionXLPipeline: compel_encode_prompt_sdxl} From 00a5df58062991e9683f166b37956e364f0a3a61 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sun, 6 Aug 2023 07:24:20 +0000 Subject: [PATCH 10/72] Small fix for refiner (untested). --- modules/prompt_parser_diffusers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 1f21c0dee..731eff229 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -26,7 +26,7 @@ def compel_encode_prompt(pipeline: typing.Any, *args, **kwargs): raise TypeError(f"Compel encoding not yet supported for {type(pipeline).__name__}.") return compel_encode_fn(pipeline, *args, **kwargs) -def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str]=None, negative_prompt_2: typing.Optional[str]=None, is_refiner: bool = False): +def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str]=None, negative_prompt_2: typing.Optional[str]=None, is_refiner: bool = None): if shared.opts.data['prompt_attention'] != 'Compel parser': prompt = convert_to_compel(prompt) negative_prompt = convert_to_compel(negative_prompt) @@ -62,4 +62,7 @@ def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, pro [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length([positive, negative]) return prompt_embed, positive_pooled, negative_embed, negative_pooled -COMPEL_ENCODE_FN_DICT = {diffusers.StableDiffusionXLPipeline: compel_encode_prompt_sdxl} +COMPEL_ENCODE_FN_DICT = { + diffusers.StableDiffusionXLPipeline: compel_encode_prompt_sdxl, + diffusers.StableDiffusionImg2ImgPipeline: compel_encode_prompt_sdxl, +} From 2ad35ca3819aed712871bf09c583be09abca9fc7 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sun, 6 Aug 2023 09:39:09 +0000 Subject: [PATCH 11/72] PyLint: Fix --- modules/prompt_parser_diffusers.py | 95 ++++++++++++++++++------------ 1 file changed, 57 insertions(+), 38 deletions(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 731eff229..a66be7cb8 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -1,68 +1,87 @@ +import typing import torch +import diffusers +from compel import Compel, ReturnedEmbeddingsType import modules.shared as shared import modules.prompt_parser as prompt_parser -from compel import Compel, ReturnedEmbeddingsType -import diffusers -import typing + def convert_to_compel(prompt: str): if prompt is None: - return None - all_schedules = prompt_parser.get_learned_conditioning_prompt_schedules(prompt, 100)[0] #100 should be steps, but doesn't actually matter because we can't schedule yet + return None + all_schedules = prompt_parser.get_learned_conditioning_prompt_schedules( + prompt, 100 + )[ + 0 + ] # 100 should be steps, but doesn't actually matter because we can't schedule yet output_list = prompt_parser.parse_prompt_attention(all_schedules[0][1]) converted_prompt = [] for subprompt, weight in output_list: - if subprompt != " ": - if weight == 1: - converted_prompt.append(subprompt) - else: - converted_prompt.append(f"({subprompt}){weight}") + if subprompt != " ": + if weight == 1: + converted_prompt.append(subprompt) + else: + converted_prompt.append(f"({subprompt}){weight}") converted_prompt = " ".join(converted_prompt) return converted_prompt -def compel_encode_prompt(pipeline: typing.Any, *args, **kwargs): - compel_encode_fn = COMPEL_ENCODE_FN_DICT.get(type(pipeline), None) - if compel_encode_fn is None: - raise TypeError(f"Compel encoding not yet supported for {type(pipeline).__name__}.") - return compel_encode_fn(pipeline, *args, **kwargs) -def compel_encode_prompt_sdxl(pipeline: diffusers.StableDiffusionXLPipeline, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str]=None, negative_prompt_2: typing.Optional[str]=None, is_refiner: bool = None): - if shared.opts.data['prompt_attention'] != 'Compel parser': +def compel_encode_prompt(pipeline: typing.Any, *args, **kwargs): + compel_encode_fn = COMPEL_ENCODE_FN_DICT.get(type(pipeline), None) + if compel_encode_fn is None: + raise TypeError( + f"Compel encoding not yet supported for {type(pipeline).__name__}." + ) + return compel_encode_fn(pipeline, *args, **kwargs) + + +def compel_encode_prompt_sdxl( + pipeline: diffusers.StableDiffusionXLPipeline, + prompt: str, + negative_prompt: str, + prompt_2: typing.Optional[str] = None, + negative_prompt_2: typing.Optional[str] = None, + is_refiner: bool = None, +): + if shared.opts.data["prompt_attention"] != "Compel parser": prompt = convert_to_compel(prompt) negative_prompt = convert_to_compel(negative_prompt) prompt_2 = convert_to_compel(prompt_2) negative_prompt_2 = convert_to_compel(negative_prompt_2) compel_te1 = Compel( - tokenizer=pipeline.tokenizer, - text_encoder=pipeline.text_encoder, - returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, - requires_pooled=False, - ) + tokenizer=pipeline.tokenizer, + text_encoder=pipeline.text_encoder, + returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, + requires_pooled=False, + ) compel_te2 = Compel( - tokenizer=pipeline.tokenizer_2, - text_encoder=pipeline.text_encoder_2, - returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, - requires_pooled=True, - ) + tokenizer=pipeline.tokenizer_2, + text_encoder=pipeline.text_encoder_2, + returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, + requires_pooled=True, + ) if not is_refiner: - positive_te1 = compel_te1(prompt) - positive_te2, positive_pooled = compel_te2(prompt_2) - positive = torch.cat((positive_te1, positive_te2), dim=-1) + positive_te1 = compel_te1(prompt) + positive_te2, positive_pooled = compel_te2(prompt_2) + positive = torch.cat((positive_te1, positive_te2), dim=-1) - negative_te1 = compel_te1(negative_prompt) - negative_te2, negative_pooled = compel_te2(negative_prompt_2) - negative = torch.cat((negative_te1, negative_te2), dim=-1) + negative_te1 = compel_te1(negative_prompt) + negative_te2, negative_pooled = compel_te2(negative_prompt_2) + negative = torch.cat((negative_te1, negative_te2), dim=-1) else: - positive, positive_pooled = compel_te2(prompt) - negative, negative_pooled = compel_te2(negative_prompt) + positive, positive_pooled = compel_te2(prompt) + negative, negative_pooled = compel_te2(negative_prompt) shared.log.debug(compel_te1.parse_prompt_string(prompt)) - [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length([positive, negative]) + [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length( + [positive, negative] + ) return prompt_embed, positive_pooled, negative_embed, negative_pooled + COMPEL_ENCODE_FN_DICT = { - diffusers.StableDiffusionXLPipeline: compel_encode_prompt_sdxl, - diffusers.StableDiffusionImg2ImgPipeline: compel_encode_prompt_sdxl, + diffusers.StableDiffusionXLPipeline: compel_encode_prompt_sdxl, + diffusers.StableDiffusionImg2ImgPipeline: compel_encode_prompt_sdxl, } From 41418c55311c61815a0e0ee9872841ed66c07670 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sun, 6 Aug 2023 09:41:08 +0000 Subject: [PATCH 12/72] Add TODO for scheduling. --- modules/prompt_parser_diffusers.py | 5 ++--- webui.sh | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index a66be7cb8..0c26bd65d 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -9,11 +9,10 @@ import modules.prompt_parser as prompt_parser def convert_to_compel(prompt: str): if prompt is None: return None + # TODO: 100 should be steps, but doesn't actually matter because we can't schedule yet all_schedules = prompt_parser.get_learned_conditioning_prompt_schedules( prompt, 100 - )[ - 0 - ] # 100 should be steps, but doesn't actually matter because we can't schedule yet + )[0] output_list = prompt_parser.parse_prompt_attention(all_schedules[0][1]) converted_prompt = [] for subprompt, weight in output_list: diff --git a/webui.sh b/webui.sh index 550008f2d..40ff50b82 100755 --- a/webui.sh +++ b/webui.sh @@ -7,7 +7,7 @@ # change to local directory cd -- "$(dirname -- "$0")" -can_run_as_root=0 +can_run_as_root=1 export ERROR_REPORTING=FALSE export PIP_IGNORE_INSTALLED=0 From 1b60d4683e2a25e9d67fd7d92bbe3163f253f7f3 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sun, 6 Aug 2023 11:10:44 +0000 Subject: [PATCH 13/72] Run black for formatting, fix pylint errors, and change to warning. --- modules/processing_diffusers.py | 2 +- modules/prompt_parser_diffusers.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index ffe3fe871..8cf96d5f5 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -63,7 +63,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro pooled = None negative_embed = None negative_pooled = None - if shared.opts.data['prompt_attention'] != 'Fixed attention': + if shared.opts.data['prompt_attention'] in {'Compel parser', 'Full parser'}: prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompt(model, prompt, negative_prompt, prompt_2, negative_prompt_2, is_refiner) if 'prompt' in possible: if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None: diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 0c26bd65d..f395df463 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -9,10 +9,11 @@ import modules.prompt_parser as prompt_parser def convert_to_compel(prompt: str): if prompt is None: return None - # TODO: 100 should be steps, but doesn't actually matter because we can't schedule yet all_schedules = prompt_parser.get_learned_conditioning_prompt_schedules( prompt, 100 - )[0] + )[ + 0 + ] output_list = prompt_parser.parse_prompt_attention(all_schedules[0][1]) converted_prompt = [] for subprompt, weight in output_list: @@ -28,9 +29,10 @@ def convert_to_compel(prompt: str): def compel_encode_prompt(pipeline: typing.Any, *args, **kwargs): compel_encode_fn = COMPEL_ENCODE_FN_DICT.get(type(pipeline), None) if compel_encode_fn is None: - raise TypeError( + shared.log.warning( f"Compel encoding not yet supported for {type(pipeline).__name__}." ) + return (None,) * 4 return compel_encode_fn(pipeline, *args, **kwargs) From 7a131296b2e47f579d6d6bce950c5e5fcdd1fe8a Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sun, 6 Aug 2023 11:16:53 +0000 Subject: [PATCH 14/72] Revert accidental can_run_as_root change. --- webui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui.sh b/webui.sh index ff834f1b2..2800b2d3d 100755 --- a/webui.sh +++ b/webui.sh @@ -7,7 +7,7 @@ # change to local directory cd -- "$(dirname -- "$0")" -can_run_as_root=1 +can_run_as_root=0 export ERROR_REPORTING=FALSE export PIP_IGNORE_INSTALLED=0 From 764c15aa9f5de66237d5ef3aefda2a15557d2cdf Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sun, 6 Aug 2023 11:19:55 +0000 Subject: [PATCH 15/72] Allow running as root inside a Docker container. --- webui.sh | 4 ++-- wiki | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webui.sh b/webui.sh index 2800b2d3d..7f339f585 100755 --- a/webui.sh +++ b/webui.sh @@ -44,8 +44,8 @@ do esac done -# Do not run as root -if [[ $(id -u) -eq 0 && can_run_as_root -eq 0 ]] +# Do not run as root unless inside a Docker container +if [[ $(id -u) -eq 0 && can_run_as_root -eq 0 && ! -f /.dockerenv ]] then echo "Cannot run as root" exit 1 diff --git a/wiki b/wiki index 35142f02a..f76cc3a9a 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 35142f02aee984aac261d1e1f563768d008398f6 +Subproject commit f76cc3a9ac124882f58f35ba3dfe930744109456 From 7ea4f32cecb2d0d90821fc0e670d5d4e586ff070 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sun, 6 Aug 2023 11:22:33 +0000 Subject: [PATCH 16/72] Check based on sd_model_type. --- modules/prompt_parser_diffusers.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index f395df463..d202b728f 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -26,17 +26,7 @@ def convert_to_compel(prompt: str): return converted_prompt -def compel_encode_prompt(pipeline: typing.Any, *args, **kwargs): - compel_encode_fn = COMPEL_ENCODE_FN_DICT.get(type(pipeline), None) - if compel_encode_fn is None: - shared.log.warning( - f"Compel encoding not yet supported for {type(pipeline).__name__}." - ) - return (None,) * 4 - return compel_encode_fn(pipeline, *args, **kwargs) - - -def compel_encode_prompt_sdxl( +def compel_encode_prompt( pipeline: diffusers.StableDiffusionXLPipeline, prompt: str, negative_prompt: str, @@ -44,6 +34,11 @@ def compel_encode_prompt_sdxl( negative_prompt_2: typing.Optional[str] = None, is_refiner: bool = None, ): + if shared.sd_model_type not in {"sd", "sdxl"}: + shared.log.warning( + f"Compel encoding not yet supported for {type(pipeline).__name__}." + ) + return (None,) * 4 if shared.opts.data["prompt_attention"] != "Compel parser": prompt = convert_to_compel(prompt) negative_prompt = convert_to_compel(negative_prompt) @@ -80,9 +75,3 @@ def compel_encode_prompt_sdxl( [positive, negative] ) return prompt_embed, positive_pooled, negative_embed, negative_pooled - - -COMPEL_ENCODE_FN_DICT = { - diffusers.StableDiffusionXLPipeline: compel_encode_prompt_sdxl, - diffusers.StableDiffusionImg2ImgPipeline: compel_encode_prompt_sdxl, -} From cdbb310fde32984e3045e5abdc9c81f8fa4aae63 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sun, 6 Aug 2023 11:30:37 +0000 Subject: [PATCH 17/72] Revert submodule updates. --- extensions-builtin/sd-dynamic-thresholding | 2 +- extensions-builtin/sd-extension-system-info | 2 +- extensions-builtin/sd-webui-controlnet | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions-builtin/sd-dynamic-thresholding b/extensions-builtin/sd-dynamic-thresholding index c60fe071e..5349f0087 160000 --- a/extensions-builtin/sd-dynamic-thresholding +++ b/extensions-builtin/sd-dynamic-thresholding @@ -1 +1 @@ -Subproject commit c60fe071e5938974a52611f87ca2fd1878aa6d15 +Subproject commit 5349f008721480a572ab9a917533afdd0dae7b9e diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index 19d190e71..9d3c0ca0f 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit 19d190e71b2f1399623519db741d2a6bf8d2c86c +Subproject commit 9d3c0ca0f2dc8f8973b3d08f5ec1fa8bbd726155 diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index af34f5144..5ae9b4a1a 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit af34f514499933d5e7e1641a6b13d56411e45e76 +Subproject commit 5ae9b4a1a0c7d9a2938e75aaf052ab078623066f From 0933b876d554017c8f18f802cc56b17671ee8dd2 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi Date: Sun, 6 Aug 2023 18:52:01 +0200 Subject: [PATCH 18/72] WIP --- modules/processing_diffusers.py | 10 ++++--- modules/prompt_parser_diffusers.py | 47 +++++++++++++++++------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index d6d5617dd..bbe0d5fb5 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -68,15 +68,17 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro if 'prompt' in possible: if hasattr(model, 'text_encoder') and 'prompt_embeds' in possible and prompt_embed is not None: args['prompt_embeds'] = prompt_embed - args['pooled_prompt_embeds'] = pooled - args['prompt_2'] = None #Cannot pass prompts when passing embeds + if shared.sd_model_type == "sdxl": + args['pooled_prompt_embeds'] = pooled + args['prompt_2'] = None #Cannot pass prompts when passing embeds else: args['prompt'] = prompt if 'negative_prompt' in possible: if hasattr(model, 'text_encoder') and 'negative_prompt_embeds' in possible and negative_embed is not None: args['negative_prompt_embeds'] = negative_embed - args['negative_pooled_prompt_embeds'] = negative_pooled - args['negative_prompt_2'] = None + if shared.sd_model_type == "sdxl": + args['negative_pooled_prompt_embeds'] = negative_pooled + args['negative_prompt_2'] = None else: args['negative_prompt'] = negative_prompt if 'num_inference_steps' in possible: diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index d202b728f..70ce69f28 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -38,7 +38,7 @@ def compel_encode_prompt( shared.log.warning( f"Compel encoding not yet supported for {type(pipeline).__name__}." ) - return (None,) * 4 + return (None, None, None, None) if shared.opts.data["prompt_attention"] != "Compel parser": prompt = convert_to_compel(prompt) negative_prompt = convert_to_compel(negative_prompt) @@ -52,26 +52,33 @@ def compel_encode_prompt( requires_pooled=False, ) - compel_te2 = Compel( - tokenizer=pipeline.tokenizer_2, - text_encoder=pipeline.text_encoder_2, - returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, - requires_pooled=True, - ) - if not is_refiner: - positive_te1 = compel_te1(prompt) - positive_te2, positive_pooled = compel_te2(prompt_2) - positive = torch.cat((positive_te1, positive_te2), dim=-1) + if shared.sd_model_type == "sdxl": + compel_te2 = Compel( + tokenizer=pipeline.tokenizer_2, + text_encoder=pipeline.text_encoder_2, + returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, + requires_pooled=True, + ) + if not is_refiner: + positive_te1 = compel_te1(prompt) + positive_te2, positive_pooled = compel_te2(prompt_2) + positive = torch.cat((positive_te1, positive_te2), dim=-1) - negative_te1 = compel_te1(negative_prompt) - negative_te2, negative_pooled = compel_te2(negative_prompt_2) - negative = torch.cat((negative_te1, negative_te2), dim=-1) - else: - positive, positive_pooled = compel_te2(prompt) - negative, negative_pooled = compel_te2(negative_prompt) + negative_te1 = compel_te1(negative_prompt) + negative_te2, negative_pooled = compel_te2(negative_prompt_2) + negative = torch.cat((negative_te1, negative_te2), dim=-1) + else: + positive, positive_pooled = compel_te2(prompt) + negative, negative_pooled = compel_te2(negative_prompt) - shared.log.debug(compel_te1.parse_prompt_string(prompt)) - [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length( + shared.log.debug(f"Parsed Compel string: {compel_te1.parse_prompt_string(prompt)}") + [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length( + [positive, negative] + ) + return prompt_embed, positive_pooled, negative_embed, negative_pooled + + positive, negative = compel_te1(prompt), compel_te1(negative_prompt) + [prompt_embed, negative_embed] = compel_te1.pad_conditioning_tensors_to_same_length( [positive, negative] ) - return prompt_embed, positive_pooled, negative_embed, negative_pooled + return prompt_embed, None, negative_embed, None From a1696269fb43bbfd269ce771528c56026f5e5be7 Mon Sep 17 00:00:00 2001 From: AI-Casanova Date: Mon, 7 Aug 2023 01:20:47 +0000 Subject: [PATCH 19/72] Enable A1111 and Full parsing conversion --- modules/prompt_parser_diffusers.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 70ce69f28..d16ffaee5 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -39,6 +39,15 @@ def compel_encode_prompt( f"Compel encoding not yet supported for {type(pipeline).__name__}." ) return (None, None, None, None) + + if shared.sd_model_type == "sdxl": + embedding_type = ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED + # if clip_skip > 1: How to pass this from processing.py? + # embedding_type = ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NORMALIZED + else: + embedding_type = ReturnedEmbeddingsType.LAST_HIDDEN_STATES_NORMALIZED + + if shared.opts.data["prompt_attention"] != "Compel parser": prompt = convert_to_compel(prompt) negative_prompt = convert_to_compel(negative_prompt) @@ -48,7 +57,7 @@ def compel_encode_prompt( compel_te1 = Compel( tokenizer=pipeline.tokenizer, text_encoder=pipeline.text_encoder, - returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, + returned_embeddings_type=embedding_type, requires_pooled=False, ) @@ -56,7 +65,7 @@ def compel_encode_prompt( compel_te2 = Compel( tokenizer=pipeline.tokenizer_2, text_encoder=pipeline.text_encoder_2, - returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, + returned_embeddings_type=embedding_type, requires_pooled=True, ) if not is_refiner: @@ -82,3 +91,7 @@ def compel_encode_prompt( [positive, negative] ) return prompt_embed, None, negative_embed, None + +# LAST_HIDDEN_STATES_NORMALIZED = 0 # SD1/2 regular +# PENULTIMATE_HIDDEN_STATES_NORMALIZED = 1 # SD1.5 with "clip skip" +# PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED = 2 # SDXL From af2b4156382ddb1fe116ed1aadf0ea02b5f25864 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi <2190658+hameerabbasi@users.noreply.github.com> Date: Mon, 7 Aug 2023 03:58:12 +0200 Subject: [PATCH 20/72] Add support for clip_skip to the diffusers back-end. --- modules/processing_diffusers.py | 2 +- modules/prompt_parser_diffusers.py | 39 +++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index bbe0d5fb5..3217c3c67 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -64,7 +64,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro negative_embed = None negative_pooled = None if shared.opts.data['prompt_attention'] in {'Compel parser', 'Full parser'}: - prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompt(model, prompt, negative_prompt, prompt_2, negative_prompt_2, is_refiner) + prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompt(model, prompt, negative_prompt, prompt_2, negative_prompt_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: args['prompt_embeds'] = prompt_embed diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index d16ffaee5..9ca929053 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -11,9 +11,7 @@ def convert_to_compel(prompt: str): return None all_schedules = prompt_parser.get_learned_conditioning_prompt_schedules( prompt, 100 - )[ - 0 - ] + )[0] output_list = prompt_parser.parse_prompt_attention(all_schedules[0][1]) converted_prompt = [] for subprompt, weight in output_list: @@ -26,6 +24,13 @@ def convert_to_compel(prompt: str): return converted_prompt +CLIP_SKIP_MAPPING = { + None: ReturnedEmbeddingsType.LAST_HIDDEN_STATES_NORMALIZED, + 1: ReturnedEmbeddingsType.LAST_HIDDEN_STATES_NORMALIZED, + 2: ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NORMALIZED, +} + + def compel_encode_prompt( pipeline: diffusers.StableDiffusionXLPipeline, prompt: str, @@ -33,6 +38,7 @@ def compel_encode_prompt( prompt_2: typing.Optional[str] = None, negative_prompt_2: typing.Optional[str] = None, is_refiner: bool = None, + clip_skip: typing.Optional[int] = None, ): if shared.sd_model_type not in {"sd", "sdxl"}: shared.log.warning( @@ -41,12 +47,17 @@ def compel_encode_prompt( return (None, None, None, None) if shared.sd_model_type == "sdxl": - embedding_type = ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED - # if clip_skip > 1: How to pass this from processing.py? - # embedding_type = ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NORMALIZED + embedding_type = ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED + if clip_skip is not None: + shared.log.debug("CLIP skip ignored as it is unsupported for SDXL") else: - embedding_type = ReturnedEmbeddingsType.LAST_HIDDEN_STATES_NORMALIZED - + embedding_type = CLIP_SKIP_MAPPING.get( + clip_skip, ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NORMALIZED + ) + if clip_skip not in CLIP_SKIP_MAPPING: + shared.log.warning( + f"Recieved a CLIP skip of {clip_skip}, but only {set(CLIP_SKIP_MAPPING.keys())} is supported." + ) if shared.opts.data["prompt_attention"] != "Compel parser": prompt = convert_to_compel(prompt) @@ -80,10 +91,13 @@ def compel_encode_prompt( positive, positive_pooled = compel_te2(prompt) negative, negative_pooled = compel_te2(negative_prompt) - shared.log.debug(f"Parsed Compel string: {compel_te1.parse_prompt_string(prompt)}") - [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length( - [positive, negative] + shared.log.debug( + f"Parsed Compel string: {compel_te1.parse_prompt_string(prompt)}" ) + [ + prompt_embed, + negative_embed, + ] = compel_te2.pad_conditioning_tensors_to_same_length([positive, negative]) return prompt_embed, positive_pooled, negative_embed, negative_pooled positive, negative = compel_te1(prompt), compel_te1(negative_prompt) @@ -92,6 +106,7 @@ def compel_encode_prompt( ) return prompt_embed, None, negative_embed, None + # LAST_HIDDEN_STATES_NORMALIZED = 0 # SD1/2 regular # PENULTIMATE_HIDDEN_STATES_NORMALIZED = 1 # SD1.5 with "clip skip" -# PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED = 2 # SDXL +# PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED = 2 # SDXL From 0a863930cf9800f9d752afdc6da40898f13dd20e Mon Sep 17 00:00:00 2001 From: Hameer Abbasi <2190658+hameerabbasi@users.noreply.github.com> Date: Mon, 7 Aug 2023 04:02:39 +0200 Subject: [PATCH 21/72] Remove superfluous comment. --- modules/prompt_parser_diffusers.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 9ca929053..2dd8a6d98 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -105,8 +105,3 @@ def compel_encode_prompt( [positive, negative] ) return prompt_embed, None, negative_embed, None - - -# LAST_HIDDEN_STATES_NORMALIZED = 0 # SD1/2 regular -# PENULTIMATE_HIDDEN_STATES_NORMALIZED = 1 # SD1.5 with "clip skip" -# PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED = 2 # SDXL From 41428efb5d4634084c44b3029c2d5b2797bd87a7 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi <2190658+hameerabbasi@users.noreply.github.com> Date: Mon, 7 Aug 2023 04:03:43 +0200 Subject: [PATCH 22/72] Make log message more descriptive. --- modules/prompt_parser_diffusers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 2dd8a6d98..1f7a4ed42 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -56,7 +56,8 @@ def compel_encode_prompt( ) if clip_skip not in CLIP_SKIP_MAPPING: shared.log.warning( - f"Recieved a CLIP skip of {clip_skip}, but only {set(CLIP_SKIP_MAPPING.keys())} is supported." + f"Recieved a CLIP skip of {clip_skip}, but only {set(CLIP_SKIP_MAPPING.keys())} is supported. " + "Falling back to 2." ) if shared.opts.data["prompt_attention"] != "Compel parser": From 66ad9ce368ca849e767854d2c41b7bf172fb9e37 Mon Sep 17 00:00:00 2001 From: Hameer Abbasi <2190658+hameerabbasi@users.noreply.github.com> Date: Mon, 7 Aug 2023 04:16:41 +0200 Subject: [PATCH 23/72] Actually pass through CLIP_skip. --- modules/processing_diffusers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 3217c3c67..e6454e031 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -161,6 +161,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro denoising_end=p.refiner_start if refiner_enabled and p.refiner_start > 0 and p.refiner_start < 1 else None, output_type='latent' if hasattr(shared.sd_model, 'vae') else 'np', is_refiner=False, + clip_skip=p.clip_skip, **task_specific_kwargs ) output = shared.sd_model(**pipe_args) # pylint: disable=not-callable @@ -214,7 +215,8 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro denoising_end=1 if p.refiner_start > 0 and p.refiner_start < 1 else None, image=output.images[i], output_type='latent' if hasattr(shared.sd_refiner, 'vae') else 'np', - is_refiner=True + is_refiner=True, + clip_skip=p.clip_skip, ) refiner_output = shared.sd_refiner(**pipe_args) # pylint: disable=not-callable if not shared.state.interrupted and not shared.state.skipped: From 7859fd22a0bea4a74072f5078a72c4c04195dad9 Mon Sep 17 00:00:00 2001 From: AI-Casanova <54461896+AI-Casanova@users.noreply.github.com> Date: Sun, 6 Aug 2023 22:33:27 -0500 Subject: [PATCH 24/72] Unload LoRA on early stoppage --- modules/processing_diffusers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 15364b739..ccbaed710 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -132,6 +132,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro # parsed_prompt = [parse_prompt_attention(prompt) for prompt in prompts] if shared.state.interrupted or shared.state.skipped: + unload_diffusers_lora() return results if shared.opts.diffusers_move_base and not shared.sd_model.has_accelerate: @@ -154,6 +155,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro output = shared.sd_model(**pipe_args) # pylint: disable=not-callable if shared.state.interrupted or shared.state.skipped: + unload_diffusers_lora() return results if shared.sd_refiner is None or not p.enable_hr: From 5fd3c5ba0122509614bd027fbef8ab86232aee39 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 7 Aug 2023 14:52:19 +0200 Subject: [PATCH 25/72] update lora methods --- extensions-builtin/sd-dynamic-thresholding | 2 +- extensions-builtin/sd-extension-system-info | 2 +- extensions-builtin/sd-webui-controlnet | 2 +- html/locale_en.json | 4 ++-- modules/lora_diffusers.py | 12 ++++++------ modules/processing_diffusers.py | 2 +- modules/shared.py | 2 +- wiki | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/extensions-builtin/sd-dynamic-thresholding b/extensions-builtin/sd-dynamic-thresholding index 5349f0087..c60fe071e 160000 --- a/extensions-builtin/sd-dynamic-thresholding +++ b/extensions-builtin/sd-dynamic-thresholding @@ -1 +1 @@ -Subproject commit 5349f008721480a572ab9a917533afdd0dae7b9e +Subproject commit c60fe071e5938974a52611f87ca2fd1878aa6d15 diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index 9d3c0ca0f..19d190e71 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit 9d3c0ca0f2dc8f8973b3d08f5ec1fa8bbd726155 +Subproject commit 19d190e71b2f1399623519db741d2a6bf8d2c86c diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 5ae9b4a1a..af34f5144 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 5ae9b4a1a0c7d9a2938e75aaf052ab078623066f +Subproject commit af34f514499933d5e7e1641a6b13d56411e45e76 diff --git a/html/locale_en.json b/html/locale_en.json index c67068070..cbfd34342 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -575,8 +575,8 @@ {"id":"","label":"Enable VAE tiling","localized":"","hint":"Divide large images into overlapping tiles with limited VRAM. Might result in a minor increase in processing time. Use with Enable Attention Slicing"}, {"id":"","label":"Enable attention slicing","localized":"","hint":"Performs attention computation in steps instead of all at once. 10% slower inference times. Greatly reduces memory usage. Best used, period"}, {"id":"","label":"Diffusers model loading variant","localized":"","hint":""}, - {"id":"","label":"Diffusers VAE 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"} ], "scripts": [ {"id":"","label":"Script","localized":"","hint":""}, diff --git a/modules/lora_diffusers.py b/modules/lora_diffusers.py index 3df991c77..816ff788b 100644 --- a/modules/lora_diffusers.py +++ b/modules/lora_diffusers.py @@ -13,7 +13,7 @@ lora_state = { # TODO Lora state for Diffusers def unload_diffusers_lora(): try: pipe = shared.sd_model - if shared.opts.diffusers_lora_loader == "Diffusers": + if shared.opts.diffusers_lora_loader == "diffusers default": pipe.unload_lora_weights() pipe._remove_text_encoder_monkey_patch() # pylint: disable=W0212 proc_cls_name = next(iter(pipe.unet.attn_processors.values())).__class__.__name__ @@ -24,9 +24,9 @@ def unload_diffusers_lora(): lora_state['all_loras'].reverse() lora_state['multiplier'].reverse() for i, lora_network in enumerate(lora_state['all_loras']): - if shared.opts.diffusers_lora_loader == "kohya-merge": + if shared.opts.diffusers_lora_loader == "merge and apply": lora_network.restore_from(multiplier=lora_state['multiplier'][i]) - if shared.opts.diffusers_lora_loader == "kohya-apply": + if shared.opts.diffusers_lora_loader == "sequential apply": lora_network.unapply_to() lora_state['active'] = False lora_state['loaded'] = 0 @@ -43,7 +43,7 @@ def load_diffusers_lora(name, lora, strength = 1.0): lora_state['active'] = True lora_state['loaded'] += 1 lora_state['multiplier'].append(strength) - if shared.opts.diffusers_lora_loader == "Diffusers": + if shared.opts.diffusers_lora_loader == "diffusers default": pipe.load_lora_weights(lora.filename, cache_dir=shared.opts.diffusers_dir, local_files_only=True, lora_scale=strength) shared.log.info(f"Diffusers LoRA loaded: {name} {lora_state['multiplier']}") else: @@ -55,9 +55,9 @@ def load_diffusers_lora(name, lora, strength = 1.0): text_encoders = pipe.text_encoder lora_network: LoRANetwork = create_network_from_weights(text_encoders, pipe.unet, lora_sd, multiplier=strength) lora_network.load_state_dict(lora_sd) - if shared.opts.diffusers_lora_loader == "kohya-merge": + if shared.opts.diffusers_lora_loader == "merge and apply": lora_network.merge_to(multiplier=strength) - if shared.opts.diffusers_lora_loader == "kohya-apply": + if shared.opts.diffusers_lora_loader == "sequential apply": lora_network.to(pipe.device, dtype=pipe.unet.dtype) lora_network.apply_to(multiplier=strength) lora_state['all_loras'].append(lora_network) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index f159bb91b..bf40fa9d0 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -93,7 +93,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro args['callback_steps'] = 1 if 'callback' in possible: args['callback'] = diffusers_callback - if 'cross_attention_kwargs' in possible and lora_state['active'] and shared.opts.diffusers_lora_loader == "Diffusers": + if 'cross_attention_kwargs' in possible and lora_state['active'] and shared.opts.diffusers_lora_loader == "diffusers default": args['cross_attention_kwargs'] = { 'scale': lora_state['multiplier'][0]} for arg in kwargs: if arg in possible: diff --git a/modules/shared.py b/modules/shared.py index fe1bb9c15..bd5444336 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -409,7 +409,7 @@ options_templates.update(options_section(('diffusers', "Diffusers Settings"), { "diffusers_attention_slicing": OptionInfo(True if devices.backend == "ipex" else False, "Enable attention slicing"), "diffusers_model_load_variant": OptionInfo("default", "Diffusers model loading variant", gr.Radio, lambda: {"choices": ['default', 'fp32', 'fp16']}), "diffusers_vae_load_variant": OptionInfo("default", "Diffusers VAE loading variant", gr.Radio, lambda: {"choices": ['default', 'fp32', 'fp16']}), - "diffusers_lora_loader": OptionInfo("default", "Diffusers LoRA loading variant", gr.Radio, lambda: {"choices": ['kohya-apply', 'kohya-merge', 'Diffusers']}), + "diffusers_lora_loader": OptionInfo("sequential apply", "Diffusers LoRA loading variant", gr.Radio, lambda: {"choices": ['sequential apply', 'merge and apply', 'diffusers default']}), # "diffusers_force_zeros": OptionInfo(False, "Force zeros for prompts when empty"), # "diffusers_aesthetics_score": OptionInfo(6.0, "Require aesthetic score", gr.Slider, {"minimum": 0, "maximum": 10, "step": 0.1}), })) diff --git a/wiki b/wiki index f76cc3a9a..35142f02a 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit f76cc3a9ac124882f58f35ba3dfe930744109456 +Subproject commit 35142f02aee984aac261d1e1f563768d008398f6 From 23f6b66bd78338848915d583687014385ab7deff Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 7 Aug 2023 15:45:49 +0200 Subject: [PATCH 26/72] update requirements --- CHANGELOG.md | 17 ++++++++++++++++- README.md | 14 +++++++++----- installer.py | 4 +++- requirements.txt | 7 +++---- wiki | 2 +- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8156706d..09f1ed913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Change Log for SD.Next +## Update for 2023-08-07 + +- diffusers: + - **prompt attention** for sd and sd-xl + native `compel` implementation and standrd -> compel translation + thanks @ai-casanova + - advanced **lora load/apply** methods + in addition to standard lora loading that was recently added to sd-xl using diffusers, now we have + - **sequential apply** (load & apply multiple loras in sequential manner) and + - **merge and apply** (load multiple loras and merge before applying to model) + see *settings -> diffusers -> lora methods* + thanks @hameerabbasi and @ai-casanova +- general: + - updated requirements + this time its a bigger change so upgrade may take longer to install new requirements + ## Update for 2023-08-05 Another minor update, but it unlocks some cool new items... @@ -11,7 +27,6 @@ Another minor update, but it unlocks some cool new items... - new torch 2.0 with ipex (intel arc) - additional callbacks for extensions enables latest comfyui extension - - update requirements ## Update for 2023-07-30 diff --git a/README.md b/README.md index 73b78ebed..10283289a 100644 --- a/README.md +++ b/README.md @@ -71,15 +71,15 @@ Additional models will be added as they become available and there is public int - *Intel Arc* GPUs using *Intel OneAPI* **Ipex/XPU** libraries - *Apple M1/M2* on *OSX* using built-in support in Torch with **MPS** optimizations -## [Installation Instructions](https://github.com/vladmandic/automatic/wiki/Installation) +## Install & Run -### Common Problems - -- [Common Installation Errors ](https://github.com/vladmandic/automatic/discussions/1627) -- [Q&A Discussions](https://github.com/vladmandic/automatic/discussions/1011) +- [Step-by-step install guide](https://github.com/vladmandic/automatic/wiki/Installation) +- [Advanced install notes](https://github.com/vladmandic/automatic/wiki/Advanced-Install) ### Installation Notes +- [Common installation errors](https://github.com/vladmandic/automatic/discussions/1627) +- [FAQ](https://github.com/vladmandic/automatic/discussions/1011) - Server can run without virtual environment, but it is recommended to use it to avoid library version conflicts with other applications - **nVidia/CUDA** and **AMD/ROCm** are auto-detected if present and available, @@ -87,6 +87,10 @@ Additional models will be added as they become available and there is public int as installer will assume CPU-only environment - Full startup sequence is logged in `sdnext.log`, so if you encounter any issues, please check it first +### Run + +Once SD.Next is installed, simply run `webui.bat` (*Windows*) or `webui.sh` (*Linux or MacOS*) + Below is partial list of all available parameters, run `webui --help` for the full list: Setup options: diff --git a/installer.py b/installer.py index 67390b382..60ba1dfb2 100644 --- a/installer.py +++ b/installer.py @@ -428,8 +428,10 @@ def install_packages(): install(invisiblewatermark_package, 'invisible-watermark') install('onnxruntime==1.15.1', 'onnxruntime', ignore=True) install('pi-heif', 'pi_heif', ignore=True) - tensorflow_package = os.environ.get('TENSORFLOW_PACKAGE', 'tensorflow==2.12.0') + tensorflow_package = os.environ.get('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0') install(tensorflow_package, 'tensorflow', ignore=True) + # bitsandbytes_package = os.environ.get('BITSANDBYTES_PACKAGE', 'bitsandbytes==0.39.1') + # install(bitsandbytes_package, 'bitsandbytes', ignore=True) if args.profile: print_profile(pr, 'Packages') diff --git a/requirements.txt b/requirements.txt index 713365b0a..7dac5b439 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ aiohttp anyio appdirs astunparse -bitsandbytes blendmodes clean-fid easydev @@ -47,15 +46,15 @@ typing-extensions==4.7.1 antlr4-python3-runtime==4.9.3 requests==2.31.0 tqdm==4.65.0 -accelerate==0.20.3 +accelerate==0.21.0 opencv-python-headless==4.7.0.72 diffusers==0.19.3 einops==0.4.1 gradio==3.32.0 huggingface_hub==0.16.4 numexpr==2.8.4 -numpy==1.23.5 -numba==0.57.0 +numpy==1.24.4 +numba==0.57.1 pandas==1.5.3 protobuf==3.20.3 pytorch_lightning==1.9.4 diff --git a/wiki b/wiki index 35142f02a..d7cd33058 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 35142f02aee984aac261d1e1f563768d008398f6 +Subproject commit d7cd33058c4b7e09460cc58ee62850bf7705bef5 From 0a3e82106731c75d7a368e0b2fd667fc274a5193 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 7 Aug 2023 17:19:30 +0000 Subject: [PATCH 27/72] diffuser auto-pipeline and fix vae --- CHANGELOG.md | 11 +++- TODO.md | 1 + configs/sd_xl_base.yaml | 98 +++++++++++++++++++++++++++++++++ configs/sd_xl_refiner.yaml | 91 ++++++++++++++++++++++++++++++ installer.py | 10 +++- launch.py | 1 + modules/processing_diffusers.py | 28 ++++------ modules/sd_models.py | 95 +++++++++++++++++++++----------- modules/sd_vae.py | 14 ++--- modules/shared.py | 1 + 10 files changed, 291 insertions(+), 59 deletions(-) create mode 100644 configs/sd_xl_base.yaml create mode 100644 configs/sd_xl_refiner.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 09f1ed913..6eee36183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,15 +3,20 @@ ## Update for 2023-08-07 - diffusers: + - **pipeline autodetect** + if pipeline is set to autodetect (default for new installs), app will try to autodetect pipeline based on selected model + this should reduce user errors such as loading sd-xl model when sd pipeline is selected - **prompt attention** for sd and sd-xl native `compel` implementation and standrd -> compel translation thanks @ai-casanova - advanced **lora load/apply** methods in addition to standard lora loading that was recently added to sd-xl using diffusers, now we have - - **sequential apply** (load & apply multiple loras in sequential manner) and + - **sequential apply** (load & apply multiple loras in sequential manner) and - **merge and apply** (load multiple loras and merge before applying to model) - see *settings -> diffusers -> lora methods* - thanks @hameerabbasi and @ai-casanova + see *settings -> diffusers -> lora methods* + thanks @hameerabbasi and @ai-casanova + - **sd-xl vae** from safetensors now applies correct config + result is that 3rd party vaes can be used without washed out colors - general: - updated requirements this time its a bigger change so upgrade may take longer to install new requirements diff --git a/TODO.md b/TODO.md index 6428c9e68..c178e4c20 100644 --- a/TODO.md +++ b/TODO.md @@ -26,6 +26,7 @@ Stuff to be added, in no particular order... - Port `p.all_hr_prompts` - Import core repos to reduce dependencies - Update `gradio` + - Parse StabilityAI `modelspec` metadata - Non-technical: - Create additional themes - Update Wiki diff --git a/configs/sd_xl_base.yaml b/configs/sd_xl_base.yaml new file mode 100644 index 000000000..8aaf5b6ec --- /dev/null +++ b/configs/sd_xl_base.yaml @@ -0,0 +1,98 @@ +model: + target: sgm.models.diffusion.DiffusionEngine + params: + scale_factor: 0.13025 + disable_first_stage_autocast: True + + denoiser_config: + target: sgm.modules.diffusionmodules.denoiser.DiscreteDenoiser + params: + num_idx: 1000 + + weighting_config: + target: sgm.modules.diffusionmodules.denoiser_weighting.EpsWeighting + scaling_config: + target: sgm.modules.diffusionmodules.denoiser_scaling.EpsScaling + discretization_config: + target: sgm.modules.diffusionmodules.discretizer.LegacyDDPMDiscretization + + network_config: + target: sgm.modules.diffusionmodules.openaimodel.UNetModel + params: + adm_in_channels: 2816 + num_classes: sequential + use_checkpoint: True + in_channels: 4 + out_channels: 4 + model_channels: 320 + attention_resolutions: [4, 2] + num_res_blocks: 2 + channel_mult: [1, 2, 4] + num_head_channels: 64 + use_spatial_transformer: True + use_linear_in_transformer: True + transformer_depth: [1, 2, 10] # note: the first is unused (due to attn_res starting at 2) 32, 16, 8 --> 64, 32, 16 + context_dim: 2048 + spatial_transformer_attn_type: softmax-xformers + legacy: False + + conditioner_config: + target: sgm.modules.GeneralConditioner + params: + emb_models: + # crossattn cond + - is_trainable: False + input_key: txt + target: sgm.modules.encoders.modules.FrozenCLIPEmbedder + params: + layer: hidden + layer_idx: 11 + # crossattn and vector cond + - is_trainable: False + input_key: txt + target: sgm.modules.encoders.modules.FrozenOpenCLIPEmbedder2 + params: + arch: ViT-bigG-14 + version: laion2b_s39b_b160k + freeze: True + layer: penultimate + always_return_pooled: True + legacy: False + # vector cond + - is_trainable: False + input_key: original_size_as_tuple + target: sgm.modules.encoders.modules.ConcatTimestepEmbedderND + params: + outdim: 256 # multiplied by two + # vector cond + - is_trainable: False + input_key: crop_coords_top_left + target: sgm.modules.encoders.modules.ConcatTimestepEmbedderND + params: + outdim: 256 # multiplied by two + # vector cond + - is_trainable: False + input_key: target_size_as_tuple + target: sgm.modules.encoders.modules.ConcatTimestepEmbedderND + params: + outdim: 256 # multiplied by two + + first_stage_config: + target: sgm.models.autoencoder.AutoencoderKLInferenceWrapper + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + attn_type: vanilla-xformers + double_z: true + z_channels: 4 + resolution: 256 + in_channels: 3 + out_ch: 3 + ch: 128 + ch_mult: [1, 2, 4, 4] + num_res_blocks: 2 + attn_resolutions: [] + dropout: 0.0 + lossconfig: + target: torch.nn.Identity diff --git a/configs/sd_xl_refiner.yaml b/configs/sd_xl_refiner.yaml new file mode 100644 index 000000000..cab5fe283 --- /dev/null +++ b/configs/sd_xl_refiner.yaml @@ -0,0 +1,91 @@ +model: + target: sgm.models.diffusion.DiffusionEngine + params: + scale_factor: 0.13025 + disable_first_stage_autocast: True + + denoiser_config: + target: sgm.modules.diffusionmodules.denoiser.DiscreteDenoiser + params: + num_idx: 1000 + + weighting_config: + target: sgm.modules.diffusionmodules.denoiser_weighting.EpsWeighting + scaling_config: + target: sgm.modules.diffusionmodules.denoiser_scaling.EpsScaling + discretization_config: + target: sgm.modules.diffusionmodules.discretizer.LegacyDDPMDiscretization + + network_config: + target: sgm.modules.diffusionmodules.openaimodel.UNetModel + params: + adm_in_channels: 2560 + num_classes: sequential + use_checkpoint: True + in_channels: 4 + out_channels: 4 + model_channels: 384 + attention_resolutions: [4, 2] + num_res_blocks: 2 + channel_mult: [1, 2, 4, 4] + num_head_channels: 64 + use_spatial_transformer: True + use_linear_in_transformer: True + transformer_depth: 4 + context_dim: [1280, 1280, 1280, 1280] # 1280 + spatial_transformer_attn_type: softmax-xformers + legacy: False + + conditioner_config: + target: sgm.modules.GeneralConditioner + params: + emb_models: + # crossattn and vector cond + - is_trainable: False + input_key: txt + target: sgm.modules.encoders.modules.FrozenOpenCLIPEmbedder2 + params: + arch: ViT-bigG-14 + version: laion2b_s39b_b160k + legacy: False + freeze: True + layer: penultimate + always_return_pooled: True + # vector cond + - is_trainable: False + input_key: original_size_as_tuple + target: sgm.modules.encoders.modules.ConcatTimestepEmbedderND + params: + outdim: 256 # multiplied by two + # vector cond + - is_trainable: False + input_key: crop_coords_top_left + target: sgm.modules.encoders.modules.ConcatTimestepEmbedderND + params: + outdim: 256 # multiplied by two + # vector cond + - is_trainable: False + input_key: aesthetic_score + target: sgm.modules.encoders.modules.ConcatTimestepEmbedderND + params: + outdim: 256 # multiplied by one + + first_stage_config: + target: sgm.models.autoencoder.AutoencoderKLInferenceWrapper + params: + embed_dim: 4 + monitor: val/rec_loss + ddconfig: + attn_type: vanilla-xformers + double_z: true + z_channels: 4 + resolution: 256 + in_channels: 3 + out_ch: 3 + ch: 128 + ch_mult: [1, 2, 4, 4] + num_res_blocks: 2 + attn_resolutions: [] + dropout: 0.0 + lossconfig: + target: torch.nn.Identity diff --git a/installer.py b/installer.py index 60ba1dfb2..01283d817 100644 --- a/installer.py +++ b/installer.py @@ -430,8 +430,14 @@ def install_packages(): install('pi-heif', 'pi_heif', ignore=True) tensorflow_package = os.environ.get('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0') install(tensorflow_package, 'tensorflow', ignore=True) - # bitsandbytes_package = os.environ.get('BITSANDBYTES_PACKAGE', 'bitsandbytes==0.39.1') - # install(bitsandbytes_package, 'bitsandbytes', ignore=True) + bitsandbytes_package = os.environ.get('BITSANDBYTES_PACKAGE', None) + if bitsandbytes_package is not None: + install(bitsandbytes_package, 'bitsandbytes', ignore=True) + else: + bitsandbytes_package = pkg_resources.working_set.by_key.get('bitsandbytes', None) + if bitsandbytes_package is not None: + log.warning(f'Not used, uninstalling: {bitsandbytes_package}') + pip('uninstall bitsandbytes --yes --quiet', ignore=True, quiet=True) if args.profile: print_profile(pr, 'Packages') diff --git a/launch.py b/launch.py index a645faf9a..31a6d533c 100644 --- a/launch.py +++ b/launch.py @@ -169,6 +169,7 @@ if __name__ == "__main__": if installer.check_timestamp(): installer.log.info('No changes detected: Quick launch active') installer.install_requirements() + installer.install_packages() installer.check_extensions() else: installer.install_requirements() diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index bf40fa9d0..51e295e02 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -1,14 +1,16 @@ import inspect +import typing import torch import modules.devices as devices import modules.shared as shared import modules.sd_samplers as sd_samplers import modules.sd_models as sd_models +import modules.sd_vae as sd_vae import modules.images as images from modules.lora_diffusers import lora_state, unload_diffusers_lora from modules.processing import StableDiffusionProcessing import modules.prompt_parser_diffusers as prompt_parser_diffusers -import typing + try: import diffusers @@ -16,16 +18,6 @@ except Exception as ex: shared.log.error(f'Failed to import diffusers: {ex}') -def encode_prompt(encoder, prompt): - cfg = encoder.config - # TODO implement similar hijack for diffusers text encoder but following diffusers pipeline.encode_prompt concepts - # from modules import sd_hijack_clip - # model.text_encoder = sd_hijack_clip.FrozenCLIPEmbedderWithCustomWords(model.text_encoder, None) - shared.log.debug(f'Diffuser encoder: {encoder.__class__.__name__} dict={getattr(cfg, "vocab_size", None)} layers={getattr(cfg, "num_hidden_layers", None)} tokens={getattr(cfg, "max_position_embeddings", None)}') - embeds = prompt - return embeds - - def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_prompts): results = [] @@ -36,7 +28,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro def vae_decode(latents, model, output_type='np'): if hasattr(model, 'vae') and torch.is_tensor(latents): - shared.log.debug(f'Diffusers VAE decode: name={model.vae.config.get("_name_or_path", "default")} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)}') + shared.log.debug(f'Diffusers VAE decode: name={sd_vae.loaded_vae_file} 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('Diffusers: Moving UNet to CPU') unet_device = model.unet.device @@ -113,6 +105,14 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro clean['prompt'] = len(clean['prompt']) if 'negative_prompt' in clean: clean['negative_prompt'] = len(clean['negative_prompt']) + if 'prompt_embeds' in clean: + clean['prompt_embeds'] = clean['prompt_embeds'].shape + if 'pooled_prompt_embeds' in clean: + clean['pooled_prompt_embeds'] = clean['pooled_prompt_embeds'].shape + if 'negative_prompt_embeds' in clean: + clean['negative_prompt_embeds'] = clean['negative_prompt_embeds'].shape + if 'negative_pooled_prompt_embeds' in clean: + clean['negative_pooled_prompt_embeds'] = clean['negative_pooled_prompt_embeds'].shape clean['generator'] = generator_device shared.log.debug(f'Diffuser pipeline: {pipeline.__class__.__name__} task={sd_models.get_diffusers_task(model)} set={clean}') return args @@ -138,10 +138,6 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro p.ops.append('inpaint') task_specific_kwargs = {"image": p.init_images, "mask_image": p.mask, "strength": p.denoising_strength, "height": p.height, "width": p.width} - # TODO diffusers use transformers for prompt parsing - # from modules.prompt_parser import parse_prompt_attention - # parsed_prompt = [parse_prompt_attention(prompt) for prompt in prompts] - if shared.state.interrupted or shared.state.skipped: unload_diffusers_lora() return results diff --git a/modules/sd_models.py b/modules/sd_models.py index f5c35b95f..07226acaa 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -533,6 +533,62 @@ def change_backend(): refresh_vae_list() +def detect_pipeline(f: str, op: str = 'model'): + guess = shared.opts.diffusers_pipeline + if guess == 'Autodetect': + try: + size = round(os.path.getsize(f) / 1024 / 1024 / 1024, 2) + if size < 1: + shared.log.warning(f'Model size smaller than expected: {f} size={size} GB') + elif size < 5: + guess = 'Stable Diffusion' + elif size < 6: + if op == 'model': + shared.log.warning(f'Model detected as SD-XL refiner model, but attempting to load a base model: {f} size={size} GB') + else: + guess = 'Stable Diffusion XL' + elif size < 7: + if op == 'refiner': + shared.log.warning(f'Model size matches SD-XL base model, but attempting to load a refiner model: {f} size={size} GB') + else: + guess = 'Stable Diffusion XL' + else: + shared.log.error(f'Diffusers autodetect failed, set diffuser pipeline manually: {f}') + return None, None + shared.log.debug(f'Diffusers autodetect {op}: {f} pipeline={guess} size={size} GB') + except Exception as e: + shared.log.error(f'Error detecting diffusers pipeline: model={f} {e}') + return None, None + if guess == shared.pipelines[1]: + pipeline = diffusers.StableDiffusionPipeline + elif guess == shared.pipelines[2]: + pipeline = diffusers.StableDiffusionXLPipeline + elif guess == shared.pipelines[3]: + pipeline = diffusers.KandinskyPipeline + elif guess == shared.pipelines[4]: + pipeline = diffusers.KandinskyV22Pipeline + elif guess == shared.pipelines[5]: + pipeline = diffusers.IFPipeline + elif guess == shared.pipelines[6]: + pipeline = diffusers.ShapEPipeline + elif guess == shared.pipelines[7]: + pipeline = diffusers.StableDiffusionImg2ImgPipeline + elif guess == shared.pipelines[8]: + pipeline = diffusers.StableDiffusionXLImg2ImgPipeline + elif guess == shared.pipelines[9]: + pipeline = diffusers.KandinskyImg2ImgPipeline + elif guess == shared.pipelines[10]: + pipeline = diffusers.KandinskyV22Img2ImgPipeline + elif guess == shared.pipelines[11]: + pipeline = diffusers.IFImg2ImgPipeline + elif guess == shared.pipelines[12]: + pipeline = diffusers.ShapEImg2ImgPipeline + else: + shared.log.error(f'Diffusers unknown pipeline: {guess}') + pipeline = None + return pipeline, guess + + def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=None, op='model'): # pylint: disable=unused-argument import torch # pylint: disable=reimported,redefined-outer-name if timer is None: @@ -593,9 +649,10 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No devices.set_cuda_params() vae = None + sd_vae.loaded_vae_file = None if op == 'model' or op == 'refiner': vae_file, vae_source = sd_vae.resolve_vae(checkpoint_info.filename) - vae = sd_vae.load_vae_diffusers(None, vae_file, vae_source) + vae = sd_vae.load_vae_diffusers(checkpoint_info.path, vae_file, vae_source) if vae is not None: diffusers_load_config["vae"] = vae @@ -609,35 +666,9 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No else: diffusers_load_config["local_files_only "] = True diffusers_load_config["extract_ema"] = shared.opts.diffusers_extract_ema - try: - if shared.opts.diffusers_pipeline == shared.pipelines[0]: - pipeline = diffusers.StableDiffusionPipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[1]: - pipeline = diffusers.StableDiffusionXLPipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[2]: - pipeline = diffusers.KandinskyPipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[3]: - pipeline = diffusers.KandinskyV22Pipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[4]: - pipeline = diffusers.IFPipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[5]: - pipeline = diffusers.ShapEPipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[6]: - pipeline = diffusers.StableDiffusionImg2ImgPipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[7]: - pipeline = diffusers.StableDiffusionXLImg2ImgPipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[8]: - pipeline = diffusers.KandinskyImg2ImgPipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[9]: - pipeline = diffusers.KandinskyV22Img2ImgPipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[10]: - pipeline = diffusers.IFImg2ImgPipeline - elif shared.opts.diffusers_pipeline == shared.pipelines[11]: - pipeline = diffusers.ShapEImg2ImgPipeline - else: - shared.log.error(f'Diffusers {op} unknown pipeline: {shared.opts.diffusers_pipeline}') - except Exception as e: - shared.log.error(f'Diffusers {op} failed initializing pipeline: {shared.opts.diffusers_pipeline} {e}') + pipeline, _model_type = detect_pipeline(checkpoint_info.path, op) + if pipeline is None: + shared.log.error(f'Diffusers {op} pipeline not initialized: {shared.opts.diffusers_pipeline}') return try: if hasattr(pipeline, 'from_single_file'): @@ -697,6 +728,8 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No else: sd_model.disable_attention_slicing() if hasattr(sd_model, "vae"): + if vae is not None: + sd_model.vae = vae if shared.opts.diffusers_vae_upcast != 'default': if shared.opts.diffusers_vae_upcast == 'true': sd_model.vae.config["force_upcast"] = True @@ -704,7 +737,7 @@ 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 - shared.log.debug(f'Diffusers {op} VAE: name={sd_model.vae.config.get("_name_or_path", "default")} upcast={sd_model.vae.config.get("force_upcast", None)}') + shared.log.debug(f'Diffusers {op} VAE: name={sd_vae.loaded_vae_file} upcast={sd_model.vae.config.get("force_upcast", None)}') if shared.opts.cross_attention_optimization == "xFormers" and hasattr(sd_model, 'enable_xformers_memory_efficient_attention'): sd_model.enable_xformers_memory_efficient_attention() if shared.opts.opt_channelslast: diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 1edd39961..317906001 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -3,7 +3,7 @@ import collections import glob from copy import deepcopy import torch -from modules import shared, paths, devices, script_callbacks, sd_models +from modules import shared, paths, paths_internal, devices, script_callbacks, sd_models vae_ignore_keys = {"model_ema.decay", "model_ema.num_updates"} @@ -169,7 +169,7 @@ def load_vae(model, vae_file=None, vae_source="from unknown source"): loaded_vae_file = vae_file -def load_vae_diffusers(_model, vae_file=None, vae_source="from unknown source"): +def load_vae_diffusers(model_file, vae_file=None, vae_source="from unknown source"): if vae_file is None: return None if not os.path.exists(vae_file): @@ -196,14 +196,14 @@ def load_vae_diffusers(_model, vae_file=None, vae_source="from unknown source"): try: import diffusers if os.path.isfile(vae_file): - if shared.opts.diffusers_pipeline == "Stable Diffusion XL": - # load_config passed to from_single_file doesn't apply - # from_single_file by default downloads VAE1.5 config - shared.log.warning("Using SDXL VAE loaded from singular file will result in low contrast images.") - vae = diffusers.AutoencoderKL.from_single_file(vae_file) + _pipeline, model_type = sd_models.detect_pipeline(model_file, 'vae') + diffusers_load_config = { "config_file": paths_internal.sd_default_config if model_type != 'Stable Diffusion XL' else os.path.join(paths_internal.sd_configs_path, 'sd_xl_base.yaml')} + vae = diffusers.AutoencoderKL.from_single_file(vae_file, **diffusers_load_config) vae = vae.to(devices.dtype_vae) else: vae = diffusers.AutoencoderKL.from_pretrained(vae_file, **diffusers_load_config) + global loaded_vae_file # pylint: disable=global-statement + loaded_vae_file = os.path.basename(vae_file) # shared.log.debug(f'Diffusers VAE config: {vae.config}') return vae except Exception as e: diff --git a/modules/shared.py b/modules/shared.py index bd5444336..25993ea99 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -41,6 +41,7 @@ loaded_hypernetworks = [] gradio_theme = gr.themes.Base() settings_components = None pipelines = [ + 'Autodetect', 'Stable Diffusion', 'Stable Diffusion XL', 'Kandinsky V1', 'Kandinsky V2', 'DeepFloyd IF', 'Shap-E', 'Stable Diffusion Img2Img', 'Stable Diffusion XL Img2Img', 'Kandinsky V1 Img2Img', 'Kandinsky V2 Img2Img', 'DeepFloyd IF Img2Img', 'Shap-E Img2Img' ] From 6760fd152558edfaf26a1cd1319d9760bf6bc691 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 7 Aug 2023 21:37:58 +0300 Subject: [PATCH 28/72] Add TAESD VAE option for base image outputs --- modules/processing_diffusers.py | 14 +++++++++++++- modules/shared.py | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 51e295e02..05b92aeef 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -6,6 +6,7 @@ import modules.shared as shared import modules.sd_samplers as sd_samplers import modules.sd_models as sd_models import modules.sd_vae as sd_vae +import modules.taesd.sd_vae_taesd as sd_vae_taesd import modules.images as images from modules.lora_diffusers import lora_state, unload_diffusers_lora from modules.processing import StableDiffusionProcessing @@ -43,6 +44,14 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro else: return latents + def taesd_vae_decode(latents, model, output_type='np'): + shared.log.debug('Diffusers VAE decode: name=TAESD') + decoded = torch.zeros((len(latents), 3, p.height, p.width), 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 + images = model.image_processor.postprocess(decoded, output_type=output_type) + return images + def set_pipeline_args(model, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str] =None, negative_prompt_2: typing.Optional[str] = None, is_refiner: bool = False, **kwargs): args = {} @@ -168,7 +177,10 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro return results if shared.sd_refiner is None or not p.enable_hr: - output.images = vae_decode(output.images, shared.sd_model) + if shared.opts.diffusers_taesd_vae_output: + output.images = taesd_vae_decode(output.images, shared.sd_model) + else: + output.images = vae_decode(output.images, shared.sd_model) if lora_state['active']: unload_diffusers_lora() diff --git a/modules/shared.py b/modules/shared.py index 25993ea99..ee601e0df 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -396,6 +396,7 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { options_templates.update(options_section(('diffusers', "Diffusers Settings"), { "diffusers_allow_safetensors": OptionInfo(True, 'Diffusers allow loading from safetensors files'), + "diffusers_taesd_vae_output": OptionInfo(False, 'Use TAESD VAE for quick image outputs'), "diffusers_pipeline": OptionInfo(pipelines[0], 'Diffusers pipeline', gr.Dropdown, lambda: {"choices": pipelines}), "diffusers_move_base": OptionInfo(False, "Move base model to CPU when using refiner"), "diffusers_move_refiner": OptionInfo(True, "Move refiner model to CPU when not in use"), From 196fa29ee0c133c2a0ff789ab341b00d01e548c7 Mon Sep 17 00:00:00 2001 From: Aptronymist <108482020+Aptronymist@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:43:38 -0400 Subject: [PATCH 29/72] Update ui_extra_networks.py Added fix to thumbnail images that exceed 70kb to address slowdowns and wasted memory. --- modules/ui_extra_networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 591e64028..f5c8f2a93 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -140,7 +140,7 @@ class ExtraNetworksPage: continue try: img = Image.open(f) - if img.width > 1024 or img.height > 1024: + if img.width > 1024 or img.height > 1024 or os.path.getsize(f) > 70000: img = img.convert('RGB') img.thumbnail((512, 512), Image.HAMMING) img.save(fn) From a5f95f4b38921350d5bb9e5c0f697df7c34d7353 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 9 Aug 2023 08:01:35 +0000 Subject: [PATCH 30/72] civitai model search and download --- CHANGELOG.md | 5 +- extensions-builtin/sd-webui-agent-scheduler | 2 +- extensions-builtin/sd-webui-controlnet | 2 +- modules/modelloader.py | 37 +++++- modules/ui_models.py | 134 +++++++++++++++++++- webui.py | 2 + wiki | 2 +- 7 files changed, 177 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eee36183..057bd5764 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-08-07 +## Update for 2023-08-09 - diffusers: - **pipeline autodetect** @@ -18,8 +18,11 @@ - **sd-xl vae** from safetensors now applies correct config result is that 3rd party vaes can be used without washed out colors - general: + - new **civitai model search and download** + native support for civitai, integrated into models -> civitai - updated requirements this time its a bigger change so upgrade may take longer to install new requirements + - improved **extra networks** performance with large number of networks ## Update for 2023-08-05 diff --git a/extensions-builtin/sd-webui-agent-scheduler b/extensions-builtin/sd-webui-agent-scheduler index ea470d752..280f08872 160000 --- a/extensions-builtin/sd-webui-agent-scheduler +++ b/extensions-builtin/sd-webui-agent-scheduler @@ -1 +1 @@ -Subproject commit ea470d75242ef7ae6fed6019b6ba227a4926b671 +Subproject commit 280f08872a79afb63477c4456eef532c9d5d1067 diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index af34f5144..4fa919043 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit af34f514499933d5e7e1641a6b13d56411e45e76 +Subproject commit 4fa9190436e29dd7d88701b18a27330ef7743343 diff --git a/modules/modelloader.py b/modules/modelloader.py index cf49a5bc3..f41c430fc 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -3,7 +3,6 @@ import shutil import importlib from typing import Dict from urllib.parse import urlparse - from modules import shared from modules.upscaler import Upscaler, UpscalerLanczos, UpscalerNearest, UpscalerNone from modules.paths import script_path, models_path @@ -11,6 +10,42 @@ from modules.paths import script_path, models_path diffuser_repos = [] +def download_civit_model(model_url: str, model_name: str, model_path: str, preview): + model_file = os.path.join(shared.opts.ckpt_dir, model_path, model_name) + res = f'CivitAI download: name={model_name} url={model_url} path={model_path}' + if os.path.isfile(model_file): + res += ' already exists' + shared.log.warning(res) + return res + import requests + import rich.progress as p + + req = requests.get(model_url, stream=True, timeout=30) + total_size = int(req.headers.get('content-length', 0)) + block_size = 16384 # 16KB blocks + written = 0 + 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: + 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 preview is not None: + preview_file = os.path.splitext(model_file)[0] + '.jpg' + preview.save(preview_file) + res += f' preview={preview_file}' + except Exception as e: + shared.log.error(f'CivitAI download error: name={model_name} url={model_url} path={model_path} {e}') + if total_size == written: + shared.log.info(f'{res} size={total_size}') + else: + shared.log.error(f'{res} size={total_size} written={written}') + return res + + 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 diff --git a/modules/ui_models.py b/modules/ui_models.py index 54b24bfea..a40c144bd 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -17,6 +17,7 @@ def create_ui(): with gr.Column(elem_id='models_output_container', scale=1): # models_output = gr.Text(elem_id="models_output", value="", show_label=False) gr.HTML(elem_id="models_progress", value="") + models_image = gr.Image(elem_id="models_image", show_label=False, interactive=False, type='pil') models_outcome = gr.HTML(elem_id="models_error", value="") with gr.Column(elem_id='models_input_container', scale=3): @@ -238,5 +239,134 @@ def create_ui(): hf_results.select(fn=hf_select, inputs=[hf_results], outputs=[hf_selected]) hf_download_model_btn.click(fn=hf_download_model, inputs=[hf_selected, hf_token, hf_variant, hf_revision, hf_mirror], outputs=[models_outcome]) - # with gr.Tab(label="CivitAI"): - # pass + with gr.Tab(label="CivitAI"): + data = [] + + def civit_search(name, tag, model_type): + import requests + headers = { 'Content-type': 'application/json' } + url = 'https://civitai.com/api/v1/models?limit=25&types=Checkpoint&Sort=Newest' + if name is not None and len(name) > 0: + url += f'&query={name}' + 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}') + if r.status_code != 200: + return [], [], [] + body = r.json() + nonlocal data + data = body.get('items', []) + data1 = [] + for model in data: + found = 0 + for variant in model['modelVersions']: + if model_type == 'SD 1.5': + if 'SD 1.' in variant['baseModel']: + found += 1 + if model_type == 'SD XL': + if 'SDXL' in variant['baseModel']: + found += 1 + else: + if 'SD 1.' not in variant['baseModel'] and 'SDXL' not in variant['baseModel']: + found += 1 + if found > 0: + data1.append([ + model['id'], + model['name'], + ', '.join(model['tags']), + model['stats']['downloadCount'], + model['stats']['rating'] + ]) + return data1, [], [] + + def civit_select1(evt: gr.SelectData, in_data): + model_id = in_data[evt.index[0]][0] + data2 = [] + preview_img = None + for model in data: + if model['id'] == model_id: + for d in model['modelVersions']: + if d.get('images') is not None and len(d['images']) > 0 and len(d['images'][0]['url']) > 0: + preview_img = d['images'][0]['url'] + data2.append([ + d['id'], + d['modelId'], + d['name'], + d['baseModel'], + d['createdAt'], + ]) + 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): + variant_id = in_data[evt.index[0]][0] + model_id = in_data[evt.index[0]][1] + data3 = [] + for model in data: + if model['id'] == model_id: + for variant in model['modelVersions']: + if variant['id'] == variant_id: + for f in variant['files']: + data3.append([ + f['name'], + round(f['sizeKB']), + json.dumps(f['metadata']), + f['downloadUrl'], + ]) + log.debug(f'CivitAI select: model={in_data[evt.index[0]]} files={len(data3)}') + return data3 + + def civit_select3(evt: gr.SelectData, in_data): + log.debug(f'CivitAI select: variant={in_data[evt.index[0]]}') + return in_data[evt.index[0]][3], in_data[evt.index[0]][0], gr.update(interactive=True) + + def civit_download_model(model_url: str, model_name: str, model_path: str, image_url: str): + if model_url is None or len(model_url) == 0: + return 'No model selected' + try: + from modules.modelloader import download_civit_model + res = download_civit_model(model_url, model_name, model_path, image_url) + except Exception as e: + res = f"CivitAI model downloaded error: model={model_url} {e}" + log.error(res) + return res + from modules.sd_models import list_models # pylint: disable=W0621 + list_models() + return res + + with gr.Row(): + with gr.Column(scale=1): + civit_model_type = gr.Dropdown(label='Model type', choices=['SD 1.5', 'SD XL', 'Other'], value='SD 1.5') + with gr.Column(scale=15): + with gr.Row(): + civit_search_text = gr.Textbox('', label = 'Seach models', placeholder='keyword') + civit_search_tag = gr.Textbox('', label = '', placeholder='tags') + civit_search_btn = ToolButton(value="🔍", label="Search", interactive=False) + with gr.Row(): + civit_download_model_btn = gr.Button(value="Download model", variant='primary') + with gr.Row(): + civit_name = gr.Textbox('', label = 'Model name', placeholder='select model from search results', visible=True) + civit_selected = gr.Textbox('', label = 'Model URL', placeholder='select model from search results', visible=True) + civit_path = gr.Textbox('', label = 'Download path', placeholder='optional subfolder path where to save model', visible=True) + with gr.Row(): + with gr.Column(): + civit_headers2 = ['ID', 'ModelID', 'Name', 'Base', 'Created', 'Preview'] + civit_types2 = ['number', 'number', 'str', 'str', 'date', 'str'] + civit_results2 = gr.DataFrame([], label = 'Model versions', show_label = True, interactive = False, wrap = True, overflow_row_behaviour = 'paginate', max_rows = 10, headers = civit_headers2, datatype = civit_types2, type='array') + with gr.Column(): + civit_headers3 = ['Name', 'Size', 'Metadata', 'URL'] + civit_types3 = ['str', 'number', 'str', 'str'] + civit_results3 = gr.DataFrame([], label = 'Model variants', show_label = True, interactive = False, wrap = True, overflow_row_behaviour = 'paginate', max_rows = 10, headers = civit_headers3, datatype = civit_types3, type='array') + with gr.Row(): + civit_headers1 = ['ID', 'Name', 'Tags', 'Downloads', 'Rating'] + civit_types1 = ['number', 'str', 'str', 'number', 'number'] + civit_results1 = gr.DataFrame([], 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') + + 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]) + civit_search_btn.click(fn=civit_search, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_results1, civit_results2, civit_results3]) + civit_results1.select(fn=civit_select1, inputs=[civit_results1], outputs=[civit_results2, models_image]) + 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, models_image], outputs=[models_outcome]) diff --git a/webui.py b/webui.py index 7119f1238..d4feb3001 100644 --- a/webui.py +++ b/webui.py @@ -9,6 +9,7 @@ import logging import warnings import importlib from threading import Thread +import urllib3 from modules import timer, errors, paths # pylint: disable=unused-import startup_timer = timer.Timer() @@ -20,6 +21,7 @@ try: import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import except Exception: pass +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) import torchvision # pylint: disable=W0611,C0411 import pytorch_lightning # pytorch_lightning should be imported after torch, but it re-enables warnings on import so import once to disable them # pylint: disable=W0611,C0411 if ".dev" in torch.__version__ or "+git" in torch.__version__: diff --git a/wiki b/wiki index d7cd33058..2e5c2a156 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit d7cd33058c4b7e09460cc58ee62850bf7705bef5 +Subproject commit 2e5c2a156e1868a7b93329fa1b43e302e9dcfa0b From a77fa4c12e5ac3cef657930004de75b218d5cc5f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 9 Aug 2023 08:42:26 +0000 Subject: [PATCH 31/72] add job info --- modules/modelloader.py | 6 ++++++ pyproject.toml | 1 + 2 files changed, 7 insertions(+) diff --git a/modules/modelloader.py b/modules/modelloader.py index f41c430fc..56a264937 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -24,6 +24,8 @@ def download_civit_model(model_url: str, model_name: str, model_path: str, previ total_size = int(req.headers.get('content-length', 0)) block_size = 16384 # 16KB blocks written = 0 + shared.state.begin() + shared.state.job = 'downloload 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: @@ -43,6 +45,7 @@ def download_civit_model(model_url: str, model_name: str, model_path: str, previ shared.log.info(f'{res} size={total_size}') else: shared.log.error(f'{res} size={total_size} written={written}') + shared.state.end() return res @@ -50,6 +53,8 @@ def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config from diffusers import DiffusionPipeline import huggingface_hub as hf + shared.state.begin() + shared.state.job = 'downloload model' if download_config is None: download_config = { "force_download": False, @@ -82,6 +87,7 @@ def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config with open(os.path.join(download_dir, "hidden"), "w", encoding="utf-8") as f: f.write("True") shared.writefile(model_info_dict, os.path.join(pipeline_dir, "model_info.json")) + shared.state.end() return pipeline_dir diff --git a/pyproject.toml b/pyproject.toml index 34a413446..bf3637f33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ ignore = [ "B905", # Without explicit scrict "C408", # Rewrite as a literal "E402", # Module level import not at top of file + "E721", # Do not compare types, use `isinstance()` "F401", # Imported but unused "EXE001", # Shebang present "ISC003", # Implicit string concatenation From e2b0d981ac249dcfd7dfed674d2e9898bdaecad5 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 9 Aug 2023 09:34:59 +0000 Subject: [PATCH 32/72] quick taesd vae decode --- CHANGELOG.md | 3 +++ modules/img2img.py | 5 +++-- modules/processing.py | 9 +++++++-- modules/processing_diffusers.py | 9 +++------ modules/shared.py | 1 - modules/txt2img.py | 5 +++-- modules/ui.py | 7 +++++-- 7 files changed, 24 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 057bd5764..0782c4660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ - **pipeline autodetect** if pipeline is set to autodetect (default for new installs), app will try to autodetect pipeline based on selected model this should reduce user errors such as loading sd-xl model when sd pipeline is selected + - **quick vae decode** as alternative to full vae decode which is very resource intensive + quick decode is based on `taesd` and produces lower quality, but its great for tests or grids as it runs much faster and uses far less vram + disabled by default, selectable in *txt2img/img2img -> advanced -> full quality* - **prompt attention** for sd and sd-xl native `compel` implementation and standrd -> compel translation thanks @ai-casanova diff --git a/modules/img2img.py b/modules/img2img.py index 18a7aba85..0c7288009 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -74,13 +74,13 @@ def process_batch(p, input_files, input_dir, output_dir, inpaint_mask_dir, args) shared.log.debug(f'Processed: {len(image_files)} Memory: {memory_stats()} batch') -def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, latent_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, diffusers_guidance_rescale: float, refiner_start: float, clip_skip: int, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_files: list, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, *args): # pylint: disable=unused-argument +def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_index: int, latent_index: int, mask_blur: int, mask_alpha: float, inpainting_fill: 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, refiner_start: float, clip_skip: int, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_files: list, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, *args): # pylint: disable=unused-argument if shared.sd_model is None: 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}|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}|args={args}') if init_img is None: shared.log.debug('Init image not set') @@ -158,6 +158,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s clip_skip=clip_skip, width=width, height=height, + full_quality=full_quality, restore_faces=restore_faces, tiling=tiling, init_images=[image], diff --git a/modules/processing.py b/modules/processing.py index a313c89d7..7bb6ec4a3 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -86,7 +86,7 @@ class StableDiffusionProcessing: """ The first set of paramaters: sd_models -> do_not_reload_embeddings represent the minimum required to create a StableDiffusionProcessing """ - def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, latent_sampler: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, image_cfg_scale: float = None, clip_skip: int = 1, width: int = 512, height: int = 512, restore_faces: bool = False, tiling: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, diffusers_guidance_rescale: float = 0.7, ddim_discretize: str = None, s_min_uncond: float = 0.0, s_churn: float = 0.0, s_tmax: float = None, s_tmin: float = 0.0, s_noise: float = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None): # pylint: disable=unused-argument + def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, latent_sampler: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, image_cfg_scale: float = None, clip_skip: int = 1, width: int = 512, height: int = 512, full_quality: bool = True, restore_faces: bool = False, tiling: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, diffusers_guidance_rescale: float = 0.7, ddim_discretize: str = None, s_min_uncond: float = 0.0, s_churn: float = 0.0, s_tmax: float = None, s_tmin: float = 0.0, s_noise: float = 1.0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None): # pylint: disable=unused-argument self.outpath_samples: str = outpath_samples self.outpath_grids: str = outpath_grids @@ -110,6 +110,7 @@ class StableDiffusionProcessing: self.diffusers_guidance_rescale = diffusers_guidance_rescale self.width: int = width self.height: int = height + self.full_quality: bool = full_quality self.restore_faces: bool = restore_faces self.tiling: bool = tiling self.do_not_save_samples: bool = do_not_save_samples @@ -450,6 +451,10 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su if all_negative_prompts is None: all_negative_prompts = p.all_negative_prompts + if p.full_quality: + vae = None if not shared.opts.add_model_name_to_info or sd_vae.loaded_vae_file is None else os.path.splitext(os.path.basename(sd_vae.loaded_vae_file))[0] + else: + vae = 'TAESD' generation_params = { "Steps": p.steps, @@ -461,7 +466,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su "Model": None if not shared.opts.add_model_name_to_info or not shared.sd_model.sd_checkpoint_info.model_name else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', ''), "Model hash": getattr(p, 'sd_model_hash', None if not shared.opts.add_model_hash_to_info or not shared.sd_model.sd_model_hash else shared.sd_model.sd_model_hash), "Refiner": None if not shared.opts.add_model_name_to_info or not shared.sd_refiner or not shared.sd_refiner.sd_checkpoint_info.model_name else shared.sd_refiner.sd_checkpoint_info.model_name.replace(',', '').replace(':', ''), - "VAE": None if not shared.opts.add_model_name_to_info or sd_vae.loaded_vae_file is None else os.path.splitext(os.path.basename(sd_vae.loaded_vae_file))[0], + "VAE": vae, # subseed "Variation seed": None if p.subseed_strength == 0 else all_subseeds[index], "Variation seed strength": None if p.subseed_strength == 0 else p.subseed_strength, diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 05b92aeef..aa27932e0 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -49,8 +49,8 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro decoded = torch.zeros((len(latents), 3, p.height, p.width), 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 - images = model.image_processor.postprocess(decoded, output_type=output_type) - return images + imgs = model.image_processor.postprocess(decoded, output_type=output_type) + return imgs def set_pipeline_args(model, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str] =None, negative_prompt_2: typing.Optional[str] = None, is_refiner: bool = False, **kwargs): @@ -177,10 +177,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro return results if shared.sd_refiner is None or not p.enable_hr: - if shared.opts.diffusers_taesd_vae_output: - output.images = taesd_vae_decode(output.images, shared.sd_model) - else: - output.images = vae_decode(output.images, shared.sd_model) + output.images = vae_decode(output.images, shared.sd_model) if p.full_quality else taesd_vae_decode(output.images, shared.sd_model) if lora_state['active']: unload_diffusers_lora() diff --git a/modules/shared.py b/modules/shared.py index ee601e0df..25993ea99 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -396,7 +396,6 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { options_templates.update(options_section(('diffusers', "Diffusers Settings"), { "diffusers_allow_safetensors": OptionInfo(True, 'Diffusers allow loading from safetensors files'), - "diffusers_taesd_vae_output": OptionInfo(False, 'Use TAESD VAE for quick image outputs'), "diffusers_pipeline": OptionInfo(pipelines[0], 'Diffusers pipeline', gr.Dropdown, lambda: {"choices": pipelines}), "diffusers_move_base": OptionInfo(False, "Move base model to CPU when using refiner"), "diffusers_move_refiner": OptionInfo(True, "Move refiner model to CPU when not in use"), diff --git a/modules/txt2img.py b/modules/txt2img.py index c4e01df9d..d45f5760e 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -5,9 +5,9 @@ from modules.ui import plaintext_to_html from modules.memstats import memory_stats -def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_index: int, latent_index: int, 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_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_second_pass_steps: int, hr_resize_x: int, hr_resize_y: 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}|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_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_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_start={refiner_start}||refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings_texts={override_settings_texts}|args={args}') if shared.sd_model is None: shared.log.warning('Model not loaded') @@ -43,6 +43,7 @@ def txt2img(id_task: str, prompt: str, negative_prompt: str, prompt_styles, step clip_skip=clip_skip, width=width, height=height, + full_quality=full_quality, restore_faces=restore_faces, tiling=tiling, enable_hr=enable_hr, diff --git a/modules/ui.py b/modules/ui.py index 7ca1261ca..5e87b070b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -379,6 +379,7 @@ def create_ui(startup_timer = None): cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.1, label='CFG Scale', value=6.0, elem_id="txt2img_cfg_scale") clip_skip = gr.Slider(label='CLIP skip', value=1, minimum=1, maximum=14, step=1, elem_id='txt2img_clip_skip', interactive=True) with FormRow(elem_classes="checkboxes-row", variant="compact"): + full_quality = gr.Checkbox(label='Full quality', value=True, elem_id="txt2img_full_quality") restore_faces = gr.Checkbox(label='Face restore', value=False, visible=len(modules.shared.face_restorers) > 1, elem_id="txt2img_restore_faces") tiling = gr.Checkbox(label='Tiling', value=False, elem_id="txt2img_tiling") @@ -435,7 +436,7 @@ def create_ui(startup_timer = None): txt2img_prompt_styles, steps, sampler_index, latent_index, - restore_faces, tiling, + full_quality, restore_faces, tiling, batch_count, batch_size, cfg_scale, image_cfg_scale, diffusers_guidance_rescale, @@ -484,6 +485,7 @@ def create_ui(startup_timer = None): (latent_index, "Latent sampler"), (denoising_strength, "Denoising strength"), (refiner_start, "Refiner start"), + (full_quality, "Full quality"), (restore_faces, "Face restoration"), (batch_size, "Batch size"), (batch_count, "Batch count"), @@ -669,6 +671,7 @@ def create_ui(startup_timer = None): clip_skip = gr.Slider(label='CLIP skip', value=1, minimum=1, maximum=4, step=1, elem_id='img2img_clip_skip', interactive=True) diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Guidance Rescale', value=0.7, elem_id="txt2img_image_cfg_rescale") with FormRow(elem_classes="img2img_checkboxes_row", variant="compact"): + full_quality = gr.Checkbox(label='Full quality', value=True, elem_id="img2img_full_quality") restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(modules.shared.face_restorers) > 1, elem_id="img2img_restore_faces") tiling = gr.Checkbox(label='Tiling', value=False, elem_id="img2img_tiling") @@ -733,7 +736,7 @@ def create_ui(startup_timer = None): sampler_index, latent_index, mask_blur, mask_alpha, inpainting_fill, - restore_faces, tiling, + full_quality, restore_faces, tiling, batch_count, batch_size, cfg_scale, image_cfg_scale, diffusers_guidance_rescale, From 16725ab38f86c46d50bec76e2c20e1387cd3a914 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 9 Aug 2023 10:28:44 +0000 Subject: [PATCH 33/72] fix compel to full and add batch sizes --- modules/processing_diffusers.py | 26 ++++++----- modules/prompt_parser_diffusers.py | 70 ++++++++++++++++++------------ 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index aa27932e0..cf84055d2 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -53,7 +53,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro return imgs - def set_pipeline_args(model, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str] =None, negative_prompt_2: typing.Optional[str] = None, is_refiner: bool = False, **kwargs): + def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: typing.Optional[list]=None, negative_prompts_2: typing.Optional[list]=None, is_refiner: bool=False, **kwargs): args = {} pipeline = model signature = inspect.signature(type(pipeline).__call__) @@ -65,7 +65,13 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro negative_embed = None negative_pooled = None if shared.opts.data['prompt_attention'] in {'Compel parser', 'Full parser'}: - prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompt(model, prompt, negative_prompt, prompt_2, negative_prompt_2, is_refiner, kwargs.pop("clip_skip", None)) + 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: args['prompt_embeds'] = prompt_embed @@ -73,7 +79,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro args['pooled_prompt_embeds'] = pooled args['prompt_2'] = None #Cannot pass prompts when passing embeds else: - args['prompt'] = prompt + args['prompt'] = prompts if 'negative_prompt' in possible: if hasattr(model, 'text_encoder') and 'negative_prompt_embeds' in possible and negative_embed is not None: args['negative_prompt_embeds'] = negative_embed @@ -81,7 +87,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro args['negative_pooled_prompt_embeds'] = negative_pooled args['negative_prompt_2'] = None else: - args['negative_prompt'] = negative_prompt + args['negative_prompt'] = negative_prompts if 'num_inference_steps' in possible: args['num_inference_steps'] = p.steps if 'guidance_scale' in possible: @@ -157,10 +163,10 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro refiner_enabled = shared.sd_refiner is not None and p.enable_hr pipe_args = set_pipeline_args( model=shared.sd_model, - prompt=prompts, - negative_prompt=negative_prompts, - prompt_2=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts, - negative_prompt_2=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts, + 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, eta=shared.opts.eta_ddim, guidance_rescale=p.diffusers_guidance_rescale, denoising_start=0 if refiner_enabled and p.refiner_start > 0 and p.refiner_start < 1 else None, @@ -211,8 +217,8 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro for i in range(len(output.images)): pipe_args = set_pipeline_args( model=shared.sd_refiner, - prompt=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts[i], - negative_prompt=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts[i], + prompts=[p.refiner_prompt] if len(p.refiner_prompt) > 0 else prompts[i], + negative_prompts=[p.refiner_negative] if len(p.refiner_negative) > 0 else negative_prompts[i], num_inference_steps=p.hr_second_pass_steps, eta=shared.opts.eta_ddim, strength=p.denoising_strength, diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 1f7a4ed42..591998355 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -1,3 +1,4 @@ +import os import typing import torch import diffusers @@ -5,13 +6,14 @@ from compel import Compel, ReturnedEmbeddingsType import modules.shared as shared import modules.prompt_parser as prompt_parser +debug_output = os.environ.get('SD_PROMPT_DEBUG', None) +debug = shared.log.info if debug_output is not None else lambda *args, **kwargs: None + def convert_to_compel(prompt: str): if prompt is None: return None - all_schedules = prompt_parser.get_learned_conditioning_prompt_schedules( - prompt, 100 - )[0] + all_schedules = prompt_parser.get_learned_conditioning_prompt_schedules([prompt], 100)[0] output_list = prompt_parser.parse_prompt_attention(all_schedules[0][1]) converted_prompt = [] for subprompt, weight in output_list: @@ -31,8 +33,35 @@ CLIP_SKIP_MAPPING = { } +def compel_encode_prompts( + pipeline: diffusers.StableDiffusionXLPipeline | diffusers.StableDiffusionPipeline, + prompts: list, + negative_prompts: list, + prompts_2: typing.Optional[list] = None, + negative_prompts_2: typing.Optional[list] = None, + is_refiner: bool = None, + clip_skip: typing.Optional[int] = None, +): + prompt_embeds = [] + positive_pooleds = [] + negative_embeds = [] + negative_pooleds = [] + for i in range(len(prompts)): + prompt_embed, positive_pooled, negative_embed, negative_pooled = compel_encode_prompt(pipeline, prompts[i], negative_prompts[i], prompts_2[i], negative_prompts_2[i], is_refiner, clip_skip) + prompt_embeds.append(prompt_embed) + positive_pooleds.append(positive_pooled) + negative_embeds.append(negative_embed) + negative_pooleds.append(negative_pooled) + + prompt_embeds = torch.cat(prompt_embeds, dim=0) + positive_pooleds = torch.cat(positive_pooleds, dim=0) + negative_embeds = torch.cat(negative_embeds, dim=0) + negative_pooleds = torch.cat(negative_pooleds, dim=0) + return prompt_embeds, positive_pooleds, negative_embeds, negative_pooleds + + def compel_encode_prompt( - pipeline: diffusers.StableDiffusionXLPipeline, + pipeline: diffusers.StableDiffusionXLPipeline | diffusers.StableDiffusionPipeline, prompt: str, negative_prompt: str, prompt_2: typing.Optional[str] = None, @@ -41,24 +70,17 @@ def compel_encode_prompt( clip_skip: typing.Optional[int] = None, ): if shared.sd_model_type not in {"sd", "sdxl"}: - shared.log.warning( - f"Compel encoding not yet supported for {type(pipeline).__name__}." - ) + shared.log.warning(f"Prompt parser: Compel not supported: {type(pipeline).__name__}") return (None, None, None, None) if shared.sd_model_type == "sdxl": embedding_type = ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED - if clip_skip is not None: - shared.log.debug("CLIP skip ignored as it is unsupported for SDXL") + if clip_skip is not None and clip_skip > 1: + shared.log.warning(f"Prompt parser SDXL unsupported: clip_skip={clip_skip}") else: - embedding_type = CLIP_SKIP_MAPPING.get( - clip_skip, ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NORMALIZED - ) + embedding_type = CLIP_SKIP_MAPPING.get(clip_skip, ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NORMALIZED) if clip_skip not in CLIP_SKIP_MAPPING: - shared.log.warning( - f"Recieved a CLIP skip of {clip_skip}, but only {set(CLIP_SKIP_MAPPING.keys())} is supported. " - "Falling back to 2." - ) + shared.log.warning(f"Prompt parser unsupported: clip_skip={clip_skip} expected={set(CLIP_SKIP_MAPPING.keys())}") if shared.opts.data["prompt_attention"] != "Compel parser": prompt = convert_to_compel(prompt) @@ -84,25 +106,17 @@ def compel_encode_prompt( positive_te1 = compel_te1(prompt) positive_te2, positive_pooled = compel_te2(prompt_2) positive = torch.cat((positive_te1, positive_te2), dim=-1) - negative_te1 = compel_te1(negative_prompt) negative_te2, negative_pooled = compel_te2(negative_prompt_2) negative = torch.cat((negative_te1, negative_te2), dim=-1) else: positive, positive_pooled = compel_te2(prompt) negative, negative_pooled = compel_te2(negative_prompt) - - shared.log.debug( - f"Parsed Compel string: {compel_te1.parse_prompt_string(prompt)}" - ) - [ - prompt_embed, - negative_embed, - ] = compel_te2.pad_conditioning_tensors_to_same_length([positive, negative]) + parsed = compel_te1.parse_prompt_string(prompt) + debug(f"Prompt parser Compel: {parsed}") + [prompt_embed, negative_embed] = compel_te2.pad_conditioning_tensors_to_same_length([positive, negative]) return prompt_embed, positive_pooled, negative_embed, negative_pooled positive, negative = compel_te1(prompt), compel_te1(negative_prompt) - [prompt_embed, negative_embed] = compel_te1.pad_conditioning_tensors_to_same_length( - [positive, negative] - ) + [prompt_embed, negative_embed] = compel_te1.pad_conditioning_tensors_to_same_length([positive, negative]) return prompt_embed, None, negative_embed, None From ea04f8feba48496ede5795d38865caff01e8d4a6 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 9 Aug 2023 10:32:24 +0000 Subject: [PATCH 34/72] update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0782c4660..88657cb60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ quick decode is based on `taesd` and produces lower quality, but its great for tests or grids as it runs much faster and uses far less vram disabled by default, selectable in *txt2img/img2img -> advanced -> full quality* - **prompt attention** for sd and sd-xl - native `compel` implementation and standrd -> compel translation + supports both `full parser` and native `compel` thanks @ai-casanova - advanced **lora load/apply** methods in addition to standard lora loading that was recently added to sd-xl using diffusers, now we have @@ -22,7 +22,7 @@ result is that 3rd party vaes can be used without washed out colors - general: - new **civitai model search and download** - native support for civitai, integrated into models -> civitai + native support for civitai, integrated into ui as *models -> civitai* - updated requirements this time its a bigger change so upgrade may take longer to install new requirements - improved **extra networks** performance with large number of networks From 627677ce613300b24e25c7e9ddb4f3d558e44103 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 9 Aug 2023 11:17:22 +0000 Subject: [PATCH 35/72] update cli --- cli/idle.py | 57 ++++++++++++++++++++++++++++++++++++++++++++ cli/image-palette.py | 0 2 files changed, 57 insertions(+) create mode 100755 cli/idle.py mode change 100755 => 100644 cli/image-palette.py diff --git a/cli/idle.py b/cli/idle.py new file mode 100755 index 000000000..d861a8c7a --- /dev/null +++ b/cli/idle.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python + +import os +import time +import datetime +import logging +import urllib3 +import requests + +class Dot(dict): # dot notation access to dictionary attributes + __getattr__ = dict.get + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ + +opts = Dot({ + "timeout": 3600, + "frequency": 3, + "action": "sudo shutdown now", + "url": "https://127.0.0.1:7860", + "user": "vlado", + "password": "qWe", +}) + +log_format = '%(asctime)s %(levelname)s: %(message)s' +logging.basicConfig(level = logging.INFO, format = log_format) +log = logging.getLogger("sd") +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +status = None + +def progress(): + auth = requests.auth.HTTPBasicAuth(opts.user, opts.password) if opts.user is not None and opts.password is not None else None + req = requests.get(f'{opts.url}/sdapi/v1/progress?skip_current_image=true', verify=False, auth=auth, timeout=60) + if req.status_code != 200: + log.error({ 'url': req.url, 'request': req.status_code, 'reason': req.reason }) + return status + else: + res = Dot(req.json()) + log.debug({ 'url': req.url, 'request': req.status_code, 'result': res }) + return res + +log.info(f'sdnext monitor started: {opts}') +while True: + try: + status = progress() + state = status.get('state', {}) + last_job = state.get('job_timestamp', '20000101000000') + last_job = datetime.datetime.strptime(last_job, "%Y%m%d%H%M%S") + elapsed = datetime.datetime.now() - last_job + timeout = round(opts.timeout - elapsed.total_seconds()) + log.info(f'sdnext: last_job={last_job} elapsed={elapsed} timeout={timeout}') + if timeout < 0: + log.warning(f'sdnext reached: timeout={opts.timeout} action={opts.action}') + os.system(opts.action) + except Exception as e: + log.error(f'sdnext monitor error: {e}') + finally: + time.sleep(opts.frequency) diff --git a/cli/image-palette.py b/cli/image-palette.py old mode 100755 new mode 100644 From 161bd6af421bb167c06ebf6c75078242b8f272ed Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 9 Aug 2023 11:29:51 +0000 Subject: [PATCH 36/72] update idle script --- cli/idle.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/cli/idle.py b/cli/idle.py index d861a8c7a..2c90fe95f 100755 --- a/cli/idle.py +++ b/cli/idle.py @@ -7,18 +7,18 @@ import logging import urllib3 import requests -class Dot(dict): # dot notation access to dictionary attributes +class Dot(dict): __getattr__ = dict.get __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__ opts = Dot({ "timeout": 3600, - "frequency": 3, + "frequency": 60, "action": "sudo shutdown now", "url": "https://127.0.0.1:7860", - "user": "vlado", - "password": "qWe", + "user": "", + "password": "", }) log_format = '%(asctime)s %(levelname)s: %(message)s' @@ -28,7 +28,7 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) status = None def progress(): - auth = requests.auth.HTTPBasicAuth(opts.user, opts.password) if opts.user is not None and opts.password is not None else None + auth = requests.auth.HTTPBasicAuth(opts.user, opts.password) if opts.user is not None and len(opts.user) > 0 and opts.password is not None and len(opts.password) > 0 else None req = requests.get(f'{opts.url}/sdapi/v1/progress?skip_current_image=true', verify=False, auth=auth, timeout=60) if req.status_code != 200: log.error({ 'url': req.url, 'request': req.status_code, 'reason': req.reason }) @@ -43,14 +43,17 @@ while True: try: status = progress() state = status.get('state', {}) - last_job = state.get('job_timestamp', '20000101000000') - last_job = datetime.datetime.strptime(last_job, "%Y%m%d%H%M%S") - elapsed = datetime.datetime.now() - last_job - timeout = round(opts.timeout - elapsed.total_seconds()) - log.info(f'sdnext: last_job={last_job} elapsed={elapsed} timeout={timeout}') - if timeout < 0: - log.warning(f'sdnext reached: timeout={opts.timeout} action={opts.action}') - os.system(opts.action) + last_job = state.get('job_timestamp', None) + if last_job is None: + log.warning(f'sdnext montoring cannot get last job info: {status}') + else: + last_job = datetime.datetime.strptime(last_job, "%Y%m%d%H%M%S") + elapsed = datetime.datetime.now() - last_job + timeout = round(opts.timeout - elapsed.total_seconds()) + log.info(f'sdnext: last_job={last_job} elapsed={elapsed} timeout={timeout}') + if timeout < 0: + log.warning(f'sdnext reached: timeout={opts.timeout} action={opts.action}') + os.system(opts.action) except Exception as e: log.error(f'sdnext monitor error: {e}') finally: From 839bd2795699f8a2cafc6897dd3b45214c0a3c7b Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 9 Aug 2023 19:15:38 +0300 Subject: [PATCH 37/72] Pass device to Compel --- modules/prompt_parser_diffusers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 591998355..be9f17fff 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -3,6 +3,7 @@ import typing import torch import diffusers from compel import Compel, ReturnedEmbeddingsType +import modules.devices as devices import modules.shared as shared import modules.prompt_parser as prompt_parser @@ -93,6 +94,7 @@ def compel_encode_prompt( text_encoder=pipeline.text_encoder, returned_embeddings_type=embedding_type, requires_pooled=False, + device=devices.device ) if shared.sd_model_type == "sdxl": @@ -101,6 +103,7 @@ def compel_encode_prompt( text_encoder=pipeline.text_encoder_2, returned_embeddings_type=embedding_type, requires_pooled=True, + device=devices.device ) if not is_refiner: positive_te1 = compel_te1(prompt) From b4ea0fa0fa0611907852a6769cf46f2431d72b8d Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 9 Aug 2023 21:08:00 +0200 Subject: [PATCH 38/72] PIP_EXTRA_ARGS --- installer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/installer.py b/installer.py index 67390b382..6437c1e0f 100644 --- a/installer.py +++ b/installer.py @@ -166,8 +166,9 @@ def pip(arg: str, ignore: bool = False, quiet: bool = False): arg = arg.replace('>=', '==') if not quiet: log.info(f'Installing package: {arg.replace("install", "").replace("--upgrade", "").replace("--no-deps", "").replace("--force", "").replace(" ", " ").strip()}') - log.debug(f"Running pip: {arg}") - result = subprocess.run(f'"{sys.executable}" -m pip {arg}', shell=True, check=False, env=os.environ, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + env_args = os.environ.get("PIP_EXTRA_ARGS", "") + log.debug(f"Running pip: {arg} {env_args}") + result = subprocess.run(f'"{sys.executable}" -m pip {arg} {env_args}', shell=True, check=False, env=os.environ, stdout=subprocess.PIPE, stderr=subprocess.PIPE) txt = result.stdout.decode(encoding="utf8", errors="ignore") if len(result.stderr) > 0: txt += ('\n' if len(txt) > 0 else '') + result.stderr.decode(encoding="utf8", errors="ignore") From a9726b33198874519b96a452729530b681474211 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 10 Aug 2023 01:23:39 +0300 Subject: [PATCH 39/72] Send to meta when unloading --- modules/sd_models.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/sd_models.py b/modules/sd_models.py index 07226acaa..af5b4335f 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -1034,16 +1034,14 @@ def unload_model_weights(op='model'): from modules import sd_hijack if op == 'model' or op == 'dict': if model_data.sd_model: - if not model_data.sd_model.has_accelerate: - model_data.sd_model.to(devices.cpu) + model_data.sd_model.to('meta') if shared.backend == shared.Backend.ORIGINAL: sd_hijack.model_hijack.undo_hijack(model_data.sd_model) model_data.sd_model = None shared.log.debug(f'Weights unloaded {op}: {memory_stats()}') else: if model_data.sd_refiner: - if not model_data.sd_refiner.has_accelerate: - model_data.sd_refiner.to(devices.cpu) + model_data.sd_refiner.to('meta') if shared.backend == shared.Backend.ORIGINAL: sd_hijack.model_hijack.undo_hijack(model_data.sd_refiner) model_data.sd_refiner = None From b94af77d29c5a4107eae6b54159587ddfec428d7 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 10 Aug 2023 08:22:39 +0200 Subject: [PATCH 40/72] update --- extensions-builtin/sd-dynamic-thresholding | 2 +- extensions-builtin/sd-webui-agent-scheduler | 2 +- extensions-builtin/sd-webui-controlnet | 2 +- html/logo-margins.png | Bin 0 -> 17223 bytes 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 html/logo-margins.png diff --git a/extensions-builtin/sd-dynamic-thresholding b/extensions-builtin/sd-dynamic-thresholding index c60fe071e..c02d806ca 160000 --- a/extensions-builtin/sd-dynamic-thresholding +++ b/extensions-builtin/sd-dynamic-thresholding @@ -1 +1 @@ -Subproject commit c60fe071e5938974a52611f87ca2fd1878aa6d15 +Subproject commit c02d806cac2a280bbcc90b586fc37bd560cd3274 diff --git a/extensions-builtin/sd-webui-agent-scheduler b/extensions-builtin/sd-webui-agent-scheduler index 280f08872..e270493ab 160000 --- a/extensions-builtin/sd-webui-agent-scheduler +++ b/extensions-builtin/sd-webui-agent-scheduler @@ -1 +1 @@ -Subproject commit 280f08872a79afb63477c4456eef532c9d5d1067 +Subproject commit e270493ab7c4b5cfb5eb39f474c34e51ebba8221 diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 4fa919043..2daccd995 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 4fa9190436e29dd7d88701b18a27330ef7743343 +Subproject commit 2daccd995d8c7e22e6af93bf7c433b2074097aff diff --git a/html/logo-margins.png b/html/logo-margins.png new file mode 100644 index 0000000000000000000000000000000000000000..5bbc3f2528970625da970b3fe3726b6101ec57c5 GIT binary patch literal 17223 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 KZj~$BM*bg^-vBEB literal 0 HcmV?d00001 From 0a7105d52436e75ce5e85651005137916a780e9a Mon Sep 17 00:00:00 2001 From: Disty0 Date: Thu, 10 Aug 2023 16:02:45 +0300 Subject: [PATCH 41/72] Fix SDXL LoRa offloading and SD 1.5 parsing --- installer.py | 2 ++ modules/lora_diffusers.py | 2 +- modules/prompt_parser_diffusers.py | 10 +++++----- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/installer.py b/installer.py index b4bfb7444..1376a5d33 100644 --- a/installer.py +++ b/installer.py @@ -186,6 +186,8 @@ def install(package, friendly: str = None, ignore: bool = False): if args.reinstall or args.upgrade: global quick_allowed # pylint: disable=global-statement quick_allowed = False + if args.use_ipex and "accelerate==" in package: + package = "accelerate==0.20.3" if args.reinstall or not installed(package, friendly): pip(f"install --upgrade {package}", ignore=ignore) diff --git a/modules/lora_diffusers.py b/modules/lora_diffusers.py index 816ff788b..f8ab54dde 100644 --- a/modules/lora_diffusers.py +++ b/modules/lora_diffusers.py @@ -58,7 +58,7 @@ def load_diffusers_lora(name, lora, strength = 1.0): if shared.opts.diffusers_lora_loader == "merge and apply": lora_network.merge_to(multiplier=strength) if shared.opts.diffusers_lora_loader == "sequential apply": - lora_network.to(pipe.device, dtype=pipe.unet.dtype) + lora_network.to(shared.device, dtype=pipe.unet.dtype) lora_network.apply_to(multiplier=strength) lora_state['all_loras'].append(lora_network) shared.log.info(f"Diffusers LoRA loaded: {name} {strength}") diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index be9f17fff..559845059 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -3,7 +3,6 @@ import typing import torch import diffusers from compel import Compel, ReturnedEmbeddingsType -import modules.devices as devices import modules.shared as shared import modules.prompt_parser as prompt_parser @@ -55,9 +54,10 @@ def compel_encode_prompts( negative_pooleds.append(negative_pooled) prompt_embeds = torch.cat(prompt_embeds, dim=0) - positive_pooleds = torch.cat(positive_pooleds, dim=0) negative_embeds = torch.cat(negative_embeds, dim=0) - negative_pooleds = torch.cat(negative_pooleds, dim=0) + if shared.sd_model_type == "sdxl": + positive_pooleds = torch.cat(positive_pooleds, dim=0) + negative_pooleds = torch.cat(negative_pooleds, dim=0) return prompt_embeds, positive_pooleds, negative_embeds, negative_pooleds @@ -94,7 +94,7 @@ def compel_encode_prompt( text_encoder=pipeline.text_encoder, returned_embeddings_type=embedding_type, requires_pooled=False, - device=devices.device + device=shared.device ) if shared.sd_model_type == "sdxl": @@ -103,7 +103,7 @@ def compel_encode_prompt( text_encoder=pipeline.text_encoder_2, returned_embeddings_type=embedding_type, requires_pooled=True, - device=devices.device + device=shared.device ) if not is_refiner: positive_te1 = compel_te1(prompt) From 5bcd65d4c2e8741b8addbdc2202afd107ac00ca0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 10 Aug 2023 19:00:02 +0000 Subject: [PATCH 42/72] revert meta --- CHANGELOG.md | 2 +- modules/sd_models.py | 14 +++++++------- modules/ui_extensions.py | 4 ++++ wiki | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88657cb60..e4a0f3eeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-08-09 +## Update for 2023-08-10 - diffusers: - **pipeline autodetect** diff --git a/modules/sd_models.py b/modules/sd_models.py index af5b4335f..02a4383d6 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -522,6 +522,7 @@ class ModelData: model_data = ModelData() + def change_backend(): shared.log.info(f'Pipeline changed: {shared.backend}') unload_model_weights() @@ -626,7 +627,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No sd_model = None try: - if shared.cmd_opts.ckpt is not None and model_data.initial: # initial load\ + if shared.cmd_opts.ckpt is not None and model_data.initial: # initial load ckpt_basename = os.path.basename(shared.cmd_opts.ckpt) model_name = modelloader.find_diffuser(ckpt_basename) if model_name is not None: @@ -800,8 +801,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No if op == 'refiner' and shared.opts.diffusers_move_refiner and not sd_model.has_accelerate: shared.log.debug('Moving refiner model to CPU') sd_model.to(devices.cpu) - elif not sd_model.has_accelerate: - # In offload modes, accelerate will move models around. + elif not sd_model.has_accelerate: # In offload modes, accelerate will move models around sd_model.to(devices.device) if op == 'refiner' and base_sent_to_cpu: shared.log.debug('Moving base model back to GPU') @@ -1034,18 +1034,18 @@ def unload_model_weights(op='model'): from modules import sd_hijack if op == 'model' or op == 'dict': if model_data.sd_model: - model_data.sd_model.to('meta') + model_data.sd_model.to(devices.cpu) if shared.backend == shared.Backend.ORIGINAL: sd_hijack.model_hijack.undo_hijack(model_data.sd_model) model_data.sd_model = None - shared.log.debug(f'Weights unloaded {op}: {memory_stats()}') + shared.log.debug(f'Unload weights {op}: {memory_stats()}') else: if model_data.sd_refiner: - model_data.sd_refiner.to('meta') + model_data.sd_refiner.to(devices.cpu) if shared.backend == shared.Backend.ORIGINAL: sd_hijack.model_hijack.undo_hijack(model_data.sd_refiner) model_data.sd_refiner = None - shared.log.debug(f'Weights unloaded {op}: {memory_stats()}') + shared.log.debug(f'Unload weights {op}: {memory_stats()}') devices.torch_gc(force=True) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index 9526e730f..aa7c60ef5 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -151,6 +151,8 @@ def install_extension_from_url(dirname, url, branch_name, search_text, sort_colu normalized_url = normalize_git_url(url) assert len([x for x in extensions.extensions if normalize_git_url(x.remote) == normalized_url]) == 0, 'Extension with this URL is already installed' tmpdir = os.path.join(paths.data_path, "tmp", dirname) + if url.endswith('.git'): + url = url.replace('.git', '') try: shutil.rmtree(tmpdir, True) if not branch_name: @@ -175,6 +177,8 @@ def install_extension_from_url(dirname, url, branch_name, search_text, sort_colu run_extension_installer(target_dir) extensions.list_extensions() return [refresh_extensions_list_from_data(search_text, sort_column), html.escape(f"Extension installed: {target_dir} | Restart required")] + except Exception as e: + shared.log.error(f'Error installing extension: {url} {e}') finally: shutil.rmtree(tmpdir, True) diff --git a/wiki b/wiki index 2e5c2a156..581054504 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 2e5c2a156e1868a7b93329fa1b43e302e9dcfa0b +Subproject commit 581054504c3ad2cfa7f0e625147484836e8a84ba From f52249d5a87e0a86c2e13b27752584e4ec2aef2f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 10 Aug 2023 21:20:56 +0000 Subject: [PATCH 43/72] fix prompt parser for sdxl and enable offloading --- html/locale_en.json | 11 ++++----- html/locale_ko.json | 1 - installer.py | 2 -- modules/lora_diffusers.py | 38 ++++++++++++------------------ modules/processing_diffusers.py | 19 +++++++++++++++ modules/prompt_parser_diffusers.py | 7 +++++- modules/sd_models.py | 25 ++++++++++++++------ modules/shared.py | 3 +-- requirements.txt | 2 +- 9 files changed, 65 insertions(+), 43 deletions(-) diff --git a/html/locale_en.json b/html/locale_en.json index cbfd34342..c7b5c77f3 100644 --- a/html/locale_en.json +++ b/html/locale_en.json @@ -562,18 +562,17 @@ {"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":"Diffusers allow loading from safetensors files","localized":"","hint":"Allow loading of safetensors files as diffuser models"}, {"id":"","label":"Select diffuser pipeline when loading from safetensors","localized":"","hint":""}, {"id":"","label":"Move base model to CPU when using refiner","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%. Use with Enable Attention slicing for minimal memory consumption"}, - {"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. Use with Enable Attention slicing for additional 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. Use with Enable Attention slicing"}, - {"id":"","label":"Enable VAE tiling","localized":"","hint":"Divide large images into overlapping tiles with limited VRAM. Might result in a minor increase in processing time. Use with Enable Attention Slicing"}, - {"id":"","label":"Enable attention slicing","localized":"","hint":"Performs attention computation in steps instead of all at once. 10% slower inference times. Greatly reduces memory usage. Best used, period"}, + {"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 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"} diff --git a/html/locale_ko.json b/html/locale_ko.json index 1dc3600e2..f85119fd0 100644 --- a/html/locale_ko.json +++ b/html/locale_ko.json @@ -562,7 +562,6 @@ {"id":"","label":"Token merging ratio","localized":"토큰 병합 비율","hint":"속도와 메모리 절감을 위해 tomesd를 사용해 토큰 병합을 활성화한다. (0이면 비활성화)"}, {"id":"","label":"Token merging ratio for img2img","localized":"이미지➠이미지 토큰 병합 비율","hint":"속도와 메모리 절감을 위해 이미지➠이미지에서 tomesd를 사용해 토큰 병합을 활성화한다. (0이면 비활성화)"}, {"id":"","label":"Token merging ratio for hires pass","localized":"텍스트➠이미지 업스케일링(Hires fix) 토큰 병합 비율","hint":"속도와 메모리 절감을 위해 Hires fix에서 tomesd를 사용해 토큰 병합을 활성화한다. (0이면 비활성화)"}, - {"id":"","label":"Diffusers allow loading from safetensors files","localized":"safetensors 파일에서 로드 허용","hint":"safetensors 파일을 Diffusers 모델로 로드할 수 있게 한다."}, {"id":"","label":"Select diffuser pipeline when loading from safetensors","localized":"safetensors 파일에서 로드할 때 사용할 파이프라인 선택","hint":""}, {"id":"","label":"Move base model to CPU when using refiner","localized":"리파이너를 사용 중일 때 base 모델을 CPU로 이동","hint":""}, {"id":"","label":"Move refiner model to CPU when not in use","localized":"사용 중이지 않을 때 리파이너 모델을 CPU로 이동","hint":""}, diff --git a/installer.py b/installer.py index 1376a5d33..b4bfb7444 100644 --- a/installer.py +++ b/installer.py @@ -186,8 +186,6 @@ def install(package, friendly: str = None, ignore: bool = False): if args.reinstall or args.upgrade: global quick_allowed # pylint: disable=global-statement quick_allowed = False - if args.use_ipex and "accelerate==" in package: - package = "accelerate==0.20.3" if args.reinstall or not installed(package, friendly): pip(f"install --upgrade {package}", ignore=ignore) diff --git a/modules/lora_diffusers.py b/modules/lora_diffusers.py index f8ab54dde..b2fe8fc00 100644 --- a/modules/lora_diffusers.py +++ b/modules/lora_diffusers.py @@ -24,10 +24,10 @@ def unload_diffusers_lora(): lora_state['all_loras'].reverse() lora_state['multiplier'].reverse() for i, lora_network in enumerate(lora_state['all_loras']): - if shared.opts.diffusers_lora_loader == "merge and apply": - lora_network.restore_from(multiplier=lora_state['multiplier'][i]) - if shared.opts.diffusers_lora_loader == "sequential apply": - lora_network.unapply_to() + if shared.opts.diffusers_lora_loader == "merge and apply": + lora_network.restore_from(multiplier=lora_state['multiplier'][i]) + if shared.opts.diffusers_lora_loader == "sequential apply": + lora_network.unapply_to() lora_state['active'] = False lora_state['loaded'] = 0 lora_state['all_loras'] = [] @@ -45,7 +45,7 @@ def load_diffusers_lora(name, lora, strength = 1.0): lora_state['multiplier'].append(strength) if shared.opts.diffusers_lora_loader == "diffusers default": pipe.load_lora_weights(lora.filename, cache_dir=shared.opts.diffusers_dir, local_files_only=True, lora_scale=strength) - shared.log.info(f"Diffusers LoRA loaded: {name} {lora_state['multiplier']}") + shared.log.info(f"LoRA loaded: {name} {lora_state['multiplier']}") else: from safetensors.torch import load_file lora_sd = load_file(lora.filename) @@ -61,7 +61,7 @@ def load_diffusers_lora(name, lora, strength = 1.0): lora_network.to(shared.device, dtype=pipe.unet.dtype) lora_network.apply_to(multiplier=strength) lora_state['all_loras'].append(lora_network) - shared.log.info(f"Diffusers LoRA loaded: {name} {strength}") + shared.log.info(f"LoRA loaded: {name}:{strength} loader={shared.opts.diffusers_lora_loader}") except Exception as e: shared.log.error(f"Diffusers LoRA loading failed: {name} {e}") @@ -332,7 +332,7 @@ def merge_lora_weights(pipe, weights_sd: Dict, multiplier: float = 1.0): # block weightや学習に対応しない簡易版 / simple version without block weight and training -class LoRANetwork(torch.nn.Module): +class LoRANetwork(torch.nn.Module): # pylint: disable=abstract-method UNET_TARGET_REPLACE_MODULE = ["Transformer2DModel"] UNET_TARGET_REPLACE_MODULE_CONV2D_3X3 = ["ResnetBlock2D", "Downsample2D", "Upsample2D"] TEXT_ENCODER_TARGET_REPLACE_MODULE = ["CLIPAttention", "CLIPMLP"] @@ -350,17 +350,17 @@ class LoRANetwork(torch.nn.Module): multiplier: float = 1.0, modules_dim: Optional[Dict[str, int]] = None, modules_alpha: Optional[Dict[str, int]] = None, - varbose: Optional[bool] = False, + varbose: Optional[bool] = False, # pylint: disable=unused-argument ) -> None: super().__init__() self.multiplier = multiplier - shared.log.debug("create LoRA network from weights") + # shared.log.debug("create LoRA network from weights") # convert SDXL Stability AI's U-Net modules to Diffusers converted = self.convert_unet_modules(modules_dim, modules_alpha) if converted: - shared.log.debug(f"converted {converted} Stability AI's U-Net LoRA modules to Diffusers (SDXL)") + shared.log.debug(f"LoRA convert: modules={converted} SDXL SAI/SGM to Diffusers") # create module instances def create_modules( @@ -422,18 +422,13 @@ class LoRANetwork(torch.nn.Module): text_encoder_loras, skipped = create_modules(False, index, text_encoder, LoRANetwork.TEXT_ENCODER_TARGET_REPLACE_MODULE) self.text_encoder_loras.extend(text_encoder_loras) skipped_te += skipped - shared.log.debug(f"create LoRA for Text Encoder: {len(self.text_encoder_loras)} modules.") - if len(skipped_te) > 0: - shared.log.debug(f"skipped {len(skipped_te)} modules because of missing weight.") # extend U-Net target modules to include Conv2d 3x3 target_modules = LoRANetwork.UNET_TARGET_REPLACE_MODULE + LoRANetwork.UNET_TARGET_REPLACE_MODULE_CONV2D_3X3 self.unet_loras: List[LoRAModule] self.unet_loras, skipped_un = create_modules(True, None, unet, target_modules) - shared.log.debug(f"create LoRA for U-Net: {len(self.unet_loras)} modules.") - if len(skipped_un) > 0: - shared.log.debug(f"skipped {len(skipped_un)} modules because of missing weight.") + shared.log.debug(f"LoRA modules loaded/skipped: te={len(self.text_encoder_loras)}/{len(skipped_te)} unet={len(self.unet_loras)}/skip={len(skipped_un)}") # assertion names = set() @@ -480,11 +475,11 @@ class LoRANetwork(torch.nn.Module): def apply_to(self, multiplier=1.0, apply_text_encoder=True, apply_unet=True): if apply_text_encoder: - shared.log.debug("enable LoRA for text encoder") + # shared.log.debug("LoRA apply for text encoder") for lora in self.text_encoder_loras: lora.apply_to(multiplier) if apply_unet: - shared.log.debug("enable LoRA for U-Net") + # shared.log.debug("LoRA apply for U-Net") for lora in self.unet_loras: lora.apply_to(multiplier) @@ -493,16 +488,14 @@ class LoRANetwork(torch.nn.Module): lora.unapply_to() def merge_to(self, multiplier=1.0): - shared.log.debug("merge LoRA weights to original weights") + # shared.log.debug("LoRA merge weights for text encoder") for lora in tqdm(self.text_encoder_loras + self.unet_loras): lora.merge_to(multiplier) - shared.log.debug("weights are merged") def restore_from(self, multiplier=1.0): - shared.log.debug("restore LoRA weights from original weights") + # shared.log.debug("LoRA restore weights") for lora in tqdm(self.text_encoder_loras + self.unet_loras): lora.restore_from(multiplier) - shared.log.debug("weights are restored") def load_state_dict(self, state_dict: Mapping[str, Any], strict: bool = True): # convert SDXL Stability AI's state dict to Diffusers' based state dict @@ -527,4 +520,3 @@ class LoRANetwork(torch.nn.Module): state_dict[key] = state_dict[key].view(my_state_dict[key].size()) return super().load_state_dict(state_dict, strict) - diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index cf84055d2..66dfbf25f 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -52,6 +52,24 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro imgs = model.image_processor.postprocess(decoded, output_type=output_type) return imgs + def fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2): + if type(prompts) is str: + prompts = [prompts] + if type(negative_prompts) is str: + negative_prompts = [negative_prompts] + while len(negative_prompts) < len(prompts): + negative_prompts.append(negative_prompts[-1]) + if type(prompts_2) is str: + prompts_2 = [prompts_2] + if type(prompts_2) is list: + while len(prompts_2) < len(prompts): + prompts_2.append(prompts_2[-1]) + if type(negative_prompts_2) is str: + negative_prompts_2 = [negative_prompts_2] + if type(negative_prompts_2) is list: + while len(negative_prompts_2) < len(prompts_2): + negative_prompts_2.append(negative_prompts_2[-1]) + return prompts, negative_prompts, prompts_2, negative_prompts_2 def set_pipeline_args(model, prompts: list, negative_prompts: list, prompts_2: typing.Optional[list]=None, negative_prompts_2: typing.Optional[list]=None, is_refiner: bool=False, **kwargs): args = {} @@ -64,6 +82,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro pooled = None 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.data['prompt_attention'] in {'Compel parser', 'Full parser'}: prompt_embed, pooled, negative_embed, negative_pooled = prompt_parser_diffusers.compel_encode_prompts(model, prompts, diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 559845059..eaa542c35 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -47,7 +47,12 @@ def compel_encode_prompts( negative_embeds = [] negative_pooleds = [] for i in range(len(prompts)): - prompt_embed, positive_pooled, negative_embed, negative_pooled = compel_encode_prompt(pipeline, prompts[i], negative_prompts[i], prompts_2[i], negative_prompts_2[i], is_refiner, clip_skip) + prompt_embed, positive_pooled, negative_embed, negative_pooled = compel_encode_prompt(pipeline, + prompts[i], + negative_prompts[i], + prompts_2[i] if prompts_2 is not None else None, + negative_prompts_2[i] if negative_prompts_2 is not None else None, + is_refiner, clip_skip) prompt_embeds.append(prompt_embed) positive_pooleds.append(positive_pooled) negative_embeds.append(negative_embed) diff --git a/modules/sd_models.py b/modules/sd_models.py index 02a4383d6..48775d2e4 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -136,12 +136,9 @@ def list_models(): checkpoints_list.clear() checkpoint_aliases.clear() ext_filter=[".safetensors"] if shared.opts.sd_disable_ckpt else [".ckpt", ".safetensors"] - model_list = [] - if shared.backend == shared.Backend.ORIGINAL or shared.opts.diffusers_allow_safetensors: - model_list += modelloader.load_models(model_path=model_path, model_url=None, command_path=shared.opts.ckpt_dir, ext_filter=ext_filter, download_name=None, ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) + model_list = modelloader.load_models(model_path=model_path, model_url=None, command_path=shared.opts.ckpt_dir, ext_filter=ext_filter, download_name=None, ext_blacklist=[".vae.ckpt", ".vae.safetensors"]) if shared.backend == shared.Backend.DIFFUSERS: model_list += modelloader.load_diffusers_models(model_path=os.path.join(models_path, 'Diffusers'), command_path=shared.opts.diffusers_dir) - for filename in sorted(model_list, key=str.lower): checkpoint_info = CheckpointInfo(filename) if checkpoint_info.name is not None: @@ -844,7 +841,6 @@ def set_diffuser_pipe(pipe, new_pipe_type): new_pipe = diffusers.AutoPipelineForImage2Image.from_pipe(pipe) elif new_pipe_type == DiffusersTaskType.INPAINTING: new_pipe = diffusers.AutoPipelineForInpainting.from_pipe(pipe) - if pipe.__class__ == new_pipe.__class__: return @@ -1030,20 +1026,35 @@ def reload_model_weights(sd_model=None, info=None, reuse_dict=False, op='model') shared.log.info(f"Weights loaded in {timer.summary()}") +def disable_offload(sd_model): + from accelerate.hooks import remove_hook_from_module + if not sd_model.has_accelerate: + return + for _name, model in sd_model.components.items(): + if not isinstance(model, torch.nn.Module): + continue + remove_hook_from_module(model, recurse=True) + + def unload_model_weights(op='model'): from modules import sd_hijack if op == 'model' or op == 'dict': if model_data.sd_model: - model_data.sd_model.to(devices.cpu) if shared.backend == shared.Backend.ORIGINAL: + model_data.sd_model.to(devices.cpu) sd_hijack.model_hijack.undo_hijack(model_data.sd_model) + else: + disable_offload(model_data.sd_model) + model_data.sd_model.to('meta') model_data.sd_model = None shared.log.debug(f'Unload weights {op}: {memory_stats()}') else: if model_data.sd_refiner: - model_data.sd_refiner.to(devices.cpu) + model_data.sd_refiner.to('meta') if shared.backend == shared.Backend.ORIGINAL: sd_hijack.model_hijack.undo_hijack(model_data.sd_refiner) + else: + disable_offload(model_data.sd_model) model_data.sd_refiner = None shared.log.debug(f'Unload weights {op}: {memory_stats()}') devices.torch_gc(force=True) diff --git a/modules/shared.py b/modules/shared.py index b156a4cb5..945f2a27f 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -395,11 +395,10 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { })) options_templates.update(options_section(('diffusers', "Diffusers Settings"), { - "diffusers_allow_safetensors": OptionInfo(True, 'Diffusers allow loading from safetensors files'), "diffusers_pipeline": OptionInfo(pipelines[0], 'Diffusers pipeline', gr.Dropdown, lambda: {"choices": pipelines}), "diffusers_move_base": OptionInfo(False, "Move base model to CPU when using refiner"), + "diffusers_move_unet": OptionInfo(False, "Move base model to CPU when using VAE"), "diffusers_move_refiner": OptionInfo(True, "Move refiner model to CPU when not in use"), - "diffusers_move_unet": OptionInfo(False, "Move UNet to CPU while VAE decoding"), "diffusers_extract_ema": OptionInfo(True, "Use model EMA weights when possible"), "diffusers_generator_device": OptionInfo("default", "Generator device", gr.Radio, lambda: {"choices": ["default", "cpu"]}), "diffusers_seq_cpu_offload": OptionInfo(False, "Enable sequential CPU offload"), diff --git a/requirements.txt b/requirements.txt index 7dac5b439..55da323bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,7 +46,7 @@ typing-extensions==4.7.1 antlr4-python3-runtime==4.9.3 requests==2.31.0 tqdm==4.65.0 -accelerate==0.21.0 +accelerate==0.20.3 opencv-python-headless==4.7.0.72 diffusers==0.19.3 einops==0.4.1 From a156751857ab5270c4a626fb67065e5046307ef2 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 10 Aug 2023 21:56:26 +0000 Subject: [PATCH 44/72] fix pipeline autodetect --- CHANGELOG.md | 4 +++- installer.py | 1 + modules/sd_models.py | 9 ++++++--- requirements.txt | 1 - 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4a0f3eeb..e2e7d4b03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log for SD.Next -## Update for 2023-08-10 +## Update for 2023-08-11 - diffusers: - **pipeline autodetect** @@ -20,6 +20,8 @@ thanks @hameerabbasi and @ai-casanova - **sd-xl vae** from safetensors now applies correct config result is that 3rd party vaes can be used without washed out colors + - options for optimized memory handling for lower memory usage + see *settings -> diffusers* - general: - new **civitai model search and download** native support for civitai, integrated into ui as *models -> civitai* diff --git a/installer.py b/installer.py index b4bfb7444..f81117a1a 100644 --- a/installer.py +++ b/installer.py @@ -429,6 +429,7 @@ def install_packages(): install(invisiblewatermark_package, 'invisible-watermark') install('onnxruntime==1.15.1', 'onnxruntime', ignore=True) install('pi-heif', 'pi_heif', ignore=True) + install('compel', 'git+https://github.com/damian0815/compel', ignore=True) tensorflow_package = os.environ.get('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0') install(tensorflow_package, 'tensorflow', ignore=True) bitsandbytes_package = os.environ.get('BITSANDBYTES_PACKAGE', None) diff --git a/modules/sd_models.py b/modules/sd_models.py index 48775d2e4..41b800f74 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -532,6 +532,8 @@ def change_backend(): def detect_pipeline(f: str, op: str = 'model'): + if not f.endswith('.safetensors'): + return None, None guess = shared.opts.diffusers_pipeline if guess == 'Autodetect': try: @@ -583,7 +585,7 @@ def detect_pipeline(f: str, op: str = 'model'): pipeline = diffusers.ShapEImg2ImgPipeline else: shared.log.error(f'Diffusers unknown pipeline: {guess}') - pipeline = None + pipeline = None, None return pipeline, guess @@ -657,7 +659,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No shared.log.info(f'Loading diffuser {op}: {checkpoint_info.filename}') if not os.path.isfile(checkpoint_info.path): try: - shared.log.debug(f'Diffusers load {op} config: {diffusers_load_config}') + # shared.log.debug(f'Diffusers load {op} config: {diffusers_load_config}') sd_model = diffusers.DiffusionPipeline.from_pretrained(checkpoint_info.path, **diffusers_load_config) except Exception as e: shared.log.error(f'Diffusers {op} failed loading model: {checkpoint_info.path} {e}') @@ -1050,11 +1052,12 @@ def unload_model_weights(op='model'): shared.log.debug(f'Unload weights {op}: {memory_stats()}') else: if model_data.sd_refiner: - model_data.sd_refiner.to('meta') if shared.backend == shared.Backend.ORIGINAL: + model_data.sd_model.to(devices.cpu) sd_hijack.model_hijack.undo_hijack(model_data.sd_refiner) else: disable_offload(model_data.sd_model) + model_data.sd_refiner.to('meta') model_data.sd_refiner = None shared.log.debug(f'Unload weights {op}: {memory_stats()}') devices.torch_gc(force=True) diff --git a/requirements.txt b/requirements.txt index 55da323bf..0f0644c95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,6 @@ voluptuous yapf scikit-image basicsr -compel fasteners typing-extensions==4.7.1 antlr4-python3-runtime==4.9.3 From 307e08653c2fbbc7aa298d0465fe206cfb4cea3d Mon Sep 17 00:00:00 2001 From: Disty0 Date: Fri, 11 Aug 2023 01:15:16 +0300 Subject: [PATCH 45/72] Fix compel install --- installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer.py b/installer.py index f81117a1a..d5cd9580d 100644 --- a/installer.py +++ b/installer.py @@ -429,7 +429,7 @@ def install_packages(): install(invisiblewatermark_package, 'invisible-watermark') install('onnxruntime==1.15.1', 'onnxruntime', ignore=True) install('pi-heif', 'pi_heif', ignore=True) - install('compel', 'git+https://github.com/damian0815/compel', ignore=True) + install('git+https://github.com/damian0815/compel', 'compel', ignore=True) tensorflow_package = os.environ.get('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0') install(tensorflow_package, 'tensorflow', ignore=True) bitsandbytes_package = os.environ.get('BITSANDBYTES_PACKAGE', None) From 7ffb66491ca86e71cce28ee7d1a5cf7f254f09b9 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Fri, 11 Aug 2023 08:10:57 +0200 Subject: [PATCH 46/72] update changelog --- CHANGELOG.md | 4 +++- extensions-builtin/sd-webui-controlnet | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2e7d4b03..fad7083c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,12 @@ ## Update for 2023-08-11 +This is a big one that's been cooking in `dev` for a while now, but finally ready for release... + - diffusers: - **pipeline autodetect** if pipeline is set to autodetect (default for new installs), app will try to autodetect pipeline based on selected model - this should reduce user errors such as loading sd-xl model when sd pipeline is selected + this should reduce user errors such as loading **sd-xl** model when **sd** pipeline is selected - **quick vae decode** as alternative to full vae decode which is very resource intensive quick decode is based on `taesd` and produces lower quality, but its great for tests or grids as it runs much faster and uses far less vram disabled by default, selectable in *txt2img/img2img -> advanced -> full quality* diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index 2daccd995..d67f31a3a 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit 2daccd995d8c7e22e6af93bf7c433b2074097aff +Subproject commit d67f31a3a7b4a70facbbec2feea3f02d5ddf2fab From 605988f520362886b965d65940a4e32a68f2f68c Mon Sep 17 00:00:00 2001 From: Aptronymist <108482020+Aptronymist@users.noreply.github.com> Date: Fri, 11 Aug 2023 19:51:42 -0400 Subject: [PATCH 47/72] Removed annoyance Took out the print statement 'no image data blocks found' as it happens frequently as embeddings rarely have image data. --- modules/textual_inversion/image_embedding.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index aca25bc9a..7c9fe881d 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -113,7 +113,6 @@ def extract_image_data_embed(image): outarr = crop_black(np.array(image.convert('RGB').getdata()).reshape(image.size[1], image.size[0], d).astype(np.uint8)) & 0x0F black_cols = np.where(np.sum(outarr, axis=(0, 2)) == 0) if black_cols[0].shape[0] < 2: - print('No Image data blocks found.') return None data_block_lower = outarr[:, :black_cols[0].min(), :].astype(np.uint8) From fc8eca1c34ac54d42299d40859e11a261f8ecb07 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 12 Aug 2023 07:18:19 +0000 Subject: [PATCH 48/72] deallocate images on batch --- modules/images.py | 1 + modules/postprocessing.py | 1 + webui.py | 1 + 3 files changed, 3 insertions(+) diff --git a/modules/images.py b/modules/images.py index fdcbac1de..92ceb4e46 100644 --- a/modules/images.py +++ b/modules/images.py @@ -477,6 +477,7 @@ def atomically_save_image(): image.save(fn, format=image_format, quality=shared.opts.jpeg_quality) except Exception as e: shared.log.warning(f'Image save failed: {fn} {e}') + image.close() # additional metadata saved in files if shared.opts.save_txt and len(exifinfo) > 0: try: diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 932b82289..4ff648461 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -76,6 +76,7 @@ def run_postprocessing(extras_mode, image, image_folder: List[tempfile.NamedTemp images.save_image(pp.image, path=outpath, basename=basename, seed=None, prompt=None, extension=ext or opts.samples_format, info=infotext, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info, forced_filename=None) if extras_mode != 2 or show_extras_results: outputs.append(pp.image) + image.close() devices.torch_gc() return outputs, infotext, params diff --git a/webui.py b/webui.py index d4feb3001..af2393d0a 100644 --- a/webui.py +++ b/webui.py @@ -17,6 +17,7 @@ local_url = None errors.log.debug('Loading Torch') import torch # pylint: disable=C0411 +errors.log.debug(f'Torch init: {torch.sum(torch.randn(2, 2)).to(0) > 0}') # fix silly pytorch_lightning issue try: import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import except Exception: From 83f38c6333fa742998cffcf87de07b9e08a3313f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 12 Aug 2023 07:36:48 +0000 Subject: [PATCH 49/72] update --- webui.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webui.py b/webui.py index af2393d0a..193b544c8 100644 --- a/webui.py +++ b/webui.py @@ -17,7 +17,10 @@ local_url = None errors.log.debug('Loading Torch') import torch # pylint: disable=C0411 -errors.log.debug(f'Torch init: {torch.sum(torch.randn(2, 2)).to(0) > 0}') # fix silly pytorch_lightning issue +try: + errors.log.debug(f'Torch init: {torch.sum(torch.randn(2, 2)).to(0) > 0}') # fix silly pytorch_lightning issue +except Exception: + pass try: import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import except Exception: From 5d5f22e6e1ba198c4b32c7d794c8ba6d77d39d23 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 12 Aug 2023 08:07:13 +0000 Subject: [PATCH 50/72] remove invalid downloaded files --- modules/modelloader.py | 3 +++ webui.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/modelloader.py b/modules/modelloader.py index 56a264937..6527e9efd 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -35,6 +35,9 @@ def download_civit_model(model_url: str, model_name: str, model_path: str, previ written = written + len(data) f.write(data) progress.update(task, advance=block_size, description="Downloading") + if written < 1024 * 1024 * 1024: # min threshold + os.remove(model_file) + raise ValueError(f'removed invalid download: bytes={written}') if preview is not None: preview_file = os.path.splitext(model_file)[0] + '.jpg' preview.save(preview_file) diff --git a/webui.py b/webui.py index 193b544c8..57a37c9a2 100644 --- a/webui.py +++ b/webui.py @@ -18,7 +18,8 @@ local_url = None errors.log.debug('Loading Torch') import torch # pylint: disable=C0411 try: - errors.log.debug(f'Torch init: {torch.sum(torch.randn(2, 2)).to(0) > 0}') # fix silly pytorch_lightning issue + rnd = torch.sum(torch.randn(2, 2)).to(0) + errors.log.debug(f'Torch init: {rnd / rnd == 1.0}') # fix silly pytorch_lightning issue except Exception: pass try: From 69eaf4c664ba0d5eff3da090cd7f6a9bdcf7d9b5 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 12 Aug 2023 08:32:19 +0000 Subject: [PATCH 51/72] fix batch --- installer.py | 2 +- modules/errors.py | 2 +- modules/images.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/installer.py b/installer.py index d5cd9580d..8ec648557 100644 --- a/installer.py +++ b/installer.py @@ -81,7 +81,7 @@ def setup_logging(): })) logging.basicConfig(level=logging.ERROR, format='%(asctime)s | %(name)s | %(levelname)s | %(module)s | %(message)s', handlers=[logging.NullHandler()]) # redirect default logger to null pretty_install(console=console) - traceback_install(console=console, extra_lines=1, width=console.width, word_wrap=False, indent_guides=False, suppress=[]) + traceback_install(console=console, extra_lines=1, max_frames=10, width=console.width, word_wrap=False, indent_guides=False, suppress=[]) while log.hasHandlers() and len(log.handlers) > 0: log.removeHandler(log.handlers[0]) diff --git a/modules/errors.py b/modules/errors.py index d0e867deb..ecbc30a36 100644 --- a/modules/errors.py +++ b/modules/errors.py @@ -36,7 +36,7 @@ def print_error_explanation(message): def display(e: Exception, task, suppress=[]): # noqa: B006 log.error(f"{task or 'error'}: {type(e).__name__}") - console.print_exception(show_locals=False, max_frames=5, extra_lines=1, suppress=suppress, theme="ansi_dark", word_wrap=False, width=min([console.width, 200])) + console.print_exception(show_locals=False, max_frames=10, extra_lines=1, suppress=suppress, theme="ansi_dark", word_wrap=False, width=min([console.width, 200])) def display_once(e: Exception, task): diff --git a/modules/images.py b/modules/images.py index 92ceb4e46..fdcbac1de 100644 --- a/modules/images.py +++ b/modules/images.py @@ -477,7 +477,6 @@ def atomically_save_image(): image.save(fn, format=image_format, quality=shared.opts.jpeg_quality) except Exception as e: shared.log.warning(f'Image save failed: {fn} {e}') - image.close() # additional metadata saved in files if shared.opts.save_txt and len(exifinfo) > 0: try: From 1cfebbb71724530ae316f5bbb87a9053b6cb9581 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 12 Aug 2023 22:16:37 +0200 Subject: [PATCH 52/72] skip uninstall on experimental --- extensions-builtin/stable-diffusion-webui-images-browser | 2 +- installer.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions-builtin/stable-diffusion-webui-images-browser b/extensions-builtin/stable-diffusion-webui-images-browser index a3aeb93fd..d31eb3c48 160000 --- a/extensions-builtin/stable-diffusion-webui-images-browser +++ b/extensions-builtin/stable-diffusion-webui-images-browser @@ -1 +1 @@ -Subproject commit a3aeb93fd7387cfe58aabf431b2dbbd1796bffed +Subproject commit d31eb3c482857055e45074efb879d37f6a21e2d5 diff --git a/installer.py b/installer.py index 8ec648557..d5b54d27a 100644 --- a/installer.py +++ b/installer.py @@ -384,7 +384,7 @@ def check_torch(): try: if 'xformers' in xformers_package: install(f'--no-deps {xformers_package}', ignore=True) - else: + elif not args.experimental: x = pkg_resources.working_set.by_key.get('xformers', None) if x is not None: log.warning(f'Not used, uninstalling: {x}') @@ -435,7 +435,7 @@ def install_packages(): bitsandbytes_package = os.environ.get('BITSANDBYTES_PACKAGE', None) if bitsandbytes_package is not None: install(bitsandbytes_package, 'bitsandbytes', ignore=True) - else: + elif not args.experimental: bitsandbytes_package = pkg_resources.working_set.by_key.get('bitsandbytes', None) if bitsandbytes_package is not None: log.warning(f'Not used, uninstalling: {bitsandbytes_package}') From 392f51cff3a926594132d8aa3c16f77417a1c467 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 12 Aug 2023 23:04:46 +0200 Subject: [PATCH 53/72] add long prompts --- TODO.md | 1 + cli/train.py | 4 ++-- modules/prompt_parser_diffusers.py | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index c178e4c20..6f24e1a1a 100644 --- a/TODO.md +++ b/TODO.md @@ -42,6 +42,7 @@ Stuff to be added, in no particular order... - Style editor (use json format instead of csv) - 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) - Image phash and hdash using `imagehash` - Model merge using `git-rebasin` - Enable refiner-style workflow for `ldm` backend diff --git a/cli/train.py b/cli/train.py index 58d97fa15..76de6e632 100755 --- a/cli/train.py +++ b/cli/train.py @@ -375,7 +375,7 @@ def check_versions(): log.info('checking accelerate') error = False import accelerate - if accelerate.__version__ != '0.19.0': + if accelerate.__version__ != '0.20.3': log.error(f'invalid accelerate version: accelerate=0.19.0 found={accelerate.__version__}') error = True log.info('checking diffusers') @@ -384,7 +384,7 @@ def check_versions(): log.error(f'invalid diffusers version: diffusers=0.10.2 found={diffusers.__version__}') error = True if error: - log.info('> pip install accelerate==0.19.0 diffusers==0.10.2') + log.info('> pip install accelerate==0.20.3 diffusers==0.10.2') exit(1) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index eaa542c35..8850fb67b 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -99,6 +99,7 @@ def compel_encode_prompt( text_encoder=pipeline.text_encoder, returned_embeddings_type=embedding_type, requires_pooled=False, + truncate_long_prompts=False, device=shared.device ) From 83d1ee77bd881ea3c4a6ffdd97e3765db73bf340 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 13 Aug 2023 00:32:03 +0200 Subject: [PATCH 54/72] revert long prompt --- modules/prompt_parser_diffusers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 8850fb67b..27dd423de 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -99,7 +99,7 @@ def compel_encode_prompt( text_encoder=pipeline.text_encoder, returned_embeddings_type=embedding_type, requires_pooled=False, - truncate_long_prompts=False, + # truncate_long_prompts=False, device=shared.device ) From 7e813557418a1d7dae754e09fd3a46df1374adc9 Mon Sep 17 00:00:00 2001 From: Seunghoon Lee Date: Sun, 13 Aug 2023 15:40:15 +0900 Subject: [PATCH 55/72] Fix pdh.dll not found. (WSL+DirectML) --- modules/dml/backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/dml/backend.py b/modules/dml/backend.py index 90b2d0528..1c7ce5f28 100644 --- a/modules/dml/backend.py +++ b/modules/dml/backend.py @@ -7,7 +7,6 @@ import modules.dml.amp as amp from .utils import rDevice, get_device from .device import device from .device_properties import DeviceProperties -from .memory import MemoryProvider def amd_mem_get_info(device: Optional[rDevice]=None) -> tuple[int, int]: from .memory_amd import AMDMemoryProvider @@ -29,7 +28,7 @@ class DirectML: is_autocast_enabled = False autocast_gpu_dtype = torch.float16 - memory_provider: Optional[MemoryProvider] = None + memory_provider = None def is_available() -> bool: return torch_directml.is_available() From 23efa8f0a58819caee9d72bbe8eb14895604fddd Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 13 Aug 2023 08:22:03 +0000 Subject: [PATCH 56/72] fix init_image --- modules/processing.py | 2 +- modules/prompt_parser_diffusers.py | 2 +- scripts/prompts_from_file.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index 7bb6ec4a3..6c0264ae5 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1077,7 +1077,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if crop_region is not None: image = image.crop(crop_region) image = images.resize_image(3, image, self.width, self.height) - self.init_images = image # assign early for diffusers + self.init_images = [image] # assign early for diffusers if image_mask is not None: if self.inpainting_fill != 1: image = masking.fill(image, latent_mask) diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 27dd423de..8850fb67b 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -99,7 +99,7 @@ def compel_encode_prompt( text_encoder=pipeline.text_encoder, returned_embeddings_type=embedding_type, requires_pooled=False, - # truncate_long_prompts=False, + truncate_long_prompts=False, device=shared.device ) diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 11cd8c2bc..6c2008043 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -2,7 +2,6 @@ import copy import random import shlex import gradio as gr -from PIL import Image import modules.scripts as scripts from modules import sd_samplers, errors from modules.processing import Processed, process_images From c5c817f48242251eb12171515b21a93c85c42d9a Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 13 Aug 2023 09:19:39 +0000 Subject: [PATCH 57/72] img2img batching --- cli/simple-img2img.py | 22 ++++++++++++++++++---- cli/simple-txt2img.py | 3 +++ modules/processing.py | 5 +++++ modules/processing_diffusers.py | 9 +++++++-- modules/sd_models.py | 1 - 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/cli/simple-img2img.py b/cli/simple-img2img.py index ceb89fd81..8590fc62a 100755 --- a/cli/simple-img2img.py +++ b/cli/simple-img2img.py @@ -1,19 +1,24 @@ #!/usr/bin/env python +import os import io import sys import base64 import logging import requests +import urllib3 from PIL import Image +sd_url = os.environ.get('SDAPI_URL', "http://127.0.0.1:7860") +sd_username = os.environ.get('SDAPI_USR', None) +sd_password = os.environ.get('SDAPI_PWD', None) + logging.basicConfig(level = logging.INFO, format = '%(asctime)s %(levelname)s: %(message)s') log = logging.getLogger(__name__) -sd_url = "http://127.0.0.1:7860" options = { "init_images": [], "prompt": "city at night", "negative_prompt": "foggy, blurry", - "steps": 1, + "steps": 20, "batch_size": 1, "n_iter": 1, "seed": -1, @@ -24,9 +29,17 @@ options = { "save_images": False, "send_images": True, } +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + +def auth(): + if sd_username is not None and sd_password is not None: + return requests.auth.HTTPBasicAuth(sd_username, sd_password) + return None + def post(endpoint: str, dct: dict = None): - req = requests.post(f'{sd_url}{endpoint}', json = dct, timeout=300) + req = requests.post(f'{sd_url}{endpoint}', json = dct, timeout=300, verify=False, auth=auth()) if req.status_code != 200: return { 'error': req.status_code, 'reason': req.reason, 'url': req.url } else: @@ -44,7 +57,8 @@ def encode(f): def generate(num: int = 0): log.info(f'sending generate request: {num+1} {options}') - options['init_images'] = [encode('../html/logo.png')] + options['init_images'] = [encode('html/logo-dark.png')] + options['batch_size'] = len(options['init_images']) data = post('/sdapi/v1/img2img', options) if 'images' in data: for i in range(len(data['images'])): diff --git a/cli/simple-txt2img.py b/cli/simple-txt2img.py index d07110b24..70e60a916 100755 --- a/cli/simple-txt2img.py +++ b/cli/simple-txt2img.py @@ -5,6 +5,7 @@ import sys import base64 import logging import requests +import urllib3 from PIL import Image sd_url = os.environ.get('SDAPI_URL', "http://127.0.0.1:7860") @@ -13,6 +14,7 @@ sd_password = os.environ.get('SDAPI_PWD', None) logging.basicConfig(level = logging.INFO, format = '%(asctime)s %(levelname)s: %(message)s') log = logging.getLogger(__name__) +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) filename='/tmp/simple-txt2img.jpg' model = None # desired model name, will be set if not none @@ -31,6 +33,7 @@ options = { "send_images": True, } + def auth(): if sd_username is not None and sd_password is not None: return requests.auth.HTTPBasicAuth(sd_username, sd_password) diff --git a/modules/processing.py b/modules/processing.py index 6c0264ae5..cb64d352e 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1058,6 +1058,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if add_color_corrections: self.color_corrections = [] imgs = [] + unprocessed = [] for img in self.init_images: # Save init image if shared.opts.save_init_img: @@ -1077,6 +1078,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): if crop_region is not None: image = image.crop(crop_region) image = images.resize_image(3, image, self.width, self.height) + if shared.backend == shared.Backend.DIFFUSERS: + unprocessed.append(image) self.init_images = [image] # assign early for diffusers if image_mask is not None: if self.inpainting_fill != 1: @@ -1086,6 +1089,8 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): image = np.array(image).astype(np.float32) / 255.0 image = np.moveaxis(image, 2, 0) imgs.append(image) + if shared.backend == shared.Backend.DIFFUSERS: + self.init_images = unprocessed # assign early for diffusers if len(imgs) == 1: batch_images = np.expand_dims(imgs[0], axis=0).repeat(self.batch_size, axis=0) if self.overlay_images is not None: diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 66dfbf25f..9686f9ae3 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -29,7 +29,10 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro def vae_decode(latents, model, output_type='np'): if hasattr(model, 'vae') and torch.is_tensor(latents): - shared.log.debug(f'Diffusers VAE decode: name={sd_vae.loaded_vae_file} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)}') + if latents.shape[0] == 0: + shared.log.error(f'VAE nothing to decode: {latents.shape}') + return [] + shared.log.debug(f'Diffusers VAE decode: name={sd_vae.loaded_vae_file} dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)} images={latents.shape[0]}') if shared.opts.diffusers_move_unet and not model.has_accelerate: shared.log.debug('Diffusers: Moving UNet to CPU') unet_device = model.unet.device @@ -159,6 +162,9 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro sd_samplers.create_sampler(sampler.name, shared.sd_model) # TODO(Patrick): For wrapped pipelines this is currently a no-op cross_attention_kwargs={} + if p.init_images is not None and len(p.init_images) > 0: + while len(p.init_images) < len(prompts): + p.init_images.append(p.init_images[-1]) if lora_state['active']: cross_attention_kwargs['scale'] = lora_state['multiplier'] task_specific_kwargs={} @@ -196,7 +202,6 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro **task_specific_kwargs ) output = shared.sd_model(**pipe_args) # pylint: disable=not-callable - if shared.state.interrupted or shared.state.skipped: unload_diffusers_lora() return results diff --git a/modules/sd_models.py b/modules/sd_models.py index 41b800f74..1cf408a96 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -874,7 +874,6 @@ def get_diffusers_task(pipe: diffusers.DiffusionPipeline) -> DiffusersTaskType: return DiffusersTaskType.IMAGE_2_IMAGE elif pipe.__class__ in diffusers.pipelines.auto_pipeline.AUTO_INPAINT_PIPELINES_MAPPING.values(): return DiffusersTaskType.INPAINTING - return DiffusersTaskType.TEXT_2_IMAGE From d4b5c487fe06683d473db9d3281950f57cb50fec Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 13 Aug 2023 09:54:09 +0000 Subject: [PATCH 58/72] fix img2img resize --- modules/processing.py | 3 +-- modules/processing_diffusers.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/processing.py b/modules/processing.py index cb64d352e..f6732e50d 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -1089,8 +1089,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): image = np.array(image).astype(np.float32) / 255.0 image = np.moveaxis(image, 2, 0) imgs.append(image) - if shared.backend == shared.Backend.DIFFUSERS: - self.init_images = unprocessed # assign early for diffusers + self.init_images = unprocessed if shared.backend == shared.Backend.DIFFUSERS else imgs if len(imgs) == 1: batch_images = np.expand_dims(imgs[0], axis=0).repeat(self.batch_size, axis=0) if self.overlay_images is not None: diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 9686f9ae3..b0dccfbfd 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -162,7 +162,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro sd_samplers.create_sampler(sampler.name, shared.sd_model) # TODO(Patrick): For wrapped pipelines this is currently a no-op cross_attention_kwargs={} - if p.init_images is not None and len(p.init_images) > 0: + if len(getattr(p, 'init_images', [])) > 0: while len(p.init_images) < len(prompts): p.init_images.append(p.init_images[-1]) if lora_state['active']: From 357b5dac6078adbfe923600c02cf2761adda6160 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 13 Aug 2023 10:19:33 +0000 Subject: [PATCH 59/72] add dpm++ 3m sde sampler --- CHANGELOG.md | 10 ++++++++++ installer.py | 2 +- modules/sd_samplers_kdiffusion.py | 4 +++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fad7083c9..54a1d90d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log for SD.Next +## Update for 2023-08-13 + +- general: + - fix img2img resizing (applies to original, diffusers, hires) +- diffusers: + - enable batch img2img workflows +- original: + - new samplers: **dpm++ 3M sde** (standard and karras variations) + enable in *settings -> samplers -> show samplers* + ## Update for 2023-08-11 This is a big one that's been cooking in `dev` for a while now, but finally ready for release... diff --git a/installer.py b/installer.py index d5b54d27a..4e5605087 100644 --- a/installer.py +++ b/installer.py @@ -463,7 +463,7 @@ def install_repositories(): clone(taming_transformers_repo, d('taming-transformers'), taming_transformers_commit) k_diffusion_repo = os.environ.get('K_DIFFUSION_REPO', 'https://github.com/crowsonkb/k-diffusion.git') # k_diffusion_commit = os.environ.get('K_DIFFUSION_COMMIT_HASH', "b43db16749d51055f813255eea2fdf1def801919") - k_diffusion_commit = os.environ.get('K_DIFFUSION_COMMIT_HASH', None) + k_diffusion_commit = os.environ.get('K_DIFFUSION_COMMIT_HASH', 'ab527a9') clone(k_diffusion_repo, d('k-diffusion'), k_diffusion_commit) codeformer_repo = os.environ.get('CODEFORMER_REPO', 'https://github.com/sczhou/CodeFormer.git') # codeformer_commit = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af") diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 7fe8abf67..3a05ab1f1 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -19,8 +19,10 @@ samplers_k_diffusion = [ ('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}), ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {"second_order": True, "brownian_noise": True}), ('DPM++ SDE Karras', 'sample_dpmpp_sde', ['k_dpmpp_sde_ka'], {'scheduler': 'karras', "second_order": True, "brownian_noise": True}), - ('DPM++ 2M SDE', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {"brownian_noise": True, 'discard_next_to_last_sigma': True}), + ('DPM++ 2M SDE', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde'], {"brownian_noise": True, 'discard_next_to_last_sigma': True}), ('DPM++ 2M SDE Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True, 'discard_next_to_last_sigma': True}), + ('DPM++ 3M SDE', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde'], {"brownian_noise": True, 'discard_next_to_last_sigma': True}), + ('DPM++ 3M SDE Karras', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True, 'discard_next_to_last_sigma': True}), ('DPM fast', 'sample_dpm_fast', ['k_dpm_fast'], {"uses_ensd": True}), ('DPM adaptive', 'sample_dpm_adaptive', ['k_dpm_ad'], {"uses_ensd": True}), ('DPM2', 'sample_dpm_2', ['k_dpm_2'], {'discard_next_to_last_sigma': True}), From 88fff06c9e5ac775c7945362a6212c36a36096f5 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 13 Aug 2023 10:58:02 +0000 Subject: [PATCH 60/72] downgrade warn to info --- modules/devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/devices.py b/modules/devices.py index 5f15eeb98..94f264532 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -67,7 +67,7 @@ def torch_gc(force=False): previous_oom = oom shared.log.warning(f'GPU out-of-memory error: {mem}') if used > 95: - shared.log.warning(f'GPU high memory utilization: {used}% {mem}') + shared.log.info(f'GPU high memory utilization: {used}% {mem}') force = True if backend == "directml": practical_used = round(100 * torch.cuda.memory_allocated() / (1 << 30) / gpu.get('total', 1)) From 414acda2e996c3fa5e167574aaae63e226bdc43a Mon Sep 17 00:00:00 2001 From: evshiron Date: Sun, 13 Aug 2023 18:57:28 +0800 Subject: [PATCH 61/72] feat: improved rocm installer for navi 3x and rocm 5.5+ (and experimental navi 2x support) --- installer.py | 44 ++++++++++++++++++++++++++++++++++++++++++-- webui.py | 5 ----- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/installer.py b/installer.py index 4e5605087..0116c7935 100644 --- a/installer.py +++ b/installer.py @@ -314,9 +314,49 @@ def check_torch(): xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.20' if opts.get('cross_attention_optimization', '') == 'xFormers' else 'none') elif allow_rocm and (shutil.which('rocminfo') is not None or os.path.exists('/opt/rocm/bin/rocminfo') or os.path.exists('/dev/kfd')): log.info('AMD ROCm toolkit detected') + + command = subprocess.run('rocm_agent_enumerator | grep -v gfx000', shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + amd_gpus = command.stdout.decode(encoding="utf8", errors="ignore").split('\n') + amd_gpus = [x for x in amd_gpus if x] + log.debug(f'ROCm agents detected: {amd_gpus}') + + # use the first available amd gpu by default + hip_visible_devices = [] + for idx, gpu in enumerate(amd_gpus): + if gpu in ['gfx1100', 'gfx1101', 'gfx1102']: + hip_visible_devices.append((idx, gpu, 'navi3x')) + break + # experimental navi 2x support + if gpu in ['gfx1030', 'gfx1031', 'gfx1032', 'gfx1034']: + hip_visible_devices.append((idx, gpu, 'navi2x')) + break + if len(hip_visible_devices) > 0: + idx, gpu, arch = hip_visible_devices[0] + log.debug(f'ROCm agent used by default: idx={idx} gpu={gpu} arch={arch}') + + os.environ.setdefault('HIP_VISIBLE_DEVICES', str(idx)) + if arch == 'navi3x': + os.environ.setdefault('HSA_OVERRIDE_GFX_VERSION', '11.0.0') + elif arch == 'navi2x': + os.environ.setdefault('HSA_OVERRIDE_GFX_VERSION', '10.3.0') + # install tensorflow-rocm for navi2x + os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow-rocm') + else: + log.debug(f'HSA_OVERRIDE_GFX_VERSION auto config is skipped for {gpu}') + os.environ.setdefault('PYTORCH_HIP_ALLOC_CONF', 'garbage_collection_threshold:0.8,max_split_size_mb:512') - os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow-rocm') - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/rocm5.4.2') + + command = subprocess.run('hipconfig --version', shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + major_ver, minor_ver, *_ = command.stdout.decode(encoding="utf8", errors="ignore").split('.') + rocm_ver = f'{major_ver}.{minor_ver}' + log.debug(f'ROCm version detected: {rocm_ver}') + + if rocm_ver in ['5.5', '5.6']: + # install torch nightly via torchvision to avoid wasting bandwidth when torchvision depends on torch from yesterday + torch_command = os.environ.get('TORCH_COMMAND', f'torchvision --pre --index-url https://download.pytorch.org/whl/nightly/rocm{rocm_ver}') + else: + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/rocm5.4.2') + xformers_package = os.environ.get('XFORMERS_PACKAGE', 'none') elif allow_ipex and (args.use_ipex or shutil.which('sycl-ls') is not None or shutil.which('sycl-ls.exe') is not None or os.environ.get('ONEAPI_ROOT') is not None or os.path.exists('/opt/intel/oneapi') or os.path.exists("C:/Program Files (x86)/Intel/oneAPI") or os.path.exists("C:/oneAPI")): args.use_ipex = True # pylint: disable=attribute-defined-outside-init diff --git a/webui.py b/webui.py index 57a37c9a2..d4feb3001 100644 --- a/webui.py +++ b/webui.py @@ -17,11 +17,6 @@ local_url = None errors.log.debug('Loading Torch') import torch # pylint: disable=C0411 -try: - rnd = torch.sum(torch.randn(2, 2)).to(0) - errors.log.debug(f'Torch init: {rnd / rnd == 1.0}') # fix silly pytorch_lightning issue -except Exception: - pass try: import intel_extension_for_pytorch as ipex # pylint: disable=import-error, unused-import except Exception: From b3029200f5304f3c049bf763aa7dfe7363b78b0f Mon Sep 17 00:00:00 2001 From: evshiron Date: Sun, 13 Aug 2023 20:18:30 +0800 Subject: [PATCH 62/72] refactor: refactor rocm installer --- installer.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/installer.py b/installer.py index 0116c7935..b9241cb0c 100644 --- a/installer.py +++ b/installer.py @@ -314,11 +314,17 @@ def check_torch(): xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.20' if opts.get('cross_attention_optimization', '') == 'xFormers' else 'none') elif allow_rocm and (shutil.which('rocminfo') is not None or os.path.exists('/opt/rocm/bin/rocminfo') or os.path.exists('/dev/kfd')): log.info('AMD ROCm toolkit detected') + os.environ.setdefault('PYTORCH_HIP_ALLOC_CONF', 'garbage_collection_threshold:0.8,max_split_size_mb:512') + os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow-rocm') - command = subprocess.run('rocm_agent_enumerator | grep -v gfx000', shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - amd_gpus = command.stdout.decode(encoding="utf8", errors="ignore").split('\n') - amd_gpus = [x for x in amd_gpus if x] - log.debug(f'ROCm agents detected: {amd_gpus}') + try: + command = subprocess.run('rocm_agent_enumerator', shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + amd_gpus = command.stdout.decode(encoding="utf8", errors="ignore").split('\n') + amd_gpus = [x for x in amd_gpus if x and x != 'gfx000'] + log.debug(f'ROCm agents detected: {amd_gpus}') + except Exception as e: + log.debug(f'Run rocm_agent_enumerator failed: {e}') + amd_gpus = [] # use the first available amd gpu by default hip_visible_devices = [] @@ -337,19 +343,21 @@ def check_torch(): os.environ.setdefault('HIP_VISIBLE_DEVICES', str(idx)) if arch == 'navi3x': os.environ.setdefault('HSA_OVERRIDE_GFX_VERSION', '11.0.0') + # do not use tensorflow-rocm for navi 3x + os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0') elif arch == 'navi2x': os.environ.setdefault('HSA_OVERRIDE_GFX_VERSION', '10.3.0') - # install tensorflow-rocm for navi2x - os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow-rocm') else: log.debug(f'HSA_OVERRIDE_GFX_VERSION auto config is skipped for {gpu}') - os.environ.setdefault('PYTORCH_HIP_ALLOC_CONF', 'garbage_collection_threshold:0.8,max_split_size_mb:512') - - command = subprocess.run('hipconfig --version', shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - major_ver, minor_ver, *_ = command.stdout.decode(encoding="utf8", errors="ignore").split('.') - rocm_ver = f'{major_ver}.{minor_ver}' - log.debug(f'ROCm version detected: {rocm_ver}') + try: + command = subprocess.run('hipconfig --version', shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + major_ver, minor_ver, *_ = command.stdout.decode(encoding="utf8", errors="ignore").split('.') + rocm_ver = f'{major_ver}.{minor_ver}' + log.debug(f'ROCm version detected: {rocm_ver}') + except Exception as e: + log.debug(f'Run hipconfig failed: {e}') + rocm_ver = None if rocm_ver in ['5.5', '5.6']: # install torch nightly via torchvision to avoid wasting bandwidth when torchvision depends on torch from yesterday From bb6b3e2e3f3c7440dfb634610a84523fd51146da Mon Sep 17 00:00:00 2001 From: evshiron Date: Mon, 14 Aug 2023 00:57:40 +0800 Subject: [PATCH 63/72] fix: fix tensorflow installer for navi 3x --- installer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/installer.py b/installer.py index b9241cb0c..05bbeb5bb 100644 --- a/installer.py +++ b/installer.py @@ -344,7 +344,8 @@ def check_torch(): if arch == 'navi3x': os.environ.setdefault('HSA_OVERRIDE_GFX_VERSION', '11.0.0') # do not use tensorflow-rocm for navi 3x - os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0') + if os.environ.get('TENSORFLOW_PACKAGE') == 'tensorflow-rocm': + os.environ['TENSORFLOW_PACKAGE'] = 'tensorflow==2.13.0' elif arch == 'navi2x': os.environ.setdefault('HSA_OVERRIDE_GFX_VERSION', '10.3.0') else: From eeca263bd2b8962d4a025a200d7c0a22387dd52f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 14 Aug 2023 07:50:12 +0000 Subject: [PATCH 64/72] update stored metadata --- extensions-builtin/sd-webui-agent-scheduler | 2 +- extensions-builtin/sd-webui-controlnet | 2 +- modules/img2img.py | 1 + modules/processing.py | 37 ++++++++++----------- modules/processing_diffusers.py | 15 +++++++++ 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/extensions-builtin/sd-webui-agent-scheduler b/extensions-builtin/sd-webui-agent-scheduler index e270493ab..4af58ffa2 160000 --- a/extensions-builtin/sd-webui-agent-scheduler +++ b/extensions-builtin/sd-webui-agent-scheduler @@ -1 +1 @@ -Subproject commit e270493ab7c4b5cfb5eb39f474c34e51ebba8221 +Subproject commit 4af58ffa2c5406db9ef43d119edcd0b5eb305346 diff --git a/extensions-builtin/sd-webui-controlnet b/extensions-builtin/sd-webui-controlnet index d67f31a3a..0cfc88b6a 160000 --- a/extensions-builtin/sd-webui-controlnet +++ b/extensions-builtin/sd-webui-controlnet @@ -1 +1 @@ -Subproject commit d67f31a3a7b4a70facbbec2feea3f02d5ddf2fab +Subproject commit 0cfc88b6a892076d199e68c02e9c306ac6ab2ead diff --git a/modules/img2img.py b/modules/img2img.py index 0c7288009..9162a5625 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -177,6 +177,7 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s ) p.scripts = modules.scripts.scripts_img2img p.script_args = args + p.extra_generation_params['Resize mode'] = resize_mode if mask: p.extra_generation_params["Mask blur"] = mask_blur if is_batch: diff --git a/modules/processing.py b/modules/processing.py index f6732e50d..4bf9b7e72 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -445,16 +445,13 @@ def fix_seed(p): p.subseed = get_fixed_seed(p.subseed) -def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, index=None, all_negative_prompts=None): # pylint: disable=unused-argument +def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, index=None, all_negative_prompts=None): if index is None: index = position_in_batch + iteration * p.batch_size - if all_negative_prompts is None: all_negative_prompts = p.all_negative_prompts - if p.full_quality: - vae = None if not shared.opts.add_model_name_to_info or sd_vae.loaded_vae_file is None else os.path.splitext(os.path.basename(sd_vae.loaded_vae_file))[0] - else: - vae = 'TAESD' + vae = (None if not shared.opts.add_model_name_to_info or sd_vae.loaded_vae_file is None else os.path.splitext(os.path.basename(sd_vae.loaded_vae_file))[0]) if p.full_quality else 'TAESD' + comment = ', '.join(comments) if comments is not None and type(comments) is list else None generation_params = { "Steps": p.steps, @@ -462,14 +459,15 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su "Sampler": p.sampler_name, "CFG scale": p.cfg_scale, "Size": f"{p.width}x{p.height}", + "Batch": f'{p.n_iter}x{p.batch_size}' if p.n_iter > 1 or p.batch_size > 1 else None, "Parser": shared.opts.prompt_attention, - "Model": None if not shared.opts.add_model_name_to_info or not shared.sd_model.sd_checkpoint_info.model_name else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', ''), - "Model hash": getattr(p, 'sd_model_hash', None if not shared.opts.add_model_hash_to_info or not shared.sd_model.sd_model_hash else shared.sd_model.sd_model_hash), - "Refiner": None if not shared.opts.add_model_name_to_info or not shared.sd_refiner or not shared.sd_refiner.sd_checkpoint_info.model_name else shared.sd_refiner.sd_checkpoint_info.model_name.replace(',', '').replace(':', ''), + "Model": None if (not shared.opts.add_model_name_to_info) or (not shared.sd_model.sd_checkpoint_info.model_name) else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', ''), + "Model hash": getattr(p, 'sd_model_hash', None if (not shared.opts.add_model_hash_to_info) or (not shared.sd_model.sd_model_hash) else shared.sd_model.sd_model_hash), + "Refiner": None if (not shared.opts.add_model_name_to_info) or (not shared.sd_refiner) or (not shared.sd_refiner.sd_checkpoint_info.model_name) else shared.sd_refiner.sd_checkpoint_info.model_name.replace(',', '').replace(':', ''), "VAE": vae, # subseed "Variation seed": None if p.subseed_strength == 0 else all_subseeds[index], - "Variation seed strength": None if p.subseed_strength == 0 else p.subseed_strength, + "Variation strength": None if p.subseed_strength == 0 else p.subseed_strength, # seed resize "Seed resize from": None if p.seed_resize_from_w == 0 or p.seed_resize_from_h == 0 else f"{p.seed_resize_from_w}x{p.seed_resize_from_h}", "Init image hash": getattr(p, 'init_img_hash', None), @@ -478,18 +476,19 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts, all_seeds, all_su "Clip skip": p.clip_skip if p.clip_skip > 1 else None, # ensd "ENSD": shared.opts.eta_noise_seed_delta if shared.opts.eta_noise_seed_delta != 0 and sd_samplers_common.is_sampler_using_eta_noise_seed_delta(p) else None, - # enable_hr - "Latent sampler": p.latent_sampler if p.enable_hr else None, - "Image CFG scale": p.image_cfg_scale if p.enable_hr else None, - "Denoising strength": p.denoising_strength if p.enable_hr else None, - "Refiner start": p.refiner_start if p.enable_hr else None, - "Secondary steps": p.hr_second_pass_steps if p.enable_hr else None, - # restore_faces + # restore_faces, tiling "Face restoration": shared.opts.face_restoration_model if p.restore_faces else None, + "Tiling": p.tiling if p.tiling else None, + # enable_hr + "Prompt2": p.refiner_prompt if p.enable_hr and len(p.refiner_prompt) > 0 else None, + "Negative2": p.refiner_negative if p.enable_hr and len(p.refiner_negative) > 0 else None, + "Latent sampler": p.latent_sampler if p.enable_hr and p.latent_sampler != p.sampler_name else None, + "Denoising strength": p.denoising_strength if p.enable_hr else None, # sdnext + "Backend": 'Diffusers' if shared.backend == shared.Backend.DIFFUSERS else 'Original', "Version": git_commit, - "Pipeline": 'Diffusers' if shared.backend == shared.Backend.DIFFUSERS else 'Original', - "Operations": ', '.join(list(set(p.ops))) if len(p.ops) > 0 else None + "Comment": comment, + "Operations": ', '.join(list(set(p.ops))) if len(p.ops) > 0 else None, } token_merging_ratio = p.get_token_merging_ratio() token_merging_ratio_hr = p.get_token_merging_ratio(for_hr=True) if p.enable_hr else None diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index b0dccfbfd..ab104b11c 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -160,6 +160,14 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro if sampler is None: sampler = sd_samplers.all_samplers_map.get("UniPC") sd_samplers.create_sampler(sampler.name, shared.sd_model) # TODO(Patrick): For wrapped pipelines this is currently a no-op + sampler_options = f'type:{shared.opts.schedulers_prediction_type} ' if shared.opts.schedulers_prediction_type != 'default' else '' + sampler_options += 'no_karras ' if not shared.opts.schedulers_use_karras else '' + sampler_options += 'no_low_order' if not shared.opts.schedulers_use_loworder else '' + sampler_options += 'dynamic_thresholding' if shared.opts.schedulers_use_thresholding else '' + sampler_options += f'solver:{shared.opts.schedulers_dpm_solver}' if shared.opts.schedulers_dpm_solver != 'sde-dpmsolver++' else '' + sampler_options += f'beta:{shared.opts.schedulers_beta_schedule}:{shared.opts.schedulers_beta_start}:{shared.opts.schedulers_beta_end}' if shared.opts.schedulers_beta_schedule != 'default' else '' + p.extra_generation_params['Sampler options'] = sampler_options if len(sampler_options) > 0 else None + p.extra_generation_params['Pipeline'] = shared.sd_model.__class__.__name__ cross_attention_kwargs={} if len(getattr(p, 'init_images', [])) > 0: @@ -201,6 +209,8 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro clip_skip=p.clip_skip, **task_specific_kwargs ) + p.extra_generation_params['CFG rescale'] = p.diffusers_guidance_rescale + p.extra_generation_params["Eta DDIM"] = shared.opts.eta_ddim if shared.opts.eta_ddim is not None and shared.opts.eta_ddim > 0 else None output = shared.sd_model(**pipe_args) # pylint: disable=not-callable if shared.state.interrupted or shared.state.skipped: unload_diffusers_lora() @@ -210,6 +220,7 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro output.images = vae_decode(output.images, shared.sd_model) if p.full_quality else taesd_vae_decode(output.images, shared.sd_model) if lora_state['active']: + p.extra_generation_params['Lora method'] = shared.opts.diffusers_lora_loader unload_diffusers_lora() if refiner_enabled: @@ -256,6 +267,10 @@ def process_diffusers(p: StableDiffusionProcessing, seeds, prompts, negative_pro clip_skip=p.clip_skip, ) refiner_output = shared.sd_refiner(**pipe_args) # pylint: disable=not-callable + p.extra_generation_params['Refiner CFG scale'] = p.image_cfg_scale if p.image_cfg_scale is not None else None + p.extra_generation_params['Refiner start'] = p.refiner_start + p.extra_generation_params["Hires steps"] = p.hr_second_pass_steps + if not shared.state.interrupted and not shared.state.skipped: refiner_images = vae_decode(refiner_output.images, shared.sd_refiner) results.append(refiner_images[0]) From 048f68bd40e3ff973c0f1982c0af8f993b92d52f Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 14 Aug 2023 07:50:46 +0000 Subject: [PATCH 65/72] update wiki --- wiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki b/wiki index 581054504..7cc5b14e1 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 581054504c3ad2cfa7f0e625147484836e8a84ba +Subproject commit 7cc5b14e12d61fcdc904989a99eb9b01743575bd From fce48be440b888ce4ceb27f4d081454d6cc8fd2b Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 14 Aug 2023 07:58:42 +0000 Subject: [PATCH 66/72] update changelog --- CHANGELOG.md | 9 +++++++-- modules/prompt_parser_diffusers.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a1d90d2..f493f05b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ # Change Log for SD.Next -## Update for 2023-08-13 +## Update for 2023-08-14 - general: - - fix img2img resizing (applies to original, diffusers, hires) + - update all metadata saved with images + see for details + (work-in-progress) + - improved **amd** installer with support for **navi 2x & 3x** and **rocm 5.4/5.5/5.6** + thanks @evshiron + - fix img2img resizing (applies to original, diffusers, hires) - diffusers: - enable batch img2img workflows - original: diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 8850fb67b..27dd423de 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -99,7 +99,7 @@ def compel_encode_prompt( text_encoder=pipeline.text_encoder, returned_embeddings_type=embedding_type, requires_pooled=False, - truncate_long_prompts=False, + # truncate_long_prompts=False, device=shared.device ) From 66394c8cd9ca86fbdea1b2d1fc81cbd5dae06da3 Mon Sep 17 00:00:00 2001 From: Disty0 Date: Mon, 14 Aug 2023 21:54:21 +0300 Subject: [PATCH 67/72] IPEX add OpenVINO as compile backend --- installer.py | 4 +-- modules/ipex_specific/__init__.py | 4 +++ modules/ipex_specific/hijacks.py | 56 ++++++++++++++++++++++--------- modules/ipex_specific/openvino.py | 33 ++++++++++++++++++ modules/shared.py | 2 +- webui.sh | 4 --- 6 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 modules/ipex_specific/openvino.py diff --git a/installer.py b/installer.py index 05bbeb5bb..c04fecead 100644 --- a/installer.py +++ b/installer.py @@ -375,10 +375,10 @@ def check_torch(): os.environ.setdefault('NEOReadDebugKeys', '1') os.environ.setdefault('ClDeviceGlobalMemSizeAvailablePercent', '100') if "linux" in sys.platform: - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1a0 torchvision==0.15.2a0 intel_extension_for_pytorch==2.0.110+xpu -f https://developer.intel.com/ipex-whl-stable-xpu') + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1a0 torchvision==0.15.2a0 intel_extension_for_pytorch==2.0.110+xpu openvino==2023.1.0.dev20230728 -f https://developer.intel.com/ipex-whl-stable-xpu') os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0 intel-extension-for-tensorflow[gpu]') else: - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.0a0 torchvision==0.15.2a0 intel_extension_for_pytorch==2.0.110+gitba7f6c1 -f https://developer.intel.com/ipex-whl-stable-xpu') + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.0a0 torchvision==0.15.1 intel_extension_for_pytorch==2.0.110+gitba7f6c1 openvino==2023.1.0.dev20230728 -f https://developer.intel.com/ipex-whl-stable-xpu') else: machine = platform.machine() if sys.platform == 'darwin': diff --git a/modules/ipex_specific/__init__.py b/modules/ipex_specific/__init__.py index ecb72641f..f8fdca2eb 100644 --- a/modules/ipex_specific/__init__.py +++ b/modules/ipex_specific/__init__.py @@ -86,3 +86,7 @@ def ipex_init(): ipex_hijacks() ipex_diffusers() + try: + from .openvino import openvino_fx + except Exception: + pass diff --git a/modules/ipex_specific/hijacks.py b/modules/ipex_specific/hijacks.py index 32e075204..05419f426 100644 --- a/modules/ipex_specific/hijacks.py +++ b/modules/ipex_specific/hijacks.py @@ -8,6 +8,40 @@ def ipex_no_cuda(orig_func, *args, **kwargs): # pylint: disable=redefined-outer- orig_func(*args, **kwargs) torch.cuda.is_available = torch.xpu.is_available +#FP32: +original_linear_forward = torch.nn.modules.Linear.forward +def linear_forward(self, input): + if input.dtype != self.weight.data.dtype: + return original_linear_forward(self, input.to(self.weight.data.dtype)) + else: + return original_linear_forward(self, input) + +#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) + else: + return original_torch_cat(input, *args, **kwargs) + +original_conv2d = torch.nn.functional.conv2d +#Diffusers BF16: +def conv2d(input, weight, *args, **kwargs): + if input.dtype != weight.data.dtype: + return original_conv2d(input.to(weight.data.dtype), weight, *args, **kwargs) + else: + return original_conv2d(input, weight, *args, **kwargs) + +original_interpolate = torch.nn.functional.interpolate +#Latent antialias: +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"), size=size, scale_factor=scale_factor, mode=mode, + align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias).to(shared.device) + else: + return original_interpolate(input, size=size, scale_factor=scale_factor, mode=mode, + align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias) + def ipex_hijacks(): #Libraries that blindly uses cuda: #Adetailer: @@ -36,10 +70,6 @@ def ipex_hijacks(): CondFunc('torch.nn.modules.GroupNorm.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) - #FP32: - CondFunc('torch.nn.modules.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), @@ -50,14 +80,6 @@ def ipex_hijacks(): orig_func(input.to(weight.data.dtype), normalized_shape, weight, *args, **kwargs), lambda orig_func, input, normalized_shape=None, weight=None, *args, **kwargs: input.dtype != weight.data.dtype and weight is not None) - #Embedding BF16 - CondFunc('torch.cat', - lambda orig_func, input, *args, **kwargs: orig_func([input[0].to(input[1].dtype), input[1], input[2].to(input[1].dtype)], *args, **kwargs), - lambda orig_func, input, *args, **kwargs: len(input) == 3 and (input[0].dtype != input[1].dtype or input[2].dtype != input[1].dtype)) - #Diffusers BF16: - CondFunc('torch.nn.functional.conv2d', - lambda orig_func, input, weight, *args, **kwargs: orig_func(input.to(weight.data.dtype), weight, *args, **kwargs), - lambda orig_func, input, weight, *args, **kwargs: input.dtype != weight.data.dtype) #Functions that does not work with the XPU: #UniPC: @@ -68,10 +90,6 @@ def ipex_hijacks(): CondFunc('torch.Generator', lambda orig_func, device: torch.xpu.Generator(device), lambda orig_func, device: device != torch.device("cpu") and device != "cpu") - #Latent antialias: - CondFunc('torch.nn.functional.interpolate', - lambda orig_func, input, *args, **kwargs: orig_func(input.to("cpu"), *args, **kwargs).to(shared.device), - lambda orig_func, input, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False: antialias) #Diffusers Float64 (ARC GPUs doesn't support double or Float64): if not torch.xpu.has_fp64_dtype(): CondFunc('torch.from_numpy', @@ -89,3 +107,9 @@ def ipex_hijacks(): weight if weight is not None else torch.ones(input.size()[1], device=shared.device), bias if bias is not None else torch.zeros(input.size()[1], device=shared.device), *args, **kwargs), lambda orig_func, input, *args, **kwargs: input.device != torch.device("cpu")) + + #Functions that make compile mad with CondFunc: + torch.nn.modules.Linear.forward = linear_forward + torch.cat = torch_cat + torch.nn.functional.conv2d = conv2d + torch.nn.functional.interpolate = interpolate diff --git a/modules/ipex_specific/openvino.py b/modules/ipex_specific/openvino.py new file mode 100644 index 000000000..5101c7d8e --- /dev/null +++ b/modules/ipex_specific/openvino.py @@ -0,0 +1,33 @@ +import os +import torch +import intel_extension_for_pytorch as ipex +from openvino.frontend.pytorch.torchdynamo.execute import execute +from openvino.frontend.pytorch.torchdynamo.partition import Partitioner +from torch._dynamo.backends.common import fake_tensor_unsupported +from torch._dynamo.backends.registry import register_backend +from torch._inductor.compile_fx import compile_fx +from torch.fx.experimental.proxy_tensor import make_fx + +class ModelState: + def __init__(self): + self.recompile = 1 + self.partition_id = 0 + +model_state = ModelState() + +@register_backend +@fake_tensor_unsupported +def openvino_fx(subgraph, example_inputs): + if os.getenv("OPENVINO_TORCH_BACKEND_DEVICE") is None: + os.environ.setdefault("OPENVINO_TORCH_BACKEND_DEVICE", "GPU") + + model = make_fx(subgraph)(*example_inputs) + with torch.no_grad(): + model.eval() + partitioner = Partitioner() + compiled_model = partitioner.make_partitions(model) + + def _call(*args): + res = execute(compiled_model, *args, executor="openvino") + return res + return _call diff --git a/modules/shared.py b/modules/shared.py index 945f2a27f..de66b56c8 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -384,7 +384,7 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { # "cuda_allow_tf32": OptionInfo(True, "Allow TF32 math ops"), # "cuda_allow_tf16_reduced": OptionInfo(True, "Allow TF16 reduced precision math ops"), "cuda_compile": OptionInfo(False, "Enable model compile (experimental)"), - "cuda_compile_backend": OptionInfo("none", "Model compile backend (experimental)", gr.Radio, lambda: {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex']}), + "cuda_compile_backend": OptionInfo("none", "Model compile backend (experimental)", gr.Radio, lambda: {"choices": ['none', 'inductor', 'cudagraphs', 'aot_ts_nvfuser', 'hidet', 'ipex', 'openvino_fx']}), "cuda_compile_mode": OptionInfo("default", "Model compile mode (experimental)", gr.Radio, lambda: {"choices": ['default', 'reduce-overhead', 'max-autotune']}), "cuda_compile_fullgraph": OptionInfo(False, "Model compile fullgraph"), "cuda_compile_verbose": OptionInfo(False, "Model compile verbose mode"), diff --git a/webui.sh b/webui.sh index 38fc189b1..59f235aac 100755 --- a/webui.sh +++ b/webui.sh @@ -96,10 +96,6 @@ if [[ ! -z "${ACCELERATE}" ]] && [ ${ACCELERATE}="True" ] && [ -x "$(command -v then echo "Launching accelerate launch.py..." exec accelerate launch --num_cpu_threads_per_process=6 launch.py "$@" -elif [[ "$@" == *"--use-ipex"* ]] && [[ -z "${first_launch}" ]] && [ -x "$(command -v ipexrun)" ] && [ -x "$(command -v sycl-ls)" ] -then - echo "Launching ipexrun launch.py..." - exec ipexrun --multi-task-manager 'taskset' launch.py "$@" else echo "Launching launch.py..." exec "${python_cmd}" launch.py "$@" From b1ea529c08ae522fc6177d6f44e20b826335fb0c Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 15 Aug 2023 00:50:46 +0300 Subject: [PATCH 68/72] Cleanup --- modules/ipex_specific/__init__.py | 4 +++- modules/ipex_specific/openvino.py | 1 - webui.sh | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/ipex_specific/__init__.py b/modules/ipex_specific/__init__.py index f8fdca2eb..7de07d78b 100644 --- a/modules/ipex_specific/__init__.py +++ b/modules/ipex_specific/__init__.py @@ -1,4 +1,5 @@ import os +import sys import contextlib import torch import intel_extension_for_pytorch as ipex @@ -39,6 +40,8 @@ def ipex_init(): torch.Tensor.is_cuda = torch.Tensor.is_xpu #Memory: + if 'linux' in sys.platform and "WSL2" in os.popen("uname -a").read(): + torch.xpu.empty_cache = lambda: None torch.cuda.empty_cache = torch.xpu.empty_cache torch.cuda.memory_stats = torch.xpu.memory_stats torch.cuda.memory_summary = torch.xpu.memory_summary @@ -76,7 +79,6 @@ 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.xpu.empty_cache = torch.xpu.empty_cache if "WSL2" not in os.popen("uname -a").read() else lambda: None torch.cuda.get_device_properties.major = 2023 torch.cuda.get_device_properties.minor = 2 torch.backends.cuda.sdp_kernel = return_null_context diff --git a/modules/ipex_specific/openvino.py b/modules/ipex_specific/openvino.py index 5101c7d8e..917d3231f 100644 --- a/modules/ipex_specific/openvino.py +++ b/modules/ipex_specific/openvino.py @@ -5,7 +5,6 @@ from openvino.frontend.pytorch.torchdynamo.execute import execute from openvino.frontend.pytorch.torchdynamo.partition import Partitioner from torch._dynamo.backends.common import fake_tensor_unsupported from torch._dynamo.backends.registry import register_backend -from torch._inductor.compile_fx import compile_fx from torch.fx.experimental.proxy_tensor import make_fx class ModelState: diff --git a/webui.sh b/webui.sh index 59f235aac..38fc189b1 100755 --- a/webui.sh +++ b/webui.sh @@ -96,6 +96,10 @@ if [[ ! -z "${ACCELERATE}" ]] && [ ${ACCELERATE}="True" ] && [ -x "$(command -v then echo "Launching accelerate launch.py..." exec accelerate launch --num_cpu_threads_per_process=6 launch.py "$@" +elif [[ "$@" == *"--use-ipex"* ]] && [[ -z "${first_launch}" ]] && [ -x "$(command -v ipexrun)" ] && [ -x "$(command -v sycl-ls)" ] +then + echo "Launching ipexrun launch.py..." + exec ipexrun --multi-task-manager 'taskset' launch.py "$@" else echo "Launching launch.py..." exec "${python_cmd}" launch.py "$@" From 86ae8175e0a8cf9e645c283ad46f51c5d5e3ecdd Mon Sep 17 00:00:00 2001 From: Disty0 Date: Tue, 15 Aug 2023 15:22:54 +0300 Subject: [PATCH 69/72] Seperate OpenVINO from IPEX --- installer.py | 6 ++++-- modules/ipex_specific/__init__.py | 4 ---- modules/sd_hijack.py | 2 ++ modules/sd_models.py | 6 ++++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/installer.py b/installer.py index c04fecead..3d2beae10 100644 --- a/installer.py +++ b/installer.py @@ -375,10 +375,10 @@ def check_torch(): os.environ.setdefault('NEOReadDebugKeys', '1') os.environ.setdefault('ClDeviceGlobalMemSizeAvailablePercent', '100') if "linux" in sys.platform: - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1a0 torchvision==0.15.2a0 intel_extension_for_pytorch==2.0.110+xpu openvino==2023.1.0.dev20230728 -f https://developer.intel.com/ipex-whl-stable-xpu') + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1a0 torchvision==0.15.2a0 intel_extension_for_pytorch==2.0.110+xpu -f https://developer.intel.com/ipex-whl-stable-xpu') os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0 intel-extension-for-tensorflow[gpu]') else: - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.0a0 torchvision==0.15.1 intel_extension_for_pytorch==2.0.110+gitba7f6c1 openvino==2023.1.0.dev20230728 -f https://developer.intel.com/ipex-whl-stable-xpu') + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.0a0 torchvision==0.15.1 intel_extension_for_pytorch==2.0.110+gitba7f6c1 -f https://developer.intel.com/ipex-whl-stable-xpu') else: machine = platform.machine() if sys.platform == 'darwin': @@ -442,6 +442,8 @@ def check_torch(): log.debug(f'Cannot install xformers package: {e}') if opts.get('cuda_compile_backend', '') == 'hidet': install('hidet', 'hidet') + if opts.get('cuda_compile_backend', '') == 'openvino_fx': + install('openvino==2023.1.0.dev20230728', 'openvino') if args.profile: print_profile(pr, 'Torch') diff --git a/modules/ipex_specific/__init__.py b/modules/ipex_specific/__init__.py index 7de07d78b..7279eb186 100644 --- a/modules/ipex_specific/__init__.py +++ b/modules/ipex_specific/__init__.py @@ -88,7 +88,3 @@ def ipex_init(): ipex_hijacks() ipex_diffusers() - try: - from .openvino import openvino_fx - except Exception: - pass diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index a583d79d8..8eaf9addc 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -188,6 +188,8 @@ class StableDiffusionModelHijack: import logging shared.log.info(f"Compiling pipeline={m.model.__class__.__name__} mode={opts.cuda_compile_backend}") import torch._dynamo # pylint: disable=unused-import,redefined-outer-name + if shared.opts.cuda_compile_backend == "openvino_fx": + from modules.ipex_specific.openvino import openvino_fx log_level = logging.WARNING if opts.cuda_compile_verbose else logging.CRITICAL # pylint: disable=protected-access if hasattr(torch, '_logging'): torch._logging.set_logs(dynamo=log_level, aot=log_level, inductor=log_level) # pylint: disable=protected-access diff --git a/modules/sd_models.py b/modules/sd_models.py index 1cf408a96..cd8998565 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -745,7 +745,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No sd_model.unet.to(memory_format=torch.channels_last) base_sent_to_cpu=False - if (shared.opts.cuda_compile or shared.opts.ipex_optimize) and torch.cuda.is_available(): + if (shared.opts.cuda_compile and shared.opts.cuda_compile_backend != 'none') or shared.opts.ipex_optimize: if op == 'refiner' and not sd_model.has_accelerate: gpu_vram = memory_stats().get('gpu', {}) free_vram = gpu_vram.get('total', 0) - gpu_vram.get('used', 0) @@ -775,9 +775,11 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No except Exception as err: shared.log.warning(f"IPEX Optimize not supported: {err}") try: - if shared.opts.cuda_compile: + if shared.opts.cuda_compile and shared.opts.cuda_compile_backend != 'none': shared.log.info(f"Compiling pipeline={sd_model.__class__.__name__} shape={8 * sd_model.unet.config.sample_size} mode={shared.opts.cuda_compile_backend}") import torch._dynamo # pylint: disable=unused-import,redefined-outer-name + if shared.opts.cuda_compile_backend == "openvino_fx": + from modules.ipex_specific.openvino import openvino_fx log_level = logging.WARNING if shared.opts.cuda_compile_verbose else logging.CRITICAL # pylint: disable=protected-access if hasattr(torch, '_logging'): torch._logging.set_logs(dynamo=log_level, aot=log_level, inductor=log_level) # pylint: disable=protected-access From 79c0131158baea7dceb011c2a0a516554aa919aa Mon Sep 17 00:00:00 2001 From: vladmandic Date: Tue, 15 Aug 2023 12:25:08 +0000 Subject: [PATCH 70/72] =?UTF-8?q?Deploying=20to=20master=20from=20@=20vlad?= =?UTF-8?q?mandic/automatic@b1ea529c08ae522fc6177d6f44e20b826335fb0c=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- installer.py | 6 ++---- modules/ipex_specific/__init__.py | 4 ++++ modules/sd_hijack.py | 2 -- modules/sd_models.py | 6 ++---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/installer.py b/installer.py index 3d2beae10..c04fecead 100644 --- a/installer.py +++ b/installer.py @@ -375,10 +375,10 @@ def check_torch(): os.environ.setdefault('NEOReadDebugKeys', '1') os.environ.setdefault('ClDeviceGlobalMemSizeAvailablePercent', '100') if "linux" in sys.platform: - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1a0 torchvision==0.15.2a0 intel_extension_for_pytorch==2.0.110+xpu -f https://developer.intel.com/ipex-whl-stable-xpu') + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.1a0 torchvision==0.15.2a0 intel_extension_for_pytorch==2.0.110+xpu openvino==2023.1.0.dev20230728 -f https://developer.intel.com/ipex-whl-stable-xpu') os.environ.setdefault('TENSORFLOW_PACKAGE', 'tensorflow==2.13.0 intel-extension-for-tensorflow[gpu]') else: - torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.0a0 torchvision==0.15.1 intel_extension_for_pytorch==2.0.110+gitba7f6c1 -f https://developer.intel.com/ipex-whl-stable-xpu') + torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.0.0a0 torchvision==0.15.1 intel_extension_for_pytorch==2.0.110+gitba7f6c1 openvino==2023.1.0.dev20230728 -f https://developer.intel.com/ipex-whl-stable-xpu') else: machine = platform.machine() if sys.platform == 'darwin': @@ -442,8 +442,6 @@ def check_torch(): log.debug(f'Cannot install xformers package: {e}') if opts.get('cuda_compile_backend', '') == 'hidet': install('hidet', 'hidet') - if opts.get('cuda_compile_backend', '') == 'openvino_fx': - install('openvino==2023.1.0.dev20230728', 'openvino') if args.profile: print_profile(pr, 'Torch') diff --git a/modules/ipex_specific/__init__.py b/modules/ipex_specific/__init__.py index 7279eb186..7de07d78b 100644 --- a/modules/ipex_specific/__init__.py +++ b/modules/ipex_specific/__init__.py @@ -88,3 +88,7 @@ def ipex_init(): ipex_hijacks() ipex_diffusers() + try: + from .openvino import openvino_fx + except Exception: + pass diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index 8eaf9addc..a583d79d8 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -188,8 +188,6 @@ class StableDiffusionModelHijack: import logging shared.log.info(f"Compiling pipeline={m.model.__class__.__name__} mode={opts.cuda_compile_backend}") import torch._dynamo # pylint: disable=unused-import,redefined-outer-name - if shared.opts.cuda_compile_backend == "openvino_fx": - from modules.ipex_specific.openvino import openvino_fx log_level = logging.WARNING if opts.cuda_compile_verbose else logging.CRITICAL # pylint: disable=protected-access if hasattr(torch, '_logging'): torch._logging.set_logs(dynamo=log_level, aot=log_level, inductor=log_level) # pylint: disable=protected-access diff --git a/modules/sd_models.py b/modules/sd_models.py index cd8998565..1cf408a96 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -745,7 +745,7 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No sd_model.unet.to(memory_format=torch.channels_last) base_sent_to_cpu=False - if (shared.opts.cuda_compile and shared.opts.cuda_compile_backend != 'none') or shared.opts.ipex_optimize: + if (shared.opts.cuda_compile or shared.opts.ipex_optimize) and torch.cuda.is_available(): if op == 'refiner' and not sd_model.has_accelerate: gpu_vram = memory_stats().get('gpu', {}) free_vram = gpu_vram.get('total', 0) - gpu_vram.get('used', 0) @@ -775,11 +775,9 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No except Exception as err: shared.log.warning(f"IPEX Optimize not supported: {err}") try: - if shared.opts.cuda_compile and shared.opts.cuda_compile_backend != 'none': + if shared.opts.cuda_compile: shared.log.info(f"Compiling pipeline={sd_model.__class__.__name__} shape={8 * sd_model.unet.config.sample_size} mode={shared.opts.cuda_compile_backend}") import torch._dynamo # pylint: disable=unused-import,redefined-outer-name - if shared.opts.cuda_compile_backend == "openvino_fx": - from modules.ipex_specific.openvino import openvino_fx log_level = logging.WARNING if shared.opts.cuda_compile_verbose else logging.CRITICAL # pylint: disable=protected-access if hasattr(torch, '_logging'): torch._logging.set_logs(dynamo=log_level, aot=log_level, inductor=log_level) # pylint: disable=protected-access From 209f9a19c64d39b1382d78eb1d9c1ca92dbc481f Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 16 Aug 2023 18:56:50 +0300 Subject: [PATCH 71/72] IPEX fixes --- modules/devices.py | 10 ++-- modules/ipex_specific/__init__.py | 81 +++++++++++++++++++++++++++++-- modules/ipex_specific/hijacks.py | 44 ++++++++++++----- modules/ipex_specific/openvino.py | 7 --- 4 files changed, 113 insertions(+), 29 deletions(-) diff --git a/modules/devices.py b/modules/devices.py index 94f264532..533e4d4ad 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -40,7 +40,7 @@ def get_cuda_device_string(): def get_optimal_device_name(): - if cuda_ok or backend == 'ipex' or backend == 'directml': + if cuda_ok or backend == 'directml': return get_cuda_device_string() if has_mps(): return "mps" @@ -76,7 +76,7 @@ def torch_gc(force=False): if shared.opts.disable_gc and not force: return collected = gc.collect() - if cuda_ok or backend == 'ipex': + if cuda_ok: try: with torch.cuda.device(get_cuda_device_string()): torch.cuda.empty_cache() @@ -182,7 +182,7 @@ elif sys.platform == 'darwin': else: backend = 'cpu' -cuda_ok = torch.cuda.is_available() and not backend == 'ipex' +cuda_ok = torch.cuda.is_available() cpu = torch.device("cpu") device = device_interrogate = device_gfpgan = device_esrgan = device_codeformer = None dtype = torch.float16 @@ -221,8 +221,6 @@ def autocast(disable=False): return contextlib.nullcontext() if shared.cmd_opts.use_directml: return torch.dml.amp.autocast(dtype) - if backend == 'ipex': - return torch.xpu.amp.autocast(enabled=True, dtype=dtype) if cuda_ok: return torch.autocast("cuda") else: @@ -234,8 +232,6 @@ def without_autocast(disable=False): return contextlib.nullcontext() if shared.cmd_opts.use_directml: return torch.dml.amp.autocast(enabled=False) if torch.is_autocast_enabled() else contextlib.nullcontext() # pylint: disable=unexpected-keyword-arg - if backend == 'ipex': - return torch.xpu.amp.autocast(enabled=False) if torch.is_autocast_enabled() else contextlib.nullcontext() if cuda_ok: return torch.autocast("cuda", enabled=False) if torch.is_autocast_enabled() else contextlib.nullcontext() else: diff --git a/modules/ipex_specific/__init__.py b/modules/ipex_specific/__init__.py index 7de07d78b..b90328668 100644 --- a/modules/ipex_specific/__init__.py +++ b/modules/ipex_specific/__init__.py @@ -38,8 +38,71 @@ def ipex_init(): torch.cuda.FloatTensor = torch.xpu.FloatTensor torch.Tensor.cuda = torch.Tensor.xpu torch.Tensor.is_cuda = torch.Tensor.is_xpu + torch.cuda._initialization_lock = torch.xpu.lazy_init._initialization_lock + torch.cuda._initialized = torch.xpu.lazy_init._initialized + torch.cuda._lazy_seed_tracker = torch.xpu.lazy_init._lazy_seed_tracker + torch.cuda._queued_calls = torch.xpu.lazy_init._queued_calls + torch.cuda._tls = torch.xpu.lazy_init._tls + torch.cuda.threading = torch.xpu.lazy_init.threading + torch.cuda.traceback = torch.xpu.lazy_init.traceback + torch.cuda.Optional = torch.xpu.Optional + torch.cuda.__cached__ = torch.xpu.__cached__ + torch.cuda.__loader__ = torch.xpu.__loader__ + torch.cuda.ComplexFloatStorage = torch.xpu.ComplexFloatStorage + torch.cuda.Tuple = torch.xpu.Tuple + torch.cuda.streams = torch.xpu.streams + torch.cuda._lazy_new = torch.xpu._lazy_new + torch.cuda.FloatStorage = torch.xpu.FloatStorage + torch.cuda.Any = torch.xpu.Any + torch.cuda.__doc__ = torch.xpu.__doc__ + torch.cuda.default_generators = torch.xpu.default_generators + torch.cuda.HalfTensor = torch.xpu.HalfTensor + torch.cuda._get_device_index = torch.xpu._get_device_index + torch.cuda.__path__ = torch.xpu.__path__ + torch.cuda.Device = torch.xpu.Device + torch.cuda.IntTensor = torch.xpu.IntTensor + torch.cuda.ByteStorage = torch.xpu.ByteStorage + torch.cuda.set_stream = torch.xpu.set_stream + torch.cuda.BoolStorage = torch.xpu.BoolStorage + torch.cuda.get_device_capability = torch.xpu.get_device_capability + torch.cuda.os = torch.xpu.os + torch.cuda.torch = torch.xpu.torch + torch.cuda.BFloat16Storage = torch.xpu.BFloat16Storage + torch.cuda.Union = torch.xpu.Union + torch.cuda.DoubleTensor = torch.xpu.DoubleTensor + torch.cuda.ShortTensor = torch.xpu.ShortTensor + torch.cuda.LongTensor = torch.xpu.LongTensor + torch.cuda.IntStorage = torch.xpu.IntStorage + torch.cuda.LongStorage = torch.xpu.LongStorage + torch.cuda.__annotations__ = torch.xpu.__annotations__ + torch.cuda.__package__ = torch.xpu.__package__ + torch.cuda.__builtins__ = torch.xpu.__builtins__ + torch.cuda.CharTensor = torch.xpu.CharTensor + torch.cuda.List = torch.xpu.List + torch.cuda._lazy_init = torch.xpu._lazy_init + torch.cuda.BFloat16Tensor = torch.xpu.BFloat16Tensor + torch.cuda.DoubleStorage = torch.xpu.DoubleStorage + torch.cuda.ByteTensor = torch.xpu.ByteTensor + torch.cuda.StreamContext = torch.xpu.StreamContext + torch.cuda.ComplexDoubleStorage = torch.xpu.ComplexDoubleStorage + torch.cuda.ShortStorage = torch.xpu.ShortStorage + torch.cuda._lazy_call = torch.xpu._lazy_call + torch.cuda.HalfStorage = torch.xpu.HalfStorage + torch.cuda.random = torch.xpu.random + torch.cuda._device = torch.xpu._device + torch.cuda.classproperty = torch.xpu.classproperty + torch.cuda.__name__ = torch.xpu.__name__ + torch.cuda._device_t = torch.xpu._device_t + torch.cuda.warnings = torch.xpu.warnings + torch.cuda.__spec__ = torch.xpu.__spec__ + torch.cuda.BoolTensor = torch.xpu.BoolTensor + torch.cuda.CharStorage = torch.xpu.CharStorage + torch.cuda.__file__ = torch.xpu.__file__ + torch.cuda._is_in_bad_fork = torch.xpu.lazy_init._is_in_bad_fork + #torch.cuda.is_current_stream_capturing = torch.xpu.is_current_stream_capturing #Memory: + torch.cuda.memory = torch.xpu.memory if 'linux' in sys.platform and "WSL2" in os.popen("uname -a").read(): torch.xpu.empty_cache = lambda: None torch.cuda.empty_cache = torch.xpu.empty_cache @@ -49,8 +112,12 @@ def ipex_init(): torch.cuda.memory_allocated = torch.xpu.memory_allocated torch.cuda.max_memory_allocated = torch.xpu.max_memory_allocated torch.cuda.memory_reserved = torch.xpu.memory_reserved + torch.cuda.memory_cached = torch.xpu.memory_reserved torch.cuda.max_memory_reserved = torch.xpu.max_memory_reserved + torch.cuda.max_memory_cached = torch.xpu.max_memory_reserved torch.cuda.reset_peak_memory_stats = torch.xpu.reset_peak_memory_stats + torch.cuda.reset_max_memory_cached = torch.xpu.reset_peak_memory_stats + torch.cuda.reset_max_memory_allocated = torch.xpu.reset_peak_memory_stats torch.cuda.memory_stats_as_nested_dict = torch.xpu.memory_stats_as_nested_dict torch.cuda.reset_accumulated_memory_stats = torch.xpu.reset_accumulated_memory_stats @@ -65,7 +132,11 @@ def ipex_init(): torch.cuda.seed_all = torch.xpu.seed_all torch.cuda.initial_seed = torch.xpu.initial_seed - #Training: + #AMP: + torch.cuda.amp = torch.xpu.amp + if not hasattr(torch.cuda.amp, "common"): + torch.cuda.amp.common = contextlib.nullcontext() + torch.cuda.amp.common.amp_definitely_not_available = lambda: False try: torch.cuda.amp.GradScaler = torch.xpu.amp.GradScaler except Exception: @@ -79,8 +150,12 @@ 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.cuda.get_device_properties.major = 2023 - torch.cuda.get_device_properties.minor = 2 + torch.has_cuda = True + torch.cuda.has_half = True + torch.cuda.is_bf16_supported = True + torch.version.cuda = "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 diff --git a/modules/ipex_specific/hijacks.py b/modules/ipex_specific/hijacks.py index 05419f426..400afba77 100644 --- a/modules/ipex_specific/hijacks.py +++ b/modules/ipex_specific/hijacks.py @@ -1,6 +1,6 @@ import torch import intel_extension_for_pytorch as ipex -from modules import shared +from modules import devices from modules.sd_hijack_utils import CondFunc def ipex_no_cuda(orig_func, *args, **kwargs): # pylint: disable=redefined-outer-name @@ -8,7 +8,18 @@ def ipex_no_cuda(orig_func, *args, **kwargs): # pylint: disable=redefined-outer- orig_func(*args, **kwargs) torch.cuda.is_available = torch.xpu.is_available -#FP32: +#Autocast +original_autocast = torch.autocast +def ipex_autocast(*args, **kwargs): + if args[0] == "cuda": + if "dtype" in kwargs: + return original_autocast("xpu", *args[1:], **kwargs) + else: + return original_autocast("xpu", *args[1:], dtype=devices.dtype, **kwargs) + else: + return original_autocast(*args, **kwargs) + +#Diffusers BF16: original_linear_forward = torch.nn.modules.Linear.forward def linear_forward(self, input): if input.dtype != self.weight.data.dtype: @@ -37,7 +48,7 @@ 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"), size=size, scale_factor=scale_factor, mode=mode, - align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias).to(shared.device) + align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias).to(devices.device) else: return original_interpolate(input, size=size, scale_factor=scale_factor, mode=mode, align_corners=align_corners, recompute_scale_factor=recompute_scale_factor, antialias=antialias) @@ -46,18 +57,25 @@ def ipex_hijacks(): #Libraries that blindly uses cuda: #Adetailer: CondFunc('torch.Tensor.to', - lambda orig_func, self, device=None, *args, **kwargs: orig_func(self, shared.device, *args, **kwargs), + lambda orig_func, self, device=None, *args, **kwargs: orig_func(self, devices.device, *args, **kwargs), lambda orig_func, self, device=None, *args, **kwargs: (type(device) is torch.device and device.type == "cuda") or (type(device) is str and "cuda" in device)) CondFunc('torch.empty', - lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=shared.device, **kwargs), + lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs), lambda orig_func, *args, device=None, **kwargs: (type(device) is torch.device and device.type == "cuda") or (type(device) is str and "cuda" in device)) #ControlNet depth_leres CondFunc('torch.load', - lambda orig_func, *args, map_location=None, **kwargs: orig_func(*args, shared.device, **kwargs), + lambda orig_func, *args, map_location=None, **kwargs: orig_func(*args, devices.device, **kwargs), lambda orig_func, *args, map_location=None, **kwargs: (map_location is None) or (type(map_location) is torch.device and map_location.type == "cuda") or (type(map_location) is str and "cuda" in map_location)) #Diffusers Model CPU Offload: CondFunc('torch.randn', - lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=shared.device, **kwargs), + lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs), + lambda orig_func, *args, device=None, **kwargs: (type(device) is torch.device and device.type == "cuda") or (type(device) is str and "cuda" in device)) + #Other: + CondFunc('torch.ones', + lambda orig_func, *args, device=None, **kwargs: orig_func(*args, device=devices.device, **kwargs), + lambda orig_func, *args, device=None, **kwargs: (type(device) is torch.device and device.type == "cuda") or (type(device) is str and "cuda" in 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: (type(device) is torch.device and device.type == "cuda") or (type(device) is str and "cuda" in device)) #Broken functions when torch.cuda.is_available is True: @@ -67,6 +85,7 @@ def ipex_hijacks(): lambda orig_func, *args, **kwargs: True) #Functions with dtype errors: + #Original backend: CondFunc('torch.nn.modules.GroupNorm.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) @@ -84,7 +103,7 @@ def ipex_hijacks(): #Functions that does not work with the XPU: #UniPC: CondFunc('torch.linalg.solve', - lambda orig_func, A, B, *args, **kwargs: orig_func(A.to("cpu"), B.to("cpu"), *args, **kwargs).to(shared.device), + 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")) #SDE Samplers: CondFunc('torch.Generator', @@ -98,17 +117,18 @@ def ipex_hijacks(): #ControlNet and TiledVAE: 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=shared.device), - bias if bias is not None else torch.zeros(input.size()[1], device=shared.device), *args, **kwargs), + 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), lambda orig_func, input, *args, **kwargs: input.device != torch.device("cpu")) #ControlNet 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=shared.device), - bias if bias is not None else torch.zeros(input.size()[1], device=shared.device), *args, **kwargs), + 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), lambda orig_func, input, *args, **kwargs: input.device != torch.device("cpu")) #Functions that make compile mad with CondFunc: + torch.autocast = ipex_autocast torch.nn.modules.Linear.forward = linear_forward torch.cat = torch_cat torch.nn.functional.conv2d = conv2d diff --git a/modules/ipex_specific/openvino.py b/modules/ipex_specific/openvino.py index 917d3231f..9674ae5a4 100644 --- a/modules/ipex_specific/openvino.py +++ b/modules/ipex_specific/openvino.py @@ -7,13 +7,6 @@ from torch._dynamo.backends.common import fake_tensor_unsupported from torch._dynamo.backends.registry import register_backend from torch.fx.experimental.proxy_tensor import make_fx -class ModelState: - def __init__(self): - self.recompile = 1 - self.partition_id = 0 - -model_state = ModelState() - @register_backend @fake_tensor_unsupported def openvino_fx(subgraph, example_inputs): From 2caee04b9a1a45b2109455eb186db820be03e8fd Mon Sep 17 00:00:00 2001 From: Disty0 Date: Wed, 16 Aug 2023 19:45:13 +0300 Subject: [PATCH 72/72] IPEX fix System Info --- modules/ipex_specific/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ipex_specific/__init__.py b/modules/ipex_specific/__init__.py index b90328668..bd3695710 100644 --- a/modules/ipex_specific/__init__.py +++ b/modules/ipex_specific/__init__.py @@ -153,7 +153,7 @@ def ipex_init(): torch.has_cuda = True torch.cuda.has_half = True torch.cuda.is_bf16_supported = True - torch.version.cuda = "11.7" + #torch.version.cuda = "11.7" #Breaks System Info torch.cuda.get_device_properties.major = 11 torch.cuda.get_device_properties.minor = 7 torch.backends.cuda.sdp_kernel = return_null_context