[Bugfix][Qwen3-Omni] Default the talker voice to Ethan (#8568)

Signed-off-by: Yueqian Lin <linyueqian@outlook.com>
This commit is contained in:
Yueqian Lin authored and GitHub committed 2026-10-07 17:07:53 -07:00
1 parent 1c71a2c03a
commit 6a70bda0f6
2 files changed
+58 -2

No files matched your search

@@ -0,0 +1,42 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Regression tests for the Qwen3-Omni default talker voice.
The released checkpoint lists ``talker_config.speaker_id`` as
``{"chelsie": 2301, "ethan": 2302, "aiden": 2303}``. The default voice used to
be the first key, so a request that named no speaker was spoken by Chelsie,
while the reference ``generate(speaker="Ethan")`` (and other servers) use Ethan.
Benchmarks that omit ``speaker`` then compared two different voices.
"""
import pytest
from vllm_omni.model_executor.models.qwen3_omni.qwen3_omni import (
DEFAULT_SPEAKER,
Qwen3OmniMoeForConditionalGeneration,
resolve_default_speaker,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
# Order as shipped in Qwen/Qwen3-Omni-30B-A3B-Instruct config.json.
CHECKPOINT_SPEAKER_IDS = {"chelsie": 2301, "ethan": 2302, "aiden": 2303}
def test_default_speaker_is_ethan_even_when_listed_second() -> None:
assert DEFAULT_SPEAKER == "ethan"
assert resolve_default_speaker(CHECKPOINT_SPEAKER_IDS) == "ethan"
def test_default_speaker_falls_back_to_first_key_without_ethan() -> None:
assert resolve_default_speaker({"chelsie": 1, "aiden": 2}) == "chelsie"
def test_unknown_or_missing_speaker_uses_ethan_token() -> None:
model = object.__new__(Qwen3OmniMoeForConditionalGeneration)
model.tts_text_spk_token_ids = dict(CHECKPOINT_SPEAKER_IDS)
model.default_tts_text_spk_type = resolve_default_speaker(model.tts_text_spk_token_ids)
assert model._get_text_spk_token_id("not-a-speaker") == CHECKPOINT_SPEAKER_IDS["ethan"]
assert model._get_text_spk_token_id(model.default_tts_text_spk_type) == CHECKPOINT_SPEAKER_IDS["ethan"]
assert model._get_text_spk_token_id("chelsie") == CHECKPOINT_SPEAKER_IDS["chelsie"]
@@ -67,6 +67,20 @@ from vllm_omni.model_executor.stage_input_processors.chunk_size_utils import par
from vllm_omni.platforms import current_omni_platform
from vllm_omni.utils.device_copy import to_device_nonblocking
# Voice used when a request names no speaker. Matches the reference
# ``Qwen3OmniMoeForConditionalGeneration.generate(speaker="Ethan")``; the
# checkpoint's ``talker_config.speaker_id`` lists "chelsie" first, so taking the
# first key would silently switch the default to a different (female) voice.
DEFAULT_SPEAKER = "ethan"
def resolve_default_speaker(speaker_token_ids: dict[str, int]) -> str:
"""Return the speaker key used for requests that do not name one."""
if DEFAULT_SPEAKER in speaker_token_ids:
return DEFAULT_SPEAKER
return next(iter(speaker_token_ids))
# Special token IDs for Qwen3 Omni MoE
# Reference: https://huggingface.co/Qwen/Qwen3-Omni-30B-A3B-Instruct/blob/main/tokenizer_config.json
@@ -552,7 +566,7 @@ class Qwen3OmniMoeForConditionalGeneration(
intermediate_tensors: IntermediateTensors | None = None,
inputs_embeds: torch.Tensor | None = None,
generate_audio: bool = True,
voice_type: str = "ethan",
voice_type: str = DEFAULT_SPEAKER,
codec: torch.Tensor | None = None,
sampling_metadata: SamplingMetadata | None = None,
logits_index: int | None = None,
@@ -963,7 +977,7 @@ class Qwen3OmniMoeForConditionalGeneration(
"prefix_caching": talker_hf_config.audio_start_token_id,
}
self.default_tts_text_spk_type = list(self.tts_text_spk_token_ids.keys())[0]
self.default_tts_text_spk_type = resolve_default_speaker(self.tts_text_spk_token_ids)
return set(["thinker_embedding.weight", "talker_embedding.weight"])