Download eval.py from fishaudio/drama-3-int4: direct link, hf CLI and curl.
- Browser
- Download file 9.53 kB
-
https://huggingface.co/fishaudio/drama-3-int4/resolve/main/eval.py
- Command line
-
hf download hf://fishaudio/drama-3-int4/eval.py
-
curl -L -o eval.py https://huggingface.co/fishaudio/drama-3-int4/resolve/main/eval.py
9.53 kB
| """A/B eval: fishaudio/drama-3 (FP8, mounted) vs akhaliq/drama-3-int4 (hybrid int4). | |
| Generates clips for a fixed prompt set on both, transcribes with faster-whisper large-v3, | |
| computes WER vs prompt text and speaker similarity (resemblyzer) for clone prompts. | |
| Uploads eval_results.json + clips to akhaliq/drama-3-int4/eval/.""" | |
| import os, subprocess, sys, time, json, re | |
| import requests | |
| VENV_PY = "/root/venv/bin/python" | |
| OUT = "/root/eval_out" | |
| REF_URL = "https://huggingface.co/datasets/zhaochenyang20/seed-tts-eval-mini/resolve/main/en/prompt-wavs/common_voice_en_10119832.wav" | |
| REF_TEXT = "We asked over twenty different people, and they all said it was his." | |
| PROMPTS = [ | |
| ("plain1", "Hello, how are you? The weather is lovely today, so let's take a long walk by the river.", False), | |
| ("plain2", "Get the trust fund to the bank early.", True), | |
| ("plain3", "The quick brown fox jumps over the lazy dog, and then runs into the forest.", False), | |
| ("long1", "The quarterly report is due on Thursday, so please finish it by noon. After that, we will review the numbers together and decide whether the new pricing plan should launch next month or wait until spring.", False), | |
| ("tags1", "[sighs] Fine. If we miss it, it's on you. [laughs] Well, maybe that's fair.", True), | |
| ("zh1", "今天天气很好,我们一起去公园散步吧。", False), | |
| ("ja1", "今日は天気がいいので、一緒に公園を散歩しましょう。", False), | |
| ("clone1", "The meeting has been moved to three o'clock on Friday afternoon.", True), | |
| ] | |
| def sh(cmd, **kw): | |
| print("+", " ".join(str(c) for c in cmd), flush=True) | |
| subprocess.run(cmd, check=True, **kw) | |
| def tail(path, n=12000): | |
| try: | |
| return open(path, errors="replace").read()[-n:] | |
| except Exception as e: | |
| return f"<no log: {e}>" | |
| def install_stack(): | |
| import io, tarfile, pathlib, glob as _glob | |
| r = requests.get("https://github.com/sgl-project/sglang-omni/archive/refs/heads/main.tar.gz", timeout=300) | |
| r.raise_for_status() | |
| with tarfile.open(fileobj=io.BytesIO(r.content), mode="r:gz") as tf: | |
| tf.extractall("/root") | |
| if not os.path.exists("/root/sglang-omni"): | |
| os.replace("/root/sglang-omni-main", "/root/sglang-omni") | |
| sh(["uv", "venv", "--python", f"{sys.version_info.major}.{sys.version_info.minor}", "/root/venv"]) | |
| try: | |
| sh(["uv", "pip", "install", "--prerelease=allow", "--python", VENV_PY, | |
| "-e", ".[fishaudio]"], cwd="/root/sglang-omni") | |
| except subprocess.CalledProcessError: | |
| with open("/root/overrides.txt", "w") as fh: | |
| fh.write("protobuf>=6.31.1,<7.0.0\n") | |
| sh(["uv", "pip", "install", "--prerelease=allow", "--python", VENV_PY, | |
| "--override", "/root/overrides.txt", "-e", "/root/sglang-omni[fishaudio]"]) | |
| sh(["uv", "pip", "install", "--python", VENV_PY, "ninja", "numpy", | |
| "faster-whisper", "jiwer", "resemblyzer", "soundfile", "setuptools"]) # setuptools: pkg_resources for webrtcvad | |
| sh(["uv", "pip", "install", "--python", VENV_PY, "nvidia-cuda-nvcc"]) | |
| _nvcc = _glob.glob("/root/venv/lib/python3.12/site-packages/nvidia/**/bin/nvcc", recursive=True) | |
| NVCC_BIN = str(pathlib.Path(_nvcc[0]).parent) | |
| CUDA_HOME = str(pathlib.Path(_nvcc[0]).parent.parent) | |
| _libdirs = sorted({str(pathlib.Path(p).parent) for p in _glob.glob( | |
| "/root/venv/lib/python3.12/site-packages/nvidia/**/libcudart.so*", recursive=True)}) | |
| for d in _libdirs: | |
| for so in pathlib.Path(d).glob("libcudart.so.*"): | |
| link = pathlib.Path(d) / "libcudart.so" | |
| if not link.exists(): | |
| link.symlink_to(so.name) | |
| os.makedirs("/root/stubs", exist_ok=True) | |
| with open("/root/stubs/deep_ep.py", "w") as fh: | |
| fh.write('"""Stub: deep_ep needs a CUDA toolkit this image lacks; unused on our path."""\n' | |
| "class Buffer:\n pass\n\nclass Config:\n pass\n") | |
| env = dict(os.environ, PYTHONPATH="/root/stubs", | |
| SGLANG_OMNI_STARTUP_TIMEOUT="2700", # FP8 (41.5GB) loads slowly from the fuse volume | |
| PATH=f"{NVCC_BIN}:/root/venv/bin:" + os.environ["PATH"], | |
| CUDA_HOME=CUDA_HOME, | |
| LIBRARY_PATH=":".join(_libdirs) + ":" + os.environ.get("LIBRARY_PATH", ""), | |
| LD_LIBRARY_PATH=":".join(_libdirs) + ":" + os.environ.get("LD_LIBRARY_PATH", "")) | |
| return env | |
| def serve_and_generate(model_dir, tag, serve_env, ref, engine_frac): | |
| log_path = f"/root/serve_{tag}.log" | |
| proc = subprocess.Popen( | |
| ["/root/venv/bin/sgl-omni", "serve", "--model-path", model_dir, "--port", "8000", | |
| "--tts_engine.gpu_memory_fraction", engine_frac, "--vocoder.gpu_memory_fraction", "0.2"], | |
| stdout=open(log_path, "w"), stderr=subprocess.STDOUT, env=serve_env) | |
| deadline = time.time() + 2700 | |
| while time.time() < deadline: | |
| if proc.poll() is not None: | |
| print(f"SERVER {tag} DIED:\n" + tail(log_path), flush=True) | |
| sys.exit(1) | |
| try: | |
| if requests.get("http://localhost:8000/health", timeout=3).ok: | |
| break | |
| except Exception: | |
| pass | |
| time.sleep(5) | |
| else: | |
| print(f"SERVER {tag} NOT READY:\n" + tail(log_path), flush=True) | |
| sys.exit(1) | |
| print(f"SERVER {tag} UP", flush=True) | |
| outdir = f"{OUT}/{tag}" | |
| os.makedirs(outdir, exist_ok=True) | |
| for name, text, use_ref in PROMPTS: | |
| payload = {"input": text} | |
| if use_ref: | |
| payload["references"] = [ref] | |
| r = requests.post("http://localhost:8000/v1/audio/speech", json=payload, timeout=600) | |
| print(f"[{tag}] {name}: HTTP {r.status_code}, {len(r.content)} bytes", flush=True) | |
| if r.status_code == 200: | |
| open(f"{outdir}/{name}.wav", "wb").write(r.content) | |
| else: | |
| print(f"[{tag}] {name} FAILED: {r.text[:400]}", flush=True) | |
| proc.terminate() | |
| time.sleep(10) | |
| def main(): | |
| t0 = time.time() | |
| print("=== eval.py v12: int4-only, setuptools fix + fault-tolerant scorer ===", flush=True) | |
| from huggingface_hub import hf_hub_download, snapshot_download, HfApi | |
| serve_env = install_stack() | |
| # patch for the int4 checkpoint (original FP8 uses the native bsgemm-moe method) | |
| patch = hf_hub_download("akhaliq/drama-3-int4", "apply_patch.py") | |
| sh([VENV_PY, patch, "/root/sglang-omni"]) | |
| int4_dir = snapshot_download("akhaliq/drama-3-int4") | |
| ref_wav = requests.get(REF_URL, timeout=120).content | |
| open("/root/ref.wav", "wb").write(ref_wav) | |
| ref = {"audio_path": "/root/ref.wav", "text": REF_TEXT} | |
| os.makedirs(OUT, exist_ok=True) | |
| # NOTE: FP8 baseline dropped — fp8e4nv kernels need Hopper (sm90+), unavailable in this session. | |
| serve_and_generate(int4_dir, "int4", serve_env, ref, "0.62") | |
| # scoring in the venv (torch for resemblyzer, ctranslate2 for whisper) | |
| scorer = "/root/score.py" | |
| with open(scorer, "w") as fh: | |
| fh.write(SCORER_SRC) | |
| sh([VENV_PY, scorer, OUT, "/root/ref.wav", json.dumps(PROMPTS)]) | |
| HfApi(token=os.environ["HF_TOKEN"]).upload_folder( | |
| repo_id="akhaliq/drama-3-int4", folder_path=OUT, path_in_repo="eval", | |
| commit_message="eval: FP8 vs int4-hybrid, Whisper WER + speaker similarity") | |
| print(f"EVAL DONE in {time.time()-t0:.0f}s", flush=True) | |
| SCORER_SRC = r''' | |
| import sys, os, json, re | |
| out_dir, ref_path, prompts_json = sys.argv[1], sys.argv[2], sys.argv[3] | |
| PROMPTS = json.loads(prompts_json) | |
| def ref_text(t): | |
| return re.sub(r"\[[^\]]+\]", "", t).strip() | |
| from faster_whisper import WhisperModel | |
| from jiwer import wer | |
| import numpy as np, soundfile as sf | |
| wm = WhisperModel("large-v3", device="cuda", compute_type="float16") | |
| try: | |
| from resemblyzer import VoiceEncoder, preprocess_wav | |
| enc = VoiceEncoder() | |
| ref_emb = enc.embed_utterance(preprocess_wav(ref_path)) | |
| except Exception as e: | |
| print(f"speaker-sim disabled (resemblyzer unavailable: {e})", flush=True) | |
| enc = ref_emb = None | |
| results = {} | |
| for tag in ("int4",): | |
| rows = {} | |
| for name, text, use_ref in PROMPTS: | |
| p = f"{out_dir}/{tag}/{name}.wav" | |
| if not os.path.exists(p): | |
| rows[name] = {"error": "no clip"} | |
| continue | |
| lang = "zh" if name.startswith("zh") else ("ja" if name.startswith("ja") else "en") | |
| segs, _ = wm.transcribe(p, language=lang) | |
| hyp = " ".join(s.text for s in segs).strip() | |
| row = {"hyp": hyp, "ref": ref_text(text), "wer": round(wer(ref_text(text).lower(), hyp.lower()), 4)} | |
| if use_ref and enc is not None: | |
| try: | |
| emb = enc.embed_utterance(preprocess_wav(p)) | |
| row["speaker_sim"] = round(float(np.dot(emb, ref_emb) / | |
| (np.linalg.norm(emb) * np.linalg.norm(ref_emb))), 4) | |
| except Exception as e: | |
| row["speaker_sim_error"] = str(e) | |
| rows[name] = row | |
| wers = [r["wer"] for r in rows.values() if "wer" in r] | |
| sims = [r["speaker_sim"] for r in rows.values() if "speaker_sim" in r] | |
| results[tag] = {"rows": rows, | |
| "mean_wer": round(sum(wers) / len(wers), 4) if wers else None, | |
| "mean_speaker_sim": round(sum(sims) / len(sims), 4) if sims else None} | |
| with open(f"{out_dir}/eval_results.json", "w") as fh: | |
| json.dump(results, fh, indent=2, ensure_ascii=False) | |
| print(json.dumps({t: {"mean_wer": r["mean_wer"], "mean_speaker_sim": r["mean_speaker_sim"]} | |
| for t, r in results.items()}, indent=2)) | |
| ''' | |
| if __name__ == "__main__": | |
| main() | |