moss-tts-pny / portable_tts_runtime.py
multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
93b0f2e verified
Raw History Blame Contribute Delete
36.3 kB
#!/usr/bin/env python
from __future__ import annotations
import gc
import importlib.util
import json
import os
import site
import sys
import time
import wave
from dataclasses import dataclass
from pathlib import Path
from types import SimpleNamespace
from typing import Any
import numpy as np
import torch
from transformers import AutoConfig, AutoModel, AutoTokenizer, TorchAoConfig
from decoder4_features_torch import Decoder4FeatureExtractor
BUNDLE_ROOT = Path(__file__).resolve().parent
def _candidate_roots() -> list[Path]:
roots = [
BUNDLE_ROOT,
BUNDLE_ROOT.parent,
Path.cwd(),
Path.cwd() / "moss_tts_clipper_istftnet2_release",
BUNDLE_ROOT.parent / "moss_tts_clipper_istftnet2_release",
]
seen: set[Path] = set()
unique: list[Path] = []
for root in roots:
resolved = root.resolve()
if resolved not in seen:
seen.add(resolved)
unique.append(resolved)
return unique
def resolve_asset(value: str | Path) -> Path:
path = Path(value)
if path.exists():
return path.resolve()
for root in _candidate_roots():
candidate = root / path
if candidate.exists():
return candidate.resolve()
return path
def add_release_root_to_syspath(checkpoint: str | Path) -> Path:
checkpoint_path = resolve_asset(checkpoint)
release_root = checkpoint_path.parent
release_root_str = str(release_root)
if release_root_str not in sys.path:
sys.path.insert(0, release_root_str)
return checkpoint_path
def ensure_nvidia_library_path() -> None:
if os.environ.get("MOSS_TTS_NVIDIA_LD_LIBRARY_PATH_READY") == "1":
return
lib_dirs: list[str] = []
site_roots = list(dict.fromkeys(site.getsitepackages() + [site.getusersitepackages()]))
for site_root in site_roots:
nvidia_root = Path(site_root) / "nvidia"
if not nvidia_root.exists():
continue
for lib_dir in nvidia_root.glob("*/lib"):
if lib_dir.is_dir():
lib_dirs.append(str(lib_dir))
if not lib_dirs:
return
current = os.environ.get("LD_LIBRARY_PATH", "")
current_parts = [part for part in current.split(":") if part]
wanted = [part for part in lib_dirs if part not in current_parts]
if not wanted:
os.environ["MOSS_TTS_NVIDIA_LD_LIBRARY_PATH_READY"] = "1"
return
os.environ["LD_LIBRARY_PATH"] = ":".join(wanted + current_parts)
os.environ["MOSS_TTS_NVIDIA_LD_LIBRARY_PATH_READY"] = "1"
os.execv(sys.executable, [sys.executable, *sys.argv])
def choose_providers(requested: str, available: list[str]) -> list[str]:
return [requested] if requested in available else ["CPUExecutionProvider"]
def select_dtype(device: torch.device, dtype_arg: str) -> torch.dtype:
if device.type != "cuda":
return torch.float32
if dtype_arg == "fp16":
return torch.float16
if dtype_arg == "bf16":
return torch.bfloat16
if dtype_arg == "fp32":
return torch.float32
major, _minor = torch.cuda.get_device_capability(device)
return torch.bfloat16 if major >= 8 else torch.float16
def save_wav_pcm16(path: Path, audio: torch.Tensor, sample_rate: int) -> None:
pcm = (audio.clamp(-1.0, 1.0) * 32767.0).to(torch.int16).cpu().numpy()
path.parent.mkdir(parents=True, exist_ok=True)
with wave.open(str(path), "wb") as handle:
handle.setnchannels(1)
handle.setsampwidth(2)
handle.setframerate(sample_rate)
handle.writeframes(pcm.tobytes())
def summarize(values: list[float]) -> dict[str, float]:
if not values:
return {"mean": 0.0, "min": 0.0, "max": 0.0}
return {
"mean": sum(values) / len(values),
"min": min(values),
"max": max(values),
}
def build_processor_without_audio_model(checkpoint: str | Path):
bundled_release_root = BUNDLE_ROOT.parent
if (bundled_release_root / "moss_tts_local_clipper_checkpoint").exists():
bundled_release_root_str = str(bundled_release_root)
if bundled_release_root_str not in sys.path:
sys.path.insert(0, bundled_release_root_str)
try:
from moss_tts_local_clipper_checkpoint.processing_moss_tts import MossTTSDelayProcessor
except ImportError:
processing_file = Path(checkpoint) / "processing_moss_tts.py"
if not processing_file.exists():
raise
spec = importlib.util.spec_from_file_location("moss_tts_checkpoint_processing", processing_file)
if spec is None or spec.loader is None:
raise ImportError(f"Could not load processor module from {processing_file}.")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
MossTTSDelayProcessor = module.MossTTSDelayProcessor
config = AutoConfig.from_pretrained(checkpoint, trust_remote_code=True)
tokenizer = AutoTokenizer.from_pretrained(checkpoint, trust_remote_code=True)
return MossTTSDelayProcessor(tokenizer=tokenizer, model_config=config)
def load_style_extractor_class(checkpoint: str | Path):
try:
from moss_tts_local.style_features import BertStyleFeatureExtractor
return BertStyleFeatureExtractor
except ImportError:
pass
style_file = Path(checkpoint) / "style_features.py"
if style_file.exists():
spec = importlib.util.spec_from_file_location("moss_tts_checkpoint_style_features", style_file)
if spec is None or spec.loader is None:
raise ImportError(f"Could not load style feature module from {style_file}.")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module.BertStyleFeatureExtractor
from moss_tts_local_clipper_checkpoint.style_features import BertStyleFeatureExtractor
return BertStyleFeatureExtractor
class TorchDecoder4FeatureRuntime:
def __init__(self, codec_path: str | Path, device: torch.device, dtype: torch.dtype) -> None:
if device.type != "cuda" or dtype != torch.float16:
raise RuntimeError("torch_fp16 decoder4 runtime requires CUDA fp16.")
audio_tokenizer = AutoModel.from_pretrained(codec_path, trust_remote_code=True).to(
device=device,
dtype=dtype,
)
audio_tokenizer.eval()
num_quantizers = int(audio_tokenizer.config.quantizer_kwargs.get("num_quantizers", 32))
self.extractor = Decoder4FeatureExtractor(
audio_tokenizer,
num_quantizers=num_quantizers,
output_dtype=dtype,
).to(device=device, dtype=dtype)
self.extractor.eval()
self.device = device
self.inputs = [SimpleNamespace(name="codes"), SimpleNamespace(name="lengths")]
del audio_tokenizer
gc.collect()
try:
torch.cuda.empty_cache()
except Exception:
pass
def get_inputs(self) -> list[Any]:
return self.inputs
def get_providers(self) -> list[str]:
return ["torch_fp16_cuda"]
def run(self, output_names: Any, feeds: dict[str, np.ndarray]) -> list[np.ndarray]:
del output_names
codes = torch.from_numpy(feeds["codes"]).to(device=self.device, dtype=torch.long)
lengths = torch.from_numpy(feeds["lengths"]).to(device=self.device, dtype=torch.long)
with torch.inference_mode():
features, feature_lengths = self.extractor(codes, lengths)
if self.device.type == "cuda":
torch.cuda.synchronize()
return [
features.detach().float().cpu().numpy(),
feature_lengths.detach().cpu().numpy(),
]
def run_vocoder_onnx(session: Any, features: np.ndarray, feature_lengths: np.ndarray) -> torch.Tensor:
input_name = session.get_inputs()[0].name
audio_np = session.run(None, {input_name: features.astype(np.float32, copy=False)})[0]
samples = int(feature_lengths[0]) * 960
audio = torch.from_numpy(audio_np[0, 0, :samples]).float().cpu()
return torch.nan_to_num(audio).reshape(-1).clamp(-1.0, 1.0)
class TorchScriptVocoderRuntime:
def __init__(
self,
decoder_dir: str | Path,
device: torch.device,
*,
use_cudagraph: bool = False,
bucket_frames: int = 0,
) -> None:
artifact = Path(decoder_dir) / (
"istftnet2_decoder_cuda.ts" if device.type == "cuda" else "istftnet2_decoder_cpu.ts"
)
if not artifact.exists():
raise FileNotFoundError(f"Missing TorchScript vocoder artifact: {artifact}")
self.device = device
# Load on CPU first, then move to device via .to() so ZeroGPU's
# torch.cuda hijack intercepts the move (torch.jit.load with
# map_location="cuda" bypasses the hijack and fails in the main
# process where no real GPU is attached).
self.module = torch.jit.load(str(artifact), map_location="cpu").eval()
if device.type == "cuda":
self.module = self.module.to(device)
self.use_cudagraph = bool(use_cudagraph and device.type == "cuda")
self.bucket_frames = max(0, int(bucket_frames))
self.graphs: dict[tuple[Any, ...], tuple[torch.cuda.CUDAGraph, torch.Tensor, torch.Tensor]] = {}
def get_inputs(self) -> list[Any]:
return [SimpleNamespace(name="features")]
def get_providers(self) -> list[str]:
suffix = "_cudagraph" if self.use_cudagraph else ""
return [f"torchscript_{self.device.type}{suffix}"]
def prewarm_buckets(self, frame_lengths: list[int]) -> dict[str, Any]:
requested = [int(length) for length in frame_lengths if int(length) > 0]
if not requested:
return {"requested": [], "elapsed_sec": 0.0}
if self.device.type == "cuda":
torch.cuda.synchronize(self.device)
start = time.perf_counter()
warmed: list[int] = []
with torch.inference_mode():
for frames in requested:
features = torch.randn(1, 768, frames, device=self.device, dtype=torch.float32)
self.run_tensor(features)
warmed.append(frames)
if self.device.type == "cuda":
torch.cuda.synchronize(self.device)
return {"requested": requested, "warmed": warmed, "elapsed_sec": time.perf_counter() - start}
def run_tensor(self, features_tensor: torch.Tensor) -> torch.Tensor:
features_tensor = features_tensor.to(device=self.device, dtype=torch.float32).contiguous()
if self.bucket_frames > 0 and features_tensor.ndim == 3:
frames = int(features_tensor.shape[-1])
bucketed = ((frames + self.bucket_frames - 1) // self.bucket_frames) * self.bucket_frames
if bucketed > frames:
padded = torch.zeros(
*features_tensor.shape[:-1],
bucketed,
device=features_tensor.device,
dtype=features_tensor.dtype,
)
padded[..., :frames] = features_tensor
features_tensor = padded
if not self.use_cudagraph:
with torch.inference_mode():
return self.module(features_tensor)
key = (
features_tensor.device.index,
features_tensor.dtype,
tuple(features_tensor.shape),
)
entry = self.graphs.get(key)
if entry is None:
try:
static_features = torch.empty_like(features_tensor)
static_features.copy_(features_tensor)
warmup_stream = torch.cuda.Stream(device=features_tensor.device)
warmup_stream.wait_stream(torch.cuda.current_stream(features_tensor.device))
with torch.cuda.stream(warmup_stream), torch.inference_mode():
for _ in range(3):
static_audio = self.module(static_features)
torch.cuda.current_stream(features_tensor.device).wait_stream(warmup_stream)
graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph), torch.inference_mode():
static_audio = self.module(static_features)
entry = (graph, static_features, static_audio)
self.graphs[key] = entry
except Exception:
self.use_cudagraph = False
self.graphs.clear()
try:
torch.cuda.synchronize(features_tensor.device)
except Exception:
pass
with torch.inference_mode():
return self.module(features_tensor)
graph, static_features, static_audio = entry
static_features.copy_(features_tensor)
graph.replay()
return static_audio
def run_vocoder(
session: Any,
features: np.ndarray | torch.Tensor,
feature_lengths: np.ndarray | torch.Tensor,
) -> torch.Tensor:
if isinstance(session, TorchScriptVocoderRuntime):
if isinstance(features, np.ndarray):
features_tensor = torch.from_numpy(features).to(device=session.device, dtype=torch.float32)
else:
features_tensor = features.to(device=session.device, dtype=torch.float32)
with torch.inference_mode():
audio_tensor = session.run_tensor(features_tensor)
if session.device.type == "cuda":
torch.cuda.synchronize()
if isinstance(feature_lengths, torch.Tensor):
samples = int(feature_lengths[0].item()) * 960
else:
samples = int(feature_lengths[0]) * 960
audio = audio_tensor[0, 0, :samples].detach().float().cpu()
return torch.nan_to_num(audio).reshape(-1).clamp(-1.0, 1.0)
if isinstance(features, torch.Tensor):
features = features.detach().cpu().numpy()
if isinstance(feature_lengths, torch.Tensor):
feature_lengths = feature_lengths.detach().cpu().numpy()
return run_vocoder_onnx(session, features, feature_lengths)
def parse_int_csv(value: str) -> list[int]:
values: list[int] = []
for part in value.split(","):
part = part.strip()
if not part:
continue
values.append(int(part))
return values
def enable_static_cache_for_global_model(model: Any) -> None:
type(model)._can_compile_fullgraph = True
language_config = model.config.language_config
for field in (
"max_position_embeddings",
"hidden_size",
"num_attention_heads",
"head_dim",
"num_key_value_heads",
"sliding_window",
"layer_types",
"num_hidden_layers",
):
if hasattr(language_config, field):
setattr(model.config, field, getattr(language_config, field))
def configure_torch_compile() -> None:
import importlib
import torch._inductor.config as inductor_config
dynamo_config = importlib.import_module("torch._dynamo.config")
dynamo_config.cache_size_limit = 256
dynamo_config.capture_scalar_outputs = True
inductor_config.triton.cudagraphs = False
inductor_config.triton.cudagraph_trees = False
inductor_config.triton.cudagraph_skip_dynamic_graphs = True
@dataclass
class OptimizedTTSConfig:
checkpoint: str | Path = "moss_tts_local_clipper_checkpoint"
codec_path: str | Path = "moss_audio_tokenizer"
decoder_dir: str | Path = "istftnet2_decoder4_50hz"
decoder4_features_onnx: str | Path = "ort_sessions/decoder4_features_fp32/model.onnx"
decoder_runtime: str = "torchscript_cuda"
vocoder_cudagraph: bool = False
vocoder_bucket_frames: int = 0
vocoder_prewarm_buckets: str = ""
decoder4_features_runtime: str = "torch_fp16"
decoder4_provider: str = "CUDAExecutionProvider"
dtype: str = "fp16"
tts_quantization: str = "none"
torch_opt_mode: str = "static-local-cache-triton-compile-cudagraph"
compile_mode: str = "max-autotune-no-cudagraphs"
cache_implementation: str = "static"
compile_global_transformer: bool = True
global_compile_mode: str = "default"
attn_implementation: str = "sdpa"
fast_prepare_inputs: bool = True
tensorrt_local: bool = False
triton_top_p: bool = False
triton_fused_lm_head: bool = False
triton_qkv_cache: bool = False
packed_local_qkv: bool = False
packed_local_mlp: bool = False
packed_adapter_mlp: bool = False
packed_adapter_mlp_scope: str = "heads"
static_packed_weights: bool = False
triton_rmsnorm: bool = False
tensorize_rmsnorm_eps: bool = True
fast_control_head: bool = False
feedback_lookup: bool = True
local_compile_fullgraph: bool = False
style_bert_model: str = "cirimus/modernbert-base-go-emotions"
style_bert_layer: int = 19
style_bert_max_length: int = 512
class OptimizedTTSRunner:
def __init__(self, config: OptimizedTTSConfig) -> None:
ensure_nvidia_library_path()
torch.backends.cudnn.benchmark = False
try:
torch.backends.cuda.enable_cudnn_sdp(False)
torch.backends.cuda.enable_flash_sdp(True)
torch.backends.cuda.enable_mem_efficient_sdp(True)
torch.backends.cuda.enable_math_sdp(True)
except Exception:
pass
checkpoint = add_release_root_to_syspath(config.checkpoint)
codec_path = resolve_asset(config.codec_path)
decoder_dir = resolve_asset(config.decoder_dir)
decoder4_features_onnx = resolve_asset(config.decoder4_features_onnx)
from torch_hf_optimizations import install_torch_frame_sampler, tensorize_rmsnorm_eps
import onnxruntime as ort
self.config = config
self.checkpoint = checkpoint
self.codec_path = codec_path
self.decoder_dir = decoder_dir
self.decoder4_features_onnx = decoder4_features_onnx
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.dtype = select_dtype(self.device, config.dtype)
wants_cuda = (
config.decoder4_provider == "CUDAExecutionProvider"
or config.decoder_runtime == "onnx_cuda"
or config.decoder4_features_runtime == "torch_fp16"
)
if wants_cuda and hasattr(ort, "preload_dlls"):
ort.preload_dlls(cuda=True, cudnn=True, directory="")
available_providers = ort.get_available_providers()
session_options = ort.SessionOptions()
session_options.log_severity_level = 2
self.available_ort_providers = available_providers
self.processor = build_processor_without_audio_model(checkpoint)
self.processor.model_config.sampling_rate = 48000
if config.decoder4_features_runtime == "onnx":
self.decoder4_session = ort.InferenceSession(
str(decoder4_features_onnx),
sess_options=session_options,
providers=choose_providers(config.decoder4_provider, available_providers),
)
elif config.decoder4_features_runtime == "torch_fp16":
self.decoder4_session = TorchDecoder4FeatureRuntime(codec_path, device=self.device, dtype=torch.float16)
else:
raise ValueError(f"Unsupported decoder4_features_runtime: {config.decoder4_features_runtime}")
if config.decoder_runtime.startswith("torchscript_"):
vocoder_device = torch.device(
"cuda" if config.decoder_runtime == "torchscript_cuda" and torch.cuda.is_available() else "cpu"
)
self.vocoder_session = TorchScriptVocoderRuntime(
decoder_dir,
vocoder_device,
use_cudagraph=config.vocoder_cudagraph,
bucket_frames=config.vocoder_bucket_frames,
)
else:
vocoder_provider = "CUDAExecutionProvider" if config.decoder_runtime == "onnx_cuda" else "CPUExecutionProvider"
self.vocoder_session = ort.InferenceSession(
str(decoder_dir / "istftnet2_decoder.onnx"),
sess_options=session_options,
providers=choose_providers(vocoder_provider, available_providers),
)
self.vocoder_prewarm_result: dict[str, Any] | None = None
if isinstance(self.vocoder_session, TorchScriptVocoderRuntime):
prewarm_buckets = parse_int_csv(config.vocoder_prewarm_buckets)
if prewarm_buckets:
try:
self.vocoder_prewarm_result = self.vocoder_session.prewarm_buckets(prewarm_buckets)
except Exception:
# Prewarm can fail in ZeroGPU main process (no real GPU
# attached at startup). It's an optimization only.
self.vocoder_prewarm_result = {"requested": prewarm_buckets, "warmed": [], "elapsed_sec": 0.0, "skipped": True}
tts_load_kwargs: dict[str, Any] = {
"trust_remote_code": True,
"torch_dtype": self.dtype,
"attn_implementation": config.attn_implementation,
}
if config.tts_quantization != "none":
if self.device.type != "cuda":
raise RuntimeError("TorchAO TTS quantization requires CUDA in this runner.")
modules_to_not_convert = None
if config.tts_quantization in {
"torchao_fp8_weight_only_global",
"torchao_fp8_dynamic_activation_fp8_weight_global",
}:
modules_to_not_convert = [
"local_transformer",
"speech_embedding_to_local_mlp",
"local_to_speech_embedding_mlps",
"layer_norm_before_lm_heads",
"lm_heads",
]
if config.tts_quantization == "torchao_fp8_weight_only_global":
quant_kind = "torchao_fp8_weight_only"
else:
quant_kind = "torchao_fp8_dynamic_activation_fp8_weight"
else:
quant_kind = config.tts_quantization
if quant_kind == "torchao_fp8_weight_only":
from torchao.quantization import Float8WeightOnlyConfig
tts_load_kwargs["quantization_config"] = TorchAoConfig(
Float8WeightOnlyConfig(),
modules_to_not_convert=modules_to_not_convert,
)
elif quant_kind == "torchao_fp8_dynamic_activation_fp8_weight":
from torchao.quantization import Float8DynamicActivationFloat8WeightConfig
tts_load_kwargs["quantization_config"] = TorchAoConfig(
Float8DynamicActivationFloat8WeightConfig(),
modules_to_not_convert=modules_to_not_convert,
)
else:
raise ValueError(f"Unsupported tts_quantization: {config.tts_quantization}")
tts_load_kwargs["device_map"] = {"": self.device.index or 0}
self.model = AutoModel.from_pretrained(checkpoint, **tts_load_kwargs)
if config.tts_quantization == "none":
self.model = self.model.to(self.device)
self.model.eval()
self.style_feature_dim = int(getattr(self.model.config, "style_feature_dim", 0) or 0)
self.num_emotions = int(getattr(self.model.config, "num_emotions", 0) or 0)
self.style_extractor = None
if self.style_feature_dim > 0 and self.style_feature_dim != 2:
BertStyleFeatureExtractor = load_style_extractor_class(checkpoint)
self.style_extractor = BertStyleFeatureExtractor(
repo_id=config.style_bert_model,
layer_index=config.style_bert_layer,
max_length=config.style_bert_max_length,
target_dim=self.style_feature_dim,
device=self.device,
dtype=self.dtype,
attn_implementation="sdpa" if self.device.type == "cuda" else "eager",
)
self.tensorized_rmsnorm_eps = 0
if config.tensorize_rmsnorm_eps:
self.tensorized_rmsnorm_eps = tensorize_rmsnorm_eps(self.model, device=self.device)
if config.compile_global_transformer:
configure_torch_compile()
if config.compile_global_transformer or config.cache_implementation == "static":
enable_static_cache_for_global_model(self.model)
if config.compile_global_transformer:
if config.global_compile_mode == "default":
global_compile_mode = None
global_compile_options = None
self.global_compile_mode_effective = "default"
else:
global_compile_mode = None
global_compile_options = {
"triton.cudagraphs": False,
"triton.cudagraph_trees": False,
"triton.cudagraph_skip_dynamic_graphs": True,
}
self.global_compile_mode_effective = "default-no-inductor-cudagraphs"
self.model.model.language_model = torch.compile(
self.model.model.language_model,
mode=global_compile_mode,
options=global_compile_options,
fullgraph=False,
dynamic=True,
)
else:
self.global_compile_mode_effective = "disabled"
install_torch_frame_sampler(
self.model,
mode=config.torch_opt_mode,
compile_mode=config.compile_mode,
packed_local_qkv=config.packed_local_qkv,
packed_local_mlp=config.packed_local_mlp,
packed_adapter_mlp=config.packed_adapter_mlp,
packed_adapter_mlp_scope=config.packed_adapter_mlp_scope,
static_packed_weights=config.static_packed_weights,
triton_rmsnorm=config.triton_rmsnorm,
tensorrt_local=config.tensorrt_local,
triton_top_p=config.triton_top_p,
triton_fused_lm_head=config.triton_fused_lm_head,
triton_qkv_cache=config.triton_qkv_cache,
fast_prepare_inputs=config.fast_prepare_inputs,
fast_control_head=config.fast_control_head,
feedback_lookup=config.feedback_lookup,
local_compile_fullgraph=config.local_compile_fullgraph,
)
self.sample_rate = int(self.processor.model_config.sampling_rate)
def decode_outputs(self, outputs: list[tuple[int, torch.Tensor]]) -> tuple[torch.Tensor, float]:
if torch.cuda.is_available():
torch.cuda.synchronize()
start = time.perf_counter()
decoded_segments: list[torch.Tensor] = []
codes_input_name = self.decoder4_session.get_inputs()[0].name
lengths_input_name = self.decoder4_session.get_inputs()[1].name
for start_length, generation_ids in outputs:
frame_tokens = generation_ids.detach().cpu()[:, 0]
audio_codes = generation_ids.detach().cpu()[:, 1:]
is_pad = (audio_codes == int(self.processor.model_config.audio_pad_code)).all(dim=1)
is_eos = frame_tokens == int(self.processor.model_config.audio_end_token_id)
non_pad = ~is_pad & ~is_eos
if not non_pad.any():
continue
idx = torch.nonzero(non_pad).squeeze(1)
breaks = torch.where(idx[1:] != idx[:-1] + 1)[0] + 1
segments_idx = [idx] if breaks.numel() == 0 else list(torch.split(idx, breaks.tolist()))
for segment_index, segment_idx in enumerate(segments_idx):
segment_codes = audio_codes[segment_idx].contiguous()
if int(segment_codes.shape[0]) <= 0:
continue
codes_np = segment_codes.T.unsqueeze(1).numpy().astype(np.int64, copy=False)
lengths_np = np.asarray([int(segment_codes.shape[0])], dtype=np.int64)
feeds = {codes_input_name: codes_np, lengths_input_name: lengths_np}
if hasattr(self.decoder4_session, "run_tensors"):
features, feature_lengths = self.decoder4_session.run_tensors(feeds)
else:
features, feature_lengths = self.decoder4_session.run(None, feeds)
segment_audio = run_vocoder(self.vocoder_session, features, feature_lengths)
if segment_index == 0 and int(start_length) > 0:
trim_ratio = max(0.0, min(float(start_length) / float(segment_codes.shape[0]), 1.0))
if trim_ratio >= 1.0:
continue
if trim_ratio > 0.0:
segment_audio = segment_audio[..., int(segment_audio.shape[-1] * trim_ratio) :]
decoded_segments.append(segment_audio)
if torch.cuda.is_available():
torch.cuda.synchronize()
elapsed = time.perf_counter() - start
if not decoded_segments:
raise RuntimeError("Generation did not produce decodable audio.")
return torch.cat(decoded_segments, dim=-1).float().cpu(), elapsed
def synthesize(
self,
*,
text: str,
language: str = "en",
speaker_id: int = 31,
max_new_tokens: int = 160,
text_temperature: float = 0.0,
text_top_p: float = 1.0,
text_top_k: int | None = None,
audio_temperature: float = 0.8,
audio_top_p: float = 0.92,
audio_top_k: int | None = None,
audio_repetition_penalty: float = 1.0,
n_vq_for_inference: int = 32,
style_text: str | None = None,
style_features: torch.Tensor | None = None,
style_emotion_id: int | None = None,
style_energy: float = 0.7,
) -> dict[str, Any]:
conversations = [[self.processor.build_user_message(text=text, language=language)]]
prompt_start = time.perf_counter()
batch = self.processor(conversations, mode="generation")
input_ids = batch["input_ids"].to(self.device)
attention_mask = batch["attention_mask"].to(self.device)
prompt_sec = time.perf_counter() - prompt_start
if self.device.type == "cuda":
torch.cuda.synchronize()
generate_start = time.perf_counter()
generation_kwargs = {}
if self.config.cache_implementation != "none":
generation_kwargs["cache_implementation"] = self.config.cache_implementation
speaker_tensor = torch.tensor([speaker_id], device=self.device, dtype=torch.long)
generation_kwargs["speaker_ids"] = speaker_tensor
if self.style_feature_dim > 0:
if style_features is None:
if self.style_feature_dim == 2 and style_emotion_id is not None:
max_emotion = max(0, (self.num_emotions or 1) - 1)
style_features = torch.tensor(
[
[
float(max(0, min(max_emotion, int(style_emotion_id)))),
float(max(0.0, min(1.0, style_energy))),
]
],
dtype=torch.float32,
)
elif self.style_extractor is None:
raise RuntimeError("Checkpoint expects style features, but no style extractor is available.")
else:
style_features = self.style_extractor.encode((style_text or text).strip())
generation_kwargs["style_features"] = style_features.to(device=self.device)
with torch.inference_mode():
outputs = self.model.generate(
input_ids=input_ids,
attention_mask=attention_mask,
max_new_tokens=max_new_tokens,
n_vq_for_inference=n_vq_for_inference,
text_temperature=text_temperature,
text_top_p=text_top_p,
text_top_k=text_top_k,
audio_temperature=audio_temperature,
audio_top_p=audio_top_p,
audio_top_k=None,
audio_repetition_penalty=audio_repetition_penalty,
**generation_kwargs,
)
if self.device.type == "cuda":
torch.cuda.synchronize()
generate_sec = time.perf_counter() - generate_start
audio, decode_sec = self.decode_outputs(outputs)
audio_sec = float(audio.numel() / self.sample_rate)
return {
"audio": audio,
"audio_sec": audio_sec,
"prompt_sec": prompt_sec,
"generate_sec": generate_sec,
"decode_sec": decode_sec,
"generate_x_realtime": audio_sec / generate_sec if generate_sec else 0.0,
"total_x_realtime": audio_sec / (generate_sec + decode_sec) if generate_sec + decode_sec else 0.0,
"generated_tokens": int(outputs[0][1].shape[0]) if outputs else 0,
"prompt_tokens": int(input_ids.shape[1]),
}
def runtime_summary(self) -> dict[str, Any]:
return {
"device": str(self.device),
"dtype": str(self.dtype),
"tts_quantization": self.config.tts_quantization,
"processor_loads_audio_tokenizer": False,
"audio_decode_runtime": f"{self.config.decoder4_features_runtime}_decoder4_features_plus_{self.config.decoder_runtime}_vocoder",
"checkpoint": str(self.checkpoint),
"codec_path": str(self.codec_path),
"decoder_dir": str(self.decoder_dir),
"decoder4_features_onnx": str(self.decoder4_features_onnx),
"vocoder_onnx": str(self.decoder_dir / "istftnet2_decoder.onnx"),
"decoder4_providers": self.decoder4_session.get_providers(),
"vocoder_providers": self.vocoder_session.get_providers(),
"available_ort_providers": self.available_ort_providers,
"torch_opt_mode": self.config.torch_opt_mode,
"compile_mode": self.config.compile_mode,
"cache_implementation": self.config.cache_implementation,
"compile_global_transformer": bool(self.config.compile_global_transformer),
"global_compile_mode": self.global_compile_mode_effective,
"fast_prepare_inputs": bool(self.config.fast_prepare_inputs),
"tensorrt_local": bool(self.config.tensorrt_local),
"triton_top_p": bool(self.config.triton_top_p),
"triton_fused_lm_head": bool(self.config.triton_fused_lm_head),
"triton_qkv_cache": bool(self.config.triton_qkv_cache),
"packed_local_qkv": bool(self.config.packed_local_qkv),
"packed_local_mlp": bool(self.config.packed_local_mlp),
"packed_adapter_mlp": bool(self.config.packed_adapter_mlp),
"packed_adapter_mlp_scope": self.config.packed_adapter_mlp_scope,
"static_packed_weights": bool(self.config.static_packed_weights),
"triton_rmsnorm": bool(self.config.triton_rmsnorm),
"tensorize_rmsnorm_eps": bool(self.config.tensorize_rmsnorm_eps),
"tensorized_rmsnorm_eps": int(self.tensorized_rmsnorm_eps),
"fast_control_head": bool(self.config.fast_control_head),
"feedback_lookup": bool(self.config.feedback_lookup),
"local_compile_fullgraph": bool(self.config.local_compile_fullgraph),
"style_feature_dim": int(self.style_feature_dim),
"num_emotions": int(self.num_emotions),
"vocoder_cudagraph": bool(self.config.vocoder_cudagraph),
"vocoder_bucket_frames": int(self.config.vocoder_bucket_frames),
"vocoder_prewarm_buckets": parse_int_csv(self.config.vocoder_prewarm_buckets),
"vocoder_prewarm_result": self.vocoder_prewarm_result,
}
def write_summary(path: Path, summary: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
serializable = {
key: value
for key, value in summary.items()
if not isinstance(value, torch.Tensor)
}
path.write_text(json.dumps(serializable, indent=2))