|
|
| import torch |
| import torch.nn as nn |
| from .model import WanLayerNorm, WanModel, WanRMSNorm, rope_apply |
| from shared.attention import pay_attention |
| |
| |
| |
|
|
| def reshape_latent(latent, latent_frames): |
| return latent.reshape(latent.shape[0], latent_frames, -1, latent.shape[-1] ) |
|
|
| def restore_latent_shape(latent): |
| return latent.reshape(latent.shape[0], -1, latent.shape[-1] ) |
|
|
| class FusionModel(nn.Module): |
| def __init__(self, video_config=None, audio_config=None): |
| super().__init__() |
| has_video = True |
| has_audio = True |
| if video_config is not None: |
| self.video_model = WanModel(**video_config) |
| else: |
| has_video = False |
| self.video_model = None |
| print("Warning: No video model is provided!") |
| |
| if audio_config is not None: |
| self.audio_model = WanModel(**audio_config) |
| else: |
| has_audio = False |
| self.audio_model = None |
| print("Warning: No audio model is provided!") |
|
|
| if has_video and has_audio: |
| assert len(self.video_model.blocks) == len(self.audio_model.blocks) |
| self.num_blocks = len(self.video_model.blocks) |
|
|
| self.use_sp = False |
| self.inject_cross_attention_kv_projections() |
|
|
| self.init_weights() |
| |
| def inject_cross_attention_kv_projections(self): |
| for vid_block in self.video_model.blocks: |
| vid_block.cross_attn.k_fusion = nn.Linear(vid_block.dim, vid_block.dim) |
| vid_block.cross_attn.v_fusion = nn.Linear(vid_block.dim, vid_block.dim) |
| vid_block.cross_attn.pre_attn_norm_fusion = WanLayerNorm(vid_block.dim, elementwise_affine=True) |
| vid_block.cross_attn.norm_k_fusion = WanRMSNorm(vid_block.dim, eps=1e-6) if vid_block.qk_norm else nn.Identity() |
|
|
| |
| for audio_block in self.audio_model.blocks: |
| audio_block.cross_attn.k_fusion = nn.Linear(audio_block.dim, audio_block.dim) |
| audio_block.cross_attn.v_fusion = nn.Linear(audio_block.dim, audio_block.dim) |
| audio_block.cross_attn.pre_attn_norm_fusion = WanLayerNorm(audio_block.dim, elementwise_affine=True) |
| audio_block.cross_attn.norm_k_fusion = WanRMSNorm(audio_block.dim, eps=1e-6) if audio_block.qk_norm else nn.Identity() |
|
|
|
|
| def merge_kwargs(self, vid_kwargs, audio_kwargs): |
| """ |
| keys in each kwarg: |
| e |
| seq_lens |
| grid_sizes |
| freqs |
| context |
| context_lens |
| """ |
| merged_kwargs = {} |
| for key in vid_kwargs: |
| merged_kwargs[f"vid_{key}"] = vid_kwargs[key] |
| for key in audio_kwargs: |
| merged_kwargs[f"audio_{key}"] = audio_kwargs[key] |
| return merged_kwargs |
|
|
| def single_fusion_cross_attention_forward(self, |
| cross_attn_block, |
| src_seq, |
| src_grid_sizes, |
| src_freqs, |
| target_seq, |
| target_seq_lens, |
| target_grid_sizes, |
| target_freqs, |
| context, |
| context_lens |
| ): |
| |
| b, n, d = src_seq.size(0), cross_attn_block.num_heads, cross_attn_block.head_dim |
| if hasattr(cross_attn_block, "k_img"): |
| |
| q, k, v, k_img, v_img = cross_attn_block.qkv_fn(src_seq, context) |
| else: |
| |
| q, k, v = cross_attn_block.qkv_fn(src_seq, context) |
| k_img = v_img = None |
|
|
| |
| qkv_list =[q, k, v] |
| del k, v |
| x = pay_attention(qkv_list) |
|
|
| if k_img is not None: |
| qkv_list =[q, k_img, v_img] |
| del k_img, v_img |
| img_x = pay_attention(qkv_list) |
|
|
| |
| x += img_x |
|
|
| is_vid = src_grid_sizes.shape[1] > 1 |
| |
| target_seq = cross_attn_block.pre_attn_norm_fusion(target_seq) |
| k_target = cross_attn_block.norm_k_fusion(cross_attn_block.k_fusion(target_seq)).view(b, -1, n, d) |
| v_target = cross_attn_block.v_fusion(target_seq).view(b, -1, n, d) |
| |
| q = rope_apply(q, src_grid_sizes, src_freqs) |
| k_target = rope_apply(k_target, target_grid_sizes, target_freqs) |
| |
| qkv_list =[q, k_target, v_target] |
| del q, k_target, v_target |
| target_x = pay_attention(qkv_list) |
|
|
| |
| |
| x += target_x |
| |
| x = x.flatten(2) |
|
|
| x = cross_attn_block.o(x) |
| return x |
|
|
| def single_fusion_cross_attention_ffn_forward(self, |
| attn_block, |
| src_seq, |
| src_grid_sizes, |
| src_freqs, |
| target_seq, |
| target_seq_lens, |
| target_grid_sizes, |
| target_freqs, |
| context, |
| context_lens, |
| src_e): |
| |
| src_seq += self.single_fusion_cross_attention_forward(attn_block.cross_attn, |
| attn_block.norm3(src_seq), |
| src_grid_sizes=src_grid_sizes, |
| src_freqs=src_freqs, |
| target_seq=target_seq, |
| target_seq_lens=target_seq_lens, |
| target_grid_sizes=target_grid_sizes, |
| target_freqs=target_freqs, |
| context=context, |
| context_lens=context_lens |
| ) |
|
|
| latent_frames = src_e[0].shape[0] |
|
|
| y = attn_block.norm2(src_seq).to(torch.bfloat16) |
| y = reshape_latent(y , latent_frames) |
| y *= (1 + src_e[4].squeeze(2)) |
| y += src_e[3].squeeze(2) |
| y = restore_latent_shape(y) |
| |
|
|
|
|
| ffn = attn_block.ffn[0] |
| gelu = attn_block.ffn[1] |
| ffn2= attn_block.ffn[2] |
|
|
| y_shape = y.shape |
| y = y.view(-1, y_shape[-1]) |
| chunk_size = int(y.shape[0]/2.7) |
| chunks =torch.split(y, chunk_size) |
| for y_chunk in chunks: |
| mlp_chunk = ffn(y_chunk) |
| mlp_chunk = gelu(mlp_chunk) |
| y_chunk[...] = ffn2(mlp_chunk) |
| del mlp_chunk |
| y = y.view(y_shape) |
|
|
| src_seq, y = reshape_latent(src_seq , latent_frames), reshape_latent(y , latent_frames) |
| src_seq.addcmul_(y, src_e[5].squeeze(2)) |
| src_seq = restore_latent_shape(src_seq) |
| del y |
|
|
| |
| |
| |
| return src_seq |
| |
| def single_fusion_block_forward(self, |
| vid_block, |
| audio_block, |
| vid, |
| audio, |
| vid_e, |
| vid_seq_lens, |
| vid_grid_sizes, |
| vid_freqs, |
| vid_context, |
| vid_context_lens, |
| audio_e, |
| audio_seq_lens, |
| audio_grid_sizes, |
| audio_freqs, |
| audio_context, |
| audio_context_lens, |
| ): |
| |
| audio_e = audio_block.modulation(audio_e).chunk(6, dim=1) |
|
|
| |
| audio_y = audio_block.norm1(audio).to(torch.bfloat16) |
| audio_y *= (1 + audio_e[1].squeeze(2)) |
| audio_y += audio_e[0].squeeze(2) |
| audio_y = audio_block.self_attn(audio_y, audio_seq_lens, audio_grid_sizes, audio_freqs) |
|
|
| audio.addcmul_(audio_y, audio_e[2].squeeze(2)) |
| del audio_y |
|
|
| latent_frames = vid_e.shape[0] |
|
|
| |
| vid_e = vid_block.modulation(vid_e).chunk(6, dim=1) |
|
|
|
|
| |
| vid_y = vid_block.norm1(vid).to(torch.bfloat16) |
| vid_y = reshape_latent(vid_y , latent_frames) |
| vid_y *= (1 + vid_e[1].squeeze(2)) |
| vid_y += vid_e[0].squeeze(2) |
| vid_y = restore_latent_shape(vid_y) |
| vid_y = vid_block.self_attn(vid_y, vid_seq_lens, vid_grid_sizes, vid_freqs) |
| |
|
|
| vid, vid_y = reshape_latent(vid , latent_frames), reshape_latent(vid_y , latent_frames) |
| vid.addcmul_(vid_y, vid_e[2].squeeze(2)) |
| vid = restore_latent_shape(vid) |
| del vid_y |
|
|
| |
|
|
| |
| audio = self.single_fusion_cross_attention_ffn_forward( |
| audio_block, |
| audio, |
| audio_grid_sizes, |
| audio_freqs, |
| vid, |
| vid_seq_lens, |
| vid_grid_sizes, |
| vid_freqs, |
| audio_context, |
| audio_context_lens, |
| audio_e, |
| ) |
| if audio is None: |
| return None, None |
| |
| |
| |
|
|
| |
| vid = self.single_fusion_cross_attention_ffn_forward( |
| vid_block, |
| vid, |
| vid_grid_sizes, |
| vid_freqs, |
| audio, |
| audio_seq_lens, |
| audio_grid_sizes, |
| audio_freqs, |
| vid_context, |
| vid_context_lens, |
| vid_e, |
| ) |
| if vid is None: |
| return None, None |
|
|
| return vid, audio |
|
|
| def forward( |
| self, |
| vid, |
| audio, |
| t, |
| vid_context, |
| audio_context, |
| vid_seq_len, |
| audio_seq_len, |
| clip_fea=None, |
| clip_fea_audio=None, |
| y=None, |
| first_frame_is_clean=False, |
| computed_perturbation_layers=None, |
| callback = None, |
| pipeline= None, |
| x_id_list = 0, |
| video_freqs = None, |
| audio_freqs = None, |
| ): |
| vid_list = [] |
| vid_e_list = [] |
| audio_list = [] |
| audio_e_list = [] |
| kwargs_list = [] |
| for one_vid_context, one_audio_context in zip(vid_context, audio_context): |
| one_vid, one_vid_e, vid_kwargs = self.video_model.prepare_transformer_block_kwargs( |
| x=[vid], t=t, context=[one_vid_context], seq_len=vid_seq_len, clip_fea=clip_fea, y=y, first_frame_is_clean=first_frame_is_clean, freqs=video_freqs |
| ) |
| vid_list.append(one_vid) |
| vid_e_list.append(one_vid_e) |
| one_vid = one_vid_e = None |
| one_audio, one_audio_e, audio_kwargs = self.audio_model.prepare_transformer_block_kwargs( |
| x=[audio], t=t, context=[one_audio_context], seq_len=audio_seq_len, clip_fea=clip_fea_audio, y=None, first_frame_is_clean=False, freqs=audio_freqs |
| ) |
| audio_list.append(one_audio) |
| audio_e_list.append(one_audio_e) |
| one_audio = one_audio_e = None |
|
|
| kwargs_list.append(self.merge_kwargs(vid_kwargs, audio_kwargs)) |
|
|
| for i in range(self.num_blocks): |
| """ |
| 1 fusion block refers to 1 audio block with 1 video block. |
| """ |
| if callback != None: |
| callback(-1, None, False, True) |
| vid_block = self.video_model.blocks[i] |
| audio_block = self.audio_model.blocks[i] |
| for x_id, one_vid, one_vid_e, one_audio, one_audio_e, one_kwargs in zip(x_id_list, vid_list, vid_e_list, audio_list, audio_e_list, kwargs_list): |
| if pipeline._interrupt: |
| return None, None |
| if x_id == 1 and computed_perturbation_layers is not None and i in computed_perturbation_layers: |
| continue |
| |
| a, b = self.single_fusion_block_forward( |
| vid_block=vid_block, |
| audio_block=audio_block, |
| vid=one_vid, |
| audio=one_audio, |
| **one_kwargs |
| ) |
| one_vid[...], one_audio[...] = a, b |
| |
|
|
| for i, (x_id, one_vid, one_vid_e, one_audio, one_audio_e) in enumerate(zip(x_id_list, vid_list, vid_e_list, audio_list, audio_e_list)): |
| one_vid = self.video_model.post_transformer_block_out(one_vid, vid_kwargs['grid_sizes'], one_vid_e) |
| vid_list[i] = one_vid |
| one_vid = None |
| one_audio = self.audio_model.post_transformer_block_out(one_audio, audio_kwargs['grid_sizes'], one_audio_e) |
| audio_list[i] = one_audio |
| one_audio = None |
|
|
| if len(vid_list) == 1: |
| return vid_list[0], audio_list[0] |
| return vid_list, audio_list |
|
|
| def init_weights(self): |
| if self.audio_model is not None: |
| self.audio_model.init_weights() |
|
|
| if self.video_model is not None: |
| self.video_model.init_weights() |
|
|
| for name, mod in self.video_model.named_modules(): |
| if "fusion" in name and isinstance(mod, nn.Linear): |
| with torch.no_grad(): |
| mod.weight.div_(10.0) |
|
|
| |
| def custom_compile(self, compile_kwargs): |
| torch.compile(self.single_fusion_block_forward, **compile_kwargs) |
|
|