Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94738a2c00 | |||
| c40ec9f15a | |||
| 819a56888a | |||
| 187051c67f | |||
| 60f0e73b71 | |||
| 4049c44809 | |||
| ecd833cb89 |
@ -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
|
||||
|
||||
19
README.md
19
README.md
@ -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".
|
||||
|
||||
25
patches/predictor_compile.patch
Normal file
25
patches/predictor_compile.patch
Normal 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()
|
||||
26
samples/german sample files/README.md
Normal file
26
samples/german sample files/README.md
Normal 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) |
|
||||
95
samples/german sample files/index.html
Normal file
95
samples/german sample files/index.html
Normal 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>
|
||||
BIN
samples/german sample files/mp3/01_DE_M_Gutfolg.mp3
Normal file
BIN
samples/german sample files/mp3/01_DE_M_Gutfolg.mp3
Normal file
Binary file not shown.
BIN
samples/german sample files/mp3/02_DE_Banker.mp3
Normal file
BIN
samples/german sample files/mp3/02_DE_Banker.mp3
Normal file
Binary file not shown.
BIN
samples/german sample files/mp3/03_DE_N_Orkgeist.mp3
Normal file
BIN
samples/german sample files/mp3/03_DE_N_Orkgeist.mp3
Normal file
Binary file not shown.
BIN
samples/german sample files/mp3/04_DE_F_Amala.mp3
Normal file
BIN
samples/german sample files/mp3/04_DE_F_Amala.mp3
Normal file
Binary file not shown.
BIN
samples/german sample files/mp3/05_DE_1_0.mp3
Normal file
BIN
samples/german sample files/mp3/05_DE_1_0.mp3
Normal file
Binary file not shown.
Binary file not shown.
BIN
samples/german sample files/mp3/07_DE_M_Rabe.mp3
Normal file
BIN
samples/german sample files/mp3/07_DE_M_Rabe.mp3
Normal file
Binary file not shown.
BIN
samples/german sample files/mp3/08_DE_N_Schwarzhaarige.mp3
Normal file
BIN
samples/german sample files/mp3/08_DE_N_Schwarzhaarige.mp3
Normal file
Binary file not shown.
BIN
samples/german sample files/mp3/09_DE_N_Magier.mp3
Normal file
BIN
samples/german sample files/mp3/09_DE_N_Magier.mp3
Normal file
Binary file not shown.
BIN
samples/german sample files/mp3/10_DE_M_Ork.mp3
Normal file
BIN
samples/german sample files/mp3/10_DE_M_Ork.mp3
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user