[Model] Add Ming-omni-tts dense 0.5B pipeline (#2906)

Signed-off-by: akshatvishu <akshatnayak197@gmail.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
akshatvishuandHongsheng Liu authored and GitHub committed 2026-06-10 20:11:04 +08:00
1 parent 39ae6e12f8
commit 2fa4c7a4a9
55 files changed
+6263 -549

No files matched your search

@@ -0,0 +1,2 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
@@ -0,0 +1,209 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# Adopted from https://github.com/inclusionAI/Ming-omni-tts/blob/main/audio_tokenizer/istft.py
import torch
import torch.nn as nn
class ISTFT(nn.Module):
"""
Custom implementation of ISTFT since torch.istft doesn't allow custom padding (other than `center=True`) with
windowing. This is because the NOLA (Nonzero Overlap Add) check fails at the edges.
See issue: https://github.com/pytorch/pytorch/issues/62323
Specifically, in the context of neural vocoding we are interested in "same" padding analogous to CNNs.
The NOLA constraint is met as we trim padded samples anyway.
Args:
n_fft (int): Size of Fourier transform.
hop_length (int): The distance between neighboring sliding window frames.
win_length (int): The size of window frame and STFT filter.
padding (str, optional): Type of padding. Options are "center" or "same". Defaults to "same".
"""
def __init__(self, n_fft: int, hop_length: int, win_length: int, padding: str = "same"):
super().__init__()
if padding not in ["center", "same"]:
raise ValueError("Padding must be 'center' or 'same'.")
self.padding = padding
self.n_fft = n_fft
self.hop_length = hop_length
self.win_length = win_length
window = torch.hann_window(win_length)
self.register_buffer("window", window)
self.buffer_len = self.win_length - self.hop_length
def _buffer_process(
self,
x: torch.Tensor,
buffer: torch.Tensor | None,
pad: int,
last_chunk: bool = False,
streaming: bool = False,
) -> tuple[torch.Tensor, torch.Tensor | None]:
if streaming:
if buffer is None:
# first chunk
x = x[:, pad:]
if buffer is not None:
# next chunk
x[:, : self.buffer_len] += buffer
buffer = x[:, -self.buffer_len :]
if not last_chunk:
x = x[:, : -self.buffer_len]
else:
x = x[:, :-pad]
else:
x = x[:, pad:-pad]
return x, buffer
def forward(
self,
spec: torch.Tensor,
audio_buffer: torch.Tensor | None = None,
window_buffer: torch.Tensor | None = None,
streaming: bool = False,
last_chunk: bool = False,
) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None]:
"""
Compute the Inverse Short Time Fourier Transform (ISTFT) of a complex spectrogram.
Args:
spec (Tensor): Input complex spectrogram of shape (B, N, T), where B is the batch size,
N is the number of frequency bins, and T is the number of time frames.
audio_buffer (Tensor): [Streaming Input/State] The audio overlap buffer from the previous chunk.
Shape: (B, win_length - hop_length)
window_buffer (Tensor): [Streaming Input/State] The window overlap buffer from the previous chunk.
streaming: If `True`, the function operates in streaming mode, processing `spec` as a single chunk.
last_chunk: When `streaming=True` and `last_chunk=True`, the function can perform final "flush" operations
Returns:
Reconstructed signal, plus streaming buffers when `padding="same"`.
"""
if self.padding == "center":
# Fallback to pytorch native implementation
return torch.istft(spec, self.n_fft, self.hop_length, self.win_length, self.window, center=True)
elif self.padding == "same":
pad = (self.win_length - self.hop_length) // 2
else:
raise ValueError("Padding must be 'center' or 'same'.")
if spec.dim() != 3:
raise ValueError(f"Expected spec rank-3 [Batch, Freq, Time], got {tuple(spec.shape)}")
_, _, T = spec.shape
# Inverse FFT
ifft = torch.fft.irfft(spec, self.n_fft, dim=1, norm="backward")
ifft = ifft * self.window[None, :, None]
# Overlap and Add
output_size = (T - 1) * self.hop_length + self.win_length
y = torch.nn.functional.fold(
ifft,
output_size=(1, output_size),
kernel_size=(1, self.win_length),
stride=(1, self.hop_length),
)[:, 0, 0, :]
y, audio_buffer = self._buffer_process(y, audio_buffer, pad, last_chunk=last_chunk, streaming=streaming)
# Window envelope
window_sq = self.window.square().expand(1, T, -1).transpose(1, 2)
window_envelope = (
torch.nn.functional.fold(
window_sq,
output_size=(1, output_size),
kernel_size=(1, self.win_length),
stride=(1, self.hop_length),
)
.squeeze(0)
.squeeze(0)
)
window_envelope, window_buffer = self._buffer_process(
window_envelope, window_buffer, pad, last_chunk=last_chunk, streaming=streaming
)
window_envelope = window_envelope.squeeze()
# Normalize
if not (window_envelope > 1e-11).all():
raise RuntimeError("ISTFT window envelope underflowed; invalid overlap-add state.")
y = y / window_envelope
return y, audio_buffer, window_buffer
class FourierHead(nn.Module):
"""Base class for inverse fourier modules."""
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Args:
x (Tensor): Input tensor of shape (B, L, H), where B is the batch size,
L is the sequence length, and H denotes the model dimension.
Returns:
Tensor: Reconstructed time-domain audio signal of shape (B, T), where T is the length of the output signal.
"""
raise NotImplementedError("Subclasses must implement the forward method.")
class ISTFTHead(FourierHead):
"""
ISTFT Head module for predicting STFT complex coefficients.
Args:
dim (int): Hidden dimension of the model.
n_fft (int): Size of Fourier transform.
hop_length (int): The distance between neighboring sliding window frames, which should align with
the resolution of the input features.
padding (str, optional): Type of padding. Options are "center" or "same". Defaults to "same".
"""
def __init__(self, dim: int, n_fft: int, hop_length: int, padding: str = "same"):
super().__init__()
out_dim = n_fft + 2
self.out = torch.nn.Linear(dim, out_dim)
self.istft = ISTFT(n_fft=n_fft, hop_length=hop_length, win_length=n_fft, padding=padding)
def forward(
self,
x: torch.Tensor,
audio_buffer: torch.Tensor | None = None,
window_buffer: torch.Tensor | None = None,
streaming: bool = False,
last_chunk: bool = False,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None, torch.Tensor | None]:
"""
Forward pass of the ISTFTHead module.
Args:
x (Tensor): Input tensor of shape (B, L, H), where B is the batch size,
L is the sequence length, and H denotes the model dimension.
Returns:
Audio, predicted spectrogram coefficients, and streaming buffers.
"""
x_pred = self.out(x)
x_pred = x_pred.transpose(1, 2)
mag, p = x_pred.chunk(2, dim=1)
mag = torch.exp(mag)
mag = torch.clip(mag, max=1e2) # safeguard to prevent excessively large magnitudes
# wrapping happens here. These two lines produce real and imaginary value
x = torch.cos(p)
y = torch.sin(p)
# recalculating phase here does not produce anything new
# only costs time
# phase = torch.atan2(y, x)
# S = mag * torch.exp(phase * 1j)
# better directly produce the complex value
S = mag * (x + 1j * y)
audio, audio_buffer, window_buffer = self.istft(
S, audio_buffer=audio_buffer, window_buffer=window_buffer, streaming=streaming, last_chunk=last_chunk
)
return audio.unsqueeze(1), x_pred, audio_buffer, window_buffer
__all__ = ["FourierHead", "ISTFT", "ISTFTHead"]
@@ -1,10 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2025 The vLLM-Omni team.
# Copyright (c) Ant Group. All rights reserved.
# Adapted from:
# https://github.com/inclusionAI/Ming/tree/e58533db227031990c5a6864dcf5f08fb53ed0d2/AudioVAE
from __future__ import annotations
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import torch
import torch.nn as nn
@@ -13,6 +8,8 @@ from transformers import PretrainedConfig, PreTrainedModel, Qwen2Config, Qwen2Mo
from transformers.utils import is_flash_attn_2_available
from vllm.logger import init_logger
from .audio_dsp import ISTFTHead
logger = init_logger(__name__)
@@ -21,114 +18,50 @@ class AudioVAEConfig(PretrainedConfig):
def __init__(
self,
sample_rate: int = 44100,
enc_kwargs: dict | None = None,
dec_kwargs: dict | None = None,
init_method: str = "kaiming",
patch_size: int = 4,
sample_rate=44100,
enc_kwargs=None,
semantic_module_kwargs=None,
dec_kwargs=None,
hifi_gan_disc_kwargs=None,
spec_disc_kwargs=None,
lambda_disc=1.0,
lambda_mel_loss=15,
lambda_adv=1.0,
lambda_feat_match_loss=1.0,
lambda_semantic=5.0,
init_method="kaiming",
patch_size=4,
**kwargs,
):
self.sample_rate = sample_rate
self.enc_kwargs = enc_kwargs or {}
self.semantic_module_kwargs = semantic_module_kwargs
self.dec_kwargs = dec_kwargs or {}
self.hifi_gan_disc_kwargs = hifi_gan_disc_kwargs
self.spec_disc_kwargs = spec_disc_kwargs
self.lambda_disc = lambda_disc
self.lambda_mel_loss = lambda_mel_loss
self.lambda_adv = lambda_adv
self.lambda_feat_match_loss = lambda_feat_match_loss
self.lambda_semantic = lambda_semantic
self.init_method = init_method
self.patch_size = patch_size
super().__init__(**kwargs)
class ISTFT(nn.Module):
def __init__(self, n_fft: int, hop_length: int, win_length: int, padding: str = "same"):
super().__init__()
if padding not in ["center", "same"]:
raise ValueError("Padding must be 'center' or 'same'.")
self.padding = padding
self.n_fft = n_fft
self.hop_length = hop_length
self.win_length = win_length
window = torch.hann_window(win_length)
self.register_buffer("window", window)
self.buffer_len = self.win_length - self.hop_length
def _buffer_process(self, x, buffer, pad, last_chunk=False, streaming=False):
if streaming:
if buffer is None:
x = x[:, pad:]
if buffer is not None:
x[:, : self.buffer_len] += buffer
buffer = x[:, -self.buffer_len :]
if not last_chunk:
x = x[:, : -self.buffer_len]
else:
x = x[:, :-pad]
else:
x = x[:, pad:-pad]
return x, buffer
def forward(self, spec, audio_buffer=None, window_buffer=None, streaming=False, last_chunk=False):
if self.padding == "center":
return torch.istft(spec, self.n_fft, self.hop_length, self.win_length, self.window, center=True)
elif self.padding == "same":
pad = (self.win_length - self.hop_length) // 2
else:
raise ValueError("Padding must be 'center' or 'same'.")
B, N, T = spec.shape
ifft = torch.fft.irfft(spec, self.n_fft, dim=1, norm="backward")
ifft = ifft * self.window[None, :, None]
output_size = (T - 1) * self.hop_length + self.win_length
y = torch.nn.functional.fold(
ifft,
output_size=(1, output_size),
kernel_size=(1, self.win_length),
stride=(1, self.hop_length),
)[:, 0, 0, :]
y, audio_buffer = self._buffer_process(y, audio_buffer, pad, last_chunk=last_chunk, streaming=streaming)
window_sq = self.window.square().expand(1, T, -1).transpose(1, 2)
window_envelope = (
torch.nn.functional.fold(
window_sq,
output_size=(1, output_size),
kernel_size=(1, self.win_length),
stride=(1, self.hop_length),
)
.squeeze(0)
.squeeze(0)
)
window_envelope, window_buffer = self._buffer_process(
window_envelope, window_buffer, pad, last_chunk=last_chunk, streaming=streaming
)
window_envelope = window_envelope.squeeze()
assert (window_envelope > 1e-11).all()
y = y / window_envelope
return y, audio_buffer, window_buffer
class ISTFTHead(nn.Module):
def __init__(self, dim: int, n_fft: int, hop_length: int, padding: str = "same"):
super().__init__()
out_dim = n_fft + 2
self.out = nn.Linear(dim, out_dim)
self.istft = ISTFT(n_fft=n_fft, hop_length=hop_length, win_length=n_fft, padding=padding)
def forward(self, x, audio_buffer=None, window_buffer=None, streaming=False, last_chunk=False):
x_pred = self.out(x)
x_pred = x_pred.transpose(1, 2)
mag, p = x_pred.chunk(2, dim=1)
mag = torch.exp(mag)
mag = torch.clip(mag, max=1e2)
x = torch.cos(p)
y = torch.sin(p)
S = mag * (x + 1j * y)
audio, audio_buffer, window_buffer = self.istft(
S, audio_buffer=audio_buffer, window_buffer=window_buffer, streaming=streaming, last_chunk=last_chunk
)
return audio.unsqueeze(1), x_pred, audio_buffer, window_buffer
def _qwen2_config(backbone):
config = Qwen2Config.from_dict(config_dict=backbone)
if (getattr(config, "_attn_implementation", None) or getattr(config, "attn_implementation", None)) not in (
None,
"flash_attention_2",
):
return config
if is_flash_attn_2_available():
config._attn_implementation_autoset = True
config._attn_implementation = "flash_attention_2"
else:
config._attn_implementation = "sdpa"
return config
class StreamingLinearUpsample(nn.Module):
@@ -138,6 +71,8 @@ class StreamingLinearUpsample(nn.Module):
self.upsampler = nn.Upsample(scale_factor=scale_factor, mode="linear", align_corners=False)
def forward(self, x, state=None, is_last=False):
if x is None and is_last and (state is None or state.get("prev_chunk") is None):
raise ValueError("Received end-of-stream without any latent chunk to upsample.")
if state is None:
state = {"prev_chunk": None, "history_last": None, "is_first": True}
@@ -188,16 +123,72 @@ class StreamingLinearUpsample(nn.Module):
return final_out, state
class Encoder(nn.Module):
def __init__(self, encoder_args, input_dim=320, hop_size=320, latent_dim=64, patch_size=-1):
super().__init__()
config = _qwen2_config(encoder_args)
logger.info("AudioVAE Encoder: using attn_implementation=%r", config._attn_implementation)
self.encoder = Qwen2Model(config)
self.input_dim = input_dim
self.hop_size = hop_size
self.latent_dim = latent_dim
self.fc1 = nn.Linear(input_dim, config.hidden_size, bias=False)
self.fc2 = nn.Linear(config.hidden_size, config.hidden_size)
self.fc3 = nn.Linear(config.hidden_size, latent_dim * 2)
self.norm = nn.LayerNorm(config.hidden_size)
self.patch_size = patch_size
if patch_size != -1:
aggregator_config = _qwen2_config({**encoder_args, "num_hidden_layers": 4})
self.aggregator = Qwen2Model(aggregator_config)
self.cls_embed = nn.Parameter(torch.rand(1, 1, config.hidden_size))
self.cls_embed.data.normal_(0, 0.02)
def get_frames(self, x):
num_frames_total = (x.size(-1) + self.hop_size - 1) // self.hop_size
expected_len = (num_frames_total - 1) * self.hop_size + self.input_dim
padding_needed = expected_len - x.size(-1)
waveform = F.pad(x, (0, padding_needed), value=0.0)
frames = waveform.unfold(dimension=-1, size=self.input_dim, step=self.hop_size)
return frames
def pad_patch_insert_cls(self, x):
bsz, _, dim = x.size()
num_frame = x.size(1)
r = num_frame % self.patch_size
pad_num = self.patch_size - r if r else 0
x = F.pad(x, (0, 0, 0, pad_num), value=0.0)
# [Batch, Time, Dimension] -> [Batch*PatchGroups, Patch, Dimension].
x = x.reshape(-1, self.patch_size, dim)
x = torch.cat((x, self.cls_embed.expand(x.size(0), -1, -1)), dim=1)
# [Batch*PatchGroups, Patch+1, Dimension] -> [Batch, Time, Dimension].
x = x.reshape(bsz, -1, dim)
return x
def forward(self, waveform):
x = self.get_frames(waveform)
x = self.fc1(x)
x = self.fc2(x)
x = self.encoder(inputs_embeds=x)
x = x.last_hidden_state
if self.patch_size != -1:
x = self.pad_patch_insert_cls(x)
x = self.aggregator(inputs_embeds=x)
x = x.last_hidden_state
bsz, _, dim = x.size()
# [Batch, Time, Dimension] -> [Batch*PatchGroups, Patch+1, Dimension].
x = x.reshape(-1, self.patch_size + 1, dim)
# [Batch*PatchGroups, 1, Dimension] -> [Batch, PatchGroups, Dimension].
x = x[:, -1:, :].reshape(bsz, -1, dim)
x = self.fc3(x)
return x, waveform.unsqueeze(1)
class Decoder(nn.Module):
def __init__(self, decoder_args, output_dim=320, latent_dim=64, patch_size=-1):
super().__init__()
config = Qwen2Config.from_dict(config_dict=decoder_args)
if is_flash_attn_2_available():
config._attn_implementation_autoset = True
config._attn_implementation = "flash_attention_2"
else:
config._attn_implementation = "sdpa"
config = _qwen2_config(decoder_args)
logger.info("AudioVAE Decoder: using attn_implementation=%r", config._attn_implementation)
self.decoder = Qwen2Model(config)
self.output_dim = output_dim
@@ -211,7 +202,21 @@ class Decoder(nn.Module):
if self.patch_size != -1:
self.upsampling = StreamingLinearUpsample(scale_factor=patch_size)
def forward(self, x):
x = self.fc1(x)
if self.patch_size != -1:
# [Batch, Time, Dimension] -> [Batch, Dimension, Time] -> [Batch, Time, Dimension].
x = self.upsampling.upsampler(x.transpose(1, 2)).transpose(1, 2)
x = self.decoder(inputs_embeds=x)
x = x.last_hidden_state
x, _ = self.head(x)
return x, None
def low_level_reconstruct(self, x, past_key_values=None, use_cache=False, stream_state=None, last_chunk=False):
if stream_state is None:
stream_state = (None, None, None)
upsample_state, audio_buffer, window_buffer = stream_state
bsz, device, dtype = x.size(0), x.device, x.dtype
x = self.fc1(x)
@@ -222,6 +227,7 @@ class Decoder(nn.Module):
stream_state = (upsample_state, audio_buffer, window_buffer)
return torch.empty(bsz, 1, 0, device=device, dtype=dtype), stream_state, past_key_values
else:
# [Batch, Time, Dimension] -> [Batch, Dimension, Time] -> [Batch, Time, Dimension].
x = self.upsampling.upsampler(x.transpose(1, 2)).transpose(1, 2)
hidden_states_list = []
@@ -239,7 +245,6 @@ class Decoder(nn.Module):
past_len = 0
curr_len = x.shape[1]
if past_len < target_len and (past_len + curr_len) >= sw_size:
fill_len = target_len - past_len
x_fill = x[:, :fill_len, :]
@@ -269,86 +274,39 @@ class Decoder(nn.Module):
return x_out, stream_state, past_key_values
class Encoder(nn.Module):
def __init__(self, encoder_args, input_dim=320, hop_size=320, latent_dim=64, patch_size=-1):
super().__init__()
config = Qwen2Config.from_dict(config_dict=encoder_args)
if is_flash_attn_2_available():
config._attn_implementation_autoset = True
config._attn_implementation = "flash_attention_2"
else:
config._attn_implementation = "sdpa"
logger.info("AudioVAE Encoder: using attn_implementation=%r", config._attn_implementation)
self.encoder = Qwen2Model(config)
self.input_dim = input_dim
self.hop_size = hop_size
self.latent_dim = latent_dim
self.fc1 = nn.Linear(input_dim, config.hidden_size, bias=False)
self.fc2 = nn.Linear(config.hidden_size, config.hidden_size)
self.fc3 = nn.Linear(config.hidden_size, latent_dim * 2)
self.norm = nn.LayerNorm(config.hidden_size)
self.patch_size = patch_size
if patch_size != -1:
config.num_hidden_layers = 4
self.aggregator = Qwen2Model(config)
self.cls_embed = nn.Parameter(torch.rand(1, 1, config.hidden_size))
self.cls_embed.data.normal_(0, 0.02)
def get_frames(self, x):
num_frames_total = (x.size(-1) + self.hop_size - 1) // self.hop_size
expected_len = (num_frames_total - 1) * self.hop_size + self.input_dim
padding_needed = expected_len - x.size(-1)
waveform = F.pad(x, (0, padding_needed), value=0.0)
frames = waveform.unfold(dimension=-1, size=self.input_dim, step=self.hop_size)
return frames
def pad_patch_insert_cls(self, x):
bsz, _, dim = x.size()
num_frame = x.size(1)
r = num_frame % self.patch_size
pad_num = self.patch_size - r if r else 0
x = F.pad(x, (0, 0, 0, pad_num), value=0.0)
x = x.reshape(-1, self.patch_size, dim)
x = torch.cat((x, self.cls_embed.expand(x.size(0), -1, -1)), dim=1)
x = x.reshape(bsz, -1, dim)
return x
def forward(self, waveform):
x = self.get_frames(waveform)
x = self.fc1(x)
x = self.fc2(x)
x = self.encoder(inputs_embeds=x)
x = x.last_hidden_state
if self.patch_size != -1:
x = self.pad_patch_insert_cls(x)
x = self.aggregator(inputs_embeds=x)
x = x.last_hidden_state
bsz, _, dim = x.size()
x = x.reshape(-1, self.patch_size + 1, dim)
x = x[:, -1:, :].reshape(bsz, -1, dim)
x = self.fc3(x)
return x, waveform.unsqueeze(1)
class AudioVAE(PreTrainedModel):
config_class = AudioVAEConfig
def __init__(self, config: AudioVAEConfig):
def __init__(self, config):
super().__init__(config)
self.config = config
enc_kwargs = config.enc_kwargs
dec_kwargs = config.dec_kwargs
for key in ("backbone", "input_dim", "latent_dim"):
if key not in enc_kwargs:
raise ValueError(f"AudioVAE.enc_kwargs missing required key: {key}")
for key in ("backbone", "output_dim", "latent_dim"):
if key not in dec_kwargs:
raise ValueError(f"AudioVAE.dec_kwargs missing required key: {key}")
hop_size = enc_kwargs.get("hop_size", enc_kwargs["input_dim"])
if enc_kwargs["input_dim"] != hop_size:
raise ValueError(f"AudioVAE encoder input_dim ({enc_kwargs['input_dim']}) != hop_size ({hop_size}).")
if hop_size != dec_kwargs["output_dim"]:
raise ValueError(
f"AudioVAE encoder hop_size ({hop_size}) != decoder output_dim ({dec_kwargs['output_dim']})."
)
self.encoder = Encoder(
encoder_args=config.enc_kwargs["backbone"],
input_dim=config.enc_kwargs["input_dim"],
hop_size=config.enc_kwargs.get("hop_size", 320),
latent_dim=config.enc_kwargs["latent_dim"],
encoder_args=enc_kwargs["backbone"],
input_dim=enc_kwargs["input_dim"],
hop_size=hop_size,
latent_dim=enc_kwargs["latent_dim"],
patch_size=config.patch_size,
)
self.decoder = Decoder(
decoder_args=config.dec_kwargs["backbone"],
output_dim=config.dec_kwargs["output_dim"],
latent_dim=config.dec_kwargs["latent_dim"],
decoder_args=dec_kwargs["backbone"],
output_dim=dec_kwargs["output_dim"],
latent_dim=dec_kwargs["latent_dim"],
patch_size=config.patch_size,
)
self.post_init()
@@ -367,21 +325,50 @@ class AudioVAE(PreTrainedModel):
if module.padding_idx is not None:
module.weight.data[module.padding_idx].zero_()
@torch.inference_mode()
def encode_latent(self, waveform, waveform_length):
from diffusers.models.autoencoders.autoencoder_oobleck import OobleckDiagonalGaussianDistribution
if waveform.ndim != 2:
raise ValueError(f"Expected waveform rank-2 [Batch, Time], got {tuple(waveform.shape)}")
if waveform_length.ndim != 1:
raise ValueError(f"Expected waveform_length rank-1 [Batch], got {tuple(waveform_length.shape)}")
if waveform.shape[0] != waveform_length.shape[0]:
raise ValueError(
"Batch mismatch: "
f"waveform batch={waveform.shape[0]} vs "
f"waveform_length batch={waveform_length.shape[0]}"
)
if torch.any(waveform_length <= 0):
raise ValueError("waveform_length must be strictly positive.")
frame_num = torch.ceil(waveform_length / self.config.enc_kwargs["input_dim"]).to(torch.int32)
if self.config.patch_size != -1:
frame_num = torch.ceil(frame_num / self.config.patch_size)
h, y = self.encoder(waveform)
h, _ = self.encoder(waveform)
h = h.transpose(1, 2)
posterior = OobleckDiagonalGaussianDistribution(h)
latent = posterior.sample()
mean, logvar = torch.chunk(h, 2, dim=1)
logvar = torch.clamp(logvar, -30.0, 20.0)
std = torch.exp(0.5 * logvar)
latent = mean + std * torch.randn_like(mean)
latent = latent.transpose(1, 2)
return latent, frame_num
@torch.inference_mode()
def decode(self, latent, past_key_values=None, use_cache=False, stream_state=(None, None, None), last_chunk=False):
if latent.dim() != 3:
raise ValueError(f"Expected latent rank-3 [B,T,D], got shape={tuple(latent.shape)}")
if latent.shape[0] <= 0:
raise ValueError("latent batch size must be positive.")
target_dtype = next(self.decoder.parameters()).dtype
target_device = next(self.decoder.parameters()).device
if latent.dtype != target_dtype or latent.device != target_device:
latent = latent.to(device=target_device, dtype=target_dtype)
expected_latent_dim = self.config.dec_kwargs["latent_dim"]
if latent.shape[-1] != expected_latent_dim:
raise ValueError(f"Latent dim mismatch in decode(): got {latent.shape[-1]}, expected {expected_latent_dim}")
waveform, stream_state, past_key_values = self.decoder.low_level_reconstruct(
latent,
past_key_values=past_key_values,
@@ -0,0 +1,193 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import torch
import torch.nn.functional as F
from torch import nn
from x_transformers.x_transformers import apply_rotary_pos_emb
class RMSNorm(nn.Module):
def __init__(self, dim, eps=1e-6):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(dim))
def forward(self, x):
if self.weight.dtype in [torch.float16, torch.bfloat16]:
x = x.to(self.weight.dtype)
x = F.rms_norm(x, normalized_shape=(x.shape[-1],), weight=self.weight, eps=self.eps)
return x
class FeedForward(nn.Module):
def __init__(self, dim, dim_out=None, mult=4, dropout=0.0, approximate="none"):
super().__init__()
inner_dim = int(dim * mult)
dim_out = dim_out if dim_out is not None else dim
activation = nn.GELU(approximate=approximate)
project_in = nn.Sequential(nn.Linear(dim, inner_dim), activation)
self.ff = nn.Sequential(project_in, nn.Dropout(dropout), nn.Linear(inner_dim, dim_out))
def forward(self, x):
return self.ff(x)
class Attention(nn.Module):
def __init__(
self,
dim,
heads=8,
dim_head=64,
dropout=0.0,
qk_norm=None,
pe_attn_head=None,
attn_mask_enabled=True,
):
super().__init__()
self.dim = dim
self.heads = heads
self.inner_dim = dim_head * heads
self.dropout = dropout
self.to_q = nn.Linear(dim, self.inner_dim)
self.to_k = nn.Linear(dim, self.inner_dim)
self.to_v = nn.Linear(dim, self.inner_dim)
if qk_norm is None:
self.q_norm = None
self.k_norm = None
elif qk_norm == "rms_norm":
self.q_norm = RMSNorm(dim_head)
self.k_norm = RMSNorm(dim_head)
else:
raise ValueError(f"Unimplemented qk_norm: {qk_norm}")
self.to_out = nn.ModuleList([])
self.to_out.append(nn.Linear(self.inner_dim, dim))
self.to_out.append(nn.Dropout(dropout))
self.pe_attn_head = pe_attn_head
self.attn_mask_enabled = attn_mask_enabled
def forward(self, x, mask=None, rope=None):
batch_size = x.shape[0]
query = self.to_q(x)
key = self.to_k(x)
value = self.to_v(x)
inner_dim = key.shape[-1]
head_dim = 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 self.q_norm is not None:
query = self.q_norm(query)
if self.k_norm is not None:
key = self.k_norm(key)
if rope is not None:
freqs, xpos_scale = rope
q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
if self.pe_attn_head is not None:
on = self.pe_attn_head
query[:, :on, :, :] = apply_rotary_pos_emb(query[:, :on, :, :], freqs, q_xpos_scale)
key[:, :on, :, :] = apply_rotary_pos_emb(key[:, :on, :, :], freqs, k_xpos_scale)
else:
query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
if self.attn_mask_enabled and mask is not None:
valid_sample_indices = mask.any(dim=1)
final_output = torch.zeros_like(query).to(query.device)
attn_mask = mask[valid_sample_indices]
query = query[valid_sample_indices]
key = key[valid_sample_indices]
value = value[valid_sample_indices]
attn_mask = attn_mask.unsqueeze(1).unsqueeze(1)
attn_mask = attn_mask.expand(valid_sample_indices.sum().item(), self.heads, query.shape[-2], key.shape[-2])
else:
attn_mask = None
x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
if self.attn_mask_enabled and mask is not None:
final_output[valid_sample_indices] = x
x = final_output
x = x.transpose(1, 2).reshape(batch_size, -1, self.heads * head_dim)
x = x.to(query.dtype)
x = self.to_out[0](x)
x = self.to_out[1](x)
if mask is not None:
mask = mask.unsqueeze(-1)
x = x.masked_fill(~mask, 0.0)
return x
class DiTBlock(nn.Module):
def __init__(
self,
hidden_size,
num_heads,
mlp_ratio=4.0,
dropout=0.1,
qk_norm=None,
pe_attn_head=None,
attn_mask_enabled=True,
**kwargs,
):
super().__init__()
self.norm1 = RMSNorm(hidden_size)
self.attn = Attention(
dim=hidden_size,
heads=num_heads,
dim_head=hidden_size // num_heads,
dropout=dropout,
qk_norm=qk_norm,
pe_attn_head=pe_attn_head,
attn_mask_enabled=attn_mask_enabled,
)
self.norm2 = RMSNorm(hidden_size)
self.mlp = FeedForward(dim=hidden_size, mult=mlp_ratio, dropout=dropout, approximate="tanh")
def forward(self, x, mask, rope):
x = x + self.attn(self.norm1(x), mask=mask, rope=rope)
x = x + self.mlp(self.norm2(x))
return x
class FinalLayer(nn.Module):
def __init__(self, hidden_size, out_channels):
super().__init__()
self.norm_final = RMSNorm(hidden_size)
self.linear = nn.Linear(hidden_size, out_channels, bias=True)
def forward(self, x):
x = self.norm_final(x)
x = self.linear(x)
return x
class CondEmbedder(nn.Module):
def __init__(self, input_feature_size, hidden_size):
super().__init__()
self.cond_embedder = nn.Linear(input_feature_size, hidden_size)
def forward(self, llm_cond):
return self.cond_embedder(llm_cond)
def get_epss_timesteps(n, device, dtype):
dt = 1 / 32
predefined_timesteps = {
5: [0, 2, 4, 8, 16, 32],
6: [0, 2, 4, 6, 8, 16, 32],
7: [0, 2, 4, 6, 8, 16, 24, 32],
10: [0, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32],
12: [0, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32],
16: [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32],
}
t = predefined_timesteps.get(n, [])
if not t:
return torch.linspace(0, 1, n + 1, device=device, dtype=dtype)
return dt * torch.tensor(t, device=device, dtype=dtype)
@@ -0,0 +1,68 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import torch
from .dit import get_epss_timesteps
def apply_sway_sampling(t, sway_sampling_coef):
if sway_sampling_coef is None:
return t
return t + sway_sampling_coef * (torch.cos(torch.pi / 2 * t) - 1 + t)
def build_timesteps(steps, device, dtype, use_epss=True, sway_sampling_coef=-1.0):
if steps <= 0:
raise ValueError(f"steps must be positive, got {steps}")
if use_epss:
t = get_epss_timesteps(steps, device=device, dtype=dtype)
else:
t = torch.linspace(0, 1, steps + 1, device=device, dtype=dtype)
return apply_sway_sampling(t, sway_sampling_coef)
class Solver:
def __init__(self, func, y0, sigma=0.25, temperature=1.5) -> None:
self.func = func
self.y0 = y0
self.sigma = sigma
self.temperature = temperature
def integrate(self, t):
solution = torch.empty(len(t), *self.y0.shape, dtype=self.y0.dtype, device=self.y0.device)
solution[0] = self.y0
j = 1
y0 = self.y0
for t0, t1 in zip(t[:-1], t[1:]):
dt = t1 - t0
f0 = self.func(t0, y0)
dy = dt * f0
y1 = y0 + dy
while j < len(t) and t1 >= t[j]:
solution[j] = self._linear_interp(t0, t1, y0, y1, t[j])
j += 1
noise = torch.randn_like(y0)
shift = self.sigma * (self.temperature**0.5) * (abs(dt) ** 0.5) * noise
y0 = y1 + shift
return solution
def _linear_interp(self, t0, t1, y0, y1, t):
if t == t0:
return y0
if t == t1:
return y1
slope = (t - t0) / (t1 - t0)
return y0 + slope * (y1 - y0)
def integrate_cfm_steps(fn, y0, t, sde_args, sde_rnd, steps):
for step in range(steps):
dt = t[step + 1] - t[step]
y0 = y0 + fn(t[step], y0) * dt
y0 = y0 + sde_args[1] * (sde_args[2] ** 0.5) * (dt.abs() ** 0.5) * sde_rnd[step]
return y0
@@ -26,10 +26,10 @@ from vllm.sequence import IntermediateTensors
from vllm_omni.model_executor.custom_process_mixin import CustomProcessMixin
from vllm_omni.model_executor.model_loader.weight_utils import download_weights_from_hf_specific
from vllm_omni.model_executor.models.common.ming.audio_vae import AudioVAE, AudioVAEConfig
from vllm_omni.model_executor.models.output_templates import OmniOutput
from vllm_omni.transformers_utils.configs.ming_flash_omni import MingFlashOmniTalkerConfig
from .audio_vae import AudioVAE, AudioVAEConfig
from .prompt_utils import DEFAULT_PROMPT as MING_DEFAULT_PROMPT
from .talker_module import CFM, Aggregator, DiT, MingAudioGenerator, build_tts_input
from .text_processing import segment_and_normalize
@@ -697,6 +697,9 @@ class MingFlashOmniThinkerForConditionalGeneration(
f"image_gen_scales={list(self.query_tokens_dict.keys())}"
)
def get_language_model(self):
return self.language_model
def _load_image_gen_query_tokens(self, model_path: str) -> None:
"""Load learnable query tokens used for image generation.
@@ -110,12 +110,18 @@ BASE_CAPTION_TEMPLATE: dict[str, Any] = {
}
def create_instruction(user_input: dict[str, Any]) -> str:
def create_instruction(user_input: Any) -> str | None:
"""Return a JSON caption string for ``audio_sequence[0]``.
Only keys already present on the base template are merged in; unknown
keys are silently ignored to keep the output schema stable.
"""
if user_input is None:
return None
if isinstance(user_input, str):
return user_input
if not isinstance(user_input, dict):
raise ValueError(f"Ming instruction must be str, dict, or None; got {type(user_input).__name__}")
caption = copy.deepcopy(BASE_CAPTION_TEMPLATE)
item = caption["audio_sequence"][0]
for key, value in user_input.items():
@@ -27,223 +27,19 @@ from threading import Lock
import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import PreTrainedTokenizerBase, Qwen2Config, Qwen2Model, StaticCache
from vllm.logger import init_logger
from vllm.platforms import current_platform
from x_transformers.x_transformers import RotaryEmbedding, apply_rotary_pos_emb
from x_transformers.x_transformers import RotaryEmbedding
from vllm_omni.model_executor.layers.timestep_embedding import DiTTimestepEmbedding
from .audio_vae import AudioVAE
from vllm_omni.model_executor.models.common.ming.audio_vae import AudioVAE
from vllm_omni.model_executor.models.common.ming.dit import CondEmbedder, DiTBlock, FinalLayer, get_epss_timesteps
from vllm_omni.model_executor.models.common.ming.fm import apply_sway_sampling, integrate_cfm_steps
logger = init_logger(__name__)
########################################################################
# DiT Modules
# Ported from:
# https://github.com/inclusionAI/Ming/blob/e58533db227031990c5a6864dcf5f08fb53ed0d2/talker_module/modules.py
# Ported from:
# https://github.com/inclusionAI/Ming/blob/e58533db227031990c5a6864dcf5f08fb53ed0d2/talker_module/dit.py
########################################################################
class RMSNorm(nn.Module):
def __init__(self, dim: int, eps: float = 1e-6):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(dim))
def forward(self, x: torch.Tensor) -> torch.Tensor:
if self.weight.dtype in [torch.float16, torch.bfloat16]:
x = x.to(self.weight.dtype)
x = F.rms_norm(x, normalized_shape=(x.shape[-1],), weight=self.weight, eps=self.eps)
return x
class FeedForward(nn.Module):
def __init__(
self, dim: int, dim_out: int | None = None, mult: float = 4, dropout: float = 0.0, approximate: str = "none"
):
super().__init__()
inner_dim = int(dim * mult)
dim_out = dim_out if dim_out is not None else dim
activation = nn.GELU(approximate=approximate)
project_in = nn.Sequential(nn.Linear(dim, inner_dim), activation)
self.ff = nn.Sequential(project_in, nn.Dropout(dropout), nn.Linear(inner_dim, dim_out))
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.ff(x)
class Attention(nn.Module):
def __init__(
self,
dim: int,
heads: int = 8,
dim_head: int = 64,
dropout: float = 0.0,
qk_norm: str | None = None,
pe_attn_head: int | None = None,
attn_mask_enabled: bool = True,
):
super().__init__()
self.dim = dim
self.heads = heads
self.inner_dim = dim_head * heads
self.dropout = dropout
self.to_q = nn.Linear(dim, self.inner_dim)
self.to_k = nn.Linear(dim, self.inner_dim)
self.to_v = nn.Linear(dim, self.inner_dim)
if qk_norm is None:
self.q_norm = None
self.k_norm = None
elif qk_norm == "rms_norm":
self.q_norm = RMSNorm(dim_head)
self.k_norm = RMSNorm(dim_head)
else:
raise ValueError(f"Unimplemented qk_norm: {qk_norm}")
self.to_out = nn.ModuleList([])
self.to_out.append(nn.Linear(self.inner_dim, dim))
self.to_out.append(nn.Dropout(dropout))
self.pe_attn_head = pe_attn_head
self.attn_mask_enabled = attn_mask_enabled
def forward(
self,
x: torch.Tensor,
mask: torch.Tensor | None = None,
rope: tuple[torch.Tensor, torch.Tensor | None] | None = None,
) -> torch.Tensor:
batch_size = x.shape[0]
query = self.to_q(x)
key = self.to_k(x)
value = self.to_v(x)
inner_dim = key.shape[-1]
head_dim = 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 self.q_norm is not None:
query = self.q_norm(query)
if self.k_norm is not None:
key = self.k_norm(key)
if rope is not None:
freqs, xpos_scale = rope
q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
if self.pe_attn_head is not None:
on = self.pe_attn_head
query[:, :on, :, :] = apply_rotary_pos_emb(query[:, :on, :, :], freqs, q_xpos_scale)
key[:, :on, :, :] = apply_rotary_pos_emb(key[:, :on, :, :], freqs, k_xpos_scale)
else:
query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
if self.attn_mask_enabled and mask is not None:
valid_sample_indices = mask.any(dim=1)
final_output = torch.zeros_like(query).to(query.device)
attn_mask = mask[valid_sample_indices]
query = query[valid_sample_indices]
key = key[valid_sample_indices]
value = value[valid_sample_indices]
attn_mask = attn_mask.unsqueeze(1).unsqueeze(1)
attn_mask = attn_mask.expand(valid_sample_indices.sum().item(), self.heads, query.shape[-2], key.shape[-2])
else:
attn_mask = None
x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
if self.attn_mask_enabled and mask is not None:
final_output[valid_sample_indices] = x
x = final_output
x = x.transpose(1, 2).reshape(batch_size, -1, self.heads * head_dim)
x = x.to(query.dtype)
x = self.to_out[0](x)
x = self.to_out[1](x)
if mask is not None:
mask = mask.unsqueeze(-1)
x = x.masked_fill(~mask, 0.0)
return x
class DiTBlock(nn.Module):
"""A DiT block with pre-norm and residual connections."""
def __init__(
self,
hidden_size: int,
num_heads: int,
mlp_ratio: float = 4.0,
dropout: float = 0.1,
qk_norm: str | None = None,
pe_attn_head: int | None = None,
attn_mask_enabled: bool = True,
**kwargs,
):
super().__init__()
self.norm1 = RMSNorm(hidden_size)
self.attn = Attention(
dim=hidden_size,
heads=num_heads,
dim_head=hidden_size // num_heads,
dropout=dropout,
qk_norm=qk_norm,
pe_attn_head=pe_attn_head,
attn_mask_enabled=attn_mask_enabled,
)
self.norm2 = RMSNorm(hidden_size)
self.mlp = FeedForward(dim=hidden_size, mult=mlp_ratio, dropout=dropout, approximate="tanh")
def forward(
self,
x: torch.Tensor,
mask: torch.Tensor | None,
rope: tuple[torch.Tensor, torch.Tensor | None] | None,
) -> torch.Tensor:
x = x + self.attn(self.norm1(x), mask=mask, rope=rope)
x = x + self.mlp(self.norm2(x))
return x
class FinalLayer(nn.Module):
"""The final layer of DiT."""
def __init__(self, hidden_size: int, out_channels: int):
super().__init__()
self.norm_final = RMSNorm(hidden_size)
self.linear = nn.Linear(hidden_size, out_channels, bias=True)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.norm_final(x)
x = self.linear(x)
return x
class CondEmbedder(nn.Module):
"""Embeds LLM hidden states with optional CFG dropout."""
def __init__(self, input_feature_size: int, hidden_size: int):
super().__init__()
self.cond_embedder = nn.Linear(input_feature_size, hidden_size)
def forward(self, llm_cond: torch.Tensor) -> torch.Tensor:
return self.cond_embedder(llm_cond)
class DiT(nn.Module):
"""Diffusion model with a Transformer backbone for audio latent generation."""
@@ -324,29 +120,6 @@ class DiT(nn.Module):
return model_out[:, -x.shape[1] :, :]
#########################################################################################
# CFM
# Ported from:
# https://github.com/inclusionAI/Ming/blob/e58533db227031990c5a6864dcf5f08fb53ed0d2/talker_module/cfm.py
#########################################################################################
def get_epss_timesteps(n, device, dtype):
dt = 1 / 32
predefined_timesteps = {
5: [0, 2, 4, 8, 16, 32],
6: [0, 2, 4, 6, 8, 16, 32],
7: [0, 2, 4, 6, 8, 16, 24, 32],
10: [0, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32],
12: [0, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32],
16: [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32],
}
t = predefined_timesteps.get(n, [])
if not t:
return torch.linspace(0, 1, n + 1, device=device, dtype=dtype)
return dt * torch.tensor(t, device=device, dtype=dtype)
class CFM(nn.Module):
"""Conditional Flow Matching module for audio latent generation."""
@@ -391,15 +164,8 @@ class CFM(nn.Module):
pred, null_pred = torch.chunk(pred_cfg, 2, dim=0)
return pred + (pred - null_pred) * sde_args[0]
if self.sway_sampling_coef is not None:
t = t + self.sway_sampling_coef * (torch.cos(torch.pi / 2 * t) - 1 + t)
for step in range(self.steps):
dt = t[step + 1] - t[step]
y0 = y0 + fn(t[step], y0) * dt
y0 = y0 + sde_args[1] * (sde_args[2] ** 0.5) * (dt.abs() ** 0.5) * sde_rnd[step]
return y0
t = apply_sway_sampling(t, self.sway_sampling_coef)
return integrate_cfm_steps(fn, y0, t, sde_args, sde_rnd, self.steps)
class CFMGraphExecutor:
@@ -22,7 +22,8 @@ from .spk_embedding import SpkembExtractor
from .talker_module import resample
if TYPE_CHECKING:
from .audio_vae import AudioVAE
from vllm_omni.model_executor.models.common.ming.audio_vae import AudioVAE
from .talker_module import Aggregator
logger = init_logger(__name__)
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from .config_ming_tts import MingDenseConfig
from .ming_tts import MingTTSForConditionalGeneration
from .ming_tts_audio_vae import MingAudioVAEModel
from .ming_tts_llm import MingLLMModel
__all__ = [
"MingDenseConfig",
"MingTTSForConditionalGeneration",
"MingLLMModel",
"MingAudioVAEModel",
]
@@ -0,0 +1,67 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# Adopted from https://github.com/inclusionAI/Ming-omni-tts/blob/main/fm/dit.py
from __future__ import annotations
import torch
import torch.nn as nn
from x_transformers.x_transformers import RotaryEmbedding
from vllm_omni.model_executor.models.common.ming.dit import DiTBlock, FinalLayer
class Aggregator(nn.Module):
def __init__(
self,
in_channels=4,
hidden_size=1152,
depth=28,
num_heads=16,
mlp_ratio=4.0,
llm_input_dim=896,
**kwargs,
):
super().__init__()
self.in_channels = in_channels
self.out_channels = in_channels
self.num_heads = num_heads
self.word_embedder = nn.Embedding(1, hidden_size)
self.x_embedder = nn.Linear(in_channels, hidden_size)
self.hidden_size = hidden_size
self.rotary_embed = RotaryEmbedding(hidden_size // num_heads)
self.blocks = nn.ModuleList(
[DiTBlock(hidden_size, num_heads, mlp_ratio=mlp_ratio, **kwargs) for _ in range(depth)]
)
self.final_layer = FinalLayer(hidden_size, llm_input_dim)
def forward(self, x, mask=None):
if x.ndim != 3:
raise ValueError(f"Expected x rank-3 [Batch, Time, Dimension], got {tuple(x.shape)}")
if x.shape[-1] != self.in_channels:
raise ValueError(f"x feature dim mismatch: got {x.shape[-1]}, expected {self.in_channels}")
# [Batch, Time, Dimension] -> [Batch, Time, Hidden].
x = self.x_embedder(x)
cls_embed = self.word_embedder(torch.zeros((x.shape[0], 1), dtype=torch.long, device=x.device))
# Prepend a learned CLS token: [Batch, Time, Hidden] -> [Batch, Time + 1, Hidden].
x = torch.cat([cls_embed, x], dim=1)
rope = self.rotary_embed.forward_from_seq_len(x.shape[1])
if mask is not None:
if mask.ndim != 2:
raise ValueError(f"Expected mask rank-2 [Batch, Time], got {tuple(mask.shape)}")
if mask.shape[0] != x.shape[0] or mask.shape[1] != x.shape[1] - 1:
raise ValueError(
f"Mask shape mismatch: got {tuple(mask.shape)}, expected {(x.shape[0], x.shape[1] - 1)}"
)
mask_pad = mask.clone().detach()[:, :1]
mask = torch.cat([mask_pad, mask], dim=-1)
for block in self.blocks:
x = block(x, mask, rope)
x = self.final_layer(x)
# Keep the CLS projection only: [Batch, Time + 1, Hidden] -> [Batch, 1, Hidden].
return x[:, :1, :]
__all__ = ["Aggregator"]
@@ -0,0 +1,261 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
import math
from io import BytesIO
from typing import Any
import torch
from .config_ming_tts import (
AUDIO_FRAME_HOP,
LATENT_DIM,
PATCH_SIZE,
SAMPLE_RATE,
VAE_PATCH_SIZE,
VISION_START_TOKEN_ID,
MingTTSConfig,
)
def pad_prompt_waveform(
waveform: Any,
*,
patch_size: int = PATCH_SIZE,
sample_rate: int = SAMPLE_RATE,
) -> torch.Tensor:
tensor = coerce_prompt_waveform(waveform)
pad_align = int((float(sample_rate) / 12.5) * int(patch_size))
new_len = ((int(tensor.shape[-1]) + pad_align - 1) // pad_align) * pad_align
if new_len == int(tensor.shape[-1]):
return tensor
padded = torch.zeros((1, new_len), dtype=tensor.dtype, device=tensor.device)
padded[:, : tensor.shape[-1]] = tensor
return padded
def coerce_prompt_waveform(value: Any) -> torch.Tensor:
if value is None:
raise ValueError("prompt waveform cannot be None")
if isinstance(value, torch.Tensor):
tensor = value.detach()
if tensor.ndim == 1:
return tensor.unsqueeze(0).to(torch.float32)
if tensor.ndim == 2:
if tensor.shape[0] != 1:
return tensor.reshape(1, -1).to(torch.float32)
return tensor.to(torch.float32)
raise ValueError(f"Unsupported Ming prompt waveform rank: {tuple(tensor.shape)}")
if isinstance(value, (list, tuple)):
parts = [coerce_prompt_waveform(item) for item in value if item is not None]
if not parts:
raise ValueError("prompt waveform list was empty")
return torch.cat(parts, dim=-1)
return coerce_prompt_waveform(torch.as_tensor(value))
def coerce_speaker_embeddings(value: Any, *, use_zero_spk_emb: bool = False) -> list[torch.Tensor] | None:
if value is None:
return [torch.zeros((192,), dtype=torch.float32)] if use_zero_spk_emb else None
if isinstance(value, torch.Tensor):
tensor = value.detach()
if tensor.ndim == 1:
tensor = tensor.unsqueeze(0)
if tensor.ndim != 2:
raise ValueError(f"Unsupported Ming speaker embedding shape: {tuple(tensor.shape)}")
items = [row.reshape(-1).to(torch.float32).cpu() for row in tensor]
elif isinstance(value, (list, tuple)):
if value and all(not isinstance(item, (list, tuple, torch.Tensor)) for item in value):
items = [torch.as_tensor(value).detach().reshape(-1).to(torch.float32).cpu()]
else:
items = []
for item in value:
if item is None:
continue
if not isinstance(item, torch.Tensor):
item = torch.as_tensor(item)
items.append(item.detach().reshape(-1).to(torch.float32).cpu())
else:
return coerce_speaker_embeddings(torch.as_tensor(value), use_zero_spk_emb=use_zero_spk_emb)
if not items:
return [torch.zeros((192,), dtype=torch.float32)] if use_zero_spk_emb else None
for item in items:
if int(item.numel()) != 192:
raise ValueError(f"Ming speaker embedding must have 192 dims, got {int(item.numel())}")
return items
def count_prompt_latent_patches(
value: Any,
*,
patch_size: int = PATCH_SIZE,
latent_dim: int = LATENT_DIM,
) -> int:
if value is None:
return 0
if not isinstance(value, torch.Tensor):
value = torch.as_tensor(value)
latents = value.detach()
if latents.ndim == 3 and latents.shape[0] == 1:
latents = latents.squeeze(0)
if latents.ndim == 3 and latents.shape[-2:] == (patch_size, latent_dim):
return int(latents.shape[0])
if latents.ndim != 2 or latents.shape[-1] != latent_dim:
raise ValueError(f"Unsupported Ming prompt_latents shape: {tuple(latents.shape)}")
if latents.shape[0] % patch_size != 0:
raise ValueError(
f"Ming prompt_latents frame count must be divisible by patch_size={patch_size}, "
f"got frames={int(latents.shape[0])}"
)
return int(latents.shape[0] // patch_size)
def count_prompt_waveform_patches(
value: Any,
*,
patch_size: int = PATCH_SIZE,
frame_hop: int = AUDIO_FRAME_HOP,
vae_patch_size: int = VAE_PATCH_SIZE,
) -> int:
if value is None:
return 0
waveform = pad_prompt_waveform(value, patch_size=patch_size)
frame_count = int(math.ceil(float(waveform.shape[-1]) / float(frame_hop)))
latent_frames = int(math.ceil(float(frame_count) / float(vae_patch_size)))
if latent_frames % int(patch_size) != 0:
raise ValueError(
f"Ming prompt waveform produced latent frame count not divisible by patch_size={patch_size}: "
f"frames={latent_frames}"
)
return int(latent_frames // int(patch_size))
def _normalize_prompt_waveform(value: Any, *, target_sr: int) -> torch.Tensor:
if isinstance(value, bytes):
import torchaudio
waveform, sr = torchaudio.load(BytesIO(value))
waveform = waveform[:1].to(torch.float32)
if int(sr) != int(target_sr):
from torchaudio.functional import resample as resample_audio
waveform = resample_audio(waveform, int(sr), int(target_sr))
return waveform
if isinstance(value, tuple) and len(value) == 2 and isinstance(value[1], int):
waveform = coerce_prompt_waveform(value[0])
if int(value[1]) != int(target_sr):
from torchaudio.functional import resample as resample_audio
waveform = resample_audio(waveform, int(value[1]), int(target_sr))
return waveform
if isinstance(value, dict):
samples = value.get("samples", value.get("array", value.get("waveform")))
sr = value.get("sample_rate", value.get("sr", target_sr))
return _normalize_prompt_waveform((samples, int(sr)), target_sr=target_sr)
return coerce_prompt_waveform(value)
def _coerce_prompt_latents(
value: Any,
*,
patch_size: int,
latent_dim: int,
) -> dict[str, torch.Tensor] | None:
if value is None:
return None
if not isinstance(value, torch.Tensor):
value = torch.as_tensor(value)
latents = value.detach()
if latents.ndim == 3 and latents.shape[0] == 1:
latents = latents.squeeze(0)
if latents.ndim == 3 and latents.shape[-2:] == (patch_size, latent_dim):
patches = latents
# [Patch, Time, Dimension] -> [Frame, Dimension] for history seeding.
frames = patches.reshape(-1, latent_dim)
return {"patches": patches, "frames": frames}
if latents.ndim != 2 or latents.shape[-1] != latent_dim:
raise ValueError(f"Unsupported prompt latent shape: {tuple(latents.shape)}")
if latents.shape[0] % patch_size != 0:
raise ValueError(
f"Prompt latent frame count must be divisible by patch_size={patch_size}, "
f"got frames={int(latents.shape[0])}"
)
# [Frame, Dimension] -> [Patch, Time, Dimension] for Aggregator prompt slots.
patches = latents.reshape(-1, patch_size, latent_dim) if latents.shape[0] > 0 else None
return {"patches": patches, "frames": latents}
def _initial_history(
frames: torch.Tensor | None,
*,
history_size: int,
latent_dim: int,
device: torch.device,
dtype: torch.dtype,
) -> torch.Tensor:
history = torch.zeros((history_size, latent_dim), device=device, dtype=dtype)
if frames is None or frames.numel() == 0:
return history
frames = frames.to(device=device, dtype=dtype)
take = min(history_size, int(frames.shape[0]))
history[-take:] = frames[-take:]
return history
def _take_scalar(value: Any, idx: int) -> float | None:
if not isinstance(value, torch.Tensor) or value.numel() == 0:
return None
return float(value.reshape(-1)[idx].item())
def _find_audio_placeholder_positions(input_ids: torch.Tensor, cfg: MingTTSConfig) -> torch.Tensor:
dummy_pos = (input_ids == cfg.audio_dummy_token_id).nonzero(as_tuple=True)[0]
if dummy_pos.numel() == 0:
return dummy_pos
audio_start_pos = (input_ids == cfg.audio_start_token_id).nonzero(as_tuple=True)[0]
audio_end_pos = (input_ids == cfg.audio_end_token_id).nonzero(as_tuple=True)[0]
if audio_start_pos.numel() == 0:
return dummy_pos
start = int(audio_start_pos[0].item())
end = int(audio_end_pos[0].item()) if audio_end_pos.numel() > 0 else int(input_ids.shape[0])
keep = (dummy_pos > start) & (dummy_pos < end)
filtered = dummy_pos[keep]
return filtered if filtered.numel() > 0 else dummy_pos
def _find_speaker_placeholder_positions(input_ids: torch.Tensor, hf_config: Any) -> list[int]:
vision_start_token_id = getattr(hf_config, "vision_start_token_id", VISION_START_TOKEN_ID)
vision_start_pos = (input_ids == int(vision_start_token_id)).nonzero(as_tuple=True)[0]
if vision_start_pos.numel() == 0:
return []
slots = []
for pos in vision_start_pos:
slot = int(pos.item()) + 1
if slot < int(input_ids.shape[0]):
slots.append(slot)
return slots
__all__ = [
"coerce_prompt_waveform",
"coerce_speaker_embeddings",
"count_prompt_latent_patches",
"count_prompt_waveform_patches",
"pad_prompt_waveform",
"_coerce_prompt_latents",
"_find_audio_placeholder_positions",
"_find_speaker_placeholder_positions",
"_initial_history",
"_normalize_prompt_waveform",
"_take_scalar",
]
@@ -0,0 +1,209 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Ming dense checkpoint config adapters."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from transformers import PretrainedConfig, Qwen2Config
from vllm_omni.model_executor.models.common.ming.audio_vae import AudioVAEConfig
from .constants import (
AGGREGATOR_HIDDEN_SIZE,
AUDIO_DUMMY_TOKEN_ID,
AUDIO_END_TOKEN_ID,
AUDIO_EOS_TOKEN_ID,
AUDIO_FRAME_HOP,
AUDIO_START_TOKEN_ID,
DEFAULT_CFG,
DEFAULT_SIGMA,
DEFAULT_TEMPERATURE,
HISTORY_PATCH_SIZE,
KEY_CFG,
KEY_CHUNK_ID,
KEY_DECODE_STEP,
KEY_LAST_STOP_PROB,
KEY_LATENT_HISTORY,
KEY_MAX_DECODE_STEPS,
KEY_MIN_DECODE_STEPS,
KEY_NEXT_EMBEDS,
KEY_PROMPT_LATENT_TAIL,
KEY_PROMPT_LATENTS,
KEY_REQUEST_ID,
KEY_SIGMA,
KEY_SPEAKER_EMBEDDING,
KEY_TEMPERATURE,
KEY_TEXT_MODE,
LATENT_CHUNK_SIZE,
LATENT_DIM,
LATENT_LEFT_CONTEXT,
LLM_HIDDEN_SIZE,
LLM_VOCAB_SIZE,
MAX_DECODE_STEPS,
PATCH_SIZE,
SAMPLE_RATE,
STOP_HEAD_MIN_STEPS,
STOP_HEAD_THRESHOLD,
TEXT_EOS_TOKEN_ID,
VAE_PATCH_SIZE,
VISION_START_TOKEN_ID,
)
from .validation import _coerce_audio_vae_config, _nested_get, _to_plain_dict, validate_ming_tts_config
def _coerce_qwen2_config(value: Any) -> Qwen2Config:
if isinstance(value, Qwen2Config):
return value
if isinstance(value, PretrainedConfig):
return Qwen2Config.from_dict(value.to_dict())
if isinstance(value, dict):
return Qwen2Config.from_dict(dict(value))
raise TypeError(f"Unsupported llm_config type for Ming dense config: {type(value)!r}")
class MingDenseConfig(PretrainedConfig):
# The upstream checkpoint declares model_type="dense". Keep it for HF
# config compatibility; deploy/ming_tts.yaml selects the vLLM-Omni
# pipeline via pipeline: ming_tts.
model_type = "dense"
def __init__(
self,
llm_config: Qwen2Config | dict[str, Any] | None = None,
ditar_config: dict[str, Any] | None = None,
aggregator_config: dict[str, Any] | None = None,
audio_tokenizer_config: AudioVAEConfig | dict[str, Any] | None = None,
architectures: list[str] | None = None,
**kwargs: Any,
) -> None:
self.llm_config = _coerce_qwen2_config(llm_config or {})
self.ditar_config = dict(ditar_config or {})
self.aggregator_config = dict(aggregator_config or {})
self.audio_tokenizer_config = _coerce_audio_vae_config(audio_tokenizer_config)
super().__init__(architectures=architectures, **kwargs)
def get_text_config(self, decoder: bool = False, **kwargs: Any) -> Qwen2Config:
del decoder, kwargs
return self.llm_config
@dataclass
class MingTTSConfig:
"""Flat config object shared by Stage-1 and Stage-2. Build via from_hf_config()."""
llm_hidden_size: int = LLM_HIDDEN_SIZE
llm_vocab_size: int = LLM_VOCAB_SIZE
llm_config: dict[str, Any] = field(default_factory=dict)
latent_dim: int = LATENT_DIM
patch_size: int = PATCH_SIZE
history_patch_size: int = HISTORY_PATCH_SIZE
ditar_config: dict[str, Any] = field(default_factory=dict)
aggregator_config: dict[str, Any] = field(default_factory=dict)
audio_tokenizer_config: AudioVAEConfig | None = None
vae_patch_size: int = VAE_PATCH_SIZE
sample_rate: int = SAMPLE_RATE
audio_frame_hop: int = AUDIO_FRAME_HOP
cfg: float = DEFAULT_CFG
sigma: float = DEFAULT_SIGMA
temperature: float = DEFAULT_TEMPERATURE
stop_head_min_steps: int = STOP_HEAD_MIN_STEPS
stop_head_threshold: float = STOP_HEAD_THRESHOLD
max_decode_steps: int = MAX_DECODE_STEPS
latent_chunk_size: int = LATENT_CHUNK_SIZE
latent_left_context: int = LATENT_LEFT_CONTEXT
text_eos_token_id: int = TEXT_EOS_TOKEN_ID
audio_dummy_token_id: int = AUDIO_DUMMY_TOKEN_ID
audio_start_token_id: int = AUDIO_START_TOKEN_ID
audio_end_token_id: int = AUDIO_END_TOKEN_ID
audio_eos_token_id: int = AUDIO_EOS_TOKEN_ID
@classmethod
def from_hf_config(cls, hf_config: PretrainedConfig) -> MingTTSConfig:
llm_raw = getattr(hf_config, "llm_config", {}) or {}
ditar_raw = getattr(hf_config, "ditar_config", {}) or {}
agg_raw = getattr(hf_config, "aggregator_config", {}) or {}
atc_raw = getattr(hf_config, "audio_tokenizer_config", None)
llm_dict = _to_plain_dict(llm_raw)
ditar = _to_plain_dict(ditar_raw)
agg = _to_plain_dict(agg_raw)
ditar.setdefault("attn_backend", "torch")
atc = _coerce_audio_vae_config(atc_raw)
atc_enc_latent_dim = _nested_get(atc, "enc_kwargs", "latent_dim", default=LATENT_DIM)
atc_patch_size = _nested_get(atc, "patch_size", default=VAE_PATCH_SIZE)
atc_sample_rate = _nested_get(atc, "sample_rate", default=SAMPLE_RATE)
enc_hop_size = _nested_get(atc, "enc_kwargs", "hop_size", default=AUDIO_FRAME_HOP)
cfg = cls(
llm_hidden_size=llm_dict.get("hidden_size", LLM_HIDDEN_SIZE),
llm_vocab_size=llm_dict.get("vocab_size", LLM_VOCAB_SIZE),
llm_config=llm_dict,
latent_dim=atc_enc_latent_dim,
patch_size=ditar.get("patch_size", PATCH_SIZE),
history_patch_size=ditar.get("history_patch_size", HISTORY_PATCH_SIZE),
ditar_config=ditar,
aggregator_config=agg,
audio_tokenizer_config=atc,
vae_patch_size=atc_patch_size,
sample_rate=atc_sample_rate,
audio_frame_hop=enc_hop_size if enc_hop_size is not None else AUDIO_FRAME_HOP,
)
return cfg
def validate(self) -> None:
validate_ming_tts_config(self)
__all__ = [
"AGGREGATOR_HIDDEN_SIZE",
"AUDIO_DUMMY_TOKEN_ID",
"AUDIO_END_TOKEN_ID",
"AUDIO_EOS_TOKEN_ID",
"AUDIO_FRAME_HOP",
"AUDIO_START_TOKEN_ID",
"DEFAULT_CFG",
"DEFAULT_SIGMA",
"DEFAULT_TEMPERATURE",
"HISTORY_PATCH_SIZE",
"KEY_CFG",
"KEY_CHUNK_ID",
"KEY_DECODE_STEP",
"KEY_LAST_STOP_PROB",
"KEY_LATENT_HISTORY",
"KEY_MAX_DECODE_STEPS",
"KEY_MIN_DECODE_STEPS",
"KEY_NEXT_EMBEDS",
"KEY_PROMPT_LATENT_TAIL",
"KEY_PROMPT_LATENTS",
"KEY_REQUEST_ID",
"KEY_SIGMA",
"KEY_SPEAKER_EMBEDDING",
"KEY_TEMPERATURE",
"KEY_TEXT_MODE",
"LATENT_CHUNK_SIZE",
"LATENT_DIM",
"LATENT_LEFT_CONTEXT",
"LLM_HIDDEN_SIZE",
"LLM_VOCAB_SIZE",
"MAX_DECODE_STEPS",
"MingDenseConfig",
"MingTTSConfig",
"PATCH_SIZE",
"SAMPLE_RATE",
"STOP_HEAD_MIN_STEPS",
"STOP_HEAD_THRESHOLD",
"TEXT_EOS_TOKEN_ID",
"VAE_PATCH_SIZE",
"VISION_START_TOKEN_ID",
]
@@ -0,0 +1,67 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
# ---------------------------------------------------------------------------
# Token IDs (confirmed from tokenizer_config.json)
# ---------------------------------------------------------------------------
AUDIO_DUMMY_TOKEN_ID = 151705 # <audioPatch>
AUDIO_START_TOKEN_ID = 151706 # <audio>
AUDIO_END_TOKEN_ID = 151707 # </audio>
AUDIO_EOS_TOKEN_ID = 151704 # <end_of_audio>
VISION_START_TOKEN_ID = 151652 # <|vision_start|>
TEXT_EOS_TOKEN_ID = 151669 # <text_eos>
# ---------------------------------------------------------------------------
# Architectural constants (confirmed from original config.json)
# ---------------------------------------------------------------------------
LATENT_DIM = 64
PATCH_SIZE = 4
HISTORY_PATCH_SIZE = 32
LLM_HIDDEN_SIZE = 896
LLM_VOCAB_SIZE = 151936
AGGREGATOR_HIDDEN_SIZE = 1024
VAE_PATCH_SIZE = 4
SAMPLE_RATE = 44100
# AudioVAE frame/hop geometry (confirmed)
AUDIO_FRAME_HOP = 882 # enc input_dim / hop_size / dec output_dim
# stop_head defaults
STOP_HEAD_MIN_STEPS = 3
STOP_HEAD_THRESHOLD = 0.5
# FlowLoss sampling defaults
DEFAULT_CFG = 2.0
DEFAULT_SIGMA = 0.25
DEFAULT_TEMPERATURE = 0.0
# Connector / Stage-2 streaming defaults (runtime tuning)
LATENT_CHUNK_SIZE = 25
LATENT_LEFT_CONTEXT = 0
MAX_DECODE_STEPS = 200
# ---------------------------------------------------------------------------
# seq_data.extra_data keys
# ---------------------------------------------------------------------------
KEY_LATENT_HISTORY = "ming_latent_history"
KEY_DECODE_STEP = "ming_decode_step"
KEY_LAST_STOP_PROB = "ming_last_stop_prob"
KEY_NEXT_EMBEDS = "ming_next_embeds"
KEY_PROMPT_LATENTS = "ming_prompt_latents"
KEY_PROMPT_LATENT_TAIL = "ming_prompt_latent_tail"
KEY_SPEAKER_EMBEDDING = "ming_speaker_embedding"
KEY_REQUEST_ID = "ming_request_id"
KEY_CHUNK_ID = "ming_chunk_id"
KEY_CFG = "ming_cfg"
KEY_SIGMA = "ming_sigma"
KEY_TEMPERATURE = "ming_temperature"
KEY_MAX_DECODE_STEPS = "ming_max_decode_steps"
KEY_MIN_DECODE_STEPS = "ming_min_decode_steps"
KEY_TEXT_MODE = "ming_text_mode"
@@ -0,0 +1,63 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# Adopted from https://github.com/inclusionAI/Ming-omni-tts/blob/main/fm/flowloss.py
import torch
import torch.nn as nn
from vllm_omni.model_executor.models.common.ming.fm import Solver, build_timesteps
from .fm.dit import DiT
class FlowLoss(nn.Module):
"""Diffusion Loss"""
def __init__(self, z_channels, llm_cond_dim, **kwargs):
super().__init__()
self.z_channels = z_channels
# Preserve checkpoint parameter names under flowloss.cfm.model.* while
# keeping the inference-only CFM sampling logic local to FlowLoss.
self.cfm = nn.Module()
self.cfm.add_module("model", DiT(in_channels=z_channels, llm_cond_dim=llm_cond_dim, **kwargs))
@torch.no_grad()
def sample(self, z, latent_history, cfg=2.0, patch_size=1, sigma=0.25, temperature=0):
# z: [Batch, Time=1, Dimension]
# latent_history: [Batch, History_Time, Dimension]
if z.ndim != 3:
raise ValueError(f"Expected conditioning rank-3 [Batch, Time, Dimension], got {tuple(z.shape)}")
if latent_history.ndim != 3:
raise ValueError(
f"Expected latent_history rank-3 [Batch, Time, Dimension], got {tuple(latent_history.shape)}"
)
if z.shape[0] != latent_history.shape[0]:
raise ValueError(
f"Batch mismatch across conditioning and latent_history: {z.shape[0]}, {latent_history.shape[0]}"
)
dtype = z.dtype
noise = torch.randn(z.shape[0], self.z_channels, patch_size, device=z.device, dtype=torch.float32)
def fn(t, x):
x = x.to(dtype)
t = t.to(dtype)
if cfg < 1e-5:
if t.ndim == 0:
t = t.repeat(x.shape[0])
pred = self.cfm.model(x=x, t=t, c=torch.zeros_like(z), latent_history=latent_history)
return pred[:, -patch_size:, :].float()
pred_cfg = self.cfm.model.forward_with_cfg(
x=x, t=t, c=z, latent_history=latent_history, cfg_scale=cfg, patch_size=patch_size
)
pred, null_pred = torch.chunk(pred_cfg, 2, dim=0)
return (pred + (pred - null_pred) * cfg).float()
t = build_timesteps(
10,
device=noise.device,
dtype=noise.dtype,
use_epss=True,
sway_sampling_coef=-1.0,
)
return Solver(fn, noise.transpose(1, 2), sigma=sigma, temperature=temperature).integrate(t)[-1].to(dtype)
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
@@ -0,0 +1,98 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# Adopted from https://github.com/inclusionAI/Ming-omni-tts/blob/main/fm/dit.py
import torch
import torch.nn as nn
from x_transformers.x_transformers import RotaryEmbedding
from vllm_omni.model_executor.layers.timestep_embedding import DiTTimestepEmbedding
from vllm_omni.model_executor.models.common.ming.dit import CondEmbedder, DiTBlock, FinalLayer
class DiT(nn.Module):
def __init__(
self,
in_channels=4,
hidden_size=1024,
depth=28,
num_heads=16,
mlp_ratio=4.0,
llm_cond_dim=896,
**kwargs,
):
super().__init__()
self.in_channels = in_channels
self.out_channels = in_channels
self.num_heads = num_heads
self.t_embedder = DiTTimestepEmbedding(hidden_size)
self.x_embedder = nn.Linear(in_channels, hidden_size)
self.c_embedder = CondEmbedder(llm_cond_dim, hidden_size)
self.hidden_size = hidden_size
self.rotary_embed = RotaryEmbedding(hidden_size // num_heads)
self.blocks = nn.ModuleList(
[DiTBlock(hidden_size, num_heads, mlp_ratio=mlp_ratio, **kwargs) for _ in range(depth)]
)
self.final_layer = FinalLayer(hidden_size, self.out_channels)
def forward(self, x, t, c, latent_history, mask=None):
if x.ndim != 3:
raise ValueError(f"Expected x rank-3 [Batch, Time, Dimension], got {tuple(x.shape)}")
if latent_history.ndim != 3:
raise ValueError(
f"Expected latent_history rank-3 [Batch, Time, Dimension], got {tuple(latent_history.shape)}"
)
if c.ndim != 3:
raise ValueError(f"Expected conditioning rank-3 [Batch, Time, Dimension], got {tuple(c.shape)}")
if x.shape[0] != latent_history.shape[0] or x.shape[0] != c.shape[0]:
raise ValueError(
"Batch mismatch across x, conditioning, and latent_history: "
f"{x.shape[0]}, {c.shape[0]}, {latent_history.shape[0]}"
)
if x.shape[-1] != self.in_channels:
raise ValueError(f"x feature dim mismatch: got {x.shape[-1]}, expected {self.in_channels}")
if latent_history.shape[-1] != self.in_channels:
raise ValueError(
f"latent_history feature dim mismatch: got {latent_history.shape[-1]}, expected {self.in_channels}"
)
if t.ndim == 0:
t = t.reshape(1)
if t.ndim != 1:
raise ValueError(f"Expected timestep rank-1 [Batch], got {tuple(t.shape)}")
if t.shape[0] != x.shape[0]:
raise ValueError(f"Timestep batch mismatch: got {t.shape[0]}, expected {x.shape[0]}")
t = self.t_embedder(t).unsqueeze(1)
x_now = self.x_embedder(x)
x_history = self.x_embedder(latent_history)
x = torch.cat([x_history, x_now], dim=1)
c = self.c_embedder(c)
y = t + c
x = torch.cat([y, x], dim=1)
rope = self.rotary_embed.forward_from_seq_len(x.shape[1])
if mask is not None:
if mask.ndim != 2:
raise ValueError(f"Expected mask rank-2 [Batch, Time], got {tuple(mask.shape)}")
if mask.shape[0] != x_now.shape[0] or mask.shape[1] != x_now.shape[1]:
raise ValueError(
f"Mask shape mismatch: got {tuple(mask.shape)}, expected {(x_now.shape[0], x_now.shape[1])}"
)
mask_pad = mask.clone().detach()[:, :1].expand(-1, x_history.shape[1] + c.shape[1])
mask = torch.cat([mask_pad, mask], dim=-1)
for block in self.blocks:
x = block(x, mask, rope)
x = self.final_layer(x)
return x
def forward_with_cfg(self, x, t, c, cfg_scale, latent_history, patch_size):
if not cfg_scale == 1:
x = torch.cat([x, x], dim=0)
latent_history = torch.cat([latent_history, latent_history], dim=0)
fake_latent = torch.zeros_like(c)
c = torch.cat([c, fake_latent], dim=0)
if t.ndim == 0:
t = t.repeat(x.shape[0])
model_out = self.forward(x, t, c, latent_history)
return model_out[:, -patch_size:, :]
@@ -0,0 +1,269 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
from collections.abc import Iterable
from functools import cached_property
from typing import Any
import torch
import torch.nn as nn
from vllm.config import VllmConfig
from vllm.model_executor.models import SupportsPP
from vllm.model_executor.models.utils import init_vllm_registered_model
from vllm.v1.sample.sampler import Sampler
from vllm_omni.model_executor.custom_process_mixin import CustomProcessMixin
from .audio_prep import (
_coerce_prompt_latents,
_find_audio_placeholder_positions,
_find_speaker_placeholder_positions,
_initial_history,
_take_scalar,
coerce_speaker_embeddings,
)
from .config_ming_tts import (
AUDIO_START_TOKEN_ID,
KEY_CFG,
KEY_DECODE_STEP,
KEY_LAST_STOP_PROB,
KEY_LATENT_HISTORY,
KEY_MAX_DECODE_STEPS,
KEY_MIN_DECODE_STEPS,
KEY_NEXT_EMBEDS,
KEY_PROMPT_LATENT_TAIL,
KEY_REQUEST_ID,
KEY_SIGMA,
KEY_SPEAKER_EMBEDDING,
KEY_TEMPERATURE,
KEY_TEXT_MODE,
MingTTSConfig,
)
from .patch_emission import MING_STOP_REASON_KEY
from .prompt_encoder import _resolve_prompt_latents
class _ModelSampleAdapter(nn.Module):
def __init__(self, model: nn.Module):
super().__init__()
self.model = model
def forward(self, logits, sampling_metadata):
return self.model.sample(logits, sampling_metadata)
class MingTTSForConditionalGeneration(nn.Module, SupportsPP, CustomProcessMixin):
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
super().__init__()
del prefix
self.vllm_config = vllm_config
self.ming_config = MingTTSConfig.from_hf_config(vllm_config.model_config.hf_config)
self.ming_config.validate()
self.have_multimodal_outputs = True
self.has_preprocess = False
self.has_postprocess = False
self.requires_raw_input_tokens = False
self.model_stage = vllm_config.model_config.model_stage
self._prompt_encoder = None
if self.model_stage == "llm":
self.model = init_vllm_registered_model(vllm_config=vllm_config, architectures=["MingLLMModel"])
self.has_preprocess = True
self.has_postprocess = True
self.set_custom_preprocess(self.preprocess)
self.set_custom_postprocess(self.postprocess)
elif self.model_stage == "audio_vae":
self.model = init_vllm_registered_model(vllm_config=vllm_config, architectures=["MingAudioVAEModel"])
self.requires_raw_input_tokens = True
else:
raise ValueError(f"Invalid Ming model_stage={self.model_stage}")
self.make_empty_intermediate_tensors = getattr(self.model, "make_empty_intermediate_tensors", lambda: None)
@cached_property
def sampler(self):
if hasattr(self.model, "sample"):
return _ModelSampleAdapter(self.model)
if hasattr(self.model, "sampler"):
return self.model.sampler
return Sampler()
def embed_input_ids(self, input_ids: torch.Tensor, **kwargs: Any) -> torch.Tensor:
return self.model.embed_input_ids(input_ids=input_ids, **kwargs)
def forward(self, *args: Any, **kwargs: Any):
return self.model(*args, **kwargs)
def compute_logits(self, hidden_states, sampling_metadata=None):
return self.model.compute_logits(hidden_states, sampling_metadata=sampling_metadata)
def sample(self, logits, sampling_metadata):
return self.model.sample(logits, sampling_metadata) if hasattr(self.model, "sample") else None
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
weights = list(weights)
if self.model_stage == "llm":
allowed = ("model.", "linear_proj_audio.", "flowloss.", "stop_head.", "spk_head.")
llm_weights = [(k, v) for k, v in weights if k.startswith(allowed)]
if not llm_weights:
raise RuntimeError(
"Ming Stage-0 received no loadable checkpoint weights. "
"Expected prefixes: model.*, linear_proj_audio.*, flowloss.*, stop_head.*, spk_head.*"
)
loaded = self.model.load_weights(llm_weights)
return {f"model.{name}" for name in loaded}
audio_weights = [(k, v) for k, v in weights if k.startswith("audio.")]
if not audio_weights:
raise RuntimeError("Ming Stage-1 received no loadable checkpoint weights. Expected prefix: audio.*")
loaded = self.model.load_weights(audio_weights)
return {f"model.{name}" for name in loaded}
def _resolve_prompt_latents(self, info_dict: dict[str, Any]):
return _resolve_prompt_latents(self, info_dict)
def preprocess(self, input_ids: torch.Tensor, input_embeds: torch.Tensor | None, **info_dict: Any):
if self.model_stage != "llm":
return input_ids, input_embeds, {}
input_embeds = self.model.embed_input_ids(input_ids).clone()
return (
self._prefill_preprocess(input_ids, input_embeds, **info_dict)
if int(input_ids.shape[0]) > 1
else self._decode_preprocess(input_ids, input_embeds, **info_dict)
)
def postprocess(self, hidden_states: torch.Tensor, **info_dict: Any) -> dict[str, Any]:
if self.model_stage != "llm" or hidden_states.numel() == 0:
return {}
req_id = info_dict.get(KEY_REQUEST_ID, info_dict.get("request_id"))
pending = self.model.pop_postprocess_update(req_id)
if not pending or not isinstance(pending.get("ming_latent_patch"), torch.Tensor):
return {}
update = {
KEY_LATENT_HISTORY: pending[KEY_LATENT_HISTORY].detach().to("cpu").contiguous(),
KEY_NEXT_EMBEDS: pending[KEY_NEXT_EMBEDS].detach().to("cpu").contiguous(),
KEY_DECODE_STEP: int(info_dict.get(KEY_DECODE_STEP, 0)) + 1,
}
stop_prob = _take_scalar(pending.get("ming_stop_prob"), 0)
if stop_prob is not None:
update[KEY_LAST_STOP_PROB] = stop_prob
stop_reason = pending.get(MING_STOP_REASON_KEY)
if isinstance(stop_reason, str):
update[MING_STOP_REASON_KEY] = stop_reason
if isinstance(req_id, str):
update[KEY_REQUEST_ID] = req_id
return update
def _prefill_preprocess(self, input_ids: torch.Tensor, input_embeds: torch.Tensor, **info_dict: Any):
if bool(info_dict.get(KEY_TEXT_MODE, False)):
update: dict[str, Any] = {KEY_TEXT_MODE: True}
request_id = info_dict.get(KEY_REQUEST_ID, info_dict.get("request_id"))
if request_id is not None:
update[KEY_REQUEST_ID] = request_id
if int(input_ids.shape[0]) > 1 and int(input_ids[-1].item()) == AUDIO_START_TOKEN_ID:
return input_ids[:-1], input_embeds[:-1], update
return input_ids, input_embeds, update
update: dict[str, Any] = {KEY_DECODE_STEP: int(info_dict.get(KEY_DECODE_STEP, 0))}
prompt_latents = _resolve_prompt_latents(self, info_dict)
history = _initial_history(
prompt_latents["frames"] if prompt_latents is not None else None,
history_size=self.ming_config.history_patch_size,
latent_dim=self.ming_config.latent_dim,
device=input_embeds.device,
dtype=torch.float32,
)
update[KEY_LATENT_HISTORY] = history.detach().to("cpu").contiguous()
update[KEY_PROMPT_LATENT_TAIL] = update[KEY_LATENT_HISTORY]
speaker_embedding = info_dict.get(KEY_SPEAKER_EMBEDDING, info_dict.get("speaker_embedding"))
speaker_embeddings = None
if speaker_embedding is not None:
speaker_embeddings = coerce_speaker_embeddings(
speaker_embedding,
use_zero_spk_emb=bool(info_dict.get("use_zero_spk_emb", False)),
)
if speaker_embeddings is not None and len(speaker_embeddings) > 0:
speaker_slots = _find_speaker_placeholder_positions(input_ids, self.vllm_config.model_config.hf_config)
if len(speaker_slots) < len(speaker_embeddings):
raise RuntimeError(
"Could not locate enough speaker placeholder slots: "
f"found {len(speaker_slots)}, need {len(speaker_embeddings)}"
)
for speaker_slot, spk in zip(speaker_slots, speaker_embeddings):
spk_proj = self.model.project_speaker_embedding(
spk.to(device=input_embeds.device, dtype=input_embeds.dtype).unsqueeze(0)
).squeeze(0)
input_embeds[speaker_slot] = spk_proj
if prompt_latents is not None and prompt_latents["patches"] is not None:
prompt_patches = prompt_latents["patches"].to(dtype=getattr(self.model, "fm_dtype", torch.float32))
prompt_embeds = self.model.linear_proj_audio(prompt_patches).squeeze(1)
placeholder_pos = _find_audio_placeholder_positions(input_ids, self.ming_config)
take = min(int(placeholder_pos.numel()), int(prompt_embeds.shape[0]))
if take > 0:
input_embeds[placeholder_pos[:take]] = prompt_embeds[:take].to(dtype=input_embeds.dtype)
request_id = info_dict.get(KEY_REQUEST_ID, info_dict.get("request_id"))
if request_id is not None:
update[KEY_REQUEST_ID] = request_id
_copy_runtime_controls(update, info_dict)
return input_ids, input_embeds, update
def _decode_preprocess(self, input_ids: torch.Tensor, input_embeds: torch.Tensor, **info_dict: Any):
if bool(info_dict.get(KEY_TEXT_MODE, False)):
update: dict[str, Any] = {KEY_TEXT_MODE: True}
request_id = info_dict.get(KEY_REQUEST_ID, info_dict.get("request_id"))
if request_id is not None:
update[KEY_REQUEST_ID] = request_id
return input_ids, input_embeds, update
update: dict[str, Any] = {KEY_DECODE_STEP: int(info_dict.get(KEY_DECODE_STEP, 0))}
history = info_dict.get(KEY_LATENT_HISTORY)
if isinstance(history, torch.Tensor):
update[KEY_LATENT_HISTORY] = history.detach().to("cpu").contiguous()
else:
zero_history = torch.zeros(
(self.ming_config.history_patch_size, self.ming_config.latent_dim),
device=input_embeds.device,
dtype=torch.float32,
)
update[KEY_LATENT_HISTORY] = zero_history.detach().to("cpu").contiguous()
next_embeds = info_dict.get(KEY_NEXT_EMBEDS)
if isinstance(next_embeds, torch.Tensor) and input_ids.numel() == 1:
if not torch.isfinite(next_embeds).all():
raise RuntimeError("Non-finite next_embeds before decode preprocess write.")
input_embeds[0] = (
next_embeds.detach()
.reshape(-1, self.ming_config.llm_hidden_size)[0]
.to(
device=input_embeds.device,
dtype=input_embeds.dtype,
)
)
if not torch.isfinite(input_embeds[0]).all():
raise RuntimeError("Non-finite backbone input_embeds after decode preprocess write.")
request_id = info_dict.get(KEY_REQUEST_ID, info_dict.get("request_id"))
if request_id is not None:
update[KEY_REQUEST_ID] = request_id
_copy_runtime_controls(update, info_dict)
return input_ids, input_embeds, update
def _copy_runtime_controls(update: dict[str, Any], info_dict: dict[str, Any]) -> None:
for key in (KEY_CFG, KEY_SIGMA, KEY_TEMPERATURE, KEY_MAX_DECODE_STEPS, KEY_MIN_DECODE_STEPS):
if key in info_dict:
update[key] = info_dict[key]
__all__ = [
"MingTTSForConditionalGeneration",
"_ModelSampleAdapter",
"_coerce_prompt_latents",
"_find_audio_placeholder_positions",
"_find_speaker_placeholder_positions",
"_initial_history",
]
@@ -0,0 +1,389 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# Adopted from https://github.com/inclusionAI/Ming-omni-tts/blob/main/audio_tokenizer/modeling_audio_vae.py
from __future__ import annotations
import os
import time
import warnings
from collections import OrderedDict
from collections.abc import Iterable
from typing import Any
import torch
import torch.nn as nn
from vllm.config import VllmConfig
from vllm.logger import init_logger
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
from vllm_omni.model_executor.models.common.ming.audio_vae import AudioVAE
from vllm_omni.model_executor.models.output_templates import OmniOutput
from .config_ming_tts import KEY_CHUNK_ID, KEY_REQUEST_ID, MingTTSConfig
from .patch_emission import MING_STOP_REASON_KEY
logger = init_logger(__name__)
MING_FINAL_DECODE_STEP_KEY = "ming_final_decode_step"
_STREAM_STATE_TTL_SECONDS = 15 * 60
_MAX_STREAM_STATES = 1024
class MingAudioVAEModel(nn.Module):
input_modalities = "audio"
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
super().__init__()
del prefix
self.vllm_config = vllm_config
self.ming_config = MingTTSConfig.from_hf_config(vllm_config.model_config.hf_config)
if self.ming_config.audio_tokenizer_config is None:
raise ValueError("MingAudioVAEModel requires audio_tokenizer_config")
self.ming_config.validate()
self.audio = AudioVAE(self.ming_config.audio_tokenizer_config)
self.have_multimodal_outputs = True
self.has_preprocess = False
self.has_postprocess = False
self.enable_update_additional_information = True
self.requires_raw_input_tokens = True
self._past_key_values: dict[str, Any] = {}
self._stream_state: dict[str, tuple[Any, Any, Any]] = {}
self._patch_totals: dict[str, int] = {}
self._sample_totals: dict[str, int] = {}
self._state_access_times: OrderedDict[str, float] = OrderedDict()
def embed_input_ids(self, input_ids: torch.Tensor, **_: Any) -> torch.Tensor:
hidden_size = int(self.ming_config.llm_hidden_size)
if input_ids is None or input_ids.numel() == 0:
return torch.empty((0, hidden_size), device=self.vllm_config.device_config.device, dtype=torch.float32)
return torch.zeros((input_ids.shape[0], hidden_size), device=input_ids.device, dtype=torch.float32)
def compute_logits(self, hidden_states: torch.Tensor | OmniOutput, sampling_metadata: Any = None) -> None:
del hidden_states, sampling_metadata
return None
def chunked_decode_streaming(
self,
latent_chunk: torch.Tensor,
*,
request_id: str,
finished: bool,
) -> tuple[torch.Tensor, Any, Any, bool, bool]:
had_past_key_values = request_id in self._past_key_values
had_stream_state = _has_stream_state(self._stream_state.get(request_id))
stream_state = self._stream_state.get(request_id, (None, None, None))
past_key_values = self._past_key_values.get(request_id)
waveform_parts: list[torch.Tensor] = []
patch_count = int(latent_chunk.shape[0])
for patch_idx in range(patch_count):
# [Batch, Time, Dimension] = [1, patch_size, latent_dim]
latent_patch = latent_chunk[patch_idx : patch_idx + 1]
is_last_patch = finished and patch_idx == patch_count - 1
waveform, stream_state, past_key_values = self.audio.decode(
latent_patch,
past_key_values=past_key_values,
use_cache=True,
stream_state=stream_state,
last_chunk=is_last_patch,
)
waveform_parts.append(waveform.reshape(-1).to(torch.float32))
waveform_flat = (
torch.cat(waveform_parts, dim=0)
if waveform_parts
else torch.zeros((0,), dtype=torch.float32, device=latent_chunk.device)
)
return waveform_flat, stream_state, past_key_values, had_past_key_values, had_stream_state
@torch.inference_mode()
def forward(
self,
input_ids: torch.Tensor | None = None,
positions: torch.Tensor | None = None,
intermediate_tensors: Any = None,
inputs_embeds: torch.Tensor | None = None,
model_intermediate_buffer: list[dict[str, Any]] | None = None,
**kwargs: Any,
) -> OmniOutput:
del input_ids, positions, intermediate_tensors, inputs_embeds
info_list = model_intermediate_buffer if isinstance(model_intermediate_buffer, list) else None
if info_list is None:
runtime_infos = kwargs.get("runtime_additional_information")
info_list = runtime_infos if isinstance(runtime_infos, list) else None
info_list = info_list or [{}]
num_reqs = max(len(info_list), 1)
sr_tensor = torch.tensor(int(self.ming_config.sample_rate), dtype=torch.int32)
empty = torch.zeros((0,), dtype=torch.float32, device=self.vllm_config.device_config.device)
outputs: list[torch.Tensor] = []
srs: list[torch.Tensor] = []
for idx in range(num_reqs):
info = info_list[idx] if idx < len(info_list) and isinstance(info_list[idx], dict) else {}
meta = info.get("meta") or {}
kv_meta = info.get("kv_metadata") or {}
has_ming_context = _has_ming_context(info)
if has_ming_context and KEY_REQUEST_ID not in info and KEY_REQUEST_ID not in kv_meta:
raise RuntimeError(
f"Ming Stage-2 received a payload without {KEY_REQUEST_ID}. keys={sorted(info.keys())}"
)
request_id = _resolve_request_id(info, idx)
self._touch_request_state(request_id)
chunk_id = _coerce_optional_int(_first_present(info.get(KEY_CHUNK_ID), kv_meta.get(KEY_CHUNK_ID)))
finished = _coerce_finished(
_first_present(
meta.get("stream_finished"),
meta.get("finished"),
info.get("stream_finished"),
kv_meta.get("stream_finished"),
torch.tensor(True),
)
)
latent = _first_present(
info.get("latent"),
info.get("ming_latent_patches"),
kv_meta.get("ming_latent_patches"),
)
stripped = bool(
_first_present(
meta.get("_ming_payload_stripped"),
info.get("_ming_payload_stripped"),
kv_meta.get("_ming_payload_stripped"),
False,
)
)
if stripped:
raise RuntimeError(
"Ming Stage-2 payload was stripped before model entry. "
f"request_id={request_id} chunk_id={chunk_id} keys={sorted(info.keys())}"
)
if latent is None:
if has_ming_context and not finished:
raise RuntimeError(
"Ming Stage-2 received no latent chunk for an unfinished request. "
f"request_id={request_id} chunk_id={chunk_id} keys={sorted(info.keys())}"
)
if finished:
self._clear_request_state(request_id)
outputs.append(empty)
srs.append(sr_tensor)
continue
latent_tensor = _coerce_latent_chunk(
latent,
device=self.vllm_config.device_config.device,
dtype=next(self.audio.parameters()).dtype,
latent_dim=self.ming_config.latent_dim,
patch_size=self.ming_config.patch_size,
)
if latent_tensor is None or latent_tensor.numel() == 0:
if not finished:
raise RuntimeError(
"Ming Stage-2 received an empty latent chunk before final flush. "
f"request_id={request_id} chunk_id={chunk_id} latent_shape={_shape_of(latent_tensor)}"
)
if finished:
self._clear_request_state(request_id)
outputs.append(empty)
srs.append(sr_tensor)
continue
patch_count = int(latent_tensor.shape[0])
waveform_flat, stream_state, past_key_values, had_past_key_values, had_stream_state = (
self.chunked_decode_streaming(
latent_tensor,
request_id=request_id,
finished=finished,
)
)
total_patch_count = self._patch_totals.get(request_id, 0) + patch_count
total_waveform_numel = self._sample_totals.get(request_id, 0) + int(waveform_flat.numel())
self._patch_totals[request_id] = total_patch_count
self._sample_totals[request_id] = total_waveform_numel
if (had_past_key_values or had_stream_state) and waveform_flat.numel() == 0 and not finished:
raise RuntimeError(
"Ming Stage-2 produced an empty waveform after cached streaming state already existed. "
f"request_id={request_id} chunk_id={chunk_id} latent_shape={tuple(latent_tensor.shape)} "
f"had_past_key_values={had_past_key_values} had_stream_state={had_stream_state}"
)
if finished:
if os.environ.get("MING_TTS_STAGE1_FINAL_LOG") == "1":
logger.info(
"MING_STAGE1_FINAL %s",
{
"request_id": request_id,
"chunk_id": chunk_id,
"stop_reason": _first_present(
info.get(MING_STOP_REASON_KEY),
kv_meta.get(MING_STOP_REASON_KEY),
),
"final_decode_step": _coerce_optional_int(
_first_present(
info.get(MING_FINAL_DECODE_STEP_KEY),
kv_meta.get(MING_FINAL_DECODE_STEP_KEY),
)
),
"final_chunk_patch_count": patch_count,
"total_patch_count": total_patch_count,
"final_chunk_waveform_numel": int(waveform_flat.numel()),
"total_waveform_numel": total_waveform_numel,
},
)
self._clear_request_state(request_id)
else:
self._past_key_values[request_id] = past_key_values
self._stream_state[request_id] = stream_state
outputs.append(waveform_flat)
srs.append(sr_tensor)
return OmniOutput(
text_hidden_states=None,
multimodal_outputs={
"model_outputs": outputs,
"sr": srs,
},
)
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
weights = list(weights)
if not weights:
raise RuntimeError("MingAudioVAEModel received no checkpoint weights.")
params_dict = dict(self.named_parameters(remove_duplicate=False))
loaded: set[str] = set()
skipped: list[str] = []
for name, loaded_weight in weights:
if name not in params_dict:
skipped.append(name)
continue
param = params_dict[name]
weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, loaded_weight)
loaded.add(name)
missing = {name for name in params_dict if name.startswith("audio.")} - loaded
if missing:
raise RuntimeError(f"MingAudioVAEModel: {len(missing)} params not loaded. First few: {sorted(missing)[:5]}")
if skipped:
warnings.warn(
f"MingAudioVAEModel: skipped {len(skipped)} checkpoint keys during load. First few: {skipped[:8]}",
stacklevel=2,
)
return loaded
def _clear_request_state(self, request_id: str) -> None:
self._past_key_values.pop(request_id, None)
self._stream_state.pop(request_id, None)
self._patch_totals.pop(request_id, None)
self._sample_totals.pop(request_id, None)
self._state_access_times.pop(request_id, None)
def _touch_request_state(self, request_id: str) -> None:
now = time.monotonic()
self._evict_expired_request_states(now)
self._state_access_times[request_id] = now
self._state_access_times.move_to_end(request_id)
while len(self._state_access_times) > _MAX_STREAM_STATES:
evicted_request_id, _ = self._state_access_times.popitem(last=False)
self._clear_request_state(evicted_request_id)
def _evict_expired_request_states(self, now: float) -> None:
cutoff = now - _STREAM_STATE_TTL_SECONDS
while self._state_access_times:
request_id, last_access = next(iter(self._state_access_times.items()))
if last_access >= cutoff:
break
self._clear_request_state(request_id)
def _coerce_finished(value: Any) -> bool:
if isinstance(value, torch.Tensor):
if value.numel() == 0:
return False
return bool(value.reshape(-1)[0].item())
return bool(value)
def _first_present(*values: Any) -> Any:
for value in values:
if value is not None:
return value
return None
def _coerce_optional_int(value: Any, default: int | None = None) -> int | None:
if value is None:
return default
if isinstance(value, list):
value = value[0] if value else default
if isinstance(value, torch.Tensor):
if value.numel() == 0:
return default
return int(value.reshape(-1)[0].item())
try:
return int(value)
except (TypeError, ValueError):
return default
def _resolve_request_id(info: dict[str, Any], idx: int) -> str:
kv_meta = info.get("kv_metadata") or {}
request_id = info.get(KEY_REQUEST_ID) or kv_meta.get(KEY_REQUEST_ID)
if request_id is None:
return str(idx)
if not isinstance(request_id, str) or not request_id:
raise RuntimeError(f"Ming Stage-2 received invalid request id: {request_id!r}")
return request_id
def _has_ming_context(info: dict[str, Any]) -> bool:
kv_meta = info.get("kv_metadata") or {}
keys = (
KEY_REQUEST_ID,
KEY_CHUNK_ID,
"ming_latent_patches",
"_ming_payload_stripped",
)
return any(key in info or key in kv_meta for key in keys) or "latent" in info
def _shape_of(value: Any) -> tuple[int, ...] | None:
if isinstance(value, torch.Tensor):
return tuple(value.shape)
return None
def _has_stream_state(value: Any) -> bool:
if not isinstance(value, tuple):
return False
return any(item is not None for item in value)
def _coerce_latent_chunk(
value: Any,
*,
device: torch.device,
dtype: torch.dtype,
latent_dim: int,
patch_size: int,
) -> torch.Tensor | None:
if not isinstance(value, torch.Tensor):
value = torch.as_tensor(value)
latent = value.detach()
if latent.ndim == 2:
latent = latent.unsqueeze(0)
if latent.ndim != 3:
raise ValueError(f"Expected latent chunk rank-3 [Batch, Time, Dimension], got {tuple(latent.shape)}")
if latent.shape[-2] != patch_size:
raise ValueError(f"Latent patch size mismatch: got {latent.shape[-2]}, expected {patch_size}")
if latent.shape[-1] != latent_dim:
raise ValueError(f"Latent dim mismatch: got {latent.shape[-1]}, expected {latent_dim}")
return latent.to(device=device, dtype=dtype)
@@ -0,0 +1,414 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# Adopted from https://github.com/inclusionAI/Ming-omni-tts/blob/main/modeling_bailingmm.py
from __future__ import annotations
import warnings
from collections.abc import Iterable
from typing import Any
import torch
import torch.nn as nn
from vllm.config import VllmConfig
from vllm.logger import init_logger
from vllm.model_executor.models.qwen2 import Qwen2Model
from vllm.model_executor.models.utils import AutoWeightsLoader, WeightsMapper, maybe_prefix
from vllm.sequence import IntermediateTensors
from vllm.v1.outputs import SamplerOutput
from vllm.v1.sample.metadata import SamplingMetadata
from vllm_omni.model_executor.models.output_templates import OmniOutput
from .aggregator import Aggregator
from .config_ming_tts import (
KEY_CFG,
KEY_DECODE_STEP,
KEY_LATENT_HISTORY,
KEY_MAX_DECODE_STEPS,
KEY_MIN_DECODE_STEPS,
KEY_NEXT_EMBEDS,
KEY_REQUEST_ID,
KEY_SIGMA,
KEY_TEMPERATURE,
KEY_TEXT_MODE,
MingTTSConfig,
)
from .flowloss_head import FlowLoss
from .patch_emission import (
MING_STOP_REASON_CODES,
MING_STOP_REASON_CONTINUE,
MING_STOP_REASON_KEY,
MING_STOP_REASON_MAX_DECODE_STEPS,
MING_STOP_REASON_STOP_HEAD,
_coerce_latent_history,
_get_request_token_counts,
_normalize_request_infos,
_resolve_ming_stop_decision,
_resolve_optional_runtime_int,
_resolve_runtime_float,
_resolve_runtime_int,
_validate_ming_decode_window,
)
logger = init_logger(__name__)
class MingLLMModel(nn.Module):
hf_to_vllm_mapper = WeightsMapper(
orig_to_new_prefix={
"model.model.": "model.",
}
)
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
super().__init__()
self.ming_config = MingTTSConfig.from_hf_config(vllm_config.model_config.hf_config)
self.ming_config.validate()
self.vllm_config = vllm_config
self.prefix = prefix
self.fm_dtype = _resolve_ming_runtime_dtype(vllm_config)
self.model = Qwen2Model(vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model"))
self.linear_proj_audio = Aggregator(
in_channels=self.ming_config.latent_dim,
llm_input_dim=self.ming_config.llm_hidden_size,
**self.ming_config.aggregator_config,
)
self.flowloss = FlowLoss(
z_channels=self.ming_config.latent_dim,
llm_cond_dim=self.ming_config.llm_hidden_size,
**self.ming_config.ditar_config,
)
self.stop_head = nn.Linear(self.ming_config.llm_hidden_size, 2, bias=True)
self.spk_head = nn.Linear(192, self.ming_config.llm_hidden_size, bias=True)
self.flowloss.to(dtype=self.fm_dtype)
self.linear_proj_audio.to(dtype=self.fm_dtype)
self.stop_head.to(dtype=self.fm_dtype)
self.spk_head.to(dtype=self.fm_dtype)
self._pending_postprocess_updates: dict[str, dict[str, Any]] = {}
self._last_ming_next_token_ids = None
self._last_text_mode = False
def embed_input_ids(
self, input_ids: torch.Tensor, inputs_embeds: torch.Tensor | None = None, **_: Any
) -> torch.Tensor:
if inputs_embeds is not None:
return inputs_embeds
return self.model.embed_input_ids(input_ids)
def project_speaker_embedding(self, spk_emb: torch.Tensor) -> torch.Tensor:
return self.spk_head(spk_emb)
def forward(
self,
input_ids: torch.Tensor,
positions: torch.Tensor,
intermediate_tensors: IntermediateTensors | None = None,
inputs_embeds: torch.Tensor | None = None,
latent_history: torch.Tensor | None = None,
model_intermediate_buffer: list[dict[str, Any]] | None = None,
seq_token_counts: list[int] | None = None,
**kwargs: object,
) -> OmniOutput | IntermediateTensors | torch.Tensor:
if inputs_embeds is None:
inputs_embeds = self.embed_input_ids(input_ids)
if model_intermediate_buffer is None:
model_intermediate_buffer = kwargs.get("runtime_additional_information")
request_infos = _normalize_request_infos(model_intermediate_buffer)
_validate_ming_decode_window(
request_infos,
min_stop_step=int(self.ming_config.stop_head_min_steps),
default_max_decode_steps=self.ming_config.max_decode_steps,
)
backbone_out = self.model(
input_ids=input_ids,
positions=positions,
intermediate_tensors=intermediate_tensors,
inputs_embeds=inputs_embeds,
)
if isinstance(backbone_out, IntermediateTensors):
self._last_ming_next_token_ids = None
return backbone_out
hidden_states = _extract_hidden_states(backbone_out)
token_counts = _get_request_token_counts(hidden_states, request_infos, seq_token_counts)
text_mode = bool(request_infos) and all(bool(info.get(KEY_TEXT_MODE, False)) for info in request_infos)
if request_infos and any(bool(info.get(KEY_TEXT_MODE, False)) for info in request_infos) and not text_mode:
raise RuntimeError("Mixed Ming text/audio modes in one Stage-0 batch are unsupported.")
if text_mode:
self._last_ming_next_token_ids = None
self._last_text_mode = True
return OmniOutput(
text_hidden_states=hidden_states,
multimodal_outputs={KEY_TEXT_MODE: True},
intermediate_tensors=intermediate_tensors,
)
self._last_text_mode = False
if latent_history is None and not token_counts:
self._last_ming_next_token_ids = None
return OmniOutput(
text_hidden_states=hidden_states, multimodal_outputs=None, intermediate_tensors=intermediate_tensors
)
if latent_history is not None and not token_counts:
token_counts, request_infos = [hidden_states.shape[0]], [{KEY_LATENT_HISTORY: latent_history}]
total_tokens = hidden_states.shape[0]
latent_patch_tokens = next_embed_tokens = new_history_tokens = None
decode_step_tokens = has_patch = stop_reason_code_tokens = None
next_token_ids = []
pending_updates: dict[str, dict[str, Any]] = {}
cursor = 0
any_decode = False
for req_idx, token_count in enumerate(token_counts):
end = min(cursor + token_count, total_tokens)
if end <= cursor:
continue
req_info = request_infos[req_idx] if req_idx < len(request_infos) else {}
req_id = req_info.get(KEY_REQUEST_ID)
req_history = _coerce_latent_history(
req_info.get(KEY_LATENT_HISTORY), device=hidden_states.device, dtype=self.fm_dtype, cfg=self.ming_config
)
if req_history is None:
cursor = end
continue
decode_step = int(req_info.get(KEY_DECODE_STEP, req_info.get("generated_len", 0)))
decode_hidden, output_index = (
(hidden_states[cursor:end], cursor) if token_count == 1 else (hidden_states[end - 1 : end], end - 1)
)
sampled_token_latent, next_embeds, new_history, stop_probs = self._decode_one_step(
hidden_states=decode_hidden,
latent_history=req_history,
cfg_scale=_resolve_runtime_float(req_info, KEY_CFG, self.ming_config.cfg),
sigma=_resolve_runtime_float(req_info, KEY_SIGMA, self.ming_config.sigma),
temperature=_resolve_runtime_float(req_info, KEY_TEMPERATURE, self.ming_config.temperature),
)
req_max_decode_steps = _resolve_runtime_int(
req_info, KEY_MAX_DECODE_STEPS, self.ming_config.max_decode_steps
)
req_min_decode_steps = _resolve_optional_runtime_int(req_info, KEY_MIN_DECODE_STEPS, 0)
if latent_patch_tokens is None:
latent_patch_tokens = sampled_token_latent.new_zeros(
(total_tokens, self.ming_config.patch_size, self.ming_config.latent_dim)
)
next_embed_tokens = next_embeds.new_zeros((total_tokens, 1, self.ming_config.llm_hidden_size))
new_history_tokens = new_history.new_zeros(
(total_tokens, self.ming_config.history_patch_size, self.ming_config.latent_dim)
)
decode_step_tokens = torch.zeros((total_tokens,), dtype=torch.int32, device=hidden_states.device)
has_patch = torch.zeros((total_tokens,), dtype=torch.bool, device=hidden_states.device)
stop_reason_code_tokens = torch.zeros((total_tokens,), dtype=torch.int32, device=hidden_states.device)
latent_patch_tokens[output_index : output_index + 1] = sampled_token_latent
next_embed_tokens[output_index : output_index + 1] = next_embeds
new_history_tokens[output_index : output_index + 1] = new_history
decode_step_tokens[output_index : output_index + 1] = decode_step
has_patch[output_index : output_index + 1] = True
stop_reason, _, _, _, next_token_id = _resolve_ming_stop_decision(
step=decode_step,
stop_prob=float(stop_probs.reshape(-1)[0].item()),
stop_threshold=float(self.ming_config.stop_head_threshold),
min_stop_step=int(self.ming_config.stop_head_min_steps),
min_decode_steps=req_min_decode_steps,
max_decode_steps=req_max_decode_steps,
audio_dummy_token_id=int(self.ming_config.audio_dummy_token_id),
text_eos_token_id=int(self.ming_config.text_eos_token_id),
)
stop_reason_code_tokens[output_index : output_index + 1] = MING_STOP_REASON_CODES[stop_reason]
next_token_ids.append(int(next_token_id))
if isinstance(req_id, str):
pending_updates[req_id] = {
KEY_LATENT_HISTORY: new_history,
KEY_NEXT_EMBEDS: next_embeds,
"ming_latent_patch": sampled_token_latent,
"ming_stop_prob": stop_probs,
MING_STOP_REASON_KEY: stop_reason,
}
any_decode = True
cursor = end
self._pending_postprocess_updates = pending_updates
if not any_decode:
self._last_ming_next_token_ids = None
return OmniOutput(
text_hidden_states=hidden_states, multimodal_outputs=None, intermediate_tensors=intermediate_tensors
)
self._last_ming_next_token_ids = next_token_ids
return OmniOutput(
text_hidden_states=hidden_states,
multimodal_outputs={
"ming_latent_patch": latent_patch_tokens,
"ming_next_embeds": next_embed_tokens,
"ming_new_history": new_history_tokens,
"ming_decode_step": decode_step_tokens,
"ming_has_patch": has_patch,
MING_STOP_REASON_KEY: stop_reason_code_tokens,
},
intermediate_tensors=intermediate_tensors,
)
def pop_postprocess_update(self, req_id: str) -> dict[str, Any]:
return self._pending_postprocess_updates.pop(req_id, {}) if isinstance(req_id, str) else {}
def compute_logits(self, hidden_states: torch.Tensor, sampling_metadata: SamplingMetadata) -> torch.Tensor | None:
del sampling_metadata
if self._last_text_mode:
return (
None
if hidden_states is None or hidden_states.numel() == 0
else self.model.compute_logits(hidden_states)
)
if hidden_states is None or hidden_states.numel() == 0:
return None
if hidden_states.dim() != 2:
raise RuntimeError(
f"Expected hidden_states rank-2 [B,H] in compute_logits, got {tuple(hidden_states.shape)}"
)
batch_size = hidden_states.shape[0]
next_token_ids = self._last_ming_next_token_ids
self._last_ming_next_token_ids = None
if next_token_ids is None:
logger.debug(
"Missing Ming forced next-token ids before compute_logits. "
"Using dummy next-token IDs (likely during a profiling or dummy run)."
)
next_token_ids = [int(self.ming_config.text_eos_token_id)] * batch_size
if len(next_token_ids) != batch_size:
raise RuntimeError(
"Ming forced next-token batch mismatch: "
f"got {len(next_token_ids)} ids for hidden_states batch {batch_size}."
)
logits = torch.full(
(batch_size, self.ming_config.llm_vocab_size),
float("-inf"),
device=hidden_states.device,
dtype=torch.float32,
)
for i, next_token_id in enumerate(next_token_ids):
logits[i, int(next_token_id)] = 0.0
return logits
def sample(self, logits, sampling_metadata):
if logits is None:
return None
if self._last_text_mode:
return self.model.sample(logits, sampling_metadata)
del sampling_metadata
return SamplerOutput(
sampled_token_ids=logits.argmax(dim=-1, keepdim=True).to(dtype=torch.int32),
logprobs_tensors=None,
)
def _decode_one_step(
self,
*,
hidden_states: torch.Tensor,
latent_history: torch.Tensor,
cfg_scale: float,
sigma: float,
temperature: float,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
if hidden_states.dim() != 2:
raise RuntimeError(f"Expected decode hidden_states rank-2 [B,H], got {tuple(hidden_states.shape)}")
if latent_history.dim() != 3:
raise RuntimeError(f"Expected latent_history rank-3 [B,T,D], got {tuple(latent_history.shape)}")
if hidden_states.shape[0] != latent_history.shape[0]:
raise RuntimeError(
"Batch mismatch: "
f"hidden_states B={hidden_states.shape[0]} "
f"vs latent_history B={latent_history.shape[0]}"
)
# [Batch, Hidden] -> [Batch, Time, Hidden] = [B, 1, H] for FlowLoss conditioning.
z_diff_cond = hidden_states.to(dtype=self.fm_dtype).unsqueeze(1)
if not torch.isfinite(z_diff_cond).all():
raise RuntimeError("Non-finite z_diff_cond before FlowLoss.sample().")
sampled_token_latent = self.flowloss.sample(
z=z_diff_cond,
latent_history=latent_history,
cfg=cfg_scale,
patch_size=self.ming_config.patch_size,
sigma=sigma,
temperature=temperature,
)
expected_shape = (hidden_states.shape[0], self.ming_config.patch_size, self.ming_config.latent_dim)
if tuple(sampled_token_latent.shape) != expected_shape:
raise RuntimeError(
f"FlowLoss output shape mismatch: got {tuple(sampled_token_latent.shape)}, expected {expected_shape}"
)
new_history = torch.cat([latent_history[:, self.ming_config.patch_size :, :], sampled_token_latent], dim=1)
# Aggregator expects [Batch, Time, Dimension] = [B, 4, 64] and returns [B, 1, H].
next_embeds = self.linear_proj_audio(sampled_token_latent)
stop_probs = self.stop_head(hidden_states.to(dtype=self.fm_dtype)).softmax(dim=-1)[:, 1]
if not torch.isfinite(sampled_token_latent).all():
raise RuntimeError("Non-finite sampled_token_latent in Ming decode step.")
if not torch.isfinite(next_embeds).all():
raise RuntimeError("Non-finite next_embeds in Ming decode step.")
if not torch.isfinite(stop_probs).all():
raise RuntimeError("Non-finite stop_probs in Ming decode step.")
return sampled_token_latent, next_embeds, new_history, stop_probs
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
params_dict = dict(self.named_parameters(remove_duplicate=False))
loaded_params = AutoWeightsLoader(self).load_weights(weights, mapper=self.hf_to_vllm_mapper)
_warn_missing_prefix("flowloss", params_dict, loaded_params, prefix="flowloss.", fatal=True)
_warn_missing_prefix("linear_proj_audio", params_dict, loaded_params, prefix="linear_proj_audio.", fatal=True)
_warn_missing_prefix("stop_head", params_dict, loaded_params, prefix="stop_head.", fatal=True)
_warn_missing_prefix("spk_head", params_dict, loaded_params, prefix="spk_head.", fatal=True)
return loaded_params
def _extract_hidden_states(backbone_out: object) -> torch.Tensor:
if isinstance(backbone_out, torch.Tensor):
return backbone_out
if hasattr(backbone_out, "last_hidden_state"):
return backbone_out.last_hidden_state
if isinstance(backbone_out, (tuple, list)) and backbone_out and isinstance(backbone_out[0], torch.Tensor):
return backbone_out[0]
raise TypeError(f"Unsupported backbone forward output type: {type(backbone_out)}")
def _resolve_ming_runtime_dtype(vllm_config: VllmConfig) -> torch.dtype:
dtype = getattr(vllm_config.model_config, "dtype", None)
if isinstance(dtype, torch.dtype):
return dtype
if isinstance(dtype, str):
normalized = dtype.strip().lower()
if normalized in ("float16", "half", "torch.float16"):
return torch.float16
if normalized in ("bfloat16", "bf16", "torch.bfloat16"):
return torch.bfloat16
if normalized in ("float32", "fp32", "torch.float32"):
return torch.float32
return torch.float32
def _warn_missing_prefix(
module_name: str,
params_dict: dict[str, nn.Parameter],
loaded_params: set[str],
prefix: str,
fatal: bool = False,
) -> None:
missing = {key for key in params_dict if key.startswith(prefix)} - loaded_params
if not missing:
return
msg = (
f"MingLLMModel: {len(missing)} {module_name} params not loaded "
f"(prefix={prefix}). First few: {sorted(missing)[:5]}"
)
if fatal:
raise RuntimeError(msg)
warnings.warn(msg, stacklevel=3)
__all__ = [
"Aggregator",
"FlowLoss",
"MING_STOP_REASON_CODES",
"MING_STOP_REASON_CONTINUE",
"MING_STOP_REASON_KEY",
"MING_STOP_REASON_MAX_DECODE_STEPS",
"MING_STOP_REASON_STOP_HEAD",
"MingLLMModel",
"_coerce_latent_history",
"_extract_hidden_states",
"_resolve_ming_runtime_dtype",
"_resolve_ming_stop_decision",
"_warn_missing_prefix",
]
@@ -0,0 +1,183 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# Adopted from https://github.com/inclusionAI/Ming-omni-tts/blob/main/modeling_bailingmm.py
from __future__ import annotations
from typing import Any
import torch
from vllm.forward_context import get_forward_context, is_forward_context_available
from .config_ming_tts import KEY_MAX_DECODE_STEPS, KEY_MIN_DECODE_STEPS, KEY_REQUEST_ID, KEY_TEXT_MODE, MingTTSConfig
MING_STOP_REASON_CONTINUE = "continue"
MING_STOP_REASON_STOP_HEAD = "stop_head"
MING_STOP_REASON_MAX_DECODE_STEPS = "max_decode_steps"
MING_STOP_REASON_KEY = "ming_stop_reason"
MING_STOP_REASON_CODES = {
MING_STOP_REASON_CONTINUE: 0,
MING_STOP_REASON_STOP_HEAD: 1,
MING_STOP_REASON_MAX_DECODE_STEPS: 2,
}
def _normalize_request_infos(model_intermediate_buffer: object) -> list[dict[str, Any]]:
if not isinstance(model_intermediate_buffer, list):
return []
infos: list[dict[str, Any]] = []
for item in model_intermediate_buffer:
infos.append(item if isinstance(item, dict) else {})
return infos
def _get_request_token_counts(
hidden_states: torch.Tensor,
request_infos: list[dict[str, Any]],
seq_token_counts: list[int] | None,
) -> list[int]:
if seq_token_counts:
return [int(x) for x in seq_token_counts]
if is_forward_context_available():
slices = getattr(get_forward_context(), "ubatch_slices", None)
if slices is not None and len(slices) > 0:
counts: list[int] = []
for item in slices:
if isinstance(item, int):
counts.append(int(item))
elif hasattr(item, "stop") and hasattr(item, "start"):
counts.append(int(item.stop) - int(item.start))
if counts:
return counts
if request_infos:
if len(request_infos) == hidden_states.shape[0]:
return [1] * hidden_states.shape[0]
return [hidden_states.shape[0]]
return []
def _coerce_latent_history(
value: object,
*,
device: torch.device,
dtype: torch.dtype,
cfg: MingTTSConfig,
) -> torch.Tensor | None:
if value is None:
return None
if not isinstance(value, torch.Tensor):
value = torch.as_tensor(value)
history = value.detach()
if history.ndim == 2:
history = history.unsqueeze(0)
if history.ndim != 3:
raise RuntimeError(f"Expected latent_history rank-3 [B,T,D], got {tuple(history.shape)}")
if history.shape[1] != cfg.history_patch_size or history.shape[2] != cfg.latent_dim:
raise RuntimeError(
f"latent_history shape mismatch: got {tuple(history.shape)}, "
f"expected [B,{cfg.history_patch_size},{cfg.latent_dim}]"
)
return history.to(device=device, dtype=dtype)
def _resolve_runtime_float(req_info: dict[str, Any], key: str, default_value: float) -> float:
raw = req_info.get(key, default_value)
try:
value = float(raw)
except (TypeError, ValueError) as exc:
raise RuntimeError(f"Invalid {key}: expected float-like value, got {raw!r}") from exc
if not value >= 0.0:
raise RuntimeError(f"Invalid {key}: expected non-negative value, got {value}")
return value
def _resolve_runtime_int(req_info: dict[str, Any], key: str, default_value: int) -> int:
raw = req_info.get(key, default_value)
try:
value = int(raw)
except (TypeError, ValueError) as exc:
raise RuntimeError(f"Invalid {key}: expected int-like value, got {raw!r}") from exc
if value <= 0:
raise RuntimeError(f"Invalid {key}: expected positive value, got {value}")
return value
def _resolve_optional_runtime_int(req_info: dict[str, Any], key: str, default_value: int) -> int:
raw = req_info.get(key, default_value)
try:
value = int(raw)
except (TypeError, ValueError) as exc:
raise RuntimeError(f"Invalid {key}: expected int-like value, got {raw!r}") from exc
if value < 0:
raise RuntimeError(f"Invalid {key}: expected non-negative value, got {value}")
return value
def _validate_ming_decode_window(
request_infos: list[dict[str, Any]],
*,
min_stop_step: int,
default_max_decode_steps: int,
) -> None:
for i, info in enumerate(request_infos):
if info.get(KEY_TEXT_MODE):
continue
max_steps = _resolve_runtime_int(info, KEY_MAX_DECODE_STEPS, default_max_decode_steps)
min_steps = _resolve_optional_runtime_int(info, KEY_MIN_DECODE_STEPS, 0)
min_required = max(min_stop_step + 1, min_steps)
if max_steps < min_required:
req_id = info.get(KEY_REQUEST_ID, f"idx={i}")
raise ValueError(
f"Ming request {req_id!r}: max_decode_steps={max_steps} < "
f"min_required_decode_steps={min_required} "
f"(min_stop_step={min_stop_step}, min_decode_steps={min_steps})"
)
def _resolve_ming_stop_decision(
*,
step: int,
stop_prob: float,
stop_threshold: float,
min_stop_step: int,
min_decode_steps: int,
max_decode_steps: int,
audio_dummy_token_id: int,
text_eos_token_id: int,
) -> tuple[str, bool, bool, int, int]:
min_required_decode_steps = max(min_stop_step + 1, min_decode_steps)
if max_decode_steps < min_required_decode_steps:
raise RuntimeError(
"Invalid Ming decode window: "
f"max_decode_steps={max_decode_steps} is smaller than "
f"min_required_decode_steps={min_required_decode_steps}"
)
should_force_stop = (step + 1) >= max_decode_steps
should_stop_head = ((step + 1) >= min_required_decode_steps) and stop_prob > stop_threshold
if should_force_stop:
return (
MING_STOP_REASON_MAX_DECODE_STEPS,
True,
True,
min_required_decode_steps,
text_eos_token_id,
)
if should_stop_head:
return (
MING_STOP_REASON_STOP_HEAD,
True,
False,
min_required_decode_steps,
text_eos_token_id,
)
return (
MING_STOP_REASON_CONTINUE,
False,
False,
min_required_decode_steps,
audio_dummy_token_id,
)
@@ -0,0 +1,54 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Ming TTS pipeline: Stage-0 LLM+flow -> Stage-1 audio VAE."""
from vllm_omni.config.stage_config import (
PipelineConfig,
StageExecutionType,
StagePipelineConfig,
)
_PROC = "vllm_omni.model_executor.stage_input_processors.ming_tts"
MING_TTS_PIPELINE = PipelineConfig(
model_type="ming_tts",
model_arch="MingTTSForConditionalGeneration",
hf_architectures=("MingTTSForConditionalGeneration",),
stages=(
StagePipelineConfig(
stage_id=0,
model_stage="llm",
execution_type=StageExecutionType.LLM_AR,
input_sources=(),
owns_tokenizer=True,
hf_config_name="llm_config",
engine_output_type="latent",
async_chunk_process_next_stage_input_func=(f"{_PROC}.llm2audio_vae_async_chunk"),
sampling_constraints={
"temperature": 0.0,
"top_p": 1.0,
"top_k": -1,
"max_tokens": 512,
"detokenize": True,
},
),
StagePipelineConfig(
stage_id=1,
model_stage="audio_vae",
execution_type=StageExecutionType.LLM_GENERATION,
input_sources=(0,),
final_output=True,
final_output_type="audio",
hf_config_name="llm_config",
engine_output_type="audio",
sync_process_input_func=f"{_PROC}.llm2audio_vae",
sampling_constraints={
"temperature": 0.0,
"top_p": 1.0,
"top_k": -1,
"max_tokens": 1,
"detokenize": False,
},
),
),
)
@@ -0,0 +1,274 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
import copy
import json
import math
import re
from typing import Any
import torch
from .audio_prep import (
coerce_speaker_embeddings,
count_prompt_latent_patches,
count_prompt_waveform_patches,
pad_prompt_waveform,
)
from .config_ming_tts import (
AUDIO_FRAME_HOP,
KEY_MAX_DECODE_STEPS,
KEY_MIN_DECODE_STEPS,
KEY_PROMPT_LATENTS,
KEY_REQUEST_ID,
KEY_SPEAKER_EMBEDDING,
LATENT_DIM,
PATCH_SIZE,
SAMPLE_RATE,
VAE_PATCH_SIZE,
)
DEFAULT_PROMPT = "Please generate speech based on the following description.\n"
BASE_CAPTION_TEMPLATE: dict[str, Any] = {
"audio_sequence": [
{
"序号": 1,
"说话人": "speaker_1",
"方言": None,
"风格": None,
"语速": None,
"基频": None,
"音量": None,
"情感": None,
"BGM": {
"Genre": None,
"Mood": None,
"Instrument": None,
"Theme": None,
"ENV": None,
"SNR": None,
},
"IP": None,
}
]
}
_DURATION_SECONDS_RE = re.compile(r"Duration:\s*([0-9]+(?:\.[0-9]+)?)\s*s\b", re.IGNORECASE)
def create_instruction(user_input: Any) -> str | None:
if user_input is None:
return None
if isinstance(user_input, str):
return user_input
if not isinstance(user_input, dict):
raise ValueError(f"Ming instruction must be str, dict, or None; got {type(user_input).__name__}")
caption = copy.deepcopy(BASE_CAPTION_TEMPLATE)
item = caption["audio_sequence"][0]
for key, value in user_input.items():
if key in item:
item[key] = value
return json.dumps(caption, ensure_ascii=False)
def parse_duration_seconds(text: str | None) -> float | None:
if not isinstance(text, str):
return None
match = _DURATION_SECONDS_RE.search(text)
if match is None:
return None
try:
value = float(match.group(1))
except ValueError:
return None
if value <= 0.0:
return None
return value
def estimate_decode_steps_for_duration(
duration_seconds: float,
*,
sample_rate: int = SAMPLE_RATE,
frame_hop: int = AUDIO_FRAME_HOP,
patch_size: int = PATCH_SIZE,
vae_patch_size: int = VAE_PATCH_SIZE,
) -> int:
if duration_seconds <= 0.0:
return 0
samples_per_decode_step = int(frame_hop) * int(patch_size) * int(vae_patch_size)
required_samples = float(duration_seconds) * float(sample_rate)
return max(1, int(math.ceil(required_samples / float(samples_per_decode_step))))
def estimate_decode_step_window_for_duration(duration_seconds: float) -> tuple[int, int]:
target_steps = estimate_decode_steps_for_duration(duration_seconds)
min_steps = max(1, target_steps - 3)
max_steps = max(min_steps, target_steps + 3)
return min_steps, max_steps
def resolve_effective_runtime_controls(
*,
text: str,
runtime_controls: dict[str, Any] | None = None,
) -> dict[str, Any]:
controls = {} if runtime_controls is None else dict(runtime_controls)
has_explicit_min = KEY_MIN_DECODE_STEPS in controls and controls[KEY_MIN_DECODE_STEPS] is not None
has_explicit_max = KEY_MAX_DECODE_STEPS in controls and controls[KEY_MAX_DECODE_STEPS] is not None
if has_explicit_min or has_explicit_max:
return controls
duration_seconds = parse_duration_seconds(text)
if duration_seconds is None:
return controls
min_decode_steps, max_decode_steps = estimate_decode_step_window_for_duration(duration_seconds)
controls[KEY_MIN_DECODE_STEPS] = min_decode_steps
controls[KEY_MAX_DECODE_STEPS] = max_decode_steps
return controls
def build_dense_prompt_token_ids(
tokenizer: Any,
*,
prompt: str,
text: str,
instruction: str | None = None,
prompt_text: str | None = None,
speaker_count: int = 0,
prompt_patch_count: int = 0,
) -> list[int]:
speaker_prompt = []
for idx in range(int(speaker_count)):
speaker_prompt.extend(
tokenizer.encode(f" speaker_{idx + 1}:")
+ tokenizer.encode("<|vision_start|>")
+ tokenizer.encode("<|vision_pad|>")
+ tokenizer.encode("<|vision_end|>\n")
)
instruction_prompt = (
tokenizer.encode(instruction) + tokenizer.encode("<|endoftext|>") if instruction is not None else []
)
prompt_text_tokens = (
tokenizer.encode(prompt_text) if int(prompt_patch_count) > 0 and prompt_text is not None else []
)
audio_patch_token_id = tokenizer.convert_tokens_to_ids("<audioPatch>")
if audio_patch_token_id == tokenizer.unk_token_id:
raise ValueError("Ming tokenizer is missing required <audioPatch> token.")
prompt_latent_tokens = [audio_patch_token_id] * int(prompt_patch_count)
text_input_prefix = (
[]
if all(token in text for token in ("Genre: ", "Mood: ", "Instrument: ", "Theme: ", "Duration: "))
else tokenizer.encode(" Text input:\n")
)
return (
tokenizer.encode("<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n")
+ tokenizer.encode("<|im_start|>user\n")
+ tokenizer.encode(prompt)
+ speaker_prompt
+ text_input_prefix
+ prompt_text_tokens
+ tokenizer.encode(text)
+ tokenizer.encode("<|im_end|>\n")
+ tokenizer.encode("<|im_start|>assistant\n")
+ instruction_prompt
+ tokenizer.encode("<audio>")
+ prompt_latent_tokens
)
def build_ming_dense_prompt(
tokenizer: Any,
*,
prompt: str,
text: str,
runtime_controls: dict[str, Any] | None = None,
instruction: Any = None,
prompt_text: str | None = None,
prompt_waveform: Any = None,
prompt_latents: Any = None,
speaker_embedding: Any = None,
use_zero_spk_emb: bool = False,
request_id: str | None = None,
) -> dict[str, Any]:
instruction_text = create_instruction(instruction)
speaker_embeddings = coerce_speaker_embeddings(speaker_embedding, use_zero_spk_emb=use_zero_spk_emb)
effective_runtime_controls = resolve_effective_runtime_controls(text=text, runtime_controls=runtime_controls)
prompt_waveform_tensor = None
prompt_patch_count = 0
if prompt_waveform is not None:
prompt_waveform_tensor = pad_prompt_waveform(prompt_waveform)
prompt_patch_count = count_prompt_waveform_patches(prompt_waveform_tensor)
if prompt_waveform_tensor is not None and prompt_latents is not None:
raise ValueError(
"Ming waveform cloning request provided both raw prompt_waveform and explicit prompt_latents. "
"Choose exactly one source of truth."
)
prompt_latent_value = None
if prompt_waveform_tensor is not None and prompt_text is None:
raise ValueError(
"Ming prompt_waveform requires prompt_text for prompt-latent conditioning. "
"Use speaker_embedding for reference-audio-only speaker conditioning."
)
if prompt_latents is not None:
prompt_latent_value = torch.as_tensor(prompt_latents)
prompt_patch_count = count_prompt_latent_patches(
prompt_latent_value, patch_size=PATCH_SIZE, latent_dim=LATENT_DIM
)
prompt_token_ids = build_dense_prompt_token_ids(
tokenizer,
prompt=prompt,
text=text,
instruction=instruction_text,
prompt_text=prompt_text if prompt_patch_count > 0 else None,
speaker_count=0 if speaker_embeddings is None else len(speaker_embeddings),
prompt_patch_count=prompt_patch_count,
)
additional_information = {}
for key, value in effective_runtime_controls.items():
if isinstance(value, torch.Tensor):
additional_information[key] = value
elif key in (KEY_MIN_DECODE_STEPS, KEY_MAX_DECODE_STEPS):
additional_information[key] = torch.tensor(int(value), dtype=torch.int32)
else:
additional_information[key] = torch.tensor(float(value), dtype=torch.float32)
if request_id is not None:
additional_information[KEY_REQUEST_ID] = request_id
if instruction_text is not None:
additional_information["instruction"] = instruction_text
if prompt_text is not None:
additional_information["prompt_text"] = prompt_text
if prompt_waveform_tensor is not None:
additional_information["prompt_waveform"] = prompt_waveform_tensor
additional_information["prompt_waveform_length"] = torch.tensor(
[int(prompt_waveform_tensor.shape[-1])], dtype=torch.int32
)
if prompt_latent_value is not None:
additional_information[KEY_PROMPT_LATENTS] = prompt_latent_value
if speaker_embeddings is not None:
additional_information[KEY_SPEAKER_EMBEDDING] = (
speaker_embeddings[0] if len(speaker_embeddings) == 1 else torch.stack(speaker_embeddings, dim=0)
)
if use_zero_spk_emb:
additional_information["use_zero_spk_emb"] = True
return {
"prompt": prompt,
"text": text,
"prompt_token_ids": prompt_token_ids,
"additional_information": additional_information,
}
__all__ = [
"DEFAULT_PROMPT",
"build_dense_prompt_token_ids",
"build_ming_dense_prompt",
"create_instruction",
"estimate_decode_step_window_for_duration",
"estimate_decode_steps_for_duration",
"parse_duration_seconds",
"resolve_effective_runtime_controls",
]
@@ -0,0 +1,174 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
import json
import threading
import time
from pathlib import Path
from typing import Any
import torch
from safetensors import safe_open
from vllm.logger import init_logger
from vllm_omni.engine.stage_init_utils import _resolve_model_to_local_path
from vllm_omni.model_executor.models.common.ming.audio_vae import AudioVAE
from .audio_prep import (
_coerce_prompt_latents,
_normalize_prompt_waveform,
count_prompt_latent_patches,
pad_prompt_waveform,
)
from .config_ming_tts import KEY_PROMPT_LATENTS
logger = init_logger(__name__)
_PROMPT_ENCODER_LOAD_LOCK = threading.Lock()
def _resolve_prompt_latents(wrapper: Any, info_dict: dict[str, Any]) -> dict[str, torch.Tensor] | None:
raw_latents = info_dict.get(KEY_PROMPT_LATENTS, info_dict.get("prompt_latents"))
raw_waveform = info_dict.get("prompt_waveform", info_dict.get("prompt_waveforms"))
if raw_latents is not None and raw_waveform is not None:
raise ValueError(
"Ming waveform cloning request provided both raw prompt_waveform and explicit prompt_latents. "
"Choose exactly one source of truth."
)
direct_latents = _coerce_prompt_latents(
raw_latents,
patch_size=wrapper.ming_config.patch_size,
latent_dim=wrapper.ming_config.latent_dim,
)
if direct_latents is not None:
return direct_latents
if raw_waveform is None:
return None
encode_fn = getattr(wrapper, "_encode_prompt_waveform_to_latents", None)
if callable(encode_fn):
latents = encode_fn(raw_waveform, info_dict.get("prompt_waveform_length"))
else:
latents = _encode_prompt_waveform_to_latents(
wrapper,
raw_waveform,
info_dict.get("prompt_waveform_length"),
)
return _coerce_prompt_latents(
latents,
patch_size=wrapper.ming_config.patch_size,
latent_dim=wrapper.ming_config.latent_dim,
)
def _load_prompt_encoder(wrapper: Any) -> AudioVAE:
if wrapper._prompt_encoder is not None:
return wrapper._prompt_encoder
with _PROMPT_ENCODER_LOAD_LOCK:
if wrapper._prompt_encoder is not None:
return wrapper._prompt_encoder
if wrapper.ming_config.audio_tokenizer_config is None:
raise RuntimeError("Ming Stage-0 requires audio_tokenizer_config to encode prompt audio.")
load_start = time.perf_counter()
encoder = AudioVAE(wrapper.ming_config.audio_tokenizer_config).eval()
state_dict = encoder.state_dict()
loaded = 0
loaded_encoder_params = set()
with torch.no_grad():
for shard_path in _iter_model_safetensors(
_resolve_model_to_local_path(str(wrapper.vllm_config.model_config.model))
):
with safe_open(str(shard_path), framework="pt", device="cpu") as handle:
for key in handle.keys():
if not key.startswith("audio.encoder."):
continue
name = key[len("audio.") :]
if name not in state_dict:
continue
target = state_dict[name]
target.copy_(handle.get_tensor(key).to(device=target.device, dtype=target.dtype))
loaded += 1
loaded_encoder_params.add(name)
if loaded == 0:
raise RuntimeError("Ming prompt encoder received no audio.encoder.* weights from checkpoint.")
expected_encoder_params = {f"encoder.{name}" for name, _ in encoder.encoder.named_parameters()}
missing = expected_encoder_params - loaded_encoder_params
if missing:
raise RuntimeError(
f"Ming prompt encoder: {len(missing)} params not loaded. First few: {sorted(missing)[:5]}"
)
dev = next(wrapper.parameters()).device
try:
del encoder.decoder
encoder.decoder = None
if dev.type != "cpu":
encoder.encoder.to(dev, dtype=getattr(wrapper.model, "fm_dtype", torch.bfloat16))
else:
encoder.encoder.to(dev)
except Exception as exc:
raise RuntimeError(f"Failed to move Ming prompt encoder to {dev}: {exc}") from exc
wrapper._prompt_encoder = encoder
logger.info("Ming prompt encoder cold-loaded in %.3f ms", (time.perf_counter() - load_start) * 1000.0)
return encoder
@torch.inference_mode()
def _encode_prompt_waveform_to_latents(wrapper: Any, waveform: Any, waveform_length: Any = None) -> torch.Tensor:
encoder = _load_prompt_encoder(wrapper)
waveform = _normalize_prompt_waveform(waveform, target_sr=wrapper.ming_config.sample_rate)
waveform = pad_prompt_waveform(
waveform,
patch_size=wrapper.ming_config.patch_size,
sample_rate=wrapper.ming_config.sample_rate,
)
dev = next(encoder.encoder.parameters()).device
waveform = waveform.to(device=dev, dtype=next(encoder.encoder.parameters()).dtype)
if waveform_length is None:
waveform_length = torch.full((waveform.shape[0],), waveform.shape[-1], dtype=torch.int32, device=dev)
elif not isinstance(waveform_length, torch.Tensor):
waveform_length = torch.as_tensor(waveform_length, dtype=torch.int32, device=dev)
else:
waveform_length = waveform_length.to(device=dev, dtype=torch.int32)
latents, _ = encoder.encode_latent(waveform, waveform_length)
if latents.ndim == 3 and latents.shape[0] == 1:
latents = latents.squeeze(0)
count_prompt_latent_patches(
latents,
patch_size=wrapper.ming_config.patch_size,
latent_dim=wrapper.ming_config.latent_dim,
)
return latents.detach().to(dtype=torch.float32).contiguous()
def _iter_model_safetensors(local_model_path: str) -> list[Path]:
model_root = Path(local_model_path)
index_path = model_root / "model.safetensors.index.json"
if index_path.exists():
with index_path.open("r", encoding="utf-8") as handle:
index_data = json.load(handle)
filenames = sorted(set(index_data.get("weight_map", {}).values()))
if not filenames:
raise RuntimeError(f"No checkpoint shards listed in {index_path}")
return [model_root / filename for filename in filenames]
single_file = model_root / "model.safetensors"
if single_file.exists():
return [single_file]
files = sorted(model_root.glob("*.safetensors"))
if not files:
raise RuntimeError(f"No .safetensors checkpoint found under {local_model_path}")
return files
__all__ = [
"_encode_prompt_waveform_to_latents",
"_iter_model_safetensors",
"_load_prompt_encoder",
"_resolve_prompt_latents",
]
@@ -0,0 +1,51 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# Adopted from https://github.com/inclusionAI/Ming-omni-tts/blob/main/spkemb_extractor.py
import os
import torch
import torchaudio
from vllm.multimodal.media.audio import load_audio
from vllm_omni.model_executor.models.ming_flash_omni.spk_embedding import SpkembExtractor
def resolve_model_to_local_path(model):
if os.path.isdir(model):
return model
from huggingface_hub import snapshot_download
return snapshot_download(model)
class MingSpeakerEmbeddingExtractor:
def __init__(self, model, target_sr=16000):
local_model_path = resolve_model_to_local_path(model)
campplus_path = os.path.join(local_model_path, "campplus.onnx")
if not os.path.exists(campplus_path):
raise RuntimeError(f"Missing Ming speaker extractor model: {campplus_path}")
self.target_sr = int(target_sr)
self._core = SpkembExtractor(campplus_path, target_sr=self.target_sr)
def extract_from_waveform(self, waveform, sample_rate):
if not isinstance(waveform, torch.Tensor):
waveform = torch.as_tensor(waveform)
tensor = waveform.detach().to(torch.float32)
if tensor.ndim == 1:
tensor = tensor.unsqueeze(0)
if int(sample_rate) != self.target_sr:
tensor = torchaudio.transforms.Resample(orig_freq=int(sample_rate), new_freq=self.target_sr)(tensor)
embedding = self._core._extract_spk_embedding(tensor)
return embedding.squeeze(0).to(dtype=torch.float32)
def extract_from_file(self, audio_path):
audio, sample_rate = load_audio(audio_path, sr=None, mono=False)
waveform = torch.from_numpy(audio).contiguous()
return self.extract_from_waveform(waveform, sample_rate)
def extract_many(self, audio_paths):
return [self.extract_from_file(path) for path in audio_paths]
@@ -0,0 +1,172 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
from typing import Any
from transformers import PretrainedConfig
from vllm_omni.model_executor.models.common.ming.audio_vae import AudioVAEConfig
from .constants import (
AGGREGATOR_HIDDEN_SIZE,
HISTORY_PATCH_SIZE,
LATENT_DIM,
LLM_HIDDEN_SIZE,
LLM_VOCAB_SIZE,
PATCH_SIZE,
SAMPLE_RATE,
)
def _to_plain_dict(obj: Any) -> dict[str, Any]:
"""Normalize nested config objects into plain dicts when possible."""
if obj is None:
return {}
if isinstance(obj, dict):
return dict(obj)
if isinstance(obj, PretrainedConfig):
return obj.to_dict()
if hasattr(obj, "to_dict") and callable(obj.to_dict):
try:
return dict(obj.to_dict())
except Exception:
pass
try:
return dict(vars(obj))
except Exception:
return {}
def _coerce_audio_vae_config(atc_raw: Any) -> AudioVAEConfig | None:
if atc_raw is None:
return None
if isinstance(atc_raw, AudioVAEConfig):
return atc_raw
if isinstance(atc_raw, PretrainedConfig):
atc_dict = atc_raw.to_dict()
elif isinstance(atc_raw, dict):
atc_dict = dict(atc_raw)
elif hasattr(atc_raw, "to_dict") and callable(atc_raw.to_dict):
atc_dict = dict(atc_raw.to_dict())
else:
raise TypeError(f"Unsupported audio_tokenizer_config type for Ming dense config: {type(atc_raw)!r}")
return AudioVAEConfig(**atc_dict)
def _nested_get(obj: Any, *keys: str, default: Any = None) -> Any:
"""Safe nested attribute/key access for dicts and config-like objects."""
cur = obj
for key in keys:
if cur is None:
return default
if isinstance(cur, dict):
cur = cur.get(key)
else:
cur = getattr(cur, key, None)
return cur if cur is not None else default
def validate_ming_tts_config(cfg: Any) -> None:
"""Run before GPU allocation/weight loading. Raises ValueError on mismatches."""
if cfg.audio_dummy_token_id != 151705:
raise ValueError(
f"audio_dummy_token_id={cfg.audio_dummy_token_id}, expected 151705 (<audioPatch>). "
"Wrong tokenizer/checkpoint?"
)
if cfg.audio_eos_token_id != 151704:
raise ValueError(
f"audio_eos_token_id={cfg.audio_eos_token_id}, expected 151704 (<end_of_audio>). "
"Wrong tokenizer/checkpoint?"
)
if cfg.text_eos_token_id != 151669:
raise ValueError(
f"text_eos_token_id={cfg.text_eos_token_id}, expected 151669 (<text_eos>). Wrong tokenizer/checkpoint?"
)
if cfg.audio_tokenizer_config is None:
raise ValueError("audio_tokenizer_config is None. Nested AudioVAE config was not deserialized correctly.")
if cfg.latent_dim != LATENT_DIM:
raise ValueError(
f"latent_dim mismatch: got {cfg.latent_dim}, expected {LATENT_DIM}. "
"Check audio_tokenizer_config.enc_kwargs.latent_dim."
)
if cfg.patch_size != PATCH_SIZE:
raise ValueError(
f"patch_size mismatch: got {cfg.patch_size}, expected {PATCH_SIZE}. Check ditar_config.patch_size."
)
if cfg.history_patch_size != HISTORY_PATCH_SIZE:
raise ValueError(
f"history_patch_size mismatch: got {cfg.history_patch_size}, expected {HISTORY_PATCH_SIZE}. "
"Check ditar_config.history_patch_size."
)
if cfg.llm_hidden_size != LLM_HIDDEN_SIZE:
raise ValueError(
f"llm_hidden_size mismatch: got {cfg.llm_hidden_size}, expected {LLM_HIDDEN_SIZE}. "
"Check llm_config.hidden_size."
)
if cfg.llm_vocab_size != LLM_VOCAB_SIZE:
raise ValueError(f"llm_vocab_size mismatch: got {cfg.llm_vocab_size}, expected {LLM_VOCAB_SIZE}.")
if cfg.sample_rate != SAMPLE_RATE:
raise ValueError(f"sample_rate mismatch: got {cfg.sample_rate}, expected {SAMPLE_RATE}.")
if cfg.vae_patch_size != cfg.patch_size:
raise ValueError(f"VAE patch size ({cfg.vae_patch_size}) != flow/DiT patch size ({cfg.patch_size}).")
llm_hidden_from_cfg = cfg.llm_config.get("hidden_size")
if llm_hidden_from_cfg is not None and llm_hidden_from_cfg != cfg.llm_hidden_size:
raise ValueError(f"llm_hidden_size ({cfg.llm_hidden_size}) != llm_config.hidden_size ({llm_hidden_from_cfg}).")
agg_h = cfg.aggregator_config.get("hidden_size")
dit_h = cfg.ditar_config.get("hidden_size")
if agg_h is not None and dit_h is not None and agg_h != dit_h:
raise ValueError(f"aggregator_config.hidden_size ({agg_h}) != ditar_config.hidden_size ({dit_h}).")
if agg_h is not None and agg_h != AGGREGATOR_HIDDEN_SIZE:
raise ValueError(f"aggregator hidden_size mismatch: got {agg_h}, expected {AGGREGATOR_HIDDEN_SIZE}.")
if dit_h is not None and dit_h != AGGREGATOR_HIDDEN_SIZE:
raise ValueError(f"ditar hidden_size mismatch: got {dit_h}, expected {AGGREGATOR_HIDDEN_SIZE}.")
atc = cfg.audio_tokenizer_config
semantic_module_kwargs = _nested_get(atc, "semantic_module_kwargs", default=None)
if semantic_module_kwargs is not None:
raise ValueError("Ming dense 0.5B expects audio_tokenizer_config.semantic_module_kwargs to be null.")
enc_latent = _nested_get(atc, "enc_kwargs", "latent_dim", default=None)
dec_latent = _nested_get(atc, "dec_kwargs", "latent_dim", default=None)
if enc_latent is not None and enc_latent != cfg.latent_dim:
raise ValueError(f"audio enc latent_dim ({enc_latent}) != Ming latent_dim ({cfg.latent_dim}).")
if dec_latent is not None and dec_latent != cfg.latent_dim:
raise ValueError(f"audio dec latent_dim ({dec_latent}) != Ming latent_dim ({cfg.latent_dim}).")
atc_patch = _nested_get(atc, "patch_size", default=None)
if atc_patch is not None and atc_patch != cfg.vae_patch_size:
raise ValueError(f"audio_tokenizer_config.patch_size ({atc_patch}) != vae_patch_size ({cfg.vae_patch_size}).")
atc_sr = _nested_get(atc, "sample_rate", default=None)
if atc_sr is not None and atc_sr != cfg.sample_rate:
raise ValueError(f"audio_tokenizer_config.sample_rate ({atc_sr}) != sample_rate ({cfg.sample_rate}).")
enc_input_dim = _nested_get(atc, "enc_kwargs", "input_dim", default=None)
enc_hop_size = _nested_get(atc, "enc_kwargs", "hop_size", default=None)
dec_output_dim = _nested_get(atc, "dec_kwargs", "output_dim", default=None)
if enc_input_dim is not None and enc_hop_size is not None and enc_input_dim != enc_hop_size:
raise ValueError(f"AudioVAE encoder input_dim ({enc_input_dim}) != hop_size ({enc_hop_size}).")
if enc_hop_size is not None and dec_output_dim is not None and enc_hop_size != dec_output_dim:
raise ValueError(
f"AudioVAE encoder hop_size ({enc_hop_size}) != decoder output_dim ({dec_output_dim}). "
"Expected 882 in this checkpoint family."
)
if cfg.latent_chunk_size <= 0:
raise ValueError(f"latent_chunk_size must be > 0, got {cfg.latent_chunk_size}.")
if cfg.latent_left_context < 0:
raise ValueError(f"latent_left_context must be >= 0, got {cfg.latent_left_context}.")
if cfg.max_decode_steps <= 0:
raise ValueError(f"max_decode_steps must be > 0, got {cfg.max_decode_steps}.")
if not (0.0 <= cfg.stop_head_threshold <= 1.0):
raise ValueError(f"stop_head_threshold must be in [0,1], got {cfg.stop_head_threshold}.")
if cfg.stop_head_min_steps < 0:
raise ValueError(f"stop_head_min_steps must be >= 0, got {cfg.stop_head_min_steps}.")
@@ -156,6 +156,22 @@ _OMNI_MODELS = {
"mimo_audio_code2wav",
"MiMoAudioToken2WavForConditionalGenerationVLLM",
),
## ming-tts
"MingTTSForConditionalGeneration": (
"ming_tts",
"ming_tts",
"MingTTSForConditionalGeneration",
),
"MingLLMModel": (
"ming_tts",
"ming_tts_llm",
"MingLLMModel",
),
"MingAudioVAEModel": (
"ming_tts",
"ming_tts_audio_vae",
"MingAudioVAEModel",
),
## glm_image
"GlmImageForConditionalGeneration": (
"glm_image",