#!/usr/bin/env python3 """ MozilLaz — uçtan uca doğrulama (smoke test). Şunları doğrular: 1. lora_config.json geçerli ve voxcpm LoRAConfig'e dönüşüyor 2. lora_weights.safetensors yapısal olarak sağlam (384 tensör, ~18,1M param) 3. Base model + LoRA yükleniyor ve TÜM anahtarlar eşleşiyor (384/384) 4. Gerçek Lazca ses üretimi çalışıyor (48 kHz çıktı) Kullanım: python test_smoke.py # tam test (base modeli indirir, ~4.6 GB) python test_smoke.py --weights-only # yalnızca ağırlık dosyası kontrolü (indirme yok) Bu script, modelin ilk bağımsız doğrulamasında (Mac mini M4, MPS, voxcpm 2.0.3) kullanılan test kodunun temizlenmiş halidir. O testte: 384/384 anahtar eşleşti, "Ngolaşa uluri?" cümlesi 1,44 sn / 48 kHz ses olarak üretildi. """ import argparse import json import struct import sys import time from pathlib import Path SCRIPT_DIR = Path(__file__).parent EXPECTED_TENSORS = 384 EXPECTED_PARAMS = 18_087_936 TEST_TEXT = "[speaker:spk_tmp_001 language:lzz] Ngolaşa uluri?" def check_weights(path: Path) -> bool: """Safetensors başlığını bağımsız oku (torch gerekmez).""" print(f"— Ağırlık dosyası: {path.name}") with open(path, "rb") as f: header_len = struct.unpack(" bool: import numpy as np import soundfile as sf from voxcpm import VoxCPM from voxcpm.model.voxcpm import LoRAConfig with open(SCRIPT_DIR / "lora_config.json", encoding="utf-8") as f: lc = json.load(f)["lora_config"] cfg = LoRAConfig(**{k: v for k, v in lc.items() if k in LoRAConfig.model_fields}) print(f"— LoRAConfig kuruldu: r={cfg.r}, α={cfg.alpha}, " f"lm={cfg.enable_lm}, dit={cfg.enable_dit}, proj={cfg.enable_proj}") print(f"— Base model yükleniyor: {base_model} (ilk seferde ~4.6 GB indirilir)") t0 = time.time() model = VoxCPM.from_pretrained( base_model, load_denoiser=False, lora_config=cfg, lora_weights_path=str(SCRIPT_DIR / "lora_weights.safetensors"), device=device, ) print(f" ✅ yüklendi ({time.time() - t0:.0f} sn)") loaded, skipped = model.load_lora(str(SCRIPT_DIR / "lora_weights.safetensors")) durum = "✅" if (len(loaded) == EXPECTED_TENSORS and not skipped) else "❌" print(f" {durum} LoRA anahtar eşleşmesi: {len(loaded)}/{EXPECTED_TENSORS} " f"(atlanan: {len(skipped)})") if skipped: print(f" atlanan örnek: {skipped[:5]}") return False print(f"— Ses üretiliyor: {TEST_TEXT!r}") t0 = time.time() audio = model.generate(text=TEST_TEXT, inference_timesteps=10, cfg_value=2.0) dt = time.time() - t0 audio = np.asarray(audio).squeeze() sample_rate = getattr(getattr(model, "tts_model", None), "sample_rate", 48000) out = SCRIPT_DIR / "smoke_test_output.wav" sf.write(out, audio, sample_rate) dur = len(audio) / sample_rate ok = dur > 0.3 and float(abs(audio).max()) > 0.01 print(f" {'✅' if ok else '❌'} {dur:.2f} sn ses üretildi " f"({dt:.0f} sn sürdü, RTF={dt / dur:.1f}) → {out.name}") return ok def main(): parser = argparse.ArgumentParser(description="MozilLaz smoke test") parser.add_argument("--weights-only", action="store_true", help="Yalnızca ağırlık dosyası kontrolü (model indirmez)") parser.add_argument("--base-model", default="openbmb/VoxCPM2") parser.add_argument("--device", default=None, help="cuda | mps | cpu (varsayılan: voxcpm otomatik seçer)") args = parser.parse_args() weights = SCRIPT_DIR / "lora_weights.safetensors" if not weights.exists() or weights.stat().st_size < 1_000_000: print("❌ lora_weights.safetensors eksik ya da LFS pointer'ı — " "`git lfs pull` yapın veya dosyayı HF'den indirin.") sys.exit(1) ok = check_weights(weights) if not args.weights_only: ok = full_test(args.base_model, args.device) and ok print("\n" + ("🎉 TÜM TESTLER GEÇTİ" if ok else "💥 TEST BAŞARISIZ")) sys.exit(0 if ok else 1) if __name__ == "__main__": main()