Spaces:
Runtime error
Runtime error
| """ | |
| 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) | |