10 Commits
299 changed files with 270 additions and 22692 deletions
-4
View File
@@ -1,13 +1,9 @@
from .nodes import (
BuildGif,
SpecialClipLoader,
MonacoPrompt,
)
NODE_CLASS_MAPPINGS = {
"Build Gif": BuildGif,
"Special CLIP Loader": SpecialClipLoader,
"Monaco Prompt": MonacoPrompt,
}
WEB_DIRECTORY = ("./web/dist", ["app.bundle.js"])
+2
View File
@@ -3,6 +3,7 @@ from custom_nodes.KepPromptLang.lib.actions.diff import DiffAction
from custom_nodes.KepPromptLang.lib.actions.mult import MultiplyAction
from custom_nodes.KepPromptLang.lib.actions.neg import NegAction
from custom_nodes.KepPromptLang.lib.actions.norm import NormAction
from custom_nodes.KepPromptLang.lib.actions.pos_scale import PosScaleAction
from custom_nodes.KepPromptLang.lib.actions.rand import RandAction
from custom_nodes.KepPromptLang.lib.actions.scale_dims import ScaleDims
from custom_nodes.KepPromptLang.lib.actions.set_dims import SetDims
@@ -20,3 +21,4 @@ register_action(SlerpAction)
register_action(AverageAction)
register_action(ScaleDims)
register_action(SetDims)
register_action(PosScaleAction)
+9 -2
View File
@@ -1,6 +1,6 @@
from abc import ABC, abstractmethod
from enum import Enum
from typing import Union, List
from typing import Union, List, TypedDict, Tuple
from torch import Tensor
from torch.nn import Embedding
@@ -13,6 +13,13 @@ class ActionArity(Enum):
SINGLE = 1
MULTI = 2
class PostModifiers(TypedDict):
"""
A dictionary of post modifiers for an action result.
"""
position_embed_scale: Union[float, None]
class Action(ABC):
@property
@abstractmethod
@@ -67,7 +74,7 @@ class Action(ABC):
pass
@abstractmethod
def get_result(self, embedding_module: Embedding) -> Tensor:
def get_result(self, embedding_module: Embedding) -> Union[Tensor, Tuple[Tensor, PostModifiers]]:
"""
Get the result of this action. This is called when the embeddings are being calculated.
:param embedding_module: The embedding module to use to get the base embeddings for tokens.
+65
View File
@@ -0,0 +1,65 @@
from typing import Tuple, List
import torch
from torch.nn import Embedding
from custom_nodes.KepPromptLang.lib.action.base import (
Action,
PostModifiers,
MultiArgAction,
)
from custom_nodes.KepPromptLang.lib.actions.types import SegOrAction
class PosScaleAction(MultiArgAction):
grammar = 'posScale(" arg+ ")"'
name = "posScale"
chars = ["[", "]"]
def __init__(self, args: List[List[SegOrAction]]) -> None:
super().__init__(args)
if len(args) != 2:
raise ValueError("PosScale action should have exactly two arguments")
self.target_arg = args[0]
self._parse_multiplier(args[1])
def _parse_multiplier(self, arg: List[SegOrAction]) -> None:
if len(arg) != 1:
raise ValueError(
"PosScale actions multiplier should have exactly one segment"
)
multiplier_seg_or_action = arg[0]
if isinstance(multiplier_seg_or_action, Action):
raise ValueError("PosScale actions multiplier must be a number")
try:
self.parsed_multiplier = float(multiplier_seg_or_action.text)
except ValueError:
raise ValueError(
"PosScale action should have an integer/float as the multiplier"
)
def token_length(self) -> int:
"""
PosScale modifies the posional embeddings of the base segment, so the length is the length of the base segment
:return:
"""
total_length = 0
for seg_or_action in self.target_arg:
total_length += seg_or_action.token_length()
return total_length
def get_result(self, embedding_module: Embedding) -> Tuple[torch.Tensor, PostModifiers]:
all_embeddings = []
for seg_or_action in self.target_arg:
if isinstance(seg_or_action, Action):
all_embeddings.append(seg_or_action.get_result(embedding_module))
else:
all_embeddings.append(seg_or_action.get_embeddings(embedding_module))
target_embeddings = torch.cat(all_embeddings, dim=1)
return target_embeddings, {"position_embed_scale": self.parsed_multiplier}
+23
View File
@@ -0,0 +1,23 @@
{
"architectures": [
"CLIPTextModel"
],
"attention_dropout": 0.0,
"bos_token_id": 0,
"dropout": 0.0,
"eos_token_id": 2,
"hidden_act": "gelu",
"hidden_size": 1280,
"initializer_factor": 1.0,
"initializer_range": 0.02,
"intermediate_size": 5120,
"layer_norm_eps": 1e-05,
"max_position_embeddings": 77,
"model_type": "clip_text_model",
"num_attention_heads": 20,
"num_hidden_layers": 32,
"pad_token_id": 1,
"projection_dim": 1280,
"torch_dtype": "float32",
"vocab_size": 49408
}
+33 -1
View File
@@ -7,6 +7,8 @@ from transformers import CLIPTextConfig, modeling_utils
from comfy import model_management
import comfy.ops
from comfy.sd1_clip import SD1ClipModel
from comfy.sdxl_clip import SDXLClipModel
from custom_nodes.KepPromptLang.lib.action.base import Action
from custom_nodes.KepPromptLang.lib.actions.types import SegOrAction
from custom_nodes.KepPromptLang.lib.fun_clip_stuff import PromptLangTextModel
@@ -14,7 +16,7 @@ from custom_nodes.KepPromptLang.lib.parser.prompt_segment import PromptSegment
# Methods with no comment can be assumed to be the same as comfy.sd1_clip.SD1ClipModel
class PromptLangClipModel(torch.nn.Module):
class PromptLangSDClipModel(torch.nn.Module):
"""Uses the CLIP transformer encoder for text (from huggingface)"""
LAYERS = [
"last",
@@ -209,3 +211,33 @@ class PromptLangClipModel(torch.nn.Module):
if (len(output) == 0):
return z_empty.cpu(), first_pooled.cpu()
return torch.cat(output, dim=-2).cpu(), first_pooled.cpu()
class PromptLangSD1ClipModel(SD1ClipModel):
def __init__(self, device="cpu", dtype=None, clip_name="l", clip_model=PromptLangSDClipModel):
super().__init__()
self.clip_name = clip_name
self.clip = "clip_{}".format(self.clip_name)
setattr(self, self.clip, clip_model(device=device, dtype=dtype))
class PromptLangSDXLClipModel(SDXLClipModel):
def __init__(self, device="cpu", dtype=None) -> None:
# Skip SDXLClipModel's init
super(SDXLClipModel, self).__init__()
self.clip_l = PromptLangSDClipModel(layer="hidden", layer_idx=11, device=device, dtype=dtype)
self.clip_l.layer_norm_hidden_state = False
self.clip_g = PromptLangSDXLClipG(device, dtype)
class PromptLangSDXLClipG(PromptLangSDClipModel):
def __init__(self, device="cpu", max_length=77, freeze=True, layer="penultimate", layer_idx=None, textmodel_path=None, dtype=None):
if layer == "penultimate":
layer="hidden"
layer_idx=-2
textmodel_json_config = os.path.join(os.path.dirname(os.path.realpath(__file__)), "clip_config_bigg.json")
super().__init__(device=device, freeze=freeze, layer=layer, layer_idx=layer_idx, textmodel_json_config=textmodel_json_config, textmodel_path=textmodel_path, dtype=dtype)
self.empty_tokens = [[49406] + [49407] + [0] * 75]
self.layer_norm_hidden_state = False
def load_sd(self, sd):
return super().load_sd(sd)
+80 -23
View File
@@ -1,10 +1,11 @@
from typing import Optional, Tuple, Union, List
from typing import Optional, Tuple, Union, List, TypedDict
from importlib_metadata import version as import_version
from packaging import version
import torch
from transformers import CLIPTextConfig
from transformers.modeling_outputs import BaseModelOutputWithPooling
from transformers.models.clip.modeling_clip import (
_expand_mask,
CLIPTextEmbeddings,
CLIPTextTransformer,
CLIPTextModel,
@@ -23,6 +24,17 @@ def slerp(val, low, high):
res = (torch.sin((1.0-val)*omega)/so).unsqueeze(1)*low + (torch.sin(val*omega)/so).unsqueeze(1) * high
return res
class PosModifier(TypedDict):
"""
A dictionary of post modifiers for an action result.
"""
position_embed_scale: Union[float]
start_idx: Union[int]
end_idx: Union[int]
class PromptLangCLIPTextEmbeddings(CLIPTextEmbeddings):
def __init__(self, config: CLIPTextConfig):
super().__init__(config)
@@ -39,14 +51,30 @@ class PromptLangCLIPTextEmbeddings(CLIPTextEmbeddings):
raise ValueError("You have to specify input_dicts")
batches = []
pos_modifiers: List[List[PosModifier]] = []
for batch_idx, batch in enumerate(input_dicts):
results = []
batch_pos_modifiers = []
token_idx = 0
for seg_or_action in batch:
if isinstance(seg_or_action, Action):
results.append(seg_or_action.get_result(self.token_embedding))
action_result = seg_or_action.get_result(self.token_embedding)
if isinstance(action_result, tuple):
result, post_modifiers = action_result
if post_modifiers["position_embed_scale"] is not None:
post_modifiers["start_idx"] = token_idx
post_modifiers["end_idx"] = (
token_idx + seg_or_action.token_length()
)
batch_pos_modifiers.append(post_modifiers)
else:
result = action_result
else:
results.append(seg_or_action.get_embeddings(self.token_embedding))
result = seg_or_action.get_embeddings(self.token_embedding)
results.append(result)
token_idx += seg_or_action.token_length()
batches.append(results)
pos_modifiers.append(batch_pos_modifiers)
seq_length = batches[0][0].shape[-2]
@@ -60,8 +88,16 @@ class PromptLangCLIPTextEmbeddings(CLIPTextEmbeddings):
else:
embeds.append(torch.cat(batch, dim=-2))
position_embeddings = self.position_embedding(position_ids)
embeddings = torch.cat(embeds, dim=0) + position_embeddings
for idx, batch_pos_modifiers in enumerate(pos_modifiers):
position_embeddings = self.position_embedding(position_ids)
if len(batch_pos_modifiers) > 0:
print(f"Found {len(batch_pos_modifiers)} pos modifiers for batch {idx}")
for post_modifier in batch_pos_modifiers:
position_embeddings[
0, post_modifier["start_idx"] : post_modifier["end_idx"]
] *= post_modifier["position_embed_scale"]
embeds[idx] = embeds[idx] + position_embeddings
embeddings = torch.cat(embeds, dim=0)
return embeddings
@@ -70,6 +106,40 @@ class PrompLangCLIPTextTransformer(CLIPTextTransformer):
def __init__(self, config: CLIPTextConfig):
super().__init__(config)
self.embeddings = PromptLangCLIPTextEmbeddings(config)
self.transformers_version = version.parse(import_version('transformers'))
def process_attention_mask(self, hidden_states, attention_mask, bsz, seq_len):
# Parse the transformer version
input_shape = torch.Size([bsz, seq_len])
v4_30 = version.parse('4.30.0')
v4_35 = version.parse('4.35')
if self.transformers_version < v4_30:
print("Using transformers < 4.30.0")
causal_attention_mask = self._build_causal_attention_mask(bsz, seq_len, hidden_states.dtype).to(
hidden_states.device)
elif v4_30 <= self.transformers_version < v4_35:
print("Using transformers >= 4.30.0 and <= 4.34.*")
from transformers.models.clip.modeling_clip import _make_causal_mask
causal_attention_mask = _make_causal_mask(input_shape, hidden_states.dtype, device=hidden_states.device)
else:
print("Using transformers >= 4.35")
from transformers.modeling_attn_mask_utils import _create_4d_causal_attention_mask
causal_attention_mask = _create_4d_causal_attention_mask(
input_shape, hidden_states.dtype, device=hidden_states.device
)
# Expand attention_mask if it exists
if attention_mask is not None:
# Import _expand_mask or _prepare_4d_attention_mask based on version
if self.transformers_version < v4_35:
from transformers.models.clip.modeling_clip import _expand_mask
attention_mask = _expand_mask(attention_mask, hidden_states.dtype)
else:
from transformers.modeling_attn_mask_utils import _prepare_4d_attention_mask
attention_mask = _prepare_4d_attention_mask(attention_mask, hidden_states.dtype)
return causal_attention_mask, attention_mask
def forward(
self,
@@ -101,24 +171,8 @@ class PrompLangCLIPTextTransformer(CLIPTextTransformer):
bsz = len(input_ids)
# TODO: Properly gather this
seq_len = 77
input_shape = torch.Size([bsz, seq_len])
# CLIP's text model uses causal mask, prepare it here.
# https://github.com/openai/CLIP/blob/cfcffb90e69f37bf2ff1e988237a0fbe41f33c04/clip/model.py#L324
## VERSION DIFF ##
# transformers < 4.30.0
if hasattr(self, "_build_causal_attention_mask"):
print("Using transformers < 4.30.0")
causal_attention_mask = self._build_causal_attention_mask(bsz, seq_len, hidden_states.dtype).to(hidden_states.device)
else:
# transformers >= 4.30.0
print("Using transformers >= 4.30.0")
from transformers.models.clip.modeling_clip import _make_causal_mask
causal_attention_mask = _make_causal_mask(input_shape, hidden_states.dtype, device=hidden_states.device)
# expand attention_mask
if attention_mask is not None:
# [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len]
attention_mask = _expand_mask(attention_mask, hidden_states.dtype)
causal_attention_mask, attention_mask = self.process_attention_mask(hidden_states, attention_mask, bsz, seq_len)
encoder_outputs = self.encoder(
inputs_embeds=hidden_states,
@@ -143,6 +197,9 @@ class PrompLangCLIPTextTransformer(CLIPTextTransformer):
else:
if seg_or_action.text == '__PAD__':
break
# Is a segment, and isn't the pad segment
idx += seg_or_action.token_length()
eot_idx.append(idx)
# text_embeds.shape = [batch_size, sequence_length, transformer.width]
# take features from the eot embedding (eot_token is the highest number in each sequence)
+2 -7
View File
@@ -2,15 +2,10 @@ from typing import List
from lark import Transformer, Token
from comfy.sd1_clip import SD1Tokenizer
from comfy.sd1_clip import SDTokenizer
from custom_nodes.KepPromptLang.lib.action.base import Action, ActionArity
from custom_nodes.KepPromptLang.lib.actions.diff import DiffAction
from custom_nodes.KepPromptLang.lib.actions.rand import RandAction
from custom_nodes.KepPromptLang.lib.parser.registration import get_action_by_name
from custom_nodes.KepPromptLang.lib.parser.utils import build_prompt_segment
from custom_nodes.KepPromptLang.lib.actions.neg import NegAction
from custom_nodes.KepPromptLang.lib.actions.norm import NormAction
from custom_nodes.KepPromptLang.lib.actions.sum import SumAction
from custom_nodes.KepPromptLang.lib.parser.prompt_segment import PromptSegment
@@ -18,7 +13,7 @@ class PromptTransformer(Transformer):
# def WORD(self, items):
# return items
def __init__(self, tokenizer: SD1Tokenizer):
def __init__(self, tokenizer: SDTokenizer):
super().__init__()
self.tokenizer = tokenizer
+2 -2
View File
@@ -1,6 +1,6 @@
from lark import Token
from comfy.sd1_clip import SD1Tokenizer
from comfy.sd1_clip import SDTokenizer
from custom_nodes.KepPromptLang.lib.parser.prompt_segment import PromptSegment
@@ -11,7 +11,7 @@ def flatten_tree(tree):
return [str(tree.data)] + sum([flatten_tree(child) for child in tree.children], [])
def build_prompt_segment(text: str, tokenizer: SD1Tokenizer) -> PromptSegment:
def build_prompt_segment(text: str, tokenizer: SDTokenizer) -> PromptSegment:
split_text = text.split(" ")
tokens = []
for word in split_text:
+27 -7
View File
@@ -1,18 +1,18 @@
from typing import List
from typing import List, Dict
from lark import Tree
from comfy.sd1_clip import SD1Tokenizer
from comfy.sd1_clip import SD1Tokenizer, SDTokenizer
from custom_nodes.KepPromptLang.lib.actions.types import SegOrAction
from custom_nodes.KepPromptLang.lib.parser import PromptParser
from custom_nodes.KepPromptLang.lib.parser.transformer import PromptTransformer
from custom_nodes.KepPromptLang.lib.parser.prompt_segment import PromptSegment
class PromptLangTokenizer(SD1Tokenizer):
def __init__(self, tokenizer_path=None, max_length=77, pad_with_end=True, embedding_directory=None, embedding_size=768, embedding_key='clip_l', special_tokens=None):
super().__init__(tokenizer_path, max_length, pad_with_end, embedding_directory, embedding_size, embedding_key)
class PromptLangSDTokenizer(SDTokenizer):
def __init__(self, tokenizer_path=None, max_length=77, pad_with_end=True, embedding_directory=None, embedding_size=768, embedding_key='clip_l'):
super().__init__(tokenizer_path, max_length, pad_with_end, embedding_directory, embedding_size, embedding_key)
"""
Doesn't actually tokenize...
Returns batches of segments and actions
@@ -43,9 +43,9 @@ class PromptLangTokenizer(SD1Tokenizer):
# If the segment is too large to fit in a single batch, pad the current batch and start a new one
if num_tokens + batch_size > self.max_length - 1:
remaining_length = self.max_length - batch_size - 1 # -1 for end token
remaining_length = self.max_length - batch_size
# Pad batch
batch.append(PromptSegment("__PAD__", [self.end_token] + [pad_token] * remaining_length - 1))
batch.append(PromptSegment("__PAD__", [self.end_token] + [pad_token] * (remaining_length - 1))) # -1 for end token
batched_segments.append(batch)
# start new batch
@@ -66,3 +66,23 @@ class PromptLangTokenizer(SD1Tokenizer):
# batch_size_info(batch)
return batched_segments
class PromptLangSD1Tokenizer(SD1Tokenizer):
def __init__(self, embedding_directory=None, clip_name='l', tokenizer=PromptLangSDTokenizer) -> None:
super().__init__(embedding_directory, clip_name, tokenizer)
class PromptLangSDXLClipGTokenizer(PromptLangSDTokenizer):
def __init__(self, tokenizer_path=None, embedding_directory=None):
super().__init__(tokenizer_path, pad_with_end=False, embedding_directory=embedding_directory, embedding_size=1280, embedding_key='clip_g')
class PromptLangSDXLTokenizer(SD1Tokenizer):
def __init__(self, embedding_directory=None) -> None:
self.clip_l = PromptLangSDTokenizer(embedding_directory=embedding_directory)
self.clip_g = PromptLangSDXLClipGTokenizer(embedding_directory=embedding_directory)
def tokenize_with_weights(self, text:str, return_word_ids=False) -> Dict[str, List[List[SegOrAction]]]:
out = {}
out["g"] = self.clip_g.tokenize_with_weights(text, return_word_ids)
out["l"] = self.clip_l.tokenize_with_weights(text, return_word_ids)
return out
+26 -30
View File
@@ -1,4 +1,3 @@
import random
import os
from typing import List, Tuple, Any
@@ -8,34 +7,24 @@ from PIL import Image
import folder_paths
import comfy.sd
import comfy.ops
from custom_nodes.KepPromptLang.lib.clip_model import PromptLangClipModel
from comfy.sd2_clip import SD2ClipModel
from comfy.sdxl_clip import SDXLClipModel
from comfy.supported_models_base import ClipTarget
from custom_nodes.KepPromptLang.lib.clip_model import (
PromptLangSDXLClipModel,
PromptLangSD1ClipModel,
)
from custom_nodes.KepPromptLang.lib.tokenizer import PromptLangTokenizer
from custom_nodes.KepPromptLang.lib.tokenizer import (
PromptLangSDXLTokenizer,
PromptLangSD1Tokenizer,
)
class EmptyClass:
pass
class MonacoPrompt:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"clip": ("CLIP",),
"prompt": ("MONACO",),
}
}
RETURN_TYPES = ("CONDITIONING",)
FUNCTION = "do_crap"
OUTPUT_IS_LIST = (False,)
CATEGORY = "conditioning"
@staticmethod
def do_crap(clip, prompt):
return (clip,)
class SpecialClipLoader:
@classmethod
def INPUT_TYPES(cls): # type: ignore
@@ -52,15 +41,22 @@ class SpecialClipLoader:
@staticmethod
def load_clip(source_clip: comfy.sd.CLIP) -> Tuple[comfy.sd.CLIP]:
clip_target = EmptyClass()
clip_target.params = {}
clip_target.clip = PromptLangClipModel
clip_target.tokenizer = PromptLangTokenizer
clip = comfy.sd.CLIP(clip_target, embedding_directory=source_clip.tokenizer.embedding_directory)
comfy.sd.load_clip_weights(
clip.cond_stage_model, source_clip.cond_stage_model.state_dict()
)
if isinstance(source_clip.cond_stage_model, SDXLClipModel):
clip_target = ClipTarget(PromptLangSDXLTokenizer, PromptLangSDXLClipModel)
clip = comfy.sd.CLIP(clip_target, embedding_directory=source_clip.tokenizer.clip_g.embedding_directory)
comfy.sd.load_clip_weights(clip.cond_stage_model.clip_g,source_clip.cond_stage_model.clip_g.state_dict())
comfy.sd.load_clip_weights(
clip.cond_stage_model.clip_l, source_clip.cond_stage_model.clip_l.state_dict()
)
elif isinstance(source_clip, SD2ClipModel):
raise ValueError("SD2 Clip model is not supported.")
else:
clip_target = ClipTarget(PromptLangSD1Tokenizer, PromptLangSD1ClipModel)
clip = comfy.sd.CLIP(clip_target, embedding_directory=source_clip.tokenizer.clip_l.embedding_directory)
comfy.sd.load_clip_weights(
clip.cond_stage_model, source_clip.cond_stage_model.state_dict()
)
return (clip,)
+1
View File
@@ -1 +1,2 @@
lark
packaging
Generated
-12963
View File
File diff suppressed because one or more lines are too long
-2047
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More