Signed-off-by: Boao Shi <aoibosh@connect.hku.hk> Signed-off-by: bowieshi <aoibosh@connect.hku.hk> Signed-off-by: princepride <wangzhipeng628@gmail.com> Signed-off-by: R2-Y <ruiruyang2@gmail.com> Signed-off-by: amy-why-3459 <wuhaiyan17@huawei.com> Signed-off-by: Shi Boao Bowie <aoibosh@connect.hku.hk> Co-authored-by: 汪志鹏 <wangzhipeng628@gmail.com> Co-authored-by: ruirui(rein) yang <73573651+R2-Y@users.noreply.github.com> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com> Co-authored-by: Gao Han <hgaoaf@connect.ust.hk> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
163 lines
5.9 KiB
Python
163 lines
5.9 KiB
Python
"""Repeatable SoulX-Singer offline benchmark (preprocess + DiT, RTF + stage timings).
|
|
|
|
Usage:
|
|
python benchmark.py --model /path/to/SoulX-Singer --svs \\
|
|
--prompt-audio ../SoulX-Singer/example/audio/zh_prompt.mp3 \\
|
|
--target-audio ../SoulX-Singer/example/audio/music.mp3 \\
|
|
--preprocess-weights-dir /path/to/SoulX-Singer-Preprocess \\
|
|
-o benchmark.wav
|
|
|
|
python benchmark.py ... --enable-diffusion-pipeline-profiler
|
|
"""
|
|
|
|
import argparse
|
|
import statistics
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import soundfile as sf
|
|
from end2end import (
|
|
SVC_DEPLOY_CONFIG,
|
|
SVS_DEPLOY_CONFIG,
|
|
add_inference_args,
|
|
build_sampling,
|
|
extract_audio,
|
|
resolve_preprocess_weights_dir,
|
|
)
|
|
|
|
from vllm_omni.entrypoints.omni import Omni
|
|
|
|
|
|
def _audio_duration_sec(outputs) -> float:
|
|
audio_np, sr = extract_audio(outputs)
|
|
return audio_np.size / sr
|
|
|
|
|
|
def _stage_durations_from_output(outputs) -> dict[str, float]:
|
|
omni_out = outputs[0]
|
|
durations = getattr(omni_out, "stage_durations", None) or {}
|
|
if durations:
|
|
return dict(durations)
|
|
ro = omni_out
|
|
if ro is not None:
|
|
return dict(getattr(ro, "stage_durations", None) or {})
|
|
return {}
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description="SoulX-Singer offline benchmark")
|
|
add_inference_args(parser)
|
|
parser.add_argument("--warmup", type=int, default=1, help="Warmup runs (excluded from stats)")
|
|
parser.add_argument("--runs", type=int, default=3, help="Measured runs")
|
|
parser.add_argument(
|
|
"--enable-diffusion-pipeline-profiler",
|
|
action="store_true",
|
|
help="Enable low-overhead stage timing in DiffusionOutput.stage_durations",
|
|
)
|
|
parser.add_argument(
|
|
"--enforce-eager",
|
|
action="store_true",
|
|
help="Disable torch.compile on diff_estimator (eager baseline for A/B)",
|
|
)
|
|
parser.add_argument(
|
|
"--dtype",
|
|
type=str,
|
|
default=None,
|
|
choices=["bfloat16", "bf16", "float16", "fp16", "half"],
|
|
help="DiT trunk dtype (default from deploy YAML). Use float16 to match upstream --fp16.",
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
"-o",
|
|
type=str,
|
|
default=None,
|
|
help="Save WAV from the last measured run (after warmup)",
|
|
)
|
|
parser.add_argument(
|
|
"--save-each-run",
|
|
action="store_true",
|
|
help="With --output, also write measured runs as <stem>_run1.wav, _run2.wav, ...",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
preprocess_dir = resolve_preprocess_weights_dir(args.preprocess_weights_dir)
|
|
deploy_config = args.deploy_config or str(SVS_DEPLOY_CONFIG if args.svs else SVC_DEPLOY_CONFIG)
|
|
mode = "SVS" if args.svs else "SVC"
|
|
|
|
omni_kwargs: dict = {
|
|
"model": args.model,
|
|
"deploy_config": deploy_config,
|
|
"async_chunk": False, # SoulX-Singer currently supports only batch mode (pseudo-streaming was stashed)
|
|
}
|
|
if args.enable_diffusion_pipeline_profiler:
|
|
omni_kwargs["enable_diffusion_pipeline_profiler"] = True
|
|
if args.enforce_eager:
|
|
omni_kwargs["enforce_eager"] = True
|
|
if args.dtype is not None:
|
|
omni_kwargs["dtype"] = args.dtype
|
|
|
|
compile_mode = "eager" if args.enforce_eager else "torch.compile"
|
|
dtype_label = args.dtype or "from deploy YAML"
|
|
print(f"Loading SoulX-Singer {mode} from {args.model} [{compile_mode}, dtype={dtype_label}]")
|
|
|
|
omni = Omni(**omni_kwargs)
|
|
kind = "svs" if args.svs else "svc"
|
|
sampling = build_sampling(args, preprocess_weights_dir=preprocess_dir, kind=kind)
|
|
prompt = {"prompt_token_ids": [0]}
|
|
|
|
latencies_ms: list[float] = []
|
|
rtfs: list[float] = []
|
|
last_stages: dict[str, float] = {}
|
|
last_measured_outputs = None
|
|
|
|
total_runs = args.warmup + args.runs
|
|
for run_idx in range(total_runs):
|
|
is_warmup = run_idx < args.warmup
|
|
label = "warmup" if is_warmup else f"run {run_idx - args.warmup + 1}/{args.runs}"
|
|
t0 = time.perf_counter()
|
|
outputs = list(omni.generate([prompt], sampling))
|
|
elapsed_ms = (time.perf_counter() - t0) * 1000.0
|
|
audio_sec = _audio_duration_sec(outputs)
|
|
rtf = (elapsed_ms / 1000.0) / audio_sec if audio_sec > 0 else float("inf")
|
|
print(f"[{label}] client={elapsed_ms:.1f} ms, audio={audio_sec:.2f}s, RTF={rtf:.3f}")
|
|
if not is_warmup:
|
|
latencies_ms.append(elapsed_ms)
|
|
rtfs.append(rtf)
|
|
last_stages = _stage_durations_from_output(outputs)
|
|
last_measured_outputs = outputs
|
|
if args.output and args.save_each_run:
|
|
measured_idx = run_idx - args.warmup + 1
|
|
out_path = Path(args.output)
|
|
run_path = out_path.with_name(f"{out_path.stem}_run{measured_idx}{out_path.suffix}")
|
|
audio_np, sr = extract_audio(outputs)
|
|
sf.write(str(run_path), audio_np, sr)
|
|
print(f" saved {run_path} ({sr} Hz, {audio_np.size / sr:.2f}s)")
|
|
|
|
omni.close()
|
|
|
|
if args.output and last_measured_outputs is not None:
|
|
audio_np, sr = extract_audio(last_measured_outputs)
|
|
out_path = Path(args.output)
|
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
sf.write(str(out_path), audio_np, sr)
|
|
print(f"\nSaved last measured run → {out_path} ({sr} Hz, {audio_np.size / sr:.2f}s)")
|
|
|
|
if latencies_ms:
|
|
print("\n=== Summary (measured runs) ===")
|
|
print(f"client_ms: mean={statistics.mean(latencies_ms):.1f}, stdev={statistics.pstdev(latencies_ms):.1f}")
|
|
print(f"RTF: mean={statistics.mean(rtfs):.3f}, stdev={statistics.pstdev(rtfs):.3f}")
|
|
if last_stages:
|
|
print("\nStage durations (last measured run):")
|
|
for name, value in sorted(last_stages.items()):
|
|
if name.endswith("_ms"):
|
|
print(f" {name}: {value:.1f} ms")
|
|
else:
|
|
print(f" {name}: {value * 1000.0:.1f} ms")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|