diff --git a/tests/e2e/online_serving/test_higgs_audio_v3.py b/tests/e2e/online_serving/test_higgs_audio_v3.py index 2ead1b5da..d26e6bff4 100644 --- a/tests/e2e/online_serving/test_higgs_audio_v3.py +++ b/tests/e2e/online_serving/test_higgs_audio_v3.py @@ -127,7 +127,7 @@ class TestHiggsAudioV3OnlineHappyPath: windows stitch into a coherent PCM stream. The byte-count gate is the same as the sync paths; per-chunk audio content is verified offline against Whisper. - NOTE: ``min_hnr_db=0.0`` sits below the typical speech-noise floor so the + NOTE: ``min_hnr_db=0.0`` sit below the typical speech-noise floor so the check still catches catastrophic codec failure (silence, white noise, sample scramble all give HNR << 0) while allowing for the sliding-window codec's slightly-noisier-than-sync output. The default 1.0 dB threshold has only diff --git a/tests/unit/higgs_audio_v3/test_higgs_audio_v3.py b/tests/unit/higgs_audio_v3/test_higgs_audio_v3.py old mode 100644 new mode 100755 index b701d9f8b..b92b0095b --- a/tests/unit/higgs_audio_v3/test_higgs_audio_v3.py +++ b/tests/unit/higgs_audio_v3/test_higgs_audio_v3.py @@ -7,6 +7,7 @@ without requiring the actual checkpoint or GPU. """ import asyncio +import json import time from collections import OrderedDict, defaultdict from types import SimpleNamespace @@ -1309,7 +1310,10 @@ class TestVoiceCloneReferenceCache: parent = tmp_path / "OmniVoice" subdir = parent / "audio_tokenizer" subdir.mkdir(parents=True) - (subdir / "config.json").write_text("{}", encoding="utf-8") + (subdir / "config.json").write_text( + json.dumps({"model_type": "higgs_audio_v2_tokenizer"}), + encoding="utf-8", + ) monkeypatch.setenv("HIGGS_AUDIO_TOKENIZER_PATH", str(parent)) assert tok._resolve_audio_tokenizer_dir() == str(subdir) @@ -1317,6 +1321,32 @@ class TestVoiceCloneReferenceCache: monkeypatch.setenv("HIGGS_AUDIO_TOKENIZER_PATH", str(subdir)) assert tok._resolve_audio_tokenizer_dir() == str(subdir) + def test_audio_tokenizer_dir_rejects_omnivoice_config(self, tmp_path, monkeypatch): + import huggingface_hub + import huggingface_hub.constants as hub_constants + + from vllm_omni.model_executor.models.higgs_audio_v2 import higgs_audio_v2_tokenizer as tok + + parent = tmp_path / "OmniVoice" + subdir = parent / "audio_tokenizer" + subdir.mkdir(parents=True) + (subdir / "config.json").write_text( + json.dumps({"model_type": "omnivoice"}), + encoding="utf-8", + ) + + monkeypatch.setenv("HIGGS_AUDIO_TOKENIZER_PATH", str(parent)) + monkeypatch.setenv("HIGGS_AUDIO_V2_TOKENIZER_PATH", "") + monkeypatch.setattr( + huggingface_hub, + "try_to_load_from_cache", + lambda **_: str(subdir / "config.json"), + ) + monkeypatch.setattr(hub_constants, "HF_HUB_CACHE", str(tmp_path / "empty_cache")) + + assert tok._normalize_audio_tokenizer_dir(str(parent)) is None + assert tok._resolve_audio_tokenizer_dir() is None + def test_higgs_v3_ref_code_cache_returns_clone(self): from vllm_omni.entrypoints.openai.serving_speech import OmniOpenAIServingSpeech diff --git a/vllm_omni/model_executor/models/higgs_audio_v2/higgs_audio_v2_tokenizer.py b/vllm_omni/model_executor/models/higgs_audio_v2/higgs_audio_v2_tokenizer.py old mode 100644 new mode 100755 index 960f6cfef..c3f5024b3 --- a/vllm_omni/model_executor/models/higgs_audio_v2/higgs_audio_v2_tokenizer.py +++ b/vllm_omni/model_executor/models/higgs_audio_v2/higgs_audio_v2_tokenizer.py @@ -22,6 +22,7 @@ generation. from __future__ import annotations +import json import os import re from typing import Any @@ -170,14 +171,25 @@ _AUDIO_TOKENIZER_PATH_ENVS = ( ) +def _is_higgs_audio_tokenizer_config(config_path: str) -> bool: + try: + with open(config_path, encoding="utf-8") as f: + config = json.load(f) + except (OSError, json.JSONDecodeError): + return False + return config.get("model_type") == "higgs_audio_v2_tokenizer" + + def _normalize_audio_tokenizer_dir(path: str) -> str | None: if not path: return None expanded = os.path.abspath(os.path.expanduser(path)) - if os.path.isfile(os.path.join(expanded, "config.json")): + config_path = os.path.join(expanded, "config.json") + if os.path.isfile(config_path) and _is_higgs_audio_tokenizer_config(config_path): return expanded nested = os.path.join(expanded, _K2_OMNIVOICE_SUBDIR) - if os.path.isfile(os.path.join(nested, "config.json")): + nested_config_path = os.path.join(nested, "config.json") + if os.path.isfile(nested_config_path) and _is_higgs_audio_tokenizer_config(nested_config_path): return nested return None @@ -201,7 +213,9 @@ def _resolve_audio_tokenizer_dir() -> str | None: filename=f"{_K2_OMNIVOICE_SUBDIR}/config.json", ) if isinstance(cached_config, str) and os.path.isfile(cached_config): - return os.path.dirname(cached_config) + candidate = _normalize_audio_tokenizer_dir(os.path.dirname(cached_config)) + if candidate is not None: + return candidate from huggingface_hub.constants import HF_HUB_CACHE @@ -240,7 +254,9 @@ def _load_audio_tokenizer(): _K2_OMNIVOICE_REPO, allow_patterns=[f"{_K2_OMNIVOICE_SUBDIR}/*"], ) - audio_tokenizer_dir = os.path.join(repo_path, _K2_OMNIVOICE_SUBDIR) + audio_tokenizer_dir = _normalize_audio_tokenizer_dir(repo_path) + if audio_tokenizer_dir is None: + raise RuntimeError(f"Downloaded {_K2_OMNIVOICE_REPO} does not contain a valid Higgs audio tokenizer") device = current_omni_platform.get_torch_device() model = HiggsAudioV2TokenizerModel.from_pretrained(audio_tokenizer_dir).to(device) return model.eval()