initial work on ipadapter plus model

This commit is contained in:
matt3o
2023-09-03 20:11:39 +02:00
parent 414486b53d
commit 57f1122cbc
3 changed files with 302 additions and 52 deletions
+179 -52
View File
@@ -5,8 +5,11 @@ import comfy.utils
import comfy.model_management
from torch import nn
import torch.nn.functional as F
from transformers import CLIPVisionModelWithProjection
from .resampler import Resampler
MODELS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "models")
# attention_channels
@@ -36,46 +39,65 @@ class To_KV(nn.Module):
super().__init__()
channels = SD_XL_CHANNELS if cross_attention_dim == 2048 else SD_V12_CHANNELS
self.to_kvs = nn.ModuleList([torch.nn.Linear(cross_attention_dim, channel, bias=False) for channel in channels])
self.to_kvs = nn.ModuleList([nn.Linear(cross_attention_dim, channel, bias=False) for channel in channels])
def load_state_dict(self, state_dict):
# input -> output -> middle
for i, key in enumerate(state_dict.keys()):
self.to_kvs[i].weight.data = state_dict[key]
'''
code from I'm using the code from:
https://github.com/comfyanonymous/ComfyUI/blob/a74c5dbf3764fa598b58da8c88da823aaf8364fa/comfy/model_patcher.py#L64
'''
def set_model_patch_replace(model, patch, block_name, number, index, name="attn2"):
def set_model_patch_replace(model, patch_kwargs, key):
to = model.model_options["transformer_options"]
if "patches_replace" not in to:
to["patches_replace"] = {}
if name not in to["patches_replace"]:
to["patches_replace"][name] = {}
to["patches_replace"][name][(block_name, number, index)] = patch
if "attn2" not in to["patches_replace"]:
to["patches_replace"]["attn2"] = {}
if key not in to["patches_replace"]["attn2"]:
patch = CrossAttentionPatch(**patch_kwargs)
to["patches_replace"]["attn2"][key] = patch
else:
to["patches_replace"]["attn2"][key].set_new_condition(**patch_kwargs)
def attention(q, k, v, extra_options):
if not hasattr(F, "multi_head_attention_forward"):
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> (b h) n d', h=extra_options["n_heads"]), (q, k, v))
sim = torch.einsum('b i d, b j d -> b i j', q, k) * (extra_options["dim_head"] ** -0.5)
sim = F.softmax(sim, dim=-1)
out = torch.einsum('b i j, b j d -> b i d', sim, v)
out = rearrange(out, '(b h) n d -> b n (h d)', h=extra_options["n_heads"])
else:
b, _, _ = q.shape
q, k, v = map(
lambda t: t.view(b, -1, extra_options["n_heads"], extra_options["dim_head"]).transpose(1, 2),
(q, k, v),
)
out = F.scaled_dot_product_attention(q, k, v, attn_mask=None, dropout_p=0.0, is_causal=False)
out = out.transpose(1, 2).reshape(b, -1, extra_options["n_heads"] * extra_options["dim_head"])
return out
class IPAdapter(nn.Module):
def __init__(self, ipadapter_model, clip_embeddings_dim, dtype=torch.float16, device="cuda", num_tokens=4):
def __init__(self, ipadapter_model, clip_embeddings, cross_attention_dim=1024, clip_embeddings_dim=1024, clip_extra_context_tokens=4):
super().__init__()
self.ipadapter_model = ipadapter_model
self.device = device
#self.num_tokens = num_tokens
self.dtype = dtype
self.clip_embeddings = clip_embeddings
self.clip_embeddings_dim = clip_embeddings_dim
self.cross_attention_dim = self.ipadapter_model["ip_adapter"]["1.to_k_ip.weight"].shape[1]
self.clip_extra_context_tokens = num_tokens
self.image_proj_model = ImageProjModel(
cross_attention_dim=self.cross_attention_dim,
clip_embeddings_dim=clip_embeddings_dim,
clip_extra_context_tokens=self.clip_extra_context_tokens
)
self.clip_extra_context_tokens = clip_extra_context_tokens
self.image_proj_model = self.init_proj()
self.image_proj_model.load_state_dict(self.ipadapter_model["image_proj"])
self.ip_layers = To_KV(self.cross_attention_dim)
self.ip_layers = To_KV(cross_attention_dim)
self.ip_layers.load_state_dict(self.ipadapter_model["ip_adapter"])
def init_proj(self):
image_proj_model = ImageProjModel(
cross_attention_dim=self.cross_attention_dim,
clip_embeddings_dim=self.clip_embeddings_dim,
clip_extra_context_tokens=self.clip_extra_context_tokens
)
return image_proj_model
@torch.inference_mode()
def get_image_embeds(self, clip_image_embeds):
@@ -83,6 +105,76 @@ class IPAdapter(nn.Module):
uncond_image_prompt_embeds = self.image_proj_model(torch.zeros_like(clip_image_embeds))
return image_prompt_embeds, uncond_image_prompt_embeds
class IPAdapterPlus(IPAdapter):
def init_proj(self):
image_proj_model = Resampler(
dim=self.cross_attention_dim,
depth=4,
dim_head=64,
heads=12,
num_queries=self.clip_extra_context_tokens,
embedding_dim=self.clip_embeddings_dim,
output_dim=self.cross_attention_dim,
ff_mult=4
)
return image_proj_model
@torch.inference_mode()
def get_image_embeds(self, clip_image_embeds):
image_prompt_embeds = self.image_proj_model(clip_image_embeds)
uncond_clip_image_embeds = self.clip_vision(torch.zeros_like(self.clip_embeddings), output_hidden_states=True).hidden_states[-2]
uncond_image_prompt_embeds = self.image_proj_model(uncond_clip_image_embeds)
return image_prompt_embeds, uncond_image_prompt_embeds
class CrossAttentionPatch:
# forward for patching
def __init__(self, weight, ipadapter, dtype, number, cond, uncond, mask=None):
self.weights = [weight]
self.ipadapters = [ipadapter]
self.conds = [cond]
self.unconds = [uncond]
self.dtype = dtype
self.number = number
self.masks = [mask]
def set_new_condition(self, weight, ipadapter, cond, uncond, dtype, number, mask=None):
self.weights.append(weight)
self.ipadapters.append(ipadapter)
self.conds.append(cond)
self.unconds.append(uncond)
self.masks.append(mask)
self.dtype = dtype
def __call__(self, n, context_attn2, value_attn2, extra_options):
org_dtype = n.dtype
with torch.autocast("cuda", dtype=self.dtype):
q = n
k = context_attn2
v = value_attn2
b, _, _ = q.shape
out = attention(q, k, v, extra_options)
for weight, cond, uncond, ipadapter, mask in zip(self.weights, self.conds, self.unconds, self.ipadapters, self.masks):
uncond_cond = torch.cat([uncond.repeat(b//2, 1, 1), cond.repeat(b//2, 1, 1)], dim=0)
# k, v for ip_adapter
ip_k = ipadapter.ip_layers.to_kvs[self.number*2](uncond_cond)
ip_v = ipadapter.ip_layers.to_kvs[self.number*2+1](uncond_cond)
ip_out = attention(q, ip_k, ip_v, extra_options)
if mask is not None:
mask_size = mask.shape[0] * mask.shape[1]
down_sample_rate = int((mask_size // 64 // out.shape[1]) ** (1/2))
mask_downsample = torch.nn.functional.interpolate(mask.unsqueeze(0).unsqueeze(0), scale_factor= 1/8/down_sample_rate, mode="nearest").squeeze(0)
mask_downsample = mask_downsample.view(1,-1, 1).repeat(out.shape[0], 1, out.shape[2])
ip_out = ip_out * mask_downsample
out = out + ip_out * weight
return out.to(dtype=org_dtype)
class IPAdapterModelLoader:
@classmethod
def INPUT_TYPES(s):
@@ -107,31 +199,58 @@ class IPAdapterModelLoader:
class IPAdapterApply:
@classmethod
def INPUT_TYPES(s):
return {"required": {"model": ("MODEL", ),
"ipadapter": ("IPADAPTER", ),
"clip_vision_output": ("CLIP_VISION_OUTPUT", ),
"weight": ("FLOAT", { "default": 1.0, "min": -1, "max": 3, "step": 0.1 }),
}}
return {
"required": {
"model": ("MODEL", ),
"ipadapter": ("IPADAPTER", ),
"clip_vision_output": ("CLIP_VISION_OUTPUT", ),
"weight": ("FLOAT", { "default": 1.0, "min": -1, "max": 3, "step": 0.1 }),
},
"optional": {
"mask": ("MASK",),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "apply_ipadapter"
CATEGORY = "conditioning/ipadapter"
def apply_ipadapter(self, model, ipadapter, clip_vision_output, weight):
def apply_ipadapter(self, model, ipadapter, clip_vision_output, weight, mask=None):
self.dtype = model.model.diffusion_model.dtype
self.device = comfy.model_management.get_torch_device()
self.weight = weight
print(self.dtype)
clip_embeddings = clip_vision_output.image_embeds.to(self.device, dtype=self.dtype)
clip_embeddings_dim = clip_embeddings.shape[1]
clip_embeddings_dim = clip_embeddings.shape[-1]
self.sdxl = clip_embeddings_dim == 1280 # is there a better way?
self.is_plus = "latents" in ipadapter["image_proj"]
cross_attention_dim = ipadapter["ip_adapter"]["1.to_k_ip.weight"].shape[1]
self.ipadapter = IPAdapter(
ipadapter,
clip_embeddings_dim,
dtype=self.dtype,
device=self.device
).to(self.device, dtype=self.dtype)
if self.is_plus:
clip_extra_context_tokens = ipadapter["image_proj"]["latents"].shape[1]
else:
clip_extra_context_tokens = 4 # ipadapter["image_proj"]["proj.weight"].shape[0] <-- doesn't work?
self.is_sdxl = cross_attention_dim == 2048
if self.is_plus:
self.ipadapter = IPAdapterPlus(
ipadapter,
clip_embeddings,
cross_attention_dim=cross_attention_dim,
clip_embeddings_dim=clip_embeddings_dim,
clip_extra_context_tokens=clip_extra_context_tokens
)
else:
self.ipadapter = IPAdapter(
ipadapter,
clip_embeddings,
cross_attention_dim=cross_attention_dim,
clip_embeddings_dim=clip_embeddings_dim,
clip_extra_context_tokens=clip_extra_context_tokens
)
self.ipadapter.to(self.device, dtype=self.dtype)
self.image_prompt_embeds, self.uncond_image_prompt_embeds = self.ipadapter.get_image_embeds(clip_embeddings)
self.image_prompt_embeds = self.image_prompt_embeds.to(self.device, dtype=self.dtype)
@@ -140,32 +259,40 @@ class IPAdapterApply:
work_model = model.clone()
if not self.sdxl:
number = 0 # index of to_kvs
patch_kwargs = {
"number": 0,
"weight": self.weight,
"ipadapter": self.ipadapter,
"dtype": self.dtype,
"cond": self.image_prompt_embeds,
"uncond": self.uncond_image_prompt_embeds,
"mask": mask if mask is None else mask.to(self.device)
}
if not self.is_sdxl:
for id in [1,2,4,5,7,8]: # id of input_blocks that have cross attention
work_model.set_model_attn2_replace(self.patch_forward(number), "input", id)
number += 1
set_model_patch_replace(work_model, patch_kwargs, ("input", id))
patch_kwargs["number"] += 1
for id in [3,4,5,6,7,8,9,10,11]: # id of output_blocks that have cross attention
work_model.set_model_attn2_replace(self.patch_forward(number), "output", id)
number += 1
work_model.set_model_attn2_replace(self.patch_forward(number), "middle", 0)
set_model_patch_replace(work_model, patch_kwargs, ("output", id))
patch_kwargs["number"] += 1
set_model_patch_replace(work_model, patch_kwargs, ("middle", 0))
else:
number = 0
for id in [4,5,7,8]: # id of input_blocks that have cross attention
block_indices = range(2) if id in [4, 5] else range(10) # transformer_depth
for index in block_indices:
set_model_patch_replace(work_model, self.patch_forward(number), "input", id, index)
number += 1
set_model_patch_replace(work_model, patch_kwargs, ("input", id, index))
patch_kwargs["number"] += 1
for id in range(6): # id of output_blocks that have cross attention
block_indices = range(2) if id in [3, 4, 5] else range(10) # transformer_depth
for index in block_indices:
set_model_patch_replace(work_model, self.patch_forward(number), "output", id, index)
number += 1
set_model_patch_replace(work_model, patch_kwargs, ("output", id, index))
patch_kwargs["number"] += 1
for index in range(10):
set_model_patch_replace(work_model, self.patch_forward(number), "middle", 0, index)
number += 1
set_model_patch_replace(work_model, patch_kwargs, ("midlle", 0, index))
patch_kwargs["number"] += 1
return (work_model, )
return (work_model,)
def patch_forward(self, number):
device = self.device.type
+2
View File
@@ -1,3 +1,5 @@
**ALPHA STAGE! Not production ready!**
# ComfyUI_IPAdapter_Plus
ComfyUI reference implementation for [IPAdapter](https://github.com/tencent-ailab/IP-Adapter/tree/6fb9d3554a5c774f41e187e8fdbc7b9a1db8c2e3) models.
+121
View File
@@ -0,0 +1,121 @@
# modified from https://github.com/mlfoundations/open_flamingo/blob/main/open_flamingo/src/helpers.py
import math
import torch
import torch.nn as nn
# FFN
def FeedForward(dim, mult=4):
inner_dim = int(dim * mult)
return nn.Sequential(
nn.LayerNorm(dim),
nn.Linear(dim, inner_dim, bias=False),
nn.GELU(),
nn.Linear(inner_dim, dim, bias=False),
)
def reshape_tensor(x, heads):
bs, length, width = x.shape
#(bs, length, width) --> (bs, length, n_heads, dim_per_head)
x = x.view(bs, length, heads, -1)
# (bs, length, n_heads, dim_per_head) --> (bs, n_heads, length, dim_per_head)
x = x.transpose(1, 2)
# (bs, n_heads, length, dim_per_head) --> (bs*n_heads, length, dim_per_head)
x = x.reshape(bs, heads, length, -1)
return x
class PerceiverAttention(nn.Module):
def __init__(self, *, dim, dim_head=64, heads=8):
super().__init__()
self.scale = dim_head**-0.5
self.dim_head = dim_head
self.heads = heads
inner_dim = dim_head * heads
self.norm1 = nn.LayerNorm(dim)
self.norm2 = nn.LayerNorm(dim)
self.to_q = nn.Linear(dim, inner_dim, bias=False)
self.to_kv = nn.Linear(dim, inner_dim * 2, bias=False)
self.to_out = nn.Linear(inner_dim, dim, bias=False)
def forward(self, x, latents):
"""
Args:
x (torch.Tensor): image features
shape (b, n1, D)
latent (torch.Tensor): latent features
shape (b, n2, D)
"""
x = self.norm1(x)
latents = self.norm2(latents)
b, l, _ = latents.shape
q = self.to_q(latents)
kv_input = torch.cat((x, latents), dim=-2)
k, v = self.to_kv(kv_input).chunk(2, dim=-1)
q = reshape_tensor(q, self.heads)
k = reshape_tensor(k, self.heads)
v = reshape_tensor(v, self.heads)
# attention
scale = 1 / math.sqrt(math.sqrt(self.dim_head))
weight = (q * scale) @ (k * scale).transpose(-2, -1) # More stable with f16 than dividing afterwards
weight = torch.softmax(weight.float(), dim=-1).type(weight.dtype)
out = weight @ v
out = out.permute(0, 2, 1, 3).reshape(b, l, -1)
return self.to_out(out)
class Resampler(nn.Module):
def __init__(
self,
dim=1024,
depth=8,
dim_head=64,
heads=16,
num_queries=8,
embedding_dim=768,
output_dim=1024,
ff_mult=4,
):
super().__init__()
self.latents = nn.Parameter(torch.randn(1, num_queries, dim) / dim**0.5)
self.proj_in = nn.Linear(embedding_dim, dim)
self.proj_out = nn.Linear(dim, output_dim)
self.norm_out = nn.LayerNorm(output_dim)
self.layers = nn.ModuleList([])
for _ in range(depth):
self.layers.append(
nn.ModuleList(
[
PerceiverAttention(dim=dim, dim_head=dim_head, heads=heads),
FeedForward(dim=dim, mult=ff_mult),
]
)
)
def forward(self, x):
latents = self.latents.repeat(x.size(0), 1, 1)
x = self.proj_in(x)
for attn, ff in self.layers:
latents = attn(x, latents) + latents
latents = ff(latents) + latents
latents = self.proj_out(latents)
return self.norm_out(latents)