voice_audio_multi/tests/warmbench.py
Fabio c36510c21e
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
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

5978 lines
244 KiB
Python

from __future__ import annotations
import argparse
import array
import hashlib
import json
import os
import random
import shutil
import subprocess
import sys
import time
import unicodedata
import wave
from datetime import datetime
from pathlib import Path
from typing import Any
REPO_ROOT = Path(__file__).resolve().parent.parent
ACE_STEP_BASELINE_CPP_AUDIO = REPO_ROOT / "build/logs/warmbench/ace_step_20s_baseline/ace_step_offline_cuda/cpp_audio/audio_0.wav"
VOXCPM2_MAX_LEN_CAP = 521
PYTHON_EXE = sys.executable
WHISPER_CONDA_ENV = "qwen3-tts"
WHISPER_MODEL = "tiny.en"
WHISPER_DEVICE = "cpu"
WHISPER_WORD_TIME_TOLERANCE_SEC = 0.05
OMNIVOICE_DEFAULT_WARMUP_TEXT = "This is a fixed warmup request for the OmniVoice session benchmark."
CJK_ASR_TRANSLATION = str.maketrans({
"軟": "软",
"聲": "声",
"說": "说",
"話": "话",
"讓": "让",
"聽": "听",
"來": "来",
"這": "这",
"個": "个",
"們": "们",
"會": "会",
"還": "还",
"裡": "里",
"裏": "里",
"嗎": "吗",
"對": "对",
"為": "为",
"風": "风",
"體": "体",
"現": "现",
"溫": "温",
"過": "过",
"實": "实",
"問": "问",
"題": "题",
"開": "开",
"閉": "闭",
"發": "发",
"聰": "聪",
"學": "学",
"習": "习",
"語": "语",
"氣": "气",
"號": "号",
"節": "节",
})
FAMILY_CONFIG: dict[str, dict[str, Any]] = {
"kokoro": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/kokoro_tts_warm_bench",
"python_script": "tests/kokoro_tts/kokoro_tts_python_warm_bench.py",
"model": "models/kokoro-82m-v1_0-ggml",
"case_catalog": "tests/kokoro_tts/kokoro_tts_warm_bench_cases.txt",
"voice_id": "af_heart",
"speaking_rate": 1.0,
"max_input_tokens": 512,
"min_text_length": 65,
},
"pocket_tts": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/pocket_tts_warm_bench",
"python_script": "tests/pocket_tts/pocket_tts_python_warm_bench.py",
"model": "models/pocket-tts/languages/english",
"case_catalog": "tests/pocket_tts/pocket_tts_warm_bench_cases.txt",
"voice_id": "alba",
"language": "english",
"min_text_length": 65,
},
"qwen3_tts": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/qwen3_tts_warm_bench",
"python_script": "tests/qwen3_tts/qwen3_tts_python_warm_bench.py",
"model": "models/Qwen3-TTS-12Hz-0.6B-Base",
"clone_audio": "resources/sample.wav",
"reference_text": "Some call me nature. Others call me Mother Nature. I've been here for over 4.5 billion years. 22,500 times longer than you.",
"case_catalog": "tests/qwen3_tts/qwen3_tts_warm_bench_cases.txt",
"case_overrides": {
"one_request": {
"model": "models/Qwen3-TTS-12Hz-1.7B-Base",
"clone_audio": "assets/resources/c.wav",
"reference_text": "Oh hi, I'm Nanashi Mumei from Hololive English Council. I'm that forgetful owl who only remembers the things that really matter.",
"source_issue": "https://github.com/0xShug0/audio.cpp/issues/67",
},
},
"max_new_tokens": 512,
"min_text_length": 70,
"run_asr_check": False,
"wav_cosine_min": 0.95,
"log_mel_cosine_min": 0.95,
"length_ratio_min": 0.98,
},
"qwen3_tts_voice_design": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/qwen3_tts_warm_bench",
"python_script": "tests/qwen3_tts/qwen3_tts_python_warm_bench.py",
"model": "models/Qwen3-TTS-12Hz-1.7B-VoiceDesign",
"task": "voice_design",
"language": "Chinese",
"voice_design_instruct": "体现撒娇稚嫩的萝莉女声,音调偏高且起伏明显,营造出黏人、做作又刻意卖萌的听觉效果。",
"case_catalog": "tests/qwen3_tts/qwen3_tts_voice_design_warm_bench_cases.txt",
"max_new_tokens": 2048,
"min_text_length": 18,
"whisper_model": "small",
"whisper_language": "Chinese",
"asr_compact_lcs_min": 0.65,
},
"qwen3_tts_custom_voice": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/qwen3_tts_warm_bench",
"python_script": "tests/qwen3_tts/qwen3_tts_python_warm_bench.py",
"model": "models/Qwen3-TTS-12Hz-1.7B-CustomVoice",
"task": "custom_voice",
"language": "English",
"speaker": "Ryan",
"custom_voice_instruct": "Very happy.",
"case_catalog": "tests/qwen3_tts/qwen3_tts_custom_voice_warm_bench_cases.txt",
"max_new_tokens": 2048,
"min_text_length": 65,
"whisper_model": "tiny.en",
},
"chatterbox": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/chatterbox_warm_bench",
"python_script": "tests/chatterbox/chatterbox_python_warm_bench.py",
"model": "models/chatterbox",
"clone_audio": "resources/sample.wav",
"case_catalog": "tests/chatterbox/chatterbox_warm_bench_cases.txt",
"default_warmup": 0,
"max_new_tokens": 1000,
"min_text_length": 70,
"whisper_model": "tiny.en",
"asr_compact_lcs_min": 0.65,
"min_rms": 1.0e-3,
},
"omnivoice_voice_clone": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/omnivoice_warm_bench",
"python_script": "tests/omnivoice/omnivoice_python_warm_bench.py",
"model": "models/OmniVoice",
"task": "voice_clone",
"clone_audio": "resources/sample.wav",
"reference_text": "Some call me nature. Others call me Mother Nature. I've been here for over 4.5 billion years. 22,500 times longer than you.",
"language": "en",
"case_catalog": "tests/omnivoice/omnivoice_warm_bench_cases.json",
"default_case_name": "default",
},
"omnivoice_voice_design": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/omnivoice_warm_bench",
"python_script": "tests/omnivoice/omnivoice_python_warm_bench.py",
"model": "models/OmniVoice",
"task": "voice_design",
"language": "en",
"voice_design_instruct": "female, high pitch",
"case_catalog": "tests/omnivoice/omnivoice_warm_bench_cases.json",
"default_case_name": "default",
},
"omnivoice_auto_voice": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/omnivoice_warm_bench",
"python_script": "tests/omnivoice/omnivoice_python_warm_bench.py",
"model": "models/OmniVoice",
"task": "auto_voice",
"language": "en",
"case_catalog": "tests/omnivoice/omnivoice_warm_bench_cases.json",
"default_case_name": "default",
},
"mel_band_roformer": {
"kind": "separation",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/roformer_warm_bench",
"python_script": "tests/roformer/roformer_python_warm_bench.py",
"model": "models/mel-roformer-mlx",
"python_model": "models/melbandroformer/MelBandRoformer.ckpt",
"python_config": "reference/Mel-Band-Roformer-Vocal-Model/configs/config_vocals_mel_band_roformer.yaml",
"case_catalog": "tests/roformer/roformer_warm_bench_cases.json",
"default_case_name": "default",
},
"htdemucs": {
"kind": "separation",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/demucs_warm_bench",
"python_script": "tests/demucs/demucs_python_warm_bench.py",
"model": "models/htdemucs",
"python_model": "models/htdemucs",
"python_name": "htdemucs",
"case_catalog": "tests/demucs/demucs_warm_bench_cases.json",
"default_case_name": "default",
},
"moss_tts_nano": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/moss_tts_nano_warm_bench",
"python_script": "tests/moss_tts_nano/moss_tts_nano_python_warm_bench.py",
"model": "models/MOSS-TTS-Nano-100M",
"audio_tokenizer_model": "models/MOSS-Audio-Tokenizer-Nano",
"clone_audio": "resources/sample.wav",
"case_catalog": "tests/moss_tts_nano/moss_tts_nano_warm_bench_cases.json",
"default_case_name": "default",
"max_new_frames": 300,
"active_codebooks": 16,
"do_sample": "true",
"text_temperature": 1.5,
"text_top_p": 1.0,
"text_top_k": 50,
"temperature": 1.7,
"top_p": 0.8,
"top_k": 25,
"repetition_penalty": 1.0,
"use_kv_cache": "true",
"voice_clone_max_text_tokens": 50,
"voice_clone_max_memory_per_sample_gb": 1.0,
"tts_max_batch_size": 0,
"codec_max_batch_size": 0,
"run_asr_check": False,
"log_mel_cosine_min": 0.80,
"length_ratio_min": 0.90,
"whisper_word_timestamps": False,
},
"moss_tts_local": {
"kind": "tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/moss_tts_local_warm_bench",
"python_script": "tests/moss_tts_local/moss_tts_local_python_warm_bench.py",
"model": "models/MOSS-TTS-Local-Transformer-v1.5",
"clone_audio": "resources/a.wav",
"case_catalog": "tests/moss_tts_local/moss_tts_local_warm_bench_cases.json",
"default_case_name": "long_lived_session",
"max_new_frames": 4096,
"do_sample": "true",
"audio_temperature": 1.7,
"audio_top_p": 0.8,
"audio_top_k": 25,
"audio_repetition_penalty": 1.0,
"dtype": "fp32",
"cpp_session_options": ["moss_tts_local.weight_type=f32"],
"use_kv_cache": "true",
"default_requests_per_session": 4,
"asr_compact_lcs_min": 0.95,
"run_asr_check": False,
"log_mel_cosine_min": 0.80,
"length_ratio_min": 0.80,
"whisper_word_timestamps": False,
},
"qwen3_asr": {
"kind": "asr",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/qwen3_asr_warm_bench",
"python_script": "tests/qwen3_asr/qwen3_asr_python_warm_bench.py",
"model": "models/Qwen3-ASR-0.6B",
"case_catalog": "tests/qwen3_asr/qwen3_asr_warm_bench_cases.json",
"max_new_tokens": 512,
"strict_text": True,
"check_language": True,
},
"higgs_audio_stt": {
"kind": "asr",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/higgs_audio_stt_warm_bench",
"python_script": "tests/higgs_audio_stt/higgs_audio_stt_python_warm_bench.py",
"model": "models/higgs-audio-v3-stt",
"case_catalog": "tests/higgs_audio_stt/higgs_audio_stt_warm_bench_cases.json",
"default_requests_per_session": 1,
},
"hviske_asr": {
"kind": "asr",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/hviske_asr_warm_bench",
"python_script": "tests/hviske_asr/hviske_asr_python_warm_bench.py",
"model": "models/hviske-v5.3",
"case_catalog": "tests/hviske_asr/hviske_asr_warm_bench_cases.json",
"default_requests_per_session": 1,
},
"nemotron_asr": {
"kind": "asr",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/nemotron_asr_warm_bench",
"python_script": "tests/nemotron_asr/nemotron_asr_python_warm_bench.py",
"model": "models/nemotron-3.5-asr-streaming-0.6b",
"case_catalog": "tests/nemotron_asr/nemotron_asr_warm_bench_cases.json",
"default_requests_per_session": 1,
},
"muscriptor": {
"kind": "asr",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/muscriptor_warm_bench",
"python_script": "tests/muscriptor/muscriptor_python_warm_bench.py",
"model": "models/muscriptor-small",
"python_model": "models/muscriptor-small/model.safetensors",
"case_catalog": "tests/muscriptor/muscriptor_warm_bench_cases.json",
"default_case_name": "known_wav_multi_request",
"default_requests_per_session": 4,
},
"neutts": {
"kind": "neutts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/neutts_warm_bench",
"python_script": "tests/neutts/neutts_2e_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "/media/leo/Share/models/audio.cpp-gguf/NeuTTS-2E-GGUF/neutts-2e-orig.gguf",
"python_model": "models/NeuTTS-2E",
"case_catalog": "tests/neutts/neutts_2e_warm_bench_cases.json",
"default_case_name": "neutts_builtin_emotions",
"default_requests_per_session": 4,
"default_warmup": 1,
"wav_cosine_min": 0.90,
"log_mel_cosine_min": 0.90,
"length_ratio_min": 0.90,
},
"vibevoice_asr": {
"kind": "asr",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/vibevoice_asr_warm_bench",
"python_script": "tests/vibevoice_asr/vibevoice_asr_python_warm_bench.py",
"model": "models/VibeVoice-ASR",
"case_catalog": "tests/vibevoice_asr/vibevoice_asr_warm_bench_cases.json",
"default_case_name": "default",
"default_requests_per_session": 1,
},
"voxtral_realtime": {
"kind": "asr",
"modes": ["offline", "streaming"],
"cpp_bin": "build/debug/bin/voxtral_realtime_warm_bench",
"python_script": "tests/voxtral_realtime/voxtral_realtime_python_warm_bench.py",
"model": "models/Voxtral-Mini-4B-Realtime-2602",
"case_catalog": "tests/voxtral_realtime/voxtral_realtime_warm_bench_cases.json",
"default_requests_per_session": 3,
"strict_text": True,
},
"qwen3_forced_aligner": {
"kind": "alignment",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/qwen3_forced_aligner_warm_bench",
"python_script": "tests/qwen3_forced_aligner/qwen3_forced_aligner_python_warm_bench.py",
"model": "models/Qwen3-ForcedAligner-0.6B",
"case_catalog": "tests/qwen3_forced_aligner/qwen3_forced_aligner_warm_bench_cases.json",
"strict_alignment": True,
},
"mms_forced_aligner": {
"kind": "alignment",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/mms_forced_aligner_warm_bench",
"python_script": "tests/mms_forced_aligner/mms_forced_aligner_python_warm_bench.py",
"model": "models/mms-300m-1130-forced-aligner",
"case_catalog": "tests/mms_forced_aligner/mms_forced_aligner_warm_bench_cases.json",
"strict_alignment": False,
},
"ace_step": {
"kind": "ace_step",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/ace_step_warm_bench",
"python_script": "tests/ace_step/ace_step_python_warm_bench.py",
"python_conda_env": "index-tts",
"model": "models/Ace-Step1.5",
"case_catalog": "tests/ace_step/ace_step_warm_bench_cases.json",
"default_case_name": "parity_find",
},
"vevo2": {
"kind": "vevo2",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/vevo2_warm_bench",
"python_script": "tests/vevo2/vevo2_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/Vevo2",
"case_catalog": "tests/vevo2/vevo2_warm_bench_cases.json",
"default_case_name": "route_zero_shot_tts",
"wav_cosine_min": 0.90,
"log_mel_cosine_min": 0.90,
"length_ratio_min": 0.80,
"cpp_session_options": ["vevo2.weight_type=f32"],
},
"seed_vc": {
"kind": "seed_vc",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/seed_vc_warm_bench",
"python_script": "tests/seed_vc/seed_vc_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/SeedVC-MLX",
"python_model": "models/SeedVC",
"case_catalog": "tests/seed_vc/seed_vc_warm_bench_cases.json",
"default_case_name": "v2_vc_example",
"wav_cosine_min": 0.80,
},
"miocodec": {
"kind": "miocodec",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/miocodec_warm_bench",
"python_script": "tests/miocodec/miocodec_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/MioCodec-25Hz-44.1kHz-v2",
"case_catalog": "tests/miocodec/miocodec_warm_bench_cases.json",
"default_case_name": "vc_metis",
"wav_cosine_min": 0.80,
"log_mel_cosine_min": 0.80,
"mrstft_spectral_convergence_max": 0.20,
"mrstft_logmag_mae_max": 0.30,
"similarity_vote_required": 2,
},
"voxcpm2": {
"kind": "voxcpm2",
"modes": ["offline", "streaming"],
"cpp_bin": "build/debug/bin/voxcpm2_warm_bench",
"python_script": "tests/voxcpm2/voxcpm2_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/VoxCPM2",
"case_catalog": "tests/voxcpm2/voxcpm2_warm_bench_cases.json",
"default_case_name": "cuda_parity_512char",
"wav_cosine_min": 0.95,
"log_mel_cosine_min": 0.95,
},
"supertonic": {
"kind": "supertonic",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/supertonic_warm_bench",
"python_script": "tests/supertonic/supertonic_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/supertonic-3",
"case_catalog": "tests/supertonic/supertonic_warm_bench_cases.json",
"default_case_name": "multi_path_voice_lang_steps_speed_chunking",
"default_requests_per_session": 6,
"default_warmup": 0,
"wav_cosine_min": 0.90,
"log_mel_cosine_min": 0.90,
},
"vibevoice": {
"kind": "vibevoice",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/vibevoice_warm_bench",
"python_script": "tests/vibevoice/vibevoice_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/VibeVoice-1.5B",
"case_catalog": "tests/vibevoice/vibevoice_warm_bench_cases.json",
"default_case_name": "short",
"default_requests_per_session": 1,
"default_warmup": 0,
"wav_cosine_min": 0.90,
"log_mel_cosine_min": 0.90,
"length_ratio_is_diagnostic": True,
},
"irodori_tts": {
"kind": "irodori_tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/irodori_tts_warm_bench",
"python_script": "tests/irodori_tts/irodori_tts_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/Irodori-TTS-500M-v3",
"case_catalog": "tests/irodori_tts/irodori_tts_warm_bench_cases.json",
"default_case_name": "short_emoji",
"default_requests_per_session": 1,
"default_warmup": 0,
"wav_cosine_min": 0.98,
"log_mel_cosine_min": 0.98,
"cpp_session_options": ["irodori_tts.weight_type=f32", "irodori_tts.codec_weight_type=f32"],
},
"heartmula": {
"kind": "heartmula",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/heartmula_warm_bench",
"python_script": "tests/heartmula/heartmula_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/HeartMuLa",
"case_catalog": "tests/heartmula/heartmula_warm_bench_cases.json",
"default_case_name": "default",
"default_requests_per_session": 1,
"default_warmup": 0,
"wav_cosine_min": 0.90,
"log_mel_cosine_min": 0.90,
"cpp_session_options": ["heartmula.weight_type=f32"],
},
"controlfoley": {
"kind": "controlfoley",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/controlfoley_warm_bench",
"python_script": "tests/controlfoley/controlfoley_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "/media/leo/Share/models/audio.cpp-gguf/ControlFoley-GGUF/controlfoley-large-44k-f32.gguf",
"python_model": "models/ControlFoley",
"case_catalog": "tests/controlfoley/controlfoley_warm_bench_cases.json",
"default_case_name": "official_examples",
"default_requests_per_session": 5,
"default_warmup": 0,
"wav_cosine_min": 0.80,
"log_mel_cosine_min": 0.90,
"similarity_vote_required": 1,
},
"confucius4_tts": {
"kind": "confucius4_tts",
"display_name": "Confucius4-TTS",
"modes": ["offline", "streaming"],
"cpp_bin": "build/debug/bin/confucius4_tts_warm_bench",
"python_script": "tests/confucius4_tts/confucius4_tts_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/Confucius4-TTS/audio_cpp/manifest.json",
"python_model": "models/Confucius4-TTS",
"case_catalog": "tests/confucius4_tts/confucius4_tts_warm_bench_cases.json",
"default_case_name": "multi_request_logic_paths",
"default_requests_per_session": 3,
"default_warmup": 0,
"wav_cosine_min": 0.98,
"log_mel_cosine_min": 0.98,
"length_ratio_min": 0.98,
},
"higgs_audio_tts": {
"kind": "higgs_audio_tts",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/higgs_audio_tts_warm_bench",
"python_script": "tests/higgs_audio_tts/higgs_audio_tts_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/higgs-audio-v3-tts-4b",
"case_catalog": "tests/higgs_audio_tts/higgs_audio_tts_warm_bench_cases.json",
"default_case_name": "default",
"default_requests_per_session": 1,
"default_warmup": 0,
"wav_cosine_min": 0.90,
"log_mel_cosine_min": 0.90,
"length_ratio_min": 0.98,
"cpp_session_options": ["higgs_audio_tts.codec_weight_type=f32"],
},
"index_tts2": {
"kind": "index_tts2",
"display_name": "IndexTTS2",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/index_tts2_warm_bench",
"python_script": "tests/index_tts2/index_tts2_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "models/IndexTTS-2",
"case_catalog": "tests/index_tts2/index_tts2_warm_bench_cases.json",
"default_case_name": "voice_clone",
"default_requests_per_session": 1,
"default_warmup": 0,
"cpp_session_options": ["index_tts2.weight_type=f32", "index_tts2.conv_weight_type=f32"],
"wav_cosine_min": 0.98,
"log_mel_cosine_min": 0.98,
"similarity_vote_required": 1,
},
"personaplex": {
"kind": "personaplex",
"display_name": "PersonaPlex",
"modes": ["offline", "streaming"],
"cpp_bin": "build/debug/bin/audiocpp_cli",
"python_script": "tests/personaplex/personaplex_python_warm_bench.py",
"python_conda_env": "qwen3-tts",
"model": "/media/leo/Share/models/audio.cpp-gguf/PersonaPlex-GGUF/personaplex-7b-v1-q4_k.gguf",
"python_model": "models/PersonaPlex",
"case_catalog": "tests/personaplex/personaplex_warm_bench_cases.json",
"default_case_name": "all_paths",
"default_streaming_case_name": "streaming_service_quality",
"default_requests_per_session": 5,
"default_warmup": 1,
"wav_cosine_min": 0.0,
"log_mel_cosine_min": 0.90,
"length_ratio_min": 0.98,
},
"parakeet": {
"kind": "asr",
"modes": ["offline", "longform", "streaming"],
"cpp_bin": "build/debug/bin/parakeet_warm_bench",
"python_script": "tests/parakeet_tdt/parakeet_python_warm_bench.py",
"model": "models/parakeet-tdt-0.6b-v3",
},
"sortformer": {
"kind": "diar",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/sortformer_diar_warm_bench",
"python_script": "tests/sortformer_diar/sortformer_diar_python_warm_bench.py",
"model": "models/diar_sortformer_4spk-v1",
"python_model": "nvidia/diar_sortformer_4spk-v1",
},
"citrinet_asr": {
"kind": "asr",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/citrinet_asr_warm_bench",
"python_script": "tests/citrinet_asr/citrinet_asr_python_warm_bench.py",
"model": "models/citrinet",
},
"marblenet_vad": {
"kind": "vad",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/marblenet_vad_warm_bench",
"python_script": "tests/marblenet_vad/marblenet_vad_python_warm_bench.py",
"model": "models/marblenet_vad",
},
"silero_vad": {
"kind": "vad",
"modes": ["offline"],
"cpp_bin": "build/debug/bin/silero_vad_warm_bench",
"python_script": "tests/silero_vad/silero_vad_python_warm_bench.py",
"model": "assets/framework/models/silero_vad",
},
}
def timestamp_seconds_local() -> str:
return datetime.now().strftime("%Y%m%d-%H%M%S")
def log_timestamp() -> str:
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def next_artifact_dir(base: Path) -> Path:
if not base.exists():
return base
index = 1
while True:
candidate = base.with_name(f"{base.name}_{index}")
if not candidate.exists():
return candidate
index += 1
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Run long-lived multi-request audio.cpp warmbench scenarios sequentially.")
parser.add_argument("--family", action="append", choices=tuple(FAMILY_CONFIG.keys()) + ("all",), dest="families", default=[])
parser.add_argument("--mode", choices=("offline", "longform", "streaming", "all"), default="all")
parser.add_argument("--backend", choices=("cpu", "cuda", "vulkan", "all"), default="all")
parser.add_argument("--warmup", type=int, default=1)
parser.add_argument("--iterations", type=int, default=1)
parser.add_argument("--threads", type=int, default=8)
parser.add_argument("--device", type=int, default=0)
parser.add_argument("--requests-per-session", type=int, default=2)
parser.add_argument(
"--audio-durations",
default="",
help="Comma-separated audio request durations in seconds for ASR/diarization benchmarks.",
)
parser.add_argument(
"--audio-warmup-duration",
type=float,
default=1.6,
help="Warmup audio duration in seconds for ASR/diarization benchmarks.",
)
parser.add_argument("--text", action="append", dest="texts", default=[], help="Repeat to provide exact TTS request texts.")
parser.add_argument(
"--case-name",
action="append",
dest="case_names",
default=[],
help="Repeat to select named TTS case-catalog sections in order.",
)
parser.add_argument("--warmup-text", default="", help="TTS warmup text passed to both Python and C++ warm benches.")
parser.add_argument("--clone-audio", default="", help="Reference audio used for voice clone parity.")
parser.add_argument("--model", default="", help="Override the model path for the selected family.")
parser.add_argument("--python-model", default="", help="Override the Python reference model path for the selected family.")
parser.add_argument(
"--voice-design-instruct",
action="append",
dest="voice_design_instructs",
default=[],
help="Repeat to provide per-request Qwen3 voice-design/custom-voice instructions.",
)
parser.add_argument(
"--request-instruct",
action="append",
dest="voice_design_instructs",
default=[],
help="Alias for --voice-design-instruct.",
)
parser.add_argument(
"--warmup-voice-design-instruct",
default="",
help="Qwen3 voice-design/custom-voice instruction used for warmup; defaults to the family instruction.",
)
parser.add_argument(
"--warmup-instruct",
dest="warmup_voice_design_instruct",
default="",
help="Alias for --warmup-voice-design-instruct.",
)
parser.add_argument("--speaker", default="", help="Qwen3 custom-voice speaker override.")
parser.add_argument("--warmup-speaker", default="", help="Qwen3 custom-voice warmup speaker override.")
parser.add_argument(
"--request-speaker",
action="append",
dest="request_speakers",
default=[],
help="Repeat to provide per-request Qwen3 custom-voice speakers.",
)
parser.add_argument("--max-new-tokens", type=int, default=0, help="Override Qwen3 TTS max generated code frames.")
parser.add_argument("--do-sample", choices=("true", "false"), default="true", help="Qwen3 TTS main talker sampling mode.")
parser.add_argument(
"--subtalker-do-sample",
choices=("true", "false"),
default="true",
help="Qwen3 TTS code-predictor sampling mode.",
)
parser.add_argument("--top-k", type=int, default=50, help="Qwen3 TTS main talker top-k.")
parser.add_argument("--top-p", type=float, default=1.0, help="Qwen3 TTS main talker top-p.")
parser.add_argument("--temperature", type=float, default=0.9, help="Qwen3 TTS main talker temperature.")
parser.add_argument("--subtalker-top-k", type=int, default=50, help="Qwen3 TTS code-predictor top-k.")
parser.add_argument("--subtalker-top-p", type=float, default=1.0, help="Qwen3 TTS code-predictor top-p.")
parser.add_argument("--subtalker-temperature", type=float, default=0.9, help="Qwen3 TTS code-predictor temperature.")
parser.add_argument("--chatterbox-language", default="en", help="Chatterbox language code.")
parser.add_argument("--chatterbox-s3gen-cfg-rate", type=float, default=0.7, help="Chatterbox S3Gen CFM CFG rate.")
parser.add_argument(
"--cpp-session-option",
action="append",
default=[],
help="Repeat key=value C++ session options for model-specific experiments.",
)
parser.add_argument("--seed", type=int, default=1234)
parser.add_argument(
"--test-noise-file",
default="",
help="Optional shared ACE-Step DiT initial-noise file (.f32). When set, both Python and C++ use this file instead of sampling noise.",
)
parser.add_argument("--artifact-stamp", default="")
parser.add_argument("--output-dir")
parser.add_argument("--output-json")
parser.add_argument("--keep-output-dir", action="store_true")
args = parser.parse_args()
args.warmup_was_explicit = any(arg == "--warmup" or arg.startswith("--warmup=") for arg in sys.argv[1:])
args.requests_per_session_was_explicit = any(
arg == "--requests-per-session" or arg.startswith("--requests-per-session=")
for arg in sys.argv[1:]
)
args.seed_was_explicit = any(arg == "--seed" or arg.startswith("--seed=") for arg in sys.argv[1:])
return args
def selected_families(args: argparse.Namespace) -> list[str]:
if not args.families or "all" in args.families:
return list(FAMILY_CONFIG.keys())
return args.families
def selected_backends(args: argparse.Namespace) -> list[str]:
return ["cpu", "cuda"] if args.backend == "all" else [args.backend]
def python_reference_backend(backend: str) -> str:
return "cuda" if backend == "vulkan" else backend
def set_command_backend(command: list[str], backend: str) -> None:
for index, value in enumerate(command[:-1]):
if value == "--backend":
command[index + 1] = backend
return
raise RuntimeError("warmbench command is missing --backend")
def selected_modes(args: argparse.Namespace, config: dict[str, Any]) -> list[str]:
modes = list(config["modes"])
if args.mode == "all":
return modes
return [args.mode] if args.mode in modes else []
def effective_warmup(config: dict[str, Any], args: argparse.Namespace) -> int:
if args.warmup_was_explicit:
return args.warmup
return int(config.get("default_warmup", args.warmup))
def aggregate_warmup_value(args: argparse.Namespace) -> int:
if args.warmup_was_explicit:
return args.warmup
values = {
effective_warmup(FAMILY_CONFIG[family], args)
for family in selected_families(args)
}
if len(values) == 1:
return next(iter(values))
return args.warmup
def aggregate_requests_per_session_value(args: argparse.Namespace) -> int:
if args.requests_per_session_was_explicit:
return args.requests_per_session
values = {
int(FAMILY_CONFIG[family].get("default_requests_per_session", args.requests_per_session))
for family in selected_families(args)
}
if len(values) == 1:
return next(iter(values))
return args.requests_per_session
def load_omnivoice_case_catalog(path: Path) -> dict[str, dict[str, dict[str, Any]]]:
payload = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise RuntimeError(f"invalid OmniVoice case catalog: {path}")
return payload
def resolve_omnivoice_case(
config: dict[str, Any],
args: argparse.Namespace,
) -> tuple[list[str], dict[str, Any], dict[str, Any]]:
if args.case_names and args.texts:
raise RuntimeError("--case-name and --text are mutually exclusive for OmniVoice warmbench")
scenario_config = dict(config)
warmup_text = args.warmup_text or OMNIVOICE_DEFAULT_WARMUP_TEXT
request_manifest: dict[str, Any] = {"warmup_text": warmup_text}
if args.texts:
if len(args.texts) < args.requests_per_session:
raise RuntimeError(
f"need {args.requests_per_session} --text values, only received {len(args.texts)}"
)
texts = args.texts[: args.requests_per_session]
request_manifest["texts"] = texts
return texts, request_manifest, scenario_config
task_name = str(config["task"])
catalog = load_omnivoice_case_catalog(REPO_ROOT / str(config["case_catalog"]))
task_cases = catalog.get(task_name)
if not isinstance(task_cases, dict):
raise RuntimeError(f"OmniVoice case catalog is missing task {task_name!r}")
if len(args.case_names) > 1:
raise RuntimeError("OmniVoice warmbench accepts at most one --case-name because a case carries scenario metadata")
case_name = args.case_names[0] if args.case_names else str(config.get("default_case_name", "default"))
case = task_cases.get(case_name)
if not isinstance(case, dict):
available = ", ".join(sorted(task_cases))
raise RuntimeError(f"unknown OmniVoice case name {case_name!r} for task {task_name}; available: {available}")
texts = case.get("texts")
if not isinstance(texts, list) or not texts:
raise RuntimeError(f"OmniVoice case {task_name}.{case_name} has no texts")
if len(texts) < args.requests_per_session:
raise RuntimeError(
f"OmniVoice case {task_name}.{case_name} needs at least {args.requests_per_session} texts, only has {len(texts)}"
)
texts = [str(text) for text in texts[: args.requests_per_session]]
for key in ("language", "voice_design_instruct", "reference_text", "whisper_model", "whisper_language", "asr_compact_lcs_min"):
if key in case:
scenario_config[key] = case[key]
if "warmup_text" in case and not args.warmup_text:
request_manifest["warmup_text"] = str(case["warmup_text"])
request_manifest["texts"] = texts
request_manifest["case_names"] = [case_name]
return texts, request_manifest, scenario_config
def resolve_tts_texts(
family: str,
config: dict[str, Any],
args: argparse.Namespace,
mode: str,
) -> tuple[list[str], dict[str, Any]]:
if args.case_names and args.texts:
raise RuntimeError("--case-name and --text are mutually exclusive for TTS benchmarks")
if family == "moss_tts_local" and not args.texts:
if len(args.case_names) > 1:
raise RuntimeError("MOSS-TTS-Local warmbench accepts at most one --case-name")
case_name = args.case_names[0] if args.case_names else str(config.get("default_case_name", "long_lived_session"))
catalog_path = REPO_ROOT / str(config["case_catalog"])
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
case = catalog.get(case_name)
if not isinstance(case, dict):
available = ", ".join(sorted(catalog))
raise RuntimeError(f"unknown MOSS-TTS-Local case name {case_name!r}; available: {available}")
requests = case.get("requests")
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"MOSS-TTS-Local case {case_name!r} has no requests")
if len(requests) < args.requests_per_session:
raise RuntimeError(
f"MOSS-TTS-Local case {case_name!r} needs at least {args.requests_per_session} requests, only has {len(requests)}"
)
selected = [dict(request) for request in requests[: args.requests_per_session]]
if getattr(args, "seed_was_explicit", False):
for request in selected:
request["seed"] = args.seed
texts = [str(request.get("text", "")) for request in selected]
if any(not text for text in texts):
raise RuntimeError(f"MOSS-TTS-Local case {case_name!r} contains an empty text")
warmup_text = args.warmup_text or str(case.get("warmup_text", ""))
return texts, {
"texts": texts,
"warmup_text": warmup_text,
"case_names": [case_name],
"requests": selected,
}
if family == "moss_tts_nano" and not args.texts:
if len(args.case_names) > 1:
raise RuntimeError("MOSS-TTS-Nano warmbench accepts at most one --case-name")
case_name = args.case_names[0] if args.case_names else str(config.get("default_case_name", "default"))
catalog_path = REPO_ROOT / str(config["case_catalog"])
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
case = catalog.get(case_name)
if not isinstance(case, dict):
available = ", ".join(sorted(catalog))
raise RuntimeError(f"unknown MOSS-TTS-Nano case name {case_name!r}; available: {available}")
texts = case.get("texts")
if not isinstance(texts, list) or not texts:
raise RuntimeError(f"MOSS-TTS-Nano case {case_name!r} has no texts")
if len(texts) < args.requests_per_session:
raise RuntimeError(
f"MOSS-TTS-Nano case {case_name!r} needs at least {args.requests_per_session} texts, only has {len(texts)}"
)
selected = [str(text) for text in texts[: args.requests_per_session]]
if any(not text for text in selected):
raise RuntimeError(f"MOSS-TTS-Nano case {case_name!r} contains an empty text")
request_manifest: dict[str, Any] = {
"texts": selected,
"case_names": [case_name],
}
if "warmup_text" in case and not args.warmup_text:
request_manifest["warmup_text"] = str(case["warmup_text"])
options = case.get("options")
if isinstance(options, dict):
request_manifest["options"] = dict(options)
return selected, request_manifest
case_catalog_path = REPO_ROOT / config["case_catalog"]
used_case_names: list[str] = []
if args.case_names:
used_case_names = list(args.case_names)
texts = load_named_case_texts(case_catalog_path, used_case_names, args.requests_per_session)
elif args.texts:
if len(args.texts) < args.requests_per_session:
raise RuntimeError(
f"need {args.requests_per_session} --text values, only received {len(args.texts)}"
)
texts = args.texts[: args.requests_per_session]
else:
texts = choose_diverse_texts(
load_case_texts(case_catalog_path),
args.requests_per_session,
scenario_seed(args.seed, family, mode),
int(config.get("min_text_length", 12)),
)
warmup_text = args.warmup_text
if not warmup_text and family == "chatterbox" and used_case_names:
warmup_text = load_named_case_warmup_text(case_catalog_path, used_case_names)
request_manifest: dict[str, Any] = {"texts": texts, "warmup_text": warmup_text}
if used_case_names:
request_manifest["case_names"] = used_case_names
return texts, request_manifest
def resolve_ace_step_case(config: dict[str, Any], args: argparse.Namespace) -> tuple[list[dict[str, Any]], dict[str, Any]]:
if len(args.case_names) > 1:
raise RuntimeError("ACE-Step warmbench accepts at most one --case-name")
if args.texts:
raise RuntimeError("ACE-Step warmbench uses JSON case requests; --text is not supported")
case_name = args.case_names[0] if args.case_names else str(config.get("default_case_name", "default"))
catalog_path = REPO_ROOT / str(config["case_catalog"])
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
case = catalog.get(case_name)
if not isinstance(case, dict):
available = ", ".join(sorted(catalog))
raise RuntimeError(f"unknown ACE-Step case name {case_name!r}; available: {available}")
requests = case.get("requests")
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"ACE-Step case {case_name!r} has no requests")
if len(requests) < args.requests_per_session:
raise RuntimeError(
f"ACE-Step case {case_name!r} needs at least {args.requests_per_session} requests, only has {len(requests)}"
)
selected = [dict(request) for request in requests[: args.requests_per_session]]
if getattr(args, "seed_was_explicit", False):
for request in selected:
request["seed"] = args.seed
assert_fields = case.get("assert_optional_request_fields", [])
if assert_fields:
if not isinstance(assert_fields, list) or not all(isinstance(field, str) for field in assert_fields):
raise RuntimeError(f"ACE-Step case {case_name!r} has invalid assert_optional_request_fields")
for request_index, request in enumerate(selected):
for field in assert_fields:
if field not in request:
raise RuntimeError(
f"ACE-Step case {case_name!r} request {request_index} missing asserted optional field {field!r}"
)
value = request[field]
if value is None or value == "" or value == []:
raise RuntimeError(
f"ACE-Step case {case_name!r} request {request_index} has empty asserted optional field {field!r}"
)
return selected, {"case_name": case_name, "requests": selected, "assert_optional_request_fields": assert_fields}
def resolve_vevo2_case(config: dict[str, Any], args: argparse.Namespace) -> tuple[list[dict[str, Any]], dict[str, Any]]:
if len(args.case_names) > 1:
raise RuntimeError("Vevo2 warmbench accepts at most one --case-name")
if args.texts:
raise RuntimeError("Vevo2 warmbench uses JSON case requests; --text is not supported")
case_name = args.case_names[0] if args.case_names else str(config.get("default_case_name", "default"))
catalog_path = REPO_ROOT / str(config["case_catalog"])
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
case = catalog.get(case_name)
if not isinstance(case, dict):
available = ", ".join(sorted(catalog))
raise RuntimeError(f"unknown Vevo2 case name {case_name!r}; available: {available}")
requests = case.get("requests")
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"Vevo2 case {case_name!r} has no requests")
if len(requests) < args.requests_per_session:
raise RuntimeError(
f"Vevo2 case {case_name!r} needs at least {args.requests_per_session} requests, only has {len(requests)}"
)
selected = [dict(request) for request in requests[: args.requests_per_session]]
if getattr(args, "seed_was_explicit", False):
for request in selected:
request["seed"] = args.seed
return selected, {"case_name": case_name, "requests": selected}
def resolve_neutts_case(config: dict[str, Any], args: argparse.Namespace) -> tuple[dict[str, Any], list[dict[str, Any]], dict[str, Any]]:
if len(args.case_names) > 1:
raise RuntimeError("NeuTTS warmbench accepts at most one --case-name")
if args.texts:
raise RuntimeError("NeuTTS warmbench uses JSON case requests; --text is not supported")
case_name = args.case_names[0] if args.case_names else str(config.get("default_case_name", ""))
catalog_path = REPO_ROOT / str(config["case_catalog"])
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
cases = catalog.get("cases")
if not isinstance(cases, list) or not cases:
raise RuntimeError(f"NeuTTS case catalog has no cases: {catalog_path}")
case = next((item for item in cases if item.get("id") == case_name), None)
if not isinstance(case, dict):
available = ", ".join(sorted(str(item.get("id", "")) for item in cases if isinstance(item, dict)))
raise RuntimeError(f"unknown NeuTTS case name {case_name!r}; available: {available}")
warmup = case.get("warmup")
requests = case.get("requests")
if not isinstance(warmup, dict):
raise RuntimeError(f"NeuTTS case {case_name!r} is missing warmup request")
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"NeuTTS case {case_name!r} has no requests")
if len(requests) < args.requests_per_session:
raise RuntimeError(
f"NeuTTS case {case_name!r} needs at least {args.requests_per_session} requests, only has {len(requests)}"
)
selected = [dict(request) for request in requests[: args.requests_per_session]]
if getattr(args, "seed_was_explicit", False):
for request in selected:
request["seed"] = args.seed
return dict(warmup), selected, {"case_name": case_name, "warmup": warmup, "requests": selected}
def resolve_personaplex_case(
config: dict[str, Any],
args: argparse.Namespace,
mode: str,
) -> tuple[list[dict[str, Any]], dict[str, Any]]:
if len(args.case_names) > 1:
raise RuntimeError("PersonaPlex warmbench accepts at most one --case-name")
if args.texts:
raise RuntimeError("PersonaPlex warmbench uses JSON case requests; --text is not supported")
if args.case_names:
case_name = args.case_names[0]
elif mode == "streaming":
case_name = str(config.get("default_streaming_case_name", config.get("default_case_name", "session_core_paths")))
else:
case_name = str(config.get("default_case_name", "session_core_paths"))
catalog_path = REPO_ROOT / str(config["case_catalog"])
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
case = catalog.get(case_name)
if not isinstance(case, dict):
available = ", ".join(sorted(catalog))
raise RuntimeError(f"unknown PersonaPlex case name {case_name!r}; available: {available}")
requests = case.get("requests")
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"PersonaPlex case {case_name!r} has no requests")
request_count = args.requests_per_session
if not args.requests_per_session_was_explicit and bool(case.get("use_all_requests_by_default", False)):
request_count = len(requests)
if len(requests) < request_count:
raise RuntimeError(
f"PersonaPlex case {case_name!r} needs at least {request_count} requests, only has {len(requests)}"
)
selected = [dict(request) for request in requests[:request_count]]
if getattr(args, "seed_was_explicit", False):
for request in selected:
request["seed"] = args.seed
return selected, {"case_name": case_name, "requests": selected}
def resolve_miocodec_case(config: dict[str, Any], args: argparse.Namespace) -> tuple[dict[str, Any], list[dict[str, Any]], dict[str, Any]]:
if len(args.case_names) > 1:
raise RuntimeError("MioCodec warmbench accepts at most one --case-name")
if args.texts:
raise RuntimeError("MioCodec warmbench uses JSON case requests; --text is not supported")
case_name = args.case_names[0] if args.case_names else str(config.get("default_case_name", "default"))
catalog_path = REPO_ROOT / str(config["case_catalog"])
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
case = catalog.get(case_name)
if not isinstance(case, dict):
available = ", ".join(sorted(catalog))
raise RuntimeError(f"unknown MioCodec case name {case_name!r}; available: {available}")
warmup = case.get("warmup")
requests = case.get("requests")
if not isinstance(warmup, dict):
raise RuntimeError(f"MioCodec case {case_name!r} is missing warmup request")
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"MioCodec case {case_name!r} has no requests")
if len(requests) < args.requests_per_session:
raise RuntimeError(
f"MioCodec case {case_name!r} needs at least {args.requests_per_session} requests, only has {len(requests)}"
)
selected = [dict(request) for request in requests[: args.requests_per_session]]
return dict(warmup), selected, {"case_name": case_name, "warmup": warmup, "requests": selected}
def resolve_voxcpm2_case(config: dict[str, Any], args: argparse.Namespace) -> tuple[dict[str, Any], list[dict[str, Any]], dict[str, Any]]:
if len(args.case_names) > 1:
raise RuntimeError("VoxCPM2 warmbench accepts at most one --case-name")
if args.texts:
raise RuntimeError("VoxCPM2 warmbench uses JSON case requests; --text is not supported")
case_name = args.case_names[0] if args.case_names else str(config.get("default_case_name", "default"))
catalog_path = REPO_ROOT / str(config["case_catalog"])
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
case = catalog.get(case_name)
if not isinstance(case, dict):
available = ", ".join(sorted(catalog))
raise RuntimeError(f"unknown VoxCPM2 case name {case_name!r}; available: {available}")
warmup = case.get("warmup")
requests = case.get("requests")
if not isinstance(warmup, dict):
raise RuntimeError(f"VoxCPM2 case {case_name!r} is missing warmup request")
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"VoxCPM2 case {case_name!r} has no requests")
if len(requests) < args.requests_per_session:
raise RuntimeError(
f"VoxCPM2 case {case_name!r} needs at least {args.requests_per_session} requests, only has {len(requests)}"
)
capped_warmup = dict(warmup)
if "max_len" in capped_warmup:
capped_warmup["max_len"] = min(int(capped_warmup["max_len"]), VOXCPM2_MAX_LEN_CAP)
selected = []
for request in requests[: args.requests_per_session]:
capped_request = dict(request)
if "max_len" in capped_request:
capped_request["max_len"] = min(int(capped_request["max_len"]), VOXCPM2_MAX_LEN_CAP)
selected.append(capped_request)
if getattr(args, "seed_was_explicit", False):
for request in selected:
request["seed"] = args.seed
return capped_warmup, selected, {"case_name": case_name, "warmup": capped_warmup, "requests": selected}
def omnivoice_instruct_values(
config: dict[str, Any],
args: argparse.Namespace,
request_count: int,
) -> tuple[str, list[str]]:
default_instruct = str(config.get("voice_design_instruct", ""))
warmup_instruct = args.warmup_voice_design_instruct or default_instruct
if args.voice_design_instructs:
if len(args.voice_design_instructs) < request_count:
raise RuntimeError(
f"need {request_count} --voice-design-instruct values for measured requests, "
f"only received {len(args.voice_design_instructs)}"
)
return warmup_instruct, args.voice_design_instructs[:request_count]
return warmup_instruct, [default_instruct] * request_count
def scenario_seed(base_seed: int, family: str, mode: str) -> int:
return base_seed + len(family) + len(mode)
def append_log(log_path: Path, message: str) -> None:
with log_path.open("a", encoding="utf-8") as handle:
handle.write(f"[{log_timestamp()}] {message}\n")
def write_timestamped_lines(path: Path, prefix: str, text: str) -> None:
with path.open("w", encoding="utf-8") as handle:
for line in text.splitlines():
handle.write(f"[{log_timestamp()}] {prefix} {line}\n")
def parse_summary_lines(text: str) -> dict[str, Any]:
summaries: list[dict[str, Any]] = []
warmup_summaries: list[dict[str, Any]] = []
texts: list[str] = []
warmup_texts: list[str] = []
audio_out: str | None = None
audio_outs: dict[int, str] = {}
final_summary: dict[str, Any] | None = None
current_average: dict[str, float] | None = None
average_metrics: list[dict[str, float]] = []
for line in text.splitlines():
if line.startswith("average["):
current_average = {}
average_metrics.append(current_average)
continue
if line.startswith("warmup_text["):
warmup_texts.append(line.split("=", 1)[1])
continue
if line.startswith("warmup_summary_json["):
warmup_summaries.append(json.loads(line.split("=", 1)[1]))
elif line.startswith("text["):
texts.append(line.split("=", 1)[1])
elif line.startswith("summary_json["):
summaries.append(json.loads(line.split("=", 1)[1]))
elif line.startswith("summary_json="):
final_summary = json.loads(line.split("=", 1)[1])
elif line.startswith("{"):
payload = json.loads(line)
if isinstance(payload, dict) and isinstance(payload.get("steps"), list):
final_summary = {
"family": payload.get("family"),
"backend": payload.get("backend"),
"sequence_steps": payload["steps"],
}
elif line.startswith("audio_out["):
index_text = line.split("]", 1)[0][len("audio_out["):]
audio_outs[int(index_text)] = line.split("=", 1)[1]
elif line.startswith("audio_out="):
audio_out = line.split("=", 1)[1]
elif current_average is not None and "=" in line:
key, value = line.split("=", 1)
try:
current_average[key] = float(value)
except ValueError:
continue
metrics: dict[str, float] = {}
if average_metrics:
totals: dict[str, float] = {}
counts: dict[str, int] = {}
for average in average_metrics:
for key, value in average.items():
totals[key] = totals.get(key, 0.0) + value
counts[key] = counts.get(key, 0) + 1
metrics = {key: totals[key] / counts[key] for key in totals}
return {
"summaries": summaries,
"warmup_summaries": warmup_summaries,
"texts": texts,
"warmup_texts": warmup_texts,
"summary": final_summary,
"audio_out": audio_out,
"audio_outs": audio_outs,
"metrics": metrics,
"request_metrics": average_metrics,
}
def wav_summary(path: Path) -> dict[str, Any]:
with wave.open(str(path), "rb") as wav_file:
sample_rate = wav_file.getframerate()
channels = wav_file.getnchannels()
frames = wav_file.getnframes()
return {
"sample_rate": sample_rate,
"channels": channels,
"frames": frames,
"samples": frames * channels,
}
def parse_personaplex_cli_summary(stdout: str, manifest_path: Path, backend: str) -> dict[str, Any]:
wall_ms_by_id: dict[str, float] = {}
for line in stdout.splitlines():
if not line.startswith("[TIMING] request.") or ".wall_ms " not in line:
continue
name_part, value_part = line.split(".wall_ms ", 1)
request_id = name_part[len("[TIMING] request."):]
wall_ms_by_id[request_id] = float(value_part)
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
steps: list[dict[str, Any]] = []
for item in manifest.get("requests", []):
request_id = str(item.get("id", ""))
audio_path = manifest_path.parent / "cpp_audio" / f"{request_id}.wav"
summary = wav_summary(audio_path) if audio_path.exists() else {}
duration_ms = 0.0
if summary:
duration_ms = 1000.0 * float(summary["frames"]) / float(summary["sample_rate"])
wall_ms = wall_ms_by_id.get(request_id, 0.0)
metrics = {"wall_ms": wall_ms}
if duration_ms > 0.0:
rtf = wall_ms / duration_ms
metrics.update({
"audio_duration_ms": duration_ms,
"rtf": rtf,
"x_realtime": 1.0 / rtf if rtf > 0.0 else 0.0,
})
steps.append({
"id": request_id,
"stems": [{
"name": "audio",
"audio": str(audio_path),
"summary": summary,
}],
"metrics": metrics,
})
return {"family": "personaplex", "backend": backend, "sequence_steps": steps}
def normalize_text(text: str) -> str:
text = unicodedata.normalize("NFKC", text.strip().lower()).translate(CJK_ASR_TRANSLATION)
chars: list[str] = []
for char in text:
if unicodedata.category(char).startswith("P"):
chars.append(" ")
else:
chars.append(char)
return " ".join("".join(chars).split())
def normalize_whisper_text(text: str) -> str:
return normalize_text(text)
def normalize_whisper_word(word: str) -> str:
return normalize_text(word)
def edit_distance(a: str, b: str, stop_after: int = 2) -> int:
if abs(len(a) - len(b)) > stop_after:
return stop_after + 1
previous = list(range(len(b) + 1))
for i, ca in enumerate(a, 1):
current = [i]
row_min = current[0]
for j, cb in enumerate(b, 1):
cost = 0 if ca == cb else 1
value = min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + cost)
current.append(value)
row_min = min(row_min, value)
if row_min > stop_after:
return stop_after + 1
previous = current
return previous[-1]
def lcs_ratio(a: str, b: str) -> float:
if not a or not b:
return 0.0
previous = [0] * (len(b) + 1)
for ca in a:
current = [0]
for j, cb in enumerate(b, 1):
current.append(previous[j - 1] + 1 if ca == cb else max(previous[j], current[-1]))
previous = current
return previous[-1] / float(max(len(a), len(b)))
def resolve_repo_path(path_text: str | None) -> Path | None:
if not path_text:
return None
path = Path(path_text)
if not path.is_absolute():
path = REPO_ROOT / path
return path
def run_whisper(
audio_path: Path,
artifact_dir: Path,
threads: int,
model: str = WHISPER_MODEL,
language: str | None = None,
word_timestamps: bool = True,
) -> dict[str, Any]:
artifact_dir.mkdir(parents=True, exist_ok=True)
command = [
"conda",
"run",
"-n",
WHISPER_CONDA_ENV,
"whisper",
str(audio_path),
"--model",
model,
"--device",
WHISPER_DEVICE,
"--output_dir",
str(artifact_dir),
"--output_format",
"json",
"--word_timestamps",
"True" if word_timestamps else "False",
"--fp16",
"False",
"--threads",
str(threads),
"--verbose",
"False",
]
if language:
command.extend(["--language", language])
completed = subprocess.run(
command,
cwd=str(REPO_ROOT),
capture_output=True,
text=True,
check=False,
)
if completed.returncode != 0:
raise RuntimeError(
f"whisper failed ({completed.returncode}) for {audio_path}\n{completed.stdout}\n{completed.stderr}"
)
output_json = artifact_dir / f"{audio_path.stem}.json"
payload = json.loads(output_json.read_text(encoding="utf-8"))
text = " ".join(segment.get("text", "").strip() for segment in payload.get("segments", [])).strip()
words: list[dict[str, Any]] = []
for segment in payload.get("segments", []):
for word in segment.get("words", []):
words.append(
{
"word": word.get("word", "").strip(),
"word_normalized": normalize_whisper_word(word.get("word", "")),
"start": word.get("start"),
"end": word.get("end"),
}
)
return {
"audio_path": str(audio_path),
"json_path": str(output_json),
"text": text,
"text_normalized": normalize_whisper_text(text),
"words": words,
}
def compare_whisper_outputs(
cpp_whisper: dict[str, Any],
py_whisper: dict[str, Any],
compact_lcs_min: float = 0.0,
) -> dict[str, Any]:
mismatches: list[str] = []
timing_mismatches: list[str] = []
cpp_text = str(cpp_whisper.get("text_normalized", ""))
py_text = str(py_whisper.get("text_normalized", ""))
if not cpp_text or not py_text:
mismatches.append("empty_text")
text_distance = edit_distance(cpp_text, py_text, 1)
cpp_compact = cpp_text.replace(" ", "")
py_compact = py_text.replace(" ", "")
compact_text_distance = edit_distance(cpp_compact, py_compact, 1)
compact_lcs = lcs_ratio(cpp_compact, py_compact)
if text_distance > 1 and compact_text_distance > 1 and compact_lcs < compact_lcs_min:
mismatches.append("text")
cpp_words = cpp_whisper.get("words", [])
py_words = py_whisper.get("words", [])
if len(cpp_words) != len(py_words):
timing_mismatches.append("word_count")
else:
for index, (cpp_word, py_word) in enumerate(zip(cpp_words, py_words)):
if cpp_word.get("word_normalized") != py_word.get("word_normalized"):
timing_mismatches.append(f"word[{index}]")
break
cpp_start = cpp_word.get("start")
py_start = py_word.get("start")
if cpp_start is None or py_start is None or abs(float(cpp_start) - float(py_start)) > WHISPER_WORD_TIME_TOLERANCE_SEC:
timing_mismatches.append(f"start[{index}]")
break
cpp_end = cpp_word.get("end")
py_end = py_word.get("end")
if cpp_end is None or py_end is None or abs(float(cpp_end) - float(py_end)) > WHISPER_WORD_TIME_TOLERANCE_SEC:
timing_mismatches.append(f"end[{index}]")
break
return {
"ok": not mismatches,
"reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}",
"mismatches": mismatches,
"text_distance": text_distance,
"compact_text_distance": compact_text_distance,
"compact_lcs": compact_lcs,
"compact_lcs_min": compact_lcs_min,
"timing_mismatches": timing_mismatches,
}
def load_case_texts(path: Path) -> list[str]:
texts: list[str] = []
for raw in path.read_text(encoding="utf-8").splitlines():
line = raw.strip()
if not line or line.startswith("#"):
continue
if line.startswith("[") and line.endswith("]"):
continue
texts.append(line)
deduped = list(dict.fromkeys(texts))
if not deduped:
raise RuntimeError(f"no texts found in case catalog: {path}")
return deduped
def load_text_case_catalog(path: Path) -> dict[str, list[str]]:
cases: dict[str, list[str]] = {}
current_case = ""
for raw in path.read_text(encoding="utf-8").splitlines():
line = raw.strip()
if not line or line.startswith("#"):
continue
if line.startswith("[") and line.endswith("]"):
current_case = line[1:-1]
cases.setdefault(current_case, [])
continue
if not current_case:
raise RuntimeError(f"case catalog entry is missing a [case] header: {path}")
cases[current_case].append(line)
return cases
def load_named_case_texts(path: Path, case_names: list[str], count: int) -> list[str]:
catalog = load_text_case_catalog(path)
texts: list[str] = []
for case_name in case_names:
if case_name not in catalog:
available = ", ".join(sorted(catalog))
raise RuntimeError(f"unknown case name {case_name!r} in {path}; available: {available}")
texts.extend(catalog[case_name])
if len(texts) < count:
raise RuntimeError(f"need {count} texts from selected cases, only found {len(texts)}")
return texts[:count]
def load_named_case_warmup_text(path: Path, case_names: list[str]) -> str:
if not case_names:
return ""
catalog = load_text_case_catalog(path)
warmup_case = f"{case_names[0]}_warmup"
warmup_texts = catalog.get(warmup_case, [])
return warmup_texts[0] if warmup_texts else ""
def load_qwen3_asr_cases(path: Path, count: int) -> tuple[dict[str, Any], list[dict[str, Any]]]:
payload = json.loads(path.read_text(encoding="utf-8"))
requests = payload.get("requests", [])
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"no Qwen3 ASR requests found in case catalog: {path}")
if len(requests) < count:
raise RuntimeError(f"need {count} Qwen3 ASR requests, only found {len(requests)}")
warmup = payload.get("warmup") or requests[0]
return warmup, requests[:count]
def load_qwen3_forced_aligner_cases(path: Path, count: int) -> tuple[dict[str, Any], list[dict[str, Any]]]:
payload = json.loads(path.read_text(encoding="utf-8"))
requests = payload.get("requests", [])
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"no Qwen3 forced aligner requests found in case catalog: {path}")
if len(requests) < count:
raise RuntimeError(f"need {count} Qwen3 forced aligner requests, only found {len(requests)}")
warmup = payload.get("warmup") or requests[0]
return warmup, requests[:count]
def load_catalog_asr_cases(
path: Path,
count: int,
family_label: str,
case_name: str = "",
) -> tuple[dict[str, Any], list[dict[str, Any]], dict[str, Any]]:
payload = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise RuntimeError(f"invalid {family_label} case catalog: {path}")
selected_case_name = case_name
case_payload: dict[str, Any]
if selected_case_name:
cases = payload.get("cases")
if isinstance(cases, dict) and selected_case_name in cases:
selected = cases[selected_case_name]
else:
selected = payload.get(selected_case_name)
if not isinstance(selected, dict):
available = []
if isinstance(cases, dict):
available.extend(str(key) for key in cases.keys())
available.extend(str(key) for key, value in payload.items() if isinstance(value, dict) and key != "cases")
raise RuntimeError(
f"unknown {family_label} case name {selected_case_name!r} in {path}; available: {', '.join(sorted(set(available)))}"
)
case_payload = selected
elif isinstance(payload.get("requests"), list):
case_payload = payload
else:
available = [str(key) for key, value in payload.items() if isinstance(value, dict)]
raise RuntimeError(f"{family_label} case catalog requires --case-name; available: {', '.join(sorted(available))}")
requests = case_payload.get("requests", [])
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"{family_label} case {selected_case_name or '<default>'!r} has no requests")
if len(requests) < count:
raise RuntimeError(f"need {count} {family_label} requests, only found {len(requests)}")
warmup = (
case_payload.get("warmup")
or case_payload.get("offline_warmup")
or case_payload.get("streaming_warmup")
or requests[0]
)
if not isinstance(warmup, dict):
raise RuntimeError(f"{family_label} case {selected_case_name or '<default>'!r} has invalid warmup")
return dict(warmup), [dict(item) for item in requests[:count]], {
"case_name": selected_case_name,
"warmup": warmup,
"requests": requests[:count],
}
def load_separation_cases(path: Path, case_name: str, count: int, family_label: str) -> tuple[Path, list[Path]]:
payload = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise RuntimeError(f"invalid {family_label} case catalog: {path}")
case = payload.get(case_name)
if not isinstance(case, dict):
available = ", ".join(sorted(payload))
raise RuntimeError(f"unknown {family_label} case name {case_name!r} in {path}; available: {available}")
warmup = case.get("warmup_audio")
requests = case.get("requests", [])
if not isinstance(warmup, str) or not warmup:
raise RuntimeError(f"{family_label} case {case_name!r} is missing warmup_audio")
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"{family_label} case {case_name!r} has no requests")
if len(requests) < count:
raise RuntimeError(f"need {count} {family_label} requests, only found {len(requests)} in case {case_name!r}")
return Path(warmup), [Path(str(item)) for item in requests[:count]]
def choose_diverse_texts(pool: list[str], count: int, seed: int, min_length: int = 12) -> list[str]:
filtered_pool = [text for text in pool if len(text) >= min_length]
if len(filtered_pool) >= count:
pool = filtered_pool
if len(pool) < count:
raise RuntimeError(f"need at least {count} texts, only found {len(pool)}")
rng = random.Random(seed)
ordered = sorted(pool, key=lambda item: (len(item), item))
chosen: list[str] = []
used: set[str] = set()
for bucket_index in range(count):
bucket_start = (len(ordered) * bucket_index) // count
bucket_end = (len(ordered) * (bucket_index + 1)) // count
bucket = [text for text in ordered[bucket_start:bucket_end] if text not in used]
if not bucket:
bucket = [text for text in ordered if text not in used]
pick = bucket[rng.randrange(len(bucket))]
chosen.append(pick)
used.add(pick)
return chosen
def read_wave_pcm16_mono(path: Path) -> tuple[int, bytes]:
with wave.open(str(path), "rb") as wav_file:
channels = wav_file.getnchannels()
sample_width = wav_file.getsampwidth()
sample_rate = wav_file.getframerate()
frames = wav_file.readframes(wav_file.getnframes())
if sample_width != 2:
raise RuntimeError(f"expected 16-bit PCM WAV, got sample width {sample_width} for {path}")
if channels == 2:
stereo = array.array("h")
stereo.frombytes(frames)
mono = array.array("h")
for index in range(0, len(stereo), 2):
mono.append(int((int(stereo[index]) + int(stereo[index + 1])) / 2))
frames = mono.tobytes()
elif channels != 1:
raise RuntimeError(f"expected mono or stereo WAV, got {channels} channels for {path}")
return sample_rate, frames
def write_wave_pcm16_mono(path: Path, sample_rate: int, frames: bytes) -> None:
with wave.open(str(path), "wb") as wav_file:
wav_file.setnchannels(1)
wav_file.setsampwidth(2)
wav_file.setframerate(sample_rate)
wav_file.writeframes(frames)
def ensure_16k_mono_audio(source: Path, dest: Path) -> Path:
sample_rate, frames = read_wave_pcm16_mono(source)
if sample_rate != 16000:
samples = array.array("h")
samples.frombytes(frames)
target_length = max(1, round(len(samples) * 16000 / sample_rate))
resampled = array.array("h")
if len(samples) == 1:
resampled.extend([samples[0]] * target_length)
else:
scale = (len(samples) - 1) / max(1, target_length - 1)
for output_index in range(target_length):
position = output_index * scale
left_index = int(position)
right_index = min(left_index + 1, len(samples) - 1)
fraction = position - left_index
left_value = int(samples[left_index])
right_value = int(samples[right_index])
interpolated = int(round(left_value * (1.0 - fraction) + right_value * fraction))
resampled.append(interpolated)
frames = resampled.tobytes()
sample_rate = 16000
write_wave_pcm16_mono(dest, sample_rate, frames)
return dest
def materialize_qwen3_asr_16k_audio(source: Path, dest: Path, start_sec: float | None, end_sec: float | None) -> Path:
import librosa
import numpy as np
import soundfile as sf
audio, sample_rate = librosa.load(source, sr=None, mono=False)
audio = np.asarray(audio, dtype=np.float32)
if audio.ndim == 2:
if audio.shape[0] <= 8 and audio.shape[1] > audio.shape[0]:
audio = audio.T
audio = audio.mean(axis=-1).astype(np.float32)
if start_sec is not None or end_sec is not None:
start = max(0, int(round((start_sec or 0.0) * sample_rate)))
end = len(audio) if end_sec is None else min(len(audio), int(round(end_sec * sample_rate)))
if end <= start:
raise RuntimeError(f"invalid Qwen3 ASR crop window for {source}: {start_sec}..{end_sec}")
audio = audio[start:end]
if sample_rate != 16000:
audio = librosa.resample(audio, orig_sr=sample_rate, target_sr=16000).astype(np.float32)
if audio.size == 0:
raise RuntimeError(f"Qwen3 ASR materialized empty audio: {source}")
peak = float(np.max(np.abs(audio)))
if peak > 1.0:
audio = audio / peak
audio = np.clip(audio, -1.0, 1.0)
dest.parent.mkdir(parents=True, exist_ok=True)
sf.write(dest, audio, 16000, subtype="PCM_16")
return dest
def materialize_qwen3_asr_audio(case: dict[str, Any], output_dir: Path, index: int, role: str) -> Path:
source = REPO_ROOT / str(case["audio"])
if "start_sec" not in case and "end_sec" not in case:
sample_rate, _ = read_wave_pcm16_mono(source)
if sample_rate == 16000:
return source
output_dir.mkdir(parents=True, exist_ok=True)
return materialize_qwen3_asr_16k_audio(source, output_dir / f"{role}_{index:02d}.wav", None, None)
if "end_sec" not in case:
raise RuntimeError(f"Qwen3 ASR crop requires end_sec for {source}")
output_dir.mkdir(parents=True, exist_ok=True)
start_sec = float(case.get("start_sec", 0.0))
end_sec = float(case["end_sec"])
if end_sec <= start_sec:
raise RuntimeError(f"invalid Qwen3 ASR crop window for {source}: {start_sec}..{end_sec}")
return materialize_qwen3_asr_16k_audio(source, output_dir / f"{role}_{index:02d}.wav", start_sec, end_sec)
def parse_audio_durations(value: str) -> list[float]:
if not value:
return []
durations: list[float] = []
for item in value.split(","):
item = item.strip()
if not item:
continue
duration = float(item)
if duration <= 0.0:
raise RuntimeError(f"audio duration must be positive: {item}")
durations.append(duration)
return durations
def build_audio_requests(
request_dir: Path,
count: int,
seed: int,
durations_sec: list[float],
warmup_duration_sec: float,
) -> tuple[Path, list[Path]]:
request_dir.mkdir(parents=True, exist_ok=True)
base_source = REPO_ROOT / "resources" / "sample.wav"
if not base_source.exists():
raise RuntimeError(f"missing base audio for request generation: {base_source}")
base_16k = ensure_16k_mono_audio(base_source, request_dir / "base_16k.wav")
sample_rate, frames = read_wave_pcm16_mono(base_16k)
total_frames = len(frames) // 2
if not durations_sec:
durations_sec = [2.2, 3.7, 5.4, 6.8, 8.1]
if len(durations_sec) < count:
raise RuntimeError(f"need {count} audio durations, only received {len(durations_sec)}")
chosen_durations = durations_sec[:count]
rng = random.Random(seed)
requests: list[Path] = []
for index, duration_sec in enumerate(chosen_durations):
request_frames = min(total_frames, int(duration_sec * sample_rate))
max_start = max(0, total_frames - request_frames)
start_frame = 0 if max_start == 0 else rng.randrange(0, max_start + 1)
byte_start = start_frame * 2
byte_end = byte_start + request_frames * 2
request_path = request_dir / f"request_{index:02d}_{request_frames}.wav"
write_wave_pcm16_mono(request_path, sample_rate, frames[byte_start:byte_end])
requests.append(request_path)
if warmup_duration_sec <= 0.0:
raise RuntimeError(f"audio warmup duration must be positive: {warmup_duration_sec}")
warmup_frames = min(total_frames, int(warmup_duration_sec * sample_rate))
warmup_path = request_dir / "warmup.wav"
write_wave_pcm16_mono(warmup_path, sample_rate, frames[: warmup_frames * 2])
return warmup_path, requests
def compare_float(a: float, b: float, tolerance: float) -> bool:
return abs(a - b) <= tolerance
def compare_kokoro(cpp_summary: dict[str, Any], py_summary: dict[str, Any]) -> dict[str, Any]:
mismatches: list[str] = []
for key in ("sample_rate", "channels", "samples"):
if cpp_summary.get(key) != py_summary.get(key):
mismatches.append(key)
for key in ("sum", "mean_abs", "rms", "min", "max"):
if not compare_float(float(cpp_summary.get(key, 0.0)), float(py_summary.get(key, 0.0)), 1e-4):
mismatches.append(key)
return {"ok": not mismatches, "reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}", "mismatches": mismatches}
def compare_pocket(cpp_summary: dict[str, Any], py_summary: dict[str, Any]) -> dict[str, Any]:
mismatches: list[str] = []
for key in (
"sample_rate",
"channels",
"samples",
"request_char_count",
"generated_steps",
"test_noise_mode",
"test_noise_seed",
"test_noise_steps",
"test_noise_latent_dim",
"test_noise_hash",
"test_noise_file",
):
if key in cpp_summary and key in py_summary and cpp_summary.get(key) != py_summary.get(key):
mismatches.append(key)
tolerances = {"sum": 1e-3, "mean_abs": 1e-4, "rms": 1e-4, "min": 1e-4, "max": 1e-4}
for key, tolerance in tolerances.items():
if key in cpp_summary and key in py_summary and not compare_float(float(cpp_summary.get(key, 0.0)), float(py_summary.get(key, 0.0)), tolerance):
mismatches.append(key)
return {"ok": not mismatches, "reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}", "mismatches": mismatches}
def compare_moss_tts(
cpp_summary: dict[str, Any],
py_summary: dict[str, Any],
cpp_audio_path: Path,
py_audio_path: Path,
log_mel_cosine_min: float,
length_ratio_min: float,
) -> dict[str, Any]:
import librosa
import numpy as np
import soundfile as sf
mismatches: list[str] = []
for key in ("sample_rate", "channels", "request_char_count"):
if key in cpp_summary and key in py_summary and cpp_summary.get(key) != py_summary.get(key):
mismatches.append(key)
cpp_audio, cpp_sr = sf.read(str(cpp_audio_path), always_2d=True)
py_audio, py_sr = sf.read(str(py_audio_path), always_2d=True)
if int(cpp_sr) != int(py_sr):
mismatches.append("sample_rate")
cpp_audio = np.asarray(cpp_audio, dtype=np.float32)
py_audio = np.asarray(py_audio, dtype=np.float32)
common_frames = min(cpp_audio.shape[0], py_audio.shape[0])
if common_frames <= 0:
return {
"ok": False,
"reason": "mismatch:empty_audio",
"mismatches": ["empty_audio"],
"metrics": {"log_mel_cosine_min": log_mel_cosine_min},
}
length_ratio = common_frames / float(max(cpp_audio.shape[0], py_audio.shape[0]))
if length_ratio < length_ratio_min:
mismatches.append("length_ratio")
cpp_mono = cpp_audio[:common_frames].mean(axis=1)
py_mono = py_audio[:common_frames].mean(axis=1)
mel_kwargs = {"sr": int(py_sr), "n_fft": 1024, "hop_length": 256, "n_mels": 80, "power": 2.0}
cpp_log_mel = np.log(np.maximum(librosa.feature.melspectrogram(y=cpp_mono, **mel_kwargs), 1.0e-10))
py_log_mel = np.log(np.maximum(librosa.feature.melspectrogram(y=py_mono, **mel_kwargs), 1.0e-10))
common_cols = min(cpp_log_mel.shape[1], py_log_mel.shape[1])
cpp_flat = cpp_log_mel[:, :common_cols].reshape(-1).astype(np.float64, copy=False)
py_flat = py_log_mel[:, :common_cols].reshape(-1).astype(np.float64, copy=False)
denom = float(np.linalg.norm(cpp_flat) * np.linalg.norm(py_flat))
log_mel_cosine = 1.0 if denom == 0.0 else float(np.dot(cpp_flat, py_flat) / denom)
if log_mel_cosine < log_mel_cosine_min:
mismatches.append("log_mel_cosine")
metrics = {
"log_mel_cosine": log_mel_cosine,
"log_mel_cosine_min": log_mel_cosine_min,
"length_ratio": length_ratio,
"length_ratio_min": length_ratio_min,
"cpp_frames": int(cpp_audio.shape[0]),
"python_frames": int(py_audio.shape[0]),
"common_frames": int(common_frames),
"sample_rate": int(py_sr),
}
return {
"ok": not mismatches,
"reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}",
"mismatches": mismatches,
"metrics": metrics,
}
def compare_qwen3_tts_audio(
cpp_summary: dict[str, Any],
py_summary: dict[str, Any],
cpp_audio_path: Path,
py_audio_path: Path,
waveform_cosine_min: float,
log_mel_cosine_min: float,
length_ratio_min: float,
) -> dict[str, Any]:
import librosa
import numpy as np
import soundfile as sf
mismatches: list[str] = []
if cpp_summary.get("sample_rate") != py_summary.get("sample_rate"):
mismatches.append("sample_rate")
if cpp_summary.get("channels") != py_summary.get("channels"):
mismatches.append("channels")
cpp_audio, cpp_sr = sf.read(str(cpp_audio_path), always_2d=True)
py_audio, py_sr = sf.read(str(py_audio_path), always_2d=True)
if int(cpp_sr) != int(py_sr):
mismatches.append("sample_rate")
cpp_audio = np.asarray(cpp_audio, dtype=np.float32)
py_audio = np.asarray(py_audio, dtype=np.float32)
cpp_flat = cpp_audio.reshape(-1).astype(np.float64, copy=False)
py_flat = py_audio.reshape(-1).astype(np.float64, copy=False)
common_samples = min(cpp_flat.size, py_flat.size)
if common_samples <= 0:
return {
"ok": False,
"reason": "mismatch:empty_audio",
"mismatches": ["empty_audio"],
"metrics": {
"wav_cosine_min": waveform_cosine_min,
"log_mel_cosine_min": log_mel_cosine_min,
"length_ratio_min": length_ratio_min,
},
}
length_ratio = common_samples / float(max(cpp_flat.size, py_flat.size))
if cpp_flat.size != py_flat.size:
mismatches.append("samples")
if length_ratio < length_ratio_min:
mismatches.append("length_ratio")
cpp_common = cpp_flat[:common_samples]
py_common = py_flat[:common_samples]
diff = cpp_common - py_common
waveform_denom = float(np.linalg.norm(cpp_common) * np.linalg.norm(py_common))
waveform_cosine = 1.0 if waveform_denom == 0.0 else float(np.dot(cpp_common, py_common) / waveform_denom)
if waveform_cosine < waveform_cosine_min:
mismatches.append("cosine")
cpp_mono = cpp_audio.mean(axis=1)[:common_samples].astype(np.float32, copy=False)
py_mono = py_audio.mean(axis=1)[:common_samples].astype(np.float32, copy=False)
mel_kwargs = {"sr": int(cpp_sr), "n_fft": 1024, "hop_length": 256, "n_mels": 80, "power": 2.0}
cpp_log_mel = np.log(np.maximum(librosa.feature.melspectrogram(y=cpp_mono, **mel_kwargs), 1.0e-10))
py_log_mel = np.log(np.maximum(librosa.feature.melspectrogram(y=py_mono, **mel_kwargs), 1.0e-10))
common_mel_frames = min(cpp_log_mel.shape[1], py_log_mel.shape[1])
if common_mel_frames > 0:
cpp_mel_flat = cpp_log_mel[:, :common_mel_frames].reshape(-1).astype(np.float64, copy=False)
py_mel_flat = py_log_mel[:, :common_mel_frames].reshape(-1).astype(np.float64, copy=False)
log_mel_denom = float(np.linalg.norm(cpp_mel_flat) * np.linalg.norm(py_mel_flat))
log_mel_cosine = 1.0 if log_mel_denom == 0.0 else float(np.dot(cpp_mel_flat, py_mel_flat) / log_mel_denom)
else:
log_mel_cosine = 0.0
if log_mel_cosine < log_mel_cosine_min:
mismatches.append("log_mel_cosine")
metrics = {
"cpp_samples": int(cpp_flat.size),
"python_samples": int(py_flat.size),
"common_samples": int(common_samples),
"length_ratio": length_ratio,
"length_ratio_min": length_ratio_min,
"cosine": waveform_cosine,
"wav_cosine_min": waveform_cosine_min,
"mae": float(np.mean(np.abs(diff), dtype=np.float64)),
"rmse": float(np.sqrt(np.mean(np.square(diff), dtype=np.float64))),
"max_abs": float(np.max(np.abs(diff))),
"cpp_mel_frames": int(cpp_log_mel.shape[1]),
"python_mel_frames": int(py_log_mel.shape[1]),
"common_mel_frames": int(common_mel_frames),
"log_mel_cosine": log_mel_cosine,
"log_mel_cosine_min": log_mel_cosine_min,
"sample_rate": int(cpp_sr),
}
return {
"ok": not mismatches,
"reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}",
"mismatches": mismatches,
"metrics": metrics,
}
def summarize_tts_asr(whisper_output: dict[str, Any]) -> str:
return f"text={json.dumps(whisper_output.get('text', ''), ensure_ascii=False)} words={len(whisper_output.get('words', []))} json={whisper_output.get('json_path', '')}"
def compare_parakeet_step(cpp_step: dict[str, Any], py_step: dict[str, Any]) -> dict[str, Any]:
mismatches: list[str] = []
sample_tolerance = 1
if normalize_text(str(cpp_step.get("text_output", ""))) != normalize_text(str(py_step.get("text_output", ""))):
mismatches.append("text_output")
cpp_words = cpp_step.get("word_timestamps", [])
py_words = py_step.get("word_timestamps", [])
if len(cpp_words) != len(py_words):
mismatches.append("word_count")
else:
for index, (cpp_word, py_word) in enumerate(zip(cpp_words, py_words)):
if normalize_text(str(cpp_word.get("word", ""))) != normalize_text(str(py_word.get("word", ""))):
mismatches.append(f"word[{index}]")
break
if abs(int(cpp_word.get("start_sample", 0)) - int(py_word.get("start_sample", 0))) > sample_tolerance:
mismatches.append(f"start_sample[{index}]")
break
if abs(int(cpp_word.get("end_sample", 0)) - int(py_word.get("end_sample", 0))) > sample_tolerance:
mismatches.append(f"end_sample[{index}]")
break
return {"ok": not mismatches, "reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}", "mismatches": mismatches}
def compare_qwen3_asr_step(
cpp_step: dict[str, Any],
py_step: dict[str, Any],
expected_fragments: list[str],
check_language: bool,
) -> dict[str, Any]:
mismatches: list[str] = []
cpp_text = normalize_text(str(cpp_step.get("text_output", "")))
py_text = normalize_text(str(py_step.get("text_output", "")))
if cpp_text != py_text:
mismatches.append("text_output")
if check_language and normalize_text(str(cpp_step.get("language", ""))) != normalize_text(str(py_step.get("language", ""))):
mismatches.append("language")
missing_fragments: list[str] = []
for fragment in expected_fragments:
expected = normalize_text(fragment)
if expected and (expected not in cpp_text or expected not in py_text):
missing_fragments.append(fragment)
if missing_fragments:
mismatches.append("expected_fragments")
return {
"ok": not mismatches,
"reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}",
"mismatches": mismatches,
"cpp_text_normalized": cpp_text,
"python_text_normalized": py_text,
"expected_fragments": expected_fragments,
"missing_fragments": missing_fragments,
}
def normalize_alignment_word(value: str) -> str:
return normalize_text(value).replace(" ", "")
def compare_qwen3_forced_aligner_step(
cpp_step: dict[str, Any],
py_step: dict[str, Any],
expected_words: list[str],
sample_tolerance: int = 1,
) -> dict[str, Any]:
mismatches: list[str] = []
cpp_words = cpp_step.get("word_timestamps", [])
py_words = py_step.get("word_timestamps", [])
if len(cpp_words) != len(py_words):
mismatches.append("word_count")
else:
for index, (cpp_word, py_word) in enumerate(zip(cpp_words, py_words)):
if normalize_alignment_word(str(cpp_word.get("word", ""))) != normalize_alignment_word(str(py_word.get("word", ""))):
mismatches.append(f"word[{index}]")
break
if abs(int(cpp_word.get("start_sample", 0)) - int(py_word.get("start_sample", 0))) > sample_tolerance:
mismatches.append(f"start_sample[{index}]")
break
if abs(int(cpp_word.get("end_sample", 0)) - int(py_word.get("end_sample", 0))) > sample_tolerance:
mismatches.append(f"end_sample[{index}]")
break
normalized_cpp_words = {normalize_alignment_word(str(item.get("word", ""))) for item in cpp_words}
missing_words = [
word for word in expected_words
if normalize_alignment_word(word) not in normalized_cpp_words
]
if missing_words:
mismatches.append("expected_words")
return {
"ok": not mismatches,
"reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}",
"mismatches": mismatches,
"expected_words": expected_words,
"missing_words": missing_words,
}
def compare_sortformer_step(cpp_step: dict[str, Any], py_step: dict[str, Any]) -> dict[str, Any]:
mismatches: list[str] = []
sample_tolerance = 1
cpp_turns = cpp_step.get("speaker_turns", [])
py_turns = py_step.get("speaker_turns", [])
if len(cpp_turns) != len(py_turns):
mismatches.append("turn_count")
else:
for index, (cpp_turn, py_turn) in enumerate(zip(cpp_turns, py_turns)):
if cpp_turn.get("speaker_id") != py_turn.get("speaker_id"):
mismatches.append(f"speaker_id[{index}]")
break
if abs(int(cpp_turn.get("start_sample", 0)) - int(py_turn.get("start_sample", 0))) > sample_tolerance:
mismatches.append(f"start_sample[{index}]")
break
if abs(int(cpp_turn.get("end_sample", 0)) - int(py_turn.get("end_sample", 0))) > sample_tolerance:
mismatches.append(f"end_sample[{index}]")
break
return {"ok": not mismatches, "reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}", "mismatches": mismatches}
def compare_vad_step(cpp_step: dict[str, Any], py_step: dict[str, Any]) -> dict[str, Any]:
mismatches: list[str] = []
cpp_segments = cpp_step.get("speech_segments", [])
py_segments = py_step.get("speech_segments", [])
if len(cpp_segments) != len(py_segments):
mismatches.append("segment_count")
else:
for index, (cpp_segment, py_segment) in enumerate(zip(cpp_segments, py_segments)):
if int(cpp_segment.get("start_sample", 0)) != int(py_segment.get("start_sample", 0)):
mismatches.append(f"start_sample[{index}]")
break
if int(cpp_segment.get("end_sample", 0)) != int(py_segment.get("end_sample", 0)):
mismatches.append(f"end_sample[{index}]")
break
if abs(float(cpp_segment.get("confidence", 0.0)) - float(py_segment.get("confidence", 0.0))) > 1e-4:
mismatches.append(f"confidence[{index}]")
break
return {"ok": not mismatches, "reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}", "mismatches": mismatches}
def compare_speaker_step(cpp_step: dict[str, Any], py_step: dict[str, Any]) -> dict[str, Any]:
mismatches: list[str] = []
if str(cpp_step.get("label", "")) != str(py_step.get("label", "")):
mismatches.append("label")
if int(cpp_step.get("index", -1)) != int(py_step.get("index", -1)):
mismatches.append("index")
if abs(float(cpp_step.get("score", 0.0)) - float(py_step.get("score", 0.0))) > 1e-4:
mismatches.append("score")
return {"ok": not mismatches, "reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}", "mismatches": mismatches}
def compare_separation_step(cpp_step: dict[str, Any], py_step: dict[str, Any]) -> dict[str, Any]:
import numpy as np
import soundfile as sf
cpp_stems = {
str(stem.get("name", "")): stem
for stem in cpp_step.get("stems", [])
if isinstance(stem, dict) and stem.get("name")
}
py_stems = {
str(stem.get("name", "")): stem
for stem in py_step.get("stems", [])
if isinstance(stem, dict) and stem.get("name")
}
mismatches: list[str] = []
metrics: dict[str, Any] = {}
if set(cpp_stems) != set(py_stems):
mismatches.append("stem_names")
return {
"ok": False,
"reason": f"mismatch:{mismatches[0]}",
"mismatches": mismatches,
"metrics": metrics,
}
for stem_name in sorted(py_stems):
cpp_stem = cpp_stems[stem_name]
py_stem = py_stems[stem_name]
cpp_summary = cpp_stem.get("summary", {})
py_summary = py_stem.get("summary", {})
stem_mismatches: list[str] = []
for key in ("sample_rate", "channels", "frames", "samples"):
if cpp_summary.get(key) != py_summary.get(key):
stem_mismatches.append(key)
cpp_audio_path = resolve_repo_path(str(cpp_stem.get("audio", "")))
py_audio_path = resolve_repo_path(str(py_stem.get("audio", "")))
if cpp_audio_path is None or py_audio_path is None or not cpp_audio_path.exists() or not py_audio_path.exists():
stem_mismatches.append("audio_path")
metrics[stem_name] = {"ok": False, "mismatches": stem_mismatches}
mismatches.append(f"{stem_name}:{stem_mismatches[0]}")
continue
cpp_audio, cpp_sr = sf.read(str(cpp_audio_path), always_2d=True)
py_audio, py_sr = sf.read(str(py_audio_path), always_2d=True)
cpp_audio = np.asarray(cpp_audio, dtype=np.float32)
py_audio = np.asarray(py_audio, dtype=np.float32)
if cpp_sr != py_sr:
stem_mismatches.append("sample_rate")
if cpp_audio.shape != py_audio.shape:
stem_mismatches.append("shape")
metrics[stem_name] = {
"ok": False,
"mismatches": stem_mismatches,
"cpp_shape": list(cpp_audio.shape),
"python_shape": list(py_audio.shape),
}
mismatches.append(f"{stem_name}:{stem_mismatches[0]}")
continue
diff = cpp_audio - py_audio
ref = py_audio
mae = float(np.mean(np.abs(diff), dtype=np.float64))
rmse = float(np.sqrt(np.mean(np.square(diff), dtype=np.float64)))
max_abs = float(np.max(np.abs(diff)))
cpp_flat = cpp_audio.reshape(-1).astype(np.float64, copy=False)
py_flat = py_audio.reshape(-1).astype(np.float64, copy=False)
denom = float(np.linalg.norm(cpp_flat) * np.linalg.norm(py_flat))
cosine = 1.0 if denom == 0.0 else float(np.dot(cpp_flat, py_flat) / denom)
ref_power = float(np.sum(np.square(ref, dtype=np.float64)))
err_power = float(np.sum(np.square(diff, dtype=np.float64)))
ref_rms = float(np.sqrt(np.mean(np.square(ref, dtype=np.float64))))
snr_db = float("inf") if err_power == 0.0 else float(10.0 * np.log10(max(ref_power, 1e-30) / err_power))
low_energy_ref = ref_rms < 1e-3
if mae > 1e-3:
stem_mismatches.append("mae")
if rmse > 2e-3:
stem_mismatches.append("rmse")
if max_abs > 2e-2:
stem_mismatches.append("max_abs")
if not low_energy_ref and cosine < 0.999:
stem_mismatches.append("cosine")
if not low_energy_ref and snr_db < 40.0:
stem_mismatches.append("snr_db")
metrics[stem_name] = {
"ok": not stem_mismatches,
"mismatches": stem_mismatches,
"mae": mae,
"rmse": rmse,
"max_abs": max_abs,
"cosine": cosine,
"snr_db": snr_db,
"ref_rms": ref_rms,
"low_energy_ref": low_energy_ref,
}
if stem_mismatches:
mismatches.append(f"{stem_name}:{stem_mismatches[0]}")
return {
"ok": not mismatches,
"reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}",
"mismatches": mismatches,
"metrics": metrics,
}
def compare_single_audio_step(
cpp_step: dict[str, Any],
py_step: dict[str, Any],
cosine_min: float,
log_mel_cosine_min: float | None = None,
mrstft_spectral_convergence_max: float | None = None,
mrstft_logmag_mae_max: float | None = None,
similarity_vote_required: int | None = None,
length_ratio_min: float = 0.98,
length_ratio_is_diagnostic: bool = False,
) -> dict[str, Any]:
import librosa
import numpy as np
import soundfile as sf
cpp_stems = cpp_step.get("stems", [])
py_stems = py_step.get("stems", [])
mismatches: list[str] = []
if not cpp_stems or not py_stems:
return {"ok": False, "reason": "missing_stem", "mismatches": ["missing_stem"], "metrics": {}}
cpp_audio_path = resolve_repo_path(str(cpp_stems[0].get("audio", "")))
py_audio_path = resolve_repo_path(str(py_stems[0].get("audio", "")))
if cpp_audio_path is None or py_audio_path is None or not cpp_audio_path.exists() or not py_audio_path.exists():
return {"ok": False, "reason": "missing_audio_path", "mismatches": ["audio_path"], "metrics": {}}
cpp_audio, cpp_sr = sf.read(str(cpp_audio_path), always_2d=True)
py_audio, py_sr = sf.read(str(py_audio_path), always_2d=True)
cpp_audio = np.asarray(cpp_audio, dtype=np.float32)
py_audio = np.asarray(py_audio, dtype=np.float32)
if cpp_sr != py_sr:
mismatches.append("sample_rate")
cpp_flat = cpp_audio.reshape(-1).astype(np.float64, copy=False)
py_flat = py_audio.reshape(-1).astype(np.float64, copy=False)
common = min(cpp_flat.size, py_flat.size)
if common <= 0:
mismatches.append("empty_audio")
common = 0
length_ratio = 0.0 if max(cpp_flat.size, py_flat.size) == 0 else common / float(max(cpp_flat.size, py_flat.size))
if length_ratio < length_ratio_min:
mismatches.append("length_ratio")
if common > 0:
cpp_common = cpp_flat[:common]
py_common = py_flat[:common]
diff = cpp_common - py_common
denom = float(np.linalg.norm(cpp_common) * np.linalg.norm(py_common))
cosine = 1.0 if denom == 0.0 else float(np.dot(cpp_common, py_common) / denom)
mae = float(np.mean(np.abs(diff), dtype=np.float64))
rmse = float(np.sqrt(np.mean(np.square(diff), dtype=np.float64)))
max_abs = float(np.max(np.abs(diff)))
else:
cosine = 0.0
mae = float("inf")
rmse = float("inf")
max_abs = float("inf")
waveform_ok = cosine >= cosine_min
if not waveform_ok:
mismatches.append("cosine")
if log_mel_cosine_min is not None and cpp_sr == py_sr and common > 0:
cpp_mono = cpp_audio.mean(axis=1)[:common].astype(np.float32, copy=False)
py_mono = py_audio.mean(axis=1)[:common].astype(np.float32, copy=False)
mel_kwargs = {"sr": int(cpp_sr), "n_fft": 1024, "hop_length": 256, "n_mels": 80, "power": 2.0}
cpp_log_mel = np.log(np.maximum(librosa.feature.melspectrogram(y=cpp_mono, **mel_kwargs), 1.0e-10))
py_log_mel = np.log(np.maximum(librosa.feature.melspectrogram(y=py_mono, **mel_kwargs), 1.0e-10))
common_cols = min(cpp_log_mel.shape[1], py_log_mel.shape[1])
if common_cols > 0:
cpp_mel_flat = cpp_log_mel[:, :common_cols].reshape(-1).astype(np.float64, copy=False)
py_mel_flat = py_log_mel[:, :common_cols].reshape(-1).astype(np.float64, copy=False)
mel_denom = float(np.linalg.norm(cpp_mel_flat) * np.linalg.norm(py_mel_flat))
log_mel_cosine = 1.0 if mel_denom == 0.0 else float(np.dot(cpp_mel_flat, py_mel_flat) / mel_denom)
else:
log_mel_cosine = 0.0
log_mel_ok = log_mel_cosine >= log_mel_cosine_min
if not log_mel_ok:
mismatches.append("log_mel_cosine")
else:
log_mel_cosine = None
log_mel_ok = None
if cpp_sr == py_sr and common > 0:
if "cpp_mono" not in locals():
cpp_mono = cpp_audio.mean(axis=1)[:common].astype(np.float32, copy=False)
py_mono = py_audio.mean(axis=1)[:common].astype(np.float32, copy=False)
spectral_convergences: list[float] = []
logmag_maes: list[float] = []
for n_fft, hop_length in ((512, 128), (1024, 256), (2048, 512)):
cpp_stft = librosa.stft(cpp_mono, n_fft=n_fft, hop_length=hop_length, center=True)
py_stft = librosa.stft(py_mono, n_fft=n_fft, hop_length=hop_length, center=True)
common_cols = min(cpp_stft.shape[1], py_stft.shape[1])
if common_cols <= 0:
continue
cpp_mag = np.abs(cpp_stft[:, :common_cols]).astype(np.float64, copy=False)
py_mag = np.abs(py_stft[:, :common_cols]).astype(np.float64, copy=False)
denom = float(np.linalg.norm(py_mag))
spectral_convergences.append(float(np.linalg.norm(cpp_mag - py_mag) / max(denom, 1.0e-12)))
cpp_logmag = np.log(np.maximum(cpp_mag, 1.0e-7))
py_logmag = np.log(np.maximum(py_mag, 1.0e-7))
logmag_maes.append(float(np.mean(np.abs(cpp_logmag - py_logmag), dtype=np.float64)))
mrstft_spectral_convergence = float(np.mean(spectral_convergences, dtype=np.float64)) if spectral_convergences else None
mrstft_logmag_mae = float(np.mean(logmag_maes, dtype=np.float64)) if logmag_maes else None
else:
mrstft_spectral_convergence = None
mrstft_logmag_mae = None
mrstft_ok = None
if mrstft_spectral_convergence_max is not None and mrstft_logmag_mae_max is not None:
mrstft_ok = (
mrstft_spectral_convergence is not None and
mrstft_logmag_mae is not None and
mrstft_spectral_convergence <= mrstft_spectral_convergence_max and
mrstft_logmag_mae <= mrstft_logmag_mae_max
)
if not mrstft_ok:
mismatches.append("mrstft")
metrics = {
"cosine": cosine,
"cosine_min": cosine_min,
"mae": mae,
"rmse": rmse,
"max_abs": max_abs,
"cpp_samples": int(cpp_flat.size),
"python_samples": int(py_flat.size),
"common_samples": int(common),
"length_ratio": length_ratio,
"length_ratio_min": length_ratio_min,
"cpp_sample_rate": int(cpp_sr),
"python_sample_rate": int(py_sr),
}
if log_mel_cosine_min is not None:
metrics["log_mel_cosine"] = log_mel_cosine
metrics["log_mel_cosine_min"] = log_mel_cosine_min
metrics["log_mel_ok"] = log_mel_ok
metrics["mrstft_spectral_convergence"] = mrstft_spectral_convergence
if mrstft_spectral_convergence_max is not None:
metrics["mrstft_spectral_convergence_max"] = mrstft_spectral_convergence_max
metrics["mrstft_logmag_mae"] = mrstft_logmag_mae
if mrstft_logmag_mae_max is not None:
metrics["mrstft_logmag_mae_max"] = mrstft_logmag_mae_max
if mrstft_ok is not None:
metrics["mrstft_ok"] = mrstft_ok
metrics["waveform_ok"] = waveform_ok
if similarity_vote_required is not None:
vote_results = [waveform_ok]
if log_mel_ok is not None:
vote_results.append(log_mel_ok)
if mrstft_ok is not None:
vote_results.append(mrstft_ok)
similarity_votes = sum(1 for item in vote_results if item)
metrics["similarity_votes"] = similarity_votes
metrics["similarity_vote_required"] = similarity_vote_required
if similarity_votes >= similarity_vote_required:
mismatches = [item for item in mismatches if item not in {"cosine", "log_mel_cosine", "mrstft"}]
elif log_mel_ok is not None:
metrics["similarity_gate"] = "waveform_or_log_mel"
if waveform_ok or log_mel_ok:
mismatches = [item for item in mismatches if item not in {"cosine", "log_mel_cosine"}]
if length_ratio_is_diagnostic:
mismatches = [item for item in mismatches if item != "length_ratio"]
return {
"ok": not mismatches,
"reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}",
"mismatches": mismatches,
"metrics": metrics,
}
def read_status_memory_kb(pid: int) -> dict[str, int]:
status_path = Path("/proc") / str(pid) / "status"
memory: dict[str, int] = {}
try:
for line in status_path.read_text(encoding="utf-8").splitlines():
if line.startswith("VmRSS:") or line.startswith("VmSize:"):
key, raw_value = line.split(":", 1)
parts = raw_value.strip().split()
if parts:
memory[key] = int(parts[0])
except OSError:
pass
return memory
def sample_process_memory_kb(pid: int) -> dict[str, int]:
memory = read_status_memory_kb(pid)
return {
"rss_kb": memory.get("VmRSS", 0),
"vms_kb": memory.get("VmSize", 0),
}
def run_command(command: list[str], log_path: Path, env: dict[str, str] | None = None) -> tuple[str, dict[str, int]]:
process = subprocess.Popen(
command,
cwd=str(REPO_ROOT),
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
peak_memory = {"peak_rss_kb": 0, "peak_vms_kb": 0}
while True:
sample = sample_process_memory_kb(process.pid)
peak_memory["peak_rss_kb"] = max(peak_memory["peak_rss_kb"], sample["rss_kb"])
peak_memory["peak_vms_kb"] = max(peak_memory["peak_vms_kb"], sample["vms_kb"])
try:
stdout, stderr = process.communicate(timeout=0.05)
break
except subprocess.TimeoutExpired:
time.sleep(0.02)
write_timestamped_lines(log_path, "STDOUT", stdout)
with log_path.open("a", encoding="utf-8") as handle:
for line in stderr.splitlines():
handle.write(f"[{log_timestamp()}] STDERR {line}\n")
if process.returncode != 0:
raise RuntimeError(
f"command failed ({process.returncode}): {' '.join(command)}\n{stdout}\n{stderr}"
)
return stdout, peak_memory
def build_tts_commands(
family: str,
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
texts: list[str],
) -> tuple[list[str], list[str]]:
timing_py = scenario_dir / "python.timing.log"
timing_cpp = scenario_dir / "cpp.timing.log"
audio_py = scenario_dir / "python_audio.wav"
audio_cpp = scenario_dir / "cpp_audio.wav"
audio_py_dir = scenario_dir / "python_audio"
audio_cpp_dir = scenario_dir / "cpp_audio"
common_text_args: list[str] = []
for text in texts:
common_text_args.extend(["--text", text])
common_warmup_args: list[str] = []
warmup_text = args.warmup_text or str(config.get("warmup_text", ""))
if warmup_text:
common_warmup_args = ["--warmup-text", warmup_text]
model_path = args.model or config["model"]
if family == "kokoro":
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--model",
config["model"],
"--voice-id",
config["voice_id"],
"--speaking-rate",
str(config.get("speaking_rate", 1.0)),
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--audio-out",
str(audio_py),
"--audio-out-dir",
str(audio_py_dir),
"--timing-file",
str(timing_py),
] + common_warmup_args + common_text_args
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
config["model"],
"--voice-id",
config["voice_id"],
"--speaking-rate",
str(config.get("speaking_rate", 1.0)),
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--max-input-tokens",
str(config.get("max_input_tokens", 512)),
"--audio-out",
str(audio_cpp),
"--audio-out-dir",
str(audio_cpp_dir),
"--timing-file",
str(timing_cpp),
] + common_warmup_args + common_text_args
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
if family in {"qwen3_tts", "qwen3_tts_voice_design", "qwen3_tts_custom_voice"}:
max_new_tokens = args.max_new_tokens if args.max_new_tokens > 0 else config["max_new_tokens"]
task_name = config.get("task", "tts")
clone_audio = args.clone_audio or config.get("clone_audio", "")
prompt_control_args: list[str] = []
if task_name in {"voice_design", "custom_voice"}:
default_instruct = config.get("voice_design_instruct", config.get("custom_voice_instruct", ""))
warmup_instruct = args.warmup_voice_design_instruct or default_instruct
prompt_control_args.extend(["--instruct", default_instruct])
prompt_control_args.extend(["--warmup-instruct", warmup_instruct])
if args.voice_design_instructs:
if len(args.voice_design_instructs) < len(texts):
raise RuntimeError(
f"need {len(texts)} --voice-design-instruct values for measured requests, "
f"only received {len(args.voice_design_instructs)}"
)
for instruct in args.voice_design_instructs[: len(texts)]:
prompt_control_args.extend(["--request-instruct", instruct])
if task_name == "custom_voice":
speaker = args.speaker or config["speaker"]
prompt_control_args.extend(["--speaker", speaker])
prompt_control_args.extend(["--warmup-speaker", args.warmup_speaker or speaker])
if args.request_speakers:
if len(args.request_speakers) < len(texts):
raise RuntimeError(
f"need {len(texts)} --request-speaker values for measured requests, "
f"only received {len(args.request_speakers)}"
)
for speaker_name in args.request_speakers[: len(texts)]:
prompt_control_args.extend(["--request-speaker", speaker_name])
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--model",
model_path,
"--task",
task_name,
"--language",
config.get("language", "Auto"),
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--max-new-tokens",
str(max_new_tokens),
"--seed",
str(args.seed),
"--do-sample",
args.do_sample,
"--subtalker-do-sample",
args.subtalker_do_sample,
"--top-k",
str(args.top_k),
"--top-p",
str(args.top_p),
"--temperature",
str(args.temperature),
"--subtalker-top-k",
str(args.subtalker_top_k),
"--subtalker-top-p",
str(args.subtalker_top_p),
"--subtalker-temperature",
str(args.subtalker_temperature),
"--audio-out",
str(audio_py),
"--audio-out-dir",
str(audio_py_dir),
"--timing-file",
str(timing_py),
] + common_warmup_args + common_text_args
if task_name in {"voice_design", "custom_voice"}:
python_command.extend(prompt_control_args)
else:
python_command.extend(["--clone-audio", clone_audio, "--reference-text", config["reference_text"]])
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--task",
task_name,
"--language",
config.get("language", "Auto"),
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--max-new-tokens",
str(max_new_tokens),
"--seed",
str(args.seed),
"--do-sample",
args.do_sample,
"--subtalker-do-sample",
args.subtalker_do_sample,
"--top-k",
str(args.top_k),
"--top-p",
str(args.top_p),
"--temperature",
str(args.temperature),
"--subtalker-top-k",
str(args.subtalker_top_k),
"--subtalker-top-p",
str(args.subtalker_top_p),
"--subtalker-temperature",
str(args.subtalker_temperature),
"--audio-out",
str(audio_cpp),
"--audio-out-dir",
str(audio_cpp_dir),
"--timing-file",
str(timing_cpp),
] + common_warmup_args + common_text_args
if task_name in {"voice_design", "custom_voice"}:
cpp_command.extend(prompt_control_args)
else:
cpp_command.extend(["--clone-audio", clone_audio, "--reference-text", config["reference_text"]])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
if family == "chatterbox":
max_new_tokens = args.max_new_tokens if args.max_new_tokens > 0 else config["max_new_tokens"]
clone_audio = args.clone_audio or config["clone_audio"]
warmup = effective_warmup(config, args)
chatterbox_args = [
"--seed",
str(args.seed),
"--language",
args.chatterbox_language,
"--exaggeration",
"0.5",
"--cfg-weight",
"0.5",
"--s3gen-cfg-rate",
str(args.chatterbox_s3gen_cfg_rate),
"--temperature",
"0.8",
"--repetition-penalty",
"1.2",
"--min-p",
"0.05",
"--top-p",
"1.0",
]
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(warmup),
"--iterations",
str(args.iterations),
"--clone-audio",
clone_audio,
"--audio-out",
str(audio_py),
"--audio-out-dir",
str(audio_py_dir),
"--timing-file",
str(timing_py),
] + chatterbox_args + common_warmup_args + common_text_args
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(warmup),
"--iterations",
str(args.iterations),
"--max-new-tokens",
str(max_new_tokens),
"--clone-audio",
clone_audio,
"--do-sample",
"true",
"--stop-on-eos",
"true",
"--audio-out",
str(audio_cpp),
"--audio-out-dir",
str(audio_cpp_dir),
"--timing-file",
str(timing_cpp),
] + chatterbox_args + common_warmup_args + common_text_args
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
if family.startswith("omnivoice_"):
task_name = str(config.get("task", "voice_clone"))
clone_audio = args.clone_audio or config.get("clone_audio", "")
omnivoice_warmup_args = common_warmup_args if common_warmup_args else ["--warmup-text", OMNIVOICE_DEFAULT_WARMUP_TEXT]
prompt_control_args: list[str] = ["--task", task_name]
if task_name == "voice_clone":
prompt_control_args.extend(["--clone-audio", clone_audio, "--reference-text", config["reference_text"]])
elif task_name == "voice_design":
warmup_instruct, request_instructs = omnivoice_instruct_values(config, args, len(texts))
default_instruct = str(config.get("voice_design_instruct", ""))
prompt_control_args.extend(["--instruct", default_instruct, "--warmup-instruct", warmup_instruct])
for instruct in request_instructs:
prompt_control_args.extend(["--request-instruct", instruct])
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--model",
model_path,
"--language",
config.get("language", "en"),
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--num-step",
"32",
"--guidance-scale",
"2.0",
"--speed",
"1.0",
"--t-shift",
"0.1",
"--denoise",
"true",
"--postprocess-output",
"true",
"--layer-penalty-factor",
"5.0",
"--position-temperature",
"0.0",
"--class-temperature",
"0.0",
"--audio-chunk-duration",
"15.0",
"--audio-chunk-threshold",
"30.0",
"--audio-out",
str(audio_py),
"--audio-out-dir",
str(audio_py_dir),
"--timing-file",
str(timing_py),
] + omnivoice_warmup_args + common_text_args + prompt_control_args
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--language",
config.get("language", "en"),
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--num-step",
"32",
"--guidance-scale",
"2.0",
"--speed",
"1.0",
"--t-shift",
"0.1",
"--denoise",
"true",
"--postprocess-output",
"true",
"--layer-penalty-factor",
"5.0",
"--position-temperature",
"0.0",
"--class-temperature",
"0.0",
"--audio-chunk-duration",
"15.0",
"--audio-chunk-threshold",
"30.0",
"--audio-out",
str(audio_cpp),
"--audio-out-dir",
str(audio_cpp_dir),
"--timing-file",
str(timing_cpp),
] + omnivoice_warmup_args + common_text_args + prompt_control_args
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
if family == "moss_tts_local":
clone_audio = args.clone_audio or config["clone_audio"]
max_new_frames = int(config.get("max_new_frames", 256))
do_sample = str(config.get("do_sample", "true"))
audio_temperature = float(config.get("audio_temperature", 1.0))
audio_top_p = float(config.get("audio_top_p", 0.95))
audio_top_k = int(config.get("audio_top_k", 50))
audio_repetition_penalty = float(config.get("audio_repetition_penalty", 1.0))
dtype = str(config.get("dtype", "bf16"))
use_kv_cache = str(config.get("use_kv_cache", "true"))
request_file_args: list[str] = []
request_file = config.get("moss_tts_local_request_file")
if request_file:
request_file_args = ["--request-file", str(request_file)]
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--model",
model_path,
"--clone-audio",
clone_audio,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--max-new-frames",
str(max_new_frames),
"--seed",
str(args.seed),
"--do-sample",
do_sample,
"--audio-temperature",
str(audio_temperature),
"--audio-top-p",
str(audio_top_p),
"--audio-top-k",
str(audio_top_k),
"--audio-repetition-penalty",
str(audio_repetition_penalty),
"--dtype",
dtype,
"--use-kv-cache",
use_kv_cache,
"--audio-out",
str(audio_py),
"--audio-out-dir",
str(audio_py_dir),
"--timing-file",
str(timing_py),
] + common_warmup_args + request_file_args + ([] if request_file_args else common_text_args)
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--clone-audio",
clone_audio,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--max-new-frames",
str(max_new_frames),
"--seed",
str(args.seed),
"--do-sample",
do_sample,
"--audio-temperature",
str(audio_temperature),
"--audio-top-p",
str(audio_top_p),
"--audio-top-k",
str(audio_top_k),
"--audio-repetition-penalty",
str(audio_repetition_penalty),
"--audio-out",
str(audio_cpp),
"--audio-out-dir",
str(audio_cpp_dir),
"--timing-file",
str(timing_cpp),
"--log-file",
str(scenario_dir / "cpp.framework.log"),
] + common_warmup_args + request_file_args + ([] if request_file_args else common_text_args)
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
if family == "moss_tts_nano":
audio_tokenizer_model = Path(model_path) / "audio_tokenizer"
if not audio_tokenizer_model.exists():
audio_tokenizer_model = resolve_repo_path(str(config["audio_tokenizer_model"])) or Path(config["audio_tokenizer_model"])
clone_audio = args.clone_audio or config["clone_audio"]
max_new_frames = int(config.get("max_new_frames", 300))
active_codebooks = int(config.get("active_codebooks", 16))
do_sample = str(config.get("do_sample", "true"))
text_temperature = float(config.get("text_temperature", 1.5))
text_top_p = float(config.get("text_top_p", 1.0))
text_top_k = int(config.get("text_top_k", 50))
audio_temperature = float(config.get("temperature", 1.7))
audio_top_p = float(config.get("top_p", 0.8))
audio_top_k = int(config.get("top_k", 25))
audio_repetition_penalty = float(config.get("repetition_penalty", 1.0))
use_kv_cache = str(config.get("use_kv_cache", "true"))
voice_clone_max_text_tokens = int(config.get("voice_clone_max_text_tokens", 50))
voice_clone_max_memory_per_sample_gb = float(config.get("voice_clone_max_memory_per_sample_gb", 1.0))
tts_max_batch_size = int(config.get("tts_max_batch_size", 0))
codec_max_batch_size = int(config.get("codec_max_batch_size", 0))
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--model",
model_path,
"--audio-tokenizer-model",
str(audio_tokenizer_model),
"--clone-audio",
clone_audio,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--max-new-frames",
str(max_new_frames),
"--active-codebooks",
str(active_codebooks),
"--do-sample",
do_sample,
"--text-temperature",
str(text_temperature),
"--text-top-p",
str(text_top_p),
"--text-top-k",
str(text_top_k),
"--audio-temperature",
str(audio_temperature),
"--audio-top-p",
str(audio_top_p),
"--audio-top-k",
str(audio_top_k),
"--audio-repetition-penalty",
str(audio_repetition_penalty),
"--seed",
str(args.seed),
"--use-kv-cache",
use_kv_cache,
"--voice-clone-max-text-tokens",
str(voice_clone_max_text_tokens),
"--voice-clone-max-memory-per-sample-gb",
str(voice_clone_max_memory_per_sample_gb),
"--tts-max-batch-size",
str(tts_max_batch_size),
"--codec-max-batch-size",
str(codec_max_batch_size),
"--audio-out",
str(audio_py),
"--audio-out-dir",
str(audio_py_dir),
"--timing-file",
str(timing_py),
] + common_warmup_args + common_text_args
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--clone-audio",
clone_audio,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--audio-out",
str(audio_cpp),
"--audio-out-dir",
str(audio_cpp_dir),
"--timing-file",
str(timing_cpp),
] + common_warmup_args + common_text_args
cpp_request_options = {
"max_tokens": str(max_new_frames),
"active_codebooks": str(active_codebooks),
"do_sample": do_sample,
"use_kv_cache": use_kv_cache,
"text_temperature": str(text_temperature),
"text_top_p": str(text_top_p),
"text_top_k": str(text_top_k),
"temperature": str(audio_temperature),
"top_p": str(audio_top_p),
"top_k": str(audio_top_k),
"repetition_penalty": str(audio_repetition_penalty),
"seed": str(args.seed),
}
for key, value in cpp_request_options.items():
cpp_command.extend(["--request-option", f"{key}={value}"])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
noise_file = scenario_dir / "shared_noise.bin"
voice_args = ["--clone-audio", args.clone_audio] if args.clone_audio else ["--voice-id", config["voice_id"]]
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--mode",
"parity",
"--model",
config["model"],
"--language",
config["language"],
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--seed",
str(args.seed),
"--noise-file",
str(noise_file),
"--audio-out",
str(audio_py),
"--audio-out-dir",
str(audio_py_dir),
"--timing-file",
str(timing_py),
] + voice_args + common_warmup_args + common_text_args
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--mode",
"parity",
"--model",
config["model"],
"--language",
config["language"],
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--seed",
str(args.seed),
"--noise-file",
str(noise_file),
"--audio-out",
str(audio_cpp),
"--audio-out-dir",
str(audio_cpp_dir),
"--timing-file",
str(timing_cpp),
] + voice_args + common_warmup_args + common_text_args
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_ace_step_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_env = str(config.get("python_conda_env", "qwen3-tts"))
noise_file = args.test_noise_file or ""
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--checkpoint-dir",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
# ACE-Step planner parity is sensitive to LM precision on CUDA, so the
# warmbench default pins the Python LM to fp32.
"--lm-dtype",
"float32",
"--noise-file",
noise_file,
"--request-sequence-json",
request_sequence_json,
]
for option in args.cpp_session_option:
if option.startswith("ace_step.dit_model_path="):
python_command.extend(["--config-path", option.split("=", 1)[1]])
elif option.startswith("ace_step.lm_model_path="):
python_command.extend(["--lm-model-path", option.split("=", 1)[1]])
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
# Keep the full ACE-Step GGML stack on fp32 by default for parity
# runs, including the VAE. The C++ VAE decoder now chunks long
# latent sequences so warmbench does not need a native-weight escape
# hatch that would hide real fp32 issues.
"--session-option",
"ace_step.weight_type=f32",
"--noise-file",
noise_file,
"--request-sequence-json",
request_sequence_json,
]
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
has_tensor_debug = any(option.startswith("ace_step.dit_tensor_") for option in args.cpp_session_option)
has_tensor_compare_result = any(
option.startswith("ace_step.dit_tensor_compare_result=")
for option in args.cpp_session_option
)
if has_tensor_debug and not has_tensor_compare_result:
cpp_command.extend([
"--session-option",
f"ace_step.dit_tensor_compare_result={scenario_dir / 'ace_step_dit_compare.json'}",
])
return python_command, cpp_command
def build_vevo2_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
cpp_model_path = args.model or config["model"]
python_model_path = args.model or config.get("python_model", config["model"])
python_env = str(config.get("python_conda_env", "qwen3-tts"))
noise_file = args.test_noise_file or ""
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
python_model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
"--noise-file",
noise_file,
"--request-sequence-json",
request_sequence_json,
]
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
cpp_model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--noise-file",
noise_file,
"--request-sequence-json",
request_sequence_json,
]
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
# Per-family default transcript language for the shared alignment path
# (qwen3_forced_aligner + mms_forced_aligner).
FORCED_ALIGNER_LANGUAGES = {"qwen3_forced_aligner": "English", "mms_forced_aligner": "eng"}
def build_audio_commands(
family: str,
config: dict[str, Any],
backend: str,
mode: str,
args: argparse.Namespace,
scenario_dir: Path,
warmup_audio: Path,
requests: list[Path],
) -> tuple[list[str], list[str]]:
audio_sequence = ",".join(str(path) for path in requests)
first_audio = str(requests[0])
if family == "qwen3_asr":
request_languages = getattr(args, "qwen3_asr_request_languages", [])
request_contexts = getattr(args, "qwen3_asr_request_contexts", [])
warmup_language = getattr(args, "qwen3_asr_warmup_language", "English")
warmup_context = getattr(args, "qwen3_asr_warmup_context", "")
max_new_tokens = args.max_new_tokens if args.max_new_tokens > 0 else config.get("max_new_tokens", 512)
common = [
"--model",
args.model or config["model"],
"--audio",
first_audio,
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
audio_sequence,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--max-new-tokens",
str(max_new_tokens),
"--language",
"English",
"--warmup-language",
warmup_language,
"--warmup-context",
warmup_context,
"--language-sequence",
",".join(request_languages),
"--context-sequence",
",".join(request_contexts),
]
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--timing-file",
str(scenario_dir / "python.timing.log"),
] + common
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
] + common
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
if family in ("qwen3_forced_aligner", "mms_forced_aligner"):
default_language = FORCED_ALIGNER_LANGUAGES[family]
request_languages = getattr(args, f"{family}_request_languages", [])
request_transcripts = getattr(args, f"{family}_request_transcripts", [])
warmup_language = getattr(args, f"{family}_warmup_language", default_language)
warmup_transcript = getattr(args, f"{family}_warmup_transcript", "")
common = [
"--model",
args.model or config["model"],
"--audio",
first_audio,
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
audio_sequence,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--language",
default_language,
"--warmup-language",
warmup_language,
"--warmup-transcript",
warmup_transcript,
]
request_args: list[str] = []
for language in request_languages:
request_args.extend(["--request-language", language])
for transcript in request_transcripts:
request_args.extend(["--request-transcript", transcript])
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--timing-file",
str(scenario_dir / "python.timing.log"),
] + common + request_args
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
] + common + request_args
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
if family == "parakeet":
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--model",
config["model"],
"--audio",
first_audio,
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
audio_sequence,
"--backend",
backend,
"--run-mode",
mode,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
]
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
config["model"],
"--audio",
first_audio,
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
audio_sequence,
"--backend",
backend,
"--run-mode",
mode,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
]
if mode == "longform":
cpp_command.extend(["--encoder-variant", "long_context"])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
if config["kind"] in {"asr", "vad", "spk"}:
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--model",
config["model"],
"--audio",
first_audio,
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
audio_sequence,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
]
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
config["model"],
"--audio",
first_audio,
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
audio_sequence,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
]
return python_command, cpp_command
if config["kind"] == "separation":
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--model",
config.get("python_model", args.model or config["model"]),
"--family",
family,
"--audio",
first_audio,
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
audio_sequence,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
]
python_config = str(config.get("python_config", ""))
if python_config:
python_command.extend(["--config-path", python_config])
python_repo = str(config.get("python_repo", ""))
if python_repo:
python_command.extend(["--repo", python_repo])
python_name = str(config.get("python_name", ""))
if python_name:
python_command.extend(["--model-name", python_name])
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
args.model or config["model"],
"--family",
family,
"--audio",
first_audio,
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
audio_sequence,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
]
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--model",
config["python_model"],
"--audio",
first_audio,
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
audio_sequence,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
]
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--model",
config["model"],
"--audio",
first_audio,
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
audio_sequence,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
]
return python_command, cpp_command
def catalog_case_audio(case: dict[str, Any], scenario_dir: Path, role: str, index: int) -> Path:
source = REPO_ROOT / str(case["audio"])
if "start_sec" not in case and "end_sec" not in case:
return source
return materialize_qwen3_asr_audio(case, scenario_dir / "catalog_audio", index, role)
def csv_values(cases: list[dict[str, Any]], key: str, default: Any = "") -> str:
return ",".join(str(case.get(key, default)) for case in cases)
def json_string_values(cases: list[dict[str, Any]], key: str, default: str = "") -> str:
return json.dumps([str(case.get(key, default)) for case in cases], separators=(",", ":"))
def csv_bools(cases: list[dict[str, Any]], key: str, default: bool) -> str:
return ",".join("true" if bool(case.get(key, default)) else "false" for case in cases)
def catalog_asr_model_path(config: dict[str, Any], args: argparse.Namespace) -> str:
return args.model or str(config["model"])
def rounded_json_events(text: str) -> list[dict[str, Any]]:
events = json.loads(text)
if not isinstance(events, list):
raise RuntimeError("MuScriptor text_output is not a JSON event list")
rounded = []
for event in events:
if not isinstance(event, dict):
raise RuntimeError("MuScriptor event is not an object")
rounded.append({
key: (round(value, 6) if isinstance(value, float) else value)
for key, value in event.items()
})
return rounded
MUSCRIPTOR_TIME_TOLERANCE_SEC = 0.05
def compare_muscriptor_step(cpp_step: dict[str, Any], py_step: dict[str, Any]) -> dict[str, Any]:
mismatches: list[str] = []
metrics: dict[str, Any] = {}
try:
cpp_events = rounded_json_events(str(cpp_step.get("text_output", "")))
py_events = rounded_json_events(str(py_step.get("text_output", "")))
except (json.JSONDecodeError, RuntimeError) as exc:
return {
"ok": False,
"reason": "mismatch:invalid_json",
"mismatches": ["invalid_json"],
"metrics": {"error": str(exc)},
}
metrics["cpp_events"] = len(cpp_events)
metrics["python_events"] = len(py_events)
max_time_abs_diff = 0.0
first_mismatch: tuple[int, dict[str, Any], dict[str, Any]] | None = None
if len(cpp_events) == len(py_events):
for index, (cpp_event, py_event) in enumerate(zip(cpp_events, py_events)):
if cpp_event == py_event:
continue
same_event = True
for key in set(cpp_event) | set(py_event):
if key in {"start_time", "end_time"}:
continue
if cpp_event.get(key) != py_event.get(key):
same_event = False
break
for key in {"start_time", "end_time"}:
if key not in cpp_event and key not in py_event:
continue
if key not in cpp_event or key not in py_event:
same_event = False
break
diff = abs(float(cpp_event[key]) - float(py_event[key]))
max_time_abs_diff = max(max_time_abs_diff, diff)
if diff > MUSCRIPTOR_TIME_TOLERANCE_SEC + 1e-7:
same_event = False
break
if not same_event:
first_mismatch = (index, cpp_event, py_event)
break
if max_time_abs_diff > 0.0:
metrics["max_time_abs_diff"] = max_time_abs_diff
if len(cpp_events) != len(py_events) or first_mismatch is not None:
mismatches.append("events")
if first_mismatch is not None:
metrics["first_mismatch_index"] = first_mismatch[0]
metrics["cpp_first_mismatch"] = first_mismatch[1]
metrics["python_first_mismatch"] = first_mismatch[2]
if len(cpp_events) != len(py_events):
metrics["event_count_mismatch"] = True
return {
"ok": not mismatches,
"reason": "ok" if not mismatches else f"mismatch:{mismatches[0]}",
"mismatches": mismatches,
"metrics": metrics,
}
def build_catalog_asr_commands(
family: str,
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
warmup_case: dict[str, Any],
request_cases: list[dict[str, Any]],
) -> tuple[list[str], list[str], dict[str, Any]]:
warmup_audio = catalog_case_audio(warmup_case, scenario_dir, "warmup", 0)
audio_requests = [
catalog_case_audio(case, scenario_dir, "request", index)
for index, case in enumerate(request_cases)
]
model_path = catalog_asr_model_path(config, args)
python_model_path = args.python_model or str(config.get("python_model", model_path))
common = [
"--model",
model_path,
"--audio",
str(audio_requests[0]),
"--warmup-audio",
str(warmup_audio),
"--audio-sequence",
",".join(str(path) for path in audio_requests),
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
]
python_extra: list[str] = []
model_parent = Path(model_path).parent
if family == "higgs_audio_stt":
common.extend([
"--prompt",
str(warmup_case.get("prompt", "")),
"--prompt-sequence",
csv_values(request_cases, "prompt", warmup_case.get("prompt", "")),
"--max-tokens",
str(warmup_case.get("max_tokens", 1024)),
"--max-tokens-sequence",
csv_values(request_cases, "max_tokens", warmup_case.get("max_tokens", 1024)),
"--enable-thinking",
"true" if bool(warmup_case.get("enable_thinking", True)) else "false",
"--enable-thinking-sequence",
csv_bools(request_cases, "enable_thinking", bool(warmup_case.get("enable_thinking", True))),
])
python_extra.extend(["--whisper-processor", str(model_parent / "whisper-large-v3")])
elif family == "hviske_asr":
common.extend([
"--language",
str(request_cases[0].get("language", warmup_case.get("language", "da"))),
"--warmup-language",
str(warmup_case.get("language", request_cases[0].get("language", "da"))),
"--language-sequence",
csv_values(request_cases, "language", "da"),
"--punctuation",
"true" if bool(warmup_case.get("punctuation", True)) else "false",
"--max-tokens",
str(warmup_case.get("max_tokens", 256)),
"--max-tokens-sequence",
csv_values(request_cases, "max_tokens", warmup_case.get("max_tokens", 256)),
"--num-beams",
str(warmup_case.get("num_beams", 1)),
"--num-beams-sequence",
csv_values(request_cases, "num_beams", warmup_case.get("num_beams", 1)),
"--length-penalty",
str(warmup_case.get("length_penalty", 1.0)),
"--length-penalty-sequence",
csv_values(request_cases, "length_penalty", warmup_case.get("length_penalty", 1.0)),
"--do-sample",
"true" if bool(warmup_case.get("do_sample", False)) else "false",
"--do-sample-sequence",
csv_bools(request_cases, "do_sample", bool(warmup_case.get("do_sample", False))),
"--temperature",
str(warmup_case.get("temperature", 1.0)),
"--temperature-sequence",
csv_values(request_cases, "temperature", warmup_case.get("temperature", 1.0)),
"--top-k",
str(warmup_case.get("top_k", 50)),
"--top-k-sequence",
csv_values(request_cases, "top_k", warmup_case.get("top_k", 50)),
"--top-p",
str(warmup_case.get("top_p", 1.0)),
"--top-p-sequence",
csv_values(request_cases, "top_p", warmup_case.get("top_p", 1.0)),
"--seed",
str(warmup_case.get("seed", args.seed)),
"--seed-sequence",
csv_values(request_cases, "seed", args.seed),
])
elif family == "nemotron_asr":
common.extend([
"--language",
str(request_cases[0].get("language", warmup_case.get("language", "en-US"))),
"--warmup-language",
str(warmup_case.get("language", request_cases[0].get("language", "en-US"))),
"--language-sequence",
csv_values(request_cases, "language", "en-US"),
"--lookahead-tokens",
str(warmup_case.get("lookahead_tokens", 3)),
"--lookahead-tokens-sequence",
csv_values(request_cases, "lookahead_tokens", warmup_case.get("lookahead_tokens", 3)),
"--max-tokens",
str(warmup_case.get("max_tokens", 256)),
"--max-tokens-sequence",
csv_values(request_cases, "max_tokens", warmup_case.get("max_tokens", 256)),
"--streaming",
"true" if bool(warmup_case.get("streaming", False)) else "false",
"--streaming-sequence",
csv_bools(request_cases, "streaming", bool(warmup_case.get("streaming", False))),
"--keep-language-tags",
"true" if bool(warmup_case.get("keep_language_tags", False)) else "false",
"--keep-language-tags-sequence",
csv_bools(request_cases, "keep_language_tags", bool(warmup_case.get("keep_language_tags", False))),
])
elif family == "muscriptor":
common.extend([
"--instruments",
str(warmup_case.get("instruments", "")),
"--instruments-sequence",
json_string_values(request_cases, "instruments", str(warmup_case.get("instruments", ""))),
"--use-sampling",
"true" if bool(warmup_case.get("use_sampling", False)) else "false",
"--use-sampling-sequence",
csv_bools(request_cases, "use_sampling", bool(warmup_case.get("use_sampling", False))),
"--temperature",
str(warmup_case.get("temperature", 1.0)),
"--temperature-sequence",
csv_values(request_cases, "temperature", warmup_case.get("temperature", 1.0)),
"--cfg-coef",
str(warmup_case.get("cfg_coef", 1.0)),
"--cfg-coef-sequence",
csv_values(request_cases, "cfg_coef", warmup_case.get("cfg_coef", 1.0)),
"--batch-size",
str(warmup_case.get("batch_size", 0)),
"--batch-size-sequence",
csv_values(request_cases, "batch_size", warmup_case.get("batch_size", 0)),
"--beam-size",
str(warmup_case.get("beam_size", 1)),
"--beam-size-sequence",
csv_values(request_cases, "beam_size", warmup_case.get("beam_size", 1)),
"--prelude-forcing",
"true" if bool(warmup_case.get("prelude_forcing", True)) else "false",
"--prelude-forcing-sequence",
csv_bools(request_cases, "prelude_forcing", bool(warmup_case.get("prelude_forcing", True))),
"--seed",
str(warmup_case.get("seed", args.seed)),
"--seed-sequence",
csv_values(request_cases, "seed", args.seed),
])
elif family == "voxtral_realtime":
common.extend([
"--streaming",
"true" if bool(warmup_case.get("streaming", False)) else "false",
"--do-sample",
"true" if bool(warmup_case.get("do_sample", False)) else "false",
"--do-sample-sequence",
csv_bools(request_cases, "do_sample", bool(warmup_case.get("do_sample", False))),
"--temperature",
str(warmup_case.get("temperature", 1.0)),
"--temperature-sequence",
csv_values(request_cases, "temperature", warmup_case.get("temperature", 1.0)),
"--top-p",
str(warmup_case.get("top_p", 1.0)),
"--top-p-sequence",
csv_values(request_cases, "top_p", warmup_case.get("top_p", 1.0)),
"--top-k",
str(warmup_case.get("top_k", 50)),
"--top-k-sequence",
csv_values(request_cases, "top_k", warmup_case.get("top_k", 50)),
"--seed",
str(warmup_case.get("seed", args.seed)),
"--seed-sequence",
csv_values(request_cases, "seed", args.seed),
])
elif family == "vibevoice_asr":
common.extend([
"--context",
str(warmup_case.get("context", "")),
"--context-sequence",
csv_values(request_cases, "context", warmup_case.get("context", "")),
"--max-tokens",
str(warmup_case.get("max_tokens", 32768)),
"--max-tokens-sequence",
csv_values(request_cases, "max_tokens", warmup_case.get("max_tokens", 32768)),
"--temperature",
str(warmup_case.get("temperature", 0.0)),
"--temperature-sequence",
csv_values(request_cases, "temperature", warmup_case.get("temperature", 0.0)),
"--top-p",
str(warmup_case.get("top_p", 1.0)),
"--top-p-sequence",
csv_values(request_cases, "top_p", warmup_case.get("top_p", 1.0)),
"--top-k",
str(warmup_case.get("top_k", 50)),
"--top-k-sequence",
csv_values(request_cases, "top_k", warmup_case.get("top_k", 50)),
"--num-beams",
str(warmup_case.get("num_beams", 1)),
"--num-beams-sequence",
csv_values(request_cases, "num_beams", warmup_case.get("num_beams", 1)),
"--seed",
str(warmup_case.get("seed", args.seed)),
"--seed-sequence",
csv_values(request_cases, "seed", args.seed),
])
python_extra.extend(["--tokenizer", model_path])
else:
raise RuntimeError(f"catalog ASR warmbench is not wired for family {family}")
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
"--timing-file",
str(scenario_dir / "python.timing.log"),
] + python_extra + ["--model", python_model_path] + common[2:]
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
] + common
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
request_manifest = {
"warmup_audio": str(warmup_audio),
"audio_sequence": [str(path) for path in audio_requests],
"warmup": warmup_case,
"requests": request_cases,
}
return python_command, cpp_command, request_manifest
def build_miocodec_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
warmup_request: dict[str, Any],
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
request_sequence_json = json.dumps(requests, ensure_ascii=False)
warmup_request_json = json.dumps(warmup_request, ensure_ascii=False)
common = [
"--model",
args.model or config["model"],
"--request-sequence-json",
request_sequence_json,
"--warmup-request-json",
warmup_request_json,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
]
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
] + common + [
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
]
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
] + common + [
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
]
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_voxcpm2_commands(
config: dict[str, Any],
mode: str,
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
warmup_request: dict[str, Any],
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
request_sequence_json = json.dumps(requests, ensure_ascii=False)
warmup_request_json = json.dumps(warmup_request, ensure_ascii=False)
common = [
"--model",
args.model or config["model"],
"--request-sequence-json",
request_sequence_json,
"--warmup-request-json",
warmup_request_json,
"--case-name",
args.case_names[0] if args.case_names else str(config.get("default_case_name", "default")),
"--run-mode",
mode,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
]
python_command = [
PYTHON_EXE,
str(REPO_ROOT / config["python_script"]),
] + common + [
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
]
if args.test_noise_file:
python_command.extend(["--noise-file", args.test_noise_file])
cpp_command = [
str(REPO_ROOT / config["cpp_bin"]),
] + common + [
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
]
if args.test_noise_file:
cpp_command.extend(["--noise-file", args.test_noise_file])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_vibevoice_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_env = str(config.get("python_conda_env", "qwen3-tts"))
model_root = Path(model_path)
if not model_root.is_absolute():
model_root = REPO_ROOT / model_root
model_config = json.loads((model_root / "config.json").read_text(encoding="utf-8"))
latent_size = int(model_config["acoustic_vae_dim"])
decoder_capacity = int(model_config["decoder_config"]["max_position_embeddings"])
total_speakers = sum(max(1, len(request.get("voice_samples", []))) for request in requests)
prompt_noise_count = total_speakers + total_speakers * decoder_capacity * latent_size
prompt_noise_path = scenario_dir / "shared_prompt_noise.f32"
prompt_rng = random.Random(args.seed + 17)
prompt_values = array.array("f", (prompt_rng.gauss(0.0, 1.0) for _ in range(prompt_noise_count)))
prompt_noise_path.parent.mkdir(parents=True, exist_ok=True)
with prompt_noise_path.open("wb") as handle:
prompt_values.tofile(handle)
prompt_noise_file = str(prompt_noise_path)
noise_file = args.test_noise_file
if not noise_file:
max_new_tokens = max([int(request.get("max_new_tokens", 0)) for request in requests] + [0])
if max_new_tokens <= 0:
max_new_tokens = decoder_capacity
noise_count = max_new_tokens * 2 * len(requests) * latent_size
noise_path = scenario_dir / "shared_diffusion_noise.f32"
rng = random.Random(args.seed)
values = array.array("f", (rng.gauss(0.0, 1.0) for _ in range(noise_count)))
noise_path.parent.mkdir(parents=True, exist_ok=True)
with noise_path.open("wb") as handle:
values.tofile(handle)
noise_file = str(noise_path)
warmup_count = 0
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(warmup_count),
"--iterations",
str(args.iterations),
"--output-dir",
str(scenario_dir / "python_audio"),
"--prompt-noise-file",
prompt_noise_file,
"--noise-file",
noise_file,
"--request-sequence-json",
request_sequence_json,
]
if any(bool(request.get("batch")) for request in requests):
python_command.append("--batch")
cpp_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(warmup_count),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--log-file",
str(scenario_dir / "cpp.trace.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--prompt-noise-file",
prompt_noise_file,
"--noise-file",
noise_file,
"--request-sequence-json",
request_sequence_json,
]
if any(bool(request.get("batch")) for request in requests):
cpp_command.append("--batch")
return python_command, cpp_command
def build_irodori_tts_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
if backend != "cuda":
raise RuntimeError("Irodori-TTS warmbench is CUDA-only")
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_model_path = args.python_model or config.get("python_model", model_path)
python_env = str(config.get("python_conda_env", "qwen3-tts"))
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
python_model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
"--request-sequence-json",
request_sequence_json,
"--model-precision",
"fp32",
"--codec-precision",
"fp32",
]
cpp_command = [
"env",
"NVIDIA_TF32_OVERRIDE=0",
"GGML_CUDA_FORCE_CUBLAS_COMPUTE_32F=1",
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--request-sequence-json",
request_sequence_json,
]
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_supertonic_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_model_path = args.python_model or config.get("python_model", model_path)
python_env = str(config.get("python_conda_env", "qwen3-tts"))
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
python_model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
"--request-sequence-json",
request_sequence_json,
]
cpp_command = [
"env",
"NVIDIA_TF32_OVERRIDE=0",
"GGML_CUDA_FORCE_CUBLAS_COMPUTE_32F=1",
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--request-sequence-json",
request_sequence_json,
]
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_index_tts2_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_model_path = args.python_model or config.get("python_model", model_path)
python_env = str(config.get("python_conda_env", "qwen3-tts"))
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
python_model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--audio-out-dir",
str(scenario_dir / "python_audio"),
"--summary-file",
str(scenario_dir / "python.summary.json"),
"--request-sequence-json",
request_sequence_json,
]
cpp_command = [
"env",
"NVIDIA_TF32_OVERRIDE=0",
"GGML_CUDA_FORCE_CUBLAS_COMPUTE_32F=1",
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(args.warmup),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--request-sequence-json",
request_sequence_json,
]
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_heartmula_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
if backend != "cuda":
raise RuntimeError("HeartMuLa warmbench is CUDA-only")
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_env = str(config.get("python_conda_env", "qwen3-tts"))
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
"--request-sequence-json",
request_sequence_json,
"--mula-dtype",
"float32",
"--codec-dtype",
"float32",
"--lazy-load",
]
cpp_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--request-sequence-json",
request_sequence_json,
]
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_controlfoley_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
if backend != "cuda":
raise RuntimeError("ControlFoley warmbench is CUDA-only")
cpp_requests: list[dict[str, Any]] = []
for request in requests:
cpp_request = dict(request)
cpp_video = cpp_request.pop("cpp_video", None)
if cpp_video:
cpp_request["video"] = cpp_video
cpp_requests.append(cpp_request)
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
cpp_request_sequence_json = json.dumps(cpp_requests, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_model_path = args.python_model or config.get("python_model", model_path)
python_env = str(config.get("python_conda_env", "qwen3-tts"))
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
python_model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
"--request-sequence-json",
request_sequence_json,
]
cpp_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--request-sequence-json",
cpp_request_sequence_json,
]
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_neutts_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
warmup_request: dict[str, Any],
requests: list[dict[str, Any]],
case_name: str,
) -> tuple[list[str], list[str]]:
if backend != "cuda":
raise RuntimeError("NeuTTS warmbench is CUDA-only")
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
warmup_request_json = json.dumps(warmup_request, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_model_path = args.python_model or config.get("python_model", model_path)
python_env = str(config.get("python_conda_env", "qwen3-tts"))
selected_cases_path = scenario_dir / "neutts_selected_cases.json"
selected_cases_path.write_text(
json.dumps(
{
"cases": [
{
"id": case_name,
"family": "neutts",
"task": "tts",
"mode": "offline",
"warmup": warmup_request,
"requests": requests,
}
]
},
indent=2,
ensure_ascii=False,
),
encoding="utf-8",
)
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--backbone",
python_model_path,
"--backend",
backend,
"--device",
"cuda",
"--codec-device",
"cuda",
"--cases",
str(selected_cases_path),
"--only",
case_name,
"--out-root",
str(scenario_dir / "python_audio"),
]
cpp_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--warmup-request-json",
warmup_request_json,
"--request-sequence-json",
request_sequence_json,
]
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_confucius4_tts_commands(
config: dict[str, Any],
backend: str,
mode: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
if backend != "cuda":
raise RuntimeError("Confucius4-TTS warmbench is CUDA-only")
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_model_path = args.python_model or config.get("python_model", model_path)
python_env = str(config.get("python_conda_env", "qwen3-tts"))
python_model_root = Path(python_model_path)
model_parent = python_model_root.parent if python_model_root.is_absolute() else Path()
w2v_bert_path = config.get("w2v_bert_model", "models/facebook-w2v-bert-2.0")
bigvgan_path = config.get("bigvgan_model", "models/bigvgan_v2_22khz_80band_256x")
campplus_path = config.get("campplus_model", "models/funasr-campplus/campplus_cn_common.bin")
if python_model_root.is_absolute():
sibling_w2v = model_parent / "facebook-w2v-bert-2.0"
sibling_bigvgan = model_parent / "bigvgan_v2_22khz_80band_256x"
sibling_campplus = model_parent / "funasr-campplus" / "campplus_cn_common.bin"
if sibling_w2v.exists():
w2v_bert_path = str(sibling_w2v)
if sibling_bigvgan.exists():
bigvgan_path = str(sibling_bigvgan)
if sibling_campplus.exists():
campplus_path = str(sibling_campplus)
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
python_model_path,
"--w2v-bert",
str(w2v_bert_path),
"--bigvgan",
str(bigvgan_path),
"--campplus",
str(campplus_path),
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
"--summary-file",
str(scenario_dir / "python.summary.json"),
"--request-sequence-json",
request_sequence_json,
]
cpp_command = [
"env",
"NVIDIA_TF32_OVERRIDE=0",
"GGML_CUDA_FORCE_CUBLAS_COMPUTE_32F=1",
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--request-sequence-json",
request_sequence_json,
"--run-mode",
mode,
]
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_dramabox_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_env = str(config.get("python_conda_env", "qwen3-tts"))
python_model_path = args.python_model or config.get("python_model", model_path)
gemma_model_path = config.get("gemma_model", "models/gemma-3-12b-it-bnb-4bit")
model_root = Path(python_model_path)
if model_root.is_absolute():
sibling_gemma = model_root.parent / "gemma-3-12b-it-bnb-4bit"
if sibling_gemma.exists():
gemma_model_path = str(sibling_gemma)
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
python_model_path,
"--gemma-root",
str(gemma_model_path),
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
"--summary-file",
str(scenario_dir / "python.summary.json"),
"--request-sequence-json",
request_sequence_json,
]
cpp_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--request-sequence-json",
request_sequence_json,
]
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def personaplex_python_request(request: dict[str, Any]) -> dict[str, Any]:
out = {
"id": request.get("id", ""),
"input_wav": request.get("input_wav", request.get("audio", "")),
"text_prompt": request.get("text_prompt", request.get("text", "")),
"voice_prompt": request.get("voice_prompt", ""),
"seed": request.get("seed", 42424242),
"temp_audio": request.get("temp_audio", request.get("temperature", 0.8)),
"temp_text": request.get("temp_text", request.get("text_temperature", 0.7)),
"topk_audio": request.get("topk_audio", request.get("top_k", 250)),
"topk_text": request.get("topk_text", request.get("text_top_k", 25)),
"greedy": bool(request.get("greedy", False)),
}
options = request.get("options", {})
if isinstance(options, dict):
if "temperature" in options:
out["temp_audio"] = options["temperature"]
if "text_temperature" in options:
out["temp_text"] = options["text_temperature"]
if "top_k" in options:
out["topk_audio"] = options["top_k"]
if "text_top_k" in options:
out["topk_text"] = options["text_top_k"]
if "do_sample" in options:
out["greedy"] = not bool(options["do_sample"])
if "voice_id" in options and not out["voice_prompt"]:
out["voice_prompt"] = str(options["voice_id"]) + ".pt"
if not out["voice_prompt"]:
voice_ref = request.get("voice_ref", "")
if not voice_ref:
raise RuntimeError(f"PersonaPlex request {out['id']!r} has neither voice_prompt nor voice_ref")
out["voice_prompt"] = voice_ref
return out
def personaplex_cpp_request(request: dict[str, Any]) -> dict[str, Any]:
options = dict(request.get("options", {})) if isinstance(request.get("options", {}), dict) else {}
if "temp_audio" in request:
options["temperature"] = str(request["temp_audio"])
elif "temperature" in request:
options["temperature"] = str(request["temperature"])
if "temp_text" in request:
options["text_temperature"] = str(request["temp_text"])
elif "text_temperature" in request:
options["text_temperature"] = str(request["text_temperature"])
if "topk_audio" in request:
options["top_k"] = str(request["topk_audio"])
elif "top_k" in request:
options["top_k"] = str(request["top_k"])
if "topk_text" in request:
options["text_top_k"] = str(request["topk_text"])
elif "text_top_k" in request:
options["text_top_k"] = str(request["text_top_k"])
if "greedy" in request:
options["do_sample"] = "false" if bool(request["greedy"]) else "true"
elif "do_sample" in request:
options["do_sample"] = "true" if bool(request["do_sample"]) else "false"
out: dict[str, Any] = {
"id": request.get("id", ""),
"audio": str(resolve_repo_path(str(request.get("input_wav", request.get("audio", ""))))),
"text": request.get("text_prompt", request.get("text", "")),
"seed": request.get("seed", 42424242),
"options": options,
}
voice_ref = request.get("voice_ref")
if voice_ref:
out["voice_ref"] = str(resolve_repo_path(str(voice_ref)))
else:
voice_prompt = str(request.get("voice_prompt", ""))
if voice_prompt.endswith(".wav"):
out["voice_ref"] = str(resolve_repo_path(voice_prompt))
elif voice_prompt.endswith(".pt"):
voice_prompt = voice_prompt[:-3]
out["voice_id"] = voice_prompt
elif voice_prompt:
out["voice_id"] = voice_prompt
elif "voice_id" in out["options"]:
out["voice_id"] = str(out["options"].pop("voice_id"))
return out
def build_personaplex_commands(
config: dict[str, Any],
mode: str,
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
if backend != "cuda":
raise RuntimeError("PersonaPlex warmbench is CUDA-only")
python_requests = [personaplex_python_request(request) for request in requests]
cpp_requests = [personaplex_cpp_request(request) for request in requests]
python_request_sequence_json = json.dumps(python_requests, ensure_ascii=False, separators=(",", ":"))
cpp_request_path = scenario_dir / "cpp_requests.json"
cpp_request_path.write_text(json.dumps({"requests": cpp_requests}, indent=2, ensure_ascii=False), encoding="utf-8")
(scenario_dir / "cpp.batch_manifest.json").write_text(
json.dumps({"requests": cpp_requests}, indent=2, ensure_ascii=False),
encoding="utf-8")
if mode == "streaming" and len(cpp_requests) != 1:
raise RuntimeError("PersonaPlex streaming warmbench accepts exactly one request")
model_path = args.model or config["model"]
python_model_path = args.python_model or config.get("python_model", model_path)
python_env = str(config.get("python_conda_env", "qwen3-tts"))
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
python_model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--audio-out-dir",
str(scenario_dir / "python_audio"),
"--text-out-dir",
str(scenario_dir / "python_text"),
"--summary-file",
str(scenario_dir / "python.summary.json"),
"--request-sequence-json",
python_request_sequence_json,
]
cpp_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--task",
"s2s",
"--family",
"personaplex",
"--model",
model_path,
"--mode",
mode,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
]
if mode == "streaming":
request = cpp_requests[0]
request_id = str(request.get("id", "request1"))
cpp_command.extend([
"--audio",
str(request["audio"]),
"--text",
str(request.get("text", "")),
"--seed",
str(request.get("seed", 42424242)),
"--out",
str(scenario_dir / "cpp_audio" / f"{request_id}.wav"),
])
if request.get("voice_ref"):
cpp_command.extend(["--voice-ref", str(request["voice_ref"])])
elif request.get("voice_id"):
cpp_command.extend(["--voice-id", str(request["voice_id"])])
for key, value in sorted(dict(request.get("options", {})).items()):
cpp_command.extend(["--request-option", f"{key}={value}"])
else:
cpp_command.extend([
"--request-sequence",
str(cpp_request_path),
"--out-dir",
str(scenario_dir / "cpp_audio"),
"--batch-manifest-out",
str(scenario_dir / "cpp.batch_manifest.json"),
"--metrics",
])
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def build_higgs_audio_tts_commands(
config: dict[str, Any],
backend: str,
args: argparse.Namespace,
scenario_dir: Path,
requests: list[dict[str, Any]],
) -> tuple[list[str], list[str]]:
if backend != "cuda":
raise RuntimeError("Higgs TTS warmbench is CUDA-only")
request_sequence_json = json.dumps(requests, ensure_ascii=False, separators=(",", ":"))
model_path = args.model or config["model"]
python_env = str(config.get("python_conda_env", "qwen3-tts"))
python_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
"python",
str(REPO_ROOT / config["python_script"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "python.timing.log"),
"--output-dir",
str(scenario_dir / "python_audio"),
"--request-sequence-json",
request_sequence_json,
]
cpp_command = [
"conda",
"run",
"--no-capture-output",
"-n",
python_env,
str(REPO_ROOT / config["cpp_bin"]),
"--model",
model_path,
"--backend",
backend,
"--device",
str(args.device),
"--threads",
str(args.threads),
"--warmup",
str(effective_warmup(config, args)),
"--iterations",
str(args.iterations),
"--timing-file",
str(scenario_dir / "cpp.timing.log"),
"--output-dir",
str(scenario_dir / "cpp_audio"),
"--request-sequence-json",
request_sequence_json,
]
if os.environ.get("HIGGS_TTS_WARMBENCH_TRACE") == "1":
python_command.append("--enable-trace")
cpp_command.extend(["--enable-trace", "--log-file", str(scenario_dir / "cpp.trace.log")])
for option in config.get("cpp_session_options", []):
cpp_command.extend(["--session-option", option])
for option in args.cpp_session_option:
cpp_command.extend(["--session-option", option])
return python_command, cpp_command
def validate_tts_result(
parsed: dict[str, Any],
request_count: int,
min_rms: float = 0.0,
) -> dict[str, Any]:
summaries = parsed.get("summaries", [])
audio_path = resolve_repo_path(parsed.get("audio_out"))
indexed_audio_outs = parsed.get("audio_outs", {})
summary_valid = len(summaries) == request_count and all(
int(item.get("samples", 0)) > 0 and float(item.get("rms", 0.0)) >= min_rms
for item in summaries
)
per_request_outputs: list[dict[str, Any]] = []
for request_index in range(request_count):
request_audio_path = resolve_repo_path(indexed_audio_outs.get(request_index))
summary_rms = float(summaries[request_index].get("rms", 0.0)) if request_index < len(summaries) else 0.0
request_valid = (
request_audio_path is not None and
request_audio_path.exists() and
request_audio_path.stat().st_size > 44 and
summary_rms >= min_rms
)
per_request_outputs.append({
"request_index": request_index,
"path": str(request_audio_path) if request_audio_path is not None else "",
"ok": request_valid,
"rms": summary_rms,
})
audio_valid = bool(per_request_outputs) and all(item["ok"] for item in per_request_outputs)
final_audio_valid = audio_path is not None and audio_path.exists() and audio_path.stat().st_size > 44
return {
"ok": summary_valid and audio_valid and final_audio_valid,
"summary_valid": summary_valid,
"audio_valid": audio_valid,
"final_audio_valid": final_audio_valid,
"audio_path": str(audio_path) if audio_path is not None else "",
"per_request_outputs": per_request_outputs,
}
def validate_sequence_result(summary: dict[str, Any], request_count: int, kind: str) -> dict[str, Any]:
steps = summary.get("sequence_steps", []) if isinstance(summary, dict) else []
count_valid = len(steps) == request_count
if kind == "asr":
payload_valid = all(
isinstance(step.get("text_output", ""), str)
and isinstance(step.get("word_timestamps", []), list)
and isinstance(step.get("metrics", {}), dict)
for step in steps)
elif kind == "alignment":
payload_valid = all(
isinstance(step.get("text_output", ""), str)
and isinstance(step.get("word_timestamps", []), list)
and len(step.get("word_timestamps", [])) > 0
and isinstance(step.get("metrics", {}), dict)
for step in steps)
elif kind == "vad":
payload_valid = all(
isinstance(step.get("speech_segments", []), list)
and isinstance(step.get("metrics", {}), dict)
for step in steps)
elif kind == "spk":
payload_valid = all(
isinstance(step.get("label", ""), str)
and isinstance(step.get("index", None), (int, float))
and isinstance(step.get("score", None), (int, float))
and isinstance(step.get("metrics", {}), dict)
for step in steps)
elif kind == "separation":
payload_valid = all(
isinstance(step.get("stems", []), list)
and len(step.get("stems", [])) > 0
and isinstance(step.get("metrics", {}), dict)
for step in steps)
elif kind == "ace_step":
payload_valid = all(
isinstance(step.get("stems", []), list)
and len(step.get("stems", [])) > 0
and isinstance(step.get("metrics", {}), dict)
for step in steps)
elif kind in {"vevo2", "seed_vc", "miocodec", "voxcpm2", "supertonic", "vibevoice", "irodori_tts", "heartmula", "controlfoley", "confucius4_tts", "higgs_audio_tts", "index_tts2", "dramabox", "personaplex"}:
payload_valid = all(
isinstance(step.get("stems", []), list)
and len(step.get("stems", [])) > 0
and isinstance(step.get("metrics", {}), dict)
for step in steps)
else:
payload_valid = all(isinstance(step.get("speaker_turns", []), list) and len(step.get("speaker_turns", [])) > 0 for step in steps)
return {"ok": count_valid and payload_valid, "count_valid": count_valid, "payload_valid": payload_valid}
def write_sequence_step_artifacts(steps: list[dict[str, Any]], output_dir: Path, prefix: str) -> list[str]:
output_dir.mkdir(parents=True, exist_ok=True)
paths: list[str] = []
for request_index, step in enumerate(steps):
path = output_dir / f"{prefix}_request_{request_index:02d}.json"
path.write_text(json.dumps(step, indent=2, ensure_ascii=False), encoding="utf-8")
paths.append(str(path))
return paths
def file_is_nonempty(path_text: str) -> bool:
if not path_text:
return False
path = Path(path_text)
return path.exists() and path.stat().st_size > 0
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def wav_frame_count(path: Path) -> int:
with wave.open(str(path), "rb") as handle:
return int(handle.getnframes())
def first_sequence_audio(summary: dict[str, Any]) -> Path:
steps = summary.get("sequence_steps", [])
if not isinstance(steps, list) or not steps:
raise RuntimeError("ACE-Step hash compare requires at least one C++ sequence step")
stems = steps[0].get("stems", [])
if not isinstance(stems, list) or not stems:
raise RuntimeError("ACE-Step hash compare requires at least one C++ audio stem")
audio_path = resolve_repo_path(str(stems[0].get("audio", "")))
if audio_path is None:
raise RuntimeError("ACE-Step hash compare C++ audio path is missing")
return audio_path
def ace_step_baseline_hash_compare(cpp_summary: dict[str, Any]) -> dict[str, Any]:
current_audio = first_sequence_audio(cpp_summary)
if not ACE_STEP_BASELINE_CPP_AUDIO.exists():
raise RuntimeError(f"ACE-Step baseline audio is missing: {ACE_STEP_BASELINE_CPP_AUDIO}")
if not current_audio.exists():
raise RuntimeError(f"ACE-Step C++ audio is missing: {current_audio}")
baseline_sha256 = sha256_file(ACE_STEP_BASELINE_CPP_AUDIO)
current_sha256 = sha256_file(current_audio)
baseline_frames = wav_frame_count(ACE_STEP_BASELINE_CPP_AUDIO)
current_frames = wav_frame_count(current_audio)
comparable = baseline_frames == current_frames
return {
"baseline_path": str(ACE_STEP_BASELINE_CPP_AUDIO),
"current_path": str(current_audio),
"baseline_frames": baseline_frames,
"current_frames": current_frames,
"comparable": comparable,
"baseline_sha256": baseline_sha256,
"current_sha256": current_sha256,
"match": baseline_sha256 == current_sha256,
}
def sanitized_result_payload(kind: str, parsed: dict[str, Any]) -> dict[str, Any]:
if kind == "tts":
return {
"summaries": parsed.get("summaries", []),
"warmup_summaries": parsed.get("warmup_summaries", []),
"texts": parsed.get("texts", []),
"warmup_texts": parsed.get("warmup_texts", []),
"audio_out": parsed.get("audio_out"),
"audio_outs": parsed.get("audio_outs", {}),
"metrics": parsed.get("metrics", {}),
"request_metrics": parsed.get("request_metrics", []),
}
return parsed.get("summary") or {}
def missing_parity(request_index: int, request: str, reason: str) -> dict[str, Any]:
return {
"ok": False,
"reason": reason,
"mismatches": [reason],
"request_index": request_index,
"request": request,
}
def run_scenario(
family: str,
config: dict[str, Any],
mode: str,
backend: str,
args: argparse.Namespace,
root_output_dir: Path,
master_log: Path,
) -> dict[str, Any]:
scenario_config = dict(config)
original_requests_per_session = args.requests_per_session
if (
not getattr(args, "requests_per_session_was_explicit", False)
and "default_requests_per_session" in scenario_config
):
args.requests_per_session = int(scenario_config["default_requests_per_session"])
scenario_name = f"{family}_{mode}_{backend}"
scenario_dir = root_output_dir / scenario_name
if scenario_dir.exists() and not args.keep_output_dir:
shutil.rmtree(scenario_dir)
scenario_dir.mkdir(parents=True, exist_ok=True)
append_log(master_log, f"SCENARIO START family={family} mode={mode} backend={backend} dir={scenario_dir}")
if scenario_config["kind"] == "ace_step":
ace_requests, request_manifest = resolve_ace_step_case(config, args)
python_command, cpp_command = build_ace_step_commands(scenario_config, backend, args, scenario_dir, ace_requests)
elif scenario_config["kind"] == "vibevoice":
vibevoice_requests, request_manifest = resolve_vevo2_case(config, args)
args.requests_per_session = len(vibevoice_requests)
python_command, cpp_command = build_vibevoice_commands(scenario_config, backend, args, scenario_dir, vibevoice_requests)
elif scenario_config["kind"] == "heartmula":
heartmula_requests, request_manifest = resolve_vevo2_case(config, args)
args.requests_per_session = len(heartmula_requests)
python_command, cpp_command = build_heartmula_commands(scenario_config, backend, args, scenario_dir, heartmula_requests)
elif scenario_config["kind"] == "controlfoley":
controlfoley_requests, request_manifest = resolve_vevo2_case(config, args)
args.requests_per_session = len(controlfoley_requests)
python_command, cpp_command = build_controlfoley_commands(scenario_config, backend, args, scenario_dir, controlfoley_requests)
elif scenario_config["kind"] == "neutts":
neutts_warmup, neutts_requests, request_manifest = resolve_neutts_case(config, args)
args.requests_per_session = len(neutts_requests)
python_command, cpp_command = build_neutts_commands(
scenario_config,
backend,
args,
scenario_dir,
neutts_warmup,
neutts_requests,
str(request_manifest["case_name"]),
)
elif scenario_config["kind"] == "confucius4_tts":
confucius_requests, request_manifest = resolve_vevo2_case(config, args)
args.requests_per_session = len(confucius_requests)
python_command, cpp_command = build_confucius4_tts_commands(scenario_config, backend, mode, args, scenario_dir, confucius_requests)
elif scenario_config["kind"] == "supertonic":
supertonic_requests, request_manifest = resolve_vevo2_case(config, args)
args.requests_per_session = len(supertonic_requests)
python_command, cpp_command = build_supertonic_commands(scenario_config, backend, args, scenario_dir, supertonic_requests)
elif scenario_config["kind"] == "irodori_tts":
irodori_requests, request_manifest = resolve_vevo2_case(config, args)
args.requests_per_session = len(irodori_requests)
python_command, cpp_command = build_irodori_tts_commands(scenario_config, backend, args, scenario_dir, irodori_requests)
elif scenario_config["kind"] == "higgs_audio_tts":
higgs_requests, request_manifest = resolve_vevo2_case(config, args)
args.requests_per_session = len(higgs_requests)
python_command, cpp_command = build_higgs_audio_tts_commands(scenario_config, backend, args, scenario_dir, higgs_requests)
elif scenario_config["kind"] == "index_tts2":
index_tts2_requests, request_manifest = resolve_vevo2_case(config, args)
args.requests_per_session = len(index_tts2_requests)
python_command, cpp_command = build_index_tts2_commands(scenario_config, backend, args, scenario_dir, index_tts2_requests)
elif scenario_config["kind"] == "dramabox":
dramabox_requests, request_manifest = resolve_vevo2_case(config, args)
args.requests_per_session = len(dramabox_requests)
python_command, cpp_command = build_dramabox_commands(scenario_config, backend, args, scenario_dir, dramabox_requests)
elif scenario_config["kind"] == "personaplex":
personaplex_requests, request_manifest = resolve_personaplex_case(config, args, mode)
args.requests_per_session = len(personaplex_requests)
python_command, cpp_command = build_personaplex_commands(
scenario_config,
mode,
backend,
args,
scenario_dir,
personaplex_requests)
elif scenario_config["kind"] in {"vevo2", "seed_vc"}:
vevo2_requests, request_manifest = resolve_vevo2_case(config, args)
python_command, cpp_command = build_vevo2_commands(scenario_config, backend, args, scenario_dir, vevo2_requests)
elif scenario_config["kind"] == "tts":
if family.startswith("omnivoice_"):
texts, request_manifest, scenario_config = resolve_omnivoice_case(config, args)
else:
texts, request_manifest = resolve_tts_texts(family, config, args, mode)
if family == "qwen3_tts":
for case_name in request_manifest.get("case_names", []):
case_override = scenario_config.get("case_overrides", {}).get(case_name)
if not case_override:
continue
for key in ("model", "clone_audio", "reference_text"):
scenario_config[key] = case_override[key]
request_manifest[key] = case_override[key]
if "source_issue" in case_override:
request_manifest["source_issue"] = case_override["source_issue"]
if family == "chatterbox" and not args.warmup_text and request_manifest.get("warmup_text"):
scenario_config["warmup_text"] = request_manifest["warmup_text"]
if family == "moss_tts_nano" and isinstance(request_manifest.get("options"), dict):
scenario_config.update(request_manifest["options"])
if family in {"qwen3_tts_voice_design", "qwen3_tts_custom_voice"}:
default_instruct = scenario_config.get("voice_design_instruct", scenario_config.get("custom_voice_instruct", ""))
request_manifest["warmup_instruct"] = args.warmup_voice_design_instruct or default_instruct
request_manifest["instructs"] = (
args.voice_design_instructs[: len(texts)]
if args.voice_design_instructs
else [default_instruct] * len(texts)
)
if family.startswith("omnivoice_") and scenario_config.get("task") == "voice_design":
warmup_instruct, request_instructs = omnivoice_instruct_values(scenario_config, args, len(texts))
request_manifest["warmup_instruct"] = warmup_instruct
request_manifest["instructs"] = request_instructs
if family == "qwen3_tts_custom_voice":
speaker = args.speaker or scenario_config["speaker"]
request_manifest["warmup_speaker"] = args.warmup_speaker or speaker
request_manifest["speakers"] = (
args.request_speakers[: len(texts)]
if args.request_speakers
else [speaker] * len(texts)
)
if family == "moss_tts_local" and "requests" in request_manifest:
request_file = scenario_dir / "moss_tts_local_requests.json"
request_file.write_text(
json.dumps({"requests": request_manifest["requests"]}, indent=2, ensure_ascii=False),
encoding="utf-8",
)
scenario_config["moss_tts_local_request_file"] = str(request_file)
python_command, cpp_command = build_tts_commands(family, scenario_config, backend, args, scenario_dir, texts)
else:
if family in {"mel_band_roformer", "htdemucs"}:
if len(args.case_names) > 1:
raise RuntimeError(f"{family} warmbench accepts at most one --case-name")
case_name = args.case_names[0] if args.case_names else str(config.get("default_case_name", "default"))
family_label = "RoFormer" if family == "mel_band_roformer" else "HTDemucs"
warmup_case, request_cases = load_separation_cases(REPO_ROOT / config["case_catalog"], case_name, args.requests_per_session, family_label)
warmup_audio = REPO_ROOT / warmup_case if not warmup_case.is_absolute() else warmup_case
audio_requests = [
REPO_ROOT / item if not item.is_absolute() else item
for item in request_cases
]
request_manifest = {
"case_name": case_name,
"warmup_audio": str(warmup_audio),
"audio_sequence": [str(path) for path in audio_requests],
}
elif family == "qwen3_asr":
warmup_case, request_cases = load_qwen3_asr_cases(REPO_ROOT / config["case_catalog"], args.requests_per_session)
qwen_audio_dir = scenario_dir / "qwen3_asr_audio"
warmup_audio = materialize_qwen3_asr_audio(warmup_case, qwen_audio_dir, 0, "warmup")
audio_requests = [
materialize_qwen3_asr_audio(item, qwen_audio_dir, index, "request")
for index, item in enumerate(request_cases)
]
args.qwen3_asr_warmup_language = str(warmup_case.get("language", "English"))
args.qwen3_asr_warmup_context = str(warmup_case.get("context", ""))
args.qwen3_asr_request_languages = [str(item.get("language", "English")) for item in request_cases]
args.qwen3_asr_request_contexts = [str(item.get("context", "")) for item in request_cases]
request_manifest = {
"warmup_audio": str(warmup_audio),
"warmup_language": args.qwen3_asr_warmup_language,
"warmup_context": args.qwen3_asr_warmup_context,
"audio_sequence": [str(path) for path in audio_requests],
"languages": args.qwen3_asr_request_languages,
"contexts": args.qwen3_asr_request_contexts,
"expected_fragments": [item.get("expected_fragments", []) for item in request_cases],
}
elif family in ("qwen3_forced_aligner", "mms_forced_aligner"):
default_language = FORCED_ALIGNER_LANGUAGES[family]
warmup_case, request_cases = load_qwen3_forced_aligner_cases(REPO_ROOT / config["case_catalog"], args.requests_per_session)
audio_dir = scenario_dir / f"{family}_audio"
warmup_audio = materialize_qwen3_asr_audio(warmup_case, audio_dir, 0, "warmup")
audio_requests = [
materialize_qwen3_asr_audio(item, audio_dir, index, "request")
for index, item in enumerate(request_cases)
]
setattr(args, f"{family}_warmup_language", str(warmup_case.get("language", default_language)))
setattr(args, f"{family}_warmup_transcript", str(warmup_case.get("transcript", "")))
setattr(args, f"{family}_request_languages", [str(item.get("language", default_language)) for item in request_cases])
setattr(args, f"{family}_request_transcripts", [str(item.get("transcript", "")) for item in request_cases])
request_manifest = {
"warmup_audio": str(warmup_audio),
"warmup_language": getattr(args, f"{family}_warmup_language"),
"warmup_transcript": getattr(args, f"{family}_warmup_transcript"),
"audio_sequence": [str(path) for path in audio_requests],
"languages": getattr(args, f"{family}_request_languages"),
"transcripts": getattr(args, f"{family}_request_transcripts"),
"expected_words": [item.get("expected_words", []) for item in request_cases],
}
elif family in {"higgs_audio_stt", "hviske_asr", "nemotron_asr", "muscriptor", "vibevoice_asr", "voxtral_realtime"}:
if len(args.case_names) > 1:
raise RuntimeError(f"{family} warmbench accepts at most one --case-name")
case_name = args.case_names[0] if args.case_names else str(config.get("default_case_name", ""))
warmup_case, request_cases, catalog_manifest = load_catalog_asr_cases(
REPO_ROOT / str(config["case_catalog"]),
args.requests_per_session,
family,
case_name,
)
python_command, cpp_command, request_manifest = build_catalog_asr_commands(
family,
scenario_config,
backend,
args,
scenario_dir,
warmup_case,
request_cases,
)
request_manifest.update(catalog_manifest)
elif family == "miocodec":
warmup_request, request_cases, request_manifest = resolve_miocodec_case(config, args)
python_command, cpp_command = build_miocodec_commands(
scenario_config,
backend,
args,
scenario_dir,
warmup_request,
request_cases,
)
elif family == "voxcpm2":
warmup_request, request_cases, request_manifest = resolve_voxcpm2_case(config, args)
python_command, cpp_command = build_voxcpm2_commands(
scenario_config,
mode,
backend,
args,
scenario_dir,
warmup_request,
request_cases,
)
else:
request_dir = scenario_dir / "requests"
warmup_audio, audio_requests = build_audio_requests(
request_dir,
args.requests_per_session,
scenario_seed(args.seed, family, mode),
parse_audio_durations(args.audio_durations),
args.audio_warmup_duration,
)
request_manifest = {
"warmup_audio": str(warmup_audio),
"audio_sequence": [str(path) for path in audio_requests],
}
if family not in {"miocodec", "voxcpm2", "higgs_audio_stt", "hviske_asr", "nemotron_asr", "muscriptor", "vibevoice_asr", "voxtral_realtime"}:
python_command, cpp_command = build_audio_commands(family, scenario_config, backend, mode, args, scenario_dir, warmup_audio, audio_requests)
(scenario_dir / "request_manifest.json").write_text(json.dumps(request_manifest, indent=2), encoding="utf-8")
set_command_backend(python_command, python_reference_backend(backend))
append_log(master_log, f"PYTHON START family={family} mode={mode} backend={backend} command={' '.join(python_command)}")
python_env = os.environ.copy()
python_env.pop("LD_LIBRARY_PATH", None)
if scenario_config["kind"] == "ace_step":
python_env["ACESTEP_PROJECT_ROOT"] = str(scenario_dir)
python_stdout, python_memory = run_command(python_command, scenario_dir / "python.log", env=python_env)
append_log(master_log, f"PYTHON END family={family} mode={mode} backend={backend}")
append_log(
master_log,
f"PYTHON MEMORY family={family} mode={mode} backend={backend} "
f"peak_rss_kb={python_memory['peak_rss_kb']} peak_vms_kb={python_memory['peak_vms_kb']}",
)
python_parsed = parse_summary_lines(python_stdout)
python_result_payload = sanitized_result_payload(scenario_config["kind"], python_parsed)
python_summary = python_parsed.get("summary") or {}
python_summary_path = scenario_dir / "python.result.json"
python_summary_path.write_text(json.dumps(python_result_payload, indent=2, ensure_ascii=False), encoding="utf-8")
append_log(master_log, f"PYTHON RESULT family={family} mode={mode} backend={backend} path={python_summary_path}")
append_log(master_log, f"CPP START family={family} mode={mode} backend={backend} command={' '.join(cpp_command)}")
cpp_stdout, cpp_memory = run_command(cpp_command, scenario_dir / "cpp.log")
append_log(master_log, f"CPP END family={family} mode={mode} backend={backend}")
append_log(
master_log,
f"CPP MEMORY family={family} mode={mode} backend={backend} "
f"peak_rss_kb={cpp_memory['peak_rss_kb']} peak_vms_kb={cpp_memory['peak_vms_kb']}",
)
cpp_parsed = parse_summary_lines(cpp_stdout)
if scenario_config["kind"] == "personaplex" and not cpp_parsed.get("summary"):
cpp_parsed["summary"] = parse_personaplex_cli_summary(
cpp_stdout,
scenario_dir / "cpp.batch_manifest.json",
backend,
)
cpp_result_payload = sanitized_result_payload(scenario_config["kind"], cpp_parsed)
cpp_summary = cpp_parsed.get("summary") or {}
cpp_summary_path = scenario_dir / "cpp.result.json"
cpp_summary_path.write_text(json.dumps(cpp_result_payload, indent=2, ensure_ascii=False), encoding="utf-8")
append_log(master_log, f"CPP RESULT family={family} mode={mode} backend={backend} path={cpp_summary_path}")
parity_results: list[dict[str, Any]] = []
if scenario_config["kind"] == "tts":
min_rms = float(scenario_config.get("min_rms", 0.0))
python_valid = validate_tts_result(python_parsed, args.requests_per_session, min_rms)
cpp_valid = validate_tts_result(cpp_parsed, args.requests_per_session, min_rms)
waveform_compare_fn = compare_kokoro if family == "kokoro" else compare_pocket
python_whisper_results: list[dict[str, Any]] = []
cpp_whisper_results: list[dict[str, Any]] = []
run_asr_check = bool(scenario_config.get("run_asr_check", True))
for request_index in range(args.requests_per_session):
python_output = python_valid["per_request_outputs"][request_index]
cpp_output = cpp_valid["per_request_outputs"][request_index]
append_log(master_log, f"PYTHON OUTPUT family={family} mode={mode} backend={backend} request={request_index} path={python_output['path']} valid={int(python_output['ok'])}")
append_log(master_log, f"CPP OUTPUT family={family} mode={mode} backend={backend} request={request_index} path={cpp_output['path']} valid={int(cpp_output['ok'])}")
if request_index >= len(python_parsed["summaries"]):
parity = missing_parity(request_index, request_manifest["texts"][request_index], "missing_python_summary")
elif request_index >= len(cpp_parsed["summaries"]):
parity = missing_parity(request_index, request_manifest["texts"][request_index], "missing_cpp_summary")
else:
python_audio_path = resolve_repo_path(python_output["path"])
cpp_audio_path = resolve_repo_path(cpp_output["path"])
if python_audio_path is None or cpp_audio_path is None:
parity = missing_parity(request_index, request_manifest["texts"][request_index], "missing_audio_path")
else:
if run_asr_check:
python_whisper = run_whisper(
python_audio_path,
scenario_dir / "whisper" / f"python_request_{request_index:02d}",
args.threads,
scenario_config.get("whisper_model", WHISPER_MODEL),
scenario_config.get("whisper_language"),
bool(scenario_config.get("whisper_word_timestamps", True)),
)
cpp_whisper = run_whisper(
cpp_audio_path,
scenario_dir / "whisper" / f"cpp_request_{request_index:02d}",
args.threads,
scenario_config.get("whisper_model", WHISPER_MODEL),
scenario_config.get("whisper_language"),
bool(scenario_config.get("whisper_word_timestamps", True)),
)
python_whisper_results.append(python_whisper)
cpp_whisper_results.append(cpp_whisper)
append_log(master_log, f"PYTHON ASR family={family} mode={mode} backend={backend} request={request_index} {summarize_tts_asr(python_whisper)}")
append_log(master_log, f"CPP ASR family={family} mode={mode} backend={backend} request={request_index} {summarize_tts_asr(cpp_whisper)}")
asr_parity = compare_whisper_outputs(
cpp_whisper,
python_whisper,
float(scenario_config.get("asr_compact_lcs_min", 0.0)),
)
else:
asr_parity = {
"ok": True,
"reason": "skipped",
"mismatches": [],
"text_distance": 0,
"compact_text_distance": 0,
"compact_lcs": 1.0,
"compact_lcs_min": float(scenario_config.get("asr_compact_lcs_min", 0.0)),
"timing_mismatches": [],
}
append_log(master_log, f"ASR SKIPPED family={family} mode={mode} backend={backend} request={request_index}")
if family == "qwen3_tts":
waveform_parity = compare_qwen3_tts_audio(
cpp_parsed["summaries"][request_index],
python_parsed["summaries"][request_index],
cpp_audio_path,
python_audio_path,
float(scenario_config.get("wav_cosine_min", 0.95)),
float(scenario_config.get("log_mel_cosine_min", 0.95)),
float(scenario_config.get("length_ratio_min", 0.98)),
)
parity_ok = asr_parity["ok"] and waveform_parity["ok"]
parity_reason = "ok" if parity_ok else (
asr_parity["reason"] if not asr_parity["ok"] else waveform_parity["reason"]
)
elif family in {"moss_tts_nano", "moss_tts_local"}:
waveform_parity = compare_moss_tts(
cpp_parsed["summaries"][request_index],
python_parsed["summaries"][request_index],
cpp_audio_path,
python_audio_path,
float(scenario_config.get("log_mel_cosine_min", 0.80)),
float(scenario_config.get("length_ratio_min", 0.98)),
)
if (
family == "moss_tts_local"
and asr_parity["mismatches"] == ["empty_text"]
and waveform_parity["ok"]
):
asr_parity = {**asr_parity, "ok": True, "reason": "ok", "mismatches": []}
parity_ok = asr_parity["ok"] and waveform_parity["ok"]
parity_reason = "ok" if parity_ok else (
asr_parity["reason"] if not asr_parity["ok"] else waveform_parity["reason"]
)
else:
waveform_parity = waveform_compare_fn(cpp_parsed["summaries"][request_index], python_parsed["summaries"][request_index])
parity_ok = asr_parity["ok"]
parity_reason = asr_parity["reason"]
parity = {
"ok": parity_ok,
"reason": parity_reason,
"mismatches": asr_parity["mismatches"] + waveform_parity["mismatches"],
"asr": asr_parity,
"waveform": waveform_parity,
}
parity["request_index"] = request_index
parity["request"] = request_manifest["texts"][request_index]
parity_results.append(parity)
if "asr" in parity and "waveform" in parity:
append_log(
master_log,
f"PARITY family={family} mode={mode} backend={backend} request={request_index} ok={int(parity['ok'])} reason={parity['reason']} asr_reason={parity['asr']['reason']} waveform_reason={parity['waveform']['reason']}",
)
elif "waveform" in parity:
append_log(
master_log,
f"PARITY family={family} mode={mode} backend={backend} request={request_index} ok={int(parity['ok'])} reason={parity['reason']} metrics={json.dumps(parity['waveform'].get('metrics', {}), sort_keys=True)}",
)
else:
append_log(master_log, f"PARITY family={family} mode={mode} backend={backend} request={request_index} ok={int(parity['ok'])} reason={parity['reason']}")
python_result_payload["whisper"] = python_whisper_results
cpp_result_payload["whisper"] = cpp_whisper_results
elif scenario_config["kind"] == "ace_step":
python_valid = validate_sequence_result(python_summary, args.requests_per_session, "ace_step")
cpp_valid = validate_sequence_result(cpp_summary, args.requests_per_session, "ace_step")
hash_compare = ace_step_baseline_hash_compare(cpp_summary)
append_log(
master_log,
f"ACE_STEP_HASH_COMPARE family={family} mode={mode} backend={backend} "
f"match={int(hash_compare['match'])} baseline_sha256={hash_compare['baseline_sha256']} "
f"current_sha256={hash_compare['current_sha256']}",
)
python_step_paths = write_sequence_step_artifacts(python_summary.get("sequence_steps", []), scenario_dir / "python_json", "python")
cpp_step_paths = write_sequence_step_artifacts(cpp_summary.get("sequence_steps", []), scenario_dir / "cpp_json", "cpp")
for request_index in range(args.requests_per_session):
python_step_path = python_step_paths[request_index] if request_index < len(python_step_paths) else ""
cpp_step_path = cpp_step_paths[request_index] if request_index < len(cpp_step_paths) else ""
append_log(master_log, f"PYTHON OUTPUT family={family} mode={mode} backend={backend} request={request_index} path={python_step_path} valid={int(file_is_nonempty(python_step_path))}")
append_log(master_log, f"CPP OUTPUT family={family} mode={mode} backend={backend} request={request_index} path={cpp_step_path} valid={int(file_is_nonempty(cpp_step_path))}")
elif scenario_config["kind"] in {"vevo2", "seed_vc", "miocodec", "voxcpm2", "supertonic", "vibevoice", "irodori_tts", "heartmula", "controlfoley", "neutts", "confucius4_tts", "higgs_audio_tts", "index_tts2", "dramabox", "personaplex"}:
python_valid = validate_sequence_result(python_summary, args.requests_per_session, scenario_config["kind"])
cpp_valid = validate_sequence_result(cpp_summary, args.requests_per_session, scenario_config["kind"])
python_step_paths = write_sequence_step_artifacts(python_summary.get("sequence_steps", []), scenario_dir / "python_json", "python")
cpp_step_paths = write_sequence_step_artifacts(cpp_summary.get("sequence_steps", []), scenario_dir / "cpp_json", "cpp")
for request_index in range(args.requests_per_session):
python_step_path = python_step_paths[request_index] if request_index < len(python_step_paths) else ""
cpp_step_path = cpp_step_paths[request_index] if request_index < len(cpp_step_paths) else ""
append_log(master_log, f"PYTHON OUTPUT family={family} mode={mode} backend={backend} request={request_index} path={python_step_path} valid={int(file_is_nonempty(python_step_path))}")
append_log(master_log, f"CPP OUTPUT family={family} mode={mode} backend={backend} request={request_index} path={cpp_step_path} valid={int(file_is_nonempty(cpp_step_path))}")
if request_index >= len(python_summary.get("sequence_steps", [])):
parity = missing_parity(request_index, request_manifest["requests"][request_index], "missing_python_step")
elif request_index >= len(cpp_summary.get("sequence_steps", [])):
parity = missing_parity(request_index, request_manifest["requests"][request_index], "missing_cpp_step")
else:
parity = compare_single_audio_step(
cpp_summary["sequence_steps"][request_index],
python_summary["sequence_steps"][request_index],
float(scenario_config.get("wav_cosine_min", 0.80)),
float(scenario_config["log_mel_cosine_min"]) if "log_mel_cosine_min" in scenario_config else None,
float(scenario_config["mrstft_spectral_convergence_max"]) if "mrstft_spectral_convergence_max" in scenario_config else None,
float(scenario_config["mrstft_logmag_mae_max"]) if "mrstft_logmag_mae_max" in scenario_config else None,
int(scenario_config["similarity_vote_required"]) if "similarity_vote_required" in scenario_config else None,
float(scenario_config.get("length_ratio_min", 0.98)),
bool(scenario_config.get("length_ratio_is_diagnostic", False)),
)
parity["request_index"] = request_index
parity["request"] = request_manifest["requests"][request_index]
parity_results.append(parity)
append_log(
master_log,
f"PARITY family={family} mode={mode} backend={backend} request={request_index} "
f"ok={int(parity['ok'])} reason={parity['reason']} metrics={json.dumps(parity.get('metrics', {}), sort_keys=True)}",
)
elif scenario_config["kind"] == "asr":
python_valid = validate_sequence_result(python_summary, args.requests_per_session, "asr")
cpp_valid = validate_sequence_result(cpp_summary, args.requests_per_session, "asr")
python_step_paths = write_sequence_step_artifacts(python_summary.get("sequence_steps", []), scenario_dir / "python_json", "python")
cpp_step_paths = write_sequence_step_artifacts(cpp_summary.get("sequence_steps", []), scenario_dir / "cpp_json", "cpp")
for request_index in range(args.requests_per_session):
python_step_path = python_step_paths[request_index] if request_index < len(python_step_paths) else ""
cpp_step_path = cpp_step_paths[request_index] if request_index < len(cpp_step_paths) else ""
append_log(master_log, f"PYTHON OUTPUT family={family} mode={mode} backend={backend} request={request_index} path={python_step_path} valid={int(file_is_nonempty(python_step_path))}")
append_log(master_log, f"CPP OUTPUT family={family} mode={mode} backend={backend} request={request_index} path={cpp_step_path} valid={int(file_is_nonempty(cpp_step_path))}")
if request_index >= len(python_summary.get("sequence_steps", [])):
parity = missing_parity(request_index, request_manifest["audio_sequence"][request_index], "missing_python_step")
elif request_index >= len(cpp_summary.get("sequence_steps", [])):
parity = missing_parity(request_index, request_manifest["audio_sequence"][request_index], "missing_cpp_step")
else:
if family == "muscriptor":
parity = compare_muscriptor_step(
cpp_summary["sequence_steps"][request_index],
python_summary["sequence_steps"][request_index],
)
elif family in {"qwen3_asr", "voxtral_realtime"}:
expected_fragments = request_manifest.get("expected_fragments", [])
parity = compare_qwen3_asr_step(
cpp_summary["sequence_steps"][request_index],
python_summary["sequence_steps"][request_index],
expected_fragments[request_index] if request_index < len(expected_fragments) else [],
bool(config.get("check_language", False)),
)
else:
parity = compare_parakeet_step(cpp_summary["sequence_steps"][request_index], python_summary["sequence_steps"][request_index])
parity["request_index"] = request_index
parity["request"] = request_manifest["audio_sequence"][request_index]
parity_results.append(parity)
append_log(master_log, f"PARITY family={family} mode={mode} backend={backend} request={request_index} ok={int(parity['ok'])} reason={parity['reason']}")
else:
sequence_kind = scenario_config["kind"]
python_valid = validate_sequence_result(python_summary, args.requests_per_session, sequence_kind)
cpp_valid = validate_sequence_result(cpp_summary, args.requests_per_session, sequence_kind)
python_step_paths = write_sequence_step_artifacts(python_summary.get("sequence_steps", []), scenario_dir / "python_json", "python")
cpp_step_paths = write_sequence_step_artifacts(cpp_summary.get("sequence_steps", []), scenario_dir / "cpp_json", "cpp")
for request_index in range(args.requests_per_session):
python_step_path = python_step_paths[request_index] if request_index < len(python_step_paths) else ""
cpp_step_path = cpp_step_paths[request_index] if request_index < len(cpp_step_paths) else ""
append_log(master_log, f"PYTHON OUTPUT family={family} mode={mode} backend={backend} request={request_index} path={python_step_path} valid={int(file_is_nonempty(python_step_path))}")
append_log(master_log, f"CPP OUTPUT family={family} mode={mode} backend={backend} request={request_index} path={cpp_step_path} valid={int(file_is_nonempty(cpp_step_path))}")
if request_index >= len(python_summary.get("sequence_steps", [])):
parity = missing_parity(request_index, request_manifest["audio_sequence"][request_index], "missing_python_step")
elif request_index >= len(cpp_summary.get("sequence_steps", [])):
parity = missing_parity(request_index, request_manifest["audio_sequence"][request_index], "missing_cpp_step")
else:
if sequence_kind == "vad":
parity = compare_vad_step(cpp_summary["sequence_steps"][request_index], python_summary["sequence_steps"][request_index])
elif sequence_kind == "spk":
parity = compare_speaker_step(cpp_summary["sequence_steps"][request_index], python_summary["sequence_steps"][request_index])
elif sequence_kind == "separation":
parity = compare_separation_step(cpp_summary["sequence_steps"][request_index], python_summary["sequence_steps"][request_index])
elif sequence_kind == "alignment":
expected_words = request_manifest.get("expected_words", [])
parity = compare_qwen3_forced_aligner_step(
cpp_summary["sequence_steps"][request_index],
python_summary["sequence_steps"][request_index],
expected_words[request_index] if request_index < len(expected_words) else [],
1 if config.get("strict_alignment", True) else 320,
)
else:
parity = compare_sortformer_step(cpp_summary["sequence_steps"][request_index], python_summary["sequence_steps"][request_index])
parity["request_index"] = request_index
parity["request"] = request_manifest["audio_sequence"][request_index]
parity_results.append(parity)
append_log(master_log, f"PARITY family={family} mode={mode} backend={backend} request={request_index} ok={int(parity['ok'])} reason={parity['reason']}")
python_summary_path.write_text(json.dumps(python_result_payload, indent=2, ensure_ascii=False), encoding="utf-8")
cpp_summary_path.write_text(json.dumps(cpp_result_payload, indent=2, ensure_ascii=False), encoding="utf-8")
scenario_summary = {
"family": family,
"mode": mode,
"backend": backend,
"warmup": effective_warmup(scenario_config, args),
"request_manifest": request_manifest,
"python": {
"summary_path": str(python_summary_path),
"parsed": python_result_payload,
"valid": python_valid,
"memory": python_memory,
},
"cpp": {
"summary_path": str(cpp_summary_path),
"parsed": cpp_result_payload,
"valid": cpp_valid,
"memory": cpp_memory,
},
"parity": parity_results,
"ok": python_valid["ok"] and cpp_valid["ok"] and all(item["ok"] for item in parity_results),
}
if scenario_config["kind"] == "ace_step":
scenario_summary["hash_compare"] = hash_compare
if hash_compare["comparable"]:
scenario_summary["ok"] = scenario_summary["ok"] and hash_compare["match"]
dit_compare_path = scenario_dir / "ace_step_dit_compare.json"
if dit_compare_path.exists():
dit_compare = json.loads(dit_compare_path.read_text(encoding="utf-8"))
scenario_summary["dit_tensor_compare"] = {
"path": str(dit_compare_path),
"result": dit_compare,
}
scenario_summary["ok"] = scenario_summary["ok"] and bool(dit_compare.get("ok", False))
(scenario_dir / "scenario_summary.json").write_text(json.dumps(scenario_summary, indent=2, ensure_ascii=False), encoding="utf-8")
append_log(master_log, f"SCENARIO END family={family} mode={mode} backend={backend} ok={int(scenario_summary['ok'])}")
args.requests_per_session = original_requests_per_session
return scenario_summary
def main() -> int:
args = parse_args()
requested_stamp = args.artifact_stamp or timestamp_seconds_local()
requested_output_dir = REPO_ROOT / (
args.output_dir if args.output_dir else f"build/logs/warmbench/{requested_stamp}")
root_output_dir = requested_output_dir if args.keep_output_dir else next_artifact_dir(requested_output_dir)
stamp = root_output_dir.name
aggregate_json = root_output_dir / (Path(args.output_json).name if args.output_json else "summary.json")
root_output_dir.mkdir(parents=True, exist_ok=True)
master_log = root_output_dir / "run.log"
aggregate_requests_per_session = aggregate_requests_per_session_value(args)
append_log(master_log, f"RUN START stamp={stamp} modes={args.mode} backends={selected_backends(args)} requests_per_session={aggregate_requests_per_session}")
summary: dict[str, Any] = {
"artifact_stamp": stamp,
"warmup": aggregate_warmup_value(args),
"warmup_arg": args.warmup,
"warmup_was_explicit": args.warmup_was_explicit,
"iterations": args.iterations,
"threads": args.threads,
"device": args.device,
"requests_per_session": aggregate_requests_per_session,
"scenarios": [],
}
for family in selected_families(args):
config = FAMILY_CONFIG[family]
for backend in selected_backends(args):
for mode in selected_modes(args, config):
scenario_summary = run_scenario(family, config, mode, backend, args, root_output_dir, master_log)
summary["scenarios"].append(scenario_summary)
summary["ok"] = all(item["ok"] for item in summary["scenarios"])
aggregate_json.write_text(json.dumps(summary, indent=2, ensure_ascii=False), encoding="utf-8")
append_log(master_log, f"RUN END ok={int(summary['ok'])} summary={aggregate_json}")
print(f"summary_json={aggregate_json}")
return 0 if summary["ok"] else 1
if __name__ == "__main__":
raise SystemExit(main())