Files
vllm-omni/benchmarks/edge_harness/experiments/probe_qwen_tts_native_cpu.py
T

155 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Run the pinned Qwen3-TTS CustomVoice checkpoint on native Windows CPU.
This uses Qwen's PyTorch wrapper as a standalone backend feasibility check.
It does not prove Omni streaming, playback, or speech intelligibility.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import math
import os
import platform
import time
from pathlib import Path
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(8 * 1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--model-dir", type=Path, required=True)
parser.add_argument("--output-wav", type=Path, required=True)
parser.add_argument("--output-report", type=Path, required=True)
parser.add_argument("--text", default="Hello from the local computer.")
parser.add_argument("--speaker", default="Ryan")
parser.add_argument("--dtype", choices=("float16", "bfloat16", "float32"), default="bfloat16")
parser.add_argument("--max-new-tokens", type=int, default=64)
parser.add_argument("--threads", type=int, default=8)
parser.add_argument("--warmups", type=int, default=0)
parser.add_argument("--repeats", type=int, default=1)
args = parser.parse_args()
if args.max_new_tokens <= 0 or args.threads <= 0 or args.warmups < 0 or args.repeats <= 0:
parser.error("Token/thread/repeat counts must be positive and warmups nonnegative")
checkpoint = args.model_dir / "model.safetensors"
tokenizer_weights = args.model_dir / "speech_tokenizer" / "model.safetensors"
if not checkpoint.is_file() or not tokenizer_weights.is_file():
raise FileNotFoundError("The exact talker and speech-tokenizer weights are required")
os.environ["HF_HUB_OFFLINE"] = "1"
os.environ["TRANSFORMERS_OFFLINE"] = "1"
import numpy as np
import soundfile as sf
import torch
import transformers
from qwen_tts import Qwen3TTSModel
torch.set_num_threads(args.threads)
started = time.perf_counter()
model = Qwen3TTSModel.from_pretrained(
str(args.model_dir.resolve()),
device_map="cpu",
dtype=getattr(torch, args.dtype),
attn_implementation="sdpa",
local_files_only=True,
)
startup_s = time.perf_counter() - started
model_devices = sorted({parameter.device.type for parameter in model.model.parameters()})
if model_devices != ["cpu"]:
raise RuntimeError(f"TTS model was not wholly on CPU: {model_devices}")
if args.speaker.lower() not in model.get_supported_speakers():
raise ValueError("The requested speaker is not supported by this checkpoint")
def generate_one(*, save_wav: bool) -> dict:
torch.manual_seed(42)
started = time.perf_counter()
wavs, sample_rate = model.generate_custom_voice(
text=args.text,
language="English",
speaker=args.speaker,
max_new_tokens=args.max_new_tokens,
do_sample=True,
non_streaming_mode=True,
)
request_s = time.perf_counter() - started
if len(wavs) != 1 or sample_rate <= 0:
raise RuntimeError("Expected one waveform with a valid sample rate")
wav = np.asarray(wavs[0], dtype=np.float32).reshape(-1)
if not wav.size or not np.isfinite(wav).all():
raise RuntimeError("TTS returned an empty or non-finite waveform")
rms = float(np.sqrt(np.mean(wav.astype(np.float64) ** 2)))
if rms <= 1e-5:
raise RuntimeError("TTS waveform is effectively silent")
if save_wav:
args.output_wav.parent.mkdir(parents=True, exist_ok=True)
sf.write(args.output_wav, wav, sample_rate, subtype="PCM_16")
return {
"request_s": request_s,
"sample_rate": sample_rate,
"samples": int(wav.size),
"duration_s": float(wav.size / sample_rate),
"rms": rms,
"peak_abs": float(np.max(np.abs(wav))),
"nonzero_samples": int(np.count_nonzero(wav)),
"float32_sha256": hashlib.sha256(wav.tobytes()).hexdigest(),
}
warmups = [generate_one(save_wav=False) for _ in range(args.warmups)]
measured = [generate_one(save_wav=index == 0) for index in range(args.repeats)]
first = measured[0]
latencies = sorted(row["request_s"] for row in measured)
report = {
"scope": "one complete native Windows CPU text-to-WAV request via Qwen's standalone PyTorch wrapper; no Omni streaming or speech-quality claim",
"platform": platform.platform(),
"python": platform.python_version(),
"torch": torch.__version__,
"transformers": transformers.__version__,
"model_dir": str(args.model_dir.resolve()),
"talker_sha256": _sha256(checkpoint),
"speech_tokenizer_sha256": _sha256(tokenizer_weights),
"model_devices": model_devices,
"dtype": args.dtype,
"attention": "sdpa",
"threads": args.threads,
"text": args.text,
"speaker": args.speaker,
"language": "English",
"max_new_tokens": args.max_new_tokens,
"seed": 42,
"startup_s": startup_s,
"request_s": first["request_s"],
"sample_rate": first["sample_rate"],
"samples": first["samples"],
"duration_s": first["duration_s"],
"rms": first["rms"],
"peak_abs": first["peak_abs"],
"nonzero_samples": first["nonzero_samples"],
"wav_sha256": _sha256(args.output_wav),
"warmups": warmups,
"measured": measured,
"profile": {
"repeats": args.repeats,
"concurrency": 1,
"p50_request_s": latencies[math.ceil(0.5 * len(latencies)) - 1],
"p95_request_s": latencies[math.ceil(0.95 * len(latencies)) - 1],
"all_same_float32_audio": len({row["float32_sha256"] for row in measured}) == 1,
},
"status": "passed",
}
args.output_report.parent.mkdir(parents=True, exist_ok=True)
args.output_report.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()