Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
5ac3be0
sdk: reference audio for client.tts voice cloning
merceod Sep 18, 2026
48c06d9
test: client.tts forwards voice and reference audio
merceod Sep 18, 2026
a796d26
qwen3_tts: variant-aware config with speaker-encoder settings
merceod Sep 18, 2026
28a34d9
qwen3_tts: project Talker-width inputs into the code predictor
merceod Sep 18, 2026
3259e93
qwen3_tts: build prefill with instruct, optional speaker and both tex…
merceod Sep 18, 2026
cbb27df
qwen3_tts: serve CustomVoice, VoiceDesign and Base from one class
merceod Sep 18, 2026
83c160b
Register the Qwen3-TTS 1.7B variants
merceod Sep 18, 2026
5092cd4
cli: default configs and hints for the Qwen3-TTS 1.7B variants
merceod Sep 18, 2026
d63c7c9
Add the Qwen3-TTS 1.7B CustomVoice deployment config
merceod Sep 18, 2026
297ebed
Add the Qwen3-TTS VoiceDesign deployment config
merceod Sep 18, 2026
db2af3d
Add the Qwen3-TTS Base deployment config
merceod Sep 18, 2026
13022b7
api: OpenAI speech adapter for Qwen3-TTS
merceod Sep 18, 2026
f6410a3
benchmark: Qwen3-TTS 1.7B and VoiceDesign model entries
merceod Sep 18, 2026
3a87ece
benchmark: route Qwen3-TTS through /v1/audio/speech
merceod Sep 18, 2026
8f32c65
test: Qwen3-TTS variants, prefill layouts and predictor projection
merceod Sep 18, 2026
20db9ee
test: Qwen3-TTS speech adapter
merceod Sep 18, 2026
1b66b1e
test: Qwen3-TTS 1.7B real-weight loading checks
merceod Sep 18, 2026
6ba7e92
benchmark: streaming /v1/audio/speech client shared by all TTS engines
merceod Sep 18, 2026
b4c4fa6
benchmark: WER scorer for synthesized speech
merceod Sep 18, 2026
ce08c3d
test: Qwen3-TTS parity harness against the qwen-tts reference
merceod Sep 18, 2026
02799bf
qwen3_tts: ECAPA-TDNN speaker encoder and mel front end components
merceod Sep 18, 2026
12e5f87
test: Qwen3-TTS speaker encoder parity against qwen-tts
merceod Sep 18, 2026
124fb13
qwen3_tts: codec encoder settings and reference frame arithmetic
merceod Sep 18, 2026
b708a57
qwen3_tts: reference encoder node, in-context clone prefill, codec tr…
merceod Sep 18, 2026
6439f21
qwen3_tts: Base voice-clone walks and CPU-built codec modules
merceod Sep 18, 2026
9b3453e
api: reference audio for Qwen3-TTS speech requests
merceod Sep 18, 2026
6d3e80b
configs: map the Qwen3-TTS RefEncoder node
merceod Sep 18, 2026
29722cc
test: Qwen3-TTS voice-clone prompt, prefill and codec trimming
merceod Sep 18, 2026
7cc5872
test: Qwen3-TTS reference audio through the speech adapter
merceod Sep 18, 2026
c0bdd17
test: float32 reference codec in the Qwen3-TTS parity harness
merceod Sep 18, 2026
82cc576
docs: Qwen3-TTS 1.7B variants, voice cloning and benchmark client
merceod Sep 18, 2026
f327647
examples: Qwen3-TTS SDK usage for the three variants
merceod Sep 18, 2026
6e65cb5
test: Qwen3-TTS rejects reference audio on non-Base checkpoints
merceod Sep 18, 2026
f60d139
streaming: scheduled left-context chunk policy for early first audio
merceod Sep 18, 2026
c5a123e
streaming: report each chunk's already-delivered context items
merceod Sep 18, 2026
cd61dfe
graph: carry stream chunk offset and context on synthetic edges
merceod Sep 18, 2026
3534f50
worker: expose stream chunk geometry to consumers via step_metadata
merceod Sep 18, 2026
796651b
test: scheduled chunk policy and chunk context geometry
merceod Sep 18, 2026
e1b8584
qwen3_tts: ramped codec chunk schedule with 25-frame left context
merceod Sep 18, 2026
f4940f7
qwen3_tts: stream codec frames through the scheduled chunk policy
merceod Sep 18, 2026
616ab0f
qwen3_tts: codec windows padded to captured buckets, context from the…
merceod Sep 18, 2026
9b36195
test: Qwen3-TTS codec ramp, bucket padding and context trimming
merceod Sep 18, 2026
f27ce58
docs: Qwen3-TTS codec chunk ramp and capture buckets
merceod Sep 18, 2026
ae11671
qwen3_tts: reference encoder keeps its float32 front ends under autocast
merceod Sep 18, 2026
7c22796
test: voice-clone mode in the Qwen3-TTS parity harness
merceod Sep 18, 2026
29691b5
qwen3_tts: advertise the codec clone walk only on Base checkpoints
merceod Sep 18, 2026
81c0514
test: codec graph configs replay the clone walk only for Base
merceod Sep 18, 2026
52b047e
qwen3_tts: pick the codec capture bucket from stream metadata
merceod Sep 18, 2026
f4ac432
test: codec capture key from stream metadata
merceod Sep 18, 2026
22013c4
streaming: report each chunk's item count
merceod Sep 18, 2026
f7d58e8
graph: carry the stream chunk item count on synthetic edges
merceod Sep 18, 2026
3972df8
worker: include the chunk item count in stream_chunks metadata
merceod Sep 18, 2026
3818263
test: stream chunk item counts
merceod Sep 18, 2026
c1ef190
deps: soundfile in the qwen3_tts extra for reference audio
merceod Sep 18, 2026
36efac1
qwen3_tts: hand the codec its reference frame count from the first cl…
merceod Sep 18, 2026
bc5b6d3
test: codec clone walk receives ref_frames on its initial inputs
merceod Sep 18, 2026
752611e
api: sentence splitter for long text-to-speech inputs
merceod Sep 18, 2026
e234e94
api: per-adapter sentence-chunking thresholds for /v1/audio/speech
merceod Sep 18, 2026
afc57df
api: synthesize long speech inputs as ordered sentence chunks
merceod Sep 18, 2026
0b77838
test: sentence chunking on /v1/audio/speech
merceod Sep 18, 2026
bf73d67
api: sentence-chunk Qwen3-TTS speech inputs from 600 characters
merceod Sep 18, 2026
dacf98b
test: Qwen3-TTS speech chunking thresholds
merceod Sep 18, 2026
46562e6
docs: Qwen3-TTS sentence chunking on /v1/audio/speech
merceod Sep 18, 2026
bc4aa0f
test: parity harness applies the deployment's resource overrides
merceod Sep 18, 2026
d3b8e60
benchmark: render the TTS protocol tables from result JSONs
merceod Sep 18, 2026
9a8d8e2
test: end-to-end smoke test for a served Qwen3-TTS variant
merceod Sep 18, 2026
5b27bcd
test: parity harness tolerates specs without a config
merceod Sep 18, 2026
f5ac4e2
test: 1.7B real-weight layouts for the five-field prompt layout and a…
merceod Sep 18, 2026
5396fdb
qwen3_tts: load codec quantizer buffers and check coverage against th…
merceod Sep 18, 2026
316ac5a
test: VoiceDesign real-weight layout check with an explicit language
merceod Sep 18, 2026
3a9c156
test: parity harness keeps whole frames in the greedy loop
merceod Sep 18, 2026
33ada0c
test: parity verdict on confident-position agreement and relative log…
merceod Sep 18, 2026
e7866d5
benchmark: report confident agreement and relative logit error in the…
merceod Sep 18, 2026
61d6bfa
test: clone parity compares codec encoders in both dtypes and shares …
merceod Sep 18, 2026
0c4d7c0
api: streaming speech surfaces an up-front engine error as its HTTP s…
merceod Sep 18, 2026
edd23dc
test: streaming speech returns the engine error status
merceod Sep 18, 2026
b1e2913
test: parity reports token diversity and loudness of each side's gree…
merceod Sep 18, 2026
60e5961
qwen3_tts: memoise reference-clip conditioning by content
merceod Sep 18, 2026
9befd9b
test: reference-clip conditioning memo
merceod Sep 18, 2026
7dc9746
docs: reference clips are encoded once per distinct clip
merceod Sep 18, 2026
3171700
qwen3_tts: codec window geometry travels with the pass, not request s…
merceod Sep 18, 2026
9262ccb
test: codec trims each pass with its own window geometry
merceod Sep 18, 2026
5affa75
benchmark: order report rows by system and concurrency, trim the envi…
merceod Sep 18, 2026
22de57d
qwen3_tts: codec batches mixed windows and returns per-request sample…
merceod Sep 18, 2026
10b293b
test: codec mixed-window batching and single-forward output shape
merceod Sep 18, 2026
0ba9b8f
qwen3_tts: codec batches up to 32 windows
merceod Sep 18, 2026
36afc51
test: codec batch limit of 32
merceod Sep 18, 2026
fc54067
docs: codec graphs captured for batch sizes up to 32
merceod Sep 18, 2026
f55e16e
qwen3_tts: first codec window is a single frame
merceod Sep 18, 2026
7b3381e
qwen3_tts: graph docstring follows the 1-3-8-16 chunk ramp
merceod Sep 18, 2026
f342ff5
qwen3_tts: submodule header follows the 1-3-8-16 chunk ramp
merceod Sep 18, 2026
e8b7fca
docs: first audio after a single codec frame
merceod Sep 18, 2026
b4fff0b
test: smoke repeats a seeded sampled run for Base, greedy for the others
merceod Sep 18, 2026
45c7b15
benchmark: WER loads audio with soundfile, no ffmpeg needed
merceod Sep 18, 2026
9042454
conductor: optional rank_devices mapping places worker ranks on a sha…
merceod Sep 18, 2026
03cd277
docs: rank_devices for several workers on one GPU
merceod Sep 18, 2026
7b4a979
test: worker device mapping
merceod Sep 18, 2026
16f3320
qwen3_tts: deployment with the codec on a second worker sharing the GPU
merceod Sep 18, 2026
a44e180
benchmark: WER drives Whisper directly from 16 kHz arrays
merceod Sep 18, 2026
35ed040
qwen3_tts: talker prefill replays a packed CUDA graph
merceod Sep 18, 2026
27ebc0d
test: talker prefill graph capture config
merceod Sep 18, 2026
88479dd
docs: talker prefill is graph-captured
merceod Sep 18, 2026
58eca9a
qwen3_tts: codec_dtype deployment option (float32 default, bfloat16 o…
merceod Sep 18, 2026
44cdb5c
qwen3_tts: codec batch comment matches the 32-request ceiling
merceod Sep 18, 2026
fe6d9ad
docs: codec_dtype option
merceod Sep 18, 2026
ac890ef
test: codec_dtype parsing
merceod Sep 18, 2026
5fca373
qwen3_tts: clone stream carries only the reference tail the codec can…
merceod Sep 18, 2026
8c90721
test: clone stream reference tail and trimming
merceod Sep 18, 2026
2a8fa50
qwen3_tts: split deployment caps the KV pool for two workers on one GPU
merceod Sep 18, 2026
708f72d
docs: split deployment and CUDA MPS note
merceod Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions benchmark/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,15 +233,40 @@ def get_supported_modalities(self):


class Qwen3TTS(Model):
"""Qwen3-TTS CustomVoice benchmark metadata for native M* requests."""
"""Qwen3-TTS CustomVoice benchmark metadata (0.6B by default).

``/v1/audio/speech`` requests carry the same ``voice`` and ``language``
for every engine so the Talker prefill is identical across systems.
"""

HF_URL = "Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice"

def get_hf_url(self):
return "Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice"
return self.HF_URL

def get_model_kwargs(self, request_type: RequestType):
return {"voice": "vivian", "language": "English"}

def get_supported_modalities(self):
return {RequestType.T2S}


class Qwen3TTS1p7B(Qwen3TTS):
HF_URL = "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice"


class Qwen3TTSVoiceDesign(Qwen3TTS):
"""VoiceDesign has no built-in speakers; the voice is the instruction."""

HF_URL = "Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign"

def get_model_kwargs(self, request_type: RequestType):
return {
"language": "English",
"instructions": "A clear, friendly adult female voice with a neutral accent.",
}


class Pi05(Model):
"""Physical Intelligence Pi0.5 VLA model.

Expand Down Expand Up @@ -328,6 +353,8 @@ class ModelType(Enum):
ORPHEUS = "orpheus"
QWEN3OMNI = "qwen3omni"
QWEN3TTS = "qwen3_tts"
QWEN3TTS_1P7B = "qwen3_tts_1p7b"
QWEN3TTS_VOICEDESIGN = "qwen3_tts_voicedesign"
PI05 = "pi05"
VJEPA2AC = "vjepa2ac"
WHISPER_LARGE = "whisper_large"
Expand All @@ -342,6 +369,10 @@ def inst(self, **kwargs) -> Model:
return Qwen3Omni(**kwargs)
if self == ModelType.QWEN3TTS:
return Qwen3TTS(**kwargs)
if self == ModelType.QWEN3TTS_1P7B:
return Qwen3TTS1p7B(**kwargs)
if self == ModelType.QWEN3TTS_VOICEDESIGN:
return Qwen3TTSVoiceDesign(**kwargs)
if self == ModelType.PI05:
return Pi05(**kwargs)
if self == ModelType.VJEPA2AC:
Expand Down
4 changes: 2 additions & 2 deletions benchmark/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import aiohttp
import numpy as np

from benchmark.base import Bagel, Model, Orpheus, RequestType, Status
from benchmark.base import Bagel, Model, Orpheus, Qwen3TTS, RequestType, Status
from benchmark.utils import _write_wav


Expand Down Expand Up @@ -1494,7 +1494,7 @@ async def send_request(
metrics=metrics,
additional_model_kwargs=additional_model_kwargs,
)
if req_type.get_output_modalities() == "audio" and isinstance(model, Orpheus):
if req_type.get_output_modalities() == "audio" and isinstance(model, (Orpheus, Qwen3TTS)):
metrics = RequestMetrics(
request_id=request_id,
type=req_type,
Expand Down
100 changes: 100 additions & 0 deletions benchmark/tts_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Assemble the BENCHMARK_PROTOCOL.md TTS table from ``tts_speech_bench`` / ``tts_wer`` / parity JSONs.

python -m benchmark.tts_report --results results/2026-09-18 --out results/2026-09-18/REPORT.md

Every ``*_c<N>.json`` written by ``benchmark/tts_speech_bench.py`` becomes one row
(label, concurrency, TTFA p50/p95, RTF, audio-seconds per second, errors); a
sibling ``*_c<N>_wer.json`` from ``benchmark/tts_wer.py`` fills the WER column
and ``parity_*.json`` files from ``test/qwen3-tts/parity_qwen3_tts.py`` become
the parity table. The markdown is printed and optionally written to ``--out``.
"""

from __future__ import annotations

import argparse
import json
import re
from pathlib import Path


def _load(path: Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))


