Compare commits

...

7 Commits

Author SHA1 Message Date
94738a2c00 docs: changelog for v6.15.1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 00:25:38 +02:00
c40ec9f15a docs: image tags v6.15.0 now on Docker Hub
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 00:23:31 +02:00
819a56888a docs: add audio player page and link table for German samples
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 00:23:16 +02:00
187051c67f chore: move German sample files to samples/
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 00:18:45 +02:00
60f0e73b71 docs: add German 0.6B streaming sample files
Ten voices from the active library, rendered with the 0.6B-Base streaming
server (v6.15.0, TTS_COMPILE_PREDICTOR=1) for the German pronunciation question.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 00:17:23 +02:00
4049c44809 docs: changelog and benchmark notes for v6.15.0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 23:35:08 +02:00
ecd833cb89 feat: opt-in torch.compile of the code predictor loop (TTS_COMPILE_PREDICTOR=1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 19:18:31 +02:00
15 changed files with 165 additions and 4 deletions

View File

@ -24,8 +24,8 @@ RUN git clone https://github.com/andimarafioti/faster-qwen3-tts.git /app && \
cd /app && git checkout --detach ${FASTER_QWEN3_TTS_REF}
# Apply DGX Spark patches (non_streaming_mode=True, per-voice temperature/top_k/top_p)
COPY patches/openai_server.patch /tmp/
RUN cd /app && git apply /tmp/openai_server.patch
COPY patches/openai_server.patch patches/predictor_compile.patch /tmp/
RUN cd /app && git apply /tmp/openai_server.patch && git apply /tmp/predictor_compile.patch
# Create virtual environment (Ubuntu 24.04 enforces PEP 668)
ENV VIRTUAL_ENV=/opt/venv

View File

@ -19,8 +19,8 @@ One Docker image covers all four backends, with two semantic tag aliases:
| Tag | Use for |
|---|---|
| `:latest` / `:v6` / `:v6.13.1` | VoiceClone, VoiceDesign, CustomVoice |
| `:latest-streaming` / `:v6-streaming` / `:v6.13.1-streaming` | Streaming VoiceClone |
| `:latest` / `:v6` / `:v6.15.0` | VoiceClone, VoiceDesign, CustomVoice |
| `:latest-streaming` / `:v6-streaming` / `:v6.15.0-streaming` | Streaming VoiceClone |
Both tags point to the same image — the `-streaming` suffix is a semantic convention so compose files and version pins are unambiguous.
@ -356,6 +356,7 @@ The first request after container startup can be slower because CUDA graph captu
- The forum playbook shows the four API containers running together on DGX Spark with low visible memory pressure, but exact usage depends on model size, sequence length, and warmup state.
- The 0.6B Qwen3-TTS variants are lighter, but their speaker embeddings (1024-dim) are incompatible with the 1.7B ones (2048-dim). Set `EMBED_TAG=0.6b` (and `EMBED_TAG=1.7b` on the 1.7B services) and mount `/voices` read-write: each voice then gets `<name>.<tag>.pt` beside its WAV and both sizes can share one `voices.json` (see `docker/docker-compose.portainer-0.6b.yml`).
- Measured on GB10 with other GPU services running (streaming PCM, first request excluded): 0.6B-Base TTFA ~570 ms, RTF 0.53–0.60 (1.7–1.9× real-time); 1.7B-Base TTFA ~820 ms, RTF 0.78–0.84 (1.2–1.3× real-time). The first request after a start is much slower while graphs/embeddings are built.
- Optional: `TTS_COMPILE_PREDICTOR=1` (image v6.15.0+) fuses the code predictor for ~10 % more throughput; see the v6.15.0 changelog entry.
- Throughput is not limited by memory pressure or GPU clocks (re-measured with the other large GPU model stopped: identical). The streaming process runs at ~100 % of one CPU core at ~32 W GPU power, so per-step launch/Python overhead is the suspected limit (not profiled yet). Lowering latency in practice: use the 0.6B stream with `response_format: "pcm"` and send one or two sentences per request.
- `--max-seq-len 2048` handles most sentence-style TTS requests. Long-form narration may need `4096`, with more memory required.
- Pin services to different GPUs with `NVIDIA_VISIBLE_DEVICES=0`, `NVIDIA_VISIBLE_DEVICES=1`, and so on if your system has more than one GPU.
@ -380,6 +381,20 @@ The first request after container startup can be slower because CUDA graph captu
## Changelog
### v6.15.1 — 2026-10-12
- **Docs:** German sample files (10 voices, 0.6B-Base streaming, same text) in `samples/german sample files/` with an `index.html` that has an audio player per voice (open locally or via GitHub Pages) and a link table, added for the German-pronunciation question in the forum.
- **Docs:** image tag table now lists `v6.15.0`; the `v6.15.0` images and the `:v6`/`:latest` aliases are on Docker Hub.
- **Image:** unchanged (`:v6.15.0`), no rebuild needed.
- **Rollback:** `git checkout v6.15.0` (documentation only).
### v6.15.0 — 2026-10-11
- **Perf (opt-in):** `TTS_COMPILE_PREDICTOR=1` compiles the 15-step code-predictor loop with `torch.compile` (Inductor/Triton) before CUDA-graph capture. Profiling showed the predictor needs ~5350 kernels per frame (about 54 % of the decode time); fusing them cuts its replay from ~23 ms to ~17.5 ms. Measured through the API on GB10 with the 0.6B-Base streaming server (same voice and text, other TTS containers stopped): 1.6–1.7x -> 1.8x real-time on long text, first audio ~670 ms -> ~550 ms. Listening comparison of 5 voices: no audible difference. Costs about one extra minute at startup. Default is off (unchanged behaviour).
- **Image:** rebuilt as `v6.15.0` / `v6.15.0-streaming` (adds `patches/predictor_compile.patch`) and pushed to Docker Hub together with the `:v6`, `:latest`, `:v6-streaming` and `:latest-streaming` aliases.
- **Investigation notes:** memory pressure/swap, GPU clocks, CPU core type and `chunk_size` do not limit throughput; `float16` fails (inf/NaN logits, device-side assert); flash-attn is irrelevant for single-token decode. F5-TTS, EmoKnob and Breeze-TTS-2 were benchmarked against Qwen3-TTS: slower than real-time (Breeze 0.5x, EmoKnob 0.1x) or without streaming/German (F5 ~5x) and not usable quality-wise in our test.
- **Rollback:** unset `TTS_COMPILE_PREDICTOR` (or use the `:v6.13.1` image).
### v6.14.1 — 2026-10-11
- **Docs:** measured GB10 benchmark numbers for 0.6B vs 1.7B, the finding that memory pressure and GPU clocks are not the bottleneck, and the corrected 0.6B guidance (`EMBED_TAG`) in "Performance and memory notes".

View File

@ -0,0 +1,25 @@
diff --git a/faster_qwen3_tts/predictor_graph.py b/faster_qwen3_tts/predictor_graph.py
index 38a52ca..70851eb 100644
--- a/faster_qwen3_tts/predictor_graph.py
+++ b/faster_qwen3_tts/predictor_graph.py
@@ -13,6 +13,8 @@ Strategy:
- Unroll the full 15-step loop for deterministic shapes
- Capture the entire loop as a single CUDA graph
"""
+import os
+
import torch
from transformers import StaticCache
from transformers.masking_utils import create_causal_mask, create_sliding_window_causal_mask
@@ -175,6 +177,11 @@ class PredictorGraph:
self._init_cache_layers()
self._build_attention_masks()
+ # Opt-in: fuse the 15-step predictor loop with Inductor before graph capture.
+ # ~17% faster streaming on GB10 (0.6B); adds ~1 min to the first warmup.
+ if os.environ.get("TTS_COMPILE_PREDICTOR") == "1":
+ self._full_loop = torch.compile(self._full_loop, dynamic=False)
+
for _ in range(num_warmup):
self.static_cache.reset()
self._full_loop()

View File

@ -0,0 +1,26 @@
# German sample files (0.6B-Base streaming)
Ten voices from the active library, rendered with the 0.6B-Base streaming server
(`v6.15.0`, `TTS_COMPILE_PREDICTOR=1`, `language: German`, default sampling).
All files use the same text:
> Guten Morgen! Das Wetter ist heute wunderbar, deshalb gehen wir am Rhein spazieren. Anschließend essen wir Käsekuchen und besprechen die Planung für das nächste Quartal, einschließlich Budget, Meeting und Zeitplan.
Files are in `mp3/` (24 kHz mono, VBR ~130 kbit/s), numbered 01-10 with the voice ID.
## Listen
GitHub's directory view has no audio player. Open `index.html` in this folder from a local clone (it has a player per voice), or enable GitHub Pages for the repository and open the same page there. Direct links to the files:
| # | Voice | File |
|---|---|---|
| 01 | DE_M_Gutfolg | [01_DE_M_Gutfolg.mp3](https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts/blob/main/samples/german%20sample%20files/mp3/01_DE_M_Gutfolg.mp3) |
| 02 | DE_Banker | [02_DE_Banker.mp3](https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts/blob/main/samples/german%20sample%20files/mp3/02_DE_Banker.mp3) |
| 03 | DE_N_Orkgeist | [03_DE_N_Orkgeist.mp3](https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts/blob/main/samples/german%20sample%20files/mp3/03_DE_N_Orkgeist.mp3) |
| 04 | DE_F_Amala | [04_DE_F_Amala.mp3](https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts/blob/main/samples/german%20sample%20files/mp3/04_DE_F_Amala.mp3) |
| 05 | DE_1_0 | [05_DE_1_0.mp3](https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts/blob/main/samples/german%20sample%20files/mp3/05_DE_1_0.mp3) |
| 06 | DE_F_19_German_Switzerland_Female_Leni | [06_DE_F_19_German_Switzerland_Female_Leni.mp3](https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts/blob/main/samples/german%20sample%20files/mp3/06_DE_F_19_German_Switzerland_Female_Leni.mp3) |
| 07 | DE_M_Rabe | [07_DE_M_Rabe.mp3](https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts/blob/main/samples/german%20sample%20files/mp3/07_DE_M_Rabe.mp3) |
| 08 | DE_N_Schwarzhaarige | [08_DE_N_Schwarzhaarige.mp3](https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts/blob/main/samples/german%20sample%20files/mp3/08_DE_N_Schwarzhaarige.mp3) |
| 09 | DE_N_Magier | [09_DE_N_Magier.mp3](https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts/blob/main/samples/german%20sample%20files/mp3/09_DE_N_Magier.mp3) |
| 10 | DE_M_Ork | [10_DE_M_Ork.mp3](https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts/blob/main/samples/german%20sample%20files/mp3/10_DE_M_Ork.mp3) |

View File

@ -0,0 +1,95 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>German samples - Qwen3-TTS 0.6B streaming</title>
<style>
:root { color-scheme: light dark; --bg:#fff; --fg:#1b1f24; --muted:#59636e; --line:#d1d9e0; }
@media (prefers-color-scheme: dark) { :root { --bg:#0d1117; --fg:#e6edf3; --muted:#9198a1; --line:#3d444d; } }
body { margin:0; padding:1.5rem 1rem; background:var(--bg); color:var(--fg); font:16px/1.5 system-ui,sans-serif; }
main { max-width:52rem; margin:0 auto; }
blockquote { margin:1rem 0; padding:.5rem 1rem; border-left:4px solid var(--line); color:var(--muted); }
.scroll { overflow-x:auto; }
table { border-collapse:collapse; width:100%; }
th, td { text-align:left; padding:.5rem; border-bottom:1px solid var(--line); vertical-align:middle; }
audio { width:100%; min-width:14rem; }
a { color:inherit; }
a:focus-visible, audio:focus-visible { outline:3px solid #0969da; outline-offset:2px; }
</style>
</head>
<body>
<main>
<h1>German samples: Qwen3-TTS 0.6B-Base streaming</h1>
<p>Ten voices from the active library, rendered with the 0.6B-Base streaming server (v6.15.0, <code>language: German</code>, default sampling). Every file uses the same text:</p>
<blockquote lang="de">Guten Morgen! Das Wetter ist heute wunderbar, deshalb gehen wir am Rhein spazieren. Anschließend essen wir Käsekuchen und besprechen die Planung für das nächste Quartal, einschließlich Budget, Meeting und Zeitplan.</blockquote>
<div class="scroll">
<table>
<thead><tr><th scope="col">#</th><th scope="col">Voice</th><th scope="col">Listen</th><th scope="col">File</th></tr></thead>
<tbody>
<tr>
<th scope="row">01</th>
<td>DE_M_Gutfolg</td>
<td><audio controls preload="none" src="mp3/01_DE_M_Gutfolg.mp3" aria-label="Sample 01: DE_M_Gutfolg"></audio></td>
<td><a href="mp3/01_DE_M_Gutfolg.mp3" download>mp3</a></td>
</tr>
<tr>
<th scope="row">02</th>
<td>DE_Banker</td>
<td><audio controls preload="none" src="mp3/02_DE_Banker.mp3" aria-label="Sample 02: DE_Banker"></audio></td>
<td><a href="mp3/02_DE_Banker.mp3" download>mp3</a></td>
</tr>
<tr>
<th scope="row">03</th>
<td>DE_N_Orkgeist</td>
<td><audio controls preload="none" src="mp3/03_DE_N_Orkgeist.mp3" aria-label="Sample 03: DE_N_Orkgeist"></audio></td>
<td><a href="mp3/03_DE_N_Orkgeist.mp3" download>mp3</a></td>
</tr>
<tr>
<th scope="row">04</th>
<td>DE_F_Amala</td>
<td><audio controls preload="none" src="mp3/04_DE_F_Amala.mp3" aria-label="Sample 04: DE_F_Amala"></audio></td>
<td><a href="mp3/04_DE_F_Amala.mp3" download>mp3</a></td>
</tr>
<tr>
<th scope="row">05</th>
<td>DE_1_0</td>
<td><audio controls preload="none" src="mp3/05_DE_1_0.mp3" aria-label="Sample 05: DE_1_0"></audio></td>
<td><a href="mp3/05_DE_1_0.mp3" download>mp3</a></td>
</tr>
<tr>
<th scope="row">06</th>
<td>DE_F_19_German_Switzerland_Female_Leni</td>
<td><audio controls preload="none" src="mp3/06_DE_F_19_German_Switzerland_Female_Leni.mp3" aria-label="Sample 06: DE_F_19_German_Switzerland_Female_Leni"></audio></td>
<td><a href="mp3/06_DE_F_19_German_Switzerland_Female_Leni.mp3" download>mp3</a></td>
</tr>
<tr>
<th scope="row">07</th>
<td>DE_M_Rabe</td>
<td><audio controls preload="none" src="mp3/07_DE_M_Rabe.mp3" aria-label="Sample 07: DE_M_Rabe"></audio></td>
<td><a href="mp3/07_DE_M_Rabe.mp3" download>mp3</a></td>
</tr>
<tr>
<th scope="row">08</th>
<td>DE_N_Schwarzhaarige</td>
<td><audio controls preload="none" src="mp3/08_DE_N_Schwarzhaarige.mp3" aria-label="Sample 08: DE_N_Schwarzhaarige"></audio></td>
<td><a href="mp3/08_DE_N_Schwarzhaarige.mp3" download>mp3</a></td>
</tr>
<tr>
<th scope="row">09</th>
<td>DE_N_Magier</td>
<td><audio controls preload="none" src="mp3/09_DE_N_Magier.mp3" aria-label="Sample 09: DE_N_Magier"></audio></td>
<td><a href="mp3/09_DE_N_Magier.mp3" download>mp3</a></td>
</tr>
<tr>
<th scope="row">10</th>
<td>DE_M_Ork</td>
<td><audio controls preload="none" src="mp3/10_DE_M_Ork.mp3" aria-label="Sample 10: DE_M_Ork"></audio></td>
<td><a href="mp3/10_DE_M_Ork.mp3" download>mp3</a></td>
</tr>
</tbody>
</table>
</div>
</main>
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.