Signed-off-by: Zhang <jianmusings@gmail.com> Signed-off-by: Zhang Jian <jianmusings@gmail.com> Signed-off-by: jian <jianmusings@gmail.com> Signed-off-by: Canlin Guo <961750412@qq.com> Co-authored-by: Canlin Guo <961750412@qq.com>
141 lines
4.9 KiB
Python
141 lines
4.9 KiB
Python
"""MOSS-TTSD dialogue dataset for ``vllm bench serve``.
|
|
|
|
Loads ``meta.lst`` rows of the form::
|
|
|
|
utt_id|ref1_wav_rel|ref2_wav_rel|dialogue_text
|
|
|
|
where ``dialogue_text`` contains ``[S1]`` / ``[S2]`` speaker tags. Both ref
|
|
wavs live under ``{root}/<locale>/prompt-wavs/``. Builds one
|
|
``SampleRequest`` per row with ``ref_audio`` (speaker 1) and ``ref_audio_2``
|
|
(speaker 2) inlined as ``data:`` URLs.
|
|
|
|
Companion to ``seed_tts_dataset.py``; same on-disk layout convention so the
|
|
existing ``--dataset-path`` flag works.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import random
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from vllm.benchmarks.datasets import SampleRequest
|
|
from vllm.tokenizers import TokenizerLike
|
|
from vllm.tokenizers.hf import get_cached_tokenizer
|
|
|
|
from vllm_omni.benchmarks.data_modules.seed_tts_dataset import (
|
|
SeedTTSDataset,
|
|
SeedTTSSampleRequest,
|
|
_ref_audio_payload,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class _TTSDRow:
|
|
utterance_id: str
|
|
ref1_rel: str
|
|
ref2_rel: str
|
|
target_text: str
|
|
|
|
|
|
def _parse_ttsd_line(line: str) -> _TTSDRow | None:
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
return None
|
|
parts = line.split("|")
|
|
if len(parts) < 4:
|
|
logger.warning("Skipping malformed TTSD meta line (need 4 '|'-fields): %r", line[:120])
|
|
return None
|
|
return _TTSDRow(
|
|
utterance_id=parts[0].strip(),
|
|
ref1_rel=parts[1].strip(),
|
|
ref2_rel=parts[2].strip(),
|
|
target_text=parts[3].strip(),
|
|
)
|
|
|
|
|
|
class TTSDDataset(SeedTTSDataset):
|
|
"""Two-speaker dialogue prompts for MOSS-TTSD-v1.0.
|
|
|
|
Reuses the SeedTTS root resolver so users can point ``--dataset-path`` at
|
|
a local directory or a HF repo with the same layout convention.
|
|
"""
|
|
|
|
DEFAULT_OUTPUT_LEN = 4096
|
|
|
|
def load_data(self) -> None:
|
|
meta = self._root / self.locale / "meta.lst"
|
|
if not meta.is_file():
|
|
raise FileNotFoundError(
|
|
f"TTSD meta not found: {meta} (expected layout: {self._root}/{self.locale}/meta.lst)"
|
|
)
|
|
text = meta.read_text(encoding="utf-8")
|
|
rows: list[_TTSDRow] = []
|
|
for line in text.splitlines():
|
|
r = _parse_ttsd_line(line)
|
|
if r is not None:
|
|
rows.append(r)
|
|
if not rows:
|
|
raise ValueError(f"No valid rows in {meta}")
|
|
if not self.disable_shuffle:
|
|
rng = random.Random(self.random_seed)
|
|
rng.shuffle(rows)
|
|
self._ttsd_rows = rows
|
|
# Parent's self._rows stays empty; sample() is overridden.
|
|
self._rows = []
|
|
self.data = self._ttsd_rows
|
|
logger.info("Loaded TTSD: root=%s locale=%s rows=%d", self._root, self.locale, len(rows))
|
|
|
|
def sample(
|
|
self,
|
|
tokenizer: TokenizerLike,
|
|
num_requests: int,
|
|
output_len: int | None = None,
|
|
request_id_prefix: str = "",
|
|
no_oversample: bool = False,
|
|
**kwargs: Any,
|
|
) -> list[SampleRequest]:
|
|
if output_len is None:
|
|
output_len = self.DEFAULT_OUTPUT_LEN
|
|
tok = get_cached_tokenizer(tokenizer)
|
|
out: list[SampleRequest] = []
|
|
for i, row in enumerate(self._ttsd_rows):
|
|
if len(out) >= num_requests:
|
|
break
|
|
wav1 = (self._root / self.locale / row.ref1_rel).resolve()
|
|
wav2 = (self._root / self.locale / row.ref2_rel).resolve()
|
|
if not wav1.is_file() or not wav2.is_file():
|
|
logger.warning("Missing TTSD refs for %s: %s / %s", row.utterance_id, wav1, wav2)
|
|
continue
|
|
ref1_uri = _ref_audio_payload(wav1, inline=self.inline_ref_audio)
|
|
ref2_uri = _ref_audio_payload(wav2, inline=self.inline_ref_audio)
|
|
speech_extra: dict[str, Any] = {
|
|
"ref_audio": ref1_uri,
|
|
"ref_audio_2": ref2_uri,
|
|
"max_new_tokens": output_len,
|
|
}
|
|
target = row.target_text
|
|
prompt_len = len(tok.encode(target))
|
|
out.append(
|
|
SeedTTSSampleRequest(
|
|
prompt=target,
|
|
prompt_len=prompt_len,
|
|
expected_output_len=output_len,
|
|
multi_modal_data=None,
|
|
request_id=f"{request_id_prefix}{i}",
|
|
seed_tts_speech_extra=speech_extra,
|
|
seed_tts_utterance_id=row.utterance_id,
|
|
seed_tts_locale=self.locale,
|
|
seed_tts_system_prompt=self._system_prompt,
|
|
# SIM computed against ref1 (speaker 1) for a stable signal;
|
|
# per-speaker SIM is left for offline eval.
|
|
seed_tts_ref_wav_path=str(wav1),
|
|
)
|
|
)
|
|
logger.info("TTSD: built %d requests (asked %d)", len(out), num_requests)
|
|
self.maybe_oversample_requests(out, num_requests, request_id_prefix, no_oversample)
|
|
return out
|