def benchmark_rows(results: Path) -> list[str]:
reports = [
_load(path) for path in sorted(results.glob("*_c[0-9]*.json"))
if not path.name.endswith("_wer.json")
]
rows = []
# one system's rows in concurrency order, systems alphabetically
for report in sorted(reports, key=lambda r: (r.get("label") or r["engine"], int(r["concurrency"]))):
path = results / f"{report.get('label') or report['engine']}_c{report['concurrency']}.json"
med = report["median_over_repeats"]
wer_path = path.with_name(path.stem + "_wer.json")
wer = f"{_load(wer_path)['wer_percent']:.2f}" if wer_path.is_file() else "n/a"
version = report.get("engine_version") or ""
label = report.get("label") or report["engine"]
rows.append(
f"| {label} {version} | {report['concurrency']} | "
f"{med['ttfa_p50_ms']:.0f} / {med['ttfa_p95_ms']:.0f} | {med['rtf_mean']:.3f} | "
f"{med['audio_s_per_wall_s']:.1f} | {wer} | {report['errors_total']} | "
f"{report['num_sentences']} sentences x {report['repeats']} repeats |"
)
return rows


def parity_rows(results: Path) -> list[str]:
rows = []
for path in sorted(results.glob("parity_*.json")):
r = _load(path)
clone = r.get("clone") or {}
clone_cell = (
f"cos {clone['xvector_cosine']:.4f}, codes {clone.get('ref_code_agreement', float('nan')):.3f}"
if clone else "-"
)
def agreement(report: dict) -> str:
confident = report.get("confident_agreement")
confident_cell = f"{confident:.4f}" if confident is not None else "n/a"
rel = report.get("rel_mean_diff", float("nan"))
return f"{report['argmax_agreement']:.4f} / {confident_cell} / {rel:.4f}"

