From 2254dc26a9fa31dd9a2ea430a4b49bf0a95644af Mon Sep 17 00:00:00 2001 From: Extraltodeus Date: Tue, 26 Mar 2024 18:35:34 +0100 Subject: [PATCH] Add files via upload --- __init__.py | 1 + nodes.py | 146 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 135 insertions(+), 12 deletions(-) diff --git a/__init__.py b/__init__.py index 85213fe..642dbd2 100644 --- a/__init__.py +++ b/__init__.py @@ -2,4 +2,5 @@ from .nodes import * NODE_CLASS_MAPPINGS = { "Automatic CFG": simpleDynamicCFG, + "Automatic CFG - Advanced settings": advancedDynamicCFG, } diff --git a/nodes.py b/nodes.py index f9918bb..889ec0f 100644 --- a/nodes.py +++ b/nodes.py @@ -1,17 +1,80 @@ +from copy import deepcopy +import comfy.samplers import torch +import math + +original_sampling_function = deepcopy(comfy.samplers.sampling_function) +minimum_sigma_to_disable_uncond = 1 + +def sampling_function_patched(model, x, timestep, uncond, cond, cond_scale, model_options={}, seed=None): + if math.isclose(cond_scale, 1.0) and model_options.get("disable_cfg1_optimization", False) == False or timestep <= minimum_sigma_to_disable_uncond: + uncond_ = None + cond_scale = 1 + else: + uncond_ = uncond + + cond_pred, uncond_pred = comfy.samplers.calc_cond_uncond_batch(model, cond, uncond_, x, timestep, model_options) + if "sampler_cfg_function" in model_options: + args = {"cond": x - cond_pred, "uncond": x - uncond_pred, "cond_scale": cond_scale, "timestep": timestep, "input": x, "sigma": timestep, + "cond_denoised": cond_pred, "uncond_denoised": uncond_pred, "model": model, "model_options": model_options} + cfg_result = x - model_options["sampler_cfg_function"](args) + else: + cfg_result = uncond_pred + (cond_pred - uncond_pred) * cond_scale + + for fn in model_options.get("sampler_post_cfg_function", []): + args = {"denoised": cfg_result, "cond": cond, "uncond": uncond, "model": model, "uncond_denoised": uncond_pred, "cond_denoised": cond_pred, + "sigma": timestep, "model_options": model_options, "input": x} + cfg_result = fn(args) + + return cfg_result + +def map_sigma(sigma, sigmax, sigmin): + return 1 + ((sigma - sigmax) * (0 - 1)) / (sigmin - sigmax) + +def center_latent_mean_values(latent, per_channel, mult): + for b in range(len(latent)): + if per_channel: + for c in range(len(latent[b])): + latent[b][c] -= latent[b][c].mean() * mult + else: + latent[b] -= latent[b].mean() * mult + return latent + +class advancedDynamicCFG: + def __init__(self): + self.last_cfg_ht_one = 8 -class simpleDynamicCFG: @classmethod def INPUT_TYPES(s): return {"required": { "model": ("MODEL",), + "center_mean_post_cfg" : ("BOOLEAN", {"default": True}), + "center_mean_to_sigma" : ("BOOLEAN", {"default": True}), + "automatic_cfg" : (["None","soft","hard","include_boost"], {"default": "hard"},), + "sigma_boost" : ("BOOLEAN", {"default": True}), + "sigma_boost_percentage": ("FLOAT", {"default": 6.86, "min": 0.0, "max": 100.0, "step": 0.01, "round": 0.01}), }} RETURN_TYPES = ("MODEL",) FUNCTION = "patch" CATEGORY = "model_patches" - def patch(self, model): + def patch(self, model, center_mean_post_cfg, center_mean_to_sigma, + automatic_cfg, sigma_boost, sigma_boost_percentage): + + global minimum_sigma_to_disable_uncond + model_sampling = model.model.model_sampling + sigmin = model_sampling.sigma(model_sampling.timestep(model_sampling.sigma_min)) + sigmax = model_sampling.sigma(model_sampling.timestep(model_sampling.sigma_max)) + + if sigma_boost_percentage > 0 and sigma_boost: + minimum_sigma_to_disable_uncond = (sigmax - sigmin) / 100 * sigma_boost_percentage + comfy.samplers.sampling_function = sampling_function_patched + print(f"Sampling function patched. Trigger when sigmas are at: {round(minimum_sigma_to_disable_uncond.item(),4)}") + else: + comfy.samplers.sampling_function = original_sampling_function + print(f"Sampling function unpatched.") + top_k = 0.25 reference_cfg = 8 def linear_cfg(args): @@ -19,21 +82,80 @@ class simpleDynamicCFG: cond_scale = args["cond_scale"] uncond = args["uncond"] input_x = args["input"] - target_intensity = 0.8/reference_cfg*cond_scale - denoised_tmp = input_x-(uncond + reference_cfg*(cond - uncond)) + sigma = args["sigma"][0] + + if sigma == sigmax or cond_scale > 1: + self.last_cfg_ht_one = cond_scale + + target_intensity = self.last_cfg_ht_one / 10 + + if sigma_boost and cond_scale > 1: + for b in range(len(cond)): + for c in range(len(cond[b])): + uncond[b][c] = uncond[b][c] * torch.norm(cond[b][c]) / torch.norm(uncond[b][c]) + + if automatic_cfg == "None" or (cond_scale == 1 and automatic_cfg != "include_boost"): + return uncond + cond_scale * (cond - uncond) + + if cond_scale > 1: + denoised_tmp = input_x - (uncond + reference_cfg * (cond - uncond)) + else: + denoised_tmp = input_x - cond + for b in range(len(denoised_tmp)): for c in range(len(denoised_tmp[b])): channel = denoised_tmp[b][c] - max_values = torch.topk(channel, k=int(len(channel)*top_k), largest=True).values + max_values = torch.topk(channel, k=int(len(channel)*top_k), largest=True ).values min_values = torch.topk(channel, k=int(len(channel)*top_k), largest=False).values max_val = torch.mean(max_values).item() - min_val = torch.mean(min_values).item() - denoised_range = (max_val+abs(min_val))/2 - tmp_scale = reference_cfg*target_intensity/denoised_range - denoised_tmp[b][c] = uncond[b][c] + tmp_scale * (cond[b][c] - uncond[b][c]) + + if automatic_cfg == "soft": + min_val = abs(torch.mean(min_values).item()) + elif automatic_cfg == "hard" or automatic_cfg == "include_boost": + min_val = torch.mean(torch.abs(min_values)).item() + + denoised_range = (max_val + min_val) / 2 + scale_correction = target_intensity / denoised_range + tmp_scale = reference_cfg * scale_correction + + if cond_scale > 1: + denoised_tmp[b][c] = uncond[b][c] + tmp_scale * (cond[b][c] - uncond[b][c]) + else: + denoised_tmp[b][c] = scale_correction * cond[b][c] + + # The scaling has been done per channel, now we set it back to norm. + if cond_scale == 1: + denoised_tmp = denoised_tmp * cond.norm() / denoised_tmp.norm() + return denoised_tmp + + def center_mean_latent_post_cfg(args): + denoised = args["denoised"] + sigma = args["sigma"][0] + mult = map_sigma(sigma, sigmax, sigmin) if center_mean_to_sigma else 1 + denoised = center_latent_mean_values(denoised, False, mult) + return denoised + m = model.clone() - m.set_model_sampler_cfg_function(linear_cfg, disable_cfg1_optimization=True) + m.set_model_sampler_cfg_function(linear_cfg, disable_cfg1_optimization=False) + if center_mean_post_cfg: + m.set_model_sampler_post_cfg_function(center_mean_latent_post_cfg) + return (m, ) -#It would have made more sense to make the min_val absolute before the mean but I've tried that way and since it's an arbitrary goal I'll leave it like that. -#Edit 2 to explain further: fixing this makes the limit too hard and starts to visibly limit the denoising. Leaving it likes this allows a margin of manoeuver. + +class simpleDynamicCFG: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "model": ("MODEL",), + "boost" : ("BOOLEAN", {"default": True}), + }} + RETURN_TYPES = ("MODEL",) + FUNCTION = "patch" + + CATEGORY = "model_patches" + + def patch(self, model, boost): + advcfg = advancedDynamicCFG() + m = advcfg.patch(model,False,False,"hard" if boost else "soft", boost, 6.86)[0] + return (m, ) \ No newline at end of file