A Rust implementation of Qwen3-TTS inference powered by Hugging Face Candle, enabling fast and lightweight text-to-speech synthesis without Python dependencies.
Rust
1
4 commits
updated Mar 1, 2026
Pure Rust inference implementation for Qwen3-TTS (Alibaba's text-to-speech model), powered by Candle ML framework.
config.jsonThree-stage TTS pipeline:
Text ──→ [TalkerModel] ──→ semantic tokens (1 per frame)
│
▼
[CodePredictor] ──→ 15 acoustic codes per semantic token
│
▼
[Decoder12Hz] ──→ 24kHz mono WAV audio
| Stage | Description |
|---|---|
| TalkerModel | 28-layer transformer with GQA + MRoPE. 0.6B (hidden=1024) or 1.7B (hidden=2048). |
| CodePredictor | 5-layer autoregressive decoder (hidden=1024). Generates 15 acoustic codes per semantic frame. |
| Decoder12Hz | ConvNeXt + transposed convolution. 16-codebook residual VQ → 24kHz f32 PCM. |
| HuggingFace ID | Size | Description |
|---|---|---|
Qwen/Qwen3-TTS-12Hz-0.6B-Base | 1.8 GB | Voice cloning via speaker encoder |
Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice | 1.8 GB | 9 preset speakers |
Qwen/Qwen3-TTS-12Hz-1.7B-Base | 3.9 GB | Voice cloning (larger model) |
Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice | 3.9 GB | 9 preset speakers (larger model) |
Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign | 3.8 GB | Text-described voice design |
git clone https://github.com/HeiSir2014/qwen3-tts-candle.git
cd qwen3-tts-candle
bash scripts/start.sh
This will:
Once running:
Model weights are downloaded automatically from HuggingFace Hub on first run.
# Build
cargo build --release -p tts-server
# Run (model auto-downloads from HuggingFace Hub)
./target/release/tts-server
# Or specify options
./target/release/tts-server \
--port 5001 \
--model-path Qwen/Qwen3-TTS-12Hz-0.6B-Base \
--device auto
CLI flags:
| Flag | Default | Description |
|---|---|---|
--port | 5001 | HTTP listen port |
--model-path | Qwen/Qwen3-TTS-12Hz-0.6B-Base | Local path or HuggingFace model ID |
--device | auto | auto, cpu, cuda, cuda:N, metal |
--log-dir | logs | Rolling daily log output directory |
Add to your Cargo.toml:
[dependencies]
qwen3-tts = { path = "crates/qwen3-tts", features = ["metal"] }
use qwen3_tts::{Qwen3TTS, SynthesisOptions, auto_device};
// Load model (auto-detected from config.json)
let device = auto_device()?;
let model = Qwen3TTS::from_pretrained("path/to/model", device)?;
// Synthesize speech
let audio = model.synthesize("Hello, world!", None)?;
audio.save("output.wav")?;
// With custom options
let options = SynthesisOptions {
temperature: 0.8,
top_k: 30,
..Default::default()
};
let audio = model.synthesize("Custom settings!", Some(options))?;
| Method | Path | Description |
|---|---|---|
GET | /health | Model status, backend, queue size, uptime |
POST | /extract-embedding | Extract speaker embedding from reference audio, save to .bin |
POST | /clone-stream | Voice clone with streaming binary frame response |
POST | /cache/clear | Clear in-memory prompt cache |
/clone-stream returns chunked binary frames (big-endian u32):
[chunkIndex: u32] [marker: u32] [sampleRate: u32] [dataLen: u32] [data bytes]
| Marker | Value | Meaning |
|---|---|---|
AUDIO | 0xFFFFFFFE | Audio chunk (~0.8s of 24kHz 16-bit PCM) |
END | 0xFFFFFFFD | End of stream |
ERROR | 0xFFFFFFFF | Error with message payload |
| Platform | Feature Flag | Compute dtype |
|---|---|---|
| CPU (any) | cpu (default) | F32 |
| macOS Apple Silicon | metal | BF16 (talker + code_predictor), F32 (decoder) |
| macOS Accelerate | accelerate | F32 + BLAS |
| Intel MKL | mkl | F32 + BLAS |
| NVIDIA CUDA | cuda | BF16 |
| NVIDIA Flash Attention | flash-attn | BF16 + FA2 |
Build with a specific backend:
# Apple Silicon (default for tts-server)
cargo build --release -p tts-server
# NVIDIA GPU
cargo build --release -p tts-server --no-default-features --features cuda
# CPU only
cargo build --release -p tts-server --no-default-features --features cpu
qwen3-tts-candle/
├── crates/qwen3-tts/ # Core inference library
│ └── src/
│ ├── lib.rs # Public API: Qwen3TTS, StreamingSession
│ ├── models/ # TalkerModel, CodePredictor, Decoder12Hz, SpeakerEncoder
│ ├── generation/ # Sampling (top-k/p, temperature, repetition penalty)
│ ├── audio/ # WAV I/O, mel spectrogram, resampling
│ └── tokenizer/ # Text tokenizer (Qwen2 vocab, 151K tokens)
├── tts-server/ # Axum HTTP server
│ └── src/
│ ├── main.rs # CLI entry point
│ ├── server.rs # Router + middleware
│ ├── routes.rs # API handlers
│ ├── streaming.rs # Binary frame streaming protocol
│ └── embedding.rs # Speaker embedding serialization
├── voice-recorder/ # Bun + React voice clone studio
│ ├── src/ # Bun backend (HTTPS, SSE, Rust bridge)
│ └── ui/ # React SPA (profiles, recording, clone lab)
└── scripts/
├── start.sh # One-command build + run
└── convert_to_f16.py # BF16 → F16 weight conversion
To halve model disk size / VRAM usage:
pip install torch safetensors huggingface_hub
python scripts/convert_to_f16.py --keep-decoder-f32
This project provides a Rust inference implementation for the Qwen3-TTS model. Please refer to Qwen3-TTS for model license terms.
4 commits
Rust
71.2%
TypeScript
16.6%
Python
5.5%
Shell
3.2%
CSS
2.7%
A Rust implementation of Qwen3-TTS inference powered by Hugging Face Candle, enabling fast and lightweight text-to-speech synthesis without Python dependencies.
Rust
1
4 commits
updated Mar 1, 2026
Pure Rust inference implementation for Qwen3-TTS (Alibaba's text-to-speech model), powered by Candle ML framework.
config.jsonThree-stage TTS pipeline:
Text ──→ [TalkerModel] ──→ semantic tokens (1 per frame)
│
▼
[CodePredictor] ──→ 15 acoustic codes per semantic token
│
▼
[Decoder12Hz] ──→ 24kHz mono WAV audio
| Stage | Description |
|---|---|
| TalkerModel | 28-layer transformer with GQA + MRoPE. 0.6B (hidden=1024) or 1.7B (hidden=2048). |
| CodePredictor | 5-layer autoregressive decoder (hidden=1024). Generates 15 acoustic codes per semantic frame. |
| Decoder12Hz | ConvNeXt + transposed convolution. 16-codebook residual VQ → 24kHz f32 PCM. |
| HuggingFace ID | Size | Description |
|---|---|---|
Qwen/Qwen3-TTS-12Hz-0.6B-Base | 1.8 GB | Voice cloning via speaker encoder |
Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice | 1.8 GB | 9 preset speakers |
Qwen/Qwen3-TTS-12Hz-1.7B-Base | 3.9 GB | Voice cloning (larger model) |
Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice | 3.9 GB | 9 preset speakers (larger model) |
Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign | 3.8 GB | Text-described voice design |
git clone https://github.com/HeiSir2014/qwen3-tts-candle.git
cd qwen3-tts-candle
bash scripts/start.sh
This will:
Once running:
Model weights are downloaded automatically from HuggingFace Hub on first run.
# Build
cargo build --release -p tts-server
# Run (model auto-downloads from HuggingFace Hub)
./target/release/tts-server
# Or specify options
./target/release/tts-server \
--port 5001 \
--model-path Qwen/Qwen3-TTS-12Hz-0.6B-Base \
--device auto
CLI flags:
| Flag | Default | Description |
|---|---|---|
--port | 5001 | HTTP listen port |
--model-path | Qwen/Qwen3-TTS-12Hz-0.6B-Base | Local path or HuggingFace model ID |
--device | auto | auto, cpu, cuda, cuda:N, metal |
--log-dir | logs | Rolling daily log output directory |
Add to your Cargo.toml:
[dependencies]
qwen3-tts = { path = "crates/qwen3-tts", features = ["metal"] }
use qwen3_tts::{Qwen3TTS, SynthesisOptions, auto_device};
// Load model (auto-detected from config.json)
let device = auto_device()?;
let model = Qwen3TTS::from_pretrained("path/to/model", device)?;
// Synthesize speech
let audio = model.synthesize("Hello, world!", None)?;
audio.save("output.wav")?;
// With custom options
let options = SynthesisOptions {
temperature: 0.8,
top_k: 30,
..Default::default()
};
let audio = model.synthesize("Custom settings!", Some(options))?;
| Method | Path | Description |
|---|---|---|
GET | /health | Model status, backend, queue size, uptime |
POST | /extract-embedding | Extract speaker embedding from reference audio, save to .bin |
POST | /clone-stream | Voice clone with streaming binary frame response |
POST | /cache/clear | Clear in-memory prompt cache |
/clone-stream returns chunked binary frames (big-endian u32):
[chunkIndex: u32] [marker: u32] [sampleRate: u32] [dataLen: u32] [data bytes]
| Marker | Value | Meaning |
|---|---|---|
AUDIO | 0xFFFFFFFE | Audio chunk (~0.8s of 24kHz 16-bit PCM) |
END | 0xFFFFFFFD | End of stream |
ERROR | 0xFFFFFFFF | Error with message payload |
| Platform | Feature Flag | Compute dtype |
|---|---|---|
| CPU (any) | cpu (default) | F32 |
| macOS Apple Silicon | metal | BF16 (talker + code_predictor), F32 (decoder) |
| macOS Accelerate | accelerate | F32 + BLAS |
| Intel MKL | mkl | F32 + BLAS |
| NVIDIA CUDA | cuda | BF16 |
| NVIDIA Flash Attention | flash-attn | BF16 + FA2 |
Build with a specific backend:
# Apple Silicon (default for tts-server)
cargo build --release -p tts-server
# NVIDIA GPU
cargo build --release -p tts-server --no-default-features --features cuda
# CPU only
cargo build --release -p tts-server --no-default-features --features cpu
qwen3-tts-candle/
├── crates/qwen3-tts/ # Core inference library
│ └── src/
│ ├── lib.rs # Public API: Qwen3TTS, StreamingSession
│ ├── models/ # TalkerModel, CodePredictor, Decoder12Hz, SpeakerEncoder
│ ├── generation/ # Sampling (top-k/p, temperature, repetition penalty)
│ ├── audio/ # WAV I/O, mel spectrogram, resampling
│ └── tokenizer/ # Text tokenizer (Qwen2 vocab, 151K tokens)
├── tts-server/ # Axum HTTP server
│ └── src/
│ ├── main.rs # CLI entry point
│ ├── server.rs # Router + middleware
│ ├── routes.rs # API handlers
│ ├── streaming.rs # Binary frame streaming protocol
│ └── embedding.rs # Speaker embedding serialization
├── voice-recorder/ # Bun + React voice clone studio
│ ├── src/ # Bun backend (HTTPS, SSE, Rust bridge)
│ └── ui/ # React SPA (profiles, recording, clone lab)
└── scripts/
├── start.sh # One-command build + run
└── convert_to_f16.py # BF16 → F16 weight conversion
To halve model disk size / VRAM usage:
pip install torch safetensors huggingface_hub
python scripts/convert_to_f16.py --keep-decoder-f32
This project provides a Rust inference implementation for the Qwen3-TTS model. Please refer to Qwen3-TTS for model license terms.
4 commits
Rust
71.2%
TypeScript
16.6%
Python
5.5%
Shell
3.2%
CSS
2.7%