diff --git a/pyproject.toml b/pyproject.toml index 2584c8c7a..fc1d5573b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -376,6 +376,9 @@ ba = "ba" SER = "SER" # NumPy ndarray.flags.writeable (canonical flag name, not "writable"). writeable = "writeable" +# ARPAbet phoneme symbol (Fish Speech phoneme-control token example, e.g. +# "IY0"), not a typo of "IT". +IY = "IY" # "convnet" abbreviates convolutional network. It appears in the vendored Fish # Speech DAC codec identifiers (convnet_type, transconvnet_type) and is not a # typo of "convent". diff --git a/tests/model_executor/models/fish_speech/test_prompt_utils.py b/tests/model_executor/models/fish_speech/test_prompt_utils.py new file mode 100644 index 000000000..98700da26 --- /dev/null +++ b/tests/model_executor/models/fish_speech/test_prompt_utils.py @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project + +import pytest + +from vllm_omni.model_executor.models.fish_speech.prompt_utils import normalize_fish_speech_text + +pytestmark = [pytest.mark.core_model, pytest.mark.cpu] + + +def test_normalize_fish_speech_text_allows_phoneme_control_tokens(): + text = "Deploy with <|phoneme_start|>K UW2 B ER0 N EH1 T IY0 Z<|phoneme_end|>." + + assert normalize_fish_speech_text(text) == text + + +def test_normalize_fish_speech_text_rejects_unknown_control_token(): + with pytest.raises(ValueError, match=r"unsupported control token\(s\): <\|foo\|>"): + normalize_fish_speech_text("<|foo|>hello") + + +def test_normalize_fish_speech_text_normalizes_legacy_speaker_tag(): + assert normalize_fish_speech_text("hello") == "<|speaker:0|>hello" diff --git a/vllm_omni/model_executor/models/fish_speech/prompt_utils.py b/vllm_omni/model_executor/models/fish_speech/prompt_utils.py index 3918a6f94..61196f18f 100644 --- a/vllm_omni/model_executor/models/fish_speech/prompt_utils.py +++ b/vllm_omni/model_executor/models/fish_speech/prompt_utils.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project + """Shared Fish Speech prompt construction helpers.""" from __future__ import annotations @@ -11,6 +14,7 @@ FISH_CLONE_SYSTEM_PROMPT_SUFFIX = "\n\nSpeech:\n" _LEGACY_SPEAKER_TAG_PATTERN = re.compile(r"") _CANONICAL_SPEAKER_TAG_PATTERN = re.compile(r"<\|speaker:\d+\|>") +_PHONEME_TAG_PATTERN = re.compile(r"<\|phoneme_(?:start|end)\|>") _CONTROL_TOKEN_PATTERN = re.compile(r"<\|[^>]+\|>") @@ -21,7 +25,7 @@ def normalize_fish_speech_text(text: str, *, add_default_speaker: bool = False) disallowed_tokens = [ token for token in _CONTROL_TOKEN_PATTERN.findall(normalized) - if not _CANONICAL_SPEAKER_TAG_PATTERN.fullmatch(token) + if not (_CANONICAL_SPEAKER_TAG_PATTERN.fullmatch(token) or _PHONEME_TAG_PATTERN.fullmatch(token)) ] if disallowed_tokens: disallowed_list = ", ".join(sorted(set(disallowed_tokens)))