240 lines
8.4 KiB
Markdown
240 lines
8.4 KiB
Markdown
# GLM-TTS for Chinese/English TTS
|
|
|
|
## Summary
|
|
|
|
- Vendor: zai-org
|
|
- Model: `zai-org/GLM-TTS`
|
|
- Task: Zero-shot voice-cloned text-to-speech synthesis
|
|
- Mode: Online serving with the OpenAI-compatible `/v1/audio/speech` API
|
|
- Maintainer: Community
|
|
|
|
## When to use this recipe
|
|
|
|
Use this recipe to serve GLM-TTS as a two-stage TTS system (AR + DiT
|
|
flow-matching) for Chinese and English speech synthesis. Every request is
|
|
conditioned on reference audio and its transcript.
|
|
|
|
## References
|
|
|
|
- Upstream or canonical docs:
|
|
[zai-org/GLM-TTS on HuggingFace](https://huggingface.co/zai-org/GLM-TTS)
|
|
- GitHub repository:
|
|
[zai-org/GLM-TTS](https://github.com/zai-org/GLM-TTS)
|
|
- Related example under `examples/`:
|
|
[`examples/online_serving/text_to_speech/README.md#glm-tts`](../../examples/online_serving/text_to_speech/README.md#glm-tts)
|
|
- Offline inference example:
|
|
[`examples/offline_inference/text_to_speech/README.md#glm-tts`](../../examples/offline_inference/text_to_speech/README.md#glm-tts)
|
|
|
|
## Hardware Support
|
|
|
|
### GPU
|
|
|
|
### 1x A40 48GB
|
|
|
|
#### Environment
|
|
|
|
- OS: Linux
|
|
- Python: 3.10+
|
|
- Driver / runtime: NVIDIA CUDA environment with A40 48GB GPU
|
|
- vLLM version: Match the repository requirements for your checkout
|
|
- vLLM-Omni version or commit: Use the commit you are deploying from
|
|
|
|
#### Command
|
|
|
|
Start the server from the repository root:
|
|
|
|
```bash
|
|
vllm serve zai-org/GLM-TTS --omni --trust-remote-code --port 8091
|
|
```
|
|
|
|
Async chunking is enabled by default in the bundled deployment config. For
|
|
the sync (non-streaming) path:
|
|
|
|
```bash
|
|
vllm serve zai-org/GLM-TTS --omni --trust-remote-code --port 8091 --no-async-chunk
|
|
```
|
|
|
|
Use a custom deploy config for advanced cases:
|
|
|
|
```bash
|
|
vllm serve zai-org/GLM-TTS --omni --trust-remote-code --port 8091 \
|
|
--deploy-config /path/to/your_glm_tts_overrides.yaml
|
|
```
|
|
|
|
#### Verification
|
|
|
|
Run the bundled OpenAI-compatible client with reference audio:
|
|
|
|
```bash
|
|
python examples/online_serving/text_to_speech/glm_tts/openai_speech_client.py \
|
|
--text "你好,这是一个语音合成测试。" \
|
|
--ref-audio file:///path/to/ref.wav \
|
|
--ref-text "这是参考音频的文本内容。"
|
|
```
|
|
|
|
For a quick API smoke test:
|
|
|
|
```bash
|
|
curl http://localhost:8091/v1/audio/speech \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"model": "zai-org/GLM-TTS",
|
|
"input": "你好,这是一个语音合成测试。",
|
|
"response_format": "wav",
|
|
"ref_audio": "file:///path/to/ref.wav",
|
|
"ref_text": "这是参考音频的文本内容。"
|
|
}' --output test.wav
|
|
```
|
|
|
|
Voice cloning with reference audio:
|
|
|
|
```bash
|
|
python examples/online_serving/text_to_speech/glm_tts/openai_speech_client.py \
|
|
--text "你好,这是语音克隆测试。" \
|
|
--ref-audio file:///path/to/ref.wav \
|
|
--ref-text "这是参考音频的文本内容。"
|
|
```
|
|
|
|
#### Notes
|
|
|
|
- Hardware scope: the default bundled config is CUDA-only and verified on 1x A40 48GB (~16.6 GiB peak); fits 24GB cards. Split stages across GPUs for higher concurrency.
|
|
- Memory usage: ~18-20GB total (AR ~10GB, DiT ~8GB); both stages share GPU 0 by default.
|
|
- Audio output: 24kHz mono WAV via HiFT vocoder (Vocos2D 32kHz fallback with resampling).
|
|
- Key flags: `--omni` is required; `--trust-remote-code` is needed for the GLM-TTS phoneme tokenizer; the DiT stage enables bucketed model-internal CUDA graphs with eager fallback.
|
|
- Voice cloning: requires `ref_audio` + `ref_text` together. Reference audio should be 3-10 seconds. Feature extraction (WhisperVQ tokenizer, CampPlus ONNX, mel) runs on the model side.
|
|
- Known limitations: First request may be slow due to lazy model loading (WhisperVQ, CampPlus ONNX). Warm-cache RTF is approximately 0.6-0.7x on A40.
|
|
|
|
### 2x non-standard RTX 4090 48GB
|
|
|
|
This configuration was personally validated on two non-standard RTX 4090 cards
|
|
reporting 49,140 MiB each in `nvidia-smi`. Stock 24GB RTX 4090 cards were not
|
|
validated.
|
|
|
|
#### Environment
|
|
|
|
- OS: Ubuntu 22.04.1 LTS
|
|
- NVIDIA driver: 580.76.05
|
|
- Python: 3.12.13
|
|
- PyTorch: 2.11.0+cu130
|
|
- CUDA runtime and JIT toolkit: 13.0
|
|
- vLLM: 0.26.0
|
|
- vLLM-Omni: commit `66a9b0c8`, version
|
|
`0.26.0rc2.dev5+g66a9b0c84`
|
|
- FlashInfer: 0.6.14
|
|
|
|
The host's default `nvcc` was 11.8. The working CUDA toolkit came from the
|
|
Python environment, so `CUDA_HOME` was set to
|
|
`$VIRTUAL_ENV/lib/python3.12/site-packages/nvidia/cu13`.
|
|
|
|
#### Command
|
|
|
|
From the repository root, start the async-chunk server with:
|
|
|
|
```bash
|
|
export CUDA_HOME="$VIRTUAL_ENV/lib/python3.12/site-packages/nvidia/cu13"
|
|
export PATH="$CUDA_HOME/bin:$PATH"
|
|
export LD_LIBRARY_PATH="$CUDA_HOME/lib:${LD_LIBRARY_PATH:-}"
|
|
|
|
CUDA_VISIBLE_DEVICES=0,1 vllm-omni serve zai-org/GLM-TTS \
|
|
--omni \
|
|
--trust-remote-code \
|
|
--port 8091 \
|
|
--deploy-config vllm_omni/deploy/glm_tts.yaml \
|
|
--stage-overrides '{"1":{"devices":"1"}}' \
|
|
--allowed-local-media-path "${PWD}/tests/assets"
|
|
```
|
|
|
|
Stage 0 runs on GPU 0, while the override places stage 1 on GPU 1. For the sync
|
|
variant, add `--no-async-chunk` to the same command.
|
|
|
|
#### Verification
|
|
|
|
Run the Chinese voice-cloning check:
|
|
|
|
```bash
|
|
python examples/online_serving/text_to_speech/glm_tts/openai_speech_client.py \
|
|
--model zai-org/GLM-TTS \
|
|
--text "今天天气很好,我们一起学习语音模型推理。" \
|
|
--ref-audio "file://${PWD}/tests/assets/glm_tts/jiayan_zh.wav" \
|
|
--ref-text "他当时还跟线下其他的站姐吵架,然后,打架进局子了。" \
|
|
--response-format wav \
|
|
--output glm_tts_zh.wav
|
|
```
|
|
|
|
Run the English voice-cloning check with the same Chinese reference:
|
|
|
|
```bash
|
|
python examples/online_serving/text_to_speech/glm_tts/openai_speech_client.py \
|
|
--model zai-org/GLM-TTS \
|
|
--text "Hello, this is a voice cloning test with GLM-TTS." \
|
|
--ref-audio "file://${PWD}/tests/assets/glm_tts/jiayan_zh.wav" \
|
|
--ref-text "他当时还跟线下其他的站姐吵架,然后,打架进局子了。" \
|
|
--response-format wav \
|
|
--output glm_tts_en.wav
|
|
```
|
|
|
|
Inspect either output:
|
|
|
|
```bash
|
|
ffprobe -v error -select_streams a:0 \
|
|
-show_entries stream=codec_name,sample_rate,channels \
|
|
-of default=noprint_wrappers=1 glm_tts_zh.wav
|
|
```
|
|
|
|
Expected output:
|
|
|
|
```text
|
|
codec_name=pcm_s16le
|
|
sample_rate=24000
|
|
channels=1
|
|
```
|
|
|
|
The English smoke test produced a valid WAV with an actual duration of 5.020
|
|
seconds.
|
|
|
|
#### Observed warm-cache results
|
|
|
|
These observations are from one controlled run, not a hardware or performance
|
|
guarantee. Cold-start and JIT costs were excluded. Values are mean +/- standard
|
|
deviation.
|
|
|
|
##### Measurement procedure
|
|
|
|
- For the single-GPU server, use the documented command with
|
|
`CUDA_VISIBLE_DEVICES=0` and omit `--stage-overrides`; both bundled stages
|
|
remain on GPU 0. For the dual-GPU server, use the command as shown.
|
|
- Async chunking is the default. Add `--no-async-chunk` for sync mode.
|
|
- For non-streaming measurements, use the exact Chinese client command above.
|
|
For streaming, add `--stream` to that command and change the output filename
|
|
to `glm_tts_zh.pcm`; streaming output is raw PCM, not WAV.
|
|
- The fixed Chinese prompt and reference were exactly those shown in
|
|
Verification and yielded 3.900 seconds of audio. Each case used one warm-up
|
|
followed by three measured requests at concurrency 1.
|
|
- Wall time starts immediately before the HTTP POST and stops after the complete
|
|
response body. TTFA runs from immediately before the POST to the first
|
|
non-empty audio bytes. RTF is wall time divided by audio duration.
|
|
- Peak total device memory used was sampled every 0.2 seconds and reported by
|
|
NVML via `nvmlDeviceGetMemoryInfo.used`. The readings include background and
|
|
baseline use and are not baseline-subtracted.
|
|
|
|
| GPUs | Mode | Response | Wall time (s) | TTFA (s) | RTF |
|
|
|---:|---|---|---:|---:|---:|
|
|
| 1 | Async | Non-streaming | 2.144 +/- 0.076 | -- | 0.550 +/- 0.019 |
|
|
| 2 | Async | Non-streaming | 1.969 +/- 0.130 | -- | 0.505 +/- 0.033 |
|
|
| 1 | Async | Streaming | 2.135 +/- 0.093 | 1.245 +/- 0.178 | 0.548 +/- 0.024 |
|
|
| 2 | Async | Streaming | 1.891 +/- 0.006 | 1.170 +/- 0.042 | 0.485 +/- 0.002 |
|
|
| 1 | Sync | Non-streaming | 1.822 +/- 0.016 | -- | 0.467 +/- 0.004 |
|
|
| 2 | Sync | Non-streaming | 1.792 +/- 0.023 | -- | 0.459 +/- 0.006 |
|
|
|
|
Moving stage 1 to GPU 1 reduced async non-streaming wall time by 8.2%. It
|
|
reduced async streaming wall time by 11.4% and TTFA by 6.0%, and reduced sync
|
|
wall time by 1.6%. Single- and dual-GPU outputs were bitwise identical only
|
|
within matching execution and response modes.
|
|
|
|
| Configuration | GPU 0 | GPU 1 |
|
|
|---|---:|---:|
|
|
| Single-GPU async | 31.52 GiB | -- |
|
|
| Dual-GPU async | 29.23 GiB | 2.91 GiB |
|
|
| Single-GPU sync | 31.47 GiB | -- |
|
|
| Dual-GPU sync | 29.23 GiB | 2.91 GiB |
|