import torch import torch.nn as nn import torch.nn.functional as F import ixformer.functions as ixf_F def time_embed(t_emb, weight1, bias1, weight2, bias2): # unet time_emd # linear + silu + linear emb = ixf_F.act_bias_mm( t_emb, weight1, act_type="silu", bias=bias1, scale=1, trans_format="TN" ) emb = ixf_F.act_bias_mm( emb, weight2, act_type="none", bias=bias2, scale=1, trans_format="TN" ) return emb def ixf_layer_norm(input, normalized_shape, weight=None, bias=None, eps=1e-05): return ixf_F.layernorm(input, weight, bias, normalized_shape) def ixf_pt_scaled_dot_product_attention( query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False ): if ( not query.is_contiguous() and query.transpose(1, 2).is_contiguous() and key.transpose(1, 2).is_contiguous() and value.transpose(1, 2).is_contiguous() and attn_mask is None ): batch_size, head_num, seq_len_q, head_dim = query.shape _, _, seq_len_k, _ = key.shape query = query.transpose(1, 2).view(batch_size * seq_len_q, head_num, head_dim) key = key.transpose(1, 2).view(batch_size * seq_len_k, head_num, head_dim) value = value.transpose(1, 2).view(batch_size * seq_len_k, head_num, head_dim) cu_seqlens_q = torch.arange( 0, seq_len_q * (batch_size + 1), seq_len_q, dtype=torch.int32, device=query.device, ) if seq_len_q == seq_len_k: cu_seqlens_k = cu_seqlens_q else: cu_seqlens_k = torch.arange( 0, seq_len_k * (batch_size + 1), seq_len_k, dtype=torch.int32, device=query.device, ) res = ixf_F.flash_attn_varlen_func( query, key, value, cu_seqlens_q.int(), cu_seqlens_k.int(), seq_len_q, seq_len_k, ) res = res.view(batch_size, seq_len_q, head_num, head_dim).transpose(1, 2) return res if not query.is_contiguous(): query = query.contiguous() if not key.is_contiguous(): key = key.contiguous() if not value.is_contiguous(): value = value.contiguous() return ixf_F.scaled_dot_product_attention( query, key, value, attn_mask=attn_mask, is_causal=is_causal ) class UnetIxformerFunction: def __init__(self) -> None: self.ixf_linear = ixf_F.linear self.pt_linear = F.linear self.pt_layer_norm = F.layer_norm self.pt_scaled_dot_product_attention = F.scaled_dot_product_attention def __enter__(self): F.linear = self.ixf_linear F.layer_norm = ixf_layer_norm F.scaled_dot_product_attention = ixf_pt_scaled_dot_product_attention return self def __exit__(self, exc_type, exc_val, exc_tb): F.linear = self.pt_linear F.layer_norm = self.pt_layer_norm F.scaled_dot_product_attention = self.pt_scaled_dot_product_attention if exc_tb is not None: print(f"{exc_type} {exc_val}") return False return True def ForwardWrapper(fun): def wrap(*args, **kwargs): with UnetIxformerFunction() as w: return fun(*args, **kwargs) return wrap class IxformerComfyWrapper(nn.Module): def __init__(self): super().__init__() self.is_ixf_wrapper = True class Conv2dNhwcWrapper(IxformerComfyWrapper): def __init__(self, module): super().__init__() module.weight.data = module.weight.permute(0, 2, 3, 1).contiguous() module.bias.data = module.bias.float() self.weight = module.weight.data self.bias = module.bias.data self.stride = module.stride self.padding = module.padding self.dilation = module.dilation self.groups = module.groups def forward(self, x): h2 = ixf_F.conv2d( x, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups, ) return h2 class ResBlockNhwcWrapper(IxformerComfyWrapper): def __init__(self, module) -> None: super().__init__() assert not module.updown assert not module.use_scale_shift_norm assert not module.skip_t_emb assert not module.exchange_temb_dims if isinstance(module.skip_connection, nn.Identity): self.skip_connection = module.skip_connection elif get_class_name(module.skip_connection) == "Conv2d": self.skip_connection = Conv2dNhwcWrapper(module.skip_connection) else: raise NotImplementedError( f"ResBlockNhwcWrapper support Conv2d or nn.Identity, but got {module.skip_connection}" ) self.in_layers = module.in_layers self.out_layers = module.out_layers self.emb_layers = module.emb_layers self.in_layers_conv = Conv2dNhwcWrapper(module.in_layers[2]) self.out_layers_conv = Conv2dNhwcWrapper(module.out_layers[3]) def forward(self, x, emb): # x: nhwc x1 = x # print(x1.shape) # fused group_norm silu h = ixf_F.group_norm( x1, # nchw->nhwc self.in_layers[0].num_groups, self.in_layers[0].weight, self.in_layers[0].bias, format=False, act_type=1, ) h = self.in_layers_conv(h) emb_out = self.emb_layers(emb) while len(emb_out.shape) < len(h.shape): emb_out = emb_out[..., None] h = h + emb_out.permute(0, 2, 3, 1) # print(h.shape) h = ixf_F.group_norm( h, self.out_layers[0].num_groups, self.out_layers[0].weight, self.out_layers[0].bias, format=False, act_type=1, ) h = self.out_layers[2](h) h = self.out_layers_conv(h) # TODO: support other skip_connection return self.skip_connection(x) + h class DownsampleNhwcWrapper(IxformerComfyWrapper): def __init__(self, module) -> None: # TODO: support avg_pool_nd super().__init__() assert module.use_conv self.channels = module.channels self.op = Conv2dNhwcWrapper(module.op) def forward(self, x): assert x.shape[-1] == self.channels return self.op(x) def ffn_forward(self, x): if get_class_name(self.net[0]) == "GEGLU": net = self.net[1:] geglu_net = self.net[0] x = geglu_net.proj(x) x = ixf_F.gelu_and_mul(x) return net(x) else: return self.net(x) # ComfyUI/comfy/ldm/modules/attention.py `class BasicTransformerBlock(nn.Module)` def transformer_block_forward(self, x, context=None, transformer_options={}): extra_options = {} block = transformer_options.get("block", None) block_index = transformer_options.get("block_index", 0) transformer_patches = {} transformer_patches_replace = {} for k in transformer_options: if k == "patches": transformer_patches = transformer_options[k] elif k == "patches_replace": transformer_patches_replace = transformer_options[k] else: extra_options[k] = transformer_options[k] extra_options["n_heads"] = self.n_heads extra_options["dim_head"] = self.d_head if self.ff_in: x_skip = x x = self.ff_in(self.norm_in(x)) if self.is_res: x += x_skip n = self.norm1(x) if self.disable_self_attn: context_attn1 = context else: context_attn1 = None value_attn1 = None if "attn1_patch" in transformer_patches: patch = transformer_patches["attn1_patch"] if context_attn1 is None: context_attn1 = n value_attn1 = context_attn1 for p in patch: n, context_attn1, value_attn1 = p( n, context_attn1, value_attn1, extra_options ) if block is not None: transformer_block = (block[0], block[1], block_index) else: transformer_block = None attn1_replace_patch = transformer_patches_replace.get("attn1", {}) block_attn1 = transformer_block if block_attn1 not in attn1_replace_patch: block_attn1 = block if block_attn1 in attn1_replace_patch: if context_attn1 is None: context_attn1 = n value_attn1 = n n = self.attn1.to_q(n) context_attn1 = self.attn1.to_k(context_attn1) value_attn1 = self.attn1.to_v(value_attn1) n = attn1_replace_patch[block_attn1]( n, context_attn1, value_attn1, extra_options ) n = self.attn1.to_out(n) else: n = self.attn1(n, context=context_attn1, value=value_attn1) if "attn1_output_patch" in transformer_patches: patch = transformer_patches["attn1_output_patch"] for p in patch: n = p(n, extra_options) x += n if "middle_patch" in transformer_patches: patch = transformer_patches["middle_patch"] for p in patch: x = p(x, extra_options) if self.attn2 is not None: n = self.norm2(x) if self.switch_temporal_ca_to_sa: context_attn2 = n else: context_attn2 = context value_attn2 = None if "attn2_patch" in transformer_patches: patch = transformer_patches["attn2_patch"] value_attn2 = context_attn2 for p in patch: n, context_attn2, value_attn2 = p( n, context_attn2, value_attn2, extra_options ) attn2_replace_patch = transformer_patches_replace.get("attn2", {}) block_attn2 = transformer_block if block_attn2 not in attn2_replace_patch: block_attn2 = block if block_attn2 in attn2_replace_patch: if value_attn2 is None: value_attn2 = context_attn2 n = self.attn2.to_q(n) context_attn2 = self.attn2.to_k(context_attn2) value_attn2 = self.attn2.to_v(value_attn2) n = attn2_replace_patch[block_attn2]( n, context_attn2, value_attn2, extra_options ) n = self.attn2.to_out(n) else: n = self.attn2(n, context=context_attn2, value=value_attn2) if "attn2_output_patch" in transformer_patches: patch = transformer_patches["attn2_output_patch"] for p in patch: n = p(n, extra_options) # x += n # if self.is_res: # x_skip = x # x = self.ff(self.norm3(x)) x, x_skip = ixf_F.residual_layer_norm( n, self.norm3.normalized_shape, self.norm3.weight, self.norm3.bias, x, eps=self.norm3.eps, is_post_ln=False, ) x = ffn_forward(self.ff, x) # x = ffn_forward(self.ff, self.norm3(x)) if self.is_res: x += x_skip return x class SpatialTransformerNhwcWrapper(IxformerComfyWrapper): def __init__(self, module): super().__init__() self.use_linear = module.use_linear self.transformer_blocks = module.transformer_blocks self.norm = module.norm if not self.use_linear: self.proj_in = Conv2dNhwcWrapper(module.proj_in) self.proj_out = Conv2dNhwcWrapper(module.proj_out) else: self.proj_in = module.proj_in self.proj_out = module.proj_out @ForwardWrapper def forward(self, x, context=None, transformer_options={}): # note: if no context is given, cross-attention defaults to self-attention if not isinstance(context, list): context = [context] * len(self.transformer_blocks) b, h, w, c = x.shape x_in = x # group_norm x = ixf_F.group_norm( x, self.norm.num_groups, self.norm.weight, self.norm.bias, format=False, ) # conv2d if not self.use_linear: x = self.proj_in(x) # n,(hw),c x = x.view(x.shape[0], -1, x.shape[-1]) if self.use_linear: x = self.proj_in(x) for i, block in enumerate(self.transformer_blocks): transformer_options["block_index"] = i # x = block(x, context=context[i], transformer_options=transformer_options) x = transformer_block_forward( block, x, context=context[i], transformer_options=transformer_options ) if self.use_linear: x = self.proj_out(x) x = x.view(b, h, w, c) if not self.use_linear: x = self.proj_out(x) return x + x_in class UpsampleNhwcWrapper(IxformerComfyWrapper): def __init__(self, module) -> None: # TODO: support mhwc interpolate super().__init__() self.dims = module.dims self.use_conv = module.use_conv self.channels = module.channels if self.use_conv: self.conv = Conv2dNhwcWrapper(module.conv) def forward(self, x, output_shape=None): # print("================== Upsample is running ==================") assert x.shape[-1] == self.channels assert len(x.shape) == 4 # nhwc -> nchw if output_shape is not None: assert len(output_shape) == 4 output_shape = [ output_shape[0], output_shape[3], output_shape[1], output_shape[2], ] x = x.permute(0, 3, 1, 2).contiguous() if self.dims == 3: shape = [x.shape[2], x.shape[3] * 2, x.shape[4] * 2] if output_shape is not None: shape[1] = output_shape[3] shape[2] = output_shape[4] else: shape = [x.shape[2] * 2, x.shape[3] * 2] if output_shape is not None: shape[0] = output_shape[2] shape[1] = output_shape[3] # TODO: interpolate 支持 nhwc, 去掉前后转置 x = F.interpolate(x, size=shape, mode="nearest") # nchw -> nhwc x = x.permute(0, 2, 3, 1).contiguous() if self.use_conv: x = self.conv(x) return x unet_wrappers = { "Conv2d": Conv2dNhwcWrapper, "ResBlock": ResBlockNhwcWrapper, "Downsample": DownsampleNhwcWrapper, "SpatialTransformer": SpatialTransformerNhwcWrapper, "Upsample": UpsampleNhwcWrapper, } def get_class_name(module): return module.__class__.__name__ def module_wrapper(module): # 将原始的 module 封装为 nhwc 模式 module_name = get_class_name(module) assert ( module_name == "TimestepEmbedSequential" ), f"ixformer unet_model_wrapper only support 'TimestepEmbedSequential' now, but got {module_name}" num_sequential = len(module) for idx_seq in range(num_sequential): sub_module = module[idx_seq] sub_module_name = get_class_name(sub_module) # 判断模块是否已经封装 if not getattr(sub_module, "is_ixf_wrapper", False): if sub_module_name in unet_wrappers: module[idx_seq].forward = unet_wrappers[sub_module_name]( sub_module ).forward module[idx_seq].is_ixf_wrapper = True else: raise NotImplementedError(f"{sub_module_name} not support") return module