cymic/Waifu_Diffusion_Webui
1
1import re2from collections import namedtuple3from typing import List4import lark5 6# a prompt like this: "fantasy landscape with a [mountain:lake:0.25] and [an oak:a christmas tree:0.75][ in foreground::0.6][ in background:0.25] [shoddy:masterful:0.5]"7# will be represented with prompt_schedule like this (assuming steps=100):8# [25, 'fantasy landscape with a mountain and an oak in foreground shoddy']9# [50, 'fantasy landscape with a lake and an oak in foreground in background shoddy']10# [60, 'fantasy landscape with a lake and an oak in foreground in background masterful']11# [75, 'fantasy landscape with a lake and an oak in background masterful']12# [100, 'fantasy landscape with a lake and a christmas tree in background masterful']13 14schedule_parser = lark.Lark(r"""15!start: (prompt | /[][():]/+)*16prompt: (emphasized | scheduled | plain | WHITESPACE)*17!emphasized: "(" prompt ")"18 | "(" prompt ":" prompt ")"19 | "[" prompt "]"20scheduled: "[" [prompt ":"] prompt ":" [WHITESPACE] NUMBER "]"21WHITESPACE: /\s+/22plain: /([^\\\[\]():]|\\.)+/23%import common.SIGNED_NUMBER -> NUMBER24""")25 26def get_learned_conditioning_prompt_schedules(prompts, steps):27 """28 >>> g = lambda p: get_learned_conditioning_prompt_schedules([p], 10)[0]29 >>> g("test")30 [[10, 'test']]31 >>> g("a [b:3]")32 [[3, 'a '], [10, 'a b']]33 >>> g("a [b: 3]")34 [[3, 'a '], [10, 'a b']]35 >>> g("a [[[b]]:2]")36 [[2, 'a '], [10, 'a [[b]]']]37 >>> g("[(a:2):3]")38 [[3, ''], [10, '(a:2)']]39 >>> g("a [b : c : 1] d")40 [[1, 'a b d'], [10, 'a c d']]41 >>> g("a[b:[c:d:2]:1]e")42 [[1, 'abe'], [2, 'ace'], [10, 'ade']]43 >>> g("a [unbalanced")44 [[10, 'a [unbalanced']]45 >>> g("a [b:.5] c")46 [[5, 'a c'], [10, 'a b c']]47 >>> g("a [{b|d{:.5] c") # not handling this right now48 [[5, 'a c'], [10, 'a {b|d{ c']]49 >>> g("((a][:b:c [d:3]")50 [[3, '((a][:b:c '], [10, '((a][:b:c d']]51 """52 53 def collect_steps(steps, tree):54 l = [steps]55 class CollectSteps(lark.Visitor):56 def scheduled(self, tree):57 tree.children[-1] = float(tree.children[-1])58 if tree.children[-1] < 1:59 tree.children[-1] *= steps60 tree.children[-1] = min(steps, int(tree.children[-1]))61 l.append(tree.children[-1])62 CollectSteps().visit(tree)63 return sorted(set(l))64 65 def at_step(step, tree):66 class AtStep(lark.Transformer):67 def scheduled(self, args):68 before, after, _, when = args69 yield before or () if step <= when else after70 def start(self, args):71 def flatten(x):72 if type(x) == str:73 yield x74 else:75 for gen in x:76 yield from flatten(gen)77 return ''.join(flatten(args))78 def plain(self, args):79 yield args[0].value80 def __default__(self, data, children, meta):81 for child in children:82 yield from child83 return AtStep().transform(tree)84 85 def get_schedule(prompt):86 try:87 tree = schedule_parser.parse(prompt)88 except lark.exceptions.LarkError as e:89 if 0:90 import traceback91 traceback.print_exc()92 return [[steps, prompt]]93 return [[t, at_step(t, tree)] for t in collect_steps(steps, tree)]94 95 promptdict = {prompt: get_schedule(prompt) for prompt in set(prompts)}96 return [promptdict[prompt] for prompt in prompts]97 98 99ScheduledPromptConditioning = namedtuple("ScheduledPromptConditioning", ["end_at_step", "cond"])100 101 102def get_learned_conditioning(model, prompts, steps):103 """converts a list of prompts into a list of prompt schedules - each schedule is a list of ScheduledPromptConditioning, specifying the comdition (cond),104 and the sampling step at which this condition is to be replaced by the next one.105 106 Input:107 (model, ['a red crown', 'a [blue:green:5] jeweled crown'], 20)108 109 Output:110 [111 [112 ScheduledPromptConditioning(end_at_step=20, cond=tensor([[-0.3886, 0.0229, -0.0523, ..., -0.4901, -0.3066, 0.0674], ..., [ 0.3317, -0.5102, -0.4066, ..., 0.4119, -0.7647, -1.0160]], device='cuda:0'))113 ],114 [115 ScheduledPromptConditioning(end_at_step=5, cond=tensor([[-0.3886, 0.0229, -0.0522, ..., -0.4901, -0.3067, 0.0673], ..., [-0.0192, 0.3867, -0.4644, ..., 0.1135, -0.3696, -0.4625]], device='cuda:0')),116 ScheduledPromptConditioning(end_at_step=20, cond=tensor([[-0.3886, 0.0229, -0.0522, ..., -0.4901, -0.3067, 0.0673], ..., [-0.7352, -0.4356, -0.7888, ..., 0.6994, -0.4312, -1.2593]], device='cuda:0'))117 ]118 ]119 """120 res = []121 122 prompt_schedules = get_learned_conditioning_prompt_schedules(prompts, steps)123 cache = {}124 125 for prompt, prompt_schedule in zip(prompts, prompt_schedules):126 127 cached = cache.get(prompt, None)128 if cached is not None:129 res.append(cached)130 continue131 132 texts = [x[1] for x in prompt_schedule]133 conds = model.get_learned_conditioning(texts)134 135 cond_schedule = []136 for i, (end_at_step, text) in enumerate(prompt_schedule):137 cond_schedule.append(ScheduledPromptConditioning(end_at_step, conds[i]))138 139 cache[prompt] = cond_schedule140 res.append(cond_schedule)141 142 return res143 144 145re_AND = re.compile(r"\bAND\b")146re_weight = re.compile(r"^(.*?)(?:\s*:\s*([-+]?(?:\d+\.?|\d*\.\d+)))?\s*$")147 148def get_multicond_prompt_list(prompts):149 res_indexes = []150 151 prompt_flat_list = []152 prompt_indexes = {}153 154 for prompt in prompts:155 subprompts = re_AND.split(prompt)156 157 indexes = []158 for subprompt in subprompts:159 match = re_weight.search(subprompt)160 161 text, weight = match.groups() if match is not None else (subprompt, 1.0)162 163 weight = float(weight) if weight is not None else 1.0164 165 index = prompt_indexes.get(text, None)166 if index is None:167 index = len(prompt_flat_list)168 prompt_flat_list.append(text)169 prompt_indexes[text] = index170 171 indexes.append((index, weight))172 173 res_indexes.append(indexes)174 175 return res_indexes, prompt_flat_list, prompt_indexes176 177 178class ComposableScheduledPromptConditioning:179 def __init__(self, schedules, weight=1.0):180 self.schedules: List[ScheduledPromptConditioning] = schedules181 self.weight: float = weight182 183 184class MulticondLearnedConditioning:185 def __init__(self, shape, batch):186 self.shape: tuple = shape # the shape field is needed to send this object to DDIM/PLMS187 self.batch: List[List[ComposableScheduledPromptConditioning]] = batch188 189def get_multicond_learned_conditioning(model, prompts, steps) -> MulticondLearnedConditioning:190 """same as get_learned_conditioning, but returns a list of ScheduledPromptConditioning along with the weight objects for each prompt.191 For each prompt, the list is obtained by splitting the prompt using the AND separator.192 193 https://energy-based-model.github.io/Compositional-Visual-Generation-with-Composable-Diffusion-Models/194 """195 196 res_indexes, prompt_flat_list, prompt_indexes = get_multicond_prompt_list(prompts)197 198 learned_conditioning = get_learned_conditioning(model, prompt_flat_list, steps)199 200 res = []201 for indexes in res_indexes:202 res.append([ComposableScheduledPromptConditioning(learned_conditioning[i], weight) for i, weight in indexes])203 204 return MulticondLearnedConditioning(shape=(len(prompts),), batch=res)205 206 207def reconstruct_cond_batch(c: List[List[ScheduledPromptConditioning]], current_step):208 param = c[0][0].cond209 res = torch.zeros((len(c),) + param.shape, device=param.device, dtype=param.dtype)210 for i, cond_schedule in enumerate(c):211 target_index = 0212 for current, (end_at, cond) in enumerate(cond_schedule):213 if current_step <= end_at:214 target_index = current215 break216 res[i] = cond_schedule[target_index].cond217 218 return res219 220 221def reconstruct_multicond_batch(c: MulticondLearnedConditioning, current_step):222 param = c.batch[0][0].schedules[0].cond223 224 tensors = []225 conds_list = []226 227 for batch_no, composable_prompts in enumerate(c.batch):228 conds_for_batch = []229 230 for cond_index, composable_prompt in enumerate(composable_prompts):231 target_index = 0232 for current, (end_at, cond) in enumerate(composable_prompt.schedules):233 if current_step <= end_at:234 target_index = current235 break236 237 conds_for_batch.append((len(tensors), composable_prompt.weight))238 tensors.append(composable_prompt.schedules[target_index].cond)239 240 conds_list.append(conds_for_batch)241 242 return conds_list, torch.stack(tensors).to(device=param.device, dtype=param.dtype)243 244 245re_attention = re.compile(r"""246\\\(|247\\\)|248\\\[|249\\]|250\\\\|251\\|252\(|253\[|254:([+-]?[.\d]+)\)|255\)|256]|257[^\\()\[\]:]+|258:259""", re.X)260 261 262def parse_prompt_attention(text):263 """264 Parses a string with attention tokens and returns a list of pairs: text and its assoicated weight.265 Accepted tokens are:266 (abc) - increases attention to abc by a multiplier of 1.1267 (abc:3.12) - increases attention to abc by a multiplier of 3.12268 [abc] - decreases attention to abc by a multiplier of 1.1269 \( - literal character '('270 \[ - literal character '['271 \) - literal character ')'272 \] - literal character ']'273 \\ - literal character '\'274 anything else - just text275 276 >>> parse_prompt_attention('normal text')277 [['normal text', 1.0]]278 >>> parse_prompt_attention('an (important) word')279 [['an ', 1.0], ['important', 1.1], [' word', 1.0]]280 >>> parse_prompt_attention('(unbalanced')281 [['unbalanced', 1.1]]282 >>> parse_prompt_attention('\(literal\]')283 [['(literal]', 1.0]]284 >>> parse_prompt_attention('(unnecessary)(parens)')285 [['unnecessaryparens', 1.1]]286 >>> parse_prompt_attention('a (((house:1.3)) [on] a (hill:0.5), sun, (((sky))).')287 [['a ', 1.0],288 ['house', 1.5730000000000004],289 [' ', 1.1],290 ['on', 1.0],291 [' a ', 1.1],292 ['hill', 0.55],293 [', sun, ', 1.1],294 ['sky', 1.4641000000000006],295 ['.', 1.1]]296 """297 298 res = []299 round_brackets = []300 square_brackets = []301 302 round_bracket_multiplier = 1.1303 square_bracket_multiplier = 1 / 1.1304 305 def multiply_range(start_position, multiplier):306 for p in range(start_position, len(res)):307 res[p][1] *= multiplier308 309 for m in re_attention.finditer(text):310 text = m.group(0)311 weight = m.group(1)312 313 if text.startswith('\\'):314 res.append([text[1:], 1.0])315 elif text == '(':316 round_brackets.append(len(res))317 elif text == '[':318 square_brackets.append(len(res))319 elif weight is not None and len(round_brackets) > 0:320 multiply_range(round_brackets.pop(), float(weight))321 elif text == ')' and len(round_brackets) > 0:322 multiply_range(round_brackets.pop(), round_bracket_multiplier)323 elif text == ']' and len(square_brackets) > 0:324 multiply_range(square_brackets.pop(), square_bracket_multiplier)325 else:326 res.append([text, 1.0])327 328 for pos in round_brackets:329 multiply_range(pos, round_bracket_multiplier)330 331 for pos in square_brackets:332 multiply_range(pos, square_bracket_multiplier)333 334 if len(res) == 0:335 res = [["", 1.0]]336 337 # merge runs of identical weights338 i = 0339 while i + 1 < len(res):340 if res[i][1] == res[i + 1][1]:341 res[i][0] += res[i + 1][0]342 res.pop(i + 1)343 else:344 i += 1345 346 return res347 348if __name__ == "__main__":349 import doctest350 doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)351else:352 import torch # doctest faster353 