cocktailpeanut/DiffRhythm
10
1"""2ein notation:3b - batch4n - sequence5nt - text sequence6nw - raw wave length7d - dimension8"""9 10from __future__ import annotations11from typing import Callable12from random import random13 14import torch15from torch import nn16import torch17import torch.nn.functional as F18from torch.nn.utils.rnn import pad_sequence19 20from torchdiffeq import odeint21 22from diffrhythm.model.modules import MelSpec23from diffrhythm.model.utils import (24 default,25 exists,26 list_str_to_idx,27 list_str_to_tensor,28 lens_to_mask,29 mask_from_frac_lengths,30)31 32def custom_mask_from_start_end_indices(seq_len: int["b"], start: int["b"], end: int["b"], device, max_seq_len): # noqa: F722 F82133 max_seq_len = max_seq_len34 seq = torch.arange(max_seq_len, device=device).long()35 start_mask = seq[None, :] >= start[:, None]36 end_mask = seq[None, :] < end[:, None]37 return start_mask & end_mask38 39class CFM(nn.Module):40 def __init__(41 self,42 transformer: nn.Module,43 sigma=0.0,44 odeint_kwargs: dict = dict(45 # atol = 1e-5,46 # rtol = 1e-5,47 method="euler" # 'midpoint'48 # method="adaptive_heun" # dopri549 ),50 odeint_options: dict = dict(51 min_step=0.0552 ),53 audio_drop_prob=0.3,54 cond_drop_prob=0.2,55 style_drop_prob=0.1,56 lrc_drop_prob=0.1,57 num_channels=None,58 frac_lengths_mask: tuple[float, float] = (0.7, 1.0),59 vocab_char_map: dict[str:int] | None = None,60 use_style_prompt: bool = False61 ):62 super().__init__()63 64 self.frac_lengths_mask = frac_lengths_mask65 66 self.num_channels = num_channels67 68 # classifier-free guidance69 self.audio_drop_prob = audio_drop_prob70 self.cond_drop_prob = cond_drop_prob71 self.style_drop_prob = style_drop_prob72 self.lrc_drop_prob = lrc_drop_prob73 74 print(f"audio drop prob -> {self.audio_drop_prob}; style_drop_prob -> {self.style_drop_prob}; lrc_drop_prob: {self.lrc_drop_prob}")75 76 # transformer77 self.transformer = transformer78 dim = transformer.dim79 self.dim = dim80 81 # conditional flow related82 self.sigma = sigma83 84 # sampling related85 self.odeint_kwargs = odeint_kwargs86 # print(f"ODE SOLVER: {self.odeint_kwargs['method']}")87 88 self.odeint_options = odeint_options89 90 # vocab map for tokenization91 self.vocab_char_map = vocab_char_map92 93 self.use_style_prompt = use_style_prompt94 95 @property96 def device(self):97 return next(self.parameters()).device98 99 @torch.no_grad()100 def sample(101 self,102 cond: float["b n d"] | float["b nw"], # noqa: F722103 text: int["b nt"] | list[str], # noqa: F722104 duration: int | int["b"], # noqa: F821105 *,106 style_prompt = None,107 style_prompt_lens = None,108 negative_style_prompt = None,109 lens: int["b"] | None = None, # noqa: F821110 steps=32,111 cfg_strength=4.0,112 sway_sampling_coef=None,113 seed: int | None = None,114 max_duration=4096,115 #max_duration=6144,116 vocoder: Callable[[float["b d n"]], float["b nw"]] | None = None, # noqa: F722117 no_ref_audio=False,118 duplicate_test=False,119 t_inter=0.1,120 edit_mask=None,121 start_time=None,122 latent_pred_start_frame=0,123 latent_pred_end_frame=2048,124 vocal_flag=False,125 odeint_method="euler"126 ):127 self.eval()128 129 if next(self.parameters()).dtype == torch.float16:130 cond = cond.half()131 132 # raw wave133 134 if cond.shape[1] > duration:135 cond = cond[:, :duration, :]136 137 if cond.ndim == 2:138 cond = self.mel_spec(cond)139 cond = cond.permute(0, 2, 1)140 assert cond.shape[-1] == self.num_channels141 142 batch, cond_seq_len, device = *cond.shape[:2], cond.device143 if not exists(lens):144 lens = torch.full((batch,), cond_seq_len, device=device, dtype=torch.long)145 146 # text147 148 if isinstance(text, list):149 if exists(self.vocab_char_map):150 text = list_str_to_idx(text, self.vocab_char_map).to(device)151 else:152 text = list_str_to_tensor(text).to(device)153 assert text.shape[0] == batch154 155 if exists(text):156 text_lens = (text != -1).sum(dim=-1)157 #lens = torch.maximum(text_lens, lens) # make sure lengths are at least those of the text characters158 159 # duration160 # import pdb; pdb.set_trace()161 cond_mask = lens_to_mask(lens)162 if edit_mask is not None:163 cond_mask = cond_mask & edit_mask164 165 latent_pred_start_frame = torch.tensor([latent_pred_start_frame]).to(cond.device)166 latent_pred_end_frame = duration167 latent_pred_end_frame = torch.tensor([latent_pred_end_frame]).to(cond.device)168 fixed_span_mask = custom_mask_from_start_end_indices(cond_seq_len, latent_pred_start_frame, latent_pred_end_frame, device=cond.device, max_seq_len=duration)169 170 fixed_span_mask = fixed_span_mask.unsqueeze(-1)171 step_cond = torch.where(fixed_span_mask, torch.zeros_like(cond), cond)172 173 if isinstance(duration, int):174 duration = torch.full((batch,), duration, device=device, dtype=torch.long)175 176 # duration = torch.maximum(lens + 1, duration) # just add one token so something is generated177 duration = duration.clamp(max=max_duration)178 max_duration = duration.amax()179 180 # duplicate test corner for inner time step oberservation181 if duplicate_test:182 test_cond = F.pad(cond, (0, 0, cond_seq_len, max_duration - 2 * cond_seq_len), value=0.0)183 184 # cond = F.pad(cond, (0, 0, 0, max_duration - cond_seq_len), value=0.0) # [b, t, d]185 # cond_mask = F.pad(cond_mask, (0, max_duration - cond_mask.shape[-1]), value=False) # [b, max_duration]186 # cond_mask = cond_mask.unsqueeze(-1) #[b, t, d]187 # step_cond = torch.where(188 # cond_mask, cond, torch.zeros_like(cond)189 # ) # allow direct control (cut cond audio) with lens passed in190 191 if batch > 1:192 mask = lens_to_mask(duration)193 else: # save memory and speed up, as single inference need no mask currently194 mask = None195 196 # test for no ref audio197 if no_ref_audio:198 cond = torch.zeros_like(cond)199 200 start_time_embed, positive_text_embed, positive_text_residuals = self.transformer.forward_timestep_invariant(text, step_cond.shape[1], drop_text=False, start_time=start_time)201 _, negative_text_embed, negative_text_residuals = self.transformer.forward_timestep_invariant(text, step_cond.shape[1], drop_text=True, start_time=start_time)202 203 if vocal_flag:204 style_prompt = negative_style_prompt205 negative_style_prompt = torch.zeros_like(style_prompt)206 207 208 text_embed = torch.cat([positive_text_embed, negative_text_embed], 0)209 text_residuals = [torch.cat([a, b], 0) for a, b in zip(positive_text_residuals, negative_text_residuals)]210 step_cond = torch.cat([step_cond, step_cond], 0)211 style_prompt = torch.cat([style_prompt, negative_style_prompt], 0)212 start_time_embed = torch.cat([start_time_embed, start_time_embed], 0)213 214 215 def fn(t, x):216 x = torch.cat([x, x], 0)217 pred = self.transformer(218 x=x, text_embed=text_embed, text_residuals=text_residuals, cond=step_cond, time=t, 219 drop_audio_cond=True, drop_prompt=False, style_prompt=style_prompt, start_time=start_time_embed220 )221 222 positive_pred, negative_pred = pred.chunk(2, 0)223 cfg_pred = positive_pred + (positive_pred - negative_pred) * cfg_strength224 225 return cfg_pred226 227 # noise input228 # to make sure batch inference result is same with different batch size, and for sure single inference229 # still some difference maybe due to convolutional layers230 y0 = []231 for dur in duration:232 if exists(seed):233 torch.manual_seed(seed)234 y0.append(torch.randn(dur, self.num_channels, device=self.device, dtype=step_cond.dtype))235 y0 = pad_sequence(y0, padding_value=0, batch_first=True)236 237 t_start = 0238 239 # duplicate test corner for inner time step oberservation240 if duplicate_test:241 t_start = t_inter242 y0 = (1 - t_start) * y0 + t_start * test_cond243 steps = int(steps * (1 - t_start))244 245 t = torch.linspace(t_start, 1, steps, device=self.device, dtype=step_cond.dtype)246 if sway_sampling_coef is not None:247 t = t + sway_sampling_coef * (torch.cos(torch.pi / 2 * t) - 1 + t)248 249 trajectory = odeint(fn, y0, t, **self.odeint_kwargs)250 251 sampled = trajectory[-1]252 out = sampled253 # out = torch.where(cond_mask, cond, out)254 out = torch.where(fixed_span_mask, out, cond)255 256 if exists(vocoder):257 out = out.permute(0, 2, 1)258 out = vocoder(out)259 260 return out, trajectory261 262 def forward(263 self,264 inp: float["b n d"] | float["b nw"], # mel or raw wave # noqa: F722265 text: int["b nt"] | list[str], # noqa: F722266 style_prompt = None,267 style_prompt_lens = None,268 lens: int["b"] | None = None, # noqa: F821269 noise_scheduler: str | None = None,270 grad_ckpt = False,271 start_time = None,272 ):273 274 batch, seq_len, dtype, device, _σ1 = *inp.shape[:2], inp.dtype, self.device, self.sigma275 276 # lens and mask277 if not exists(lens):278 lens = torch.full((batch,), seq_len, device=device)279 280 mask = lens_to_mask(lens, length=seq_len) # useless here, as collate_fn will pad to max length in batch281 282 # get a random span to mask out for training conditionally283 frac_lengths = torch.zeros((batch,), device=self.device).float().uniform_(*self.frac_lengths_mask)284 rand_span_mask = mask_from_frac_lengths(lens, frac_lengths)285 286 if exists(mask):287 rand_span_mask = mask288 # rand_span_mask &= mask289 290 # mel is x1291 x1 = inp292 293 # x0 is gaussian noise294 x0 = torch.randn_like(x1)295 296 # time step297 # time = torch.rand((batch,), dtype=dtype, device=self.device)298 time = torch.normal(mean=0, std=1, size=(batch,), device=self.device)299 time = torch.nn.functional.sigmoid(time)300 # TODO. noise_scheduler301 302 # sample xt (φ_t(x) in the paper)303 t = time.unsqueeze(-1).unsqueeze(-1)304 φ = (1 - t) * x0 + t * x1305 flow = x1 - x0306 307 # only predict what is within the random mask span for infilling308 cond = torch.where(rand_span_mask[..., None], torch.zeros_like(x1), x1)309 310 # transformer and cfg training with a drop rate311 drop_audio_cond = random() < self.audio_drop_prob # p_drop in voicebox paper312 drop_text = random() < self.lrc_drop_prob313 drop_prompt = random() < self.style_drop_prob314 315 # if want rigourously mask out padding, record in collate_fn in dataset.py, and pass in here316 # adding mask will use more memory, thus also need to adjust batchsampler with scaled down threshold for long sequences317 pred = self.transformer(318 x=φ, cond=cond, text=text, time=time, drop_audio_cond=drop_audio_cond, drop_text=drop_text, drop_prompt=drop_prompt,319 style_prompt=style_prompt, style_prompt_lens=style_prompt_lens, grad_ckpt=grad_ckpt, start_time=start_time320 )321 322 # flow matching loss323 loss = F.mse_loss(pred, flow, reduction="none")324 loss = loss[rand_span_mask]325 326 return loss.mean(), cond, pred327 