Support for Hunyuan Video Avatar
This commit is contained in:
220
hyvideo/modules/audio_adapters.py
Normal file
220
hyvideo/modules/audio_adapters.py
Normal file
@@ -0,0 +1,220 @@
|
||||
"""
|
||||
This module provides the implementation of an Audio Projection Model, which is designed for
|
||||
audio processing tasks. The model takes audio embeddings as input and outputs context tokens
|
||||
that can be used for various downstream applications, such as audio analysis or synthesis.
|
||||
|
||||
The AudioProjModel class is based on the ModelMixin class from the diffusers library, which
|
||||
provides a foundation for building custom models. This implementation includes multiple linear
|
||||
layers with ReLU activation functions and a LayerNorm for normalization.
|
||||
|
||||
Key Features:
|
||||
- Audio embedding input with flexible sequence length and block structure.
|
||||
- Multiple linear layers for feature transformation.
|
||||
- ReLU activation for non-linear transformation.
|
||||
- LayerNorm for stabilizing and speeding up training.
|
||||
- Rearrangement of input embeddings to match the model's expected input shape.
|
||||
- Customizable number of blocks, channels, and context tokens for adaptability.
|
||||
|
||||
The module is structured to be easily integrated into larger systems or used as a standalone
|
||||
component for audio feature extraction and processing.
|
||||
|
||||
Classes:
|
||||
- AudioProjModel: A class representing the audio projection model with configurable parameters.
|
||||
|
||||
Functions:
|
||||
- (none)
|
||||
|
||||
Dependencies:
|
||||
- torch: For tensor operations and neural network components.
|
||||
- diffusers: For the ModelMixin base class.
|
||||
- einops: For tensor rearrangement operations.
|
||||
|
||||
"""
|
||||
|
||||
import torch
|
||||
from diffusers import ModelMixin
|
||||
from einops import rearrange
|
||||
|
||||
import math
|
||||
import torch.nn as nn
|
||||
|
||||
class AudioProjNet2(ModelMixin):
|
||||
"""Audio Projection Model
|
||||
|
||||
This class defines an audio projection model that takes audio embeddings as input
|
||||
and produces context tokens as output. The model is based on the ModelMixin class
|
||||
and consists of multiple linear layers and activation functions. It can be used
|
||||
for various audio processing tasks.
|
||||
|
||||
Attributes:
|
||||
seq_len (int): The length of the audio sequence.
|
||||
blocks (int): The number of blocks in the audio projection model.
|
||||
channels (int): The number of channels in the audio projection model.
|
||||
intermediate_dim (int): The intermediate dimension of the model.
|
||||
context_tokens (int): The number of context tokens in the output.
|
||||
output_dim (int): The output dimension of the context tokens.
|
||||
|
||||
Methods:
|
||||
__init__(self, seq_len=5, blocks=12, channels=768, intermediate_dim=512, context_tokens=32, output_dim=768):
|
||||
Initializes the AudioProjModel with the given parameters.
|
||||
forward(self, audio_embeds):
|
||||
Defines the forward pass for the AudioProjModel.
|
||||
Parameters:
|
||||
audio_embeds (torch.Tensor): The input audio embeddings with shape (batch_size, video_length, blocks, channels).
|
||||
Returns:
|
||||
context_tokens (torch.Tensor): The output context tokens with shape (batch_size, video_length, context_tokens, output_dim).
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
seq_len=5,
|
||||
blocks=12, # add a new parameter blocks
|
||||
channels=768, # add a new parameter channels
|
||||
intermediate_dim=512,
|
||||
output_dim=768,
|
||||
context_tokens=4,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.seq_len = seq_len
|
||||
self.blocks = blocks
|
||||
self.channels = channels
|
||||
self.input_dim = (
|
||||
seq_len * blocks * channels
|
||||
)
|
||||
self.intermediate_dim = intermediate_dim
|
||||
self.context_tokens = context_tokens
|
||||
self.output_dim = output_dim
|
||||
|
||||
# define multiple linear layers
|
||||
self.proj1 = nn.Linear(self.input_dim, intermediate_dim)
|
||||
self.proj2 = nn.Linear(intermediate_dim, intermediate_dim)
|
||||
self.proj3 = nn.Linear(intermediate_dim, context_tokens * output_dim)
|
||||
|
||||
self.norm = nn.LayerNorm(output_dim)
|
||||
|
||||
|
||||
def forward(self, audio_embeds):
|
||||
|
||||
video_length = audio_embeds.shape[1]
|
||||
audio_embeds = rearrange(audio_embeds, "bz f w b c -> (bz f) w b c")
|
||||
batch_size, window_size, blocks, channels = audio_embeds.shape
|
||||
audio_embeds = audio_embeds.view(batch_size, window_size * blocks * channels)
|
||||
|
||||
audio_embeds = torch.relu(self.proj1(audio_embeds))
|
||||
audio_embeds = torch.relu(self.proj2(audio_embeds))
|
||||
|
||||
context_tokens = self.proj3(audio_embeds).reshape(
|
||||
batch_size, self.context_tokens, self.output_dim
|
||||
)
|
||||
context_tokens = self.norm(context_tokens)
|
||||
out_all = rearrange(
|
||||
context_tokens, "(bz f) m c -> bz f m c", f=video_length
|
||||
)
|
||||
|
||||
return out_all
|
||||
|
||||
|
||||
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)
|
||||
x = x.reshape(bs, heads, length, -1)
|
||||
return x
|
||||
|
||||
|
||||
class PerceiverAttentionCA(nn.Module):
|
||||
def __init__(self, *, dim=3072, dim_head=1024, heads=33):
|
||||
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)
|
||||
|
||||
import torch.nn.init as init
|
||||
init.zeros_(self.to_out.weight)
|
||||
if self.to_out.bias is not None:
|
||||
init.zeros_(self.to_out.bias)
|
||||
|
||||
def forward(self, x, latents):
|
||||
"""
|
||||
Args:
|
||||
x (torch.Tensor): image features
|
||||
shape (b, t, aa, D)
|
||||
latent (torch.Tensor): latent features
|
||||
shape (b, t, hw, D)
|
||||
"""
|
||||
x = self.norm1(x)
|
||||
latents = self.norm2(latents)
|
||||
# print("latents shape: ", latents.shape)
|
||||
# print("x shape: ", x.shape)
|
||||
q = self.to_q(latents)
|
||||
k, v = self.to_kv(x).chunk(2, dim=-1)
|
||||
|
||||
|
||||
# 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)
|
||||
return self.to_out(out)
|
||||
#def forward(self, x, latents):
|
||||
# """
|
||||
# Args:
|
||||
# x (torch.Tensor): image features
|
||||
# shape (b, t, aa, D)
|
||||
# latent (torch.Tensor): latent features
|
||||
# shape (b, t, hw, D)
|
||||
# """
|
||||
# if get_sequence_parallel_state():
|
||||
# sp_size = nccl_info.sp_size
|
||||
# sp_rank = nccl_info.rank_within_group
|
||||
# print("rank:", latents.shape, sp_size, sp_rank)
|
||||
# latents = torch.chunk(latents, sp_size, dim=1)[sp_rank]
|
||||
|
||||
# x = self.norm1(x)
|
||||
# latents = self.norm2(latents)
|
||||
# # print("latents shape: ", latents.shape)
|
||||
# # print("x shape: ", x.shape)
|
||||
# q = self.to_q(latents)
|
||||
# k, v = self.to_kv(x).chunk(2, dim=-1)
|
||||
|
||||
# # print("q, k, v: ", q.shape, k.shape, v.shape)
|
||||
|
||||
# # 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
|
||||
# def shrink_head(encoder_state, dim):
|
||||
# local_heads = encoder_state.shape[dim] // nccl_info.sp_size
|
||||
# return encoder_state.narrow(dim, nccl_info.rank_within_group * local_heads, local_heads)
|
||||
|
||||
# if get_sequence_parallel_state():
|
||||
# # batch_size, seq_len, attn_heads, head_dim
|
||||
# q = all_to_all_4D(q, scatter_dim=2, gather_dim=1) # [2, 32256, 24, 128]
|
||||
# k = shrink_head(k ,dim=2)
|
||||
# v = shrink_head(v ,dim=2)
|
||||
# qkv = torch.stack([query, key, value], dim=2)
|
||||
# attn = flash_attn_no_pad(qkv, causal=False, dropout_p=0.0, softmax_scale=None)
|
||||
# # out = out.permute(0, 2, 1, 3)
|
||||
# #b, s, a, d = attn.shape
|
||||
# #attn = attn.reshape(b, s, -1)
|
||||
#
|
||||
# out = self.to_out(attn)
|
||||
# if get_sequence_parallel_state():
|
||||
# out = all_gather(out, dim=1)
|
||||
# return out
|
||||
@@ -53,10 +53,11 @@ class PatchEmbed(nn.Module):
|
||||
|
||||
def forward(self, x):
|
||||
x = self.proj(x)
|
||||
shape = x.shape
|
||||
if self.flatten:
|
||||
x = x.flatten(2).transpose(1, 2) # BCHW -> BNC
|
||||
x = self.norm(x)
|
||||
return x
|
||||
return x, shape
|
||||
|
||||
|
||||
class TextProjection(nn.Module):
|
||||
|
||||
@@ -19,6 +19,7 @@ from .token_refiner import SingleTokenRefiner
|
||||
import numpy as np
|
||||
from mmgp import offload
|
||||
from wan.modules.attention import pay_attention
|
||||
from .audio_adapters import AudioProjNet2, PerceiverAttentionCA
|
||||
|
||||
def get_linear_split_map():
|
||||
hidden_size = 3072
|
||||
@@ -589,7 +590,8 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
use_attention_mask: bool = True,
|
||||
dtype: Optional[torch.dtype] = None,
|
||||
device: Optional[torch.device] = None,
|
||||
attention_mode: Optional[str] = "sdpa"
|
||||
attention_mode: Optional[str] = "sdpa",
|
||||
avatar = False,
|
||||
):
|
||||
factory_kwargs = {"device": device, "dtype": dtype}
|
||||
super().__init__()
|
||||
@@ -708,6 +710,45 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
get_activation_layer("silu"),
|
||||
**factory_kwargs,
|
||||
)
|
||||
avatar_audio = avatar
|
||||
if avatar_audio:
|
||||
self.ref_in = PatchEmbed(
|
||||
self.patch_size, self.in_channels, self.hidden_size, **factory_kwargs
|
||||
)
|
||||
|
||||
# -------------------- audio_proj_model --------------------
|
||||
self.audio_proj = AudioProjNet2(seq_len=10, blocks=5, channels=384, intermediate_dim=1024, output_dim=3072, context_tokens=4)
|
||||
|
||||
# -------------------- motion-embeder --------------------
|
||||
self.motion_exp = TimestepEmbedder(
|
||||
self.hidden_size // 4,
|
||||
get_activation_layer("silu"),
|
||||
**factory_kwargs
|
||||
)
|
||||
self.motion_pose = TimestepEmbedder(
|
||||
self.hidden_size // 4,
|
||||
get_activation_layer("silu"),
|
||||
**factory_kwargs
|
||||
)
|
||||
|
||||
self.fps_proj = TimestepEmbedder(
|
||||
self.hidden_size,
|
||||
get_activation_layer("silu"),
|
||||
**factory_kwargs
|
||||
)
|
||||
|
||||
self.before_proj = nn.Linear(self.hidden_size, self.hidden_size)
|
||||
|
||||
# -------------------- audio_insert_model --------------------
|
||||
self.double_stream_list = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
|
||||
self.single_stream_list = []
|
||||
self.double_stream_map = {str(i): j for j, i in enumerate(self.double_stream_list)}
|
||||
self.single_stream_map = {str(i): j+len(self.double_stream_list) for j, i in enumerate(self.single_stream_list)}
|
||||
|
||||
self.audio_adapter_blocks = nn.ModuleList([
|
||||
PerceiverAttentionCA(dim=3072, dim_head=1024, heads=33) for _ in range(len(self.double_stream_list) + len(self.single_stream_list))
|
||||
])
|
||||
|
||||
|
||||
|
||||
def lock_layers_dtypes(self, dtype = torch.float32):
|
||||
@@ -750,11 +791,17 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
guidance: torch.Tensor = None, # Guidance for modulation, should be cfg_scale x 1000.
|
||||
pipeline=None,
|
||||
x_id = 0,
|
||||
step_no = 0,
|
||||
callback = None,
|
||||
audio_prompts = None,
|
||||
motion_exp = None,
|
||||
motion_pose = None,
|
||||
fps = None,
|
||||
face_mask = None,
|
||||
) -> Union[torch.Tensor, Dict[str, torch.Tensor]]:
|
||||
|
||||
img = x
|
||||
batch_no, _, ot, oh, ow = x.shape
|
||||
bsz, _, ot, oh, ow = x.shape
|
||||
del x
|
||||
txt = text_states
|
||||
tt, th, tw = (
|
||||
@@ -765,6 +812,17 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
|
||||
# Prepare modulation vectors.
|
||||
vec = self.time_in(t)
|
||||
if motion_exp != None:
|
||||
vec += self.motion_exp(motion_exp.view(-1)).view(bsz, -1) # (b, 3072)
|
||||
if motion_pose != None:
|
||||
vec += self.motion_pose(motion_pose.view(-1)).view(bsz, -1) # (b, 3072)
|
||||
if fps != None:
|
||||
vec += self.fps_proj(fps) # (b, 3072)
|
||||
if audio_prompts != None:
|
||||
audio_feature_all = self.audio_proj(audio_prompts)
|
||||
audio_feature_pad = audio_feature_all[:,:1].repeat(1,3,1,1)
|
||||
audio_feature_all_insert = torch.cat([audio_feature_pad, audio_feature_all], dim=1).view(bsz, ot, 16, 3072)
|
||||
audio_feature_all = None
|
||||
|
||||
if self.i2v_condition_type == "token_replace":
|
||||
token_replace_t = torch.zeros_like(t)
|
||||
@@ -777,7 +835,6 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
# token_replace_mask_txt = None
|
||||
|
||||
# text modulation
|
||||
# vec = vec + self.vector_in(text_states_2)
|
||||
vec_2 = self.vector_in(text_states_2)
|
||||
del text_states_2
|
||||
vec += vec_2
|
||||
@@ -793,12 +850,17 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
)
|
||||
|
||||
# our timestep_embedding is merged into guidance_in(TimestepEmbedder)
|
||||
vec = vec + self.guidance_in(guidance)
|
||||
vec += self.guidance_in(guidance)
|
||||
|
||||
# Embed image and text.
|
||||
img = self.img_in(img)
|
||||
if ref_latents != None:
|
||||
ref_latents = self.img_in(ref_latents)
|
||||
img, shape_mask = self.img_in(img)
|
||||
if audio_prompts != None:
|
||||
ref_latents_first = ref_latents[:, :, :1].clone()
|
||||
ref_latents,_ = self.ref_in(ref_latents)
|
||||
ref_latents_first,_ = self.img_in(ref_latents_first)
|
||||
elif ref_latents != None:
|
||||
ref_latents, _ = self.img_in(ref_latents)
|
||||
|
||||
if self.text_projection == "linear":
|
||||
txt = self.txt_in(txt)
|
||||
elif self.text_projection == "single_refiner":
|
||||
@@ -808,7 +870,18 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
f"Unsupported text_projection: {self.text_projection}"
|
||||
)
|
||||
|
||||
if ref_latents == None:
|
||||
if audio_prompts != None:
|
||||
img += self.before_proj(ref_latents)
|
||||
ref_length = ref_latents_first.shape[-2] # [b s c]
|
||||
img = torch.cat([ref_latents_first, img], dim=-2) # t c
|
||||
img_len = img.shape[1]
|
||||
mask_len = img_len - ref_length
|
||||
if face_mask.shape[2] == 1:
|
||||
face_mask = face_mask.repeat(1,1,ot,1,1) # repeat if number of mask frame is 1
|
||||
face_mask = torch.nn.functional.interpolate(face_mask, size=[ot, shape_mask[-2], shape_mask[-1]], mode="nearest")
|
||||
# face_mask = face_mask.view(-1,mask_len,1).repeat(1,1,img.shape[-1]).type_as(img)
|
||||
face_mask = face_mask.view(-1,mask_len,1).type_as(img)
|
||||
elif ref_latents == None:
|
||||
ref_length = None
|
||||
else:
|
||||
ref_length = ref_latents.shape[-2]
|
||||
@@ -828,13 +901,13 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
if x_id == 0:
|
||||
self.should_calc = True
|
||||
inp = img[0:1]
|
||||
vec_ = vec
|
||||
vec_ = vec[0:1]
|
||||
( img_mod1_shift, img_mod1_scale, _ , _ , _ , _ , ) = self.double_blocks[0].img_mod(vec_).chunk(6, dim=-1)
|
||||
normed_inp = self.double_blocks[0].img_norm1(inp)
|
||||
normed_inp = normed_inp.to(torch.bfloat16)
|
||||
modulated_inp = modulate( normed_inp, shift=img_mod1_shift, scale=img_mod1_scale )
|
||||
del normed_inp, img_mod1_shift, img_mod1_scale
|
||||
if self.teacache_counter <= self.teacache_start_step or self.teacache_counter == self.num_steps-1:
|
||||
if step_no <= self.teacache_start_step or step_no == self.num_steps-1:
|
||||
self.accumulated_rel_l1_distance = 0
|
||||
else:
|
||||
coefficients = [7.33226126e+02, -4.01131952e+02, 6.75869174e+01, -3.14987800e+00, 9.61237896e-02]
|
||||
@@ -846,9 +919,6 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
else:
|
||||
self.accumulated_rel_l1_distance = 0
|
||||
self.previous_modulated_input = modulated_inp
|
||||
self.teacache_counter += 1
|
||||
if self.teacache_counter == self.num_steps:
|
||||
self.teacache_counter = 0
|
||||
else:
|
||||
self.should_calc = True
|
||||
|
||||
@@ -859,7 +929,7 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
self.previous_residual[x_id] = None
|
||||
ori_img = img[0:1].clone()
|
||||
# --------------------- Pass through DiT blocks ------------------------
|
||||
for _, block in enumerate(self.double_blocks):
|
||||
for layer_num, block in enumerate(self.double_blocks):
|
||||
for i in range(len(img)):
|
||||
if callback != None:
|
||||
callback(-1, None, False, True)
|
||||
@@ -880,6 +950,16 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
|
||||
img[i], txt[i] = block(*double_block_args)
|
||||
double_block_args = None
|
||||
# insert audio feature to img
|
||||
if audio_prompts != None:
|
||||
audio_adapter = getattr(self.double_blocks[layer_num], "audio_adapter", None)
|
||||
if audio_adapter != None:
|
||||
real_img = img[i:i+1,ref_length:].view(1, ot, -1, 3072)
|
||||
real_img = audio_adapter(audio_feature_all_insert[i:i+1], real_img).view(1, -1, 3072)
|
||||
real_img *= face_mask[i:i+1]
|
||||
img[i:i+1, ref_length:] += real_img
|
||||
real_img = None
|
||||
|
||||
|
||||
for _, block in enumerate(self.single_blocks):
|
||||
for i in range(len(img)):
|
||||
@@ -932,6 +1012,7 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
|
||||
img = torch.cat(img_list)
|
||||
img_list = None
|
||||
|
||||
# img = self.unpatchify(img, tt, th, tw)
|
||||
img = self.unpatchify(img, tt, th, tw)
|
||||
|
||||
return img
|
||||
@@ -1015,5 +1096,14 @@ HUNYUAN_VIDEO_CONFIG = {
|
||||
"heads_num": 24,
|
||||
"mlp_width_ratio": 4,
|
||||
},
|
||||
'HYVideo-T/2-avatar': { # 9.0B / 12.5B
|
||||
'mm_double_blocks_depth': 20,
|
||||
'mm_single_blocks_depth': 40,
|
||||
'rope_dim_list': [16, 56, 56],
|
||||
'hidden_size': 3072,
|
||||
'heads_num': 24,
|
||||
'mlp_width_ratio': 4,
|
||||
'avatar': True,
|
||||
},
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user