Text-to-Speech
Transformers
Safetensors
audiodit
feature-extraction
audio
tts
environmental-tts
flow-matching
dit
custom_code
Instructions to use humanify/LongCat-AudioDiT-Env-TTS-1B-augment with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use humanify/LongCat-AudioDiT-Env-TTS-1B-augment with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-to-speech", model="humanify/LongCat-AudioDiT-Env-TTS-1B-augment", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("humanify/LongCat-AudioDiT-Env-TTS-1B-augment", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
| """PyTorch AudioDiT model β Conditional Flow Matching TTS with DiT backbone.""" | |
| import math | |
| from dataclasses import dataclass | |
| from typing import Optional | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import nn | |
| # Use the modern parametrizations-based weight_norm. Backward-compatible: it | |
| # auto-remaps legacy `weight_g`/`weight_v` keys in state_dict to the new | |
| # `parametrizations.weight.original0`/`original1` names on load (via a | |
| # state_dict pre-hook registered inside parametrize.register_parametrization). | |
| # This fixes the silent VAE load failure where transformers 5.x's | |
| # `Materializing param` path renamed weight_norm modules to parametrizations | |
| # without remapping, leading to VAE outputs of pure silence. | |
| from torch.nn.utils.parametrizations import weight_norm | |
| from torch.nn.utils.rnn import pad_sequence | |
| from transformers import PreTrainedModel, logging | |
| from transformers.modeling_outputs import ModelOutput | |
| from .configuration_audiodit import AudioDiTConfig, AudioDiTVaeConfig | |
| logger = logging.get_logger(__name__) | |
| # --------------------------------------------------------------------------- | |
| # Output dataclass | |
| # --------------------------------------------------------------------------- | |
| class AudioDiTOutput(ModelOutput): | |
| """ | |
| Output of [`AudioDiTModel`]. | |
| Args: | |
| waveform (`torch.FloatTensor` of shape `(batch_size, num_samples)`): | |
| Generated audio waveform. | |
| latent (`torch.FloatTensor` of shape `(batch_size, latent_dim, num_frames)`): | |
| Predicted latent representation before VAE decoding. | |
| """ | |
| waveform: torch.FloatTensor | None = None | |
| latent: torch.FloatTensor | None = None | |
| # --------------------------------------------------------------------------- | |
| # ODE solver (inline Euler β replaces torchdiffeq dependency) | |
| # --------------------------------------------------------------------------- | |
| def odeint_euler(fn, y0, t): | |
| """Simple Euler ODE integrator (equivalent to `torchdiffeq.odeint` with `method='euler'`). | |
| Args: | |
| fn: callable(t, y) β dy/dt | |
| y0: initial state tensor | |
| t: 1-D tensor of time steps (must be monotonically increasing) | |
| Returns: | |
| Tensor of shape `(len(t), *y0.shape)` containing the trajectory. | |
| """ | |
| ys = [y0] | |
| y = y0 | |
| for i in range(len(t) - 1): | |
| dt = t[i + 1] - t[i] | |
| y = y + fn(t[i], y) * dt | |
| ys.append(y) | |
| return torch.stack(ys) | |
| # --------------------------------------------------------------------------- | |
| # Utility helpers (from model/utils.py) | |
| # --------------------------------------------------------------------------- | |
| def lens_to_mask(lengths: torch.Tensor, length: int | None = None) -> torch.BoolTensor: | |
| if length is None: | |
| length = lengths.amax() | |
| seq = torch.arange(length, device=lengths.device) | |
| return seq[None, :] < lengths[:, None] | |
| # --------------------------------------------------------------------------- | |
| # Low-level modules (from model/modules.py) | |
| # --------------------------------------------------------------------------- | |
| class AudioDiTRMSNorm(nn.Module): | |
| def __init__(self, dim: int, eps: float = 1e-6): | |
| super().__init__() | |
| self.dim = dim | |
| self.eps = eps | |
| self.weight = nn.Parameter(torch.ones(dim)) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return self._norm(x.float()).type_as(x) * self.weight | |
| def _norm(self, x: torch.Tensor) -> torch.Tensor: | |
| return x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps) | |
| class AudioDiTSinusPositionEmbedding(nn.Module): | |
| def __init__(self, dim: int): | |
| super().__init__() | |
| self.dim = dim | |
| def forward(self, x: torch.Tensor, scale: float = 1000.0) -> torch.Tensor: | |
| device = x.device | |
| half_dim = self.dim // 2 | |
| emb = math.log(10000) / (half_dim - 1) | |
| emb = torch.exp(torch.arange(half_dim, device=device).float() * -emb) | |
| emb = scale * x.unsqueeze(1) * emb.unsqueeze(0) | |
| return torch.cat((emb.sin(), emb.cos()), dim=-1) | |
| class AudioDiTTimestepEmbedding(nn.Module): | |
| def __init__(self, dim: int, freq_embed_dim: int = 256): | |
| super().__init__() | |
| self.time_embed = AudioDiTSinusPositionEmbedding(freq_embed_dim) | |
| self.time_mlp = nn.Sequential(nn.Linear(freq_embed_dim, dim), nn.SiLU(), nn.Linear(dim, dim)) | |
| def forward(self, timestep: torch.Tensor) -> torch.Tensor: | |
| time_hidden = self.time_embed(timestep) | |
| time_hidden = time_hidden.to(timestep.dtype) | |
| return self.time_mlp(time_hidden) | |
| class AudioDiTRotaryEmbedding(nn.Module): | |
| """Qwen2-style rotary position embedding. | |
| All state (inv_freq, cos/sin caches) is built lazily on first ``forward`` | |
| call. This avoids corruption from ``from_pretrained`` meta-device | |
| construction while producing bit-identical results to the original | |
| ``Qwen2RotaryEmbedding`` (which creates ``inv_freq`` on CPU then moves | |
| the whole model to CUDA with ``.to(device)``). | |
| """ | |
| def __init__(self, dim: int, max_position_embeddings: int = 2048, base: float = 100000.0): | |
| super().__init__() | |
| self.dim = dim | |
| self.max_position_embeddings = max_position_embeddings | |
| self.base = base | |
| # Do NOT register any buffers here β they get corrupted by meta-device. | |
| # Everything is built lazily in forward(). | |
| self._cos: torch.Tensor | None = None | |
| self._sin: torch.Tensor | None = None | |
| self._cached_len: int = 0 | |
| self._cached_device: torch.device | None = None | |
| def _build(self, seq_len: int, device: torch.device, dtype: torch.dtype): | |
| """Build cos/sin tables entirely on CPU (matching original | |
| Qwen2RotaryEmbedding which builds in __init__ on CPU, then the | |
| whole model is moved with .to(device)), then move to target.""" | |
| inv_freq = 1.0 / (self.base ** (torch.arange(0, self.dim, 2, dtype=torch.int64).float() / self.dim)) | |
| t = torch.arange(seq_len, dtype=torch.int64).type_as(inv_freq) | |
| freqs = torch.outer(t, inv_freq) | |
| emb = torch.cat((freqs, freqs), dim=-1) | |
| self._cos = emb.cos().to(dtype=dtype, device=device) | |
| self._sin = emb.sin().to(dtype=dtype, device=device) | |
| self._cached_len = seq_len | |
| self._cached_device = device | |
| def forward(self, x: torch.Tensor, seq_len: int | None = None) -> tuple[torch.Tensor, torch.Tensor]: | |
| if seq_len is None: | |
| seq_len = x.shape[1] | |
| if self._cos is None or seq_len > self._cached_len or self._cached_device != x.device: | |
| self._build(max(seq_len, self.max_position_embeddings), x.device, x.dtype) | |
| return ( | |
| self._cos[:seq_len].to(dtype=x.dtype), | |
| self._sin[:seq_len].to(dtype=x.dtype), | |
| ) | |
| def _rotate_half(x: torch.Tensor) -> torch.Tensor: | |
| x1, x2 = x.chunk(2, dim=-1) | |
| return torch.cat([-x2, x1], dim=-1) | |
| def _apply_rotary_emb(x: torch.Tensor, freqs_cis: tuple[torch.Tensor, torch.Tensor]) -> torch.Tensor: | |
| cos, sin = freqs_cis | |
| cos = cos[None, None].to(x.device) | |
| sin = sin[None, None].to(x.device) | |
| return (x.float() * cos + _rotate_half(x).float() * sin).to(x.dtype) | |
| # --------------------------------------------------------------------------- | |
| # GRN + ConvNeXtV2 (for text conv) | |
| # --------------------------------------------------------------------------- | |
| class AudioDiTGRN(nn.Module): | |
| """Global Response Normalization.""" | |
| def __init__(self, dim: int): | |
| super().__init__() | |
| self.gamma = nn.Parameter(torch.zeros(1, 1, dim)) | |
| self.beta = nn.Parameter(torch.zeros(1, 1, dim)) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| gx = torch.norm(x, p=2, dim=1, keepdim=True) | |
| nx = gx / (gx.mean(dim=-1, keepdim=True) + 1e-6) | |
| return self.gamma * (x * nx) + self.beta + x | |
| class AudioDiTConvNeXtV2Block(nn.Module): | |
| def __init__(self, dim: int, intermediate_dim: int, dilation: int = 1, kernel_size: int = 7, bias: bool = True, eps: float = 1e-6): | |
| super().__init__() | |
| padding = (dilation * (kernel_size - 1)) // 2 | |
| self.dwconv = nn.Conv1d(dim, dim, kernel_size=kernel_size, padding=padding, groups=dim, dilation=dilation, bias=bias) | |
| self.norm = nn.LayerNorm(dim, eps=eps) | |
| self.pwconv1 = nn.Linear(dim, intermediate_dim, bias=bias) | |
| self.act = nn.SiLU() | |
| self.grn = AudioDiTGRN(intermediate_dim) | |
| self.pwconv2 = nn.Linear(intermediate_dim, dim, bias=bias) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| residual = x | |
| x = x.transpose(1, 2) | |
| x = self.dwconv(x) | |
| x = x.transpose(1, 2) | |
| x = self.norm(x) | |
| x = self.pwconv1(x) | |
| x = self.act(x) | |
| x = self.grn(x) | |
| x = self.pwconv2(x) | |
| return residual + x | |
| # --------------------------------------------------------------------------- | |
| # Embedder (shared for input / text / latent) | |
| # --------------------------------------------------------------------------- | |
| class AudioDiTEmbedder(nn.Module): | |
| def __init__(self, in_dim: int, out_dim: int): | |
| super().__init__() | |
| self.proj = nn.Sequential(nn.Linear(in_dim, out_dim), nn.SiLU(), nn.Linear(out_dim, out_dim)) | |
| def forward(self, x: torch.Tensor, mask: torch.BoolTensor | None = None) -> torch.Tensor: | |
| if mask is not None: | |
| x = x.masked_fill(mask.logical_not().unsqueeze(-1), 0.0) | |
| x = self.proj(x) | |
| if mask is not None: | |
| x = x.masked_fill(mask.logical_not().unsqueeze(-1), 0.0) | |
| return x | |
| # --------------------------------------------------------------------------- | |
| # AdaLN modules | |
| # --------------------------------------------------------------------------- | |
| class AudioDiTAdaLNMLP(nn.Module): | |
| def __init__(self, in_dim: int, out_dim: int, bias: bool = True): | |
| super().__init__() | |
| self.mlp = nn.Sequential(nn.SiLU(), nn.Linear(in_dim, out_dim, bias=bias)) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return self.mlp(x) | |
| class AudioDiTAdaLayerNormZeroFinal(nn.Module): | |
| def __init__(self, dim: int, bias: bool = True, eps: float = 1e-6): | |
| super().__init__() | |
| self.silu = nn.SiLU() | |
| self.linear = nn.Linear(dim, dim * 2, bias=bias) | |
| self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=eps) | |
| def forward(self, x: torch.Tensor, emb: torch.Tensor) -> torch.Tensor: | |
| emb = self.linear(self.silu(emb)) | |
| scale, shift = torch.chunk(emb, 2, dim=-1) | |
| x = self.norm(x.float()).type_as(x) | |
| if scale.ndim == 2: | |
| x = x * (1 + scale)[:, None, :] + shift[:, None, :] | |
| else: | |
| x = x * (1 + scale) + shift | |
| return x | |
| # --------------------------------------------------------------------------- | |
| # Attention | |
| # --------------------------------------------------------------------------- | |
| def _modulate(x: torch.Tensor, scale: torch.Tensor, shift: torch.Tensor, eps: float = 1e-6) -> torch.Tensor: | |
| """LayerNorm without affine + modulate.""" | |
| x = F.layer_norm(x.float(), (x.shape[-1],), eps=eps).type_as(x) | |
| if scale.ndim == 2: | |
| return x * (1 + scale[:, None]) + shift[:, None] | |
| return x * (1 + scale) + shift | |
| class AudioDiTSelfAttention(nn.Module): | |
| def __init__(self, dim: int, heads: int, dim_head: int, dropout: float = 0.0, bias: bool = True, qk_norm: bool = False, eps: float = 1e-6): | |
| super().__init__() | |
| self.heads = heads | |
| self.inner_dim = dim_head * heads | |
| self.to_q = nn.Linear(dim, self.inner_dim, bias=bias) | |
| self.to_k = nn.Linear(dim, self.inner_dim, bias=bias) | |
| self.to_v = nn.Linear(dim, self.inner_dim, bias=bias) | |
| self.qk_norm = qk_norm | |
| if qk_norm: | |
| self.q_norm = AudioDiTRMSNorm(self.inner_dim, eps=eps) | |
| self.k_norm = AudioDiTRMSNorm(self.inner_dim, eps=eps) | |
| self.to_out = nn.ModuleList([nn.Linear(self.inner_dim, dim, bias=bias), nn.Dropout(dropout)]) | |
| def forward(self, x: torch.Tensor, mask: torch.BoolTensor | None = None, rope: tuple | None = None) -> torch.Tensor: | |
| batch_size = x.shape[0] | |
| query = self.to_q(x) | |
| key = self.to_k(x) | |
| value = self.to_v(x) | |
| if self.qk_norm: | |
| query = self.q_norm(query) | |
| key = self.k_norm(key) | |
| head_dim = self.inner_dim // self.heads | |
| query = query.view(batch_size, -1, self.heads, head_dim).transpose(1, 2) | |
| key = key.view(batch_size, -1, self.heads, head_dim).transpose(1, 2) | |
| value = value.view(batch_size, -1, self.heads, head_dim).transpose(1, 2) | |
| if rope is not None: | |
| query = _apply_rotary_emb(query, rope) | |
| key = _apply_rotary_emb(key, rope) | |
| attn_mask = None | |
| if mask is not None: | |
| attn_mask = mask.unsqueeze(1).unsqueeze(1).expand(batch_size, self.heads, query.shape[-2], key.shape[-2]) | |
| x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False) | |
| x = x.transpose(1, 2).reshape(batch_size, -1, self.inner_dim).to(query.dtype) | |
| x = self.to_out[0](x) | |
| x = self.to_out[1](x) | |
| return x | |
| class AudioDiTCrossAttention(nn.Module): | |
| def __init__(self, q_dim: int, kv_dim: int, heads: int, dim_head: int, dropout: float = 0.0, bias: bool = True, qk_norm: bool = False, eps: float = 1e-6): | |
| super().__init__() | |
| self.heads = heads | |
| self.inner_dim = dim_head * heads | |
| self.to_q = nn.Linear(q_dim, self.inner_dim, bias=bias) | |
| self.to_k = nn.Linear(kv_dim, self.inner_dim, bias=bias) | |
| self.to_v = nn.Linear(kv_dim, self.inner_dim, bias=bias) | |
| self.qk_norm = qk_norm | |
| if qk_norm: | |
| self.q_norm = AudioDiTRMSNorm(self.inner_dim, eps=eps) | |
| self.k_norm = AudioDiTRMSNorm(self.inner_dim, eps=eps) | |
| self.to_out = nn.ModuleList([nn.Linear(self.inner_dim, q_dim, bias=bias), nn.Dropout(dropout)]) | |
| def forward( | |
| self, x: torch.Tensor, cond: torch.Tensor, mask: torch.BoolTensor | None = None, | |
| cond_mask: torch.BoolTensor | None = None, rope: tuple | None = None, cond_rope: tuple | None = None, | |
| ) -> torch.Tensor: | |
| batch_size = x.shape[0] | |
| query = self.to_q(x) | |
| key = self.to_k(cond) | |
| value = self.to_v(cond) | |
| if self.qk_norm: | |
| query = self.q_norm(query) | |
| key = self.k_norm(key) | |
| head_dim = self.inner_dim // self.heads | |
| query = query.view(batch_size, -1, self.heads, head_dim).transpose(1, 2) | |
| key = key.view(batch_size, -1, self.heads, head_dim).transpose(1, 2) | |
| value = value.view(batch_size, -1, self.heads, head_dim).transpose(1, 2) | |
| if rope is not None: | |
| query = _apply_rotary_emb(query, rope) | |
| if cond_rope is not None: | |
| key = _apply_rotary_emb(key, cond_rope) | |
| attn_mask = None | |
| if mask is not None: | |
| attn_mask = cond_mask.unsqueeze(1).expand(-1, mask.shape[1], -1).unsqueeze(1) | |
| attn_mask = attn_mask.expand(batch_size, self.heads, query.shape[-2], key.shape[-2]) | |
| x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False) | |
| x = x.transpose(1, 2).reshape(batch_size, -1, self.inner_dim).to(query.dtype) | |
| x = self.to_out[0](x) | |
| x = self.to_out[1](x) | |
| return x | |
| # --------------------------------------------------------------------------- | |
| # FeedForward | |
| # --------------------------------------------------------------------------- | |
| class AudioDiTFeedForward(nn.Module): | |
| def __init__(self, dim: int, mult: float = 4.0, dropout: float = 0.0, bias: bool = True): | |
| super().__init__() | |
| inner_dim = int(dim * mult) | |
| self.ff = nn.Sequential( | |
| nn.Linear(dim, inner_dim, bias=bias), | |
| nn.GELU(approximate="tanh"), | |
| nn.Dropout(dropout), | |
| nn.Linear(inner_dim, dim, bias=bias), | |
| ) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return self.ff(x) | |
| # --------------------------------------------------------------------------- | |
| # Transformer Block (CrossDiTBlock) | |
| # --------------------------------------------------------------------------- | |
| class AudioDiTBlock(nn.Module): | |
| """Single DiT block with self-attention, optional cross-attention, FFN, and AdaLN modulation.""" | |
| def __init__(self, config: AudioDiTConfig): | |
| super().__init__() | |
| dim = config.dit_dim | |
| cond_dim = config.dit_dim # after text embedding, cond_dim == dim | |
| heads = config.dit_heads | |
| dim_head = dim // heads | |
| bias = config.dit_bias | |
| eps = config.dit_eps | |
| self.adaln_type = config.dit_adaln_type | |
| self.adaln_use_text_cond = config.dit_adaln_use_text_cond | |
| if config.dit_adaln_type == "local": | |
| self.adaln_mlp = AudioDiTAdaLNMLP(dim, dim * 6, bias=True) | |
| elif config.dit_adaln_type == "global": | |
| self.adaln_scale_shift = nn.Parameter(torch.randn(dim * 6) / dim**0.5) | |
| self.self_attn = AudioDiTSelfAttention( | |
| dim=dim, heads=heads, dim_head=dim_head, dropout=config.dit_dropout, | |
| bias=bias, qk_norm=config.dit_qk_norm, eps=eps, | |
| ) | |
| self.use_cross_attn = config.dit_cross_attn | |
| if config.dit_cross_attn: | |
| self.cross_attn = AudioDiTCrossAttention( | |
| q_dim=dim, kv_dim=cond_dim, heads=heads, dim_head=dim_head, | |
| dropout=config.dit_dropout, bias=bias, qk_norm=config.dit_qk_norm, eps=eps, | |
| ) | |
| self.cross_attn_norm = nn.LayerNorm(dim, elementwise_affine=True, eps=eps) if config.dit_cross_attn_norm else nn.Identity() | |
| self.cross_attn_norm_c = nn.LayerNorm(cond_dim, elementwise_affine=True, eps=eps) if config.dit_cross_attn_norm else nn.Identity() | |
| self.ffn = AudioDiTFeedForward(dim=dim, mult=config.dit_ff_mult, dropout=config.dit_dropout, bias=bias) | |
| def forward( | |
| self, x: torch.Tensor, t: torch.Tensor, cond: torch.Tensor, | |
| mask: torch.BoolTensor | None = None, cond_mask: torch.BoolTensor | None = None, | |
| rope: tuple | None = None, cond_rope: tuple | None = None, | |
| adaln_global_out: torch.Tensor | None = None, | |
| ) -> torch.Tensor: | |
| if self.adaln_type == "local" and adaln_global_out is None: | |
| if self.adaln_use_text_cond: | |
| cond_mean = cond.sum(1) / cond_mask.sum(1, keepdim=True) | |
| norm_cond = t + cond_mean | |
| else: | |
| norm_cond = t | |
| adaln_out = self.adaln_mlp(norm_cond) | |
| gate_sa, scale_sa, shift_sa, gate_ffn, scale_ffn, shift_ffn = torch.chunk(adaln_out, 6, dim=-1) | |
| else: | |
| from einops import rearrange | |
| adaln_out = adaln_global_out + rearrange(self.adaln_scale_shift, "f -> 1 f") | |
| gate_sa, scale_sa, shift_sa, gate_ffn, scale_ffn, shift_ffn = torch.chunk(adaln_out, 6, dim=-1) | |
| # Self-attention | |
| norm = _modulate(x, scale_sa, shift_sa) | |
| attn_output = self.self_attn(norm, mask=mask, rope=rope) | |
| if gate_sa.ndim == 2: | |
| gate_sa = gate_sa.unsqueeze(1) | |
| x = x + gate_sa * attn_output | |
| # Cross-attention | |
| if self.use_cross_attn: | |
| cross_out = self.cross_attn( | |
| x=self.cross_attn_norm(x), cond=self.cross_attn_norm_c(cond), | |
| mask=mask, cond_mask=cond_mask, rope=rope, cond_rope=cond_rope, | |
| ) | |
| x = x + cross_out | |
| # FFN | |
| norm = _modulate(x, scale_ffn, shift_ffn) | |
| ff_output = self.ffn(norm) | |
| if gate_ffn.ndim == 2: | |
| gate_ffn = gate_ffn.unsqueeze(1) | |
| x = x + gate_ffn * ff_output | |
| return x | |
| # --------------------------------------------------------------------------- | |
| # AudioDiTTransformer (CrossDiT backbone) | |
| # --------------------------------------------------------------------------- | |
| class AudioDiTTransformer(nn.Module): | |
| """The core DiT transformer backbone for AudioDiT.""" | |
| def __init__(self, config: AudioDiTConfig): | |
| super().__init__() | |
| dim = config.dit_dim | |
| latent_dim = config.latent_dim # 64 | |
| text_dim = config.dit_text_dim | |
| dim_head = dim // config.dit_heads | |
| self.config = config | |
| self.dim = dim | |
| self.depth = config.dit_depth | |
| self.long_skip = config.dit_long_skip | |
| self.adaln_type = config.dit_adaln_type | |
| self.adaln_use_text_cond = config.dit_adaln_use_text_cond | |
| self.time_embed = AudioDiTTimestepEmbedding(dim) | |
| self.input_embed = AudioDiTEmbedder(latent_dim, dim) | |
| self.text_embed = AudioDiTEmbedder(text_dim, dim) | |
| self.rotary_embed = AudioDiTRotaryEmbedding(dim_head, 2048, base=100000.0) | |
| self.blocks = nn.ModuleList([AudioDiTBlock(config) for _ in range(config.dit_depth)]) | |
| self.norm_out = AudioDiTAdaLayerNormZeroFinal(dim, bias=True, eps=config.dit_eps) | |
| self.proj_out = nn.Linear(dim, latent_dim) | |
| if config.dit_adaln_type == "global": | |
| self.adaln_global_mlp = AudioDiTAdaLNMLP(dim, dim * 6, bias=True) | |
| self.text_conv = config.dit_text_conv | |
| if config.dit_text_conv: | |
| self.text_conv_layer = nn.Sequential( | |
| *[AudioDiTConvNeXtV2Block(dim, dim * 2, bias=config.dit_bias, eps=config.dit_eps) for _ in range(4)] | |
| ) | |
| self.use_latent_condition = config.dit_use_latent_condition | |
| if config.dit_use_latent_condition: | |
| self.latent_embed = AudioDiTEmbedder(latent_dim, dim) | |
| self.latent_cond_embedder = AudioDiTEmbedder(dim * 2, dim) | |
| # Latent-space boundary tokens for env-spk multistream input. | |
| # Layout: [<boe>, env_latent, <bos>, spk_latent, <bon>, target_latent]. | |
| # Each (1, 1, latent_dim), trainable nn.Parameter. Re-initialized in | |
| # _initialize_weights() to N(0, 0.02). The trainer / inference code | |
| # is responsible for concatenating these into the latent sequence. | |
| self.boe_token = nn.Parameter(torch.zeros(1, 1, latent_dim)) | |
| self.bos_token = nn.Parameter(torch.zeros(1, 1, latent_dim)) | |
| self.bon_token = nn.Parameter(torch.zeros(1, 1, latent_dim)) | |
| # Text-space boundary tokens (parallel to latent ones but in the UMT5 | |
| # output space). Used in encode_multistream_text() to build: | |
| # [<boe_text>, env_text_emb, <bos_text>, spk_text_emb, <bon_text>, target_text_emb]. | |
| # Sized to dit_text_dim (UMT5 d_model = 768 for the base config). | |
| self.boe_text_token = nn.Parameter(torch.zeros(1, 1, text_dim)) | |
| self.bos_text_token = nn.Parameter(torch.zeros(1, 1, text_dim)) | |
| self.bon_text_token = nn.Parameter(torch.zeros(1, 1, text_dim)) | |
| self._initialize_weights() | |
| def _initialize_weights(self): | |
| """Zero-out AdaLN and output projection weights for stable training init.""" | |
| bias = self.config.dit_bias | |
| if self.adaln_type == "local": | |
| for block in self.blocks: | |
| nn.init.constant_(block.adaln_mlp.mlp[-1].weight, 0) | |
| if bias: | |
| nn.init.constant_(block.adaln_mlp.mlp[-1].bias, 0) | |
| elif self.adaln_type == "global": | |
| nn.init.constant_(self.adaln_global_mlp.mlp[-1].weight, 0) | |
| if bias: | |
| nn.init.constant_(self.adaln_global_mlp.mlp[-1].bias, 0) | |
| nn.init.constant_(self.norm_out.linear.weight, 0) | |
| nn.init.constant_(self.proj_out.weight, 0) | |
| if bias: | |
| nn.init.constant_(self.norm_out.linear.bias, 0) | |
| nn.init.constant_(self.proj_out.bias, 0) | |
| for m in self.time_embed.modules(): | |
| if isinstance(m, nn.Linear): | |
| nn.init.normal_(m.weight, std=0.02) | |
| if m.bias is not None: | |
| nn.init.constant_(m.bias, 0) | |
| for m in self.text_embed.modules(): | |
| if isinstance(m, nn.Linear): | |
| nn.init.normal_(m.weight, std=0.02) | |
| if m.bias is not None: | |
| nn.init.constant_(m.bias, 0) | |
| # Boundary tokens: N(0, 0.02) so they carry non-trivial signal from | |
| # step 0. HF from_pretrained's meta-init can leave new params with | |
| # garbage (~1e36) past bf16 saturation; this re-init guarantees finite. | |
| for tok in ( | |
| self.boe_token, self.bos_token, self.bon_token, | |
| self.boe_text_token, self.bos_text_token, self.bon_text_token, | |
| ): | |
| nn.init.normal_(tok, mean=0.0, std=0.02) | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| text: torch.Tensor, | |
| text_len: torch.Tensor, | |
| time: torch.Tensor, | |
| mask: torch.BoolTensor | None = None, | |
| cond_mask: torch.BoolTensor | None = None, | |
| return_ith_layer: int | None = None, | |
| latent_cond: torch.Tensor | None = None, | |
| ) -> dict[str, torch.Tensor | None]: | |
| dtype = next(self.parameters()).dtype | |
| x = x.to(dtype) | |
| text = text.to(dtype) | |
| time = time.to(dtype) | |
| batch = x.shape[0] | |
| text_seq_len = text.shape[1] | |
| if time.ndim == 0: | |
| time = time.repeat(batch) | |
| t = self.time_embed(time) | |
| text = self.text_embed(text, cond_mask) | |
| if self.text_conv: | |
| # The text ConvNeXt contains a GRN (ConvNeXtV2) that L2-pools over the | |
| # TIME axis. GRN assumes every position is valid (it's an image op); | |
| # running it on a zero-padded batch makes the pool length/padding- | |
| # dependent β batched output β single-sample. UMT5's own norms are | |
| # per-token, so they're already batch-invariant. To match B=1 exactly, | |
| # run the conv PER SAMPLE on each sequence's valid tokens (no padding | |
| # enters the GRN), then scatter back. Robust to padding side; a no-op | |
| # difference vs the old path when B=1 / no padding. | |
| conv_out = torch.zeros_like(text) | |
| for i in range(text.shape[0]): | |
| mi = cond_mask[i] | |
| conv_out[i][mi] = self.text_conv_layer(text[i][mi].unsqueeze(0))[0] | |
| text = conv_out | |
| x = self.input_embed(x, mask) | |
| if self.use_latent_condition: | |
| latent_cond = latent_cond.to(dtype) | |
| latent_cond = self.latent_embed(latent_cond, mask) | |
| x = self.latent_cond_embedder(torch.cat([x, latent_cond], dim=-1)) | |
| if self.long_skip: | |
| x_clone = x.clone() | |
| seq_len = x.shape[1] | |
| rope = self.rotary_embed(x, seq_len) | |
| cond_rope = self.rotary_embed(text, text_seq_len) | |
| if self.adaln_type == "global": | |
| if self.adaln_use_text_cond: | |
| text_mean = text.sum(1) / text_len.unsqueeze(1).to(text.dtype) | |
| norm_cond = t + text_mean | |
| else: | |
| norm_cond = t | |
| adaln_mlp_out = self.adaln_global_mlp(norm_cond) | |
| else: | |
| adaln_mlp_out = None | |
| norm_cond = None | |
| hidden_state = None | |
| for i, block in enumerate(self.blocks): | |
| x = block( | |
| x=x, t=t, cond=text, mask=mask, cond_mask=cond_mask, | |
| rope=rope, cond_rope=cond_rope, adaln_global_out=adaln_mlp_out, | |
| ) | |
| if return_ith_layer == i + 1: | |
| hidden_state = x.clone() | |
| if self.long_skip: | |
| x = x + x_clone | |
| if self.long_skip: | |
| x = x + x_clone | |
| x = self.norm_out(x, norm_cond if norm_cond is not None else t) | |
| output = self.proj_out(x) | |
| return {"last_hidden_state": output, "hidden_state": hidden_state} | |
| # --------------------------------------------------------------------------- | |
| # WAV-VAE components (from wav_vae.py) | |
| # --------------------------------------------------------------------------- | |
| def _snake_beta(x: torch.Tensor, alpha: torch.Tensor, beta: torch.Tensor) -> torch.Tensor: | |
| return x + (1.0 / (beta + 1e-9)) * torch.sin(x * alpha).pow(2) | |
| class AudioDiTSnakeBeta(nn.Module): | |
| def __init__(self, in_features: int, alpha_logscale: bool = True): | |
| super().__init__() | |
| self.alpha_logscale = alpha_logscale | |
| self.alpha = nn.Parameter(torch.zeros(in_features)) | |
| self.beta = nn.Parameter(torch.zeros(in_features)) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| alpha = self.alpha.unsqueeze(0).unsqueeze(-1) | |
| beta = self.beta.unsqueeze(0).unsqueeze(-1) | |
| if self.alpha_logscale: | |
| alpha = torch.exp(alpha) | |
| beta = torch.exp(beta) | |
| return _snake_beta(x, alpha, beta) | |
| def _get_vae_activation(activation: str, channels: int | None = None) -> nn.Module: | |
| if activation == "elu": | |
| return nn.ELU() | |
| elif activation == "snake": | |
| return AudioDiTSnakeBeta(channels) | |
| elif activation == "none": | |
| return nn.Identity() | |
| raise ValueError(f"Unknown activation {activation}") | |
| def _wn_conv1d(*args, **kwargs): | |
| return weight_norm(nn.Conv1d(*args, **kwargs)) | |
| def _wn_conv_transpose1d(*args, **kwargs): | |
| return weight_norm(nn.ConvTranspose1d(*args, **kwargs)) | |
| def _pixel_unshuffle_1d(x: torch.Tensor, factor: int) -> torch.Tensor: | |
| b, c, w = x.size() | |
| return x.view(b, c, w // factor, factor).permute(0, 1, 3, 2).contiguous().view(b, c * factor, w // factor) | |
| def _pixel_shuffle_1d(x: torch.Tensor, factor: int) -> torch.Tensor: | |
| b, c, w = x.size() | |
| c = c // factor | |
| return x.view(b, c, factor, w).permute(0, 1, 3, 2).contiguous().view(b, c, w * factor) | |
| class _DownsampleShortcut(nn.Module): | |
| def __init__(self, in_channels: int, out_channels: int, factor: int): | |
| super().__init__() | |
| self.factor = factor | |
| self.group_size = in_channels * factor // out_channels | |
| self.out_channels = out_channels | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| x = _pixel_unshuffle_1d(x, self.factor) | |
| b, c, n = x.shape | |
| return x.view(b, self.out_channels, self.group_size, n).mean(dim=2) | |
| class _UpsampleShortcut(nn.Module): | |
| def __init__(self, in_channels: int, out_channels: int, factor: int): | |
| super().__init__() | |
| self.factor = factor | |
| self.repeats = out_channels * factor // in_channels | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| x = x.repeat_interleave(self.repeats, dim=1) | |
| return _pixel_shuffle_1d(x, self.factor) | |
| class _VaeResidualUnit(nn.Module): | |
| def __init__(self, in_channels: int, out_channels: int, dilation: int, kernel_size: int = 7, use_snake: bool = False): | |
| super().__init__() | |
| padding = (dilation * (kernel_size - 1)) // 2 | |
| act = "snake" if use_snake else "elu" | |
| self.layers = nn.Sequential( | |
| _get_vae_activation(act, channels=out_channels), | |
| _wn_conv1d(in_channels, out_channels, kernel_size, dilation=dilation, padding=padding), | |
| _get_vae_activation(act, channels=out_channels), | |
| _wn_conv1d(out_channels, out_channels, kernel_size=1), | |
| ) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return x + self.layers(x) | |
| class _VaeEncoderBlock(nn.Module): | |
| def __init__(self, in_ch: int, out_ch: int, stride: int, use_snake: bool = False, downsample_shortcut: str = "none"): | |
| super().__init__() | |
| layers = [] | |
| for d in [1, 3, 9]: | |
| layers.append(_VaeResidualUnit(in_ch, in_ch, dilation=d, use_snake=use_snake)) | |
| act = "snake" if use_snake else "elu" | |
| layers.append(_get_vae_activation(act, channels=in_ch)) | |
| layers.append(_wn_conv1d(in_ch, out_ch, kernel_size=2 * stride, stride=stride, padding=math.ceil(stride / 2))) | |
| self.layers = nn.Sequential(*layers) | |
| self.res = _DownsampleShortcut(in_ch, out_ch, stride) if downsample_shortcut == "averaging" else None | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| if self.res is not None: | |
| return self.layers(x) + self.res(x) | |
| return self.layers(x) | |
| class _VaeDecoderBlock(nn.Module): | |
| def __init__(self, in_ch: int, out_ch: int, stride: int, use_snake: bool = False, upsample_shortcut: str = "none"): | |
| super().__init__() | |
| act = "snake" if use_snake else "elu" | |
| layers = [ | |
| _get_vae_activation(act, channels=in_ch), | |
| _wn_conv_transpose1d(in_ch, out_ch, kernel_size=2 * stride, stride=stride, padding=math.ceil(stride / 2)), | |
| ] | |
| for d in [1, 3, 9]: | |
| layers.append(_VaeResidualUnit(out_ch, out_ch, dilation=d, use_snake=use_snake)) | |
| self.layers = nn.Sequential(*layers) | |
| self.res = _UpsampleShortcut(in_ch, out_ch, stride) if upsample_shortcut == "duplicating" else None | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| if self.res is not None: | |
| return self.layers(x) + self.res(x) | |
| return self.layers(x) | |
| class AudioDiTVaeEncoder(nn.Module): | |
| def __init__(self, config: AudioDiTVaeConfig): | |
| super().__init__() | |
| c_mults = [1] + config.c_mults | |
| ch = config.channels | |
| layers = [_wn_conv1d(config.in_channels, c_mults[0] * ch, kernel_size=7, padding=3)] | |
| for i in range(len(c_mults) - 1): | |
| layers.append(_VaeEncoderBlock(c_mults[i] * ch, c_mults[i + 1] * ch, config.strides[i], use_snake=config.use_snake, downsample_shortcut=config.downsample_shortcut)) | |
| layers.append(_wn_conv1d(c_mults[-1] * ch, config.encoder_latent_dim, kernel_size=3, padding=1)) | |
| self.layers = nn.Sequential(*layers) | |
| if config.out_shortcut == "averaging": | |
| self.shortcut = _DownsampleShortcut(c_mults[-1] * ch, config.encoder_latent_dim, 1) | |
| else: | |
| self.shortcut = None | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| if self.shortcut is None: | |
| return self.layers(x) | |
| x = self.layers[:-1](x) | |
| return self.layers[-1](x) + self.shortcut(x) | |
| class AudioDiTVaeDecoder(nn.Module): | |
| def __init__(self, config: AudioDiTVaeConfig): | |
| super().__init__() | |
| c_mults = [1] + config.c_mults | |
| ch = config.channels | |
| if config.in_shortcut == "duplicating": | |
| self.shortcut = _UpsampleShortcut(config.latent_dim, c_mults[-1] * ch, 1) | |
| else: | |
| self.shortcut = None | |
| layers = [_wn_conv1d(config.latent_dim, c_mults[-1] * ch, kernel_size=7, padding=3)] | |
| for i in range(len(c_mults) - 1, 0, -1): | |
| layers.append(_VaeDecoderBlock(c_mults[i] * ch, c_mults[i - 1] * ch, config.strides[i - 1], use_snake=config.use_snake, upsample_shortcut=config.upsample_shortcut)) | |
| act = "snake" if config.use_snake else "elu" | |
| layers.append(_get_vae_activation(act, channels=c_mults[0] * ch)) | |
| layers.append(_wn_conv1d(c_mults[0] * ch, config.in_channels, kernel_size=7, padding=3, bias=False)) | |
| if config.final_tanh: | |
| layers.append(nn.Tanh()) | |
| else: | |
| layers.append(nn.Identity()) | |
| self.layers = nn.Sequential(*layers) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| if self.shortcut is None: | |
| return self.layers(x) | |
| x_short = self.shortcut(x) + self.layers[0](x) | |
| return self.layers[1:](x_short) | |
| class AudioDiTVae(nn.Module): | |
| """WAV-VAE audio autoencoder with VAE bottleneck and scale factor. | |
| The original checkpoint runs encode/decode in **float16** (``model_half=True`` | |
| in ``AutoencoderPretransform``). We replicate this behaviour so that the | |
| outputs are numerically identical to the original codebase. | |
| """ | |
| def __init__(self, config: AudioDiTVaeConfig): | |
| super().__init__() | |
| self.config = config | |
| self.encoder = AudioDiTVaeEncoder(config) | |
| self.decoder = AudioDiTVaeDecoder(config) | |
| self.scale = config.scale | |
| self.downsampling_ratio = config.downsampling_ratio | |
| def to_half(self): | |
| """Convert encoder and decoder weights to float16 (matching original behaviour).""" | |
| self.encoder.half() | |
| self.decoder.half() | |
| return self | |
| def encode(self, audio: torch.Tensor) -> torch.Tensor: | |
| """Encode audio to latent space. | |
| Runs encoder **and** VAE bottleneck in float16 when weights are float16, | |
| matching the original ``AutoencoderPretransform(model_half=True)`` + | |
| ``AudioAutoencoder.encode`` behaviour where the bottleneck operates on | |
| the fp16 encoder output before the final ``.float()`` conversion. | |
| Args: | |
| audio: ``(batch, 1, num_samples)`` raw waveform. | |
| Returns: | |
| Latent tensor ``(batch, latent_dim, num_frames)`` in float32. | |
| """ | |
| is_half = next(self.encoder.parameters()).dtype == torch.float16 | |
| if is_half: | |
| audio = audio.half() | |
| latents = self.encoder(audio) | |
| # VAE bottleneck runs in the same dtype as encoder output (fp16) | |
| # to match original: bottleneck.encode(latents) happens before .float() | |
| mean, scale_param = latents.chunk(2, dim=1) | |
| stdev = F.softplus(scale_param) + 1e-4 | |
| latents = torch.randn_like(mean) * stdev + mean | |
| # Convert to fp32 after bottleneck, matching original AutoencoderPretransform | |
| if is_half: | |
| latents = latents.float() | |
| return latents / self.scale | |
| def decode(self, latents: torch.Tensor) -> torch.Tensor: | |
| """Decode latents to audio waveform. | |
| Runs decoder in float16 when weights are float16, matching the original | |
| ``AutoencoderPretransform(model_half=True)`` behaviour. | |
| Args: | |
| latents: ``(batch, latent_dim, num_frames)``. | |
| Returns: | |
| Waveform tensor ``(batch, 1, num_samples)`` in float32. | |
| """ | |
| z = latents * self.scale | |
| is_half = next(self.decoder.parameters()).dtype == torch.float16 | |
| if is_half: | |
| z = z.half() | |
| decoded = self.decoder(z) | |
| if is_half: | |
| decoded = decoded.float() | |
| return decoded | |
| # --------------------------------------------------------------------------- | |
| # Top-level AudioDiTModel | |
| # --------------------------------------------------------------------------- | |
| class AudioDiTPreTrainedModel(PreTrainedModel): | |
| config_class = AudioDiTConfig | |
| base_model_prefix = "audiodit" | |
| supports_gradient_checkpointing = True | |
| _supports_sdpa = True | |
| def _init_weights(self, module): | |
| if isinstance(module, nn.Linear): | |
| nn.init.normal_(module.weight, std=0.02) | |
| if module.bias is not None: | |
| nn.init.zeros_(module.bias) | |
| elif isinstance(module, nn.Embedding): | |
| nn.init.normal_(module.weight, std=0.02) | |
| elif isinstance(module, AudioDiTTransformer): | |
| # Re-init the boundary tokens after HF from_pretrained β they're | |
| # nn.Parameter (not modules) and don't appear in the pretrained | |
| # ckpt for the env-tts task, so HF's meta-init path leaves them | |
| # with uninitialized memory (~1e32) past bf16 saturation. | |
| for tok_name in ( | |
| "boe_token", "bos_token", "bon_token", | |
| "boe_text_token", "bos_text_token", "bon_text_token", | |
| ): | |
| tok = getattr(module, tok_name, None) | |
| if tok is not None: | |
| nn.init.normal_(tok, mean=0.0, std=0.02) | |
| class AudioDiTModel(AudioDiTPreTrainedModel): | |
| """AudioDiT: Conditional Flow Matching TTS model with DiT backbone, UMT5 text encoder, and WAV-VAE. | |
| All sub-models (text_encoder, transformer, vae) are constructed from config | |
| and their weights are loaded together via ``from_pretrained``. | |
| Example:: | |
| model = AudioDiTModel.from_pretrained("hf_audiodit_1b") | |
| tokenizer = AutoTokenizer.from_pretrained(model.config.text_encoder_model) | |
| output = model(text=["Hello world"], tokenizer=tokenizer) | |
| waveform = output.waveform # (B, num_samples) | |
| """ | |
| def __init__(self, config: AudioDiTConfig): | |
| super().__init__(config) | |
| self.config = config | |
| # Text encoder β constructed from embedded config, weights loaded by from_pretrained | |
| from transformers import UMT5EncoderModel, UMT5Config | |
| if config.text_encoder_config is not None: | |
| self.text_encoder = UMT5EncoderModel(config.text_encoder_config) | |
| else: | |
| te_config = UMT5Config.from_pretrained(config.text_encoder_model) | |
| self.text_encoder = UMT5EncoderModel(te_config) | |
| self.text_encoder.requires_grad_(False) | |
| # DiT transformer | |
| self.transformer = AudioDiTTransformer(config) | |
| # WAV-VAE | |
| self.vae = AudioDiTVae(config.vae_config) | |
| self.vae.requires_grad_(False) | |
| self.post_init() | |
| def encode_text( | |
| self, | |
| input_ids: torch.LongTensor, | |
| attention_mask: torch.LongTensor, | |
| ) -> torch.FloatTensor: | |
| """Encode tokenized text using the UMT5 text encoder. | |
| Args: | |
| input_ids: Token ids ``(batch, seq_len)``. | |
| attention_mask: Attention mask ``(batch, seq_len)``. | |
| Returns: | |
| Text embeddings ``(batch, seq_len, text_dim)`` in float32. | |
| """ | |
| with torch.no_grad(): | |
| output = self.text_encoder( | |
| input_ids=input_ids, | |
| attention_mask=attention_mask, | |
| output_hidden_states=True, | |
| ) | |
| emb = output.last_hidden_state | |
| d_model = self.text_encoder.config.d_model | |
| if self.config.text_norm_feat: | |
| emb = F.layer_norm(emb, (d_model,), eps=1e-6) | |
| if self.config.text_add_embed: | |
| first_hidden = output.hidden_states[0] | |
| if self.config.text_norm_feat: | |
| first_hidden = F.layer_norm(first_hidden, (d_model,), eps=1e-6) | |
| emb = emb + first_hidden | |
| return emb.float() | |
| def encode_multistream_text( | |
| self, | |
| env_input_ids: torch.LongTensor, | |
| env_attn: torch.LongTensor, | |
| spk_input_ids: torch.LongTensor, | |
| spk_attn: torch.LongTensor, | |
| target_input_ids: torch.LongTensor, | |
| target_attn: torch.LongTensor, | |
| drop_env_text: torch.BoolTensor | None = None, | |
| drop_spk_text: torch.BoolTensor | None = None, | |
| drop_target_text: torch.BoolTensor | None = None, | |
| ) -> tuple[torch.FloatTensor, torch.BoolTensor, torch.LongTensor]: | |
| """Encode three text streams and assemble with boundary tokens. | |
| Each segment is independently tokenized + frozen-UMT5 encoded, then | |
| concatenated as: | |
| [<boe_text>, env_emb, <bos_text>, spk_emb, <bon_text>, tgt_emb] | |
| Boundary tokens are always visible in the output mask. If a per-sample | |
| drop flag is set, that segment's embedding values are zeroed in place | |
| (position + boundary preserved, content zeroed). | |
| Args: | |
| env_input_ids / env_attn: (B, S_env) | |
| spk_input_ids / spk_attn: (B, S_spk) | |
| target_input_ids / target_attn: (B, S_tgt) | |
| drop_env_text / drop_spk_text / drop_target_text: (B,) bool or None. | |
| Returns: | |
| text_emb: (B, 3 + S_env + S_spk + S_tgt, dit_text_dim) float32 | |
| text_mask: (B, 3 + S_env + S_spk + S_tgt) bool β boundary positions | |
| always True; segment positions follow their attention masks | |
| (NOT cleared by drop flags, consistent with dit-training CFG | |
| null-pass convention which preserves cond_mask). | |
| text_len: (B,) long β sum of text_mask along dim=1. | |
| """ | |
| device = self.device | |
| # Concat-batch UMT5 encode: pad three streams to a common seq_len then | |
| # run ONE encode_text call on shape (3B, S_max). Splits back per-stream | |
| # at the end. Saves 2 kernel-launch round-trips per cfm_step. | |
| env_ids = env_input_ids.to(device) | |
| spk_ids = spk_input_ids.to(device) | |
| tgt_ids = target_input_ids.to(device) | |
| env_msk = env_attn.to(device) | |
| spk_msk = spk_attn.to(device) | |
| tgt_msk = target_attn.to(device) | |
| S_env, S_spk, S_tgt = env_ids.shape[1], spk_ids.shape[1], tgt_ids.shape[1] | |
| S_max = max(S_env, S_spk, S_tgt) | |
| def _pad(t, s): | |
| return F.pad(t, (0, s - t.shape[1])) if t.shape[1] < s else t | |
| all_ids = torch.cat([_pad(env_ids, S_max), _pad(spk_ids, S_max), _pad(tgt_ids, S_max)], dim=0) | |
| all_msk = torch.cat([_pad(env_msk, S_max), _pad(spk_msk, S_max), _pad(tgt_msk, S_max)], dim=0) | |
| all_emb = self.encode_text(all_ids, all_msk) # (3B, S_max, D) | |
| B = env_ids.shape[0] | |
| env_emb = all_emb[0 : B , :S_env, :] | |
| spk_emb = all_emb[B : 2*B , :S_spk, :] | |
| tgt_emb = all_emb[2*B : 3*B , :S_tgt, :] | |
| text_dim = env_emb.shape[-1] | |
| # Apply text-side drop (position-preserving content zero). | |
| if drop_env_text is not None: | |
| env_emb = env_emb * (~drop_env_text.to(device)).view(B, 1, 1).to(env_emb.dtype) | |
| if drop_spk_text is not None: | |
| spk_emb = spk_emb * (~drop_spk_text.to(device)).view(B, 1, 1).to(spk_emb.dtype) | |
| if drop_target_text is not None: | |
| tgt_emb = tgt_emb * (~drop_target_text.to(device)).view(B, 1, 1).to(tgt_emb.dtype) | |
| # Resolve PEFT-wrapped transformer to access the boundary nn.Parameter. | |
| src = getattr(self.transformer, "base_model", None) | |
| src = src.model if src is not None else self.transformer | |
| boe_t = src.boe_text_token.to(device=device, dtype=env_emb.dtype) | |
| bos_t = src.bos_text_token.to(device=device, dtype=env_emb.dtype) | |
| bon_t = src.bon_text_token.to(device=device, dtype=env_emb.dtype) | |
| # ββ Tight per-sample assembly (batch-invariant positions) βββββββββ | |
| # Pack each sample's VALID tokens contiguously [boe|env|bos|spk|bon|tgt] | |
| # and end-pad to the batch max. A sample's assembled text β and thus its | |
| # cond_rope positions β is therefore INDEPENDENT of other batch members' | |
| # stream lengths, so batched == single-sample. | |
| # | |
| # (The earlier version padded each stream to the batch-max per-stream | |
| # length and concatenated WITH that padding inside, interleaving padding | |
| # mid-sequence. For any sample shorter than the batch max this shifted the | |
| # bos/bon + spk/tgt positions β cond_rope mismatch at B>1 β corrupted | |
| # generation that compounds over the ODE. B=1 is unaffected and stays | |
| # byte-identical; the single-stream encode_text path is untouched.) | |
| boe1, bos1, bon1 = boe_t.reshape(1, text_dim), bos_t.reshape(1, text_dim), bon_t.reshape(1, text_dim) | |
| env_m = env_attn.to(device).bool() | |
| spk_m = spk_attn.to(device).bool() | |
| tgt_m = target_attn.to(device).bool() | |
| seqs = [ | |
| torch.cat([ | |
| boe1, env_emb[i][env_m[i]], | |
| bos1, spk_emb[i][spk_m[i]], | |
| bon1, tgt_emb[i][tgt_m[i]], | |
| ], dim=0) # (L_i, text_dim), tight | |
| for i in range(B) | |
| ] | |
| L_max = max(s.shape[0] for s in seqs) | |
| text_emb = torch.stack( | |
| [F.pad(s, (0, 0, 0, L_max - s.shape[0])) for s in seqs], dim=0) # (B, L_max, text_dim) | |
| text_mask = torch.zeros(B, L_max, dtype=torch.bool, device=device) | |
| for i, s in enumerate(seqs): | |
| text_mask[i, : s.shape[0]] = True | |
| text_len = text_mask.sum(dim=1).long() | |
| return text_emb.float(), text_mask, text_len | |
| def encode_prompt_audio(self, prompt_audio: torch.FloatTensor) -> tuple[torch.FloatTensor, int]: | |
| """Encode prompt audio to latent space. | |
| Args: | |
| prompt_audio: Waveform tensor ``(batch, 1, num_samples)`` or ``(batch, num_samples)``. | |
| Returns: | |
| Tuple of (prompt_latent ``(batch, num_frames, latent_dim)``, prompt_duration_frames). | |
| """ | |
| full_hop = self.config.latent_hop | |
| off = 3 | |
| wav = prompt_audio.to(self.device) | |
| if wav.ndim == 2: | |
| wav = wav.unsqueeze(1) | |
| if wav.shape[-1] % full_hop != 0: | |
| wav = F.pad(wav, (0, full_hop - wav.shape[-1] % full_hop)) | |
| wav = F.pad(wav, (0, full_hop * off)) | |
| latent = self.vae.encode(wav) | |
| if off != 0: | |
| latent = latent[..., :-off] | |
| prompt_duration_frames = latent.shape[-1] | |
| return latent.permute(0, 2, 1), prompt_duration_frames | |
| def forward( | |
| self, | |
| input_ids: torch.LongTensor | None = None, | |
| attention_mask: torch.LongTensor | None = None, | |
| text_embedding: torch.FloatTensor | None = None, | |
| text_mask: torch.BoolTensor | None = None, | |
| prompt_audio: torch.FloatTensor | None = None, | |
| prompt_latent: torch.FloatTensor | None = None, | |
| prompt_lens: torch.LongTensor | None = None, | |
| duration: int | None = None, | |
| steps: int = 16, | |
| cfg_strength: float = 4.0, | |
| guidance_method: str = "cfg", | |
| return_dict: bool = True, | |
| ) -> AudioDiTOutput | tuple: | |
| """Generate audio from text (and optional prompt audio). | |
| Args: | |
| input_ids: Tokenized text ``(batch, seq_len)``. Use with ``attention_mask``. | |
| attention_mask: Attention mask ``(batch, seq_len)``. | |
| text_embedding: Pre-computed text embeddings ``(batch, seq_len, dim)``. Alternative to input_ids. | |
| When supplied alongside ``text_mask`` the model bypasses ``encode_text`` | |
| entirely β used by the env-tts pipeline which builds a multi-stream | |
| text embedding via ``encode_multistream_text``. | |
| text_mask: Optional bool mask ``(batch, seq_len)`` for ``text_embedding``. | |
| Required when ``text_embedding`` is supplied without ``attention_mask``. | |
| prompt_audio: Optional prompt audio ``(batch, 1, num_samples)`` for voice cloning. | |
| prompt_latent: Optional pre-assembled prompt latent ``(batch, T_prompt, latent_dim)``, | |
| bypassing ``encode_prompt_audio``. Use this for env-tts multi-stream | |
| latents already containing latent-space boundary tokens. | |
| Mutually exclusive with ``prompt_audio`` β if both are given, | |
| ``prompt_latent`` wins. | |
| duration: Target duration in latent frames (prompt + gen). If None, uses max_wav_duration. | |
| steps: Number of ODE Euler steps (default 16). | |
| cfg_strength: Guidance strength for CFG/APG (default 4.0). | |
| guidance_method: ``"cfg"`` or ``"apg"`` (default ``"cfg"``). | |
| return_dict: Whether to return ``AudioDiTOutput`` or tuple. | |
| """ | |
| device = self.device | |
| sr = self.config.sampling_rate | |
| full_hop = self.config.latent_hop | |
| max_duration_frames = int(self.config.max_wav_duration * sr // full_hop) | |
| repa_layer = self.config.repa_dit_layer | |
| # ββ text encoding βββββββββββββββββββββββββββββββββββββββββββββ | |
| if text_embedding is not None: | |
| text_condition = text_embedding.to(device, torch.float32) | |
| if text_mask is not None: | |
| text_condition_len = text_mask.to(device).sum(dim=1).long() | |
| elif attention_mask is not None: | |
| text_condition_len = attention_mask.sum(dim=1).to(device) | |
| else: | |
| text_condition_len = torch.full( | |
| (text_condition.shape[0],), text_condition.shape[1], device=device, | |
| ) | |
| else: | |
| text_condition = self.encode_text( | |
| input_ids.to(device), attention_mask.to(device), | |
| ) | |
| text_condition_len = attention_mask.sum(dim=1).to(device) | |
| batch = text_condition.shape[0] | |
| # ββ prompt latent / audio encoding ββββββββββββββββββββββββββββ | |
| # Precedence: explicit ``prompt_latent`` > ``prompt_audio`` > empty. | |
| # ``prompt_latent`` is used by the env-tts pipeline which builds a | |
| # multi-stream latent [boe|z_env|bos|z_spk|bon] externally; the | |
| # ``prompt_audio`` path is the single-stream voice-cloning default. | |
| has_prompt = prompt_latent is not None or prompt_audio is not None | |
| if prompt_latent is not None: | |
| prompt_latent = prompt_latent.to(device) | |
| prompt_dur = prompt_latent.shape[1] | |
| elif prompt_audio is not None: | |
| prompt_latent, prompt_dur = self.encode_prompt_audio(prompt_audio) | |
| else: | |
| prompt_latent = torch.empty(batch, 0, self.config.latent_dim, device=device) | |
| prompt_dur = 0 | |
| # ββ duration ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # ``duration`` may be a scalar (uniform, the single-sample path) OR a | |
| # per-sample 1-D tensor / list of TOTAL frames (prompt+gen). The latter | |
| # enables BATCHED generation of variable-length samples β callers pad all | |
| # prompts to a common width (so ``prompt_dur`` stays uniform) and pass the | |
| # per-sample total lengths here; the transformer ``mask`` + per-sample | |
| # ``y0`` already handle ragged gen lengths. | |
| if duration is None: | |
| duration = max_duration_frames | |
| if torch.is_tensor(duration) or isinstance(duration, (list, tuple)): | |
| duration_tensor = torch.as_tensor(duration, device=device, dtype=torch.long).clamp(max=max_duration_frames) | |
| else: | |
| duration_tensor = torch.full((batch,), min(int(duration), max_duration_frames), | |
| device=device, dtype=torch.long) | |
| max_dur = int(duration_tensor.max().item()) | |
| # ββ masks & conditioning ββββββββββββββββββββββββββββββββββββββ | |
| mask = lens_to_mask(duration_tensor, length=max_dur) | |
| if text_mask is not None: | |
| text_cond_mask = text_mask.to(device).bool() | |
| else: | |
| text_cond_mask = lens_to_mask(text_condition_len, length=text_condition.shape[1]) | |
| neg_text = torch.zeros_like(text_condition) | |
| neg_text_len = text_condition_len | |
| # ``prompt_lens`` (B,) gives each sample's REAL prompt length so the gen | |
| # region starts immediately after that sample's ``bon`` (no padding between | |
| # the boundary token and gen). When None, the single-sample uniform path is | |
| # used unchanged. ``latent_cond`` is the real prompt at [0:T_p_i] then zeros. | |
| latent_len = prompt_dur | |
| prompt_mask = None | |
| if prompt_lens is not None: | |
| prompt_lens = torch.as_tensor(prompt_lens, device=device, dtype=torch.long) | |
| prompt_mask = lens_to_mask(prompt_lens, length=max_dur) # (B, max_dur) | |
| if has_prompt: | |
| latent_cond = F.pad(prompt_latent, (0, 0, 0, max_dur - prompt_latent.shape[1])) | |
| empty_latent_cond = torch.zeros_like(latent_cond) | |
| else: | |
| latent_cond = torch.zeros(batch, max_dur, self.config.latent_dim, device=device) | |
| empty_latent_cond = latent_cond | |
| # ββ APG buffer ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if guidance_method == "apg": | |
| if prompt_mask is not None: | |
| raise NotImplementedError("APG guidance unsupported with batched prompt_lens; use cfg.") | |
| apg_buffer = _MomentumBuffer(momentum=-0.3) | |
| # ββ ODE function ββββββββββββββββββββββββββββββββββββββββββββββ | |
| def fn(t, x): | |
| if prompt_mask is not None: # per-sample prompt region (in-place reset each step) | |
| x[prompt_mask] = (prompt_noise * (1 - t) + latent_cond * t)[prompt_mask] | |
| else: | |
| x[:, :latent_len] = prompt_noise * (1 - t) + latent_cond[:, :latent_len] * t | |
| output = self.transformer( | |
| x=x, text=text_condition, text_len=text_condition_len, time=t, | |
| mask=mask, cond_mask=text_cond_mask, | |
| return_ith_layer=repa_layer, latent_cond=latent_cond, | |
| ) | |
| pred = output["last_hidden_state"] | |
| if cfg_strength < 1e-5: | |
| return pred | |
| if prompt_mask is not None: | |
| x[prompt_mask] = 0 | |
| else: | |
| x[:, :latent_len] = 0 | |
| null_output = self.transformer( | |
| x=x, text=neg_text, text_len=neg_text_len, time=t, | |
| mask=mask, cond_mask=text_cond_mask, | |
| return_ith_layer=repa_layer, latent_cond=empty_latent_cond, | |
| ) | |
| null_pred = null_output["last_hidden_state"] | |
| if guidance_method == "cfg": | |
| return pred + (pred - null_pred) * cfg_strength | |
| # APG (single-sample path only) | |
| x_s = x[:, latent_len:] | |
| pred_s = pred[:, latent_len:] | |
| null_s = null_pred[:, latent_len:] | |
| pred_sample = x_s + (1 - t) * pred_s | |
| null_sample = x_s + (1 - t) * null_s | |
| out = _apg_forward( | |
| pred_sample, null_sample, cfg_strength, apg_buffer, | |
| eta=0.5, norm_threshold=0.0, dims=[-1, -2], | |
| ) | |
| out = (out - x_s) / (1 - t) | |
| return F.pad(out, (0, 0, latent_len, 0), value=0.0) | |
| # ββ initial noise βββββββββββββββββββββββββββββββββββββββββββββ | |
| y0 = [] | |
| for dur in duration_tensor: | |
| noise = torch.randn(dur.item(), self.config.latent_dim, device=device) | |
| y0.append(noise) | |
| y0 = pad_sequence(y0, padding_value=0, batch_first=True) | |
| # ββ ODE solve βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| t = torch.linspace(0, 1, steps, device=device) | |
| prompt_noise = y0.clone() if prompt_mask is not None else y0[:, :latent_len].clone() | |
| trajectory = odeint_euler(fn, y0, t) | |
| sampled = trajectory[-1] | |
| # ββ extract gen region + decode βββββββββββββββββββββββββββββββ | |
| if prompt_mask is not None: | |
| # Decode each sample's gen latent at its EXACT length (decoding a | |
| # zero-PADDED batch latent bleeds VAE-decoder artifacts into the valid | |
| # audio), then zero-pad the waveforms; the caller trims to true length. | |
| wavs = [] | |
| for b in range(batch): | |
| g = sampled[b, int(prompt_lens[b]):int(duration_tensor[b])] # (T_gen, D) | |
| gl = g.permute(1, 0).unsqueeze(0).float() # (1, D, T_gen) | |
| wavs.append(self.vae.decode(gl).reshape(-1)) # (T_gen*hop,) | |
| max_w = max((w.shape[0] for w in wavs), default=1) | |
| waveform = torch.stack([F.pad(w, (0, max_w - w.shape[0])) for w in wavs], dim=0) | |
| pred_latent = None | |
| else: | |
| pred_latent = sampled | |
| if has_prompt: | |
| pred_latent = pred_latent[:, prompt_dur:] | |
| pred_latent = pred_latent.permute(0, 2, 1).float() | |
| waveform = self.vae.decode(pred_latent).squeeze(1) | |
| if not return_dict: | |
| return (waveform, pred_latent) | |
| return AudioDiTOutput(waveform=waveform, latent=pred_latent) | |
| # --------------------------------------------------------------------------- | |
| # APG helpers (from model/cfm.py β Adaptive Projected Guidance) | |
| # --------------------------------------------------------------------------- | |
| class _MomentumBuffer: | |
| def __init__(self, momentum: float = -0.75): | |
| self.momentum = momentum | |
| self.running_average = 0 | |
| def update(self, update_value: torch.Tensor): | |
| new_average = self.momentum * self.running_average | |
| self.running_average = update_value + new_average | |
| def _project(v0: torch.Tensor, v1: torch.Tensor, dims=(-1, -2)): | |
| dtype = v0.dtype | |
| device_type = v0.device.type | |
| if device_type == "mps": | |
| v0, v1 = v0.cpu(), v1.cpu() | |
| v0, v1 = v0.double(), v1.double() | |
| v1 = F.normalize(v1, dim=dims) | |
| v0_parallel = (v0 * v1).sum(dim=dims, keepdim=True) * v1 | |
| v0_orthogonal = v0 - v0_parallel | |
| return v0_parallel.to(dtype).to(device_type), v0_orthogonal.to(dtype).to(device_type) | |
| def _apg_forward(pred_cond, pred_uncond, guidance_scale, momentum_buffer=None, eta=0.0, norm_threshold=2.5, dims=(-1, -2)): | |
| diff = pred_cond - pred_uncond | |
| if momentum_buffer is not None: | |
| momentum_buffer.update(diff) | |
| diff = momentum_buffer.running_average | |
| if norm_threshold > 0: | |
| ones = torch.ones_like(diff) | |
| diff_norm = diff.norm(p=2, dim=dims, keepdim=True) | |
| scale_factor = torch.minimum(ones, norm_threshold / diff_norm) | |
| diff = diff * scale_factor | |
| diff_parallel, diff_orthogonal = _project(diff, pred_cond, dims) | |
| normalized_update = diff_orthogonal + eta * diff_parallel | |
| return pred_cond + guidance_scale * normalized_update | |
| __all__ = [ | |
| "AudioDiTConfig", | |
| "AudioDiTVaeConfig", | |
| "AudioDiTOutput", | |
| "AudioDiTPreTrainedModel", | |
| "AudioDiTModel", | |
| "AudioDiTTransformer", | |
| "AudioDiTVae", | |
| ] | |