464 lines
19 KiB
Python
464 lines
19 KiB
Python
# from .long_clip_model import longclip
|
|
from ...components.long_clip_model import longclip
|
|
import os
|
|
import torch
|
|
from comfy.sd import CLIP
|
|
import folder_paths
|
|
from comfy.sd1_clip import load_embed,ClipTokenWeightEncoder
|
|
from comfy.model_management import get_torch_device
|
|
from comfy import model_management
|
|
import comfy
|
|
|
|
class SDLongClipModel(torch.nn.Module, ClipTokenWeightEncoder):
|
|
"""Uses the CLIP transformer encoder for text (from huggingface)"""
|
|
LAYERS = [
|
|
"last",
|
|
"pooled",
|
|
"hidden"
|
|
]
|
|
def __init__(self, version="openai/clip-vit-large-patch14", device="cpu", max_length=77, freeze=True, layer="last", layer_idx=None, dtype=None, special_tokens={"start": 49406, "end": 49407, "pad": 49407}, layer_norm_hidden_state=True, enable_attention_masks=False, return_projected_pooled=True, **kwargs): # clip-vit-base-patch32
|
|
super().__init__()
|
|
assert layer in self.LAYERS
|
|
self.transformer, _ = longclip.load(version, device=device)
|
|
self.num_layers = self.transformer.transformer_layers
|
|
self.max_length = max_length
|
|
if freeze:
|
|
self.freeze()
|
|
self.layer = layer
|
|
self.layer_idx = None
|
|
self.special_tokens = special_tokens
|
|
self.text_projection = torch.nn.Parameter(torch.eye(self.transformer.get_input_embeddings().weight.shape[1]))
|
|
self.logit_scale = torch.nn.Parameter(torch.tensor(4.6055))
|
|
self.enable_attention_masks = enable_attention_masks
|
|
|
|
self.layer_norm_hidden_state = layer_norm_hidden_state
|
|
self.return_projected_pooled = return_projected_pooled
|
|
if layer == "hidden":
|
|
assert layer_idx is not None
|
|
assert abs(layer_idx) < self.num_layers
|
|
self.clip_layer(layer_idx)
|
|
self.layer_default = (self.layer, self.layer_idx)
|
|
self.options_default = (self.layer, self.layer_idx, self.return_projected_pooled)
|
|
|
|
self.dtypes = [param.dtype for param in self.parameters()]
|
|
|
|
def freeze(self):
|
|
self.transformer = self.transformer.eval()
|
|
#self.train = disabled_train
|
|
for param in self.parameters():
|
|
param.requires_grad = False
|
|
|
|
def clip_layer(self, layer_idx):
|
|
if abs(layer_idx) > self.num_layers:
|
|
self.layer = "last"
|
|
else:
|
|
self.layer = "hidden"
|
|
self.layer_idx = layer_idx
|
|
|
|
def reset_clip_layer(self):
|
|
self.layer = self.layer_default[0]
|
|
self.layer_idx = self.layer_default[1]
|
|
|
|
def set_clip_options(self, options):
|
|
layer_idx = options.get("layer", self.layer_idx)
|
|
self.return_projected_pooled = options.get("projected_pooled", self.return_projected_pooled)
|
|
if layer_idx is None or abs(layer_idx) > self.num_layers:
|
|
self.layer = "last"
|
|
else:
|
|
self.layer = "hidden"
|
|
self.layer_idx = layer_idx
|
|
|
|
def reset_clip_options(self):
|
|
self.layer = self.options_default[0]
|
|
self.layer_idx = self.options_default[1]
|
|
self.return_projected_pooled = self.options_default[2]
|
|
|
|
def set_up_textual_embeddings(self, tokens, current_embeds):
|
|
out_tokens = []
|
|
next_new_token = token_dict_size = current_embeds.weight.shape[0] - 1
|
|
embedding_weights = []
|
|
|
|
for x in tokens:
|
|
tokens_temp = []
|
|
for y in x:
|
|
if isinstance(y, int):
|
|
if y == token_dict_size: #EOS token
|
|
y = -1
|
|
tokens_temp += [y]
|
|
else:
|
|
if y.shape[0] == current_embeds.weight.shape[1]:
|
|
embedding_weights += [y]
|
|
tokens_temp += [next_new_token]
|
|
next_new_token += 1
|
|
else:
|
|
print("WARNING: shape mismatch when trying to apply embedding, embedding will be ignored", y.shape[0], current_embeds.weight.shape[1])
|
|
while len(tokens_temp) < len(x):
|
|
tokens_temp += [self.special_tokens["pad"]]
|
|
out_tokens += [tokens_temp]
|
|
|
|
n = token_dict_size
|
|
if len(embedding_weights) > 0:
|
|
new_embedding = torch.nn.Embedding(next_new_token + 1, current_embeds.weight.shape[1], device=current_embeds.weight.device, dtype=current_embeds.weight.dtype)
|
|
new_embedding.weight[:token_dict_size] = current_embeds.weight[:-1]
|
|
for x in embedding_weights:
|
|
new_embedding.weight[n] = x
|
|
n += 1
|
|
new_embedding.weight[n] = current_embeds.weight[-1] #EOS embedding
|
|
self.transformer.set_input_embeddings(new_embedding)
|
|
|
|
processed_tokens = []
|
|
for x in out_tokens:
|
|
processed_tokens += [list(map(lambda a: n if a == -1 else a, x))] #The EOS token should always be the largest one
|
|
|
|
return processed_tokens
|
|
|
|
def forward(self, tokens):
|
|
backup_embeds = self.transformer.get_input_embeddings()
|
|
device = backup_embeds.weight.device
|
|
tokens = self.set_up_textual_embeddings(tokens, backup_embeds)
|
|
tokens = torch.LongTensor(tokens).to(device)
|
|
|
|
attention_mask = None
|
|
if self.enable_attention_masks:
|
|
attention_mask = torch.zeros_like(tokens)
|
|
max_token = self.transformer.get_input_embeddings().weight.shape[0] - 1
|
|
for x in range(attention_mask.shape[0]):
|
|
for y in range(attention_mask.shape[1]):
|
|
attention_mask[x, y] = 1
|
|
if tokens[x, y] == max_token:
|
|
break
|
|
|
|
outputs = self.transformer(tokens, attention_mask, intermediate_output=self.layer_idx, final_layer_norm_intermediate=self.layer_norm_hidden_state)
|
|
self.transformer.set_input_embeddings(backup_embeds)
|
|
|
|
if self.layer == "last":
|
|
z = outputs[0]
|
|
else:
|
|
z = outputs[1]
|
|
|
|
pooled_output = None
|
|
if len(outputs) >= 3:
|
|
if not self.return_projected_pooled and len(outputs) >= 4 and outputs[3] is not None:
|
|
pooled_output = outputs[3].float()
|
|
elif outputs[2] is not None:
|
|
pooled_output = outputs[2].float()
|
|
|
|
return z.float(), pooled_output
|
|
|
|
def encode(self, tokens):
|
|
return self(tokens)
|
|
|
|
def load_sd(self, sd):
|
|
if "text_projection" in sd:
|
|
self.text_projection[:] = sd.pop("text_projection")
|
|
if "text_projection.weight" in sd:
|
|
self.text_projection[:] = sd.pop("text_projection.weight").transpose(0, 1)
|
|
return self.transformer.load_state_dict(sd, strict=False)
|
|
|
|
class SDLongTokenizer:
|
|
def __init__(self, max_length=248, pad_with_end=True, embedding_directory=None, tokenizer_data=None, embedding_size=768, embedding_key='clip_l', has_start_token=True, pad_to_max_length=True):
|
|
self.tokenizer = longclip.only_tokenize ##tokenizer_class.from_pretrained(tokenizer_path)
|
|
self.max_length = max_length
|
|
empty = self.tokenizer('')[0]
|
|
if has_start_token:
|
|
self.tokens_start = 1
|
|
self.start_token = empty[0]
|
|
self.end_token = empty[1]
|
|
else:
|
|
self.tokens_start = 0
|
|
self.start_token = None
|
|
self.end_token = empty[0]
|
|
self.pad_with_end = pad_with_end
|
|
self.pad_to_max_length = pad_to_max_length
|
|
|
|
##vocab = self.tokenizer.get_vocab()
|
|
##self.inv_vocab = {v: k for k, v in vocab.items()}
|
|
self.embedding_directory = embedding_directory
|
|
self.max_word_length = 8
|
|
self.embedding_identifier = "embedding:"
|
|
self.embedding_size = embedding_size
|
|
self.embedding_key = embedding_key
|
|
self.tokenizer_data = tokenizer_data
|
|
|
|
def _try_get_embedding(self, embedding_name:str):
|
|
'''
|
|
Takes a potential embedding name and tries to retrieve it.
|
|
Returns a Tuple consisting of the embedding and any leftover string, embedding can be None.
|
|
'''
|
|
embed = load_embed(embedding_name, self.embedding_directory, self.embedding_size, self.embedding_key)
|
|
if embed is None:
|
|
stripped = embedding_name.strip(',')
|
|
if len(stripped) < len(embedding_name):
|
|
embed = load_embed(stripped, self.embedding_directory, self.embedding_size, self.embedding_key)
|
|
return (embed, embedding_name[len(stripped):])
|
|
return (embed, "")
|
|
|
|
|
|
def tokenize_with_weights(self, text:str, return_word_ids=False):
|
|
'''
|
|
Takes a prompt and converts it to a list of (token, weight, word id) elements.
|
|
Tokens can both be integer tokens and pre computed CLIP tensors.
|
|
Word id values are unique per word and embedding, where the id 0 is reserved for non word tokens.
|
|
Returned list has the dimensions NxM where M is the input size of CLIP
|
|
'''
|
|
if self.pad_with_end:
|
|
pad_token = self.end_token
|
|
else:
|
|
pad_token = 0
|
|
from comfy.sd1_clip import token_weights,escape_important,unescape_important
|
|
|
|
text = escape_important(text)
|
|
parsed_weights = token_weights(text, 1.0)
|
|
|
|
tokens = []
|
|
for weighted_segment, weight in parsed_weights:
|
|
to_tokenize = unescape_important(weighted_segment).replace("\n", " ").split(' ')
|
|
to_tokenize = [x for x in to_tokenize if x != ""]
|
|
for word in to_tokenize:
|
|
#if we find an embedding, deal with the embedding
|
|
if word.startswith(self.embedding_identifier) and self.embedding_directory is not None:
|
|
embedding_name = word[len(self.embedding_identifier):].strip('\n')
|
|
embed, leftover = self._try_get_embedding(embedding_name)
|
|
if embed is None:
|
|
print(f"warning, embedding:{embedding_name} does not exist, ignoring")
|
|
else:
|
|
if len(embed.shape) == 1:
|
|
tokens.append([(embed, weight)])
|
|
else:
|
|
tokens.append([(embed[x], weight) for x in range(embed.shape[0])])
|
|
#if we accidentally have leftover text, continue parsing using leftover, else move on to next word
|
|
if leftover != "":
|
|
word = leftover
|
|
else:
|
|
continue
|
|
#parse word
|
|
tokens.append([(t, weight) for t in self.tokenizer(word)[0][self.tokens_start:-1]])
|
|
|
|
#reshape token array to CLIP input size
|
|
batched_tokens = []
|
|
batch = []
|
|
if self.start_token is not None:
|
|
batch.append((self.start_token, 1.0, 0))
|
|
batched_tokens.append(batch)
|
|
for i, t_group in enumerate(tokens):
|
|
#determine if we're going to try and keep the tokens in a single batch
|
|
is_large = len(t_group) >= self.max_word_length
|
|
|
|
while len(t_group) > 0:
|
|
if len(t_group) + len(batch) > self.max_length - 1:
|
|
remaining_length = self.max_length - len(batch) - 1
|
|
#break word in two and add end token
|
|
if is_large:
|
|
batch.extend([(t,w,i+1) for t,w in t_group[:remaining_length]])
|
|
batch.append((self.end_token, 1.0, 0))
|
|
t_group = t_group[remaining_length:]
|
|
#add end token and pad
|
|
else:
|
|
batch.append((self.end_token, 1.0, 0))
|
|
if self.pad_to_max_length:
|
|
batch.extend([(pad_token, 1.0, 0)] * (remaining_length))
|
|
#start new batch
|
|
batch = []
|
|
if self.start_token is not None:
|
|
batch.append((self.start_token, 1.0, 0))
|
|
batched_tokens.append(batch)
|
|
else:
|
|
batch.extend([(t,w,i+1) for t,w in t_group])
|
|
t_group = []
|
|
|
|
#fill last batch
|
|
batch.append((self.end_token, 1.0, 0))
|
|
if self.pad_to_max_length:
|
|
batch.extend([(pad_token, 1.0, 0)] * (self.max_length - len(batch)))
|
|
|
|
if not return_word_ids:
|
|
batched_tokens = [[(t, w) for t, w,_ in x] for x in batched_tokens]
|
|
|
|
return batched_tokens
|
|
|
|
|
|
def untokenize(self, token_weight_pair):
|
|
return list(map(lambda a: (a, self.inv_vocab[a[0]]), token_weight_pair))
|
|
|
|
def pad_tokens(tokens,clip,add_token_num):
|
|
if clip.pad_with_end:
|
|
pad_token = clip.end_token
|
|
else:
|
|
pad_token = 0
|
|
while add_token_num > 0:
|
|
batch = []
|
|
batch.append((clip.end_token, 1.0, 0))
|
|
add_pad = clip.max_length - 1
|
|
batch.extend([(pad_token, 1.0, 0)] * add_pad)
|
|
tokens.append(batch)
|
|
add_token_num -= (add_pad+1)
|
|
return tokens
|
|
|
|
def token_num(tokens):
|
|
n = 0
|
|
for token in tokens:
|
|
n += len(token)
|
|
return n
|
|
|
|
class SDXLLongClipModel(torch.nn.Module):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.clip_l = None
|
|
self.clip_g = None
|
|
|
|
def set_clip_options(self, options):
|
|
self.clip_l.set_clip_options(options)
|
|
self.clip_g.set_clip_options(options)
|
|
|
|
def reset_clip_options(self):
|
|
self.clip_g.reset_clip_options()
|
|
self.clip_l.reset_clip_options()
|
|
|
|
def encode_token_weights(self, token_weight_pairs):
|
|
token_weight_pairs_g = token_weight_pairs["g"]
|
|
token_weight_pairs_l = token_weight_pairs["l"]
|
|
g_out, g_pooled = self.clip_g.encode_token_weights(token_weight_pairs_g)
|
|
l_out, l_pooled = self.clip_l.encode_token_weights(token_weight_pairs_l)
|
|
g_tokens = g_out.shape[1]
|
|
l_tokens = l_out.shape[1]
|
|
min_tokens = min(g_tokens,l_tokens)
|
|
g_out = g_out[:,:min_tokens,:]
|
|
l_out = l_out[:,:min_tokens,:]
|
|
return torch.cat([l_out, g_out], dim=-1), g_pooled
|
|
|
|
def load_sd(self, sd):
|
|
if "text_model.encoder.layers.30.mlp.fc1.weight" in sd:
|
|
return self.clip_g.load_sd(sd)
|
|
else:
|
|
return self.clip_l.load_sd(sd)
|
|
|
|
class SDXLLongTokenizer:
|
|
def __init__(self):
|
|
self.clip_l = None
|
|
self.clip_g = None
|
|
|
|
def tokenize_with_weights(self, text:str, return_word_ids=False):
|
|
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)
|
|
g_tokens = token_num(out["g"])
|
|
l_tokens = token_num(out["l"])
|
|
if g_tokens > l_tokens:
|
|
out["l"] = pad_tokens(out["l"],self.clip_l,g_tokens-l_tokens)
|
|
elif l_tokens > g_tokens:
|
|
out["g"] = pad_tokens(out["g"],self.clip_g,l_tokens-g_tokens)
|
|
return out
|
|
|
|
def untokenize(self, token_weight_pair):
|
|
return self.clip_g.untokenize(token_weight_pair)
|
|
|
|
class LongCLIPFluxModel(torch.nn.Module):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.clip_l = None
|
|
self.t5xxl = None
|
|
|
|
def set_clip_options(self, options):
|
|
self.clip_l.set_clip_options(options)
|
|
self.t5xxl.set_clip_options(options)
|
|
|
|
def reset_clip_options(self):
|
|
self.clip_l.reset_clip_options()
|
|
self.t5xxl.reset_clip_options()
|
|
|
|
def encode_token_weights(self, token_weight_pairs):
|
|
token_weight_pairs_l = token_weight_pairs["l"]
|
|
token_weight_pairs_t5 = token_weight_pairs["t5xxl"]
|
|
|
|
# Encode using Long-CLIP
|
|
l_out, l_pooled = self.clip_l.encode_token_weights(token_weight_pairs_l)
|
|
# Encode using T5XXL
|
|
t5_out, t5_pooled = self.t5xxl.encode_token_weights(token_weight_pairs_t5)
|
|
|
|
return t5_out, l_pooled
|
|
|
|
def load_sd(self, sd):
|
|
if "text_model.encoder.layers.1.mlp.fc1.weight" in sd:
|
|
return self.clip_l.load_sd(sd)
|
|
else:
|
|
return self.t5xxl.load_sd(sd)
|
|
|
|
class LongCLIPFluxTokenizer:
|
|
def __init__(self):
|
|
self.clip_l = None
|
|
self.t5xxl = None
|
|
|
|
def tokenize_with_weights(self, text: str, return_word_ids=False):
|
|
# Tokenize with both Long-CLIP and T5XXL
|
|
out = {}
|
|
out["l"] = self.clip_l.tokenize_with_weights(text, return_word_ids) # Long-CLIP tokenization
|
|
out["t5xxl"] = self.t5xxl.tokenize_with_weights(text, return_word_ids) # T5XXL tokenization
|
|
|
|
# Check the number of tokens
|
|
l_tokens = token_num(out["l"])
|
|
t5_tokens = token_num(out["t5xxl"])
|
|
|
|
# Leaving this here as a reminder: Do NOT pad T5XXL!
|
|
if l_tokens > t5_tokens:
|
|
pass # Do not pad T5XXL
|
|
|
|
return out
|
|
|
|
def untokenize(self, token_weight_pair):
|
|
# Untokenize using Long-CLIP tokenizer
|
|
return self.clip_l.untokenize(token_weight_pair)
|
|
|
|
def state_dict(self):
|
|
return {}
|
|
|
|
class SDXLLongClip:
|
|
def sdxl_longclip(self, clip_name, clip):
|
|
clip_clone = clip.clone()
|
|
clip_path = folder_paths.get_full_path("clip", clip_name)
|
|
load_device = model_management.text_encoder_device()
|
|
device = model_management.text_encoder_offload_device()
|
|
dtype = model_management.text_encoder_dtype(load_device)
|
|
clip_l = SDLongClipModel(version=clip_path, layer="hidden", layer_idx=-2, device=device, dtype=dtype, layer_norm_hidden_state=False)
|
|
sdxl_long_clip_model = SDXLLongClipModel()
|
|
sdxl_long_clip_model.clip_l = clip_l
|
|
sdxl_long_clip_model.clip_g = clip_clone.cond_stage_model.clip_g
|
|
clip_clone.cond_stage_model = sdxl_long_clip_model
|
|
embedding_directory = folder_paths.get_folder_paths("embeddings")
|
|
long_tokenizer = SDXLLongTokenizer()
|
|
tokenizer_clip_l = SDLongTokenizer(embedding_directory=embedding_directory)
|
|
long_tokenizer.clip_l = tokenizer_clip_l
|
|
long_tokenizer.clip_g = clip_clone.tokenizer.clip_g
|
|
clip_clone.tokenizer = long_tokenizer
|
|
return (clip_clone,)
|
|
|
|
class SDLongClip:
|
|
def sd_longclip(self, clip_name):
|
|
class EmptyClass:
|
|
pass
|
|
clip_target = EmptyClass()
|
|
clip_path = folder_paths.get_full_path("clip", clip_name)
|
|
clip_target.params = {"version": clip_path}
|
|
clip_target.clip = SDLongClipModel
|
|
clip_target.tokenizer = SDLongTokenizer
|
|
embedding_directory = folder_paths.get_folder_paths("embeddings")
|
|
clip = CLIP(clip_target, embedding_directory = embedding_directory)
|
|
return (clip,)
|
|
|
|
class FluxLongClip:
|
|
def flux_longclip(self, clip_name, clip):
|
|
clip_clone = clip.clone()
|
|
clip_path = folder_paths.get_full_path("clip", clip_name)
|
|
load_device = model_management.text_encoder_device()
|
|
device = model_management.text_encoder_offload_device()
|
|
dtype = model_management.text_encoder_dtype(load_device)
|
|
longclip_model = SDLongClipModel(version=clip_path, layer="hidden", layer_idx=-2, device=device, dtype=dtype, max_length=248)
|
|
flux_clip_model = LongCLIPFluxModel()
|
|
flux_clip_model.clip_l = longclip_model
|
|
flux_clip_model.t5xxl = clip_clone.cond_stage_model.t5xxl
|
|
clip_clone.cond_stage_model = flux_clip_model
|
|
long_tokenizer = LongCLIPFluxTokenizer()
|
|
long_tokenizer.clip_l = SDLongTokenizer(embedding_directory=clip_clone.tokenizer.clip_l.embedding_directory, max_length=248)
|
|
long_tokenizer.t5xxl = clip_clone.tokenizer.t5xxl
|
|
clip_clone.tokenizer = long_tokenizer
|
|
return (clip_clone,)
|