voice_audio_multi/tests/meanvc2/meanvc2_python_warm_bench.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

213 lines
8.2 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import importlib.util
import json
import random
import sys
import time
from pathlib import Path
from typing import Any
import numpy as np
import soundfile as sf
REPO_ROOT = Path(__file__).resolve().parents[2]
REFERENCE_ROOT = REPO_ROOT / "reference" / "MeanVC2"
MODEL_ROOT = REPO_ROOT / "models" / "MeanVC2"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Python reference MeanVC2 warmbench.")
parser.add_argument("--family", default="meanvc2")
parser.add_argument("--model", default=str(MODEL_ROOT))
parser.add_argument("--backend", choices=["cpu", "cuda"], default="cuda")
parser.add_argument("--device", type=int, default=0)
parser.add_argument("--threads", type=int, default=8)
parser.add_argument("--warmup", type=int, default=1)
parser.add_argument("--iterations", type=int, default=1)
parser.add_argument("--timing-file", default="")
parser.add_argument("--output-dir", default="")
parser.add_argument("--request-json", default="")
parser.add_argument("--request-sequence-json", default="")
parser.add_argument("--cases", default=str(REPO_ROOT / "tests" / "meanvc2" / "meanvc2_warm_bench_cases.json"))
parser.add_argument("--only", default="quality_120ms_default")
return parser.parse_args()
def resolve_path(path: str) -> Path:
value = Path(path)
return value if value.is_absolute() else REPO_ROOT / value
def load_case_requests(args: argparse.Namespace) -> list[dict[str, Any]]:
if args.request_sequence_json:
payload = json.loads(args.request_sequence_json)
if not isinstance(payload, list):
raise RuntimeError("--request-sequence-json must decode to a list")
return payload
if args.request_json:
payload = json.loads(args.request_json)
if not isinstance(payload, dict):
raise RuntimeError("--request-json must decode to an object")
return [payload]
with open(resolve_path(args.cases), "r", encoding="utf-8") as handle:
cases = json.load(handle)
if args.only not in cases:
raise RuntimeError(f"MeanVC2 warmbench case not found: {args.only}")
requests = cases[args.only].get("requests")
if not isinstance(requests, list) or not requests:
raise RuntimeError(f"MeanVC2 warmbench case has no requests: {args.only}")
return requests
def load_reference_runtime(model_root: Path):
runtime_dir = REFERENCE_ROOT / "runtime"
if not runtime_dir.exists():
raise RuntimeError(f"MeanVC2 reference runtime not found: {runtime_dir}")
sys.path.insert(0, str(runtime_dir))
spec = importlib.util.spec_from_file_location("meanvc2_runtime_run_rt", runtime_dir / "run_rt.py")
if spec is None or spec.loader is None:
raise RuntimeError("failed to import MeanVC2 runtime")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
module.VOCODER_PATH = str(model_root / "ckpts" / "vocos" / "vocos.pt")
module.SPEAKER_MODEL_PATH = str(model_root / "preprocess" / "ckpts" / "wavlm_large_finetune.pth")
module.WAVLM_CONFIG_PATH = str(model_root / "preprocess" / "ckpts" / "wavlm_large_cfg.pt")
module.MODEL_PATHS["120ms"]["ckpt"] = str(model_root / "ckpts" / "pretrained_models" / "meanvc2_120ms_40ms.safetensors")
module.MODEL_PATHS["120ms"]["config"] = str(REFERENCE_ROOT / "src" / "config" / "config_120ms_40ms.json")
module.MODEL_PATHS["120ms"]["asr_ckpt"] = str(model_root / "preprocess" / "ckpts" / "fastu2pp_160ms.pt")
return module
def reseed(seed: int, backend: str) -> None:
import torch
torch.manual_seed(seed)
if backend == "cuda":
torch.cuda.manual_seed_all(seed)
np.random.seed(seed & 0xFFFFFFFF)
random.seed(seed)
def synchronize(backend: str) -> None:
if backend == "cuda":
import torch
torch.cuda.synchronize()
def summarize_audio(path: Path) -> dict[str, Any]:
audio, sample_rate = sf.read(str(path), always_2d=True, dtype="float32")
flat = np.asarray(audio, dtype=np.float32).reshape(-1)
if flat.size == 0:
raise RuntimeError(f"MeanVC2 reference produced empty audio: {path}")
return {
"sample_rate": int(sample_rate),
"channels": int(audio.shape[1]),
"frames": int(audio.shape[0]),
"samples": int(flat.size),
"duration_sec": float(audio.shape[0]) / float(sample_rate),
"sum": float(np.sum(flat, dtype=np.float64)),
"mean_abs": float(np.mean(np.abs(flat), dtype=np.float64)),
"rms": float(np.sqrt(np.mean(np.square(flat), dtype=np.float64))),
"min": float(np.min(flat)),
"max": float(np.max(flat)),
}
def source_duration_sec(path: Path) -> float:
info = sf.info(str(path))
if info.samplerate <= 0:
raise RuntimeError(f"MeanVC2 source audio has invalid sample rate: {path}")
return float(info.frames) / float(info.samplerate)
def main() -> None:
args = parse_args()
model_root = resolve_path(args.model)
output_root = resolve_path(args.output_dir) if args.output_dir else REPO_ROOT / "build" / "logs" / "meanvc2" / "python_baseline"
output_root.mkdir(parents=True, exist_ok=True)
import torch
torch.set_num_threads(args.threads)
if args.backend == "cuda":
torch.cuda.set_device(args.device)
device = "cuda"
else:
device = "cpu"
requests = load_case_requests(args)
runtime = load_reference_runtime(model_root)
runners: dict[tuple[str, str], Any] = {}
results: list[dict[str, Any]] = []
def runner_for(model: str, target_audio: Path):
key = (model, str(target_audio))
if key not in runners:
runners[key] = runtime.VCRunner(target_wav=str(target_audio), device=device, model=model)
return runners[key]
def run_request(request: dict[str, Any], output_path: Path, record: bool, ordinal: int) -> dict[str, Any] | None:
model = str(request.get("model", "120ms"))
if model != "120ms":
raise RuntimeError("MeanVC2 warmbench currently prepares only the quality 120ms_40ms path")
source_audio = resolve_path(str(request["source_audio"]))
target_audio = resolve_path(str(request["target_audio"]))
seed = int(request.get("seed", 42))
reseed(seed, args.backend)
runner = runner_for(model, target_audio)
synchronize(args.backend)
start = time.perf_counter()
runner.process_file(str(source_audio), str(output_path), seed=seed)
synchronize(args.backend)
elapsed_ms = (time.perf_counter() - start) * 1000.0
if not record:
return None
summary = summarize_audio(output_path)
source_duration = source_duration_sec(source_audio)
return {
"request_index": ordinal,
"model": model,
"source_audio": str(source_audio),
"target_audio": str(target_audio),
"seed": seed,
"output": str(output_path),
"wall_ms": elapsed_ms,
"source_duration_sec": source_duration,
"rtf": elapsed_ms / 1000.0 / source_duration,
"audio": summary,
}
for request_index, request in enumerate(requests):
for warmup_index in range(args.warmup):
output_path = output_root / f"warmup_{request_index}_{warmup_index}.wav"
run_request(request, output_path, record=False, ordinal=request_index)
for iteration_index in range(args.iterations):
output_path = output_root / f"request_{request_index}_iter_{iteration_index}.wav"
result = run_request(request, output_path, record=True, ordinal=request_index)
if result is not None:
results.append(result)
payload = {
"family": args.family,
"backend": args.backend,
"device": args.device,
"threads": args.threads,
"output_dir": str(output_root),
"results": results,
}
if args.timing_file:
timing_file = resolve_path(args.timing_file)
timing_file.parent.mkdir(parents=True, exist_ok=True)
timing_file.write_text(json.dumps(payload, indent=2), encoding="utf-8")
print(json.dumps(payload, indent=2))
if __name__ == "__main__":
main()