diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index 4647bd7f8..257be050a 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit 4647bd7f86be9d2783a9ba1f38acaa9bcec942d2 +Subproject commit 257be050afd46a21e77cc9fe60a04d30ed5ffbe4 diff --git a/modules/extra_networks.py b/modules/extra_networks.py index a574e8469..b464bd349 100644 --- a/modules/extra_networks.py +++ b/modules/extra_networks.py @@ -102,8 +102,9 @@ def activate(p, extra_network_data, step=0): except Exception as e: errors.display(e, f"Activating network: type={extra_network_name}") + p.extra_network_data = extra_network_data if stepwise: - p.extra_network_data = extra_network_data + p.stepwise_lora = True shared.opts.data['lora_functional'] = functional diff --git a/modules/face/instantid.py b/modules/face/instantid.py index 9c7c16f61..662d17c7d 100644 --- a/modules/face/instantid.py +++ b/modules/face/instantid.py @@ -68,7 +68,7 @@ def instant_id(p: processing.StableDiffusionProcessing, app, source_images, stre processing.process_init(p) p.init(p.all_prompts, p.all_seeds, p.all_subseeds) orig_prompt_attention = shared.opts.prompt_attention - shared.opts.data['prompt_attention'] = 'Fixed attention' # otherwise need to deal with class_tokens_mask + shared.opts.data['prompt_attention'] = 'fixed' # otherwise need to deal with class_tokens_mask p.task_args['image_embeds'] = face_embeds[0].shape # placeholder p.task_args['image'] = face_images[0] p.task_args['controlnet_conditioning_scale'] = float(conditioning) diff --git a/modules/face/photomaker.py b/modules/face/photomaker.py index c8f58b42a..b89f28a10 100644 --- a/modules/face/photomaker.py +++ b/modules/face/photomaker.py @@ -49,7 +49,7 @@ def photo_maker(p: processing.StableDiffusionProcessing, input_images, trigger, shared.sd_model.to(dtype=devices.dtype) orig_prompt_attention = shared.opts.prompt_attention - shared.opts.data['prompt_attention'] = 'Fixed attention' # otherwise need to deal with class_tokens_mask + shared.opts.data['prompt_attention'] = 'fixed' # otherwise need to deal with class_tokens_mask p.task_args['input_id_images'] = input_images p.task_args['start_merge_step'] = int(start * p.steps) p.task_args['prompt'] = p.all_prompts[0] if p.all_prompts is not None else p.prompt diff --git a/modules/processing_args.py b/modules/processing_args.py index cc8d39c11..5b320f9ea 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -107,56 +107,57 @@ def set_pipeline_args(p, model, prompts: list, negative_prompts: list, prompts_2 debug(f'Diffusers pipeline possible: {possible}') prompts, negative_prompts, prompts_2, negative_prompts_2 = fix_prompts(prompts, negative_prompts, prompts_2, negative_prompts_2) - parser = 'Fixed attention' steps = kwargs.get("num_inference_steps", None) or len(getattr(p, 'timesteps', ['1'])) clip_skip = kwargs.pop("clip_skip", 1) - # prompt_parser_diffusers.fix_position_ids(model) - if shared.opts.prompt_attention != 'Fixed attention' and 'Onnx' not in model.__class__.__name__ and ( + parser = 'fixed' + if shared.opts.prompt_attention != 'fixed' and 'Onnx' not in model.__class__.__name__ and ( 'StableDiffusion' in model.__class__.__name__ or 'StableCascade' in model.__class__.__name__ or 'Flux' in model.__class__.__name__ ): try: - prompt_parser_diffusers.encode_prompts(model, p, prompts, negative_prompts, steps=steps, clip_skip=clip_skip) + prompt_parser_diffusers.embedder = prompt_parser_diffusers.PromptEmbedder(prompts, negative_prompts, steps, clip_skip, p) parser = shared.opts.prompt_attention except Exception as e: shared.log.error(f'Prompt parser encode: {e}') if os.environ.get('SD_PROMPT_DEBUG', None) is not None: errors.display(e, 'Prompt parser encode') timer.process.record('encode', reset=False) + else: + prompt_parser_diffusers.embedder = None if 'prompt' in possible: if 'OmniGen' in model.__class__.__name__: prompts = [p.replace('|image|', '<|image_1|>') for p in prompts] - if hasattr(model, 'text_encoder') and hasattr(model, 'tokenizer') and 'prompt_embeds' in possible and len(p.prompt_embeds) > 0 and p.prompt_embeds[0] is not None: - args['prompt_embeds'] = p.prompt_embeds[0] + if hasattr(model, 'text_encoder') and hasattr(model, 'tokenizer') and 'prompt_embeds' in possible and prompt_parser_diffusers.embedder is not None: + args['prompt_embeds'] = prompt_parser_diffusers.embedder('prompt_embeds') if 'StableCascade' in model.__class__.__name__ and len(getattr(p, 'negative_pooleds', [])) > 0: - args['prompt_embeds_pooled'] = p.positive_pooleds[0].unsqueeze(0) - elif 'XL' in model.__class__.__name__ and len(getattr(p, 'positive_pooleds', [])) > 0: - args['pooled_prompt_embeds'] = p.positive_pooleds[0] - elif 'StableDiffusion3' in model.__class__.__name__ and len(getattr(p, 'positive_pooleds', [])) > 0: - args['pooled_prompt_embeds'] = p.positive_pooleds[0] - elif 'Flux' in model.__class__.__name__ and len(getattr(p, 'positive_pooleds', [])) > 0: - args['pooled_prompt_embeds'] = p.positive_pooleds[0] + args['prompt_embeds_pooled'] = prompt_parser_diffusers.embedder('positive_pooleds').unsqueeze(0) + elif 'XL' in model.__class__.__name__ and prompt_parser_diffusers.embedder is not None: + args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') + elif 'StableDiffusion3' in model.__class__.__name__ and prompt_parser_diffusers.embedder is not None: + args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') + elif 'Flux' in model.__class__.__name__ and prompt_parser_diffusers.embedder is not None: + args['pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('positive_pooleds') else: args['prompt'] = prompts if 'negative_prompt' in possible: - if hasattr(model, 'text_encoder') and hasattr(model, 'tokenizer') and 'negative_prompt_embeds' in possible and len(p.negative_embeds) > 0 and p.negative_embeds[0] is not None: - args['negative_prompt_embeds'] = p.negative_embeds[0] - if 'StableCascade' in model.__class__.__name__ and len(getattr(p, 'negative_pooleds', [])) > 0: - args['negative_prompt_embeds_pooled'] = p.negative_pooleds[0].unsqueeze(0) - if 'XL' in model.__class__.__name__ and len(getattr(p, 'negative_pooleds', [])) > 0: - args['negative_pooled_prompt_embeds'] = p.negative_pooleds[0] - if 'StableDiffusion3' in model.__class__.__name__ and len(getattr(p, 'negative_pooleds', [])) > 0: - args['negative_pooled_prompt_embeds'] = p.negative_pooleds[0] + if hasattr(model, 'text_encoder') and hasattr(model, 'tokenizer') and 'negative_prompt_embeds' in possible and prompt_parser_diffusers.embedder is not None: + args['negative_prompt_embeds'] = prompt_parser_diffusers.embedder('negative_prompt_embeds') + if 'StableCascade' in model.__class__.__name__ and prompt_parser_diffusers.embedder is not None: + args['negative_prompt_embeds_pooled'] = prompt_parser_diffusers.embedder('negative_pooleds').unsqueeze(0) + if 'XL' in model.__class__.__name__ and prompt_parser_diffusers.embedder is not None: + args['negative_pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('negative_pooleds') + if 'StableDiffusion3' in model.__class__.__name__ and prompt_parser_diffusers.embedder is not None: + args['negative_pooled_prompt_embeds'] = prompt_parser_diffusers.embedder('negative_pooleds') else: if 'PixArtSigmaPipeline' in model.__class__.__name__: # pixart-sigma pipeline throws list-of-list for negative prompt args['negative_prompt'] = negative_prompts[0] else: args['negative_prompt'] = negative_prompts - if 'clip_skip' in possible and parser == 'Fixed attention': + if 'clip_skip' in possible and parser == 'fixed': if clip_skip == 1: pass # clip_skip = None else: diff --git a/modules/processing_callbacks.py b/modules/processing_callbacks.py index 45a0724fb..52ea3e575 100644 --- a/modules/processing_callbacks.py +++ b/modules/processing_callbacks.py @@ -3,8 +3,7 @@ import os import time import torch import numpy as np -from modules import shared, processing_correction, extra_networks, timer - +from modules import shared, processing_correction, extra_networks, timer, prompt_parser_diffusers p = None debug_callback = shared.log.trace if os.environ.get('SD_CALLBACK_DEBUG', None) is not None else lambda *args, **kwargs: None @@ -14,6 +13,19 @@ def set_callbacks_p(processing): global p # pylint: disable=global-statement p = processing +def prompt_callback(step, kwargs): + if prompt_parser_diffusers.embedder is None or 'prompt_embeds' not in kwargs: + return kwargs + try: + prompt_embeds = prompt_parser_diffusers.embedder('prompt_embeds', step + 1) + negative_prompt_embeds = prompt_parser_diffusers.embedder('negative_prompt_embeds', step + 1) + if p.cfg_scale > 1: # Perform guidance + prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0) # Combined embeds + assert prompt_embeds.shape == kwargs['prompt_embeds'].shape, f"prompt_embed shape mismatch {kwargs['prompt_embeds'].shape} {prompt_embeds.shape}" + kwargs['prompt_embeds'] = prompt_embeds + except Exception as e: + debug_callback(f"Callback: {e}") + return kwargs def diffusers_callback_legacy(step: int, timestep: int, latents: typing.Union[torch.FloatTensor, np.ndarray]): if p is None: @@ -49,7 +61,7 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {} if shared.state.interrupted or shared.state.skipped: raise AssertionError('Interrupted...') time.sleep(0.1) - if hasattr(p, "extra_network_data"): + if hasattr(p, "stepwise_lora"): extra_networks.activate(p, p.extra_network_data, step=step) if latents is None: return kwargs @@ -67,14 +79,7 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {} pipe.set_ip_adapter_scale(ip_adapter_scales) if step != getattr(pipe, 'num_timesteps', 0): kwargs = processing_correction.correction_callback(p, timestep, kwargs) - if p.scheduled_prompt and 'prompt_embeds' in kwargs and 'negative_prompt_embeds' in kwargs: - try: - i = (step + 1) % len(p.prompt_embeds) - kwargs["prompt_embeds"] = p.prompt_embeds[i][0:1].expand(kwargs["prompt_embeds"].shape) - j = (step + 1) % len(p.negative_embeds) - kwargs["negative_prompt_embeds"] = p.negative_embeds[j][0:1].expand(kwargs["negative_prompt_embeds"].shape) - except Exception as e: - shared.log.debug(f"Callback: {e}") + kwargs = prompt_callback(step, kwargs) # monkey patch for diffusers callback issues if step == int(getattr(pipe, 'num_timesteps', 100) * p.cfg_end) and 'prompt_embeds' in kwargs and 'negative_prompt_embeds' in kwargs: if "PAG" in shared.sd_model.__class__.__name__: pipe._guidance_scale = 1.001 if pipe._guidance_scale > 1 else pipe._guidance_scale # pylint: disable=protected-access diff --git a/modules/processing_class.py b/modules/processing_class.py index 37cb3d72a..333e11925 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -281,6 +281,46 @@ class StableDiffusionProcessing: self.s_max = shared.opts.s_max self.s_tmin = shared.opts.s_tmin self.s_tmax = float('inf') # not representable as a standard ui option + self.task_args = {} + # a1111 compatibility items + self.batch_index = 0 + self.refiner_switch_at = 0 + self.hr_prompt = '' + self.all_hr_prompts = [] + self.hr_negative_prompt = '' + self.all_hr_negative_prompts = [] + self.comments = {} + self.is_api = False + self.scripts_value: scripts.ScriptRunner = field(default=None, init=False) + self.script_args_value: list = field(default=None, init=False) + self.scripts_setup_complete: bool = field(default=False, init=False) + # ip adapter + self.ip_adapter_names = [] + self.ip_adapter_scales = [0.0] + self.ip_adapter_images = [] + self.ip_adapter_starts = [0.0] + self.ip_adapter_ends = [1.0] + self.ip_adapter_crops = [] + # hdr + self.hdr_mode=hdr_mode + self.hdr_brightness=hdr_brightness + self.hdr_color=hdr_color + self.hdr_sharpen=hdr_sharpen + self.hdr_clamp=hdr_clamp + self.hdr_boundary=hdr_boundary + self.hdr_threshold=hdr_threshold + self.hdr_maximize=hdr_maximize + self.hdr_max_center=hdr_max_center + self.hdr_max_boundry=hdr_max_boundry + self.hdr_color_picker=hdr_color_picker + self.hdr_tint_ratio=hdr_tint_ratio + # globals + self.embedder = None + self.scheduled_prompt: bool = False + self.prompt_embeds = [] + self.positive_pooleds = [] + self.negative_embeds = [] + self.negative_pooleds = [] @property def sd_model(self): diff --git a/modules/processing_info.py b/modules/processing_info.py index 7d16dd956..714ebf35f 100644 --- a/modules/processing_info.py +++ b/modules/processing_info.py @@ -4,6 +4,7 @@ from modules import shared, sd_samplers_common, sd_vae, generation_parameters_co from modules.processing_class import StableDiffusionProcessing +debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None if not shared.native: from modules import sd_hijack else: @@ -39,25 +40,27 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No ops.reverse() args = { # basic + "Size": f"{p.width}x{p.height}" if hasattr(p, 'width') and hasattr(p, 'height') else None, + "Sampler": p.sampler_name if p.sampler_name != 'Default' else None, "Steps": p.steps, "Seed": all_seeds[index], - "Sampler": p.sampler_name if p.sampler_name != 'Default' else None, + "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}", "CFG scale": p.cfg_scale if p.cfg_scale > 1.0 else None, "CFG end": p.cfg_end if p.cfg_end < 1.0 else None, - "Size": f"{p.width}x{p.height}" if hasattr(p, 'width') and hasattr(p, 'height') else None, - "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.split()[0], - "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', - "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}", "Clip skip": p.clip_skip if p.clip_skip > 1 else None, + "Batch": f'{p.n_iter}x{p.batch_size}' if p.n_iter > 1 or p.batch_size > 1 else None, + "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), + "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', "Prompt2": p.refiner_prompt if len(p.refiner_prompt) > 0 else None, "Negative2": p.refiner_negative if len(p.refiner_negative) > 0 else None, "Styles": "; ".join(p.styles) if p.styles is not None and len(p.styles) > 0 else None, - "Tiling": p.tiling if p.tiling else None, # sdnext - "Backend": 'Diffusers' if shared.native else 'Original', "App": 'SD.Next', "Version": git_commit, + "Backend": 'Diffusers' if shared.native else 'Original', + "Pipeline": 'LDM', + "Parser": shared.opts.prompt_attention.split()[0], "Comment": comment, "Operations": '; '.join(ops).replace('"', '') if len(p.ops) > 0 else 'none', } @@ -167,7 +170,9 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No if isinstance(v, str): if len(v) == 0 or v == '0x0': del args[k] + debug(f'Infotext: args={args}') params_text = ", ".join([k if k == v else f'{k}: {generation_parameters_copypaste.quote(v)}' for k, v in args.items()]) negative_prompt_text = f"\nNegative prompt: {all_negative_prompts[index]}" if all_negative_prompts[index] else "" infotext = f"{all_prompts[index]}{negative_prompt_text}\n{params_text}".strip() + debug(f'Infotext: "{infotext}"') return infotext diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index 2a71d5053..3a1288097 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -308,11 +308,11 @@ def parse_prompt_attention(text): res = [] round_brackets = [] square_brackets = [] - if opts.prompt_attention == 'Fixed attention': + if opts.prompt_attention == 'fixed': res = [[text, 1.0]] debug(f'Prompt: parser="{opts.prompt_attention}" {res}') return res - elif opts.prompt_attention == 'Compel parser': + elif opts.prompt_attention == 'compel': conjunction = Compel.parse_prompt_string(text) if conjunction is None or conjunction.prompts is None or conjunction.prompts is None or len(conjunction.prompts[0].children) == 0: return [["", 1.0]] @@ -321,7 +321,7 @@ def parse_prompt_attention(text): res.append([frag.text, frag.weight]) debug(f'Prompt: parser="{opts.prompt_attention}" {res}') return res - elif opts.prompt_attention == 'A1111 parser': + elif opts.prompt_attention == 'a1111': re_attention = re_attention_v1 whitespace = '' else: @@ -360,7 +360,7 @@ def parse_prompt_attention(text): for i, part in enumerate(parts): if i > 0: res.append(["BREAK", -1]) - if opts.prompt_attention == 'Full parser': + if opts.prompt_attention == 'native': part = re_clean.sub("", part) part = re_whitespace.sub(" ", part).strip() if len(part) == 0: @@ -392,15 +392,15 @@ if __name__ == "__main__": log.info(f'Schedules: {all_schedules}') for schedule in all_schedules: log.info(f'Schedule: {schedule[0]}') - opts.data['prompt_attention'] = 'Fixed attention' + opts.data['prompt_attention'] = 'fixed' output_list = parse_prompt_attention(schedule[1]) log.info(f' Fixed: {output_list}') - opts.data['prompt_attention'] = 'Compel parser' + opts.data['prompt_attention'] = 'compel' output_list = parse_prompt_attention(schedule[1]) log.info(f' Compel: {output_list}') - opts.data['prompt_attention'] = 'A1111 parser' + opts.data['prompt_attention'] = 'a1111' output_list = parse_prompt_attention(schedule[1]) log.info(f' A1111: {output_list}') - opts.data['prompt_attention'] = 'Full parser' + opts.data['prompt_attention'] = 'native' log.info = parse_prompt_attention(schedule[1]) log.info(f' Full: {output_list}') diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 2994d6ef5..e53af7957 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -2,6 +2,7 @@ import os import math import time import typing +from collections import OrderedDict import torch from compel.embeddings_provider import BaseTextualInversionManager, EmbeddingsProvider from transformers import PreTrainedTokenizer @@ -14,7 +15,174 @@ debug('Trace: PROMPT') orig_encode_token_ids_to_embeddings = EmbeddingsProvider._encode_token_ids_to_embeddings # pylint: disable=protected-access token_dict = None # used by helper get_tokens token_type = None # used by helper get_tokens -cache = {} +cache = OrderedDict() +embedder = None + + +def prompt_compatible(): + if ( + 'StableDiffusion' not in shared.sd_model.__class__.__name__ and + 'DemoFusion' not in shared.sd_model.__class__.__name__ and + 'StableCascade' not in shared.sd_model.__class__.__name__ and + 'Flux' not in shared.sd_model.__class__.__name__ + ): + shared.log.warning(f"Prompt parser not supported: {shared.sd_model.__class__.__name__}") + return False + return True + + +def prepare_model(): + pipe = shared.sd_model + if shared.opts.diffusers_offload_mode == "balanced": + pipe = sd_models.apply_balanced_offload(pipe) + elif hasattr(pipe, "maybe_free_model_hooks"): + pipe.maybe_free_model_hooks() + devices.torch_gc() + return pipe + + +class PromptEmbedder: + def __init__(self, prompts, negative_prompts, steps, clip_skip, p): + t0 = time.time() + self.prompts = prompts + self.negative_prompts = negative_prompts + self.batchsize = len(self.prompts) + self.attention = None + self.allsame = self.compare_prompts() # collapses batched prompts to single prompt if possible + self.steps = steps + self.clip_skip = clip_skip + # All embeds are nested lists, outer list batch length, inner schedule length + self.prompt_embeds = [[]] * self.batchsize + self.positive_pooleds = [[]] * self.batchsize + self.negative_prompt_embeds = [[]] * self.batchsize + self.negative_pooleds = [[]] * self.batchsize + self.positive_schedule = None + self.negative_schedule = None + self.scheduled_prompt = False + earlyout = self.checkcache(p) + if earlyout: + return + pipe = prepare_model() + # per prompt in batch + for batchidx, (prompt, negative_prompt) in enumerate(zip(self.prompts, self.negative_prompts)): + self.prepare_schedule(prompt, negative_prompt) + if self.scheduled_prompt: + self.scheduled_encode(pipe, batchidx) + else: + self.encode(pipe, prompt, negative_prompt, batchidx) + self.checkcache(p) + debug(f"Prompt encode: time={(time.time() - t0):.3f}") + + def checkcache(self, p): + if shared.opts.sd_textencoder_cache_size == 0: + return False + if self.attention != shared.opts.prompt_attention: + debug(f"Prompt change: parser={shared.opts.prompt_attention}") + cache.clear() + return False + + def flatten(xss): + return [x for xs in xss for x in xs] + + # unpack EN data in case of TE LoRA + en_data = p.extra_network_data + en_data = [idx.items for item in en_data.values() for idx in item] + effective_batch = 1 if self.allsame else self.batchsize + key = str([self.prompts, self.negative_prompts, effective_batch, self.clip_skip, self.steps, en_data]) + item = cache.get(key) + if not item: + if not any(flatten(emb) for emb in [self.prompt_embeds, + self.negative_prompt_embeds, + self.positive_pooleds, + self.negative_pooleds]): + return False + else: + cache[key] = {'prompt_embeds': self.prompt_embeds, + 'negative_prompt_embeds': self.negative_prompt_embeds, + 'positive_pooleds': self.positive_pooleds, + 'negative_pooleds': self.negative_pooleds, + } + debug(f"Prompt cache: add={key}") + while len(cache) > int(shared.opts.sd_textencoder_cache_size): + cache.popitem(last=False) + if item: + self.__dict__.update(cache[key]) + cache.move_to_end(key) + if self.allsame and len(self.prompt_embeds) < self.batchsize: + self.prompt_embeds = [self.prompt_embeds[0]] * self.batchsize + self.positive_pooleds = [self.positive_pooleds[0]] * self.batchsize + self.negative_prompt_embeds = [self.negative_prompt_embeds[0]] * self.batchsize + self.negative_pooleds = [self.negative_pooleds[0]] * self.batchsize + debug(f"Prompt cache: get={key}") + return True + + def compare_prompts(self): + same = (self.prompts == [self.prompts[0]] * len(self.prompts) and self.negative_prompts == [self.negative_prompts[0]] * len(self.negative_prompts)) + if same: + self.prompts = [self.prompts[0]] + self.negative_prompts = [self.negative_prompts[0]] + return same + + def prepare_schedule(self, prompt, negative_prompt): + self.positive_schedule, scheduled = get_prompt_schedule(prompt, self.steps) + self.negative_schedule, neg_scheduled = get_prompt_schedule(negative_prompt, self.steps) + self.scheduled_prompt = scheduled or neg_scheduled + debug(f"Prompt schedule: positive={self.positive_schedule} negative={self.negative_schedule} scheduled={scheduled}") + + def scheduled_encode(self, pipe, batchidx): + prompt_dict = {} # index cache + for i in range(max(len(self.positive_schedule), len(self.negative_schedule))): + positive_prompt = self.positive_schedule[i % len(self.positive_schedule)] + negative_prompt = self.negative_schedule[i % len(self.negative_schedule)] + # skip repeated scheduled subprompts + idx = prompt_dict.get(positive_prompt+negative_prompt) + if idx is not None: + self.extend_embeds(batchidx, idx) + continue + self.encode(pipe, positive_prompt, negative_prompt, batchidx) + prompt_dict[positive_prompt+negative_prompt] = i + + def extend_embeds(self, batchidx, idx): # Extends scheduled prompt via index + if len(self.prompt_embeds[batchidx]) > 0: + self.prompt_embeds[batchidx].append(self.prompt_embeds[batchidx][idx]) + if len(self.negative_prompt_embeds[batchidx]) > 0: + self.negative_prompt_embeds[batchidx].append(self.negative_prompt_embeds[batchidx][idx]) + if len(self.positive_pooleds[batchidx]) > 0: + self.positive_pooleds[batchidx].append(self.positive_pooleds[batchidx][idx]) + if len(self.negative_pooleds[batchidx]) > 0: + self.negative_pooleds[batchidx].append(self.negative_pooleds[batchidx][idx]) + + def encode(self, pipe, positive_prompt, negative_prompt, batchidx): + self.attention = shared.opts.prompt_attention + if self.attention == "xhinker" or 'Flux' in pipe.__class__.__name__: + prompt_embed, positive_pooled, negative_embed, negative_pooled = get_xhinker_text_embeddings(pipe, positive_prompt, negative_prompt, self.clip_skip) + else: + prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, positive_prompt, negative_prompt, self.clip_skip) + if prompt_embed is not None: + self.prompt_embeds[batchidx].append(prompt_embed) + if negative_embed is not None: + self.negative_prompt_embeds[batchidx].append(negative_embed) + if positive_pooled is not None: + self.positive_pooleds[batchidx].append(positive_pooled) + if negative_pooled is not None: + self.negative_pooleds[batchidx].append(negative_pooled) + + if debug_enabled: + get_tokens('positive', positive_prompt) + get_tokens('negative', negative_prompt) + pipe = prepare_model() + + def __call__(self, key, step=0): + batch = getattr(self, key) + res = [] + for i in range(self.batchsize): + if len(batch[i]) == 0: # if asking for a null key, ie pooled on SD1.5 + return None + try: + res.append(batch[i][step]) + except IndexError: + res.append(batch[i][0]) # if not scheduled, return default + return torch.cat(res) def compel_hijack(self, token_ids: torch.Tensor, attention_mask: typing.Optional[torch.Tensor] = None) -> torch.Tensor: @@ -59,9 +227,9 @@ def insert_parser_highjack(pipename): debug("Load Standard Parser hijack") - insert_parser_highjack("Initialize") + # from https://github.com/damian0815/compel/blob/main/src/compel/diffusers_textual_inversion_manager.py class DiffusersTextualInversionManager(BaseTextualInversionManager): def __init__(self, pipe, tokenizer): @@ -108,12 +276,6 @@ class DiffusersTextualInversionManager(BaseTextualInversionManager): def get_prompt_schedule(prompt, steps): t0 = time.time() - if shared.native: - # TODO prompt scheduling - # prompt schedule returns array of prompts which would require that each prompt is fed to the model per-step - # prompt scheduling should instead interpolate between each prompt in schedule - # this temporarily disables prompt scheduling - return [prompt], False temp = [] schedule = prompt_parser.get_learned_conditioning_prompt_schedules([prompt], steps)[0] if all(x == schedule[0] for x in schedule): @@ -155,120 +317,7 @@ def get_tokens(msg, prompt): tokens.append(f'UNK_{i}') token_count = len(ids) - int(has_bos_token) - int(has_eos_token) debug(f'Prompt tokenizer: type={msg} tokens={token_count} {tokens}') - - -def encode_prompts(pipe, p, prompts: list, negative_prompts: list, steps: int, clip_skip: typing.Optional[int] = None): - if not hasattr(pipe, 'text_encoder') or not hasattr(pipe, 'tokenizer'): - return - params_match = prompts == cache.get('prompts', None) and negative_prompts == cache.get('negative_prompts', None) and clip_skip == cache.get('clip_skip', None) and steps == cache.get('steps', None) - if ( - 'StableDiffusion' not in pipe.__class__.__name__ and - 'DemoFusion' not in pipe.__class__.__name__ and - 'StableCascade' not in pipe.__class__.__name__ and - 'Flux' not in pipe.__class__.__name__ - ): - shared.log.warning(f"Prompt parser not supported: {pipe.__class__.__name__}") - return - elif shared.opts.sd_textencoder_cache and cache.get('model_type', None) == shared.sd_model_type and params_match: - p.prompt_embeds = cache.get('prompt_embeds', None) - p.positive_pooleds = cache.get('positive_pooleds', None) - p.negative_embeds = cache.get('negative_embeds', None) - p.negative_pooleds = cache.get('negative_pooleds', None) - p.scheduled_prompt = cache.get('scheduled_prompt', None) - debug("Prompt encode: cached") - return - else: - t0 = time.time() - if shared.opts.diffusers_offload_mode == "balanced": - pipe = sd_models.apply_balanced_offload(pipe) - elif hasattr(pipe, "maybe_free_model_hooks"): - pipe.maybe_free_model_hooks() - devices.torch_gc() - - prompt_embeds, positive_pooleds, negative_embeds, negative_pooleds = [], [], [], [] - last_prompt, last_negative = None, None - for prompt, negative in zip(prompts, negative_prompts): - prompt_embed, positive_pooled, negative_embed, negative_pooled = None, None, None, None - if last_prompt == prompt and last_negative == negative: - prompt_embeds.append(prompt_embeds[-1]) - negative_embeds.append(negative_embeds[-1]) - if len(positive_pooleds) > 0: - positive_pooleds.append(positive_pooleds[-1]) - if len(negative_pooleds) > 0: - negative_pooleds.append(negative_pooleds[-1]) - continue - positive_schedule, scheduled = get_prompt_schedule(prompt, steps) - negative_schedule, neg_scheduled = get_prompt_schedule(negative, steps) - p.scheduled_prompt = scheduled or neg_scheduled - p.prompt_embeds = [] - p.positive_pooleds = [] - p.negative_embeds = [] - p.negative_pooleds = [] - - for i in range(max(len(positive_schedule), len(negative_schedule))): - positive_prompt = positive_schedule[i % len(positive_schedule)] - negative_prompt = negative_schedule[i % len(negative_schedule)] - if shared.opts.prompt_attention == "xhinker parser" or 'Flux' in pipe.__class__.__name__: - prompt_embed, positive_pooled, negative_embed, negative_pooled = get_xhinker_text_embeddings(pipe, positive_prompt, negative_prompt, clip_skip) - else: - prompt_embed, positive_pooled, negative_embed, negative_pooled = get_weighted_text_embeddings(pipe, positive_prompt, negative_prompt, clip_skip) - if prompt_embed is not None: - prompt_embeds.append(prompt_embed) - if negative_embed is not None: - negative_embeds.append(negative_embed) - if positive_pooled is not None: - positive_pooleds.append(positive_pooled) - if negative_pooled is not None: - negative_pooleds.append(negative_pooled) - last_prompt, last_negative = prompt, negative - # TODO prompt scheduling - # interpolation should happen here and then we can re-enable prompt scheduling - # ive tried simple torch.mean and its not good-enough - - def fix_length(embeds): - max_len = max([e.shape[1] for e in embeds if e is not None]) - for i, e in enumerate(embeds): - if e is not None and e.shape[1] < max_len: - expanded = torch.zeros((e.shape[0], max_len, e.shape[2]), device=e.device, dtype=e.dtype) - expanded[:, :e.shape[1], :] = e - embeds[i] = expanded - return torch.cat(embeds, dim=0).to(devices.device, dtype=devices.dtype) - - if len(prompt_embeds) > 0: - p.prompt_embeds.append(fix_length(prompt_embeds)) - if len(negative_embeds) > 0: - p.negative_embeds.append(fix_length(negative_embeds)) - if len(positive_pooleds) > 0: - p.positive_pooleds.append(fix_length(positive_pooleds)) - if len(negative_pooleds) > 0: - p.negative_pooleds.append(fix_length(negative_pooleds)) - - if shared.opts.sd_textencoder_cache and p.batch_size == 1: - cache.update({ - 'prompt_embeds': p.prompt_embeds, - 'negative_embeds': p.negative_embeds, - 'positive_pooleds': p.positive_pooleds, - 'negative_pooleds': p.negative_pooleds, - 'scheduled_prompt': p.scheduled_prompt, - 'prompts': prompts, - 'negative_prompts': negative_prompts, - 'clip_skip': clip_skip, - 'steps': steps, - 'model_type': shared.sd_model_type - }) - else: - cache.clear() - if debug_enabled: - get_tokens('positive', prompts[0]) - get_tokens('negative', negative_prompts[0]) - if shared.opts.diffusers_offload_mode == "balanced": - pipe = sd_models.apply_balanced_offload(pipe) - elif hasattr(pipe, "maybe_free_model_hooks"): - # text encoder will stay in the vram and cause oom, send everything back to cpu before continuing - pipe.maybe_free_model_hooks() - debug(f"Prompt encode: time={(time.time() - t0):.3f}") - devices.torch_gc() - return + return token_count def normalize_prompt(pairs: list): @@ -296,6 +345,12 @@ def get_prompts_with_weights(prompt: str): if shared.opts.prompt_mean_norm: texts_and_weights = normalize_prompt(texts_and_weights) texts, text_weights = zip(*texts_and_weights) + if debug_enabled: + all_tokens = 0 + for text in texts: + tokens = get_tokens('section', text) + all_tokens += tokens + debug(f'Prompt tokenizer: parser={shared.opts.prompt_attention} tokens={all_tokens}') debug(f'Prompt: weights={texts_and_weights} time={(time.time() - t0):.3f}') return texts, text_weights @@ -356,6 +411,7 @@ def pad_to_same_length(pipe, embeds, empty_embedding_providers=None): embeds[i] = embed return embeds + def split_prompts(prompt, SD3 = False): if prompt.find("TE2:") != -1: prompt, prompt2 = prompt.split("TE2:") @@ -436,7 +492,7 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c # negative prompt has no keywords embed, ntokens = embedding_providers[i].get_embeddings_for_weighted_prompt_fragments(text_batch=[negatives[i]], fragment_weights_batch=[negative_weights[i]], device=device, should_return_tokens=True) negative_prompt_embeds.append(embed) - debug(f'Prompt: unpadded shape={prompt_embeds[0].shape} TE{i+1} ptokens={torch.count_nonzero(ptokens)} ntokens={torch.count_nonzero(ntokens)} time={(time.time() - t0):.3f}') + debug(f'Prompt: unpadded={prompt_embeds[0].shape} TE{i+1} ptokens={torch.count_nonzero(ptokens)} ntokens={torch.count_nonzero(ntokens)} time={(time.time() - t0):.3f}') if SD3: t0 = time.time() pooled_prompt_embeds.append(embedding_providers[0].get_pooled_embeddings(texts=positives[0] if len(positives[0]) == 1 else [" ".join(positives[0])], device=device)) @@ -445,7 +501,7 @@ def get_weighted_text_embeddings(pipe, prompt: str = "", neg_prompt: str = "", c negative_pooled_prompt_embeds.append(embedding_providers[1].get_pooled_embeddings(texts=negatives[-1] if len(negatives[-1]) == 1 else [" ".join(negatives[-1])], device=device)) pooled_prompt_embeds = torch.cat(pooled_prompt_embeds, dim=-1) negative_pooled_prompt_embeds = torch.cat(negative_pooled_prompt_embeds, dim=-1) - debug(f'Prompt: pooled shape={pooled_prompt_embeds[0].shape} time={(time.time() - t0):.3f}') + debug(f'Prompt: pooled={pooled_prompt_embeds[0].shape} time={(time.time() - t0):.3f}') elif prompt_embeds[-1].shape[-1] > 768: t0 = time.time() if shared.opts.diffusers_pooled == "weighted": diff --git a/modules/prompt_parser_xhinker.py b/modules/prompt_parser_xhinker.py index 6a8acf8c6..c0ddc9bc7 100644 --- a/modules/prompt_parser_xhinker.py +++ b/modules/prompt_parser_xhinker.py @@ -1305,7 +1305,7 @@ def get_weighted_text_embeddings_sd3( # ---------------------- get neg t5 embeddings ------------------------- neg_prompt_tokens_3 = torch.tensor([neg_prompt_tokens_3], dtype=torch.long) - t5_neg_prompt_embeds = pipe.text_encoder_3(neg_prompt_tokens_3.to(pipe.pipe.text_encoder_3.device))[0].squeeze(0) + t5_neg_prompt_embeds = pipe.text_encoder_3(neg_prompt_tokens_3.to(pipe.text_encoder_3.device))[0].squeeze(0) t5_neg_prompt_embeds = t5_neg_prompt_embeds.to(device=pipe.text_encoder_3.device) # add weight to neg t5 embeddings diff --git a/modules/sd_models.py b/modules/sd_models.py index 801c3705d..e139895ea 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -881,8 +881,9 @@ def load_diffuser(checkpoint_info=None, already_loaded_state_dict=None, timer=No sd_model.embedding_db.load_textual_inversion_embeddings(force_reload=True) timer.record("embeddings") - from modules.prompt_parser_diffusers import insert_parser_highjack - insert_parser_highjack(sd_model.__class__.__name__) + from modules import prompt_parser_diffusers + prompt_parser_diffusers.insert_parser_highjack(sd_model.__class__.__name__) + prompt_parser_diffusers.cache.clear() set_diffuser_options(sd_model, vae, op, offload=False) if shared.opts.nncf_compress_weights and not ('Model' in shared.opts.cuda_compile and shared.opts.cuda_compile_backend == "openvino_fx"): diff --git a/modules/shared.py b/modules/shared.py index 52f40f313..7d19c721e 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -273,6 +273,40 @@ class OptionInfo: self.comment_after += " (requires restart)" return self + def validate(self, opt, value): + args = self.component_args if self.component_args is not None else {} + if callable(args): + try: + args = args() + except Exception: + args = {} + choices = args.get("choices", []) + if callable(choices): + try: + choices = choices() + except Exception: + choices = [] + if len(choices) > 0: + if not isinstance(value, list): + value = [value] + for v in value: + if v not in choices: + log.warning(f'Setting validation: "{opt}"="{v}" default="{self.default}" choices={choices}') + return False + minimum = args.get("minimum", None) + maximum = args.get("maximum", None) + if (minimum is not None and value < minimum) or (maximum is not None and value > maximum): + log.error(f'Setting validation: "{opt}"={value} default={self.default} minimum={minimum} maximum={maximum}') + return False + return True + + def __str__(self) -> str: + args = self.component_args if self.component_args is not None else {} + if callable(args): + args = args() + choices = args.get("choices", []) + return f'OptionInfo: label="{self.label}" section="{self.section}" component="{self.component}" default="{self.default}" refresh="{self.refresh is not None}" change="{self.onchange is not None}" args={args} choices={choices}' + def options_section(section_identifier, options_dict): for v in options_dict.values(): @@ -436,11 +470,12 @@ options_templates.update(options_section(('sd', "Execution & Models"), { "sd_model_dict": OptionInfo('None', "Use separate base dict", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_titles()}, refresh=refresh_checkpoints), "sd_checkpoint_autoload": OptionInfo(True, "Model autoload on start"), "sd_checkpoint_autodownload": OptionInfo(True, "Model auto-download on demand"), - "sd_textencoder_cache": OptionInfo(True, "Cache text encoder results"), + "sd_textencoder_cache": OptionInfo(True, "Cache text encoder results", gr.Checkbox, {"visible": False}), + "sd_textencoder_cache_size": OptionInfo(4, "Text encoder results LRU cache size", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), "stream_load": OptionInfo(False, "Load models using stream loading method", gr.Checkbox, {"visible": not native }), "prompt_mean_norm": OptionInfo(False, "Prompt attention normalization", gr.Checkbox), "comma_padding_backtrack": OptionInfo(20, "Prompt padding", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1, "visible": not native }), - "prompt_attention": OptionInfo("Full parser", "Prompt attention parser", gr.Radio, {"choices": ["Full parser", "Compel parser", "xhinker parser", "A1111 parser", "Fixed attention"] }), + "prompt_attention": OptionInfo("native", "Prompt attention parser", gr.Radio, {"choices": ["native", "compel", "xhinker", "a1111", "fixed"] }), "latent_history": OptionInfo(16, "Latent history size", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), "sd_checkpoint_cache": OptionInfo(0, "Cached models", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1, "visible": not native }), })) @@ -992,7 +1027,7 @@ class Options: if filename is None: filename = self.filename if cmd_opts.freeze: - log.warning(f'Settings saving is disabled: {filename}') + log.warning(f'Setting: fn="{filename}" save disabled') return try: # output = json.dumps(self.data, indent=2) @@ -1000,12 +1035,12 @@ class Options: unused_settings = [] if os.environ.get('SD_CONFIG_DEBUG', None) is not None: - log.debug('Config: user settings') + log.debug('Settings: user') for k, v in self.data.items(): log.trace(f' Config: item={k} value={v} default={self.data_labels[k].default if k in self.data_labels else None}') - log.debug('Config: default settings') + log.debug('Settings: defaults') for k in self.data_labels.keys(): - log.trace(f' Config: item={k} default={self.data_labels[k].default}') + log.trace(f' Setting: item={k} default={self.data_labels[k].default}') for k, v in self.data.items(): if k in self.data_labels: @@ -1020,9 +1055,9 @@ class Options: unused_settings.append(k) writefile(diff, filename, silent=silent) if len(unused_settings) > 0: - log.debug(f"Unused settings: {unused_settings}") + log.debug(f"Settings: unused={unused_settings}") except Exception as err: - log.error(f'Save settings failed: {filename} {err}') + log.error(f'Settings: fn="{filename}" {err}') def save(self, filename=None, silent=False): threading.Thread(target=self.save_atomic, args=(filename, silent)).start() @@ -1038,7 +1073,7 @@ class Options: if filename is None: filename = self.filename if not os.path.isfile(filename): - log.debug(f'Created default config: {filename}') + log.debug(f'Settings: fn="{filename}" created') self.save(filename) return self.data = readfile(filename, lock=True) @@ -1046,13 +1081,16 @@ class Options: self.data['quicksettings_list'] = [i.strip() for i in self.data.get('quicksettings').split(',')] unknown_settings = [] for k, v in self.data.items(): - info = self.data_labels.get(k, None) + info: OptionInfo = self.data_labels.get(k, None) + if not info.validate(k, v): + self.data[k] = info.default if info is not None and not self.same_type(info.default, v): - log.error(f"Error: bad setting value: {k}: {v} ({type(v).__name__}; expected {type(info.default).__name__})") + log.warning(f"Setting validation: {k}={v} ({type(v).__name__} expected={type(info.default).__name__})") + self.data[k] = info.default if info is None and k not in compatibility_opts and not k.startswith('uiux_'): unknown_settings.append(k) if len(unknown_settings) > 0: - log.debug(f"Unknown settings: {unknown_settings}") + log.warning(f"Setting validation: unknown={unknown_settings}") def onchange(self, key, func, call=True): item = self.data_labels.get(key) diff --git a/scripts/animatediff.py b/scripts/animatediff.py index c4d7ba68f..4c50f9cf6 100644 --- a/scripts/animatediff.py +++ b/scripts/animatediff.py @@ -258,7 +258,7 @@ class Script(scripts.Script): shared.log.debug(f'AnimateDiff args: {p.task_args}') set_prompt(p) orig_prompt_attention = shared.opts.prompt_attention - shared.opts.data['prompt_attention'] = 'Fixed attention' + shared.opts.data['prompt_attention'] = 'fixed' processed: processing.Processed = processing.process_images(p) # runs processing using main loop shared.opts.data['prompt_attention'] = orig_prompt_attention devices.torch_gc() diff --git a/scripts/ctrlx.py b/scripts/ctrlx.py index bba8c846b..f372e2a94 100644 --- a/scripts/ctrlx.py +++ b/scripts/ctrlx.py @@ -49,7 +49,7 @@ class Script(scripts.Script): from modules.ctrlx.utils import get_self_recurrence_schedule orig_prompt_attention = shared.opts.prompt_attention - shared.opts.data['prompt_attention'] = 'Fixed attention' + shared.opts.data['prompt_attention'] = 'fixed' shared.sd_model = sd_models.switch_pipe(CtrlXStableDiffusionXLPipeline, shared.sd_model) shared.sd_model.restore_pipeline = self.restore diff --git a/scripts/ledits.py b/scripts/ledits.py index e860e5a0b..b75c6ff6f 100644 --- a/scripts/ledits.py +++ b/scripts/ledits.py @@ -44,7 +44,7 @@ class Script(scripts.Script): orig_offload = shared.opts.diffusers_model_cpu_offload orig_prompt_attention = shared.opts.prompt_attention shared.opts.data['diffusers_model_cpu_offload'] = False - shared.opts.data['prompt_attention'] = 'Fixed attention' + shared.opts.data['prompt_attention'] = 'fixed' # shared.sd_model.maybe_free_model_hooks() # ledits is not compatible with offloading # shared.sd_model.has_accelerate = False sd_models.move_model(shared.sd_model, devices.device, force=True) diff --git a/scripts/mixture_tiling.py b/scripts/mixture_tiling.py index 29182d0a9..5b5aab9db 100644 --- a/scripts/mixture_tiling.py +++ b/scripts/mixture_tiling.py @@ -66,7 +66,7 @@ class Script(scripts.Script): shared.sd_model = orig_pipeline return sd_models.set_diffuser_options(shared.sd_model) - shared.opts.data['prompt_attention'] = 'Fixed attention' # this pipeline is not compatible with embeds + shared.opts.data['prompt_attention'] = 'fixed' # this pipeline is not compatible with embeds shared.sd_model.to(torch.float32) # this pipeline unet is not compatible with fp16 processing.fix_seed(p) # set pipeline specific params, note that standard params are applied when applicable diff --git a/scripts/mulan.py b/scripts/mulan.py index 260212721..829ce1463 100644 --- a/scripts/mulan.py +++ b/scripts/mulan.py @@ -87,7 +87,7 @@ class Script(scripts.Script): # mulan only works with single image, single prompt and in fixed attention p.batch_size = 1 p.n_iter = 1 - shared.opts.prompt_attention = 'Fixed attention' + shared.opts.prompt_attention = 'fixed' if isinstance(p.prompt, list): p.prompt = p.prompt[0] p.task_args['prompt'] = p.prompt diff --git a/scripts/regional_prompting.py b/scripts/regional_prompting.py index cecef747d..08b84dd94 100644 --- a/scripts/regional_prompting.py +++ b/scripts/regional_prompting.py @@ -64,7 +64,7 @@ class Script(scripts.Script): shared.sd_model = orig_pipeline return sd_models.set_diffuser_options(shared.sd_model) - shared.opts.data['prompt_attention'] = 'Fixed attention' # this pipeline is not compatible with embeds + shared.opts.data['prompt_attention'] = 'fixed' # this pipeline is not compatible with embeds processing.fix_seed(p) # set pipeline specific params, note that standard params are applied when applicable rp_args = { diff --git a/scripts/x_adapter.py b/scripts/x_adapter.py index 553a20d30..c67eca18b 100644 --- a/scripts/x_adapter.py +++ b/scripts/x_adapter.py @@ -107,7 +107,7 @@ class Script(scripts.Script): pipe.to(device=devices.device, dtype=devices.dtype) except Exception: pass - shared.opts.data['prompt_attention'] = 'Fixed attention' + shared.opts.data['prompt_attention'] = 'fixed' prompt = shared.prompt_styles.apply_styles_to_prompt(p.prompt, p.styles) negative = shared.prompt_styles.apply_negative_styles_to_prompt(p.negative_prompt, p.styles) p.task_args['prompt'] = prompt diff --git a/scripts/xyz_grid_classes.py b/scripts/xyz_grid_classes.py index b292ae767..c3d3554e2 100644 --- a/scripts/xyz_grid_classes.py +++ b/scripts/xyz_grid_classes.py @@ -37,6 +37,7 @@ class SharedSettingsStackHelper(object): sd_text_encoder = None extra_networks_default_multiplier = None disable_weights_auto_swap = None + prompt_attention = None def __enter__(self): #Save overridden settings so they can be restored later. @@ -52,6 +53,7 @@ class SharedSettingsStackHelper(object): self.sd_text_encoder = shared.opts.sd_text_encoder self.extra_networks_default_multiplier = shared.opts.extra_networks_default_multiplier self.disable_weights_auto_swap = shared.opts.disable_weights_auto_swap + self.prompt_attention = shared.opts.prompt_attention shared.opts.data["disable_weights_auto_swap"] = False def __exit__(self, exc_type, exc_value, tb): @@ -62,6 +64,7 @@ class SharedSettingsStackHelper(object): shared.opts.data["tome_ratio"] = self.tome_ratio shared.opts.data["todo_ratio"] = self.todo_ratio shared.opts.data["extra_networks_default_multiplier"] = self.extra_networks_default_multiplier + shared.opts.data["prompt_attention"] = self.prompt_attention if self.sd_model_checkpoint != shared.opts.sd_model_checkpoint: shared.opts.data["sd_model_checkpoint"] = self.sd_model_checkpoint sd_models.reload_model_weights(op='model') @@ -92,6 +95,7 @@ axis_options = [ AxisOption("[Model] Dictionary", str, apply_dict, fmt=format_value_add_label, cost=0.9, choices=lambda: ['None'] + list(sd_models.checkpoints_list)), AxisOption("[Prompt] Search & replace", str, apply_prompt, fmt=format_value_add_label), AxisOption("[Prompt] Prompt order", str_permutations, apply_order, fmt=format_value_join_list), + AxisOption("[Prompt] Prompt parser", str, apply_setting("prompt_attention"), choices=lambda: ["native", "compel", "xhinker", "a1111", "fixed"]), AxisOption("[Network] LoRA", str, apply_lora, cost=0.5, choices=list_lora), AxisOption("[Network] LoRA strength", float, apply_setting('extra_networks_default_multiplier')), AxisOption("[Network] Styles", str, apply_styles, choices=lambda: [s.name for s in shared.prompt_styles.styles.values()]),