rows.append(
f"| {r['repo'].split('/')[-1]} | {re.sub(r'^parity_', '', path.stem)} | {r['frames']} | "
f"{agreement(r['talker'])} | {agreement(r['code_predictor'])} | "
f"{r['greedy_codes']['identical_frames_before_divergence']}/{r['greedy_codes']['frames_compared']} | "
f"{r['codec']['max_abs_diff']:.2e} | {r['audio']['max_abs_diff']:.3f} | {clone_cell} |"
)
return rows


def main(argv: list[str] | None = None) -> None:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--results", required=True, help="directory with the benchmark / parity JSON files")
parser.add_argument("--out", default=None)
args = parser.parse_args(argv)
results = Path(args.results)

lines = ["## Benchmarks (H100, back to back, warmup excluded, median over repeats)", "",
"| System (version) | concurrency | TTFA p50 / p95 ms | RTF | audio-s / s | WER % | errors | notes |",
"|---|---|---|---|---|---|---|---|", *benchmark_rows(results), "",
"## Parity vs qwen-tts (greedy, bf16 Talker, fp32 codec)", "",
"| checkpoint | mode | frames | Talker argmax / confident agreement / rel. logit diff | "
"CodePredictor argmax / confident agreement / rel. logit diff | "
"identical greedy frames | codec max-abs-diff | greedy audio max-abs-diff | "
"clone (x-vector cosine, ref-code agreement) |",
"|---|---|---|---|---|---|---|---|---|", *parity_rows(results)]
env = results / "environment.txt"
if env.is_file():
# versions, GPU and clocks; the raw nvidia-smi clock dump that follows is left out
summary = env.read_text(encoding="utf-8").split("==============NVSMI LOG")[0].strip()
lines += ["", "## Environment", "", "```", summary, "```"]
text = "\n".join(lines) + "\n"
print(text)
if args.out:
Path(args.out).write_text(text, encoding="utf-8")


if __name__ == "__main__":
main()
Loading
Loading