MomsVoiceAI / test_modules /test_latency_optimizations.py
minhahwang's picture
feat: persist voice profiles to Voice_Profile/ with auto-load on startup
0d420e4
Raw
History Blame Contribute Delete
8.46 kB
"""
Test latency optimizations in voice_clone.py and tts.py.
Tests configuration logic, dtype selection, attention selection,
audio trimming, and generation parameter values — without loading
full models (no GPU required).
"""
import os
import sys
import tempfile
import importlib
import numpy as np
import soundfile as sf
import torch
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
PASS = 0
FAIL = 0
def check(label, condition, detail=""):
global PASS, FAIL
if condition:
PASS += 1
print(f" [OK] {label}")
else:
FAIL += 1
print(f" [FAIL] {label} -- {detail}")
def test_global_matmul_precision():
print("\n=== torch.set_float32_matmul_precision ===")
import voice_clone # noqa: F401 — importing sets precision
# PyTorch doesn't expose a getter, but the call should not raise
check("Module imported without error (precision set)", True)
def test_dtype_selection():
print("\n=== Dtype selection ===")
from voice_clone import _select_dtype
dtype = _select_dtype()
if torch.cuda.is_available():
cap = torch.cuda.get_device_capability()
if cap[0] >= 8:
check("Ampere+ GPU -> bfloat16", dtype == torch.bfloat16,
f"got {dtype}")
else:
check("Pre-Ampere GPU -> float16", dtype == torch.float16,
f"got {dtype}")
else:
check("CPU -> float32", dtype == torch.float32, f"got {dtype}")
def test_attn_selection():
print("\n=== Attention implementation selection ===")
from voice_clone import _select_attn_impl
impl = _select_attn_impl()
try:
import flash_attn # noqa: F401
check("flash-attn installed -> flash_attention_2",
impl == "flash_attention_2", f"got {impl}")
except ImportError:
check("flash-attn not installed -> sdpa fallback",
impl == "sdpa", f"got {impl}")
def test_generation_params():
print("\n=== Generation parameters ===")
from voice_clone import GENERATION_PARAMS
check("top_k reduced to 20",
GENERATION_PARAMS["top_k"] == 20,
f"got {GENERATION_PARAMS['top_k']}")
check("temperature reduced to 0.7",
GENERATION_PARAMS["temperature"] == 0.7,
f"got {GENERATION_PARAMS['temperature']}")
check("max_new_tokens capped at 1024",
GENERATION_PARAMS["max_new_tokens"] == 1024,
f"got {GENERATION_PARAMS['max_new_tokens']}")
check("subtalker_top_k reduced to 20",
GENERATION_PARAMS["subtalker_top_k"] == 20,
f"got {GENERATION_PARAMS['subtalker_top_k']}")
check("subtalker_temperature reduced to 0.7",
GENERATION_PARAMS["subtalker_temperature"] == 0.7,
f"got {GENERATION_PARAMS['subtalker_temperature']}")
def test_ref_audio_trimming():
print("\n=== Reference audio trimming ===")
from voice_clone import _trim_reference_audio, REF_AUDIO_MAX_SEC
# Create a 20-second test WAV
sr = 24000
long_audio = np.random.randn(int(20 * sr)).astype(np.float32) * 0.1
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
sf.write(f.name, long_audio, sr)
long_path = f.name
# Create a 4-second test WAV (under limit)
short_audio = np.random.randn(int(4 * sr)).astype(np.float32) * 0.1
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
sf.write(f.name, short_audio, sr)
short_path = f.name
try:
# Long audio should be trimmed
trimmed = _trim_reference_audio(long_path)
check("Long audio (20s) is trimmed", trimmed != long_path)
if trimmed != long_path:
info = sf.info(trimmed)
check(f"Trimmed to <= {REF_AUDIO_MAX_SEC}s",
info.duration <= REF_AUDIO_MAX_SEC + 0.1,
f"got {info.duration:.1f}s")
os.unlink(trimmed)
# Short audio should NOT be trimmed
result = _trim_reference_audio(short_path)
check("Short audio (4s) is NOT trimmed", result == short_path)
finally:
os.unlink(long_path)
os.unlink(short_path)
def test_model_mode_env():
print("\n=== Model mode env var ===")
from voice_clone import get_model_mode, BASE_MODEL_ID, CUSTOM_VOICE_MODEL_ID
mode = get_model_mode()
check("Default mode is 'base'", mode == "base", f"got '{mode}'")
check("BASE_MODEL_ID is 1.7B", "1.7B" in BASE_MODEL_ID, BASE_MODEL_ID)
check("CUSTOM_VOICE_MODEL_ID is 0.6B", "0.6B" in CUSTOM_VOICE_MODEL_ID,
CUSTOM_VOICE_MODEL_ID)
def test_custom_voice_clone_blocked():
print("\n=== CustomVoice clone attempt blocked ===")
# Simulate QWEN_TTS_MODE=custom_voice by patching
import voice_clone
original = voice_clone._MODEL_MODE
voice_clone._MODEL_MODE = "custom_voice"
try:
voice_clone.create_voice_profile("dummy.wav")
check("Should have raised ValueError", False)
except ValueError as e:
check("create_voice_profile raises ValueError for custom_voice mode",
"Base model" in str(e), str(e))
except Exception as e:
check("Unexpected error type", False, str(e))
finally:
voice_clone._MODEL_MODE = original
def test_tts_module_imports():
print("\n=== TTS module structure ===")
from tts import generate_audio_stream, split_into_chunks
import inspect
sig = inspect.signature(generate_audio_stream)
params = list(sig.parameters.keys())
check("generate_audio_stream has 'use_custom_voice' param",
"use_custom_voice" in params, f"params: {params}")
check("generate_audio_stream has 'custom_voice_speaker' param",
"custom_voice_speaker" in params, f"params: {params}")
check("generate_audio_stream has 'voice_profile_id' param",
"voice_profile_id" in params, f"params: {params}")
def test_voice_profile_persistence():
print("\n=== Voice profile persistence ===")
from voice_clone import (
VOICE_PROFILE_DIR, list_saved_profiles, load_default_profile,
has_profile, save_profile_to_disk, load_profile_from_disk,
_PROFILE_CACHE, _cache_lock,
)
import inspect
check("VOICE_PROFILE_DIR exists", VOICE_PROFILE_DIR.exists(),
str(VOICE_PROFILE_DIR))
check("VOICE_PROFILE_DIR is named Voice_Profile",
VOICE_PROFILE_DIR.name == "Voice_Profile")
# Verify API signatures
sig_create = inspect.signature(
__import__('voice_clone').create_voice_profile
)
check("create_voice_profile accepts voice_name",
"voice_name" in sig_create.parameters,
f"params: {list(sig_create.parameters)}")
check("list_saved_profiles returns a list",
isinstance(list_saved_profiles(), list))
# load_default_profile returns None when no profiles saved
# (since Voice_Profile/ might have profiles from prior runs, just check type)
result = load_default_profile()
check("load_default_profile returns str or None",
result is None or isinstance(result, str), f"got {type(result)}")
# has_profile checks disk too
check("has_profile('nonexistent') is False", has_profile("nonexistent") is False)
def test_app_default_profile():
print("\n=== App default profile loading ===")
source = open(
os.path.join(os.path.dirname(os.path.dirname(__file__)), "app.py"),
encoding="utf-8"
).read()
check("App imports load_default_profile",
"from voice_clone import load_default_profile" in source)
check("App calls load_default_profile()",
"_default_profile_id = load_default_profile()" in source)
check("voice_profile_state initialized with default",
"voice_profile_state = gr.State(_default_profile_id)" in source)
check("create_voice_profile passes voice_name",
"voice_name=v_name.strip()" in source)
if __name__ == "__main__":
print("=" * 60)
print("Latency Optimization Tests")
print("=" * 60)
test_global_matmul_precision()
test_dtype_selection()
test_attn_selection()
test_generation_params()
test_ref_audio_trimming()
test_model_mode_env()
test_custom_voice_clone_blocked()
test_tts_module_imports()
test_voice_profile_persistence()
test_app_default_profile()
print("\n" + "=" * 60)
print(f"Results: {PASS} passed, {FAIL} failed")
print("=" * 60)
sys.exit(1 if FAIL > 0 else 0)