add VibeVoice-Realtime
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
""" VibeVoice_AcousticTokenizer model configuration"""
|
||||
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
from transformers.models.qwen2.configuration_qwen2 import Qwen2Config
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class VibeVoiceAcousticTokenizerConfig(PretrainedConfig):
|
||||
model_type = "vibevoice_acoustic_tokenizer"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
channels: int = 1,
|
||||
corpus_normalize: float = 0.0,
|
||||
causal: bool = True,
|
||||
vae_dim: int = 64,
|
||||
fix_std: float = 0.5,
|
||||
std_dist_type: str = 'gaussian',
|
||||
# common
|
||||
mixer_layer: str = 'depthwise_conv',
|
||||
conv_norm: str = 'none',
|
||||
pad_mode: str = 'constant',
|
||||
disable_last_norm: bool = True,
|
||||
layernorm: str = 'RMSNorm',
|
||||
layernorm_eps: float = 1e-5,
|
||||
layernorm_elementwise_affine: bool = True,
|
||||
conv_bias: bool = True,
|
||||
layer_scale_init_value: float = 1e-6,
|
||||
weight_init_value: float = 1e-2,
|
||||
# encoder specific
|
||||
encoder_n_filters: int = 32,
|
||||
encoder_ratios: Optional[List[int]] = [8,5,5,4,2,2],
|
||||
encoder_depths: str = "3-3-3-3-3-3-8",
|
||||
# decoder specific
|
||||
decoder_n_filters: int = 32,
|
||||
decoder_ratios: Optional[List[int]] = None, # if None, same as encoder
|
||||
decoder_depths: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.channels = channels
|
||||
self.corpus_normalize = corpus_normalize
|
||||
self.causal = causal
|
||||
self.vae_dim = vae_dim
|
||||
self.fix_std = fix_std
|
||||
self.std_dist_type = std_dist_type
|
||||
|
||||
# common parameters
|
||||
self.conv_norm = conv_norm
|
||||
self.pad_mode = pad_mode
|
||||
self.layernorm_eps = layernorm_eps
|
||||
self.disable_last_norm = disable_last_norm
|
||||
self.layernorm = layernorm
|
||||
self.layernorm_elementwise_affine = layernorm_elementwise_affine
|
||||
self.conv_bias = conv_bias
|
||||
self.layer_scale_init_value = layer_scale_init_value
|
||||
self.weight_init_value = weight_init_value
|
||||
self.mixer_layer = mixer_layer
|
||||
|
||||
# encoder specific parameters
|
||||
self.encoder_n_filters = encoder_n_filters
|
||||
self.encoder_ratios = encoder_ratios
|
||||
self.encoder_depths = encoder_depths
|
||||
|
||||
# decoder specific parameters
|
||||
self.decoder_ratios = decoder_ratios if decoder_ratios is not None else encoder_ratios
|
||||
self.decoder_n_filters = decoder_n_filters
|
||||
self.decoder_depths = decoder_depths
|
||||
|
||||
|
||||
class VibeVoiceSemanticTokenizerConfig(PretrainedConfig):
|
||||
model_type = "vibevoice_semantic_tokenizer"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
channels: int = 1,
|
||||
corpus_normalize: float = 0.0,
|
||||
causal: bool = True,
|
||||
vae_dim: int = 64,
|
||||
fix_std: float = 0,
|
||||
std_dist_type: str = 'none',
|
||||
# common
|
||||
mixer_layer: str = 'depthwise_conv',
|
||||
conv_norm: str = 'none',
|
||||
pad_mode: str = 'constant',
|
||||
disable_last_norm: bool = True,
|
||||
layernorm: str = 'RMSNorm',
|
||||
layernorm_eps: float = 1e-5,
|
||||
layernorm_elementwise_affine: bool = True,
|
||||
conv_bias: bool = True,
|
||||
layer_scale_init_value: float = 1e-6,
|
||||
weight_init_value: float = 1e-2,
|
||||
# encoder specific
|
||||
encoder_n_filters: int = 32,
|
||||
encoder_ratios: Optional[List[int]] = [8,5,5,4,2,2],
|
||||
encoder_depths: str = "3-3-3-3-3-3-8",
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.channels = channels
|
||||
self.corpus_normalize = corpus_normalize
|
||||
self.causal = causal
|
||||
self.vae_dim = vae_dim
|
||||
self.fix_std = fix_std
|
||||
self.std_dist_type = std_dist_type
|
||||
|
||||
# common parameters
|
||||
self.conv_norm = conv_norm
|
||||
self.pad_mode = pad_mode
|
||||
self.layernorm_eps = layernorm_eps
|
||||
self.disable_last_norm = disable_last_norm
|
||||
self.layernorm = layernorm
|
||||
self.layernorm_elementwise_affine = layernorm_elementwise_affine
|
||||
self.conv_bias = conv_bias
|
||||
self.layer_scale_init_value = layer_scale_init_value
|
||||
self.weight_init_value = weight_init_value
|
||||
self.mixer_layer = mixer_layer
|
||||
|
||||
# encoder specific parameters
|
||||
self.encoder_n_filters = encoder_n_filters
|
||||
self.encoder_ratios = encoder_ratios
|
||||
self.encoder_depths = encoder_depths
|
||||
|
||||
|
||||
class VibeVoiceDiffusionHeadConfig(PretrainedConfig):
|
||||
model_type = "vibevoice_diffusion_head"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size=768,
|
||||
head_layers=4,
|
||||
head_ffn_ratio=3.0,
|
||||
rms_norm_eps=1e-5,
|
||||
latent_size=64,
|
||||
speech_vae_dim=None,
|
||||
prediction_type="v_prediction",
|
||||
diffusion_type="ddpm",
|
||||
ddpm_num_steps=1000,
|
||||
ddpm_num_inference_steps=20,
|
||||
ddpm_beta_schedule="cosine",
|
||||
ddpm_batch_mul=4,
|
||||
**kwargs
|
||||
):
|
||||
self.hidden_size = hidden_size
|
||||
self.head_layers = head_layers
|
||||
self.head_ffn_ratio = head_ffn_ratio
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.latent_size = latent_size
|
||||
self.speech_vae_dim = speech_vae_dim
|
||||
self.prediction_type = prediction_type
|
||||
self.diffusion_type = diffusion_type
|
||||
self.ddpm_num_steps = ddpm_num_steps
|
||||
self.ddpm_num_inference_steps = ddpm_num_inference_steps
|
||||
self.ddpm_beta_schedule = ddpm_beta_schedule
|
||||
self.ddpm_batch_mul = ddpm_batch_mul
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
class VibeVoiceConfig(PretrainedConfig):
|
||||
model_type = "vibevoice"
|
||||
is_composition = True
|
||||
sub_configs = {
|
||||
"acoustic_tokenizer_config": VibeVoiceAcousticTokenizerConfig,
|
||||
"semantic_tokenizer_config": VibeVoiceSemanticTokenizerConfig,
|
||||
"decoder_config": Qwen2Config,
|
||||
"diffusion_head_config": VibeVoiceDiffusionHeadConfig,
|
||||
}
|
||||
# keys_to_ignore_at_inference = ["past_key_values"]
|
||||
# Default tensor parallel plan for base model `Qwen2`
|
||||
base_model_tp_plan = {
|
||||
"layers.*.self_attn.q_proj": "colwise",
|
||||
"layers.*.self_attn.k_proj": "colwise",
|
||||
"layers.*.self_attn.v_proj": "colwise",
|
||||
"layers.*.self_attn.o_proj": "rowwise",
|
||||
"layers.*.mlp.gate_proj": "colwise",
|
||||
"layers.*.mlp.up_proj": "colwise",
|
||||
"layers.*.mlp.down_proj": "rowwise",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
acoustic_tokenizer_config=None,
|
||||
semantic_tokenizer_config=None,
|
||||
decoder_config=None,
|
||||
diffusion_head_config=None,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
# kwargs["_attn_implementation"] = "flash_attention_2"
|
||||
kwargs["_attn_implementation_autoset"] = False
|
||||
|
||||
if acoustic_tokenizer_config is None:
|
||||
self.acoustic_tokenizer_config = self.sub_configs["acoustic_tokenizer_config"]()
|
||||
elif isinstance(acoustic_tokenizer_config, dict):
|
||||
acoustic_tokenizer_config["model_type"] = "vibevoice_acoustic_tokenizer"
|
||||
self.acoustic_tokenizer_config = self.sub_configs["acoustic_tokenizer_config"](**acoustic_tokenizer_config)
|
||||
elif isinstance(acoustic_tokenizer_config, VibeVoiceAcousticTokenizerConfig):
|
||||
# If an instance of the config class is provided
|
||||
self.acoustic_tokenizer_config = acoustic_tokenizer_config
|
||||
|
||||
if semantic_tokenizer_config is None:
|
||||
self.semantic_tokenizer_config = self.sub_configs["semantic_tokenizer_config"]()
|
||||
elif isinstance(semantic_tokenizer_config, dict):
|
||||
semantic_tokenizer_config["model_type"] = "vibevoice_semantic_tokenizer"
|
||||
self.semantic_tokenizer_config = self.sub_configs["semantic_tokenizer_config"](**semantic_tokenizer_config)
|
||||
elif isinstance(semantic_tokenizer_config, VibeVoiceSemanticTokenizerConfig):
|
||||
# If an instance of the config class is provided
|
||||
self.semantic_tokenizer_config = semantic_tokenizer_config
|
||||
|
||||
if decoder_config is None:
|
||||
self.decoder_config = self.sub_configs["decoder_config"]()
|
||||
elif isinstance(decoder_config, dict):
|
||||
# If a dictionary is provided, instantiate the config class with it
|
||||
# self.decoder_config = self.sub_configs["decoder_config"](**decoder_config)
|
||||
if decoder_config.get("model_type", '') == "qwen2":
|
||||
self.decoder_config = Qwen2Config(**decoder_config)
|
||||
else:
|
||||
raise ValueError(f"Unsupported decoder model type: {decoder_config.get('model_type', '')}")
|
||||
elif isinstance(decoder_config, (Qwen2Config,)):
|
||||
# If an instance of the config class is provided
|
||||
self.decoder_config = decoder_config
|
||||
|
||||
if diffusion_head_config is None:
|
||||
self.diffusion_head_config = self.sub_configs["diffusion_head_config"]()
|
||||
elif isinstance(diffusion_head_config, dict):
|
||||
diffusion_head_config["model_type"] = "vibevoice_diffusion_head"
|
||||
self.diffusion_head_config = self.sub_configs["diffusion_head_config"](**diffusion_head_config)
|
||||
elif isinstance(diffusion_head_config, VibeVoiceDiffusionHeadConfig):
|
||||
# If an instance of the config class is provided
|
||||
self.diffusion_head_config = diffusion_head_config
|
||||
|
||||
# other parameters
|
||||
self.acoustic_vae_dim = getattr(self.acoustic_tokenizer_config, 'vae_dim', 64)
|
||||
self.semantic_vae_dim = getattr(self.semantic_tokenizer_config, 'vae_dim', 128)
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
__all__ = [
|
||||
"VibeVoiceAcousticTokenizerConfig",
|
||||
"VibeVoiceSemanticTokenizerConfig",
|
||||
"VibeVoiceDiffusionHeadConfig",
|
||||
"VibeVoiceConfig"
|
||||
]
|
||||
@@ -0,0 +1,85 @@
|
||||
""" VibeVoice Streaming model configuration"""
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
from transformers.models.qwen2.configuration_qwen2 import Qwen2Config
|
||||
|
||||
from .configuration_vibevoice import VibeVoiceAcousticTokenizerConfig, VibeVoiceDiffusionHeadConfig
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class VibeVoiceStreamingConfig(PretrainedConfig):
|
||||
model_type = "vibevoice_streaming"
|
||||
is_composition = True
|
||||
sub_configs = {
|
||||
"acoustic_tokenizer_config": VibeVoiceAcousticTokenizerConfig,
|
||||
"decoder_config": Qwen2Config,
|
||||
"diffusion_head_config": VibeVoiceDiffusionHeadConfig,
|
||||
}
|
||||
# keys_to_ignore_at_inference = ["past_key_values"]
|
||||
# Default tensor parallel plan for base model `Qwen2`
|
||||
base_model_tp_plan = {
|
||||
"layers.*.self_attn.q_proj": "colwise",
|
||||
"layers.*.self_attn.k_proj": "colwise",
|
||||
"layers.*.self_attn.v_proj": "colwise",
|
||||
"layers.*.self_attn.o_proj": "rowwise",
|
||||
"layers.*.mlp.gate_proj": "colwise",
|
||||
"layers.*.mlp.up_proj": "colwise",
|
||||
"layers.*.mlp.down_proj": "rowwise",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
acoustic_tokenizer_config=None,
|
||||
decoder_config=None,
|
||||
diffusion_head_config=None,
|
||||
tts_backbone_num_hidden_layers=20,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
# kwargs["_attn_implementation"] = "flash_attention_2"
|
||||
kwargs["_attn_implementation_autoset"] = False
|
||||
|
||||
if acoustic_tokenizer_config is None:
|
||||
self.acoustic_tokenizer_config = self.sub_configs["acoustic_tokenizer_config"]()
|
||||
elif isinstance(acoustic_tokenizer_config, dict):
|
||||
acoustic_tokenizer_config["model_type"] = "vibevoice_acoustic_tokenizer"
|
||||
self.acoustic_tokenizer_config = self.sub_configs["acoustic_tokenizer_config"](**acoustic_tokenizer_config)
|
||||
elif isinstance(acoustic_tokenizer_config, VibeVoiceAcousticTokenizerConfig):
|
||||
# If an instance of the config class is provided
|
||||
self.acoustic_tokenizer_config = acoustic_tokenizer_config
|
||||
|
||||
if decoder_config is None:
|
||||
self.decoder_config = self.sub_configs["decoder_config"]()
|
||||
elif isinstance(decoder_config, dict):
|
||||
# If a dictionary is provided, instantiate the config class with it
|
||||
# self.decoder_config = self.sub_configs["decoder_config"](**decoder_config)
|
||||
if decoder_config.get("model_type", '') == "qwen2":
|
||||
self.decoder_config = Qwen2Config(**decoder_config)
|
||||
else:
|
||||
raise ValueError(f"Unsupported decoder model type: {decoder_config.get('model_type', '')}")
|
||||
elif isinstance(decoder_config, (Qwen2Config,)):
|
||||
# If an instance of the config class is provided
|
||||
self.decoder_config = decoder_config
|
||||
|
||||
if diffusion_head_config is None:
|
||||
self.diffusion_head_config = self.sub_configs["diffusion_head_config"]()
|
||||
elif isinstance(diffusion_head_config, dict):
|
||||
diffusion_head_config["model_type"] = "vibevoice_diffusion_head"
|
||||
self.diffusion_head_config = self.sub_configs["diffusion_head_config"](**diffusion_head_config)
|
||||
elif isinstance(diffusion_head_config, VibeVoiceDiffusionHeadConfig):
|
||||
# If an instance of the config class is provided
|
||||
self.diffusion_head_config = diffusion_head_config
|
||||
|
||||
# other parameters
|
||||
self.acoustic_vae_dim = getattr(self.acoustic_tokenizer_config, 'vae_dim', 64)
|
||||
# The decoder of the model is divided into two components. The lower Transformer layers are only used for encoding text, while the upper Transformer layers are used for encoding text and generating speech. `tts_backbone_num_hidden_layers` indicates the number of upper layers used for TTS.
|
||||
self.tts_backbone_num_hidden_layers = tts_backbone_num_hidden_layers
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
__all__ = [
|
||||
"VibeVoiceStreamingConfig"
|
||||
]
|
||||
@@ -0,0 +1,190 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional, Tuple, Union, Callable
|
||||
from tqdm import tqdm
|
||||
import copy
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.distributed as dist
|
||||
|
||||
from transformers.models.auto import AutoModel, AutoModelForCausalLM
|
||||
|
||||
from transformers.activations import ACT2FN
|
||||
from transformers.modeling_outputs import CausalLMOutput, BaseModelOutputWithPast, ModelOutput
|
||||
from transformers.models.llama.modeling_llama import LlamaRMSNorm
|
||||
from transformers import modeling_utils
|
||||
from transformers.modeling_utils import PreTrainedModel
|
||||
from transformers.modeling_flash_attention_utils import FlashAttentionKwargs
|
||||
from transformers.utils import logging
|
||||
|
||||
from .modular_vibevoice_diffusion_head import VibeVoiceDiffusionHead
|
||||
from vibevoice.schedule.dpm_solver import DPMSolverMultistepScheduler
|
||||
|
||||
from .configuration_vibevoice_streaming import VibeVoiceStreamingConfig
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
if not hasattr(modeling_utils, "ALL_PARALLEL_STYLES") or modeling_utils.ALL_PARALLEL_STYLES is None:
|
||||
modeling_utils.ALL_PARALLEL_STYLES = ["tp", "none", "colwise", "rowwise"]
|
||||
|
||||
|
||||
class BinaryClassifier(nn.Module):
|
||||
def __init__(self, hidden_size):
|
||||
super(BinaryClassifier, self).__init__()
|
||||
self.fc1 = nn.Linear(hidden_size, hidden_size)
|
||||
self.fc2 = nn.Linear(hidden_size, 1)
|
||||
|
||||
def forward(self, x):
|
||||
x = torch.relu(self.fc1(x))
|
||||
x = self.fc2(x)
|
||||
return x
|
||||
|
||||
|
||||
class SpeechConnector(nn.Module):
|
||||
def __init__(self, input_dim, output_dim):
|
||||
super().__init__()
|
||||
self.fc1 = nn.Linear(input_dim, output_dim)
|
||||
self.norm = LlamaRMSNorm(output_dim, eps=1e-6)
|
||||
self.fc2 = nn.Linear(output_dim, output_dim)
|
||||
|
||||
def forward(self, features, **kwargs):
|
||||
x = self.fc1(features)
|
||||
x = self.norm(x)
|
||||
x = self.fc2(x)
|
||||
return x
|
||||
|
||||
|
||||
# @auto_docstring
|
||||
class VibeVoiceStreamingPreTrainedModel(PreTrainedModel):
|
||||
config_class = VibeVoiceStreamingConfig
|
||||
base_model_prefix = "model"
|
||||
supports_gradient_checkpointing = True
|
||||
_skip_keys_device_placement = "past_key_values"
|
||||
_supports_cache_class = True
|
||||
_supports_flash_attn_2 = True
|
||||
_supports_sdpa = True
|
||||
_supports_quantized_cache = True
|
||||
_supports_static_cache = True
|
||||
_supports_attention_backend = True
|
||||
|
||||
def _init_weights(self, module):
|
||||
if isinstance(module, VibeVoiceDiffusionHead):
|
||||
module.initialize_weights()
|
||||
return
|
||||
|
||||
# Use the language model's initializer_range if available
|
||||
if hasattr(self.config, 'language_model_config') and hasattr(self.config.language_model_config, 'initializer_range'):
|
||||
std = self.config.language_model_config.initializer_range
|
||||
elif hasattr(self.config, 'decoder_config') and hasattr(self.config.decoder_config, 'initializer_range'):
|
||||
std = self.config.decoder_config.initializer_range
|
||||
else:
|
||||
std = 0.02 # Default value
|
||||
|
||||
if isinstance(module, nn.Linear):
|
||||
module.weight.data.normal_(mean=0.0, std=std)
|
||||
if module.bias is not None:
|
||||
module.bias.data.zero_()
|
||||
elif isinstance(module, nn.LayerNorm):
|
||||
module.weight.data.fill_(1.0)
|
||||
module.bias.data.zero_()
|
||||
|
||||
|
||||
# @auto_docstring
|
||||
class VibeVoiceStreamingModel(VibeVoiceStreamingPreTrainedModel):
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
|
||||
if hasattr(config, 'torch_dtype') and config.torch_dtype is not None:
|
||||
if isinstance(config.torch_dtype, str):
|
||||
dtype = getattr(torch, config.torch_dtype)
|
||||
else:
|
||||
dtype = config.torch_dtype
|
||||
else:
|
||||
dtype = torch.float32
|
||||
|
||||
# Initialize Qwen2 model for language modeling.
|
||||
# The lower Transformer layers are only used for encoding text, while the upper Transformer layers are used for encoding text and generating speech.
|
||||
# To keep the code clean, we constructs two language models.
|
||||
# The final norm layer of the first language_model is set to identity and will not be used in inference.
|
||||
lm_config = copy.deepcopy(config.decoder_config)
|
||||
lm_backbone_num_hidden_layers = getattr(lm_config, 'num_hidden_layers', 24) - config.tts_backbone_num_hidden_layers
|
||||
lm_config.num_hidden_layers = lm_backbone_num_hidden_layers
|
||||
self.language_model = AutoModel.from_config(lm_config)
|
||||
self.language_model.norm = nn.Identity()
|
||||
|
||||
# We only need the Transformer layers here. Note that embed_tokens in tts_language_model is unused
|
||||
tts_lm_config = copy.deepcopy(lm_config)
|
||||
tts_lm_config.num_hidden_layers = config.tts_backbone_num_hidden_layers
|
||||
self.tts_language_model = AutoModel.from_config(tts_lm_config)
|
||||
|
||||
# Marks the text that needs to be spoken by the TTS model.
|
||||
self.tts_input_types = nn.Embedding(num_embeddings=2, embedding_dim=config.decoder_config.hidden_size)
|
||||
|
||||
# Initialize speech components if needed
|
||||
self.acoustic_tokenizer = AutoModel.from_config(config.acoustic_tokenizer_config).to(dtype)
|
||||
self.acoustic_connector = SpeechConnector(config.acoustic_vae_dim, lm_config.hidden_size).to(dtype)
|
||||
|
||||
# Register scaling factors as buffers - use 1D tensors for FSDP compatibility
|
||||
self.register_buffer('speech_scaling_factor', torch.tensor(float('nan')))
|
||||
self.register_buffer('speech_bias_factor', torch.tensor(float('nan')))
|
||||
|
||||
# Initialize prediction head for speech generation
|
||||
self.prediction_head = AutoModel.from_config(config.diffusion_head_config).to(dtype)
|
||||
|
||||
# Initialize noise scheduler
|
||||
self.noise_scheduler = DPMSolverMultistepScheduler(
|
||||
num_train_timesteps=config.diffusion_head_config.ddpm_num_steps,
|
||||
beta_schedule=config.diffusion_head_config.ddpm_beta_schedule,
|
||||
prediction_type=config.diffusion_head_config.prediction_type
|
||||
)
|
||||
|
||||
def get_input_embeddings(self):
|
||||
if hasattr(self.language_model, 'embed_tokens'):
|
||||
# If the language model has an embed_tokens attribute, return it
|
||||
return self.language_model.embed_tokens
|
||||
|
||||
for name, attr in self.language_model.fullmap.items(): # parallel by nnscaler, the name is changed
|
||||
if attr.orig_name == 'embed_tokens.weight':
|
||||
return getattr(self.language_model, name)
|
||||
assert False, 'should not arrive here'
|
||||
|
||||
def set_input_embeddings(self, value):
|
||||
self.language_model.embed_tokens = value
|
||||
|
||||
def set_speech_tokenizers(self, acoustic_tokenizer=None):
|
||||
"""Set the speech tokenizers used for encoding and decoding speech."""
|
||||
self.acoustic_tokenizer = acoustic_tokenizer
|
||||
|
||||
# Reset the encoder to evaluation mode
|
||||
if self.acoustic_tokenizer is not None:
|
||||
self.acoustic_tokenizer.eval()
|
||||
|
||||
def forward(self, *args, **kwargs):
|
||||
"""
|
||||
Intentionally not implemented.
|
||||
|
||||
This streaming model is split into two explicit submodules:
|
||||
- `language_model` for plain text processing (lower layers).
|
||||
- `tts_language_model` for TTS-related upper layers.
|
||||
|
||||
We deliberately avoid a unified `forward` to prevent accidental calls
|
||||
that mix responsibilities.
|
||||
|
||||
To use the model:
|
||||
- Call `self.language_model(...)` for text embeddings / hidden states.
|
||||
- Call `self.tts_language_model(...)` for the TTS portion.
|
||||
- Use the dedicated inference class for combined generation logic.
|
||||
"""
|
||||
raise RuntimeError(
|
||||
"VibeVoiceStreamingModel.forward is intentionally disabled. "
|
||||
"Use `model.language_model(...)` or `model.tts_language_model(...)` instead."
|
||||
)
|
||||
|
||||
|
||||
AutoModel.register(VibeVoiceStreamingConfig, VibeVoiceStreamingModel)
|
||||
|
||||
__all__ = [
|
||||
"VibeVoiceStreamingPreTrainedModel",
|
||||
"VibeVoiceStreamingModel",
|
||||
]
|
||||
@@ -0,0 +1,726 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union, Callable
|
||||
from tqdm import tqdm
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from transformers.models.auto import AutoModel, AutoModelForCausalLM
|
||||
|
||||
from transformers.generation import GenerationMixin, GenerationConfig, LogitsProcessor, LogitsProcessorList, StoppingCriteriaList
|
||||
from transformers.modeling_outputs import BaseModelOutputWithPast, ModelOutput
|
||||
from transformers import modeling_utils
|
||||
from transformers.modeling_utils import PreTrainedModel
|
||||
from transformers.modeling_flash_attention_utils import FlashAttentionKwargs
|
||||
from transformers.utils import logging
|
||||
|
||||
from .modular_vibevoice_tokenizer import VibeVoiceTokenizerStreamingCache
|
||||
from .modular_vibevoice_diffusion_head import VibeVoiceDiffusionHead
|
||||
from vibevoice.schedule.dpm_solver import DPMSolverMultistepScheduler
|
||||
from .configuration_vibevoice_streaming import VibeVoiceStreamingConfig
|
||||
from .modular_vibevoice_text_tokenizer import VibeVoiceTextTokenizer, VibeVoiceTextTokenizerFast
|
||||
from .modeling_vibevoice_streaming import VibeVoiceStreamingPreTrainedModel, VibeVoiceStreamingModel, BinaryClassifier
|
||||
from .streamer import AudioStreamer, AsyncAudioStreamer
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
if not hasattr(modeling_utils, "ALL_PARALLEL_STYLES") or modeling_utils.ALL_PARALLEL_STYLES is None:
|
||||
modeling_utils.ALL_PARALLEL_STYLES = ["tp", "none", "colwise", "rowwise"]
|
||||
|
||||
TTS_TEXT_WINDOW_SIZE = 5
|
||||
TTS_SPEECH_WINDOW_SIZE = 6
|
||||
|
||||
|
||||
def _update_model_kwargs_for_generation(
|
||||
outputs: ModelOutput,
|
||||
model_kwargs: Dict[str, Any],
|
||||
num_new_tokens: int = 1,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Update model_kwargs after adding new tokens.
|
||||
|
||||
Mainly for the case num_new_tokens > 1 (e.g. a whole text window):
|
||||
- past_key_values: take from current outputs
|
||||
- attention_mask: append num_new_tokens ones
|
||||
- cache_position: advance by creating a range for all new positions
|
||||
"""
|
||||
|
||||
# update past_key_values keeping its naming used in model code
|
||||
model_kwargs["past_key_values"] = getattr(outputs, "past_key_values")
|
||||
|
||||
attention_mask = model_kwargs["attention_mask"]
|
||||
model_kwargs["attention_mask"] = torch.cat(
|
||||
[attention_mask, attention_mask.new_ones((attention_mask.shape[0], num_new_tokens))], dim=-1
|
||||
)
|
||||
|
||||
model_kwargs["cache_position"] = torch.arange(model_kwargs["cache_position"][-1] + 1, model_kwargs["cache_position"][-1] + num_new_tokens + 1).to(model_kwargs["cache_position"].device)
|
||||
|
||||
return model_kwargs
|
||||
|
||||
|
||||
@dataclass
|
||||
class VibeVoiceCausalLMOutputWithPast(BaseModelOutputWithPast):
|
||||
logits: Optional[torch.FloatTensor] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class VibeVoiceGenerationOutput(ModelOutput):
|
||||
"""
|
||||
Output type for VibeVoice generation.
|
||||
|
||||
Args:
|
||||
sequences (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
|
||||
The generated sequences.
|
||||
speech_outputs (`List[torch.FloatTensor]`, *optional*):
|
||||
List of generated speech waveforms or latents for each speech segment.
|
||||
"""
|
||||
sequences: torch.LongTensor = None
|
||||
speech_outputs: Optional[List[torch.FloatTensor]] = None
|
||||
reach_max_step_sample: Optional[torch.BoolTensor] = None
|
||||
|
||||
|
||||
class VibeVoiceStreamingForConditionalGenerationInference(VibeVoiceStreamingPreTrainedModel, GenerationMixin):
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
|
||||
# Initialize the base model
|
||||
self.model = VibeVoiceStreamingModel(config)
|
||||
|
||||
# TTS generation EOS classifier
|
||||
self.tts_eos_classifier = BinaryClassifier(config.decoder_config.hidden_size)
|
||||
|
||||
# inference configuration
|
||||
self.ddpm_inference_steps = config.diffusion_head_config.ddpm_num_inference_steps
|
||||
|
||||
# Initialize weights and apply final processing
|
||||
self.post_init()
|
||||
|
||||
@property
|
||||
def noise_scheduler(self):
|
||||
return self.model.noise_scheduler
|
||||
|
||||
@property
|
||||
def prediction_head(self):
|
||||
return self.model.prediction_head
|
||||
|
||||
@property
|
||||
def speech_scaling_factor(self):
|
||||
return self.model.speech_scaling_factor
|
||||
|
||||
@property
|
||||
def speech_bias_factor(self):
|
||||
return self.model.speech_bias_factor
|
||||
|
||||
@property
|
||||
def acoustic_tokenizer(self):
|
||||
return self.model.acoustic_tokenizer
|
||||
|
||||
@property
|
||||
def acoustic_connector(self):
|
||||
return self.model.acoustic_connector
|
||||
|
||||
def tie_weights(self):
|
||||
"""
|
||||
Tie the weights between the input embeddings and the output embeddings.
|
||||
"""
|
||||
# Tie lm_head.weight to language_model.embed_tokens.weight
|
||||
if not getattr(self.config, 'tie_word_embeddings', False):
|
||||
return
|
||||
|
||||
if hasattr(self, 'lm_head') and hasattr(self.model.language_model, 'embed_tokens'):
|
||||
self.lm_head.weight = self.model.language_model.embed_tokens.weight
|
||||
|
||||
def get_input_embeddings(self):
|
||||
return self.model.get_input_embeddings()
|
||||
|
||||
def set_input_embeddings(self, value):
|
||||
self.model.set_input_embeddings(value)
|
||||
|
||||
def get_output_embeddings(self):
|
||||
"""
|
||||
This model does not define an `lm_head` (vocabulary projection).
|
||||
"""
|
||||
return None
|
||||
|
||||
def set_output_embeddings(self, new_embeddings):
|
||||
"""
|
||||
No-op because there is no `lm_head`. Provided only to satisfy optional API calls.
|
||||
To enable, first create `self.lm_head` then allow assignment.
|
||||
"""
|
||||
raise RuntimeError("Output embeddings (lm_head) are not defined for this model. "
|
||||
"Create one before calling set_output_embeddings if needed.")
|
||||
|
||||
def set_speech_tokenizers(self, acoustic_tokenizer=None):
|
||||
"""Set the speech tokenizers used for encoding and decoding speech."""
|
||||
self.model.set_speech_tokenizers(acoustic_tokenizer)
|
||||
|
||||
def set_ddpm_inference_steps(self, num_steps=None):
|
||||
self.ddpm_inference_steps = num_steps or self.config.diffusion_head_config.ddpm_num_inference_steps
|
||||
|
||||
# @can_return_tuple
|
||||
def forward_lm(
|
||||
self,
|
||||
input_ids: torch.LongTensor = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
position_ids: Optional[torch.LongTensor] = None,
|
||||
past_key_values: Optional[Tuple[Tuple[torch.FloatTensor]]] = None,
|
||||
inputs_embeds: Optional[torch.FloatTensor] = None,
|
||||
labels: Optional[torch.LongTensor] = None,
|
||||
use_cache: Optional[bool] = None,
|
||||
output_attentions: Optional[bool] = None,
|
||||
output_hidden_states: Optional[bool] = None,
|
||||
return_dict: Optional[bool] = None,
|
||||
cache_position: Optional[torch.LongTensor] = None,
|
||||
**kwargs,
|
||||
) -> Union[Tuple, BaseModelOutputWithPast]:
|
||||
"""
|
||||
Single pass of the base text LM.
|
||||
|
||||
- Builds embeddings if `inputs_embeds` not provided.
|
||||
- Uses (and returns) `past_key_values` when `use_cache=True`.
|
||||
- No loss / no lm_head / no speech logic.
|
||||
|
||||
Args:
|
||||
input_ids: (B, S) token ids.
|
||||
attention_mask: (B, S) mask.
|
||||
past_key_values: cache from previous steps.
|
||||
cache_position: positions for cached tokens.
|
||||
labels: unsupported (will raise).
|
||||
|
||||
Returns:
|
||||
BaseModelOutputWithPast with `last_hidden_state` and `past_key_values`.
|
||||
"""
|
||||
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
||||
|
||||
# Get embeddings
|
||||
if inputs_embeds is None:
|
||||
inputs_embeds = self.model.get_input_embeddings()(input_ids)
|
||||
|
||||
outputs = self.model.language_model(
|
||||
inputs_embeds=inputs_embeds,
|
||||
attention_mask=attention_mask,
|
||||
position_ids=position_ids,
|
||||
past_key_values=past_key_values,
|
||||
use_cache=use_cache,
|
||||
output_attentions=output_attentions,
|
||||
output_hidden_states=output_hidden_states,
|
||||
return_dict=return_dict,
|
||||
cache_position=cache_position,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
hidden_states = outputs[0] if not return_dict else outputs.last_hidden_state
|
||||
|
||||
if labels is not None:
|
||||
raise NotImplementedError("Loss computation is not implemented in this version.")
|
||||
|
||||
return BaseModelOutputWithPast(
|
||||
past_key_values=outputs.past_key_values,
|
||||
last_hidden_state=hidden_states,
|
||||
attentions=outputs.attentions,
|
||||
)
|
||||
|
||||
# @can_return_tuple
|
||||
def forward_tts_lm(
|
||||
self,
|
||||
input_ids: torch.LongTensor = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
position_ids: Optional[torch.LongTensor] = None,
|
||||
past_key_values: Optional[Tuple[Tuple[torch.FloatTensor]]] = None,
|
||||
inputs_embeds: Optional[torch.FloatTensor] = None,
|
||||
labels: Optional[torch.LongTensor] = None,
|
||||
use_cache: Optional[bool] = None,
|
||||
output_attentions: Optional[bool] = None,
|
||||
output_hidden_states: Optional[bool] = None,
|
||||
return_dict: Optional[bool] = None,
|
||||
cache_position: Optional[torch.LongTensor] = None,
|
||||
lm_last_hidden_state: Optional[torch.FloatTensor] = None,
|
||||
tts_text_masks: Optional[torch.BoolTensor] = None,
|
||||
**kwargs,
|
||||
) -> Union[Tuple, VibeVoiceCausalLMOutputWithPast]:
|
||||
"""
|
||||
Single pass of the TTS LM.
|
||||
|
||||
- Overwrites tail embeddings with `lm_last_hidden_state`.
|
||||
- Adds type embedding via `tts_text_masks` (1=text, 0=speech).
|
||||
- Predicts EOS from last hidden state (binary classifier).
|
||||
- No loss / no full acoustic decoding here.
|
||||
|
||||
Args:
|
||||
input_ids: (B, S) token ids.
|
||||
attention_mask: (B, S) mask.
|
||||
lm_last_hidden_state: (B, K, H) hidden states to splice into the tail.
|
||||
tts_text_masks: (B, 1) mask marking current position as text(1)/speech(0).
|
||||
past_key_values: cache from previous TTS steps.
|
||||
cache_position: positions for cached tokens.
|
||||
labels: unsupported (will raise).
|
||||
|
||||
Returns:
|
||||
VibeVoiceCausalLMOutputWithPast with `logits` (EOS), `last_hidden_state`, `past_key_values`.
|
||||
"""
|
||||
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
||||
|
||||
# Get embeddings
|
||||
if inputs_embeds is None:
|
||||
# Will be replaced with lm_last_hidden_state
|
||||
inputs_embeds = self.model.get_input_embeddings()(input_ids)
|
||||
|
||||
# Replace the last part of inputs_embeds with lm_last_hidden_state
|
||||
start_idx = inputs_embeds.shape[1] - lm_last_hidden_state.shape[1]
|
||||
inputs_embeds[:, start_idx:, :] = lm_last_hidden_state
|
||||
|
||||
# Adds type embedding via `tts_text_masks`.
|
||||
inputs_embeds = inputs_embeds + self.model.tts_input_types(tts_text_masks.long())
|
||||
|
||||
outputs = self.model.tts_language_model(
|
||||
inputs_embeds=inputs_embeds,
|
||||
attention_mask=attention_mask,
|
||||
position_ids=position_ids,
|
||||
past_key_values=past_key_values,
|
||||
use_cache=use_cache,
|
||||
output_attentions=output_attentions,
|
||||
output_hidden_states=output_hidden_states,
|
||||
return_dict=return_dict,
|
||||
cache_position=cache_position,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
hidden_states = outputs[0] if not return_dict else outputs.last_hidden_state
|
||||
logits = self.tts_eos_classifier(hidden_states[:, -1, :])
|
||||
|
||||
if labels is not None:
|
||||
raise NotImplementedError("Loss computation is not implemented in this version.")
|
||||
|
||||
return VibeVoiceCausalLMOutputWithPast(
|
||||
logits=logits,
|
||||
past_key_values=outputs.past_key_values,
|
||||
last_hidden_state=hidden_states,
|
||||
attentions=outputs.attentions,
|
||||
)
|
||||
|
||||
def forward(self, *args, **kwargs):
|
||||
"""
|
||||
Unified forward is intentionally disabled.
|
||||
|
||||
Reasons:
|
||||
1. The inference pipeline is staged: base text LM, then TTS LM, plus streaming & diffusion handled in `generate`.
|
||||
2. A monolithic call would hide required sequencing (prefill, window stepping, speech diffusion sampling).
|
||||
|
||||
Use instead:
|
||||
- self.forward_lm(...) for a base text LM step (prefill or incremental).
|
||||
- self.forward_tts_lm(...) for a single TTS LM step (needs LM hidden states).
|
||||
- self.generate(...) for full streaming (text + speech + diffusion + audio assembly).
|
||||
|
||||
Raises:
|
||||
RuntimeError: Always (by design).
|
||||
"""
|
||||
raise RuntimeError(
|
||||
"Unified forward is disabled. Use `forward_lm`, `forward_tts_lm`, or `generate` instead."
|
||||
)
|
||||
|
||||
def _build_generate_config_model_kwargs(self, generation_config, inputs, tokenizer, return_processors=False, **kwargs):
|
||||
if generation_config is None:
|
||||
generation_config = GenerationConfig(
|
||||
bos_token_id=tokenizer.bos_token_id,
|
||||
eos_token_id=tokenizer.eos_token_id,
|
||||
pad_token_id = tokenizer.pad_token_id
|
||||
)
|
||||
else:
|
||||
generation_config = GenerationConfig(
|
||||
**generation_config,
|
||||
bos_token_id=tokenizer.bos_token_id,
|
||||
eos_token_id=tokenizer.eos_token_id,
|
||||
pad_token_id = tokenizer.pad_token_id
|
||||
)
|
||||
|
||||
generation_config, model_kwargs = self._prepare_generation_config(
|
||||
generation_config,
|
||||
True,
|
||||
speech_start_id=tokenizer.speech_start_id,
|
||||
speech_end_id=tokenizer.speech_end_id,
|
||||
speech_diffusion_id=tokenizer.speech_diffusion_id,
|
||||
**kwargs
|
||||
)
|
||||
generation_config.speech_start_id = tokenizer.speech_start_id
|
||||
generation_config.speech_end_id = tokenizer.speech_end_id
|
||||
generation_config.speech_diffusion_id = tokenizer.speech_diffusion_id
|
||||
|
||||
inputs_tensor, model_input_name, model_kwargs = self._prepare_model_inputs(inputs, generation_config.bos_token_id, model_kwargs)
|
||||
batch_size = inputs_tensor.shape[0]
|
||||
device = self.device
|
||||
|
||||
self._prepare_special_tokens(generation_config, True, device=device)
|
||||
generation_config.use_cache = True
|
||||
model_kwargs["use_cache"] = generation_config.use_cache
|
||||
input_ids = inputs_tensor.to(self.device)
|
||||
|
||||
input_ids_length = input_ids.shape[1]
|
||||
has_default_max_length = kwargs.get("max_length") is None and generation_config.max_length is not None
|
||||
has_default_min_length = kwargs.get("min_length") is None and generation_config.min_length is not None
|
||||
generation_config = self._prepare_generated_length(
|
||||
generation_config=generation_config,
|
||||
has_default_max_length=has_default_max_length,
|
||||
has_default_min_length=has_default_min_length,
|
||||
model_input_name=model_input_name,
|
||||
inputs_tensor=inputs_tensor,
|
||||
input_ids_length=input_ids_length,
|
||||
)
|
||||
|
||||
max_cache_length = generation_config.max_length - 1
|
||||
self._prepare_cache_for_generation(generation_config, model_kwargs, None, batch_size, max_cache_length, device)
|
||||
model_kwargs['cache_position'] = torch.arange(input_ids_length, device=device, dtype=torch.long)
|
||||
for k, v in model_kwargs.items():
|
||||
if isinstance(v, torch.Tensor):
|
||||
model_kwargs[k] = v.to(device=device)
|
||||
|
||||
if return_processors:
|
||||
logits_processor = self._get_logits_processor(
|
||||
generation_config=generation_config,
|
||||
input_ids_seq_length=input_ids_length,
|
||||
encoder_input_ids=inputs_tensor,
|
||||
prefix_allowed_tokens_fn=None,
|
||||
logits_processor=LogitsProcessorList(),
|
||||
device=inputs_tensor.device,
|
||||
model_kwargs=model_kwargs,
|
||||
)
|
||||
|
||||
stopping_criteria = self._get_stopping_criteria(generation_config=generation_config, stopping_criteria=StoppingCriteriaList())
|
||||
|
||||
return generation_config, model_kwargs, input_ids, logits_processor, stopping_criteria
|
||||
else:
|
||||
return generation_config, model_kwargs, input_ids
|
||||
|
||||
@torch.no_grad()
|
||||
def generate(
|
||||
self,
|
||||
inputs: Optional[torch.Tensor] = None,
|
||||
generation_config: Optional[GenerationConfig] = None,
|
||||
logits_processor: Optional[LogitsProcessorList] = None,
|
||||
stopping_criteria: Optional[StoppingCriteriaList] = None,
|
||||
prefix_allowed_tokens_fn: Optional[Callable[[int, torch.Tensor], List[int]]] = None,
|
||||
synced_gpus: Optional[bool] = None,
|
||||
assistant_model: Optional["PreTrainedModel"] = None,
|
||||
audio_streamer: Optional[Union[AudioStreamer, AsyncAudioStreamer]] = None,
|
||||
negative_prompt_ids: Optional[torch.Tensor] = None,
|
||||
negative_prompt_attention_mask: Optional[torch.Tensor] = None,
|
||||
speech_tensors: Optional[torch.FloatTensor] = None,
|
||||
speech_masks: Optional[torch.BoolTensor] = None,
|
||||
speech_input_mask: Optional[torch.BoolTensor] = None,
|
||||
tts_text_ids: Optional[torch.LongTensor] = None,
|
||||
return_speech: bool = True,
|
||||
cfg_scale: float = 1.0,
|
||||
stop_check_fn: Optional[Callable[[], bool]] = None,
|
||||
**kwargs,
|
||||
) -> Union[torch.LongTensor, VibeVoiceGenerationOutput]:
|
||||
"""
|
||||
Text is fed in small windows (dynamic slicing of `tts_text_ids`), which enables streaming text input: you don’t need the full text upfront. After each text window, a loop samples several speech latents (diffusion). The interleaved text encoding + speech generation enables streaming text input and realtime speech output.
|
||||
The function only supports batch size = 1 currently.
|
||||
|
||||
- Windowed text prefill → incremental LM + TTS LM updates.
|
||||
- Interleave speech token diffusion sampling (`sample_speech_tokens`).
|
||||
- Stops on EOS (binary classifier) or max length / external `stop_check_fn`.
|
||||
- Returns final token `sequences` and (optionally) concatenated speech audio.
|
||||
|
||||
Args (selected):
|
||||
tts_text_ids: Full text tokens to stream in windows.
|
||||
audio_streamer: If provided, emits audio chunks during generation.
|
||||
cfg_scale: Classifier-free guidance scale for speech diffusion.
|
||||
return_speech: If False, skips audio decode concatenation.
|
||||
stop_check_fn: External early-stop hook (returns True to halt).
|
||||
|
||||
Returns:
|
||||
VibeVoiceGenerationOutput with:
|
||||
- sequences: final token ids
|
||||
- speech_outputs: list of concatenated audio tensors (or None)
|
||||
- reach_max_step_sample: flags for samples stopped by max length
|
||||
"""
|
||||
# 1. Handle `generation_config` and kwargs that might update it, and validate the `.generate()` call
|
||||
tokenizer = kwargs.pop("tokenizer", None)
|
||||
neg_text_input_id = tokenizer.convert_tokens_to_ids("<|image_pad|>")
|
||||
|
||||
tts_lm_input_ids = kwargs.pop("tts_lm_input_ids", None)
|
||||
tts_lm_attention_mask = kwargs.pop("tts_lm_attention_mask", None)
|
||||
# all_prefilled_outputs: cached prefilled prompt outputs for lm, tts_lm, neg_lm, neg_tts_lm
|
||||
all_prefilled_outputs = kwargs.pop("all_prefilled_outputs", None)
|
||||
tts_text_ids = tts_text_ids.to(self.device)
|
||||
|
||||
if kwargs.get('max_new_tokens', None) is None:
|
||||
kwargs['max_new_tokens'] = self.config.decoder_config.max_position_embeddings - tts_lm_input_ids.shape[-1]
|
||||
|
||||
generation_config, model_kwargs, input_ids, logits_processor, stopping_criteria = self._build_generate_config_model_kwargs(
|
||||
generation_config, inputs, tokenizer, return_processors=True, **kwargs
|
||||
)
|
||||
|
||||
negative_kwargs = {
|
||||
'input_ids': torch.full((kwargs['input_ids'].shape[0], 1), neg_text_input_id, dtype=torch.long, device=kwargs['input_ids'].device),
|
||||
'attention_mask': torch.ones((kwargs['input_ids'].shape[0], 1), dtype=torch.long, device=kwargs['input_ids'].device),
|
||||
'max_new_tokens': kwargs.get('max_new_tokens', 100)
|
||||
}
|
||||
negative_generation_config, negative_model_kwargs, negative_input_ids = self._build_generate_config_model_kwargs(
|
||||
None, None, tokenizer, return_processors=False, **negative_kwargs
|
||||
)
|
||||
|
||||
tts_lm_kwargs = {
|
||||
'input_ids': tts_lm_input_ids,
|
||||
'attention_mask': tts_lm_attention_mask,
|
||||
'max_new_tokens': kwargs.get('max_new_tokens', 100)
|
||||
}
|
||||
tts_lm_generation_config, tts_lm_model_kwargs, tts_lm_input_ids = self._build_generate_config_model_kwargs(
|
||||
None, None, tokenizer, return_processors=False, **tts_lm_kwargs
|
||||
)
|
||||
|
||||
tts_lm_negative_kwargs = {
|
||||
'input_ids': torch.full((kwargs['input_ids'].shape[0], 1), neg_text_input_id, dtype=torch.long, device=kwargs['input_ids'].device),
|
||||
'attention_mask': torch.ones((kwargs['input_ids'].shape[0], 1), dtype=torch.long, device=kwargs['input_ids'].device),
|
||||
'max_new_tokens': kwargs.get('max_new_tokens', 100)
|
||||
}
|
||||
tts_lm_negative_generation_config, tts_lm_negative_model_kwargs, tts_lm_negative_input_ids = self._build_generate_config_model_kwargs(
|
||||
None, None, tokenizer, return_processors=False, **tts_lm_negative_kwargs
|
||||
)
|
||||
|
||||
acoustic_cache = VibeVoiceTokenizerStreamingCache()
|
||||
batch_size = input_ids.shape[0]
|
||||
assert batch_size == 1, "Currently only supports batch size == 1"
|
||||
device = input_ids.device
|
||||
finished_tags = torch.zeros(batch_size, dtype=torch.bool, device=device)
|
||||
verbose = kwargs.get("verbose", False)
|
||||
|
||||
# Initialize audio chunks storage for each sample
|
||||
audio_chunks = [[] for _ in range(batch_size)]
|
||||
tts_text_window_index = 0
|
||||
reach_max_step_sample = torch.zeros(batch_size, dtype=torch.bool, device=device)
|
||||
first_text_window_size = TTS_TEXT_WINDOW_SIZE if tts_text_ids.shape[1] >= TTS_TEXT_WINDOW_SIZE else tts_text_ids.shape[1]
|
||||
|
||||
outputs = all_prefilled_outputs["lm"]
|
||||
tts_lm_outputs = all_prefilled_outputs["tts_lm"]
|
||||
negative_outputs = all_prefilled_outputs["neg_lm"]
|
||||
tts_lm_negative_outputs = all_prefilled_outputs["neg_tts_lm"]
|
||||
|
||||
model_kwargs = _update_model_kwargs_for_generation(
|
||||
outputs, model_kwargs, num_new_tokens=first_text_window_size,
|
||||
)
|
||||
tts_lm_model_kwargs = _update_model_kwargs_for_generation(
|
||||
tts_lm_outputs, tts_lm_model_kwargs, num_new_tokens=first_text_window_size,
|
||||
)
|
||||
negative_model_kwargs = self._update_model_kwargs_for_generation(
|
||||
negative_outputs, negative_model_kwargs, is_encoder_decoder=False,
|
||||
)
|
||||
tts_lm_negative_model_kwargs = self._update_model_kwargs_for_generation(
|
||||
tts_lm_negative_outputs, tts_lm_negative_model_kwargs, is_encoder_decoder=False,
|
||||
)
|
||||
|
||||
step = tts_lm_input_ids.shape[1]
|
||||
total_generated_speech_tokens = 0
|
||||
total_prefilled_text_tokens = 0
|
||||
if kwargs.get("show_progress_bar", True):
|
||||
progress_bar = tqdm(
|
||||
total=tts_lm_generation_config.max_length,
|
||||
desc=f"Prefilled {step} tokens, current step ({step} / {tts_lm_generation_config.max_length})",
|
||||
initial=step,
|
||||
leave=False
|
||||
)
|
||||
else:
|
||||
progress_bar = None
|
||||
|
||||
while True:
|
||||
# Check for external stop signal
|
||||
if stop_check_fn is not None and stop_check_fn():
|
||||
if verbose:
|
||||
print(f"Generation stopped externally at step {step + 1}")
|
||||
# End the audio streamer if it exists
|
||||
if audio_streamer is not None:
|
||||
audio_streamer.end()
|
||||
break
|
||||
|
||||
# # Check if audio_streamer has been ended (stopped externally)
|
||||
# if audio_streamer is not None and hasattr(audio_streamer, 'finished_flags'):
|
||||
# if any(audio_streamer.finished_flags):
|
||||
# if verbose:
|
||||
# print(f"Audio generation stopped externally at step {step + 1}")
|
||||
# break
|
||||
|
||||
if finished_tags.all():
|
||||
if hasattr(progress_bar, 'set_description'):
|
||||
progress_bar.set_description("Generation complete")
|
||||
break
|
||||
|
||||
cur_input_tts_text_ids = tts_text_ids[:, tts_text_window_index*TTS_TEXT_WINDOW_SIZE:(tts_text_window_index+1)*TTS_TEXT_WINDOW_SIZE]
|
||||
next_text_window_size = tts_text_ids[:, (tts_text_window_index+1)*TTS_TEXT_WINDOW_SIZE:(tts_text_window_index+2)*TTS_TEXT_WINDOW_SIZE].shape[1]
|
||||
tts_text_window_index += 1
|
||||
|
||||
if cur_input_tts_text_ids.shape[1] > 0:
|
||||
input_ids = torch.cat([input_ids, cur_input_tts_text_ids], dim=-1)
|
||||
tts_lm_input_ids = torch.cat([tts_lm_input_ids, cur_input_tts_text_ids], dim=-1)
|
||||
|
||||
if tts_lm_input_ids.shape[1] > tts_lm_generation_config.max_length:
|
||||
if verbose:
|
||||
print(f"Reached maximum generation length {generation_config.max_length}, stopped it.")
|
||||
reached_samples = torch.arange(batch_size, device=device)[~finished_tags]
|
||||
if reached_samples.numel() > 0:
|
||||
reach_max_step_sample[reached_samples] = True
|
||||
break
|
||||
|
||||
step += cur_input_tts_text_ids.shape[1]
|
||||
total_prefilled_text_tokens += cur_input_tts_text_ids.shape[1]
|
||||
if progress_bar is not None:
|
||||
progress_bar.update(cur_input_tts_text_ids.shape[1])
|
||||
progress_bar.set_description(f"Prefilled {total_prefilled_text_tokens} text tokens, generated {total_generated_speech_tokens} speech tokens, current step ({step} / {tts_lm_generation_config.max_length})")
|
||||
|
||||
model_inputs = self.prepare_inputs_for_generation(input_ids, **model_kwargs)
|
||||
# Forward pass through the model
|
||||
outputs = self.forward_lm(
|
||||
**model_inputs, return_dict=True, output_attentions=False, output_hidden_states=False,
|
||||
)
|
||||
model_kwargs = _update_model_kwargs_for_generation(
|
||||
outputs, model_kwargs, num_new_tokens=next_text_window_size,
|
||||
)
|
||||
|
||||
tts_lm_model_inputs = self.prepare_inputs_for_generation(tts_lm_input_ids, **tts_lm_model_kwargs)
|
||||
tts_lm_additional_inputs = {
|
||||
"tts_text_masks": torch.ones_like(tts_lm_input_ids[:, -1:]),
|
||||
"lm_last_hidden_state": outputs.last_hidden_state,
|
||||
}
|
||||
# Forward pass through the model
|
||||
tts_lm_outputs = self.forward_tts_lm(
|
||||
**tts_lm_model_inputs, **tts_lm_additional_inputs, return_dict=True, output_attentions=False, output_hidden_states=False,
|
||||
)
|
||||
tts_lm_model_kwargs = self._update_model_kwargs_for_generation(
|
||||
tts_lm_outputs, tts_lm_model_kwargs, is_encoder_decoder=False,
|
||||
)
|
||||
|
||||
diffusion_indices = torch.LongTensor([0])
|
||||
for cur_speech_index in range(TTS_SPEECH_WINDOW_SIZE):
|
||||
positive_condition = tts_lm_outputs.last_hidden_state[diffusion_indices, -1, :]
|
||||
negative_condition = tts_lm_negative_outputs.last_hidden_state[diffusion_indices, -1, :]
|
||||
|
||||
speech_latent = self.sample_speech_tokens(
|
||||
positive_condition,
|
||||
negative_condition,
|
||||
cfg_scale=cfg_scale,
|
||||
).unsqueeze(1)
|
||||
|
||||
# Decode acoustic latent to audio using acoustic streaming cache
|
||||
scaled_latent = speech_latent / self.model.speech_scaling_factor.to(speech_latent.device) - self.model.speech_bias_factor.to(speech_latent.device)
|
||||
audio_chunk = self.model.acoustic_tokenizer.decode(
|
||||
scaled_latent.to(self.model.acoustic_tokenizer.device),
|
||||
cache=acoustic_cache, # Use acoustic-specific cache
|
||||
sample_indices=diffusion_indices.to(self.model.acoustic_tokenizer.device),
|
||||
use_cache=True,
|
||||
debug=False
|
||||
)
|
||||
|
||||
# Store audio chunks for each sample
|
||||
for i, sample_idx in enumerate(diffusion_indices):
|
||||
idx = sample_idx.item()
|
||||
# Only append audio chunk if the sample is not finished
|
||||
if not finished_tags[idx]:
|
||||
audio_chunks[idx].append(audio_chunk[i])
|
||||
|
||||
# Add streaming support here
|
||||
if audio_streamer is not None:
|
||||
# Stream the audio chunks immediately
|
||||
audio_streamer.put(audio_chunk, diffusion_indices)
|
||||
|
||||
acoustic_embed = self.model.acoustic_connector(speech_latent)
|
||||
tts_lm_input_ids = torch.cat([tts_lm_input_ids, torch.ones_like(tts_lm_input_ids[:, -1:])], dim=-1)
|
||||
|
||||
if tts_lm_input_ids.shape[1] > tts_lm_generation_config.max_length:
|
||||
break
|
||||
|
||||
step += 1
|
||||
total_generated_speech_tokens += 1
|
||||
if progress_bar is not None:
|
||||
progress_bar.update(1)
|
||||
progress_bar.set_description(f"Prefilled {total_prefilled_text_tokens} text tokens, generated {total_generated_speech_tokens} speech tokens, current step ({step} / {tts_lm_generation_config.max_length})")
|
||||
|
||||
tts_lm_model_inputs = self.prepare_inputs_for_generation(tts_lm_input_ids, **tts_lm_model_kwargs)
|
||||
tts_lm_additional_inputs = {
|
||||
"tts_text_masks": torch.zeros_like(tts_lm_input_ids[:, -1:]),
|
||||
"lm_last_hidden_state": acoustic_embed,
|
||||
}
|
||||
# Forward pass through the model
|
||||
tts_lm_outputs = self.forward_tts_lm(
|
||||
**tts_lm_model_inputs, **tts_lm_additional_inputs, return_dict=True, output_attentions=False, output_hidden_states=False,
|
||||
)
|
||||
if cur_speech_index == TTS_SPEECH_WINDOW_SIZE - 1 and next_text_window_size > 0:
|
||||
tts_lm_model_kwargs = _update_model_kwargs_for_generation(
|
||||
tts_lm_outputs, tts_lm_model_kwargs, num_new_tokens=next_text_window_size,
|
||||
)
|
||||
else:
|
||||
tts_lm_model_kwargs = self._update_model_kwargs_for_generation(
|
||||
tts_lm_outputs, tts_lm_model_kwargs, is_encoder_decoder=False,
|
||||
)
|
||||
|
||||
tts_lm_negative_input_ids = torch.cat([tts_lm_negative_input_ids, torch.ones_like(tts_lm_input_ids[:, -1:])], dim=-1)
|
||||
tts_lm_negative_model_inputs = self.prepare_inputs_for_generation(tts_lm_negative_input_ids, **tts_lm_negative_model_kwargs)
|
||||
# Forward negative pass through the model
|
||||
tts_lm_negative_additional_inputs = {
|
||||
"tts_text_masks": torch.zeros_like(tts_lm_negative_input_ids[:, -1:]),
|
||||
"lm_last_hidden_state": acoustic_embed,
|
||||
}
|
||||
tts_lm_negative_outputs = self.forward_tts_lm(
|
||||
**tts_lm_negative_model_inputs, **tts_lm_negative_additional_inputs, return_dict=True, output_attentions=False, output_hidden_states=False,
|
||||
)
|
||||
tts_lm_negative_model_kwargs = self._update_model_kwargs_for_generation(
|
||||
tts_lm_negative_outputs, tts_lm_negative_model_kwargs, is_encoder_decoder=False,
|
||||
)
|
||||
|
||||
tts_eos_logits = torch.sigmoid(self.tts_eos_classifier(tts_lm_outputs.last_hidden_state[diffusion_indices, -1, :]))
|
||||
if tts_eos_logits[0].item() > 0.5:
|
||||
# If EOS token is predicted, we can stop generation for this sample
|
||||
finished_tags[diffusion_indices] = True
|
||||
if audio_streamer is not None:
|
||||
audio_streamer.end(diffusion_indices)
|
||||
|
||||
if tts_lm_input_ids.shape[1] > tts_lm_generation_config.max_length:
|
||||
if verbose:
|
||||
print(f"Reached maximum generation length {tts_lm_generation_config.max_length}, stopped it.")
|
||||
reached_samples = torch.arange(batch_size, device=device)[~finished_tags]
|
||||
if reached_samples.numel() > 0:
|
||||
reach_max_step_sample[reached_samples] = True
|
||||
break
|
||||
|
||||
if audio_streamer is not None:
|
||||
audio_streamer.end()
|
||||
|
||||
# Concatenate audio chunks for each sample
|
||||
final_audio_outputs = []
|
||||
for sample_chunks in audio_chunks:
|
||||
if sample_chunks:
|
||||
# Concatenate all chunks along the time dimension (assumed to be the last dimension)
|
||||
concatenated_audio = torch.cat(sample_chunks, dim=-1)
|
||||
final_audio_outputs.append(concatenated_audio)
|
||||
else:
|
||||
# If no audio was generated for this sample, append None
|
||||
final_audio_outputs.append(None)
|
||||
|
||||
if reach_max_step_sample is not None and reach_max_step_sample.any():
|
||||
print(f"Reached maximum generation length {tts_lm_generation_config.max_length}, stopped it.")
|
||||
|
||||
return VibeVoiceGenerationOutput(
|
||||
sequences=tts_lm_input_ids,
|
||||
speech_outputs=final_audio_outputs if return_speech else None,
|
||||
reach_max_step_sample=reach_max_step_sample,
|
||||
)
|
||||
|
||||
@torch.no_grad()
|
||||
def sample_speech_tokens(self, condition, neg_condition, cfg_scale=3.0):
|
||||
self.model.noise_scheduler.set_timesteps(self.ddpm_inference_steps)
|
||||
condition = torch.cat([condition, neg_condition], dim=0).to(self.model.prediction_head.device)
|
||||
speech = torch.randn(condition.shape[0], self.config.acoustic_vae_dim).to(condition)
|
||||
for t in self.model.noise_scheduler.timesteps:
|
||||
half = speech[: len(speech) // 2]
|
||||
combined = torch.cat([half, half], dim=0)
|
||||
eps = self.model.prediction_head(combined, t.repeat(combined.shape[0]).to(combined), condition=condition)
|
||||
cond_eps, uncond_eps = torch.split(eps, len(eps) // 2, dim=0)
|
||||
half_eps = uncond_eps + cfg_scale * (cond_eps - uncond_eps)
|
||||
eps = torch.cat([half_eps, half_eps], dim=0)
|
||||
speech = self.model.noise_scheduler.step(eps, t, speech).prev_sample
|
||||
return speech[: len(speech) // 2]
|
||||
|
||||
|
||||
AutoModelForCausalLM.register(VibeVoiceStreamingConfig, VibeVoiceStreamingForConditionalGenerationInference)
|
||||
|
||||
__all__ = [
|
||||
"VibeVoiceStreamingForConditionalGenerationInference",
|
||||
]
|
||||
@@ -0,0 +1,287 @@
|
||||
import math
|
||||
from typing import Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from transformers.models.auto import AutoModel
|
||||
from transformers.modeling_utils import PreTrainedModel
|
||||
# from transformers.modeling_layers import GradientCheckpointingLayer
|
||||
from transformers.activations import ACT2FN
|
||||
from transformers.utils import logging
|
||||
|
||||
from .configuration_vibevoice import VibeVoiceDiffusionHeadConfig
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class RMSNorm(nn.Module):
|
||||
def __init__(self, dim: int, eps: float = 1e-6, elementwise_affine=True, memory_efficient=False):
|
||||
super().__init__()
|
||||
self.dim = dim
|
||||
self.eps = eps
|
||||
self.elementwise_affine = elementwise_affine
|
||||
if self.elementwise_affine:
|
||||
self.weight = nn.Parameter(torch.ones(dim))
|
||||
else:
|
||||
self.register_parameter('weight', None)
|
||||
|
||||
def _norm(self, x):
|
||||
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
||||
|
||||
def forward(self, x):
|
||||
output = self._norm(x.float()).type_as(x)
|
||||
if self.weight is not None:
|
||||
output = output * self.weight
|
||||
return output
|
||||
|
||||
def extra_repr(self) -> str:
|
||||
return f'dim={self.dim}, eps={self.eps}, elementwise_affine={self.elementwise_affine}'
|
||||
|
||||
def modulate(x, shift, scale):
|
||||
"""Apply modulation to input tensor."""
|
||||
return x * (1 + scale) + shift
|
||||
|
||||
|
||||
class TimestepEmbedder(nn.Module):
|
||||
"""
|
||||
Embeds scalar timesteps into vector representations.
|
||||
|
||||
Args:
|
||||
hidden_size (`int`): Size of the output embedding
|
||||
frequency_embedding_size (`int`, optional): Size of the intermediate frequency embedding
|
||||
"""
|
||||
def __init__(self, hidden_size, frequency_embedding_size=256):
|
||||
super().__init__()
|
||||
self.mlp = nn.Sequential(
|
||||
nn.Linear(frequency_embedding_size, hidden_size, bias=False),
|
||||
# nn.SiLU(),
|
||||
ACT2FN['silu'],
|
||||
nn.Linear(hidden_size, hidden_size, bias=False),
|
||||
)
|
||||
self.frequency_embedding_size = frequency_embedding_size
|
||||
|
||||
@staticmethod
|
||||
def timestep_embedding(t, dim, max_period=10000):
|
||||
"""
|
||||
Create sinusoidal timestep embeddings.
|
||||
|
||||
Args:
|
||||
t (`torch.Tensor`): A 1-D Tensor of N indices, one per batch element.
|
||||
These may be fractional.
|
||||
dim (`int`): The dimension of the output.
|
||||
max_period (`int`, optional): Controls the minimum frequency of the embeddings.
|
||||
|
||||
Returns:
|
||||
`torch.Tensor`: An [N, D] Tensor of positional embeddings.
|
||||
"""
|
||||
half = dim // 2
|
||||
freqs = torch.exp(
|
||||
-math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half
|
||||
).to(t.device)
|
||||
args = t[:, None].float() * freqs[None]
|
||||
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
|
||||
if dim % 2:
|
||||
embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
|
||||
return embedding.to(t.dtype)
|
||||
|
||||
def forward(self, t):
|
||||
t_freq = self.timestep_embedding(t, self.frequency_embedding_size)
|
||||
t_emb = self.mlp(t_freq)
|
||||
return t_emb
|
||||
|
||||
|
||||
class FeedForwardNetwork(nn.Module):
|
||||
"""
|
||||
Standard feed-forward network with SwiGLU activation.
|
||||
|
||||
Args:
|
||||
embed_dim (`int`): Input dimension
|
||||
ffn_dim (`int`): Hidden dimension
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
embed_dim,
|
||||
ffn_dim,
|
||||
):
|
||||
super().__init__()
|
||||
self.embed_dim = embed_dim
|
||||
self.gate_proj = nn.Linear(self.embed_dim, ffn_dim, bias=False)
|
||||
self.up_proj = nn.Linear(self.embed_dim, ffn_dim, bias=False)
|
||||
self.down_proj = nn.Linear(ffn_dim, self.embed_dim, bias=False)
|
||||
self.act_fn = ACT2FN['silu'] # Using SiLU as the activation function
|
||||
|
||||
def forward(self, x):
|
||||
gate = self.gate_proj(x)
|
||||
up = self.up_proj(x)
|
||||
|
||||
# SwiGLU activation
|
||||
# gate = F.silu(gate)
|
||||
gate = self.act_fn(gate)
|
||||
return self.down_proj(gate * up)
|
||||
|
||||
|
||||
class HeadLayer(nn.Module):
|
||||
"""
|
||||
A layer in the diffusion head.
|
||||
|
||||
Args:
|
||||
embed_dim (`int`): Input dimension
|
||||
ffn_dim (`int`): Hidden dimension
|
||||
cond_dim (`int`): Condition embedding dimension
|
||||
norm_eps (`float`, optional): Epsilon for normalization
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
embed_dim,
|
||||
ffn_dim,
|
||||
cond_dim,
|
||||
norm_eps=1e-5,
|
||||
):
|
||||
super().__init__()
|
||||
self.embed_dim = embed_dim
|
||||
self.cond_dim = cond_dim
|
||||
self.ffn_dim = ffn_dim
|
||||
self.ffn = FeedForwardNetwork(
|
||||
self.embed_dim,
|
||||
self.ffn_dim,
|
||||
)
|
||||
self.norm = RMSNorm(self.embed_dim, eps=norm_eps)
|
||||
self.adaLN_modulation = nn.Sequential(
|
||||
# nn.SiLU(),
|
||||
ACT2FN['silu'],
|
||||
nn.Linear(cond_dim, 3 * self.embed_dim, bias=False)
|
||||
)
|
||||
|
||||
def forward(self, x, c):
|
||||
shift_ffn, scale_ffn, gate_ffn = self.adaLN_modulation(c).chunk(3, dim=-1)
|
||||
x = x + gate_ffn * self.ffn(modulate(self.norm(x), shift_ffn, scale_ffn))
|
||||
return x
|
||||
|
||||
|
||||
class FinalLayer(nn.Module):
|
||||
"""
|
||||
Final layer in the diffusion head.
|
||||
|
||||
Args:
|
||||
hidden_size (`int`): Input dimension
|
||||
output_size (`int`): Output dimension
|
||||
cond_size (`int`): Condition embedding dimension
|
||||
norm_eps (`float`, optional): Epsilon for normalization
|
||||
"""
|
||||
def __init__(self, hidden_size, output_size, cond_size, norm_eps=1e-5):
|
||||
super().__init__()
|
||||
self.norm_final = RMSNorm(hidden_size, eps=norm_eps, elementwise_affine=False)
|
||||
self.linear = nn.Linear(hidden_size, output_size, bias=False)
|
||||
self.adaLN_modulation = nn.Sequential(
|
||||
# nn.SiLU(),
|
||||
ACT2FN['silu'],
|
||||
nn.Linear(cond_size, 2 * hidden_size, bias=False)
|
||||
)
|
||||
|
||||
def forward(self, x, c):
|
||||
shift, scale = self.adaLN_modulation(c).chunk(2, dim=-1)
|
||||
x = modulate(self.norm_final(x), shift, scale)
|
||||
x = self.linear(x)
|
||||
return x
|
||||
|
||||
|
||||
class VibeVoiceDiffusionHead(PreTrainedModel):
|
||||
"""
|
||||
Diffusion head model for vibevoice.
|
||||
|
||||
Args:
|
||||
config (`VibeVoiceDiffusionHeadConfig`): Model configuration
|
||||
latent_size (`int`, optional): Size of the latent space. If not provided, uses `config.latent_size`.
|
||||
"""
|
||||
config_class = VibeVoiceDiffusionHeadConfig
|
||||
supports_gradient_checkpointing = True
|
||||
_supports_flash_attn_2 = True
|
||||
_supports_sdpa = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config,
|
||||
):
|
||||
super().__init__(config)
|
||||
self.config = config
|
||||
self.cond_dim = config.hidden_size
|
||||
latent_size = config.latent_size
|
||||
|
||||
self.noisy_images_proj = nn.Linear(latent_size, config.hidden_size, bias=False)
|
||||
self.cond_proj = nn.Linear(config.hidden_size, self.cond_dim, bias=False)
|
||||
self.t_embedder = TimestepEmbedder(self.cond_dim)
|
||||
|
||||
ffn_dim = int(config.hidden_size * config.head_ffn_ratio)
|
||||
|
||||
# Create the intermediate layers
|
||||
self.layers = nn.ModuleList([
|
||||
HeadLayer(
|
||||
embed_dim=config.hidden_size,
|
||||
ffn_dim=ffn_dim,
|
||||
cond_dim=self.cond_dim,
|
||||
norm_eps=config.rms_norm_eps
|
||||
)
|
||||
for _ in range(config.head_layers)
|
||||
])
|
||||
|
||||
# Final layer for output
|
||||
self.final_layer = FinalLayer(
|
||||
hidden_size=config.hidden_size,
|
||||
output_size=latent_size,
|
||||
cond_size=self.cond_dim,
|
||||
norm_eps=config.rms_norm_eps
|
||||
)
|
||||
|
||||
self.initialize_weights()
|
||||
|
||||
def initialize_weights(self):
|
||||
"""Initialize the weights of the model."""
|
||||
# Initialize timestep embedder
|
||||
nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
|
||||
nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
|
||||
|
||||
# Zero-out adaLN modulation layers
|
||||
for layer in self.layers:
|
||||
nn.init.constant_(layer.adaLN_modulation[-1].weight, 0)
|
||||
|
||||
# Zero-out output layers
|
||||
nn.init.constant_(self.final_layer.adaLN_modulation[-1].weight, 0)
|
||||
nn.init.constant_(self.final_layer.linear.weight, 0)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
noisy_images,
|
||||
timesteps,
|
||||
condition,
|
||||
):
|
||||
"""
|
||||
Forward pass of the prediction head.
|
||||
|
||||
Args:
|
||||
noisy_images (`torch.Tensor`): Noisy images/latents to denoise
|
||||
timesteps (`torch.Tensor`): Timesteps for diffusion
|
||||
condition (`torch.Tensor`): Conditioning information
|
||||
|
||||
Returns:
|
||||
`torch.Tensor`: The predicted noise/velocity
|
||||
"""
|
||||
x = self.noisy_images_proj(noisy_images)
|
||||
t = self.t_embedder(timesteps)
|
||||
condition = self.cond_proj(condition)
|
||||
c = condition + t
|
||||
|
||||
for layer in self.layers:
|
||||
x = layer(x, c)
|
||||
|
||||
x = self.final_layer(x, c)
|
||||
return x
|
||||
|
||||
|
||||
AutoModel.register(VibeVoiceDiffusionHeadConfig, VibeVoiceDiffusionHead)
|
||||
|
||||
__all__ = [
|
||||
"VibeVoiceDiffusionHead",
|
||||
]
|
||||
@@ -0,0 +1,214 @@
|
||||
"""Tokenization classes for vibevoice."""
|
||||
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from transformers.utils import logging
|
||||
from transformers.models.qwen2.tokenization_qwen2 import Qwen2Tokenizer
|
||||
from transformers.models.qwen2.tokenization_qwen2_fast import Qwen2TokenizerFast
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class VibeVoiceTextTokenizer(Qwen2Tokenizer):
|
||||
"""
|
||||
Construct a VibeVoice tokenizer. Based on the Qwen2 tokenizer with additional special tokens for speech.
|
||||
|
||||
Args:
|
||||
vocab_file (`str`):
|
||||
Path to the vocabulary file.
|
||||
merges_file (`str`):
|
||||
Path to the merges file.
|
||||
errors (`str`, *optional*, defaults to `"replace"`):
|
||||
Paradigm to follow when decoding bytes to UTF-8.
|
||||
unk_token (`str`, *optional*, defaults to `"<|endoftext|>"`):
|
||||
The unknown token.
|
||||
bos_token (`str`, *optional*):
|
||||
The beginning of sequence token. Not used for vibevoice.
|
||||
eos_token (`str`, *optional*, defaults to `"<|endoftext|>"`):
|
||||
The end of sequence token.
|
||||
pad_token (`str`, *optional*, defaults to `"<|endoftext|>"`):
|
||||
The token used for padding.
|
||||
add_special_tokens (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to add special tokens when encoding.
|
||||
"""
|
||||
|
||||
model_input_names = ["input_ids", "attention_mask"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_file,
|
||||
merges_file,
|
||||
errors="replace",
|
||||
unk_token="<|endoftext|>",
|
||||
bos_token=None,
|
||||
eos_token="<|endoftext|>",
|
||||
pad_token="<|endoftext|>",
|
||||
add_prefix_space=False,
|
||||
add_special_tokens=True,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
vocab_file=vocab_file,
|
||||
merges_file=merges_file,
|
||||
errors=errors,
|
||||
unk_token=unk_token,
|
||||
bos_token=bos_token,
|
||||
eos_token=eos_token,
|
||||
pad_token=pad_token,
|
||||
add_prefix_space=add_prefix_space,
|
||||
add_special_tokens=add_special_tokens,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Add VibeVoice-specific special tokens
|
||||
self._add_vibevoice_special_tokens()
|
||||
|
||||
def _add_vibevoice_special_tokens(self):
|
||||
"""Add VibeVoice-specific special tokens."""
|
||||
special_tokens = {
|
||||
"additional_special_tokens": [
|
||||
"<|vision_start|>", # Speech start (reusing vision tokens)
|
||||
"<|vision_end|>", # Speech end
|
||||
"<|vision_pad|>", # Speech diffusion pad
|
||||
]
|
||||
}
|
||||
num_added = self.add_special_tokens(special_tokens)
|
||||
|
||||
# Cache special token IDs
|
||||
self._speech_start_id = self.convert_tokens_to_ids("<|vision_start|>")
|
||||
self._speech_end_id = self.convert_tokens_to_ids("<|vision_end|>")
|
||||
self._speech_diffusion_id = self.convert_tokens_to_ids("<|vision_pad|>")
|
||||
|
||||
self._eos_id = self.convert_tokens_to_ids('<|endoftext|>')
|
||||
|
||||
return num_added
|
||||
|
||||
@property
|
||||
def eos_id(self) -> int:
|
||||
"""Id of the end of sequence token."""
|
||||
return self._eos_id
|
||||
|
||||
@property
|
||||
def speech_start_id(self) -> int:
|
||||
"""Id of the speech start token."""
|
||||
return self._speech_start_id
|
||||
|
||||
@property
|
||||
def speech_end_id(self) -> int:
|
||||
"""Id of the speech end token."""
|
||||
return self._speech_end_id
|
||||
|
||||
@property
|
||||
def speech_diffusion_id(self) -> int:
|
||||
"""Id of the speech diffusion token."""
|
||||
return self._speech_diffusion_id
|
||||
|
||||
@property
|
||||
def pad_id(self) -> int:
|
||||
"""Id used for padding (returns -100 for loss masking)."""
|
||||
return -100
|
||||
|
||||
|
||||
class VibeVoiceTextTokenizerFast(Qwen2TokenizerFast):
|
||||
"""
|
||||
Construct a "fast" VibeVoice tokenizer (backed by HuggingFace's *tokenizers* library).
|
||||
Based on the Qwen2 tokenizer with additional special tokens for speech.
|
||||
|
||||
Args:
|
||||
vocab_file (`str`, *optional*):
|
||||
Path to the vocabulary file.
|
||||
merges_file (`str`, *optional*):
|
||||
Path to the merges file.
|
||||
tokenizer_file (`str`, *optional*):
|
||||
Path to [tokenizers](https://github.com/huggingface/tokenizers) file.
|
||||
unk_token (`str`, *optional*, defaults to `"<|endoftext|>"`):
|
||||
The unknown token.
|
||||
bos_token (`str`, *optional*):
|
||||
The beginning of sequence token. Not used for vibevoice.
|
||||
eos_token (`str`, *optional*, defaults to `"<|endoftext|>"`):
|
||||
The end of sequence token.
|
||||
pad_token (`str`, *optional*, defaults to `"<|endoftext|>"`):
|
||||
The token used for padding.
|
||||
"""
|
||||
|
||||
model_input_names = ["input_ids", "attention_mask"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_file=None,
|
||||
merges_file=None,
|
||||
tokenizer_file=None,
|
||||
unk_token="<|endoftext|>",
|
||||
bos_token=None,
|
||||
eos_token="<|endoftext|>",
|
||||
pad_token="<|endoftext|>",
|
||||
add_prefix_space=False,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
vocab_file=vocab_file,
|
||||
merges_file=merges_file,
|
||||
tokenizer_file=tokenizer_file,
|
||||
unk_token=unk_token,
|
||||
bos_token=bos_token,
|
||||
eos_token=eos_token,
|
||||
pad_token=pad_token,
|
||||
add_prefix_space=add_prefix_space,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Add VibeVoice-specific special tokens
|
||||
self._add_vibevoice_special_tokens()
|
||||
|
||||
def _add_vibevoice_special_tokens(self):
|
||||
"""Add VibeVoice-specific special tokens."""
|
||||
special_tokens = {
|
||||
"additional_special_tokens": [
|
||||
"<|vision_start|>", # Speech start (reusing vision tokens)
|
||||
"<|vision_end|>", # Speech end
|
||||
"<|vision_pad|>", # Speech diffusion pad
|
||||
]
|
||||
}
|
||||
num_added = self.add_special_tokens(special_tokens)
|
||||
|
||||
# Cache special token IDs
|
||||
self._speech_start_id = self.convert_tokens_to_ids("<|vision_start|>")
|
||||
self._speech_end_id = self.convert_tokens_to_ids("<|vision_end|>")
|
||||
self._speech_diffusion_id = self.convert_tokens_to_ids("<|vision_pad|>")
|
||||
|
||||
# self._eos_id = self.convert_tokens_to_ids('<|endoftext|>')
|
||||
self._eos_id = self.eos_token_id # qwen2 / qwen3
|
||||
self._pad_id = self.convert_tokens_to_ids('<|image_pad|>')
|
||||
|
||||
return num_added
|
||||
|
||||
@property
|
||||
def eos_id(self) -> int:
|
||||
"""Id of the end of sequence token."""
|
||||
return self._eos_id
|
||||
|
||||
@property
|
||||
def speech_start_id(self) -> int:
|
||||
"""Id of the speech start token."""
|
||||
return self._speech_start_id
|
||||
|
||||
@property
|
||||
def speech_end_id(self) -> int:
|
||||
"""Id of the speech end token."""
|
||||
return self._speech_end_id
|
||||
|
||||
@property
|
||||
def speech_diffusion_id(self) -> int:
|
||||
"""Id of the speech diffusion token."""
|
||||
return self._speech_diffusion_id
|
||||
|
||||
@property
|
||||
def pad_id(self) -> int:
|
||||
"""Id used for padding (returns -100 for loss masking)."""
|
||||
return self._pad_id
|
||||
|
||||
|
||||
__all__ = [
|
||||
"VibeVoiceTextTokenizer",
|
||||
"VibeVoiceTextTokenizerFast",
|
||||
]
|
||||
@@ -0,0 +1,904 @@
|
||||
import math
|
||||
import typing as tp
|
||||
from functools import partial
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
import copy
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from transformers.models.auto import AutoModel
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
from transformers.modeling_utils import PreTrainedModel
|
||||
from transformers.activations import ACT2FN
|
||||
|
||||
from .configuration_vibevoice import VibeVoiceAcousticTokenizerConfig
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
import os
|
||||
# Try to import APEX FusedRMSNorm
|
||||
try:
|
||||
from apex.normalization.fused_layer_norm import fused_rms_norm_affine
|
||||
APEX_AVAILABLE = True
|
||||
logger.info("APEX FusedRMSNorm is available and will be used for optimization")
|
||||
if int(os.getenv("OPTIMIZE_FOR_SPEED", "0")) == 0:
|
||||
APEX_AVAILABLE = False
|
||||
logger.warning("APEX FusedRMSNorm is disabled by environment variable OPTIMIZE_FOR_SPEED=0")
|
||||
except ImportError:
|
||||
APEX_AVAILABLE = False
|
||||
logger.warning("APEX FusedRMSNorm not available, using native implementation")
|
||||
# APEX_AVAILABLE=False
|
||||
|
||||
# Normalization modules
|
||||
class ConvLayerNorm(nn.LayerNorm):
|
||||
"""
|
||||
Convolution-friendly LayerNorm that moves channels to last dimensions
|
||||
before running the normalization and moves them back to original position right after.
|
||||
"""
|
||||
def __init__(self, normalized_shape: tp.Union[int, tp.List[int], torch.Size], **kwargs):
|
||||
super().__init__(normalized_shape, **kwargs)
|
||||
|
||||
def forward(self, x):
|
||||
x = x.transpose(1, 2) # b ... t -> b t ...
|
||||
x = nn.functional.layer_norm(x.float(), self.normalized_shape, self.weight.float(), self.bias.float(), self.eps).type_as(x)
|
||||
x = x.transpose(1, 2) # b t ... -> b ... t
|
||||
return x
|
||||
|
||||
class RMSNorm(nn.Module):
|
||||
def __init__(self, dim: int, eps: float = 1e-5, elementwise_affine=True, weight_shape=None):
|
||||
super().__init__()
|
||||
self.dim = dim
|
||||
self.eps = eps
|
||||
self.elementwise_affine = elementwise_affine
|
||||
if self.elementwise_affine:
|
||||
weight_shape = (dim,) if weight_shape is None else weight_shape
|
||||
self.weight = nn.Parameter(torch.ones(weight_shape))
|
||||
else:
|
||||
self.register_parameter('weight', None)
|
||||
|
||||
def _norm(self, x):
|
||||
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
||||
|
||||
def forward(self, x):
|
||||
output = self._norm(x.float()).type_as(x)
|
||||
if self.weight is not None:
|
||||
output = output * self.weight
|
||||
return output
|
||||
|
||||
def extra_repr(self) -> str:
|
||||
return f'dim={self.dim}, eps={self.eps}, elementwise_affine={self.elementwise_affine}'
|
||||
|
||||
class ConvRMSNorm(RMSNorm):
|
||||
def __init__(self, dim: int, eps: float = 1e-5, elementwise_affine=True, weight_shape=None):
|
||||
super().__init__(dim, eps, elementwise_affine, weight_shape)
|
||||
|
||||
def forward(self, x):
|
||||
x = x.transpose(1, 2) # b ... t -> b t ...
|
||||
if (not APEX_AVAILABLE) or (not self.elementwise_affine):
|
||||
# Fallback to native implementation
|
||||
output = self._norm(x.float()).type_as(x)
|
||||
if self.weight is not None:
|
||||
output = output * self.weight
|
||||
else:
|
||||
output = fused_rms_norm_affine(x, self.weight, self.weight.shape, self.eps)
|
||||
output = output.transpose(1, 2) # b t ... -> b ... t
|
||||
return output
|
||||
|
||||
# Convolutional layers and utilities
|
||||
CONV_NORMALIZATIONS = frozenset(['none', 'weight_norm', 'spectral_norm',
|
||||
'time_layer_norm', 'layer_norm', 'time_group_norm'])
|
||||
|
||||
|
||||
def apply_parametrization_norm(module: nn.Module, norm: str = 'none') -> nn.Module:
|
||||
assert norm in CONV_NORMALIZATIONS
|
||||
if norm == 'weight_norm':
|
||||
return nn.utils.weight_norm(module)
|
||||
elif norm == 'spectral_norm':
|
||||
return nn.utils.spectral_norm(module)
|
||||
else:
|
||||
# We already check was in CONV_NORMALIZATION, so any other choice
|
||||
# doesn't need reparametrization.
|
||||
return module
|
||||
|
||||
|
||||
def get_norm_module(module: nn.Module, causal: bool = False, norm: str = 'none', **norm_kwargs) -> nn.Module:
|
||||
"""Return the proper normalization module. If causal is True, this will ensure the returned
|
||||
module is causal, or return an error if the normalization doesn't support causal evaluation.
|
||||
"""
|
||||
assert norm in CONV_NORMALIZATIONS
|
||||
if norm == 'layer_norm':
|
||||
assert isinstance(module, nn.modules.conv._ConvNd)
|
||||
return ConvLayerNorm(module.out_channels, **norm_kwargs)
|
||||
elif norm == 'time_group_norm':
|
||||
if causal:
|
||||
raise ValueError("GroupNorm doesn't support causal evaluation.")
|
||||
assert isinstance(module, nn.modules.conv._ConvNd)
|
||||
return nn.GroupNorm(1, module.out_channels, **norm_kwargs)
|
||||
else:
|
||||
return nn.Identity()
|
||||
|
||||
|
||||
def get_extra_padding_for_conv1d(x: torch.Tensor, kernel_size: int, stride: int,
|
||||
padding_total: int = 0) -> int:
|
||||
"""Calculate extra padding needed for convolution to have the same output length"""
|
||||
length = x.shape[-1]
|
||||
n_frames = (length - kernel_size + padding_total) / stride + 1
|
||||
ideal_length = (math.ceil(n_frames) - 1) * stride + (kernel_size - padding_total)
|
||||
return ideal_length - length
|
||||
|
||||
|
||||
def pad1d(x: torch.Tensor, paddings: tp.Tuple[int, int], mode: str = 'zero', value: float = 0.):
|
||||
"""Pad 1D input with handling for small inputs in reflect mode"""
|
||||
length = x.shape[-1]
|
||||
padding_left, padding_right = paddings
|
||||
assert padding_left >= 0 and padding_right >= 0, (padding_left, padding_right)
|
||||
if mode == 'reflect':
|
||||
max_pad = max(padding_left, padding_right)
|
||||
extra_pad = 0
|
||||
if length <= max_pad:
|
||||
extra_pad = max_pad - length + 1
|
||||
x = F.pad(x, (0, extra_pad))
|
||||
padded = F.pad(x, paddings, mode, value)
|
||||
end = padded.shape[-1] - extra_pad
|
||||
return padded[..., :end]
|
||||
else:
|
||||
return F.pad(x, paddings, mode, value)
|
||||
|
||||
|
||||
def unpad1d(x: torch.Tensor, paddings: tp.Tuple[int, int]):
|
||||
"""Remove padding from x, handling properly zero padding. Only for 1d!"""
|
||||
padding_left, padding_right = paddings
|
||||
assert padding_left >= 0 and padding_right >= 0, (padding_left, padding_right)
|
||||
assert (padding_left + padding_right) <= x.shape[-1]
|
||||
end = x.shape[-1] - padding_right
|
||||
return x[..., padding_left: end]
|
||||
|
||||
|
||||
class NormConv1d(nn.Module):
|
||||
"""Wrapper around Conv1d and normalization applied to this conv"""
|
||||
def __init__(self, *args, causal: bool = False, norm: str = 'none',
|
||||
norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs):
|
||||
super().__init__()
|
||||
self.conv = apply_parametrization_norm(nn.Conv1d(*args, **kwargs), norm)
|
||||
self.norm = get_norm_module(self.conv, causal, norm, **norm_kwargs)
|
||||
self.norm_type = norm
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv(x)
|
||||
x = self.norm(x)
|
||||
return x
|
||||
|
||||
|
||||
class NormConvTranspose1d(nn.Module):
|
||||
"""Wrapper around ConvTranspose1d and normalization applied to this conv"""
|
||||
def __init__(self, *args, causal: bool = False, norm: str = 'none',
|
||||
norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs):
|
||||
super().__init__()
|
||||
self.convtr = apply_parametrization_norm(nn.ConvTranspose1d(*args, **kwargs), norm)
|
||||
self.norm = get_norm_module(self.convtr, causal, norm, **norm_kwargs)
|
||||
self.norm_type = norm
|
||||
|
||||
def forward(self, x):
|
||||
x = self.convtr(x)
|
||||
x = self.norm(x)
|
||||
return x
|
||||
|
||||
|
||||
class VibeVoiceTokenizerStreamingCache:
|
||||
"""Cache for streaming convolution, similar to KV cache in attention"""
|
||||
def __init__(self):
|
||||
self.cache = {} # Dict mapping (layer_id, sample_idx) to state tensor
|
||||
|
||||
def get(self, layer_id: str, sample_indices: torch.Tensor) -> Optional[torch.Tensor]:
|
||||
"""Get cached states for given layer and sample indices"""
|
||||
states = []
|
||||
max_length = 0
|
||||
|
||||
# First pass: collect states and find max length
|
||||
for idx in sample_indices.tolist():
|
||||
key = (layer_id, idx)
|
||||
if key not in self.cache:
|
||||
return None # If any sample is missing, return None
|
||||
state = self.cache[key]
|
||||
states.append(state)
|
||||
max_length = max(max_length, state.shape[-1])
|
||||
|
||||
# Second pass: pad states to max length if needed
|
||||
if len(states) > 0 and states[0].dim() >= 2:
|
||||
padded_states = []
|
||||
for state in states:
|
||||
if state.shape[-1] < max_length:
|
||||
# Pad on the time dimension (last dimension)
|
||||
pad_size = max_length - state.shape[-1]
|
||||
# Pad with zeros on the LEFT to align the most recent samples
|
||||
padded_state = F.pad(state, (pad_size, 0), mode='constant', value=0)
|
||||
padded_states.append(padded_state)
|
||||
else:
|
||||
padded_states.append(state)
|
||||
return torch.stack(padded_states, dim=0)
|
||||
else:
|
||||
return torch.stack(states, dim=0)
|
||||
|
||||
def set(self, layer_id: str, sample_indices: torch.Tensor, states: torch.Tensor):
|
||||
"""Set cached states for given layer and sample indices"""
|
||||
for i, idx in enumerate(sample_indices.tolist()):
|
||||
key = (layer_id, idx)
|
||||
self.cache[key] = states[i].detach()
|
||||
|
||||
def set_to_zero(self, sample_indices: torch.Tensor):
|
||||
"""Set all cached states to zero for given sample indices"""
|
||||
for key in list(self.cache.keys()):
|
||||
layer_id, sample_idx = key
|
||||
if sample_idx in sample_indices.tolist():
|
||||
# Create zero tensor with same shape and dtype as cached tensor
|
||||
cached_tensor = self.cache[key]
|
||||
self.cache[key] = torch.zeros_like(cached_tensor)
|
||||
|
||||
def clear(self, layer_id: Optional[str] = None, sample_indices: Optional[torch.Tensor] = None):
|
||||
"""Clear cache for specific layer/samples or everything"""
|
||||
if layer_id is None and sample_indices is None:
|
||||
self.cache.clear()
|
||||
elif layer_id is not None and sample_indices is None:
|
||||
# Clear all samples for a specific layer
|
||||
keys_to_remove = [k for k in self.cache.keys() if k[0] == layer_id]
|
||||
for k in keys_to_remove:
|
||||
del self.cache[k]
|
||||
elif layer_id is not None and sample_indices is not None:
|
||||
# Clear specific samples for a specific layer
|
||||
for idx in sample_indices.tolist():
|
||||
key = (layer_id, idx)
|
||||
self.cache.pop(key, None)
|
||||
|
||||
class SConv1d(nn.Module):
|
||||
"""Conv1d with built-in handling of asymmetric or causal padding and normalization."""
|
||||
def __init__(self, in_channels: int, out_channels: int,
|
||||
kernel_size: int, stride: int = 1, dilation: int = 1,
|
||||
groups: int = 1, bias: bool = True, causal: bool = False,
|
||||
norm: str = 'none', norm_kwargs: tp.Dict[str, tp.Any] = {},
|
||||
pad_mode: str = 'reflect'):
|
||||
super().__init__()
|
||||
self.conv = NormConv1d(in_channels, out_channels, kernel_size, stride,
|
||||
dilation=dilation, groups=groups, bias=bias, causal=causal,
|
||||
norm=norm, norm_kwargs=norm_kwargs)
|
||||
self.causal = causal
|
||||
self.pad_mode = pad_mode
|
||||
|
||||
# Store configuration
|
||||
self.kernel_size = kernel_size
|
||||
self.dilation = dilation
|
||||
self.stride = stride
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
|
||||
# For causal convolution, we need to maintain kernel_size - 1 samples as context
|
||||
# need to check use which context_size is more suitable
|
||||
# self.context_size = (kernel_size - 1) * dilation
|
||||
self.context_size = (kernel_size - 1) * dilation - (stride - 1)
|
||||
|
||||
# For non-streaming mode, calculate padding
|
||||
self.padding_total = (kernel_size - 1) * dilation - (stride - 1)
|
||||
|
||||
# Create a unique layer ID for cache management
|
||||
self._layer_id = None
|
||||
|
||||
@property
|
||||
def layer_id(self):
|
||||
if self._layer_id is None:
|
||||
self._layer_id = f"sconv1d_{id(self)}"
|
||||
return self._layer_id
|
||||
|
||||
def forward(self, x: torch.Tensor,
|
||||
cache: Optional[VibeVoiceTokenizerStreamingCache] = None,
|
||||
sample_indices: Optional[torch.Tensor] = None,
|
||||
use_cache: bool = False,
|
||||
debug: bool = False) -> torch.Tensor:
|
||||
"""
|
||||
Forward pass with optional streaming support via cache.
|
||||
|
||||
Args:
|
||||
x: Input tensor [batch_size, channels, time]
|
||||
cache: VibeVoiceTokenizerStreamingCache object for maintaining states
|
||||
sample_indices: Indices identifying each sample for cache management
|
||||
use_cache: Whether to use cached states for streaming
|
||||
debug: Whether to print debug information
|
||||
|
||||
Returns:
|
||||
Output tensor
|
||||
"""
|
||||
B, C, T = x.shape
|
||||
|
||||
# Non-streaming mode
|
||||
if not use_cache or cache is None:
|
||||
return self._forward_non_streaming(x, debug=debug)
|
||||
|
||||
# Streaming mode
|
||||
assert self.causal, "Streaming mode is only supported for causal convolutions"
|
||||
assert sample_indices is not None, "sample_indices must be provided for streaming mode"
|
||||
assert len(sample_indices) == B, "sample_indices must match batch size"
|
||||
|
||||
return self._forward_streaming(x, cache, sample_indices, debug)
|
||||
|
||||
def _forward_streaming(self, x: torch.Tensor,
|
||||
cache: VibeVoiceTokenizerStreamingCache,
|
||||
sample_indices: torch.Tensor,
|
||||
debug: bool = False) -> torch.Tensor:
|
||||
"""Streaming forward pass with cache operations kept separate from compiled code"""
|
||||
B, C, T = x.shape
|
||||
|
||||
# Cache operations (not compiled)
|
||||
cached_states = cache.get(self.layer_id, sample_indices)
|
||||
|
||||
if cached_states is None:
|
||||
# First chunk - initialize with zeros for context
|
||||
if self.context_size > 0:
|
||||
cached_states = torch.zeros(B, C, self.context_size, device=x.device, dtype=x.dtype)
|
||||
if debug:
|
||||
print(f"[DEBUG] Initialized cache with shape: {cached_states.shape}, context_size={self.context_size}")
|
||||
else:
|
||||
cached_states = torch.zeros(B, C, 0, device=x.device, dtype=x.dtype)
|
||||
if debug:
|
||||
print(f"[DEBUG] No context needed (kernel_size=stride)")
|
||||
|
||||
# Concatenate cached states with input
|
||||
if cached_states.shape[2] > 0:
|
||||
input_with_context = torch.cat([cached_states, x], dim=2)
|
||||
else:
|
||||
input_with_context = x
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG] Input shape: {x.shape}, Cache shape: {cached_states.shape}, Combined: {input_with_context.shape}")
|
||||
|
||||
# Apply convolution directly - no extra padding in streaming mode
|
||||
# The conv layer will handle its own padding internally
|
||||
output = self.conv(input_with_context)
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG] Output shape: {output.shape}")
|
||||
|
||||
# Update cache for next chunk
|
||||
if self.context_size > 0:
|
||||
# Calculate how many samples to keep
|
||||
total_input_length = input_with_context.shape[2]
|
||||
|
||||
# Keep the last context_size samples
|
||||
if total_input_length >= self.context_size:
|
||||
new_cache_start = total_input_length - self.context_size
|
||||
new_cache = input_with_context[:, :, new_cache_start:]
|
||||
else:
|
||||
# If we have less than context_size samples, keep everything
|
||||
new_cache = input_with_context
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG] New cache shape: {new_cache.shape}")
|
||||
|
||||
cache.set(self.layer_id, sample_indices, new_cache)
|
||||
|
||||
return output
|
||||
|
||||
def _forward_non_streaming(self, x: torch.Tensor, debug: bool = False) -> torch.Tensor:
|
||||
"""Standard forward pass without streaming"""
|
||||
B, C, T = x.shape
|
||||
kernel_size = self.kernel_size
|
||||
stride = self.stride
|
||||
dilation = self.dilation
|
||||
padding_total = self.padding_total
|
||||
|
||||
# Compute extra padding for stride alignment
|
||||
extra_padding = get_extra_padding_for_conv1d(x, kernel_size, stride, padding_total)
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG NON-STREAMING] Input shape: {x.shape}, padding_total={padding_total}, extra_padding={extra_padding}")
|
||||
|
||||
if self.causal:
|
||||
# Left padding for causal
|
||||
if self.pad_mode == 'constant':
|
||||
x = pad1d(x, (padding_total, extra_padding), mode=self.pad_mode, value=0)
|
||||
else:
|
||||
x = pad1d(x, (padding_total, extra_padding), mode=self.pad_mode)
|
||||
else:
|
||||
# Symmetric padding for non-causal
|
||||
padding_right = padding_total // 2
|
||||
padding_left = padding_total - padding_right
|
||||
x = pad1d(x, (padding_left, padding_right + extra_padding), mode=self.pad_mode)
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG NON-STREAMING] After padding: {x.shape}")
|
||||
|
||||
output = self.conv(x)
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG NON-STREAMING] Output shape: {output.shape}")
|
||||
|
||||
return output
|
||||
|
||||
|
||||
class SConvTranspose1d(nn.Module):
|
||||
"""ConvTranspose1d with built-in handling of asymmetric or causal padding and normalization."""
|
||||
def __init__(self, in_channels: int, out_channels: int,
|
||||
kernel_size: int, stride: int = 1, causal: bool = False,
|
||||
norm: str = 'none', trim_right_ratio: float = 1.,
|
||||
norm_kwargs: tp.Dict[str, tp.Any] = {}, bias: bool = True):
|
||||
super().__init__()
|
||||
self.convtr = NormConvTranspose1d(in_channels, out_channels, kernel_size, stride,
|
||||
causal=causal, norm=norm, norm_kwargs=norm_kwargs, bias=bias)
|
||||
self.causal = causal
|
||||
self.trim_right_ratio = trim_right_ratio
|
||||
assert self.causal or self.trim_right_ratio == 1., \
|
||||
"`trim_right_ratio` != 1.0 only makes sense for causal convolutions"
|
||||
assert self.trim_right_ratio >= 0. and self.trim_right_ratio <= 1.
|
||||
|
||||
# Store configuration
|
||||
self.kernel_size = kernel_size
|
||||
self.stride = stride
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
|
||||
# For transposed convolution, padding calculation is different
|
||||
self.padding_total = kernel_size - stride
|
||||
|
||||
# For streaming, we need to keep track of input history
|
||||
# Transposed conv needs to see multiple input samples to produce correct output
|
||||
self.context_size = kernel_size - 1
|
||||
|
||||
# Create a unique layer ID for cache management
|
||||
self._layer_id = None
|
||||
|
||||
@property
|
||||
def layer_id(self):
|
||||
if self._layer_id is None:
|
||||
self._layer_id = f"sconvtr1d_{id(self)}"
|
||||
return self._layer_id
|
||||
|
||||
def forward(self, x: torch.Tensor,
|
||||
cache: Optional[VibeVoiceTokenizerStreamingCache] = None,
|
||||
sample_indices: Optional[torch.Tensor] = None,
|
||||
use_cache: bool = False,
|
||||
debug: bool = False) -> torch.Tensor:
|
||||
"""
|
||||
Forward pass with optional streaming support via cache.
|
||||
"""
|
||||
B, C, T = x.shape
|
||||
|
||||
# Non-streaming mode
|
||||
if not use_cache or cache is None:
|
||||
return self._forward_non_streaming(x, debug=debug)
|
||||
|
||||
# Streaming mode
|
||||
assert sample_indices is not None, "sample_indices must be provided for streaming mode"
|
||||
assert len(sample_indices) == B, "sample_indices must match batch size"
|
||||
|
||||
return self._forward_streaming(x, cache, sample_indices, debug)
|
||||
|
||||
def _forward_streaming(self, x: torch.Tensor,
|
||||
cache: VibeVoiceTokenizerStreamingCache,
|
||||
sample_indices: torch.Tensor,
|
||||
debug: bool = False) -> torch.Tensor:
|
||||
"""Streaming forward pass with cache operations kept separate from compiled code"""
|
||||
B, C, T = x.shape
|
||||
|
||||
# Cache operations (not compiled)
|
||||
cached_input = cache.get(self.layer_id, sample_indices)
|
||||
|
||||
if cached_input is None:
|
||||
# First chunk - no history yet
|
||||
cached_input = torch.zeros(B, C, 0, device=x.device, dtype=x.dtype)
|
||||
if debug:
|
||||
print(f"[DEBUG] Initialized empty cache for transposed conv")
|
||||
|
||||
# Concatenate cached input with new input
|
||||
full_input = torch.cat([cached_input, x], dim=2)
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG] Input shape: {x.shape}, Cache shape: {cached_input.shape}, Combined: {full_input.shape}")
|
||||
|
||||
# First chunk or debug mode - use uncompiled version
|
||||
full_output = self.convtr(full_input)
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG] Full transposed conv output shape: {full_output.shape}")
|
||||
|
||||
# Calculate padding to remove
|
||||
if self.causal:
|
||||
padding_right = math.ceil(self.padding_total * self.trim_right_ratio)
|
||||
padding_left = self.padding_total - padding_right
|
||||
else:
|
||||
padding_right = self.padding_total // 2
|
||||
padding_left = self.padding_total - padding_right
|
||||
|
||||
# Remove padding
|
||||
if padding_left + padding_right > 0:
|
||||
full_output = unpad1d(full_output, (padding_left, padding_right))
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG] After unpadding: {full_output.shape}")
|
||||
|
||||
# Determine which part of the output corresponds to the new input
|
||||
if cached_input.shape[2] == 0:
|
||||
# First chunk - return all output
|
||||
output = full_output
|
||||
else:
|
||||
# Subsequent chunks - return only the new output
|
||||
expected_new_output = T * self.stride
|
||||
|
||||
# Take the last expected_new_output samples
|
||||
if full_output.shape[2] >= expected_new_output:
|
||||
output = full_output[:, :, -expected_new_output:]
|
||||
else:
|
||||
output = full_output
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG] Final streaming output shape: {output.shape}")
|
||||
|
||||
# Update cache
|
||||
if full_input.shape[2] > self.context_size:
|
||||
new_cache = full_input[:, :, -self.context_size:]
|
||||
else:
|
||||
new_cache = full_input
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG] New cache shape: {new_cache.shape}")
|
||||
|
||||
cache.set(self.layer_id, sample_indices, new_cache)
|
||||
|
||||
return output
|
||||
|
||||
def _forward_non_streaming(self, x: torch.Tensor, debug: bool = False) -> torch.Tensor:
|
||||
"""Standard forward pass without streaming"""
|
||||
if debug:
|
||||
print(f"[DEBUG NON-STREAMING] Input shape: {x.shape}")
|
||||
|
||||
# Apply transposed convolution
|
||||
y = self.convtr(x)
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG NON-STREAMING] After transposed conv: {y.shape}")
|
||||
|
||||
# Calculate and remove padding
|
||||
if self.causal:
|
||||
padding_right = math.ceil(self.padding_total * self.trim_right_ratio)
|
||||
padding_left = self.padding_total - padding_right
|
||||
else:
|
||||
padding_right = self.padding_total // 2
|
||||
padding_left = self.padding_total - padding_right
|
||||
|
||||
if padding_left + padding_right > 0:
|
||||
y = unpad1d(y, (padding_left, padding_right))
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG NON-STREAMING] Final output shape: {y.shape}")
|
||||
|
||||
return y
|
||||
|
||||
# FFN
|
||||
class FFN(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
embed_dim,
|
||||
ffn_dim,
|
||||
bias=False,
|
||||
):
|
||||
super().__init__()
|
||||
self.embed_dim = embed_dim
|
||||
self.linear1 = nn.Linear(self.embed_dim, ffn_dim, bias=bias)
|
||||
self.gelu = ACT2FN["gelu"]
|
||||
self.linear2 = nn.Linear(ffn_dim, self.embed_dim, bias=bias)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.linear1(x)
|
||||
x = self.gelu(x)
|
||||
x = self.linear2(x)
|
||||
return x
|
||||
|
||||
|
||||
class Convlayer(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
bias=True,
|
||||
pad_mode='zeros',
|
||||
norm='weight_norm',
|
||||
causal=True,
|
||||
):
|
||||
super().__init__()
|
||||
self.conv = SConv1d(in_channels, out_channels, kernel_size, stride=stride, dilation=dilation,
|
||||
groups=groups, bias=bias, pad_mode=pad_mode, norm=norm, causal=causal)
|
||||
|
||||
def forward(self, x):
|
||||
return self.conv(x)
|
||||
|
||||
class Block1D(nn.Module):
|
||||
def __init__(self, dim, kernel_size=7, drop_path=0., mixer_layer='conv',
|
||||
layer_scale_init_value=1e-6, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
if kwargs.get('layernorm', 'LN') == 'LN':
|
||||
self.norm = ConvLayerNorm(dim, eps=kwargs.get('eps', 1e-6))
|
||||
self.ffn_norm = ConvLayerNorm(dim, eps=kwargs.get('eps', 1e-6))
|
||||
elif kwargs.get('layernorm', 'RMSNorm') == 'RMSNorm':
|
||||
self.norm = ConvRMSNorm(dim, eps=kwargs.get('eps', 1e-6))
|
||||
self.ffn_norm = ConvRMSNorm(dim, eps=kwargs.get('eps', 1e-6))
|
||||
|
||||
if mixer_layer == 'conv':
|
||||
self.mixer = Convlayer(dim, dim, groups=kwargs.get('groups', 1),
|
||||
kernel_size=kernel_size,
|
||||
pad_mode=kwargs.get('pad_mode', 'reflect'),
|
||||
norm=kwargs.get('norm', 'none'),
|
||||
causal=kwargs.get('causal', True),
|
||||
bias=kwargs.get('bias', True),
|
||||
)
|
||||
elif mixer_layer == 'depthwise_conv':
|
||||
self.mixer = Convlayer(dim, dim, groups=dim,
|
||||
kernel_size=kernel_size,
|
||||
pad_mode=kwargs.get('pad_mode', 'reflect'),
|
||||
norm=kwargs.get('norm', 'none'),
|
||||
causal=kwargs.get('causal', True),
|
||||
bias=kwargs.get('bias', True),
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported mixer layer: {mixer_layer}")
|
||||
|
||||
self.ffn = FFN(
|
||||
dim,
|
||||
kwargs.get('ffn_expansion', 4) * dim,
|
||||
bias=kwargs.get('bias', False),
|
||||
)
|
||||
self.drop_path = nn.Identity() if drop_path <= 0. else nn.modules.DropPath(drop_path)
|
||||
|
||||
if layer_scale_init_value > 0:
|
||||
self.gamma = nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True)
|
||||
self.ffn_gamma = nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True)
|
||||
else:
|
||||
self.gamma = None
|
||||
self.ffn_gamma = None
|
||||
|
||||
def forward(self, x):
|
||||
# mixer
|
||||
residual = x
|
||||
x = self.norm(x)
|
||||
x = self.mixer(x)
|
||||
if self.gamma is not None:
|
||||
x = x * self.gamma.unsqueeze(-1)
|
||||
x = residual + self.drop_path(x)
|
||||
|
||||
# ffn
|
||||
residual = x
|
||||
x = self.ffn_norm(x)
|
||||
x = x.permute(0, 2, 1)
|
||||
x = self.ffn(x)
|
||||
x = x.permute(0, 2, 1)
|
||||
if self.ffn_gamma is not None:
|
||||
x = x * self.ffn_gamma.unsqueeze(-1)
|
||||
x = residual + self.drop_path(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class TokenizerDecoder(nn.Module):
|
||||
"""
|
||||
Decoder component for the VibeVoice tokenizer that converts latent representations back to audio.
|
||||
|
||||
Args:
|
||||
config: Configuration object with model parameters
|
||||
"""
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
|
||||
# Extract parameters from config
|
||||
self.dimension = config.dimension
|
||||
self.channels = config.channels
|
||||
self.n_filters = config.n_filters
|
||||
self.ratios = config.ratios
|
||||
|
||||
# IMPORTANT CHANGE: Don't reverse depths again since they're already reversed in VibeVoiceAcousticTokenizerModel
|
||||
self.depths = config.depths # Changed from list(reversed(config.depths))
|
||||
|
||||
self.n_residual_layers = getattr(config, "n_residual_layers", 1)
|
||||
self.hop_length = np.prod(self.ratios)
|
||||
self.causal = config.causal
|
||||
|
||||
# Additional config parameters with defaults
|
||||
kernel_size = getattr(config, "kernel_size", 7)
|
||||
last_kernel_size = getattr(config, "last_kernel_size", 7)
|
||||
norm = getattr(config, "norm", "none")
|
||||
norm_params = getattr(config, "norm_params", {})
|
||||
pad_mode = getattr(config, "pad_mode", "reflect")
|
||||
bias = getattr(config, "bias", True)
|
||||
layernorm = getattr(config, "layernorm", "LN")
|
||||
layernorm_eps = getattr(config, "layernorm_eps", 1e-6)
|
||||
trim_right_ratio = getattr(config, "trim_right_ratio", 1.0)
|
||||
layernorm_elementwise_affine = getattr(config, "layernorm_elementwise_affine", True)
|
||||
drop_path_rate = getattr(config, "drop_path_rate", 0.0)
|
||||
mixer_layer = getattr(config, "mixer_layer", "conv")
|
||||
layer_scale_init_value = getattr(config, "layer_scale_init_value", 0)
|
||||
disable_last_norm = getattr(config, "disable_last_norm", False)
|
||||
|
||||
# determine the norm type based on layernorm
|
||||
if layernorm == 'LN':
|
||||
norm_type = ConvLayerNorm
|
||||
elif layernorm == 'RMSNorm':
|
||||
norm_type = partial(ConvRMSNorm, elementwise_affine=layernorm_elementwise_affine)
|
||||
else:
|
||||
raise ValueError(f"Unsupported norm type: {layernorm}")
|
||||
|
||||
# stem and upsampling layers
|
||||
stem = nn.Sequential(
|
||||
SConv1d(self.dimension, self.n_filters * 2 ** (len(self.depths) - 1), kernel_size, norm=norm,
|
||||
norm_kwargs=norm_params, causal=self.causal, pad_mode=pad_mode, bias=bias),
|
||||
)
|
||||
|
||||
self.upsample_layers = nn.ModuleList()
|
||||
self.upsample_layers.append(stem)
|
||||
for i in range(len(self.ratios)):
|
||||
in_ch = self.n_filters * (2 ** (len(self.depths) - 1 - i))
|
||||
out_ch = self.n_filters * (2 ** (len(self.depths) - 1 - i - 1))
|
||||
upsample_layer = nn.Sequential(
|
||||
SConvTranspose1d(in_ch, out_ch,
|
||||
kernel_size=self.ratios[i] * 2, stride=self.ratios[i],
|
||||
norm=norm, norm_kwargs=norm_params, bias=bias,
|
||||
causal=self.causal, trim_right_ratio=trim_right_ratio),
|
||||
)
|
||||
self.upsample_layers.append(upsample_layer)
|
||||
|
||||
# configure transformer blocks
|
||||
layer_type = partial(
|
||||
Block1D,
|
||||
mixer_layer=mixer_layer,
|
||||
layernorm=layernorm,
|
||||
eps=layernorm_eps,
|
||||
causal=self.causal,
|
||||
pad_mode=pad_mode,
|
||||
norm=norm,
|
||||
bias=bias,
|
||||
layer_scale_init_value=layer_scale_init_value,
|
||||
)
|
||||
|
||||
self.stages = nn.ModuleList()
|
||||
dp_rates = [x.item() for x in torch.linspace(0, drop_path_rate, sum(self.depths))]
|
||||
cur = 0
|
||||
|
||||
# Create stages in the same order as the original model
|
||||
for i in range(len(self.depths)):
|
||||
in_ch = self.n_filters * (2 ** (len(self.depths) - 1 - i))
|
||||
stage = nn.Sequential(
|
||||
*[layer_type(dim=in_ch, drop_path=dp_rates[cur + j]) for j in range(self.depths[i])]
|
||||
)
|
||||
self.stages.append(stage)
|
||||
cur += self.depths[i]
|
||||
|
||||
if not disable_last_norm:
|
||||
self.norm = norm_type(in_ch, eps=layernorm_eps)
|
||||
else:
|
||||
self.norm = nn.Identity()
|
||||
self.head = SConv1d(in_ch, self.channels, kernel_size=last_kernel_size, causal=self.causal, pad_mode=pad_mode, norm=norm, bias=bias)
|
||||
|
||||
def forward_features(self, x, cache=None, sample_indices=None, use_cache=False, debug=False):
|
||||
for i in range(len(self.depths)):
|
||||
# Apply upsampling
|
||||
for layer in self.upsample_layers[i]:
|
||||
if isinstance(layer, (SConv1d, SConvTranspose1d)):
|
||||
x = layer(x, cache=cache, sample_indices=sample_indices, use_cache=use_cache, debug=debug)
|
||||
else:
|
||||
x = layer(x)
|
||||
|
||||
# Apply stage (Block1D contains Convlayer which contains SConv1d)
|
||||
for block in self.stages[i]:
|
||||
if hasattr(block, 'mixer') and hasattr(block.mixer, 'conv') and isinstance(block.mixer.conv, SConv1d):
|
||||
# Block1D forward with cache support
|
||||
residual = x
|
||||
x = block.norm(x)
|
||||
x = block.mixer.conv(x, cache=cache, sample_indices=sample_indices, use_cache=use_cache, debug=debug)
|
||||
if block.gamma is not None:
|
||||
x = x * block.gamma.unsqueeze(-1)
|
||||
x = residual + x
|
||||
|
||||
# FFN part
|
||||
residual = x
|
||||
x = block.ffn_norm(x)
|
||||
x = x.permute(0, 2, 1)
|
||||
x = block.ffn(x)
|
||||
x = x.permute(0, 2, 1)
|
||||
if block.ffn_gamma is not None:
|
||||
x = x * block.ffn_gamma.unsqueeze(-1)
|
||||
x = residual + x
|
||||
else:
|
||||
x = block(x)
|
||||
|
||||
return self.norm(x)
|
||||
|
||||
def forward(self, x, cache=None, sample_indices=None, use_cache=False, debug=False):
|
||||
x = self.forward_features(x, cache=cache, sample_indices=sample_indices, use_cache=use_cache, debug=debug)
|
||||
x = self.head(x, cache=cache, sample_indices=sample_indices, use_cache=use_cache, debug=debug)
|
||||
return x
|
||||
|
||||
|
||||
class VibeVoiceAcousticTokenizerModel(PreTrainedModel):
|
||||
"""VibeVoice speech tokenizer model (only decoder) for acoustic tokens"""
|
||||
|
||||
config_class = VibeVoiceAcousticTokenizerConfig
|
||||
base_model_prefix = "vibevoice_acoustic_tokenizer"
|
||||
_supports_flash_attn_2 = True
|
||||
_supports_sdpa = True
|
||||
_no_split_modules = ["TokenizerDecoder"]
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
|
||||
self.register_buffer('fix_std', torch.tensor(config.fix_std), persistent=False)
|
||||
self.std_dist_type = getattr(config, "std_dist_type", "fix")
|
||||
|
||||
# Parse encoder depths
|
||||
if isinstance(config.encoder_depths, str):
|
||||
encoder_depths = [int(d) for d in config.encoder_depths.split('-')]
|
||||
else:
|
||||
encoder_depths = config.encoder_depths
|
||||
|
||||
# Parse decoder depths if provided
|
||||
if config.decoder_depths is not None and isinstance(config.decoder_depths, str):
|
||||
decoder_depths = [int(d) for d in config.decoder_depths.split('-')]
|
||||
else:
|
||||
# Default: use reversed encoder depths if decoder_depths is None
|
||||
decoder_depths = list(reversed(encoder_depths))
|
||||
|
||||
# Create decoder config
|
||||
decoder_config = copy.deepcopy(config)
|
||||
decoder_config.dimension = config.vae_dim
|
||||
decoder_config.n_filters = config.decoder_n_filters
|
||||
decoder_config.ratios = config.decoder_ratios
|
||||
decoder_config.depths = decoder_depths
|
||||
decoder_config.norm = config.conv_norm
|
||||
decoder_config.pad_mode = config.pad_mode
|
||||
decoder_config.bias = config.conv_bias
|
||||
decoder_config.layernorm_eps = config.layernorm_eps
|
||||
decoder_config.layernorm_elementwise_affine = config.layernorm_elementwise_affine
|
||||
decoder_config.mixer_layer = config.mixer_layer
|
||||
decoder_config.layer_scale_init_value = config.layer_scale_init_value
|
||||
decoder_config.disable_last_norm = config.disable_last_norm
|
||||
|
||||
self.decoder = TokenizerDecoder(decoder_config)
|
||||
|
||||
# Initialize weights
|
||||
self.apply(self._init_weights)
|
||||
|
||||
def _init_weights(self, module):
|
||||
"""Initialize weights for the model"""
|
||||
if isinstance(module, nn.Linear):
|
||||
nn.init.normal_(module.weight, std=self.config.weight_init_value)
|
||||
if module.bias is not None:
|
||||
nn.init.zeros_(module.bias)
|
||||
elif isinstance(module, nn.LayerNorm):
|
||||
nn.init.ones_(module.weight)
|
||||
nn.init.zeros_(module.bias)
|
||||
elif isinstance(module, nn.Conv1d):
|
||||
nn.init.normal_(module.weight, std=self.config.weight_init_value)
|
||||
if module.bias is not None:
|
||||
nn.init.zeros_(module.bias)
|
||||
|
||||
@torch.no_grad()
|
||||
def decode(self, latents, cache=None, sample_indices=None, use_cache=False, debug=False):
|
||||
"""Convert latent representations back to audio"""
|
||||
if latents.shape[1] == self.config.vae_dim:
|
||||
pass
|
||||
else:
|
||||
latents = latents.permute(0, 2, 1)
|
||||
|
||||
audio = self.decoder(latents, cache=cache, sample_indices=sample_indices, use_cache=use_cache, debug=debug)
|
||||
return audio
|
||||
|
||||
|
||||
AutoModel.register(VibeVoiceAcousticTokenizerConfig, VibeVoiceAcousticTokenizerModel)
|
||||
|
||||
__all__ = [
|
||||
"VibeVoiceTokenizerStreamingCache",
|
||||
"VibeVoiceAcousticTokenizerModel",
|
||||
]
|
||||
@@ -0,0 +1,264 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import torch
|
||||
|
||||
import asyncio
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
|
||||
from transformers.generation import BaseStreamer
|
||||
|
||||
|
||||
class AudioStreamer(BaseStreamer):
|
||||
"""
|
||||
Audio streamer that stores audio chunks in queues for each sample in the batch.
|
||||
This allows streaming audio generation for multiple samples simultaneously.
|
||||
|
||||
Parameters:
|
||||
batch_size (`int`):
|
||||
The batch size for generation
|
||||
stop_signal (`any`, *optional*):
|
||||
The signal to put in the queue when generation ends. Defaults to None.
|
||||
timeout (`float`, *optional*):
|
||||
The timeout for the audio queue. If `None`, the queue will block indefinitely.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
batch_size: int,
|
||||
stop_signal: Optional[any] = None,
|
||||
timeout: Optional[float] = None,
|
||||
):
|
||||
self.batch_size = batch_size
|
||||
self.stop_signal = stop_signal
|
||||
self.timeout = timeout
|
||||
|
||||
# Create a queue for each sample in the batch
|
||||
self.audio_queues = [Queue() for _ in range(batch_size)]
|
||||
self.finished_flags = [False for _ in range(batch_size)]
|
||||
self.sample_indices_map = {} # Maps from sample index to queue index
|
||||
|
||||
def put(self, audio_chunks: torch.Tensor, sample_indices: torch.Tensor):
|
||||
"""
|
||||
Receives audio chunks and puts them in the appropriate queues.
|
||||
|
||||
Args:
|
||||
audio_chunks: Tensor of shape (num_samples, ...) containing audio chunks
|
||||
sample_indices: Tensor indicating which samples these chunks belong to
|
||||
"""
|
||||
for i, sample_idx in enumerate(sample_indices):
|
||||
idx = sample_idx.item()
|
||||
if idx < self.batch_size and not self.finished_flags[idx]:
|
||||
# Convert to numpy or keep as tensor based on preference
|
||||
audio_chunk = audio_chunks[i].detach().cpu()
|
||||
self.audio_queues[idx].put(audio_chunk, timeout=self.timeout)
|
||||
|
||||
def end(self, sample_indices: Optional[torch.Tensor] = None):
|
||||
"""
|
||||
Signals the end of generation for specified samples or all samples.
|
||||
|
||||
Args:
|
||||
sample_indices: Optional tensor of sample indices to end. If None, ends all.
|
||||
"""
|
||||
if sample_indices is None:
|
||||
# End all samples
|
||||
for idx in range(self.batch_size):
|
||||
if not self.finished_flags[idx]:
|
||||
self.audio_queues[idx].put(self.stop_signal, timeout=self.timeout)
|
||||
self.finished_flags[idx] = True
|
||||
else:
|
||||
# End specific samples
|
||||
for sample_idx in sample_indices:
|
||||
idx = sample_idx.item() if torch.is_tensor(sample_idx) else sample_idx
|
||||
if idx < self.batch_size and not self.finished_flags[idx]:
|
||||
self.audio_queues[idx].put(self.stop_signal, timeout=self.timeout)
|
||||
self.finished_flags[idx] = True
|
||||
|
||||
def __iter__(self):
|
||||
"""Returns an iterator over the batch of audio streams."""
|
||||
return AudioBatchIterator(self)
|
||||
|
||||
def get_stream(self, sample_idx: int):
|
||||
"""Get the audio stream for a specific sample."""
|
||||
if sample_idx >= self.batch_size:
|
||||
raise ValueError(f"Sample index {sample_idx} exceeds batch size {self.batch_size}")
|
||||
return AudioSampleIterator(self, sample_idx)
|
||||
|
||||
|
||||
class AudioSampleIterator:
|
||||
"""Iterator for a single audio stream from the batch."""
|
||||
|
||||
def __init__(self, streamer: AudioStreamer, sample_idx: int):
|
||||
self.streamer = streamer
|
||||
self.sample_idx = sample_idx
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
value = self.streamer.audio_queues[self.sample_idx].get(timeout=self.streamer.timeout)
|
||||
if value == self.streamer.stop_signal:
|
||||
raise StopIteration()
|
||||
return value
|
||||
|
||||
|
||||
class AudioBatchIterator:
|
||||
"""Iterator that yields audio chunks for all samples in the batch."""
|
||||
|
||||
def __init__(self, streamer: AudioStreamer):
|
||||
self.streamer = streamer
|
||||
self.active_samples = set(range(streamer.batch_size))
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
if not self.active_samples:
|
||||
raise StopIteration()
|
||||
|
||||
batch_chunks = {}
|
||||
samples_to_remove = set()
|
||||
|
||||
# Try to get chunks from all active samples
|
||||
for idx in self.active_samples:
|
||||
try:
|
||||
value = self.streamer.audio_queues[idx].get(block=False)
|
||||
if value == self.streamer.stop_signal:
|
||||
samples_to_remove.add(idx)
|
||||
else:
|
||||
batch_chunks[idx] = value
|
||||
except:
|
||||
# Queue is empty for this sample, skip it this iteration
|
||||
pass
|
||||
|
||||
# Remove finished samples
|
||||
self.active_samples -= samples_to_remove
|
||||
|
||||
if batch_chunks:
|
||||
return batch_chunks
|
||||
elif self.active_samples:
|
||||
# If no chunks were ready but we still have active samples,
|
||||
# wait a bit and try again
|
||||
import time
|
||||
time.sleep(0.01)
|
||||
return self.__next__()
|
||||
else:
|
||||
raise StopIteration()
|
||||
|
||||
|
||||
class AsyncAudioStreamer(AudioStreamer):
|
||||
"""
|
||||
Async version of AudioStreamer for use in async contexts.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
batch_size: int,
|
||||
stop_signal: Optional[any] = None,
|
||||
timeout: Optional[float] = None,
|
||||
):
|
||||
super().__init__(batch_size, stop_signal, timeout)
|
||||
# Replace regular queues with async queues
|
||||
self.audio_queues = [asyncio.Queue() for _ in range(batch_size)]
|
||||
self.loop = asyncio.get_running_loop()
|
||||
|
||||
def put(self, audio_chunks: torch.Tensor, sample_indices: torch.Tensor):
|
||||
"""Put audio chunks in the appropriate async queues."""
|
||||
for i, sample_idx in enumerate(sample_indices):
|
||||
idx = sample_idx.item()
|
||||
if idx < self.batch_size and not self.finished_flags[idx]:
|
||||
audio_chunk = audio_chunks[i].detach().cpu()
|
||||
self.loop.call_soon_threadsafe(
|
||||
self.audio_queues[idx].put_nowait, audio_chunk
|
||||
)
|
||||
|
||||
def end(self, sample_indices: Optional[torch.Tensor] = None):
|
||||
"""Signal the end of generation for specified samples."""
|
||||
if sample_indices is None:
|
||||
indices_to_end = range(self.batch_size)
|
||||
else:
|
||||
indices_to_end = [s.item() if torch.is_tensor(s) else s for s in sample_indices]
|
||||
|
||||
for idx in indices_to_end:
|
||||
if idx < self.batch_size and not self.finished_flags[idx]:
|
||||
self.loop.call_soon_threadsafe(
|
||||
self.audio_queues[idx].put_nowait, self.stop_signal
|
||||
)
|
||||
self.finished_flags[idx] = True
|
||||
|
||||
async def get_stream(self, sample_idx: int):
|
||||
"""Get async iterator for a specific sample's audio stream."""
|
||||
if sample_idx >= self.batch_size:
|
||||
raise ValueError(f"Sample index {sample_idx} exceeds batch size {self.batch_size}")
|
||||
|
||||
while True:
|
||||
value = await self.audio_queues[sample_idx].get()
|
||||
if value == self.stop_signal:
|
||||
break
|
||||
yield value
|
||||
|
||||
def __aiter__(self):
|
||||
"""Returns an async iterator over all audio streams."""
|
||||
return AsyncAudioBatchIterator(self)
|
||||
|
||||
|
||||
class AsyncAudioBatchIterator:
|
||||
"""Async iterator for batch audio streaming."""
|
||||
|
||||
def __init__(self, streamer: AsyncAudioStreamer):
|
||||
self.streamer = streamer
|
||||
self.active_samples = set(range(streamer.batch_size))
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
if not self.active_samples:
|
||||
raise StopAsyncIteration()
|
||||
|
||||
batch_chunks = {}
|
||||
samples_to_remove = set()
|
||||
|
||||
# Create tasks for all active samples
|
||||
tasks = {
|
||||
idx: asyncio.create_task(self._get_chunk(idx))
|
||||
for idx in self.active_samples
|
||||
}
|
||||
|
||||
# Wait for at least one chunk to be ready
|
||||
done, pending = await asyncio.wait(
|
||||
tasks.values(),
|
||||
return_when=asyncio.FIRST_COMPLETED,
|
||||
timeout=self.streamer.timeout
|
||||
)
|
||||
|
||||
# Cancel pending tasks
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
|
||||
# Process completed tasks
|
||||
for idx, task in tasks.items():
|
||||
if task in done:
|
||||
try:
|
||||
value = await task
|
||||
if value == self.streamer.stop_signal:
|
||||
samples_to_remove.add(idx)
|
||||
else:
|
||||
batch_chunks[idx] = value
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
self.active_samples -= samples_to_remove
|
||||
|
||||
if batch_chunks:
|
||||
return batch_chunks
|
||||
elif self.active_samples:
|
||||
# Try again if we still have active samples
|
||||
return await self.__anext__()
|
||||
else:
|
||||
raise StopAsyncIteration()
|
||||
|
||||
async def _get_chunk(self, idx):
|
||||
"""Helper to get a chunk from a specific queue."""
|
||||
return await self.streamer.audio_queues[idx].get()
|
||||
Reference in New Issue
Block a user