Download src/models/transformers/anymo.py from human-motion-capture/AnyMo: direct link, hf CLI and curl.
- Browser
- Download file 33 kB
-
https://huggingface.co/human-motion-capture/AnyMo/resolve/main/src/models/transformers/anymo.py
- Command line
-
hf download hf://human-motion-capture/AnyMo/src/models/transformers/anymo.py
-
curl -L -o anymo.py https://huggingface.co/human-motion-capture/AnyMo/resolve/main/src/models/transformers/anymo.py
33 kB
| import torch, math | |
| import torch.nn.functional as F | |
| from mmengine.registry import MODELS | |
| from mmengine.model import BaseModel | |
| from typing import Dict, List | |
| from torch.distributions.categorical import Categorical | |
| import numpy as np | |
| from random import random | |
| from ...utils.motion import recover_from_local_position_fast | |
| from ..tokenizers.encdoc.encdoc_attn import length_to_mask | |
| def cosine_schedule(t): | |
| return torch.cos(t * math.pi * 0.5) | |
| def get_mask_subset_prob(mask, prob): | |
| subset_mask = torch.bernoulli(mask, p=prob) & mask | |
| return subset_mask | |
| def uniform(shape, device=None): | |
| return torch.zeros(shape, device=device).float().uniform_(0, 1) | |
| def top_k(logits, thres = 0.9, dim = 1): | |
| k = math.ceil((1 - thres) * logits.shape[dim]) | |
| val, ind = logits.topk(k, dim = dim) | |
| probs = torch.full_like(logits, float('-inf')) | |
| probs.scatter_(dim, ind, val) | |
| return probs | |
| class AnyMo(BaseModel): | |
| def __init__( | |
| self, | |
| vqvae_cfg: Dict, | |
| transformer_cfg: Dict, | |
| max_motion_len: int, | |
| motion_downsample_ratio: int, | |
| audio_downsample_ratio: int, | |
| text_encoder_cfg: Dict=None, | |
| traj_encoder_cfg: Dict=None, | |
| audio_encoder_cfg: Dict=None, | |
| stage=1, | |
| inference_timesteps=6, | |
| cond_types = ['text'], | |
| **kwargs | |
| ): | |
| super().__init__(**kwargs) | |
| self.transformer = MODELS.build(transformer_cfg) | |
| # import ipdb; ipdb.set_trace() | |
| if text_encoder_cfg is not None: | |
| self.text_encoder = MODELS.build(text_encoder_cfg) | |
| if traj_encoder_cfg is not None: | |
| self.traj_encoder = MODELS.build(traj_encoder_cfg) | |
| if audio_encoder_cfg is not None: | |
| self.audio_encoder = MODELS.build(audio_encoder_cfg) | |
| # 第一阶段tm训练,第二阶段只训练projector,第三阶段全量训练, 4阶段测评 | |
| assert stage in [1, 2, 3, 4] | |
| self.stage = stage | |
| # build vqvae | |
| self.vqvae = MODELS.build(vqvae_cfg) | |
| self.vqvae.init_weights() | |
| for param in self.vqvae.parameters(): | |
| param.requires_grad = False | |
| self.vqvae.eval() | |
| self.vocab_size = transformer_cfg.vocab_size # type: ignore | |
| self.n_embd = transformer_cfg.n_embd | |
| self.mask_idx = self.vocab_size - 1 | |
| self.pad_idx = self.vocab_size - 2 | |
| self.ignore_index = -100 | |
| self.max_motion_len = max_motion_len | |
| self.motion_downsample_ratio = motion_downsample_ratio | |
| self.audio_downsample_ratio = audio_downsample_ratio | |
| self.topk_filter_thres = 0.9 | |
| self.noise_schedule = cosine_schedule | |
| self.n_quant = len(self.vqvae.quantizer.quantizers) | |
| self.inference_timesteps = inference_timesteps | |
| self.cond_types = cond_types | |
| for cond_type in cond_types: | |
| assert cond_type in ['text', 'audio', 'traj'] | |
| if self.stage == 2: | |
| self.init_weights() | |
| # only train audio and traj projector | |
| for param in self.transformer.parameters(): | |
| param.requires_grad = False | |
| for param in self.text_encoder.parameters(): | |
| param.requires_grad = False | |
| def forward_stage_1(self, motion, motion_length, text_data, mode='loss'): | |
| ''' | |
| 第一阶段只训t2m | |
| ''' | |
| B = motion.shape[0] | |
| # 1. prepare text feature | |
| # text feat: B, text_token_cnt, C | |
| # text mask: B, text_token_cnt | |
| # import ipdb; ipdb.set_trace() | |
| text_feat, text_mask = self.text_encoder(text_data) | |
| audio_feat = torch.zeros(B, 0, self.n_embd).to(text_feat) | |
| audio_mask = torch.zeros(B, 0).to(text_feat) | |
| traj_feat = torch.zeros(B, 0, self.n_embd).to(text_feat) | |
| traj_mask = torch.zeros(B, 0).to(text_feat) | |
| # import ipdb; ipdb.set_trace() | |
| if mode == 'loss': | |
| tokens, label_tokens, non_pad_mask = self.prepare_train_input(motion, motion_length) | |
| loss = self.forward_loss(tokens, label_tokens, text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask, non_pad_mask) | |
| return loss | |
| else: | |
| non_pad_mask = self.prepare_test_input(motion_length) | |
| return self.forward_predict(text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask, non_pad_mask) | |
| def forward_stage_2(self, motion, motion_length, audio_data, traj_data, audio_length, mode='loss', **kwargs): | |
| B = motion.shape[0] | |
| # 2. prepare audio and traj feature | |
| # audio feat: B, audio_token_cnt, C | |
| # traj feat: B, traj_token_cnt, C | |
| prob = random() | |
| # import ipdb; ipdb.set_trace() | |
| if prob < 0.7: # 百分之70的语音数据 | |
| audio_feat = self.audio_encoder(audio_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| audio_mask = length_to_mask(audio_length // self.audio_downsample_ratio) | |
| traj_feat = torch.zeros(B, 0, self.n_embd).to(audio_feat) | |
| traj_mask = torch.zeros(B, 0).to(audio_feat) | |
| dtype = audio_feat.dtype | |
| device = audio_feat.device | |
| elif prob >= 0.7 and prob < 0.9: # 百分之20的轨迹数据 | |
| traj_feat = self.traj_encoder(traj_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| traj_mask = length_to_mask(motion_length // self.motion_downsample_ratio) | |
| audio_feat = torch.zeros(B, 0, self.n_embd).to(traj_feat) | |
| audio_mask = torch.zeros(B, 0).to(traj_feat) | |
| dtype = traj_feat.dtype | |
| device = traj_feat.device | |
| else: | |
| audio_feat = self.audio_encoder(audio_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| audio_mask = length_to_mask(audio_length // self.audio_downsample_ratio) | |
| traj_feat = self.traj_encoder(traj_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| traj_mask = length_to_mask(motion_length // self.motion_downsample_ratio) | |
| dtype = audio_feat.dtype | |
| device = audio_feat.device | |
| text_feat = torch.zeros(B, 0, self.n_embd).to(dtype).to(device) | |
| text_mask = torch.zeros(B, 0).to(dtype).to(device) | |
| if mode == 'loss': | |
| tokens, label_tokens, non_pad_mask = self.prepare_train_input(motion, motion_length) | |
| loss = self.forward_loss( | |
| tokens, label_tokens, text_feat, text_mask, audio_feat, | |
| audio_mask, traj_feat, traj_mask, non_pad_mask, motion=motion, mean=kwargs['mean'], | |
| std=kwargs['std'] | |
| ) | |
| return loss | |
| else: | |
| non_pad_mask = self.prepare_test_input(motion_length) | |
| return self.forward_predict(text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask, non_pad_mask, **kwargs) | |
| def forward_stage_3(self, motion, motion_length, text_data, audio_data, traj_data, audio_length, mode='loss', **kwargs): | |
| B = motion.shape[0] | |
| # import ipdb; ipdb.set_trace() | |
| has_traj = random() < 0.1 # 10%的几率加traj | |
| has_text_lst = (audio_length == 0) # 没 audio 的加 text | |
| has_text_lst[has_text_lst == False] = random() < 0.1 # audio 10% 的几率加 text | |
| text_feat, text_mask = self.text_encoder(text_data) | |
| traj_feat = self.traj_encoder(traj_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| traj_mask = length_to_mask(motion_length // self.motion_downsample_ratio) | |
| traj_mask[:] = False if not has_traj else True | |
| text_mask = text_mask * has_text_lst[:, None] | |
| if not torch.all(audio_length == 0): | |
| audio_feat = self.audio_encoder(audio_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| audio_mask = length_to_mask(audio_length // self.audio_downsample_ratio) | |
| else: | |
| # audio下采样8倍 | |
| audio_data = torch.zeros((B, 8, 512)).to(motion) | |
| audio_feat = self.audio_encoder(audio_data.permute(0, 2, 1)).permute(0, 2, 1).mean() * 0.0 | |
| audio_mask = torch.zeros(B, 0).to(text_feat.device) | |
| text_feat += audio_feat * 0.0 | |
| traj_feat += audio_feat * 0.0 | |
| audio_feat = torch.zeros(B, 0, self.n_embd).to(text_feat.device) | |
| if mode == 'loss': | |
| tokens, label_tokens, non_pad_mask = self.prepare_train_input(motion, motion_length) | |
| loss = self.forward_loss( | |
| tokens, label_tokens, text_feat, text_mask, audio_feat, | |
| audio_mask, traj_feat, traj_mask, non_pad_mask, motion=motion, | |
| mean=kwargs['mean'], std=kwargs['std'] | |
| ) | |
| return loss | |
| else: | |
| # 只测text | |
| text_feat, text_mask = self.text_encoder(text_data) | |
| B = text_feat.shape[0] | |
| audio_feat = torch.zeros(B, 0, self.n_embd).to(device) | |
| audio_mask = torch.zeros(B, 0).to(device) | |
| traj_feat = torch.zeros(B, 0, self.n_embd).to(device) | |
| traj_mask = torch.zeros(B, 0).to(device) | |
| non_pad_mask = self.prepare_test_input(motion_length) | |
| return self.forward_predict(text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask, non_pad_mask, **kwargs) | |
| def forward( | |
| self, | |
| motion: torch.Tensor, | |
| motion_length: torch.Tensor, | |
| text_data: List[str]=None, | |
| audio_data: torch.Tensor=None, | |
| traj_data: torch.Tensor=None, | |
| audio_length: torch.Tensor=None, | |
| mode = 'loss', | |
| **kwargs | |
| ): | |
| ''' | |
| motion: tensor, B, T, C | |
| motion_length: tensor, B | |
| text_data: list[str], len B | |
| has_xxx: list[bool], len B | |
| music_data: tensor, B, T, C | |
| ''' | |
| if self.stage == 1: | |
| return self.forward_stage_1(motion, motion_length, text_data, mode) | |
| elif self.stage == 2: | |
| return self.forward_stage_2(motion, motion_length, audio_data, traj_data, audio_length, mode, **kwargs) | |
| elif self.stage == 3: | |
| return self.forward_stage_3(motion, motion_length, text_data, audio_data, traj_data, audio_length, mode, **kwargs) | |
| elif self.stage == 4: # for evaluation | |
| B = motion.shape[0] | |
| device = motion.device | |
| text_feat = torch.zeros(B, 0, self.n_embd).to(device) | |
| audio_feat = torch.zeros(B, 0, self.n_embd).to(device) | |
| traj_feat = torch.zeros(B, 0, self.n_embd).to(device) | |
| text_mask = torch.zeros(B, 0).to(device) | |
| audio_mask = torch.zeros(B, 0).to(device) | |
| traj_mask = torch.zeros(B, 0).to(device) | |
| if 'text' in self.cond_types: | |
| text_feat, text_mask = self.text_encoder(text_data) | |
| dtype = text_feat.dtype | |
| if 'audio' in self.cond_types: | |
| audio_feat = self.audio_encoder(audio_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| audio_mask = length_to_mask(audio_length // self.audio_downsample_ratio) | |
| dtype = audio_feat.dtype | |
| if 'traj' in self.cond_types: | |
| traj_feat = self.traj_encoder(traj_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| traj_mask = length_to_mask(motion_length // self.motion_downsample_ratio) | |
| dtype = traj_feat.dtype | |
| text_feat, text_mask = text_feat.to(dtype), text_mask.to(device) | |
| audio_feat, audio_mask = audio_feat.to(dtype), audio_mask.to(dtype) | |
| traj_feat, traj_mask = traj_feat.to(dtype), traj_mask.to(dtype) | |
| non_pad_mask = self.prepare_test_input(motion_length) | |
| return self.forward_predict(text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask, non_pad_mask, **kwargs) | |
| else: | |
| raise NotImplementedError | |
| def prepare_train_input(self, motion, motion_length): | |
| if self.vqvae.training: | |
| self.vqvae.eval() | |
| B = motion.shape[0] | |
| device = motion.device | |
| m_token_lens = motion_length // self.motion_downsample_ratio | |
| max_token_len = m_token_lens.max() | |
| ids = self.vqvae.encode(motion, motion_length) # B, T, n_quant | |
| # True, True, ..., False | |
| non_pad_mask = torch.arange(max_token_len, device=device).expand(B, max_token_len) < m_token_lens.unsqueeze(1) | |
| all_x_ids = [] | |
| all_labels = [] | |
| mask = None | |
| for i in range(ids.shape[-1]): # n_quant | |
| # import ipdb; ipdb.set_trace() | |
| m_tokens_i = ids[..., i] # B, T | |
| # 1. prepare label token | |
| labels = torch.where(non_pad_mask, m_tokens_i, self.ignore_index) | |
| # 2. prepare input token | |
| m_tokens_i = torch.where(non_pad_mask, m_tokens_i, self.pad_idx) # [m1, m2, m3, ..., pad1, pad2, ...] | |
| # 2.1 prepare mask | |
| if mask is None: | |
| rand_time = uniform((B,), device=device) | |
| rand_mask_probs = self.noise_schedule(rand_time) | |
| num_token_masked = (max_token_len * rand_mask_probs).round().clamp(min=1) | |
| batch_randperm = torch.rand((B, max_token_len), device=device).argsort(dim=-1) | |
| # Positions to be masked are all true | |
| mask = batch_randperm < num_token_masked.unsqueeze(-1) | |
| # Positions to be MASKED must also be NON-PADDED | |
| mask &= non_pad_mask | |
| # 2.2 Further Apply Bert Masking Scheme | |
| # Step 1: 10% replace with an incorrect token | |
| x_ids = m_tokens_i.clone() | |
| mask_rid = get_mask_subset_prob(mask, 0.1) | |
| rand_id = torch.randint_like(x_ids, high=self.vocab_size - 2) | |
| x_ids = torch.where(mask_rid, rand_id, x_ids) | |
| # Step 2: 90% x 10% replace with correct token, and 90% x 88% replace with mask token | |
| mask_mid = get_mask_subset_prob(mask & ~mask_rid, 0.88) | |
| x_ids = torch.where(mask_mid, self.mask_idx, x_ids) | |
| all_x_ids.append(x_ids) | |
| all_labels.append(labels) | |
| # import ipdb; ipdb.set_trace() | |
| return all_x_ids, all_labels, non_pad_mask | |
| def forward_loss(self, | |
| tokens, label_tokens, | |
| text_feat, text_mask, | |
| audio_feat, audio_mask, | |
| traj_feat, traj_mask, | |
| non_pad_mask, mean=None, std=None, motion=None): | |
| all_logits = self.transformer(tokens, non_pad_mask, text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask) | |
| ce_loss = torch.tensor(0.).to(text_feat) | |
| acc_dict = dict() | |
| for i, (logits, labels) in enumerate(zip(all_logits, label_tokens)): | |
| logits = logits.flatten(0, 1) | |
| labels = labels.flatten(0, 1) | |
| ce_loss = ce_loss + F.cross_entropy(logits, labels.to(torch.int64), ignore_index=self.ignore_index) | |
| # calculate acc | |
| probs = torch.softmax(logits, dim=-1) | |
| cls_pred_index = torch.argmax(probs, dim=-1) | |
| token_mask = (labels != self.ignore_index).int() | |
| right_num = (cls_pred_index == labels).int() * token_mask | |
| acc_dict[f'ACC_{i}'] = (right_num.sum() / token_mask.sum()).float().detach() | |
| # add traj loss | |
| traj_loss = torch.tensor(0.0) | |
| if traj_mask.sum() > 0: | |
| all_pred_motion_tokens = [] | |
| for i, (logits, labels) in enumerate(zip(all_logits, label_tokens)): | |
| # import ipdb; ipdb.set_trace() | |
| probs_gumbel_softmax = F.gumbel_softmax(logits, tau=1, eps=1e-10, hard=True, dim = -1) | |
| pred_motion_tokens = torch.topk(probs_gumbel_softmax, k=1, dim=-1)[1].reshape(labels.shape[0], -1) # B, T | |
| all_pred_motion_tokens.append(pred_motion_tokens) | |
| all_pred_motion_tokens = torch.stack(all_pred_motion_tokens).permute(1, 2, 0).to(logits.dtype) # nq, B, T | |
| pred_motion = self.vqvae.decode(all_pred_motion_tokens, motion_length=non_pad_mask.sum(1)) | |
| pred_traj = recover_from_local_position_fast(pred_motion.float() * std + mean, njoint=22)[:, :, 0].to(logits.dtype) | |
| gt_traj = recover_from_local_position_fast(motion.float() * std + mean, njoint=22)[:, :, 0].to(logits.dtype) | |
| arange_t = torch.arange(motion.shape[1])[None, :].to(motion.device) # (1, T) | |
| mask = arange_t < (non_pad_mask.sum(1)[:, None] * self.motion_downsample_ratio) # (B, T) | |
| loss_raw = F.l1_loss(pred_traj, gt_traj, reduction='none') # (B, T, C) | |
| traj_loss = (loss_raw * mask[..., None]).sum() / (mask.sum() * loss_raw.shape[-1]) | |
| return dict( | |
| ce_loss=ce_loss / self.n_quant, | |
| traj_loss=traj_loss, | |
| **acc_dict | |
| ) | |
| def forward_predict(self, text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask, non_pad_mask, return_ids=False, **kwargs): | |
| device = non_pad_mask.device | |
| # tokens: all pad mask seqence | |
| ids = torch.where(non_pad_mask, self.mask_idx, self.pad_idx)[None].repeat(self.n_quant, 1, 1) | |
| scores = torch.where(non_pad_mask, 0., 1e5)[None].repeat(self.n_quant, 1, 1) | |
| m_lens = non_pad_mask.sum(-1) | |
| for timestep in torch.linspace(0, 1, self.inference_timesteps, device=device): | |
| # import ipdb; ipdb.set_trace() | |
| rand_mask_prob = self.noise_schedule(timestep) | |
| # Maskout, and cope with variable length | |
| num_token_masked = torch.round(rand_mask_prob * m_lens).clamp(min=1) # (b, ) | |
| # select num_token_masked tokens with lowest scores to be masked | |
| sorted_indices = scores.argsort(dim=-1) # (b, k), sorted_indices[i, j] = the index of j-th lowest element in scores on dim=1 | |
| ranks = sorted_indices.argsort(dim=-1) # (b, k), rank[i, j] = the rank (0: lowest) of scores[i, j] on dim=1 | |
| is_mask = (ranks < num_token_masked.unsqueeze(-1).unsqueeze(0)) | |
| ids = torch.where(is_mask, self.mask_idx, ids) | |
| # all_logits = self.transformer(ids, non_pad_mask, text_feat, text_mask) | |
| all_logits = self.transformer(ids, non_pad_mask, text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask) | |
| for i, logits in enumerate(all_logits): | |
| # import ipdb; ipdb.set_trace() | |
| filtered_logits = top_k(logits, self.topk_filter_thres, dim=-1) | |
| probs = F.softmax(filtered_logits, dim=-1) # (b, seqlen, ntoken) | |
| pred_ids = Categorical(probs).sample() # (b, seqlen) | |
| ids[i] = torch.where(is_mask[i], pred_ids, ids[i]) | |
| # Updating scores | |
| probs_without_temperature = logits.softmax(dim=-1) # (b, seqlen, ntoken) | |
| scores[i] = probs_without_temperature.gather(2, pred_ids.unsqueeze(dim=-1)).squeeze(-1) # (b, seqlen) | |
| # We do not want to re-mask the previously kept tokens, or pad tokens | |
| scores[i] = scores[i].masked_fill(~is_mask[i], 1e5) | |
| ids = torch.where(non_pad_mask[None].repeat(self.n_quant, 1, 1), ids, -1) | |
| # import ipdb; ipdb.set_trace() | |
| pred_motions = [] | |
| pred_motion_lengths = [] | |
| # import ipdb; ipdb.set_trace() | |
| pred_motions = self.vqvae.decode(ids.permute(1, 2, 0), motion_length=non_pad_mask.sum(1)) | |
| if return_ids: | |
| return pred_motions, pred_motion_lengths, ids | |
| else: | |
| return pred_motions | |
| def prepare_test_input(self, motion_length): | |
| B = motion_length.shape[0] | |
| device = motion_length.device | |
| m_lens = motion_length // self.motion_downsample_ratio | |
| max_len = motion_length.max() // self.motion_downsample_ratio | |
| non_pad_mask = torch.arange(max_len, device=device).expand(B, max_len) < m_lens.unsqueeze(1) | |
| return non_pad_mask | |
| class AnyMo_audio(BaseModel): | |
| def __init__( | |
| self, | |
| vqvae_cfg: Dict, | |
| transformer_cfg: Dict, | |
| max_motion_len: int, | |
| motion_downsample_ratio: int, | |
| audio_downsample_ratio: int, | |
| text_encoder_cfg: Dict=None, | |
| traj_encoder_cfg: Dict=None, | |
| audio_encoder_cfg: Dict=None, | |
| inference_timesteps=6, | |
| cond_types = ['audio'], | |
| **kwargs | |
| ): | |
| super().__init__(**kwargs) | |
| self.transformer = MODELS.build(transformer_cfg) | |
| self.text_encoder = MODELS.build(text_encoder_cfg) | |
| self.traj_encoder = MODELS.build(traj_encoder_cfg) | |
| self.audio_encoder = MODELS.build(audio_encoder_cfg) | |
| # build vqvae | |
| self.vqvae = MODELS.build(vqvae_cfg) | |
| self.vqvae.init_weights() | |
| for param in self.vqvae.parameters(): | |
| param.requires_grad = False | |
| self.vqvae.eval() | |
| self.vocab_size = transformer_cfg.vocab_size # type: ignore | |
| self.n_embd = transformer_cfg.n_embd | |
| self.mask_idx = self.vocab_size - 1 | |
| self.pad_idx = self.vocab_size - 2 | |
| self.ignore_index = -100 | |
| self.max_motion_len = max_motion_len | |
| self.motion_downsample_ratio = motion_downsample_ratio | |
| self.audio_downsample_ratio = audio_downsample_ratio | |
| self.topk_filter_thres = 0.9 | |
| self.noise_schedule = cosine_schedule | |
| self.n_quant = len(self.vqvae.quantizer.quantizers) | |
| self.inference_timesteps = inference_timesteps | |
| self.cond_types = cond_types | |
| for cond_type in cond_types: | |
| assert cond_type in ['text', 'audio', 'traj'] | |
| def forward_train(self, motion, motion_length, text_data, audio_data, traj_data, audio_length, **kwargs): | |
| audio_feat = self.audio_encoder(audio_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| audio_mask = length_to_mask(audio_length // self.audio_downsample_ratio) | |
| B = motion.shape[0] | |
| device = motion.device | |
| text_feat = torch.zeros(B, 0, self.n_embd).to(device).to(audio_feat.dtype) | |
| traj_feat = torch.zeros(B, 0, self.n_embd).to(device).to(audio_feat.dtype) | |
| text_mask = torch.zeros(B, 0).to(device).to(audio_feat.dtype) | |
| traj_mask = torch.zeros(B, 0).to(device).to(audio_feat.dtype) | |
| tokens, label_tokens, non_pad_mask = self.prepare_train_input(motion, motion_length) | |
| loss = self.forward_loss( | |
| tokens, label_tokens, text_feat, text_mask, audio_feat, | |
| audio_mask, traj_feat, traj_mask, non_pad_mask, motion=motion, mean=kwargs['mean'], | |
| std=kwargs['std'] | |
| ) | |
| return loss | |
| def forward( | |
| self, | |
| motion: torch.Tensor, | |
| motion_length: torch.Tensor, | |
| text_data: List[str]=None, | |
| audio_data: torch.Tensor=None, | |
| traj_data: torch.Tensor=None, | |
| audio_length: torch.Tensor=None, | |
| mode = 'loss', | |
| **kwargs | |
| ): | |
| ''' | |
| motion: tensor, B, T, C | |
| motion_length: tensor, B | |
| text_data: list[str], len B | |
| has_xxx: list[bool], len B | |
| music_data: tensor, B, T, C | |
| ''' | |
| if mode == 'loss': | |
| return self.forward_train(motion, motion_length, text_data, audio_data, traj_data, audio_length, **kwargs) | |
| else: # for evaluation | |
| B = motion.shape[0] | |
| device = motion.device | |
| text_feat = torch.zeros(B, 0, self.n_embd).to(device) | |
| audio_feat = torch.zeros(B, 0, self.n_embd).to(device) | |
| traj_feat = torch.zeros(B, 0, self.n_embd).to(device) | |
| text_mask = torch.zeros(B, 0).to(device) | |
| audio_mask = torch.zeros(B, 0).to(device) | |
| traj_mask = torch.zeros(B, 0).to(device) | |
| # import ipdb; ipdb.set_trace() | |
| if 'text' in self.cond_types: | |
| text_feat, text_mask = self.text_encoder(text_data) | |
| dtype = text_feat.dtype | |
| if 'audio' in self.cond_types: | |
| audio_feat = self.audio_encoder(audio_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| audio_mask = length_to_mask(audio_length // self.audio_downsample_ratio) | |
| dtype = audio_feat.dtype | |
| if 'traj' in self.cond_types: | |
| traj_feat = self.traj_encoder(traj_data.permute(0, 2, 1)).permute(0, 2, 1) | |
| traj_mask = length_to_mask(motion_length // self.motion_downsample_ratio) | |
| dtype = traj_feat.dtype | |
| text_feat, text_mask = text_feat.to(dtype), text_mask.to(device) | |
| audio_feat, audio_mask = audio_feat.to(dtype), audio_mask.to(dtype) | |
| traj_feat, traj_mask = traj_feat.to(dtype), traj_mask.to(dtype) | |
| non_pad_mask = self.prepare_test_input(motion_length) | |
| return self.forward_predict(text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask, non_pad_mask, **kwargs) | |
| def prepare_train_input(self, motion, motion_length): | |
| if self.vqvae.training: | |
| self.vqvae.eval() | |
| B = motion.shape[0] | |
| device = motion.device | |
| m_token_lens = motion_length // self.motion_downsample_ratio | |
| max_token_len = m_token_lens.max() | |
| ids = self.vqvae.encode(motion, motion_length) # B, T, n_quant | |
| # True, True, ..., False | |
| non_pad_mask = torch.arange(max_token_len, device=device).expand(B, max_token_len) < m_token_lens.unsqueeze(1) | |
| all_x_ids = [] | |
| all_labels = [] | |
| mask = None | |
| for i in range(ids.shape[-1]): # n_quant | |
| # import ipdb; ipdb.set_trace() | |
| m_tokens_i = ids[..., i] # B, T | |
| # 1. prepare label token | |
| labels = torch.where(non_pad_mask, m_tokens_i, self.ignore_index) | |
| # 2. prepare input token | |
| m_tokens_i = torch.where(non_pad_mask, m_tokens_i, self.pad_idx) # [m1, m2, m3, ..., pad1, pad2, ...] | |
| # 2.1 prepare mask | |
| if mask is None: | |
| rand_time = uniform((B,), device=device) | |
| rand_mask_probs = self.noise_schedule(rand_time) | |
| num_token_masked = (max_token_len * rand_mask_probs).round().clamp(min=1) | |
| batch_randperm = torch.rand((B, max_token_len), device=device).argsort(dim=-1) | |
| # Positions to be masked are all true | |
| mask = batch_randperm < num_token_masked.unsqueeze(-1) | |
| # Positions to be MASKED must also be NON-PADDED | |
| mask &= non_pad_mask | |
| # 2.2 Further Apply Bert Masking Scheme | |
| # Step 1: 10% replace with an incorrect token | |
| x_ids = m_tokens_i.clone() | |
| mask_rid = get_mask_subset_prob(mask, 0.1) | |
| rand_id = torch.randint_like(x_ids, high=self.vocab_size - 2) | |
| x_ids = torch.where(mask_rid, rand_id, x_ids) | |
| # Step 2: 90% x 10% replace with correct token, and 90% x 88% replace with mask token | |
| mask_mid = get_mask_subset_prob(mask & ~mask_rid, 0.88) | |
| x_ids = torch.where(mask_mid, self.mask_idx, x_ids) | |
| all_x_ids.append(x_ids) | |
| all_labels.append(labels) | |
| # import ipdb; ipdb.set_trace() | |
| return all_x_ids, all_labels, non_pad_mask | |
| def forward_loss(self, | |
| tokens, label_tokens, | |
| text_feat, text_mask, | |
| audio_feat, audio_mask, | |
| traj_feat, traj_mask, | |
| non_pad_mask, mean=None, std=None, motion=None): | |
| all_logits = self.transformer(tokens, non_pad_mask, text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask) | |
| ce_loss = torch.tensor(0.).to(text_feat) | |
| acc_dict = dict() | |
| for i, (logits, labels) in enumerate(zip(all_logits, label_tokens)): | |
| logits = logits.flatten(0, 1) | |
| labels = labels.flatten(0, 1) | |
| ce_loss = ce_loss + F.cross_entropy(logits, labels.to(torch.int64), ignore_index=self.ignore_index) | |
| # calculate acc | |
| probs = torch.softmax(logits, dim=-1) | |
| cls_pred_index = torch.argmax(probs, dim=-1) | |
| token_mask = (labels != self.ignore_index).int() | |
| right_num = (cls_pred_index == labels).int() * token_mask | |
| acc_dict[f'ACC_{i}'] = (right_num.sum() / token_mask.sum()).float().detach() | |
| # add traj loss | |
| traj_loss = torch.tensor(0.0) | |
| if traj_mask.sum() > 0: | |
| all_pred_motion_tokens = [] | |
| for i, (logits, labels) in enumerate(zip(all_logits, label_tokens)): | |
| # import ipdb; ipdb.set_trace() | |
| probs_gumbel_softmax = F.gumbel_softmax(logits, tau=1, eps=1e-10, hard=True, dim = -1) | |
| pred_motion_tokens = torch.topk(probs_gumbel_softmax, k=1, dim=-1)[1].reshape(labels.shape[0], -1) # B, T | |
| all_pred_motion_tokens.append(pred_motion_tokens) | |
| all_pred_motion_tokens = torch.stack(all_pred_motion_tokens) # nq, B, T | |
| pred_motion = self.vqvae.decode(all_pred_motion_tokens.permute(1, 2, 0), motion_length=non_pad_mask.sum(1)) | |
| pred_traj = recover_from_local_position_fast(pred_motion * std + mean, njoint=22)[:, :, 0] | |
| gt_traj = recover_from_local_position_fast(motion * std + mean, njoint=22)[:, :, 0] | |
| arange_t = torch.arange(motion.shape[1])[None, :].to(motion.device) # (1, T) | |
| mask = arange_t < (non_pad_mask.sum(1)[:, None] * self.motion_downsample_ratio) # (B, T) | |
| loss_raw = F.l1_loss(pred_traj, gt_traj, reduction='none') # (B, T, C) | |
| traj_loss = (loss_raw * mask[..., None]).sum() / (mask.sum() * loss_raw.shape[-1]) | |
| return dict( | |
| ce_loss=ce_loss / self.n_quant, | |
| traj_loss=traj_loss, | |
| **acc_dict | |
| ) | |
| def forward_predict(self, text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask, non_pad_mask, return_ids=False, **kwargs): | |
| device = non_pad_mask.device | |
| # tokens: all pad mask seqence | |
| ids = torch.where(non_pad_mask, self.mask_idx, self.pad_idx)[None].repeat(self.n_quant, 1, 1) | |
| scores = torch.where(non_pad_mask, 0., 1e5)[None].repeat(self.n_quant, 1, 1) | |
| m_lens = non_pad_mask.sum(-1) | |
| for timestep in torch.linspace(0, 1, self.inference_timesteps, device=device): | |
| # import ipdb; ipdb.set_trace() | |
| rand_mask_prob = self.noise_schedule(timestep) | |
| # Maskout, and cope with variable length | |
| num_token_masked = torch.round(rand_mask_prob * m_lens).clamp(min=1) # (b, ) | |
| # select num_token_masked tokens with lowest scores to be masked | |
| sorted_indices = scores.argsort(dim=-1) # (b, k), sorted_indices[i, j] = the index of j-th lowest element in scores on dim=1 | |
| ranks = sorted_indices.argsort(dim=-1) # (b, k), rank[i, j] = the rank (0: lowest) of scores[i, j] on dim=1 | |
| is_mask = (ranks < num_token_masked.unsqueeze(-1).unsqueeze(0)) | |
| ids = torch.where(is_mask, self.mask_idx, ids) | |
| # all_logits = self.transformer(ids, non_pad_mask, text_feat, text_mask) | |
| all_logits = self.transformer(ids, non_pad_mask, text_feat, text_mask, audio_feat, audio_mask, traj_feat, traj_mask) | |
| for i, logits in enumerate(all_logits): | |
| # import ipdb; ipdb.set_trace() | |
| filtered_logits = top_k(logits, self.topk_filter_thres, dim=-1) | |
| probs = F.softmax(filtered_logits, dim=-1) # (b, seqlen, ntoken) | |
| pred_ids = Categorical(probs).sample() # (b, seqlen) | |
| ids[i] = torch.where(is_mask[i], pred_ids, ids[i]) | |
| # Updating scores | |
| probs_without_temperature = logits.softmax(dim=-1) # (b, seqlen, ntoken) | |
| scores[i] = probs_without_temperature.gather(2, pred_ids.unsqueeze(dim=-1)).squeeze(-1) # (b, seqlen) | |
| # We do not want to re-mask the previously kept tokens, or pad tokens | |
| scores[i] = scores[i].masked_fill(~is_mask[i], 1e5) | |
| ids = torch.where(non_pad_mask[None].repeat(self.n_quant, 1, 1), ids, -1) | |
| # import ipdb; ipdb.set_trace() | |
| pred_motions = [] | |
| pred_motion_lengths = [] | |
| # import ipdb; ipdb.set_trace() | |
| pred_motions = self.vqvae.decode(ids.permute(1, 2, 0), motion_length=non_pad_mask.sum(1)) | |
| if return_ids: | |
| return pred_motions, pred_motion_lengths, ids | |
| else: | |
| return pred_motions | |
| def prepare_test_input(self, motion_length): | |
| B = motion_length.shape[0] | |
| device = motion_length.device | |
| m_lens = motion_length // self.motion_downsample_ratio | |
| max_len = motion_length.max() // self.motion_downsample_ratio | |
| non_pad_mask = torch.arange(max_len, device=device).expand(B, max_len) < m_lens.unsqueeze(1) | |
| return non_pad_mask | |