1358 lines
57 KiB
Python
1358 lines
57 KiB
Python
import torch
|
|
import safetensors.torch
|
|
import nodes
|
|
import logging
|
|
import folder_paths
|
|
import os
|
|
import re
|
|
from comfy.comfy_types import IO, InputTypeDict
|
|
|
|
|
|
condelta_dir = os.path.join(folder_paths.models_dir, "ConDelta")
|
|
|
|
# A list of prompts for various things to use to generate a style conditioning delta
|
|
style_prompts = {
|
|
"misc": [
|
|
"A tabby cat with a blue collar and a red bowtie, wearing glasses, sitting on a stack of books",
|
|
"A sexy anime succubus with a red dress and black wings, holding a pitchfork, with a mischievous smile",
|
|
"A scene of a futuristic city with flying cars and neon lights, with a giant robot in the background",
|
|
"A tranquil mountain landscape with a clear blue lake and a small cabin, with a sunset in the background",
|
|
"An old man sitting at a rustic table.",
|
|
"A surreal scene with a giant clock melting over a tree.",
|
|
"A cliff overlooking a stormy sea, with a lighthouse in the distance.",
|
|
],
|
|
"anime": [
|
|
"A cute girl with long pink hair and a blue dress, holding a bouquet of flowers",
|
|
"A sexy woman with red fox ears and nine tails wearing a revealing kimono and smiling seductively, standing in front of a Japanese shrine",
|
|
"A teen age boy with spiky blue hair and a black jacket, riding a motorcycle",
|
|
"An old railway track running through the japanese countryside, with a forest and mountains in the background",
|
|
"A girl with blue hair in an elaborate, revealing sci-fi plugsuit, standing in a mecha hanger",
|
|
"A cyber-ninja in a tight black suit with neon highlighs, sneaking down a hallway in a futuristic factory",
|
|
"A cute young girl with her pet monster. The monster is glowing blue asnd has large cute eyes",
|
|
"A samurai engaged in battle at night. There are burning buildings in the background",
|
|
]
|
|
}
|
|
|
|
try:
|
|
if condelta_dir not in folder_paths.get_folder_paths("ConDelta"):
|
|
raise KeyError
|
|
except KeyError:
|
|
try:
|
|
os.mkdir(condelta_dir)
|
|
except OSError:
|
|
# Already exists
|
|
pass
|
|
|
|
folder_paths.add_model_folder_path("ConDelta", condelta_dir)
|
|
|
|
def tensor_mean(tensor):
|
|
return torch.mean(torch.abs(torch.flatten(tensor, start_dim=1))).item()
|
|
|
|
def tensor_max(tensor):
|
|
return torch.max(torch.abs(torch.flatten(tensor, start_dim=1))).item()
|
|
|
|
def tensor_median(tensor):
|
|
return torch.median(torch.abs(torch.flatten(tensor, start_dim=1))).item()
|
|
|
|
def tensor_ratio(tensor, type="mean"):
|
|
if type == "mean":
|
|
return tensor_mean(tensor)
|
|
elif type == "max":
|
|
return tensor_max(tensor)
|
|
elif type == "median":
|
|
return tensor_median(tensor)
|
|
else:
|
|
raise ValueError("Invalid type: " + type)
|
|
|
|
# Various experiments with noise to see if I can find something that is predictable and useful
|
|
def tensor_mask(tensor1, tensor2):
|
|
"""
|
|
Clips all the values in tensor1 to the magnitude of the same values in tensor2.
|
|
"""
|
|
return torch.sign(tensor1) * torch.minimum(torch.abs(tensor1), torch.abs(tensor2))
|
|
|
|
def tenser_get_gaussian_noise(tensor, strength, seed=None):
|
|
"""
|
|
Returns a tensor of gaussian noise, multiplied by the strength.
|
|
"""
|
|
if seed is not None:
|
|
torch.manual_seed(seed)
|
|
|
|
return torch.randn_like(tensor) * strength
|
|
|
|
def tenser_add_gaussian_noise(tensor, strength):
|
|
"""
|
|
Adds gaussian noise to the tensor, multiplied by the strength.
|
|
"""
|
|
return tensor + torch.randn_like(tensor) * strength
|
|
|
|
def tensor_add_multiplied_gaussian_noise(tensor1, strength):
|
|
"""
|
|
Makes a tensor of gaussian noise, then multiplies each element by the absolute value of
|
|
each corresponding element in tensor1. This is multiplied by strength and added to tensor1.
|
|
"""
|
|
return tensor1 + torch.randn_like(tensor1) * torch.abs(tensor1) * strength
|
|
|
|
def tensor_get_type2_noise(tensor, strength, seed=None):
|
|
"""
|
|
Makes a tensor of gaussian noise, clips it with tanh, then multiplies each element by the
|
|
absolute value of each corresponding element in tensor1. This is multiplied by strength
|
|
and returned
|
|
"""
|
|
if seed is not None:
|
|
torch.manual_seed(seed)
|
|
|
|
return torch.tanh(torch.randn_like(tensor)) * torch.abs(tensor) * strength
|
|
|
|
def tensor_add_type2_noise(tensor, strength, seed=None):
|
|
"""
|
|
Makes a tensor of gaussian noise, clips it with tanh, then multiplies each element by the
|
|
absolute value of each corresponding element in tensor1. This is multiplied by strength
|
|
and added to tensor1.
|
|
"""
|
|
return tensor + tensor_get_type2_noise(tensor, strength, seed)
|
|
|
|
def tensor_get_type1_noise(tensor, strength, seed=None):
|
|
"""
|
|
Makes a tensor of gaussian noise, then multiplies each element by the absolute value of
|
|
each corresponding element in tensor. This is multiplied by strength and returned
|
|
"""
|
|
if seed is not None:
|
|
torch.manual_seed(seed)
|
|
|
|
return torch.randn_like(tensor) * torch.abs(tensor) * strength
|
|
|
|
def tensor_add_type1_noise(tensor, strength, seed=None):
|
|
"""
|
|
Makes a tensor of gaussian noise, then multiplies each element by the absolute value of
|
|
each corresponding element in tensor. This is multiplied by strength and added to tensor
|
|
"""
|
|
return tensor + tensor_get_type1_noise(tensor, strength, seed)
|
|
|
|
def tensor_get_type3_noise(tensor, strength, seed=None):
|
|
"""
|
|
Makes a tensor of gaussian noise, takes the absolute value, then multiples each element
|
|
by each value in the given tensor. This is multiplied by strength.
|
|
"""
|
|
if seed is not None:
|
|
torch.manual_seed(seed)
|
|
|
|
return torch.abs(torch.randn_like(tensor)) * tensor * strength
|
|
|
|
def tensor_add_type3_noise(tensor, strength, seed=None):
|
|
"""
|
|
Makes a tensor of gaussian noise, takes the absolute value, then multiples each element
|
|
by each value in the given tensor. This is multiplied by strength and added to the tensor.
|
|
"""
|
|
return tensor + tensor_get_type3_noise(tensor, strength, seed)
|
|
|
|
def tensor_get_type4_noise(tensor, strength, seed=None):
|
|
"""
|
|
Makes a tensor of gaussian noise, takes the absolute value, runs tanh on it, then multiples
|
|
each element by each value in the given tensor. This is multiplied by strength.
|
|
"""
|
|
if seed is not None:
|
|
torch.manual_seed(seed)
|
|
|
|
return torch.tanh(torch.abs(torch.randn_like(tensor))) * tensor * strength
|
|
|
|
def tensor_add_type4_noise(tensor, strength, seed=None):
|
|
"""
|
|
Makes a tensor of gaussian noise, takes the absolute value, runs tanh on it, then multiples
|
|
each element by each value in the given tensor. This is multiplied by strength and added to the tensor.
|
|
"""
|
|
return tensor + tensor_get_type4_noise(tensor, strength, seed)
|
|
|
|
def conditioning_add(conditioning_a, conditioning_b):
|
|
"""
|
|
Adds two conditionings together (A + B), producing a conditioning delta.
|
|
"""
|
|
|
|
out = []
|
|
|
|
cond_b = conditioning_b[0][0]
|
|
pooled_output_b = conditioning_b[0][1].get("pooled_output", None)
|
|
conditioning_llama3_b = conditioning_b[0][1].get("conditioning_llama3", None)
|
|
|
|
for i in range(len(conditioning_a)):
|
|
# Print the keys of conditioning_a[i][1]
|
|
if conditioning_a[i][1] is None:
|
|
logging.warning(f"conditioning_a[{i}][1] is None, skipping.")
|
|
if conditioning_a[i][1] is not None:
|
|
logging.debug(f"conditioning_a[{i}][1] keys: {list(conditioning_a[i][1].keys())}")
|
|
|
|
t1 = conditioning_a[i][0]
|
|
pooled_output_a = conditioning_a[i][1].get("pooled_output", pooled_output_b)
|
|
conditioning_llama3_a = conditioning_a[i][1].get("conditioning_llama3", None)
|
|
t0 = cond_b[:,:t1.shape[1]]
|
|
if t0.shape[1] < t1.shape[1]:
|
|
t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
|
|
|
|
# Pad conditioning_llama3_a or _b if [1, 32, a, 4096] is not [1, 32, b, 4096]
|
|
if conditioning_llama3_b is not None and conditioning_llama3_a is not None:
|
|
if conditioning_llama3_b.shape[2] < conditioning_llama3_a.shape[2]:
|
|
conditioning_llama3_b = torch.cat([conditioning_llama3_b] + [torch.zeros((1, 32, (conditioning_llama3_a.shape[2] - conditioning_llama3_b.shape[2]), 4096))], dim=2)
|
|
elif conditioning_llama3_b.shape[2] > conditioning_llama3_a.shape[2]:
|
|
conditioning_llama3_a = torch.cat([conditioning_llama3_a] + [torch.zeros((1, 32, (conditioning_llama3_b.shape[2] - conditioning_llama3_a.shape[2]), 4096))], dim=2)
|
|
|
|
tw = t1 + torch.mul(t0, 1)
|
|
t_to = conditioning_a[i][1].copy()
|
|
if pooled_output_b is not None and pooled_output_a is not None:
|
|
t_to["pooled_output"] = pooled_output_a + torch.mul(pooled_output_b, 1)
|
|
elif pooled_output_b is not None:
|
|
t_to["pooled_output"] = pooled_output_b
|
|
|
|
if conditioning_llama3_b is not None and conditioning_llama3_a is not None:
|
|
t_to["conditioning_llama3"] = conditioning_llama3_a + torch.mul(conditioning_llama3_b, 1)
|
|
elif conditioning_llama3_b is not None:
|
|
t_to["conditioning_llama3"] = conditioning_llama3_b
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return out
|
|
|
|
def conditioning_scale(conditioning, scalar):
|
|
"""
|
|
Multiplies the conditioning by a scalar.
|
|
"""
|
|
|
|
out = []
|
|
|
|
for i in range(len(conditioning)):
|
|
t1 = conditioning[i][0]
|
|
pooled_output = conditioning[i][1].get("pooled_output", None)
|
|
conditioning_llama3 = conditioning[i][1].get("conditioning_llama3", None)
|
|
tw = torch.mul(t1, scalar)
|
|
#print(f"Mean before multiply: {tensor_mean(t1)}, Mean after multiply: {tensor_mean(tw)}, Scalar: {scalar}")
|
|
t_to = conditioning[i][1].copy()
|
|
|
|
#print("scale: conditioning[i][1] keys: ", t_to.keys())
|
|
|
|
if pooled_output is not None:
|
|
t_to["pooled_output"] = torch.mul(pooled_output, scalar)
|
|
print(f"Pooled Mean before multiply: {tensor_mean(pooled_output)}, Mean after multiply: {tensor_mean(t_to['pooled_output'])}, Scalar: {scalar}")
|
|
#else:
|
|
#print("scale: No pooled_output found in conditioning, skipping.")
|
|
if conditioning_llama3 is not None:
|
|
mean_before_multiply = tensor_mean(conditioning_llama3)
|
|
t_to["conditioning_llama3"] = torch.mul(conditioning_llama3, scalar)
|
|
mean_after_multiply = tensor_mean(t_to["conditioning_llama3"])
|
|
print(f"Llama3 Mean before multiply: {mean_before_multiply}, Mean after multiply: {mean_after_multiply}, Scalar: {scalar}")
|
|
#else:
|
|
#print("scale: No conditioning_llama3 found in conditioning, skipping.")
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return out
|
|
|
|
def conditioning_subtract(conditioning_a, conditioning_b):
|
|
"""
|
|
Subtracts one conditioning from another (A-B), producing a conditioning delta by
|
|
multiplying B by -1 and adding it to A with conditionining_scale and conditioning_add.
|
|
"""
|
|
|
|
conditioning_b = conditioning_scale(conditioning_b, -1)
|
|
return conditioning_add(conditioning_a, conditioning_b)
|
|
|
|
def get_conditioning_from_prompt(prompt, clip, **kwargs):
|
|
"""
|
|
This function gets the conditioning from a prompt and a clip model.
|
|
"""
|
|
|
|
# Get the conditioning from the prompt
|
|
tokens = clip.tokenize(prompt, **kwargs)
|
|
return clip.encode_from_tokens_scheduled(tokens)
|
|
|
|
|
|
class QuickConDelta:
|
|
"""
|
|
This shortcut node takes a conditioning, clip, and prompt as input. It encodes the prompt
|
|
using the clip model, then encodes a "" prompt using the clip model. It subtracts the "" conditioning
|
|
from the prompt conditioning, producing a conditioning delta. Then it adds the conditioning delta
|
|
to the user supplied conditioning, producing a new conditioning.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(s) -> InputTypeDict:
|
|
return {
|
|
"required": {
|
|
"conditioning": ("CONDITIONING", ),
|
|
"clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."}),
|
|
"prompt": (IO.STRING, {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}),
|
|
"strength": ("FLOAT", {"default": 0.6, "step": 0.01, "min": -100.0, "max": 100.0}),
|
|
}
|
|
}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "getConditioning"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def getConditioning(self, conditioning, clip, prompt, strength):
|
|
# Get the conditioning from the prompt
|
|
conditioning_from_prompt = get_conditioning_from_prompt(prompt, clip)
|
|
|
|
blank_conditioning = get_conditioning_from_prompt("", clip)
|
|
# Subtract the blank conditioning from the conditioning from the prompt
|
|
conditioning_delta = conditioning_subtract(conditioning_from_prompt, blank_conditioning)
|
|
|
|
# Scale the conditioning delta by the negative prompt strength
|
|
conditioning_delta = conditioning_scale(conditioning_delta, strength)
|
|
|
|
# Subtract the conditioning from the conditioning from the prompt
|
|
new_conditioning = conditioning_add(conditioning, conditioning_delta)
|
|
# Return the conditioning delta
|
|
return (new_conditioning, )
|
|
|
|
class CFGlessNegativePrompt:
|
|
"""
|
|
This shortcut node takes a conditioning, clip, and prompt as input. It encodes the prompt
|
|
using the clip model, then encodes a "" prompt using the clip model. It subtracts the "" conditioning
|
|
from the prompt conditioning, producing a conditioning delta. Then it substracts the conditioning delta
|
|
from the user supplied conditioning, producing a new conditioning.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(s) -> InputTypeDict:
|
|
return {
|
|
"required": {
|
|
"conditioning": ("CONDITIONING", ),
|
|
"clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."}),
|
|
"negative_prompt": (IO.STRING, {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}),
|
|
"negative_prompt_strength": ("FLOAT", {"default": 0.6, "step": 0.01, "min": -100.0, "max": 100.0}),
|
|
}
|
|
}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "getConditioning"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def getConditioning(self, conditioning, clip, negative_prompt, negative_prompt_strength):
|
|
# Get the conditioning from the prompt
|
|
conditioning_from_prompt = get_conditioning_from_prompt(negative_prompt, clip)
|
|
blank_conditioning = get_conditioning_from_prompt("", clip)
|
|
# Subtract the blank conditioning from the conditioning from the prompt
|
|
conditioning_delta = conditioning_subtract(conditioning_from_prompt, blank_conditioning)
|
|
|
|
# Scale the conditioning delta by the negative prompt strength
|
|
conditioning_delta = conditioning_scale(conditioning_delta, negative_prompt_strength)
|
|
|
|
# Subtract the conditioning from the conditioning from the prompt
|
|
conditioning_with_negative = conditioning_subtract(conditioning, conditioning_delta)
|
|
# Return the conditioning delta
|
|
return (conditioning_with_negative, )
|
|
|
|
class GetConDeltaFromPrompt:
|
|
"""
|
|
This node gets a conditioning delta from a prompt and a clip model.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s) -> InputTypeDict:
|
|
return {
|
|
"required": {
|
|
"prompt": (IO.STRING, {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}),
|
|
"clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."}),
|
|
"prompt_type": ([*style_prompts.keys(), 'custom', 'none'], {"default": "misc"}),
|
|
"custom_prompts": (IO.STRING, {"multiline": True, "tooltip": "Custom prompts to use for conditioning delta, one per line."}),
|
|
}
|
|
}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "getConDelta"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def getConDelta(self, prompt, clip, prompt_type, custom_prompts):
|
|
# Get the style prompts
|
|
_style_prompts = []
|
|
|
|
if prompt_type == "custom":
|
|
_style_prompts = custom_prompts.split("\n")
|
|
elif prompt_type == "none":
|
|
_style_prompts = [""]
|
|
else:
|
|
_style_prompts = style_prompts[prompt_type]
|
|
|
|
|
|
conditioning_delta = None
|
|
# Iterate through the style prompts
|
|
for style_prompt in _style_prompts:
|
|
# Append the style prompt to the prompt
|
|
prompt_with_style = f"{prompt}. {style_prompt}"
|
|
# Get the conditioning from the prompt with the style prompt appended
|
|
conditioning_with_style = get_conditioning_from_prompt(prompt_with_style, clip)
|
|
# Get the conditioning from the prompt without the style appended
|
|
conditioning_without_style = get_conditioning_from_prompt(prompt, clip)
|
|
# Subtract the conditioning without the style from the conditioning with the style
|
|
conditioning = conditioning_subtract(conditioning_with_style, conditioning_without_style)
|
|
|
|
# If the conditioning delta is None, set it to the conditioning
|
|
if conditioning_delta is None:
|
|
conditioning_delta = conditioning
|
|
else:
|
|
# Add the conditioning to the conditioning delta
|
|
conditioning_delta = conditioning_add(conditioning_delta, conditioning)
|
|
|
|
# Scale the conditioning delta by the number of style prompts
|
|
conditioning_delta = conditioning_scale(conditioning_delta, 1.0 / len(style_prompts))
|
|
# Return the conditioning delta
|
|
return (conditioning_delta, )
|
|
|
|
class ConditioningGetNoise:
|
|
"""
|
|
This generates type1 or type2 noise based on the input conditioning and strength
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning": ("CONDITIONING", ),
|
|
"strength": ("FLOAT", {"default": 1.0, "step": 0.01}),
|
|
"type": (["type1", "type2", "type3", "type4"], {"default": "type1"}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "getNoise"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def getNoise(self, conditioning, strength, type, seed):
|
|
out = []
|
|
|
|
noise_functions = {
|
|
"type1": tensor_get_type1_noise,
|
|
"type2": tensor_get_type2_noise,
|
|
"type3": tensor_get_type3_noise,
|
|
"type4": tensor_get_type4_noise,
|
|
}
|
|
|
|
noise_function = noise_functions[type]
|
|
|
|
for i in range(len(conditioning)):
|
|
t1 = conditioning[i][0]
|
|
pooled_output = conditioning[i][1].get("pooled_output", None)
|
|
tw = noise_function(t1, strength, seed)
|
|
if pooled_output is not None:
|
|
pooled_output = noise_function(pooled_output, strength, seed)
|
|
|
|
t_to = conditioning[i][1].copy()
|
|
if pooled_output is not None:
|
|
t_to["pooled_output"] = pooled_output
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
class ConditioningGetRandom:
|
|
"""
|
|
This generates random gaussian noise based on the input conditioning and strength
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning": ("CONDITIONING", ),
|
|
"strength": ("FLOAT", {"default": 1.0, "step": 0.01}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "getRandom"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def getRandom(self, conditioning, strength, seed):
|
|
out = []
|
|
|
|
for i in range(len(conditioning)):
|
|
t1 = conditioning[i][0]
|
|
pooled_output = conditioning[i][1].get("pooled_output", None)
|
|
tw = tenser_get_gaussian_noise(t1, strength, seed)
|
|
if pooled_output is not None:
|
|
pooled_output = tenser_get_gaussian_noise(pooled_output, strength, seed)
|
|
|
|
t_to = conditioning[i][1].copy()
|
|
if pooled_output is not None:
|
|
t_to["pooled_output"] = pooled_output
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
class MaskConDelta:
|
|
"""
|
|
This node masks the conditioning delta with another conditioning.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"conditioning_delta": ("CONDITIONING", ),
|
|
"mask": ("CONDITIONING", )
|
|
}
|
|
}
|
|
RETURN_TYPES = ("CONDITIONING", )
|
|
FUNCTION = "mask"
|
|
OUTPUT_NODE = True
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def mask(self, conditioning_delta, mask):
|
|
out = []
|
|
|
|
for i in range(len(conditioning_delta)):
|
|
t1 = conditioning_delta[i][0]
|
|
pooled_output = conditioning_delta[i][1].get("pooled_output", None)
|
|
t0 = mask[i][0]
|
|
if t0.shape[1] < t1.shape[1]:
|
|
t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
|
|
|
|
tw = tensor_mask(t1, t0)
|
|
t_to = conditioning_delta[i][1].copy()
|
|
mask_pooled_output = mask[i][1].get("pooled_output", None)
|
|
if pooled_output is not None:
|
|
t_to["pooled_output"] = tensor_mask(pooled_output, mask_pooled_output)
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
class SaveConditioningDelta:
|
|
"""
|
|
This node saves the conditioning as a safetensors file for later use.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"conditioning_delta": ("CONDITIONING", ),
|
|
"file_name": ("STRING", ),
|
|
"overwrite": ("BOOLEAN", {"default": False, "tooltip": "If true, will overwrite the file if it already exists."})
|
|
}
|
|
}
|
|
RETURN_TYPES = ()
|
|
FUNCTION = "save"
|
|
OUTPUT_NODE = True
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def save(self, conditioning_delta, file_name, overwrite):
|
|
# remove .safetenors from the file_name
|
|
if file_name.endswith(".safetensors"):
|
|
file_name = file_name[:-12]
|
|
|
|
#file_path = sys.path[0] + f"/models/ConDelta/{file_name}.safetensors"
|
|
file_path = os.path.join(condelta_dir, f"{file_name}.safetensors")
|
|
print("Saving conditioning delta to file: ", file_path)
|
|
|
|
# Throw exception if file already exists
|
|
if not overwrite and os.path.exists(file_path):
|
|
raise Exception(f"File already exists: {file_path}")
|
|
#print(conditioning_delta)
|
|
|
|
# Extract the tensor data from the conditioning delta
|
|
tensor_data = conditioning_delta[0][0]
|
|
pooled_output = conditioning_delta[0][1].get("pooled_output", None)
|
|
#print(tensor_data)
|
|
# Save the tensor data to a safetensors file
|
|
try:
|
|
safetensors.torch.save_file({"conditioning_delta": tensor_data, "pooled_output": pooled_output}, file_path)
|
|
except Exception as e:
|
|
print("Error saving conditioning delta: ", e)
|
|
return ()
|
|
|
|
class LoadConditioningDelta:
|
|
"""
|
|
This node loads the conditioning delta from a safetensors file.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"condelta": (folder_paths.get_filename_list("ConDelta"), {"tooltip": "The name of the LoRA."}),
|
|
}
|
|
}
|
|
RETURN_TYPES = ("CONDITIONING", )
|
|
FUNCTION = "load"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def load(self, condelta):
|
|
#file_path = sys.path[0] + f"/models/ConDelta/{file_name}"
|
|
file_path = os.path.join(condelta_dir, condelta)
|
|
print("Loading conditioning delta from file: ", file_path)
|
|
# Load the tensor data from the safetensors file
|
|
tensor_data = safetensors.torch.load_file(file_path)["conditioning_delta"]
|
|
pooled_output = safetensors.torch.load_file(file_path)["pooled_output"]
|
|
# Wrap the tensor data in the expected format
|
|
if pooled_output is not None:
|
|
conditioning_delta = [[tensor_data, {"pooled_output": pooled_output}]]
|
|
else:
|
|
conditioning_delta = [[tensor_data, {}]]
|
|
#print(conditioning_delta)
|
|
return (conditioning_delta,)
|
|
|
|
# Load a conditioning delta from a file and apply it to the conditioning
|
|
class ApplyConDelta:
|
|
"""
|
|
This node applies a condelta to a conditioning.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning": ("CONDITIONING", ),
|
|
"condelta": (folder_paths.get_filename_list("ConDelta"), {"tooltip": "The name of the LoRA."}),
|
|
"strength": ("FLOAT", {"default": 1.0, "step": 0.01, "min": -100.0, "max": 100.0, })}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "apply"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def apply(self, conditioning, condelta, strength):
|
|
out = []
|
|
file_path = os.path.join(condelta_dir, condelta)
|
|
print("Loading conditioning delta from file: ", file_path)
|
|
|
|
# Load the tensor data from the safetensors file
|
|
tensor_data = safetensors.torch.load_file(file_path)["conditioning_delta"]
|
|
pooled_output = safetensors.torch.load_file(file_path)["pooled_output"]
|
|
|
|
# Wrap the tensor data in the expected format
|
|
if pooled_output is not None:
|
|
conditioning_delta = [[tensor_data, {"pooled_output": pooled_output}]]
|
|
else:
|
|
conditioning_delta = [[tensor_data, {}]]
|
|
|
|
if len(conditioning_delta) > 1:
|
|
logging.warning("Warning: ApplyConditioningDelta conditioning_delta contains more than 1 cond, only the first one will actually be applied to conditioning.")
|
|
|
|
cond_delta = conditioning_delta[0][0]
|
|
pooled_output_from = conditioning_delta[0][1].get("pooled_output", None)
|
|
|
|
for i in range(len(conditioning)):
|
|
t1 = conditioning[i][0]
|
|
pooled_output_to = conditioning[i][1].get("pooled_output", pooled_output_from)
|
|
t0 = cond_delta[:,:t1.shape[1]]
|
|
if t0.shape[1] < t1.shape[1]:
|
|
t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
|
|
|
|
tw = t1 + torch.mul(t0, strength)
|
|
t_to = conditioning[i][1].copy()
|
|
if pooled_output_from is not None and pooled_output_to is not None:
|
|
t_to["pooled_output"] = pooled_output_to + torch.mul(pooled_output_from, strength)
|
|
elif pooled_output_from is not None:
|
|
t_to["pooled_output"] = pooled_output_from
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
class ApplyConDeltaAutoScale:
|
|
"""
|
|
This node applies a conditioning delta to a conditioning, scaling the delta to match the conditioning.
|
|
It does this by determining the ratio of the means of the two vectors.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning": ("CONDITIONING", ),
|
|
"condelta": (folder_paths.get_filename_list("ConDelta"), {"tooltip": "The name of the LoRA."}),
|
|
"ratio_type": (["mean", "max", "median"], {"default": "median"}),
|
|
"strength": ("FLOAT", {"default": 1.0, "step": 0.01, "min": -100.0, "max": 100.0, }),
|
|
"clamp": ("BOOLEAN", {"default": False, "tooltip": "If true, will clamp the ConDelta's values."}),
|
|
"clamp_value": ("FLOAT", {"default": 3.0, "step": 0.01})
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "apply"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def apply(self, conditioning, ratio_type, condelta, strength, clamp, clamp_value):
|
|
out = []
|
|
file_path = os.path.join(condelta_dir, condelta)
|
|
print("Loading conditioning delta from file: ", file_path)
|
|
|
|
# Load the tensor data from the safetensors file
|
|
tensor_data = safetensors.torch.load_file(file_path)["conditioning_delta"]
|
|
pooled_output = safetensors.torch.load_file(file_path)["pooled_output"]
|
|
|
|
# Wrap the tensor data in the expected format
|
|
if pooled_output is not None:
|
|
conditioning_delta = [[tensor_data, {"pooled_output": pooled_output}]]
|
|
else:
|
|
conditioning_delta = [[tensor_data, {}]]
|
|
|
|
if len(conditioning_delta) > 1:
|
|
logging.warning("Warning: ApplyConditioningDelta conditioning_delta contains more than 1 cond, only the first one will actually be applied to conditioning.")
|
|
|
|
cond_delta = conditioning_delta[0][0]
|
|
pooled_output_from = conditioning_delta[0][1].get("pooled_output", None)
|
|
|
|
if clamp:
|
|
cond_delta = torch.clamp(cond_delta, -clamp_value, clamp_value)
|
|
pooled_output_from = torch.clamp(pooled_output_from, -clamp_value, clamp_value)
|
|
|
|
for i in range(len(conditioning)):
|
|
t1 = conditioning[i][0]
|
|
pooled_output_to = conditioning[i][1].get("pooled_output", pooled_output_from)
|
|
t0 = cond_delta[:,:t1.shape[1]]
|
|
if t0.shape[1] < t1.shape[1]:
|
|
t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
|
|
|
|
mean_ratio = tensor_ratio(t1, ratio_type) / tensor_ratio(t0, ratio_type)
|
|
|
|
tw = t1 + torch.mul(t0, strength * mean_ratio)
|
|
t_to = conditioning[i][1].copy()
|
|
if pooled_output_from is not None and pooled_output_to is not None:
|
|
mean_ratio_pooled = tensor_ratio(pooled_output_to, ratio_type) / tensor_ratio(pooled_output_from, ratio_type)
|
|
t_to["pooled_output"] = pooled_output_to + torch.mul(pooled_output_from, strength * mean_ratio_pooled)
|
|
elif pooled_output_from is not None:
|
|
t_to["pooled_output"] = pooled_output_from
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
|
|
class ConditioningScale:
|
|
"""
|
|
This node multiplies the conditioning by a scalar.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning": ("CONDITIONING", ), "scalar": ("FLOAT", {"default": 1.0, "step": 0.01})
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "multiply"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def multiply(self, conditioning, scalar):
|
|
out = []
|
|
|
|
for i in range(len(conditioning)):
|
|
t1 = conditioning[i][0]
|
|
pooled_output = conditioning[i][1].get("pooled_output", None)
|
|
conditioning_llama3 = conditioning[i][1].get("conditioning_llama3", None)
|
|
tw = torch.mul(t1, scalar)
|
|
t_to = conditioning[i][1].copy()
|
|
if pooled_output is not None:
|
|
t_to["pooled_output"] = torch.mul(pooled_output, scalar)
|
|
if conditioning_llama3 is not None:
|
|
t_to["conditioning_llama3"] = torch.mul(conditioning_llama3, scalar)
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
class ConditioningSubtract:
|
|
"""
|
|
This node subtracts one conditioning from another (A-B), producing a conditioning delta.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning_a": ("CONDITIONING", ), "conditioning_b": ("CONDITIONING", ),
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "subtract"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def subtract(self, conditioning_a, conditioning_b):
|
|
'''
|
|
out = []
|
|
|
|
cond_b = conditioning_b[0][0]
|
|
pooled_output_b = conditioning_b[0][1].get("pooled_output", None)
|
|
conditioning_llama3_b = conditioning_b[0][1].get("conditioning_llama3", None)
|
|
|
|
for i in range(len(conditioning_a)):
|
|
# Print all keys in conditioning_a and conditioning_b
|
|
if conditioning_a[i][1] is not None:
|
|
print(f"conditioning_a[{i}] keys: ", conditioning_a[i][1].keys())
|
|
if conditioning_b[i][1] is not None:
|
|
print(f"conditioning_b[{i}] keys: ", conditioning_b[i][1].keys())
|
|
|
|
t1 = conditioning_a[i][0]
|
|
pooled_output_a = conditioning_a[i][1].get("pooled_output", pooled_output_b)
|
|
conditioning_llama3_a = conditioning_a[i][1].get("conditioning_llama3", None)
|
|
t0 = cond_b[:,:t1.shape[1]]
|
|
if t0.shape[1] < t1.shape[1]:
|
|
t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
|
|
|
|
tw = t1 + torch.mul(t0, -1)
|
|
t_to = conditioning_a[i][1].copy()
|
|
if pooled_output_b is not None and pooled_output_a is not None:
|
|
t_to["pooled_output"] = pooled_output_a + torch.mul(pooled_output_b, -1)
|
|
elif pooled_output_b is not None:
|
|
t_to["pooled_output"] = pooled_output_b
|
|
|
|
if conditioning_llama3_b is not None and conditioning_llama3_a is not None:
|
|
t_to["conditioning_llama3"] = conditioning_llama3_a + torch.mul(conditioning_llama3_b, -1)
|
|
elif conditioning_llama3_b is not None:
|
|
t_to["conditioning_llama3"] = conditioning_llama3_b
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
'''
|
|
out = conditioning_subtract(conditioning_a, conditioning_b)
|
|
# Print the keys of the first conditioning in the output
|
|
if out[0][1] is not None:
|
|
print(f"Output conditioning keys: {list(out[0][1].keys())}")
|
|
else:
|
|
print("Output conditioning has no keys.")
|
|
return (out, )
|
|
|
|
class ThresholdConditioning:
|
|
"""
|
|
This node applies a threshold to the conditioning.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning": ("CONDITIONING", ), "threshold": ("FLOAT", {"default": 1.0, "step": 0.01, "min": -100.0, "max": 100.0, })
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "threshold"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def threshold(self, conditioning, threshold):
|
|
out = []
|
|
|
|
for i in range(len(conditioning)):
|
|
t1 = conditioning[i][0]
|
|
print(t1.size())
|
|
print(t1)
|
|
pooled_output = conditioning[i][1].get("pooled_output", None)
|
|
print(pooled_output.size())
|
|
tw = torch.where(torch.abs(t1) < threshold, 0, t1)
|
|
t_to = conditioning[i][1].copy()
|
|
if pooled_output is not None:
|
|
t_to["pooled_output"] = torch.where(torch.abs(pooled_output) < threshold, 0, pooled_output)
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
class ClampConDelta:
|
|
"""
|
|
Use tanh to clip the conditioning between -1 and 1.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning": ("CONDITIONING", )}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "clip"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def clip(self, conditioning):
|
|
out = []
|
|
|
|
for i in range(len(conditioning)):
|
|
t1 = conditioning[i][0]
|
|
pooled_output = conditioning[i][1].get("pooled_output", None)
|
|
tw = torch.tanh(t1)
|
|
t_to = conditioning[i][1].copy()
|
|
if pooled_output is not None:
|
|
t_to["pooled_output"] = torch.tanh(pooled_output)
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
class HardClampConDelta:
|
|
"""
|
|
Use clamp to clip the conditioning between -strength and strength.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning": ("CONDITIONING", ), "strength": ("FLOAT", {"default": 1.0, "step": 0.01})
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "hardclip"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def hardclip(self, conditioning, strength):
|
|
out = []
|
|
|
|
for i in range(len(conditioning)):
|
|
t1 = conditioning[i][0]
|
|
pooled_output = conditioning[i][1].get("pooled_output", None)
|
|
tw = torch.clamp(t1, -strength, strength)
|
|
t_to = conditioning[i][1].copy()
|
|
if pooled_output is not None:
|
|
t_to["pooled_output"] = torch.clamp(pooled_output, -strength, strength)
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
class PromptTravel:
|
|
"""
|
|
Travels between two prompts by a specified amount (can exceed 0-1 range).
|
|
|
|
Inputs:
|
|
prompt A (string),
|
|
prompt B (string),
|
|
travel_amount (float),
|
|
clip (for encoding prompts)
|
|
Returns: new conditioning
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s) -> InputTypeDict:
|
|
return {
|
|
"required": {
|
|
"prompt_a": (IO.STRING, {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}),
|
|
"prompt_b": (IO.STRING, {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}),
|
|
"travel_amount": ("FLOAT", {"default": 0.5, "step": 0.01, "min": -100.0, "max": 100.0}),
|
|
"clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."}),
|
|
}
|
|
}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "travel"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def travel(self, prompt_a, prompt_b, travel_amount, clip):
|
|
# Get the conditioning from the prompts
|
|
conditioning_a = get_conditioning_from_prompt(prompt_a, clip)
|
|
conditioning_b = get_conditioning_from_prompt(prompt_b, clip)
|
|
|
|
# Subtract the conditioning from prompt A from the conditioning from prompt B
|
|
conditioning_delta = conditioning_subtract(conditioning_b, conditioning_a)
|
|
|
|
# Scale the conditioning delta by the travel amount
|
|
conditioning_delta = conditioning_scale(conditioning_delta, travel_amount)
|
|
|
|
# Add the conditioning delta to the conditioning from prompt A
|
|
new_conditioning = conditioning_add(conditioning_a, conditioning_delta)
|
|
# Return the new conditioning
|
|
return (new_conditioning, )
|
|
|
|
class MultiDimensionalPromptTravel:
|
|
"""
|
|
Inputs:
|
|
Prompt (string, see special formatting below)
|
|
Clip (for encoding prompts)
|
|
|
|
Returns: new conditioning
|
|
|
|
Prompt format:
|
|
The prompt is written as normal, except that it contains sections enclosed in angle brackets <>. Each angle bracket section is formatted as follows: <base_subprompt:destination1:weight1:destination2:weight2:...>.
|
|
|
|
For instance:
|
|
A beautiful <cat:dog:0.5:wolf:-0.3> sitting on a <red:blue:0.25:green:0.1> mat.
|
|
|
|
First, it determines the base prompt by replacing each angle bracket section with the base subprompt. In the above example, the base prompt would be "A beautiful cat sitting on a red mat." It encodes this prompt to get the base conditioning.
|
|
|
|
Then, for each angle bracket section, it processes each destination:weight pair. For each pair, it:
|
|
1. Creates a prompt with the destination subprompt replacing the base subprompt
|
|
2. Encodes this destination prompt to get its conditioning
|
|
3. Subtracts the base conditioning from the destination conditioning to get a conditioning delta
|
|
4. Scales the conditioning delta by the specified weight
|
|
5. Adds the scaled conditioning delta to the base conditioning
|
|
|
|
For the above example, it would generate the following delta prompts:
|
|
1. A beautiful dog sitting on a red mat. (weight: 0.5)
|
|
2. A beautiful wolf sitting on a red mat. (weight: -0.3)
|
|
3. A beautiful cat sitting on a blue mat. (weight: 0.25)
|
|
4. A beautiful cat sitting on a green mat. (weight: 0.1)
|
|
|
|
The result is a conditioning that has traveled towards each of the destination subprompts by their specified weights.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s) -> InputTypeDict:
|
|
return {
|
|
"required": {
|
|
"prompt": (IO.STRING, {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded. Use special formatting for multi-dimensional travel."}),
|
|
"clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."}),
|
|
}
|
|
}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "travel"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def travel(self, prompt, clip):
|
|
# Find all bracket patterns and their positions
|
|
bracket_pattern = r"<([^>]+)>"
|
|
|
|
# Create base prompt by replacing each bracket with its first element
|
|
base_prompt = re.sub(bracket_pattern, lambda m: m.group(1).split(":")[0], prompt)
|
|
|
|
print("Original prompt: ", prompt)
|
|
print("Base prompt: ", base_prompt)
|
|
|
|
# Get the conditioning from the base prompt
|
|
base_conditioning = get_conditioning_from_prompt(base_prompt, clip)
|
|
original_base_conditioning = base_conditioning
|
|
|
|
matches = list(re.finditer(bracket_pattern, prompt))
|
|
|
|
for match in matches:
|
|
full_bracket = match.group(0)
|
|
bracket_content = match.group(1)
|
|
|
|
print(f"Processing bracket: {full_bracket}")
|
|
|
|
parts = bracket_content.split(":")
|
|
if len(parts) < 3:
|
|
print(f"Invalid delta prompt format (need at least base:destination:weight): {bracket_content}")
|
|
continue
|
|
if len(parts) % 2 == 0:
|
|
print(f"Invalid delta prompt format (need base:destination:weight pairs): {bracket_content}")
|
|
continue
|
|
|
|
base_subprompt = parts[0]
|
|
|
|
for i in range(1, len(parts), 2):
|
|
if i + 1 >= len(parts):
|
|
print(f"Missing weight for destination '{parts[i]}' in: {bracket_content}")
|
|
break
|
|
|
|
destination_subprompt = parts[i]
|
|
try:
|
|
travel_distance = float(parts[i + 1])
|
|
except ValueError:
|
|
print(f"Invalid travel distance '{parts[i + 1]}' for destination '{destination_subprompt}': {bracket_content}")
|
|
continue
|
|
|
|
# Let's build the destination prompt from the original prompt by replacing just the current bracket
|
|
# and all other brackets with their base values.
|
|
|
|
# To do this, we can iterate through all matches and build the string segment by segment.
|
|
|
|
current_prompt_for_dest = ""
|
|
last_index = 0
|
|
for m in matches:
|
|
# Add the text between the last match and this one
|
|
current_prompt_for_dest += prompt[last_index:m.start()]
|
|
|
|
if m.group(0) == full_bracket:
|
|
# This is the bracket we're processing, so use the destination subprompt
|
|
current_prompt_for_dest += destination_subprompt
|
|
else:
|
|
# This is a different bracket, so use its base subprompt
|
|
current_prompt_for_dest += m.group(1).split(":")[0]
|
|
|
|
last_index = m.end()
|
|
|
|
# Add the remaining part of the prompt after the last match
|
|
current_prompt_for_dest += prompt[last_index:]
|
|
|
|
print(f" Processing: {base_subprompt} -> {destination_subprompt} (weight: {travel_distance})")
|
|
print(f" Full destination prompt: {current_prompt_for_dest}")
|
|
|
|
conditioning_destination = get_conditioning_from_prompt(current_prompt_for_dest, clip)
|
|
|
|
conditioning_delta = conditioning_subtract(conditioning_destination, original_base_conditioning)
|
|
|
|
conditioning_delta = conditioning_scale(conditioning_delta, travel_distance)
|
|
|
|
base_conditioning = conditioning_add(base_conditioning, conditioning_delta)
|
|
|
|
return (base_conditioning, )
|
|
|
|
class ConditioningAddConDelta:
|
|
"""
|
|
This node adds a conditioning delta with a specific strength to a conditioning.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning_base": ("CONDITIONING", ), "conditioning_delta": ("CONDITIONING", ),
|
|
"conditioning_delta_strength": ("FLOAT", {"default": 1.0, "step": 0.01, "min": -100.0, "max": 100.0, })
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "addDelta"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def addDelta(self, conditioning_base, conditioning_delta, conditioning_delta_strength):
|
|
'''
|
|
out = []
|
|
|
|
if len(conditioning_delta) > 1:
|
|
logging.warning("Warning: ExtendedConditioningAverage conditioning_delta contains more than 1 cond, only the first one will actually be applied to conditioning_base.")
|
|
|
|
cond_delta = conditioning_delta[0][0]
|
|
pooled_output_from = conditioning_delta[0][1].get("pooled_output", None)
|
|
conditioning_llama3_from = conditioning_delta[0][1].get("conditioning_llama3", None)
|
|
|
|
for i in range(len(conditioning_base)):
|
|
t1 = conditioning_base[i][0]
|
|
pooled_output_to = conditioning_base[i][1].get("pooled_output", pooled_output_from)
|
|
conditioning_llama3_to = conditioning_base[i][1].get("conditioning_llama3", conditioning_llama3_from)
|
|
t0 = cond_delta[:,:t1.shape[1]]
|
|
if t0.shape[1] < t1.shape[1]:
|
|
t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
|
|
|
|
tw = t1 + torch.mul(t0, conditioning_delta_strength)
|
|
t_to = conditioning_base[i][1].copy()
|
|
if pooled_output_from is not None and pooled_output_to is not None:
|
|
t_to["pooled_output"] = pooled_output_to + torch.mul(pooled_output_from, conditioning_delta_strength)
|
|
elif pooled_output_from is not None:
|
|
t_to["pooled_output"] = pooled_output_from
|
|
|
|
if conditioning_llama3_from is not None and conditioning_llama3_to is not None:
|
|
t_to["conditioning_llama3"] = conditioning_llama3_to + torch.mul(conditioning_llama3_from, conditioning_delta_strength)
|
|
elif conditioning_llama3_from is not None:
|
|
t_to["conditioning_llama3"] = conditioning_llama3_from
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )'''
|
|
conditioning_delta = conditioning_scale(conditioning_delta, conditioning_delta_strength)
|
|
out = conditioning_add(conditioning_base, conditioning_delta)
|
|
|
|
# Print the keys of the first conditioning in the output
|
|
if out[0][1] is not None:
|
|
print(f"Output conditioning keys: {list(out[0][1].keys())}")
|
|
else:
|
|
print("Output conditioning has no keys.")
|
|
return (out, )
|
|
|
|
class ConditioningAddConDeltaAutoScale:
|
|
"""
|
|
This node adds a conditioning delta with a specific strength to a conditioning, scaling the delta to match the conditioning.
|
|
It does this by determining the ratio of the means of the two vectors.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning_base": ("CONDITIONING", ), "conditioning_delta": ("CONDITIONING", ),
|
|
"ratio_type": (["mean", "max", "median"], {"default": "median"}),
|
|
"conditioning_delta_strength": ("FLOAT", {"default": 1.0, "step": 0.01, "min": -100.0, "max": 100.0, }),
|
|
"clamp": ("BOOLEAN", {"default": False, "tooltip": "If true, will clamp the ConDeltas's values."}),
|
|
"clamp_value": ("FLOAT", {"default": 3.0, "step": 0.01})
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "addDelta"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def addDelta(self, conditioning_base, ratio_type, conditioning_delta, conditioning_delta_strength, clamp, clamp_value):
|
|
out = []
|
|
|
|
if len(conditioning_delta) > 1:
|
|
logging.warning("Warning: ExtendedConditioningAverage conditioning_delta contains more than 1 cond, only the first one will actually be applied to conditioning_base.")
|
|
|
|
cond_delta = conditioning_delta[0][0]
|
|
pooled_output_from = conditioning_delta[0][1].get("pooled_output", None)
|
|
|
|
if clamp:
|
|
cond_delta = torch.clamp(cond_delta, -clamp_value, clamp_value)
|
|
pooled_output_from = torch.clamp(pooled_output_from, -clamp_value, clamp_value)
|
|
|
|
for i in range(len(conditioning_base)):
|
|
t1 = conditioning_base[i][0]
|
|
pooled_output_to = conditioning_base[i][1].get("pooled_output", pooled_output_from)
|
|
t0 = cond_delta[:,:t1.shape[1]]
|
|
if t0.shape[1] < t1.shape[1]:
|
|
t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
|
|
|
|
mean_ratio = tensor_ratio(t1, ratio_type) / tensor_ratio(t0, ratio_type)
|
|
|
|
tw = t1 + torch.mul(t0, conditioning_delta_strength * mean_ratio)
|
|
t_to = conditioning_base[i][1].copy()
|
|
if pooled_output_from is not None and pooled_output_to is not None:
|
|
mean_ratio_pooled = tensor_ratio(pooled_output_to, ratio_type) / tensor_ratio(pooled_output_from, ratio_type)
|
|
t_to["pooled_output"] = pooled_output_to + torch.mul(pooled_output_from, conditioning_delta_strength * mean_ratio_pooled)
|
|
elif pooled_output_from is not None:
|
|
t_to["pooled_output"] = pooled_output_from
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
class ConditioningAverageMultiple:
|
|
"""
|
|
This node averages up to 10 conditioning vectors
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"conditioning0": ("CONDITIONING", ),
|
|
}, "optional": {
|
|
"conditioning1": ("CONDITIONING", ),
|
|
"conditioning2": ("CONDITIONING", ),
|
|
"conditioning3": ("CONDITIONING", ),
|
|
"conditioning4": ("CONDITIONING", ),
|
|
"conditioning5": ("CONDITIONING", ),
|
|
"conditioning6": ("CONDITIONING", ),
|
|
"conditioning7": ("CONDITIONING", ),
|
|
"conditioning8": ("CONDITIONING", ),
|
|
"conditioning9": ("CONDITIONING", ),
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "average"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def average(self, conditioning0, conditioning1 = None, conditioning2 = None, conditioning3 = None, conditioning4 = None, conditioning5 = None, conditioning6 = None, conditioning7 = None, conditioning8 = None, conditioning9 = None):
|
|
conditionings = [conditioning0, conditioning1, conditioning2, conditioning3, conditioning4, conditioning5, conditioning6, conditioning7, conditioning8, conditioning9]
|
|
|
|
# Add them all up, counting the number that aren't null
|
|
count = 0
|
|
sum = None
|
|
for i in range(10):
|
|
if conditionings[i] is not None:
|
|
count += 1
|
|
if sum is None:
|
|
sum = conditionings[i]
|
|
else:
|
|
# Call ConditioningAddConDelta with a strength of 1.0
|
|
sum = self.addDelta(sum, conditionings[i], 1.0)
|
|
|
|
|
|
# Divide by the count
|
|
if count > 0:
|
|
sum = self.multiply(sum, 1.0 / count)
|
|
|
|
# Return the result
|
|
return (sum, )
|
|
|
|
|
|
def addDelta(self, conditioning_base, conditioning_delta, conditioning_delta_strength):
|
|
out = []
|
|
|
|
if len(conditioning_delta) > 1:
|
|
logging.warning("Warning: ExtendedConditioningAverage conditioning_delta contains more than 1 cond, only the first one will actually be applied to conditioning_base.")
|
|
|
|
cond_delta = conditioning_delta[0][0]
|
|
pooled_output_from = conditioning_delta[0][1].get("pooled_output", None)
|
|
|
|
for i in range(len(conditioning_base)):
|
|
t1 = conditioning_base[i][0]
|
|
pooled_output_to = conditioning_base[i][1].get("pooled_output", pooled_output_from)
|
|
t0 = cond_delta[:,:t1.shape[1]]
|
|
if t0.shape[1] < t1.shape[1]:
|
|
t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
|
|
|
|
tw = t1 + torch.mul(t0, conditioning_delta_strength)
|
|
t_to = conditioning_base[i][1].copy()
|
|
if pooled_output_from is not None and pooled_output_to is not None:
|
|
t_to["pooled_output"] = pooled_output_to + torch.mul(pooled_output_from, conditioning_delta_strength)
|
|
elif pooled_output_from is not None:
|
|
t_to["pooled_output"] = pooled_output_from
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
|
|
def multiply(self, conditioning, scalar):
|
|
out = []
|
|
|
|
for i in range(len(conditioning)):
|
|
t1 = conditioning[i][0]
|
|
pooled_output = conditioning[i][1].get("pooled_output", None)
|
|
tw = torch.mul(t1, scalar)
|
|
t_to = conditioning[i][1].copy()
|
|
if pooled_output is not None:
|
|
t_to["pooled_output"] = torch.mul(pooled_output, scalar)
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
class ExtendedConditioningAverage:
|
|
"""
|
|
This node is the basic Conditioning Average node, but with the weight capped at [-9, 10] instead of [0, 1]. It's otherwise a drop-in replacement.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {"conditioning_to": ("CONDITIONING", ), "conditioning_from": ("CONDITIONING", ),
|
|
"conditioning_to_strength": ("FLOAT", {"default": 1.0, "step": 0.01, "min": -100.0, "max": 100.0, })
|
|
}}
|
|
RETURN_TYPES = ("CONDITIONING",)
|
|
FUNCTION = "addWeighted"
|
|
|
|
CATEGORY = "conditioning"
|
|
|
|
def addWeighted(self, conditioning_to, conditioning_from, conditioning_to_strength):
|
|
out = []
|
|
|
|
if len(conditioning_from) > 1:
|
|
logging.warning("Warning: ExtendedConditioningAverage conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
|
|
|
|
cond_from = conditioning_from[0][0]
|
|
pooled_output_from = conditioning_from[0][1].get("pooled_output", None)
|
|
|
|
for i in range(len(conditioning_to)):
|
|
t1 = conditioning_to[i][0]
|
|
pooled_output_to = conditioning_to[i][1].get("pooled_output", pooled_output_from)
|
|
t0 = cond_from[:,:t1.shape[1]]
|
|
if t0.shape[1] < t1.shape[1]:
|
|
t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
|
|
|
|
tw = torch.mul(t1, conditioning_to_strength) + torch.mul(t0, (1.0 - conditioning_to_strength))
|
|
t_to = conditioning_to[i][1].copy()
|
|
if pooled_output_from is not None and pooled_output_to is not None:
|
|
t_to["pooled_output"] = torch.mul(pooled_output_to, conditioning_to_strength) + torch.mul(pooled_output_from, (1.0 - conditioning_to_strength))
|
|
elif pooled_output_from is not None:
|
|
t_to["pooled_output"] = pooled_output_from
|
|
|
|
n = [tw, t_to]
|
|
out.append(n)
|
|
return (out, )
|
|
|
|
|
|
# A dictionary that contains all nodes you want to export with their names
|
|
# NOTE: names should be globally unique
|
|
NODE_CLASS_MAPPINGS = {
|
|
"GetConDeltaFromPrompt": GetConDeltaFromPrompt,
|
|
"ExtendedConditioningAverage": ExtendedConditioningAverage,
|
|
"ConditioningSubtract": ConditioningSubtract,
|
|
"ConditioningAddConDelta": ConditioningAddConDelta,
|
|
"ConditioningAddConDeltaAutoScale": ConditioningAddConDeltaAutoScale,
|
|
"SaveConditioningDelta": SaveConditioningDelta,
|
|
"LoadConditioningDelta": LoadConditioningDelta,
|
|
"ConditioningScale": ConditioningScale,
|
|
"ApplyConDelta": ApplyConDelta,
|
|
"ApplyConDeltaAutoScale": ApplyConDeltaAutoScale,
|
|
"ThresholdConditioning": ThresholdConditioning,
|
|
"ClampConDelta": ClampConDelta,
|
|
"HardClampConDelta": HardClampConDelta,
|
|
"MaskConDelta": MaskConDelta,
|
|
"ConditioningAverageMultiple": ConditioningAverageMultiple,
|
|
"ConditioningGetNoise": ConditioningGetNoise,
|
|
"ConditioningGetRandom": ConditioningGetRandom,
|
|
"CFGlessNegativePrompt": CFGlessNegativePrompt,
|
|
"QuickConDelta": QuickConDelta,
|
|
"PromptTravel": PromptTravel,
|
|
"MultiDimensionalPromptTravel": MultiDimensionalPromptTravel,
|
|
}
|
|
|
|
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"ExtendedConditioningAverage": "Conditioning Average Extended",
|
|
"ConditioningSubtract": "Conditioning Subtract (Create ConDelta)",
|
|
"ConditioningAddConDelta": "Conditioning Add ConDelta",
|
|
"ConditioningAddConDeltaAutoScale": "Conditioning Add ConDelta AutoScale",
|
|
"SaveConditioningDelta": "Save ConDelta",
|
|
"LoadConditioningDelta": "Load ConDelta",
|
|
"ConditioningScale": "ConDelta Scale (Multiple ConDelta by a Float)",
|
|
"ApplyConDelta": "Apply ConDelta",
|
|
"ApplyConDeltaAutoScale": "Apply ConDelta AutoScale",
|
|
"ThresholdConditioning": "Threshold ConDelta",
|
|
"ClampConDelta": "Smooth Clamp ConDelta between -1 and 1",
|
|
"HardClampConDelta": "Hard Clamp ConDelta between -strength and strength",
|
|
"MaskConDelta": "Mask ConDelta (Clamp ConDelta with another ConDelta or Conditioning)",
|
|
"ConditioningAverageMultiple": "Average Multiple Conditionings or ConDeltas",
|
|
"ConditioningGetNoise": "Get Noise from a Conditioning or ConDelta",
|
|
"ConditioningGetRandom": "Get random Gaussian noise from a Conditioning or ConDelta",
|
|
"GetConDeltaFromPrompt": "Get ConDelta from Prompt",
|
|
"CFGlessNegativePrompt": "CFG-less Negative Prompt",
|
|
"QuickConDelta": "Quick ConDelta",
|
|
"PromptTravel": "Prompt Travel",
|
|
"MultiDimensionalPromptTravel": "Multi-Dimensional Prompt Travel",
|
|
}
|
|
|
|
print("\033[94mConDelta Nodes: \033[92mLoaded\033[0m")
|