QaDiT-160 / modelling_qadit.py
Sidharthan's picture
Upload folder using huggingface_hub
60c2ee0 verified
Raw
History Blame Contribute Delete
30.5 kB
"""
QaDiT model for Hugging Face Transformers (`trust_remote_code=True`).
Contains:
* DiT backbone (ported from audio_dit/dit.py)
* Cosine-schedule v-prediction DDIM sampler (from audio_dit/diffusion.py)
* :class:`QaDiTModel` — ``PreTrainedModel`` with ``generate(prompt=...)``
Example::
from transformers import AutoModel
model = AutoModel.from_pretrained("USER/qadit", trust_remote_code=True)
model = model.to("cuda")
out = model.generate("Rain falls on a metal roof with distant thunder")
# out.audios: list[np.ndarray], mono float32 in [-1, 1]
"""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import List, Optional, Union
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import PreTrainedModel
from transformers.modeling_outputs import ModelOutput
from transformers.utils import logging
try:
from .configuration_qadit import QaDiTConfig
except ImportError: # Hub dynamic module loads files as siblings
from configuration_qadit import QaDiTConfig
logger = logging.get_logger(__name__)
# --------------------------------------------------------------------------- #
# DiT building blocks (self-contained for Hub upload) #
# --------------------------------------------------------------------------- #
class TimestepEmbedder(nn.Module):
def __init__(self, hidden_size: int, freq_dim: int = 256):
super().__init__()
self.freq_dim = freq_dim
self.mlp = nn.Sequential(
nn.Linear(freq_dim, hidden_size),
nn.SiLU(),
nn.Linear(hidden_size, hidden_size),
)
@staticmethod
def sinusoidal(t: torch.Tensor, dim: int, max_period: int = 10_000) -> torch.Tensor:
half = dim // 2
freqs = torch.exp(
-math.log(max_period)
* torch.arange(half, dtype=torch.float32, device=t.device)
/ half
)
args = t.float()[:, None] * freqs[None]
return torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
def forward(self, t: torch.Tensor) -> torch.Tensor:
# Sinusoidal features are always built in float32 for precision; cast
# to the weight dtype so half-precision backbones work unchanged.
freqs = self.sinusoidal(t, self.freq_dim)
return self.mlp(freqs.to(self.mlp[0].weight.dtype))
def build_2d_sincos_pos_embed(dim: int, grid_t: int, grid_f: int) -> torch.Tensor:
assert dim % 4 == 0
def axis_embed(positions: torch.Tensor, axis_dim: int) -> torch.Tensor:
omega = torch.arange(axis_dim // 2, dtype=torch.float32) / (axis_dim // 2)
omega = 1.0 / (10_000 ** omega)
out = positions.float()[:, None] * omega[None]
return torch.cat([torch.sin(out), torch.cos(out)], dim=-1)
t_pos = torch.arange(grid_t).repeat_interleave(grid_f)
f_pos = torch.arange(grid_f).repeat(grid_t)
return torch.cat(
[axis_embed(t_pos, dim // 2), axis_embed(f_pos, dim // 2)], dim=-1
)
class PatchEmbed(nn.Module):
def __init__(self, in_channels: int, hidden_size: int, patch_size: int):
super().__init__()
self.patch_size = patch_size
self.proj = nn.Conv2d(
in_channels, hidden_size, kernel_size=patch_size, stride=patch_size
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.proj(x)
return x.flatten(2).transpose(1, 2)
class SelfAttention(nn.Module):
def __init__(self, dim: int, num_heads: int):
super().__init__()
assert dim % num_heads == 0
self.num_heads = num_heads
self.head_dim = dim // num_heads
self.qkv = nn.Linear(dim, dim * 3)
self.out = nn.Linear(dim, dim)
def forward(self, x: torch.Tensor) -> torch.Tensor:
B, N, D = x.shape
qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim)
q, k, v = qkv.permute(2, 0, 3, 1, 4)
x = F.scaled_dot_product_attention(q, k, v)
return self.out(x.transpose(1, 2).reshape(B, N, D))
class CrossAttention(nn.Module):
def __init__(self, dim: int, num_heads: int):
super().__init__()
assert dim % num_heads == 0
self.num_heads = num_heads
self.head_dim = dim // num_heads
self.q = nn.Linear(dim, dim)
self.kv = nn.Linear(dim, dim * 2)
self.out = nn.Linear(dim, dim)
def forward(
self,
x: torch.Tensor,
ctx: torch.Tensor,
ctx_mask: Optional[torch.Tensor],
) -> torch.Tensor:
B, N, D = x.shape
L = ctx.shape[1]
q = self.q(x).reshape(B, N, self.num_heads, self.head_dim).transpose(1, 2)
kv = self.kv(ctx).reshape(B, L, 2, self.num_heads, self.head_dim)
k, v = kv.permute(2, 0, 3, 1, 4)
attn_mask = None
if ctx_mask is not None:
attn_mask = torch.where(ctx_mask.bool(), 0.0, float("-inf"))
attn_mask = attn_mask[:, None, None, :].to(q.dtype)
x = F.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask)
return self.out(x.transpose(1, 2).reshape(B, N, D))
class MLP(nn.Module):
def __init__(self, dim: int, hidden: int):
super().__init__()
self.fc1 = nn.Linear(dim, hidden)
self.fc2 = nn.Linear(hidden, dim)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.fc2(F.gelu(self.fc1(x), approximate="tanh"))
def modulate(x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
class DiTBlock(nn.Module):
def __init__(self, dim: int, num_heads: int, mlp_ratio: float):
super().__init__()
self.norm1 = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
self.attn = SelfAttention(dim, num_heads)
self.norm_ctx = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
self.cross = CrossAttention(dim, num_heads)
self.norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
self.mlp = MLP(dim, int(dim * mlp_ratio))
self.adaLN = nn.Sequential(nn.SiLU(), nn.Linear(dim, 6 * dim))
nn.init.zeros_(self.adaLN[-1].weight)
nn.init.zeros_(self.adaLN[-1].bias)
nn.init.zeros_(self.cross.out.weight)
nn.init.zeros_(self.cross.out.bias)
def forward(
self,
x: torch.Tensor,
cond: torch.Tensor,
ctx: torch.Tensor,
ctx_mask: Optional[torch.Tensor],
) -> torch.Tensor:
(
shift_sa,
scale_sa,
gate_sa,
shift_mlp,
scale_mlp,
gate_mlp,
) = self.adaLN(cond).chunk(6, dim=-1)
x = x + gate_sa.unsqueeze(1) * self.attn(
modulate(self.norm1(x), shift_sa, scale_sa)
)
x = x + self.cross(self.norm_ctx(x), ctx, ctx_mask)
x = x + gate_mlp.unsqueeze(1) * self.mlp(
modulate(self.norm2(x), shift_mlp, scale_mlp)
)
return x
class FinalLayer(nn.Module):
def __init__(self, dim: int, patch_size: int, out_channels: int):
super().__init__()
self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
self.linear = nn.Linear(dim, patch_size * patch_size * out_channels)
self.adaLN = nn.Sequential(nn.SiLU(), nn.Linear(dim, 2 * dim))
nn.init.zeros_(self.adaLN[-1].weight)
nn.init.zeros_(self.adaLN[-1].bias)
nn.init.zeros_(self.linear.weight)
nn.init.zeros_(self.linear.bias)
def forward(self, x: torch.Tensor, cond: torch.Tensor) -> torch.Tensor:
shift, scale = self.adaLN(cond).chunk(2, dim=-1)
return self.linear(modulate(self.norm(x), shift, scale))
class DiT(nn.Module):
"""Text-conditioned Diffusion Transformer over VAE mel-latents."""
def __init__(
self,
latent_channels: int,
latent_time: int,
latent_freq: int,
patch_size: int,
hidden_size: int,
depth: int,
num_heads: int,
mlp_ratio: float,
text_dim: int,
repa_layer: int,
):
super().__init__()
assert latent_time % patch_size == 0 and latent_freq % patch_size == 0
self.out_channels = latent_channels
self.hidden_size = hidden_size
self.patch_size = patch_size
self.grid_t = latent_time // patch_size
self.grid_f = latent_freq // patch_size
self.num_tokens = self.grid_t * self.grid_f
self.repa_layer = repa_layer
self.patch_embed = PatchEmbed(latent_channels, hidden_size, patch_size)
# Built on first use rather than registered as a buffer: from_pretrained
# materializes the model on the meta device, so a derived buffer that no
# checkpoint supplies would silently load as zeros and corrupt every
# sample. Recomputing it is exact, cheap and cached per device/dtype.
self._pos_embed_cache: Optional[torch.Tensor] = None
self.t_embed = TimestepEmbedder(hidden_size)
self.text_proj = nn.Sequential(
nn.LayerNorm(text_dim),
nn.Linear(text_dim, hidden_size),
)
self.pooled_proj = nn.Sequential(
nn.SiLU(),
nn.Linear(hidden_size, hidden_size),
)
self.null_text = nn.Parameter(torch.zeros(1, 1, hidden_size))
self.blocks = nn.ModuleList(
DiTBlock(hidden_size, num_heads, mlp_ratio) for _ in range(depth)
)
self.final = FinalLayer(hidden_size, patch_size, self.out_channels)
self._init_weights()
def _init_weights(self):
def basic(m):
if isinstance(m, nn.Linear):
nn.init.xavier_uniform_(m.weight)
if m.bias is not None:
nn.init.zeros_(m.bias)
self.apply(basic)
w = self.patch_embed.proj.weight
nn.init.xavier_uniform_(w.view(w.shape[0], -1))
nn.init.zeros_(self.patch_embed.proj.bias)
nn.init.normal_(self.t_embed.mlp[0].weight, std=0.02)
nn.init.normal_(self.t_embed.mlp[2].weight, std=0.02)
for block in self.blocks:
nn.init.zeros_(block.adaLN[-1].weight)
nn.init.zeros_(block.adaLN[-1].bias)
nn.init.zeros_(block.cross.out.weight)
nn.init.zeros_(block.cross.out.bias)
nn.init.zeros_(self.final.adaLN[-1].weight)
nn.init.zeros_(self.final.adaLN[-1].bias)
nn.init.zeros_(self.final.linear.weight)
nn.init.zeros_(self.final.linear.bias)
def pos_embed(self, device: torch.device, dtype: torch.dtype) -> torch.Tensor:
cache = self._pos_embed_cache
if cache is None or cache.device != device or cache.dtype != dtype:
cache = build_2d_sincos_pos_embed(
self.hidden_size, self.grid_t, self.grid_f
)[None].to(device=device, dtype=dtype)
self._pos_embed_cache = cache
return cache
def null_context(self, batch_size: int):
ctx = self.null_text.expand(batch_size, -1, -1)
mask = torch.ones(batch_size, 1, device=ctx.device, dtype=torch.long)
return ctx, mask
def unpatchify(self, x: torch.Tensor) -> torch.Tensor:
B = x.shape[0]
p, c = self.patch_size, self.out_channels
x = x.reshape(B, self.grid_t, self.grid_f, p, p, c)
x = torch.einsum("btfpqc->bctpfq", x)
return x.reshape(B, c, self.grid_t * p, self.grid_f * p)
def forward(
self,
z_t: torch.Tensor,
t: torch.Tensor,
text_emb: Optional[torch.Tensor],
text_mask: Optional[torch.Tensor],
drop_mask: Optional[torch.Tensor] = None,
return_repa_hidden: bool = False,
):
B = z_t.shape[0]
x = self.patch_embed(z_t)
x = x + self.pos_embed(x.device, x.dtype)
if text_emb is None:
ctx, ctx_mask = self.null_context(B)
else:
ctx = self.text_proj(text_emb)
ctx_mask = text_mask
if drop_mask is not None:
null = self.null_text.expand(B, ctx.shape[1], -1)
ctx = torch.where(drop_mask[:, None, None], null, ctx)
null_mask = torch.zeros_like(ctx_mask)
null_mask[:, 0] = 1
ctx_mask = torch.where(drop_mask[:, None], null_mask, ctx_mask)
cond = self.t_embed(t)
if ctx_mask is not None:
denom = ctx_mask.sum(dim=1, keepdim=True).clamp(min=1)
pooled = (ctx * ctx_mask.unsqueeze(-1)).sum(dim=1) / denom
else:
pooled = ctx.mean(dim=1)
cond = cond + self.pooled_proj(pooled)
repa_hidden = None
for i, block in enumerate(self.blocks):
x = block(x, cond, ctx, ctx_mask)
if return_repa_hidden and i == self.repa_layer:
repa_hidden = x
out = self.unpatchify(self.final(x, cond))
if return_repa_hidden:
return out, repa_hidden
return out
# --------------------------------------------------------------------------- #
# Diffusion schedule + DDIM #
# --------------------------------------------------------------------------- #
class DiffusionScheduler:
"""Cosine alpha-bar schedule with v-prediction DDIM + CFG."""
def __init__(
self,
num_train_steps: int = 1000,
schedule: str = "cosine",
logit_normal_mean: float = 0.0,
logit_normal_std: float = 1.0,
):
self.T = num_train_steps
self.ln_mean = logit_normal_mean
self.ln_std = logit_normal_std
self.schedule = schedule
if schedule != "cosine":
raise ValueError(f"unknown schedule: {schedule}")
# May be created on the meta device under HF's init_empty_weights();
# materialize_real() / to() rebuilds a real CPU/CUDA table before use.
self.alpha_bar = self._build_alpha_bar(self.T)
@staticmethod
def _build_alpha_bar(num_train_steps: int, device=None) -> torch.Tensor:
device = torch.device(device) if device is not None else torch.device("cpu")
# Force a concrete device — never allocate on "meta".
if device.type == "meta":
device = torch.device("cpu")
s = 0.008
steps = torch.arange(
num_train_steps + 1, dtype=torch.float64, device=device
)
f = torch.cos((steps / num_train_steps + s) / (1 + s) * math.pi / 2) ** 2
abar = (f / f[0]).clamp(1e-5, 1.0)
return abar[1:].float()
def _is_meta(self) -> bool:
t = self.alpha_bar
return bool(getattr(t, "is_meta", False) or t.device.type == "meta")
def materialize_real(self, device=None) -> "DiffusionScheduler":
"""Rebuild alpha_bar if it was left on the meta device by from_pretrained."""
target = torch.device(device) if device is not None else torch.device("cpu")
if target.type == "meta":
target = torch.device("cpu")
if self._is_meta() or self.alpha_bar.device != target:
self.alpha_bar = self._build_alpha_bar(self.T, device="cpu").to(target)
return self
def to(self, device) -> "DiffusionScheduler":
return self.materialize_real(device)
def _gather(self, t: torch.Tensor):
if self._is_meta():
self.materialize_real(t.device)
abar = self.alpha_bar.to(t.device)[t]
return abar.sqrt().view(-1, 1, 1, 1), (1 - abar).sqrt().view(-1, 1, 1, 1)
def z0_from_v(self, z_t, t, v):
sqrt_abar, sqrt_1m = self._gather(t)
return sqrt_abar * z_t - sqrt_1m * v
def eps_from_v(self, z_t, t, v):
sqrt_abar, sqrt_1m = self._gather(t)
return sqrt_1m * z_t + sqrt_abar * v
@torch.no_grad()
def ddim_sample(
self,
model: nn.Module,
shape: tuple,
text_emb: torch.Tensor,
text_mask: torch.Tensor,
num_steps: int = 50,
guidance_scale: float = 4.0,
eta: float = 0.0,
device: Union[str, torch.device] = "cpu",
generator: Optional[torch.Generator] = None,
dtype: Optional[torch.dtype] = None,
) -> torch.Tensor:
self.materialize_real(device)
B = shape[0]
z = torch.randn(shape, device=device, generator=generator)
times = torch.linspace(self.T - 1, 0, num_steps, device=device).long()
use_cfg = guidance_scale is not None and guidance_scale > 1.0
for i in range(num_steps):
t = times[i].expand(B)
# Keep the schedule arithmetic in float32 even when the backbone
# runs in half precision: the DDIM update is sensitive to it.
z_in = z.to(dtype) if dtype is not None else z
if use_cfg:
v_cond = model(z_in, t.float(), text_emb, text_mask)
v_uncond = model(z_in, t.float(), None, None)
v = v_uncond + guidance_scale * (v_cond - v_uncond)
else:
v = model(z_in, t.float(), text_emb, text_mask)
v = v.float()
z0_hat = self.z0_from_v(z, t, v)
eps_hat = self.eps_from_v(z, t, v)
if i == num_steps - 1:
z = z0_hat
break
t_next = times[i + 1].expand(B)
abar_next = self.alpha_bar[t_next].view(-1, 1, 1, 1)
abar_now = self.alpha_bar[t].view(-1, 1, 1, 1)
sigma = eta * torch.sqrt(
(1 - abar_next) / (1 - abar_now) * (1 - abar_now / abar_next)
)
noise = (
torch.randn(shape, device=device, generator=generator)
if eta > 0
else torch.zeros_like(z)
)
dir_zt = torch.sqrt((1 - abar_next - sigma ** 2).clamp(min=0.0)) * eps_hat
z = abar_next.sqrt() * z0_hat + dir_zt + sigma * noise
return z
# --------------------------------------------------------------------------- #
# HF model outputs #
# --------------------------------------------------------------------------- #
@dataclass
class QaDiTOutput(ModelOutput):
"""Output of :meth:`QaDiTModel.forward` (single denoising step)."""
sample: torch.FloatTensor = None
@dataclass
class QaDiTGeneratorOutput(ModelOutput):
"""Output of :meth:`QaDiTModel.generate`.
Hugging Face ``ModelOutput`` requires every field after the first to default
to ``None`` (not other sentinels like ``16000``).
"""
audios: Optional[List[np.ndarray]] = None
audio_values: Optional[torch.FloatTensor] = None
latents: Optional[torch.FloatTensor] = None
sampling_rate: Optional[int] = None
# --------------------------------------------------------------------------- #
# PreTrainedModel #
# --------------------------------------------------------------------------- #
class QaDiTModel(PreTrainedModel):
"""QaDiT: latent Diffusion Transformer for text-to-audio (~160M).
Load with::
model = AutoModel.from_pretrained("USER/qadit", trust_remote_code=True)
Then::
out = model.generate("A dog barks while birds chirp in the distance")
# out.audios[0] -> np.ndarray, shape [num_samples], float32
"""
config_class = QaDiTConfig
base_model_prefix = "transformer"
main_input_name = "latents"
supports_gradient_checkpointing = False
_no_split_modules = ["DiTBlock"]
def __init__(self, config: QaDiTConfig):
super().__init__(config)
self.config = config
self.transformer = DiT(
latent_channels=config.latent_channels,
latent_time=config.latent_time,
latent_freq=config.latent_freq,
patch_size=config.patch_size,
hidden_size=config.hidden_size,
depth=config.depth,
num_heads=config.num_heads,
mlp_ratio=config.mlp_ratio,
text_dim=config.text_dim,
repa_layer=config.repa_layer,
)
self.scheduler = DiffusionScheduler(
num_train_steps=config.num_train_timesteps,
schedule=config.schedule,
logit_normal_mean=config.logit_normal_mean,
logit_normal_std=config.logit_normal_std,
)
# Lazily populated by prepare_auxiliaries() / generate()
self.tokenizer = None
self.text_encoder = None
self.vae = None
self.vocoder = None
self._aux_loaded = False
self.post_init()
# ------------------------------------------------------------------ #
# Core forward (one denoising step) #
# ------------------------------------------------------------------ #
def forward(
self,
latents: torch.FloatTensor,
timesteps: torch.FloatTensor,
encoder_hidden_states: Optional[torch.FloatTensor] = None,
encoder_attention_mask: Optional[torch.Tensor] = None,
return_dict: bool = True,
):
"""Predict v for noisy ``latents`` at ``timesteps``.
Parameters
----------
latents:
``[B, C, T, F]`` noisy latents in *scaled* training space.
timesteps:
``[B]`` diffusion timesteps (float).
encoder_hidden_states:
T5 hidden states ``[B, L, text_dim]``, or ``None`` for unconditional.
encoder_attention_mask:
``[B, L]`` with 1 = real token.
"""
sample = self.transformer(
latents,
timesteps,
encoder_hidden_states,
encoder_attention_mask,
)
if not return_dict:
return (sample,)
return QaDiTOutput(sample=sample)
# ------------------------------------------------------------------ #
# Auxiliaries (T5 / VAE / vocoder) #
# ------------------------------------------------------------------ #
def prepare_auxiliaries(self, device: Optional[torch.device] = None):
"""Load frozen T5, AudioLDM VAE and HiFi-GAN if not already loaded."""
if self._aux_loaded:
return self
device = device or self.device
cfg = self.config
from transformers import AutoTokenizer, SpeechT5HifiGan, T5EncoderModel
try:
from diffusers import AutoencoderKL
except ImportError as exc:
raise ImportError(
"diffusers is required for QaDiT waveform generation. "
"Install with: pip install diffusers"
) from exc
logger.info("Loading text encoder %s", cfg.text_model)
self.tokenizer = AutoTokenizer.from_pretrained(cfg.text_model)
self.text_encoder = (
T5EncoderModel.from_pretrained(cfg.text_model).to(device).eval()
)
for p in self.text_encoder.parameters():
p.requires_grad_(False)
logger.info("Loading VAE %s/%s", cfg.vae_model, cfg.vae_subfolder)
self.vae = (
AutoencoderKL.from_pretrained(cfg.vae_model, subfolder=cfg.vae_subfolder)
.to(device)
.eval()
)
for p in self.vae.parameters():
p.requires_grad_(False)
logger.info(
"Loading vocoder %s/%s", cfg.vocoder_model, cfg.vocoder_subfolder
)
self.vocoder = (
SpeechT5HifiGan.from_pretrained(
cfg.vocoder_model, subfolder=cfg.vocoder_subfolder
)
.to(device)
.eval()
)
for p in self.vocoder.parameters():
p.requires_grad_(False)
self._aux_loaded = True
return self
def encode_prompt(
self,
prompt: Union[str, List[str]],
device: Optional[torch.device] = None,
):
"""Tokenize + T5-encode captions → ``(text_emb, text_mask)``."""
if not self._aux_loaded:
self.prepare_auxiliaries(device)
device = device or self.device
if isinstance(prompt, str):
prompt = [prompt]
tok = self.tokenizer(
prompt,
padding="max_length",
truncation=True,
max_length=self.config.text_max_length,
return_tensors="pt",
)
input_ids = tok.input_ids.to(device)
attention_mask = tok.attention_mask.to(device)
with torch.no_grad():
text_emb = self.text_encoder(
input_ids=input_ids, attention_mask=attention_mask
).last_hidden_state
return text_emb, attention_mask
# ------------------------------------------------------------------ #
# Generation #
# ------------------------------------------------------------------ #
@torch.no_grad()
def generate(
self,
prompt: Optional[Union[str, List[str]]] = None,
encoder_hidden_states: Optional[torch.FloatTensor] = None,
encoder_attention_mask: Optional[torch.Tensor] = None,
num_inference_steps: Optional[int] = None,
guidance_scale: Optional[float] = None,
seed: Optional[int] = 0,
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
eta: float = 0.0,
output_type: str = "np",
return_dict: bool = True,
**kwargs,
):
"""Generate audio from text prompts.
Parameters
----------
prompt:
Caption string or list of captions. Ignored if
``encoder_hidden_states`` is provided.
num_inference_steps:
DDIM steps (default from config).
guidance_scale:
Classifier-free guidance scale (default from config).
seed:
Random seed used when ``generator`` is not supplied. Defaults to
0, matching the original ``audio_dit/sample.py`` CLI.
output_type:
``"np"`` → numpy waveforms, ``"pt"`` → torch waveforms,
``"latent"`` → scaled latents only (no VAE/vocoder).
"""
cfg = self.config
device = self.device
# from_pretrained() normally returns eval mode, but make generation
# invariant to callers having toggled train() in the same process.
self.eval()
steps = num_inference_steps or cfg.num_inference_steps
guidance = (
guidance_scale if guidance_scale is not None else cfg.guidance_scale
)
if cfg.latent_scale <= 0:
raise ValueError(
f"config.latent_scale must be positive, got {cfg.latent_scale}"
)
if encoder_hidden_states is None:
if prompt is None:
raise ValueError("Provide `prompt` or `encoder_hidden_states`")
if cfg.load_auxiliaries or output_type != "latent":
self.prepare_auxiliaries(device)
encoder_hidden_states, encoder_attention_mask = self.encode_prompt(
prompt, device=device
)
else:
encoder_hidden_states = encoder_hidden_states.to(device)
if encoder_attention_mask is not None:
encoder_attention_mask = encoder_attention_mask.to(device)
if isinstance(prompt, str):
batch = 1
elif isinstance(prompt, list):
batch = len(prompt)
else:
batch = encoder_hidden_states.shape[0]
# silence unused
_ = batch
B = encoder_hidden_states.shape[0]
shape = (
B,
cfg.latent_channels,
cfg.latent_time,
cfg.latent_freq,
)
if generator is None and seed is not None:
generator = torch.Generator(device=device.type).manual_seed(seed)
if isinstance(generator, list):
if len(generator) != B:
raise ValueError(
f"Got {len(generator)} generators for batch size {B}"
)
# Fall back to first generator for the shared noise draw; per-sample
# generators are uncommon for this model.
generator = generator[0]
self.scheduler.to(device)
latents = self.scheduler.ddim_sample(
model=self.transformer,
shape=shape,
text_emb=encoder_hidden_states.to(self.dtype),
text_mask=encoder_attention_mask,
num_steps=steps,
guidance_scale=guidance,
eta=eta,
device=device,
generator=generator,
dtype=self.dtype,
)
if output_type == "latent":
if not return_dict:
return (latents,)
return QaDiTGeneratorOutput(
latents=latents, sampling_rate=cfg.sample_rate
)
if not self._aux_loaded:
self.prepare_auxiliaries(device)
# Undo training latent scale, then VAE decode → mel → waveform.
z = (latents / cfg.latent_scale).to(self.vae.dtype)
mel = self.vae.decode(z).sample # [B, 1, 1024, 64]
wav = self.vocoder(mel.squeeze(1).to(self.vocoder.dtype)) # [B, num_samples]
wav = wav.float().clamp(-1, 1)
if output_type == "pt":
if not return_dict:
return (wav, latents)
return QaDiTGeneratorOutput(
audio_values=wav,
latents=latents,
sampling_rate=cfg.sample_rate,
)
# default: numpy
audios = [w.detach().cpu().float().numpy() for w in wav]
if not return_dict:
return (audios, latents)
return QaDiTGeneratorOutput(
audios=audios,
latents=latents,
sampling_rate=cfg.sample_rate,
)
def _set_gradient_checkpointing(self, module, value=False):
pass
# Register for Auto* when used as a local package / after from_pretrained
try:
QaDiTConfig.register_for_auto_class()
QaDiTModel.register_for_auto_class("AutoModel")
except Exception:
# Older transformers or already-registered; auto_map in config.json still works.
pass
__all__ = [
"DiT",
"DiffusionScheduler",
"QaDiTModel",
"QaDiTOutput",
"QaDiTGeneratorOutput",
]