Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
225 lines
7.1 KiB
Python
225 lines
7.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Per-request benchmark for the official GLM-TTS Python implementation."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
import types
|
|
from pathlib import Path
|
|
|
|
import torch
|
|
import torchaudio
|
|
|
|
|
|
class IdentityNormalizer:
|
|
def __init__(self, *args, **kwargs):
|
|
del args, kwargs
|
|
|
|
def normalize(self, text: str) -> str:
|
|
return text
|
|
|
|
|
|
def install_wetext_identity_stub() -> None:
|
|
modules = {
|
|
"tn": types.ModuleType("tn"),
|
|
"tn.chinese": types.ModuleType("tn.chinese"),
|
|
"tn.chinese.normalizer": types.ModuleType(
|
|
"tn.chinese.normalizer"
|
|
),
|
|
"tn.english": types.ModuleType("tn.english"),
|
|
"tn.english.normalizer": types.ModuleType(
|
|
"tn.english.normalizer"
|
|
),
|
|
}
|
|
modules["tn.chinese.normalizer"].Normalizer = IdentityNormalizer
|
|
modules["tn.english.normalizer"].Normalizer = IdentityNormalizer
|
|
sys.modules.update(modules)
|
|
|
|
|
|
def load_reference_module(root: Path):
|
|
entry = root / "glmtts_inference.py"
|
|
if not entry.is_file():
|
|
raise FileNotFoundError(entry)
|
|
install_wetext_identity_stub()
|
|
if not hasattr(torch, "npu"):
|
|
torch.npu = types.SimpleNamespace(is_available=lambda: False)
|
|
os.chdir(root)
|
|
sys.path.insert(0, str(root))
|
|
spec = importlib.util.spec_from_file_location(
|
|
"glmtts_reference_inference", entry
|
|
)
|
|
if spec is None or spec.loader is None:
|
|
raise RuntimeError(f"cannot import {entry}")
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def synchronize() -> None:
|
|
if torch.cuda.is_available():
|
|
torch.cuda.synchronize()
|
|
|
|
|
|
def flatten_token_chunks(chunks) -> list[int]:
|
|
output: list[int] = []
|
|
for chunk in chunks:
|
|
if isinstance(chunk, (int, float)):
|
|
output.append(int(chunk))
|
|
elif torch.is_tensor(chunk) and chunk.ndim == 0:
|
|
output.append(int(chunk.item()))
|
|
else:
|
|
output.extend(int(value) for value in chunk)
|
|
return output
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("reference_root", type=Path)
|
|
parser.add_argument("--request-file", type=Path, required=True)
|
|
parser.add_argument("--voice-ref", type=Path, required=True)
|
|
parser.add_argument("--reference-text", required=True)
|
|
parser.add_argument("--out-dir", type=Path, required=True)
|
|
parser.add_argument(
|
|
"--greedy",
|
|
action="store_true",
|
|
help="Replace upstream RAS sampling with argmax for token parity.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
root = args.reference_root.resolve()
|
|
request_file = args.request_file.resolve()
|
|
voice_ref = args.voice_ref.resolve()
|
|
out_dir = args.out_dir.resolve()
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
module = load_reference_module(root)
|
|
frontend, text_frontend, _, llama, flow = module.load_models(
|
|
use_phoneme=False, sample_rate=24000
|
|
)
|
|
if args.greedy:
|
|
import llm.glmtts as glmtts_module
|
|
|
|
glmtts_module.common.ras_sampling = (
|
|
lambda weighted_scores, decoded_tokens, sampling, **kwargs:
|
|
weighted_scores.argmax()
|
|
)
|
|
requests = json.loads(
|
|
request_file.read_text(encoding="utf-8")
|
|
)["requests"]
|
|
results = []
|
|
for request in requests:
|
|
name = request["name"]
|
|
seed = int(request.get("seed", 0))
|
|
if torch.cuda.is_available():
|
|
torch.cuda.reset_peak_memory_stats()
|
|
synchronize()
|
|
started = time.perf_counter()
|
|
|
|
prompt_text = text_frontend.text_normalize(
|
|
args.reference_text
|
|
)
|
|
target_text = text_frontend.text_normalize(request["text"])
|
|
prompt_text_tokens = frontend._extract_text_token(
|
|
prompt_text + " "
|
|
)
|
|
prompt_speech_tokens = frontend._extract_speech_token(
|
|
[str(voice_ref)]
|
|
)
|
|
speech_features = frontend._extract_speech_feat(
|
|
str(voice_ref), sample_rate=24000
|
|
)
|
|
speaker_embedding = frontend._extract_spk_embedding(
|
|
str(voice_ref)
|
|
)
|
|
prompt_speech_list = (
|
|
prompt_speech_tokens.squeeze().tolist()
|
|
)
|
|
cache = {
|
|
"cache_text": [prompt_text],
|
|
"cache_text_token": [prompt_text_tokens],
|
|
"cache_speech_token": [prompt_speech_list],
|
|
"use_cache": True,
|
|
}
|
|
flow_prompt_tokens = torch.tensor(
|
|
[prompt_speech_list], dtype=torch.int32
|
|
).to(module.DEVICE)
|
|
waveform, mel, token_chunks, normalized = (
|
|
module.generate_long(
|
|
frontend=frontend,
|
|
text_frontend=text_frontend,
|
|
llm=llama,
|
|
flow=flow,
|
|
text_info=[name, target_text],
|
|
cache=cache,
|
|
embedding=speaker_embedding,
|
|
seed=seed,
|
|
flow_prompt_token=flow_prompt_tokens,
|
|
speech_feat=speech_features,
|
|
device=module.DEVICE,
|
|
use_phoneme=False,
|
|
)
|
|
)
|
|
synchronize()
|
|
wall_ms = (time.perf_counter() - started) * 1000.0
|
|
output_path = out_dir / f"{name}.wav"
|
|
torchaudio.save(str(output_path), waveform.cpu(), 24000)
|
|
frames = int(waveform.shape[-1])
|
|
audio_seconds = frames / 24000.0
|
|
peak_vram_mib = (
|
|
torch.cuda.max_memory_allocated() / (1024.0 * 1024.0)
|
|
if torch.cuda.is_available()
|
|
else 0.0
|
|
)
|
|
result = {
|
|
"name": name,
|
|
"seed": seed,
|
|
"wall_ms": wall_ms,
|
|
"audio_seconds": audio_seconds,
|
|
"rtf": wall_ms / 1000.0 / audio_seconds,
|
|
"frames": frames,
|
|
"mel_frames": int(mel.shape[-1]) if mel is not None else 0,
|
|
"prompt_text_ids": [
|
|
int(value)
|
|
for value in prompt_text_tokens.squeeze().tolist()
|
|
],
|
|
"prompt_speech_ids": [
|
|
int(value) for value in prompt_speech_list
|
|
],
|
|
"generated_speech_ids": flatten_token_chunks(
|
|
token_chunks
|
|
),
|
|
"normalized_text": (
|
|
normalized
|
|
if isinstance(
|
|
normalized,
|
|
(str, int, float, bool, list, dict, type(None)),
|
|
)
|
|
else str(normalized)
|
|
),
|
|
"peak_vram_mib": peak_vram_mib,
|
|
"audio_out": str(output_path),
|
|
}
|
|
results.append(result)
|
|
print(f"request={name}")
|
|
print(f"wall_ms={wall_ms}")
|
|
print(f"audio_seconds={audio_seconds}")
|
|
print(f"rtf={result['rtf']}")
|
|
print(f"frames={frames}")
|
|
print(f"peak_vram_mib={peak_vram_mib}")
|
|
print(f"audio_out={output_path}")
|
|
|
|
result_path = out_dir / "results.json"
|
|
result_path.write_text(
|
|
json.dumps({"requests": results}, indent=2),
|
|
encoding="utf-8",
|
|
)
|
|
print(f"results_out={result_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|