HeiSir2014/qwen3-tts-candle

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

See the code

README

qwen3-tts-candle

Pure Rust inference implementation for Qwen3-TTS (Alibaba's text-to-speech model), powered by Candle ML framework.

Features

  • Pure Rust — no Python runtime or C++ dependencies required
  • Multi-platform GPU — Apple Silicon (Metal), NVIDIA CUDA, Flash Attention 2
  • CPU fallback — with optional MKL / Accelerate BLAS acceleration
  • Voice cloning — ECAPA-TDNN speaker encoder extracts voice embeddings from reference audio
  • Streaming synthesis — binary frame protocol delivers ~0.8s audio chunks incrementally
  • HTTP server — production-ready Axum server with health checks, prompt caching, and request tracing
  • Voice Recorder UI — React + Bun full-stack app for recording, managing speaker profiles, and voice clone jobs
  • Model auto-detection — automatically identifies model variant (0.6B / 1.7B, Base / CustomVoice / VoiceDesign) from config.json
  • HuggingFace Hub — downloads model weights automatically if not found locally

Architecture

Three-stage TTS pipeline:

Text ──→ [TalkerModel] ──→ semantic tokens (1 per frame)
              │
              ▼
         [CodePredictor] ──→ 15 acoustic codes per semantic token
              │
              ▼
         [Decoder12Hz] ──→ 24kHz mono WAV audio
StageDescription
TalkerModel28-layer transformer with GQA + MRoPE. 0.6B (hidden=1024) or 1.7B (hidden=2048).
CodePredictor5-layer autoregressive decoder (hidden=1024). Generates 15 acoustic codes per semantic frame.
Decoder12HzConvNeXt + transposed convolution. 16-codebook residual VQ → 24kHz f32 PCM.

Supported Models

HuggingFace IDSizeDescription
Qwen/Qwen3-TTS-12Hz-0.6B-Base1.8 GBVoice cloning via speaker encoder
Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice1.8 GB9 preset speakers
Qwen/Qwen3-TTS-12Hz-1.7B-Base3.9 GBVoice cloning (larger model)
Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice3.9 GB9 preset speakers (larger model)
Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign3.8 GBText-described voice design

Quick Start

Prerequisites

  • Rust (1.75+) — rustup.rs
  • Bun (optional, for Voice Recorder UI) — bun.sh

One-command startup (server + UI)

git clone https://github.com/HeiSir2014/qwen3-tts-candle.git
cd qwen3-tts-candle
bash scripts/start.sh

This will:

  1. Build the Rust TTS server in release mode
  2. Install Bun dependencies for the Voice Recorder UI
  3. Start the Bun server (auto-launches the Rust TTS server)

Once running:

Model weights are downloaded automatically from HuggingFace Hub on first run.

Run TTS server only

# 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:

FlagDefaultDescription
--port5001HTTP listen port
--model-pathQwen/Qwen3-TTS-12Hz-0.6B-BaseLocal path or HuggingFace model ID
--deviceautoauto, cpu, cuda, cuda:N, metal
--log-dirlogsRolling daily log output directory

Use as a Rust library

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))?;

API Endpoints

MethodPathDescription
GET/healthModel status, backend, queue size, uptime
POST/extract-embeddingExtract speaker embedding from reference audio, save to .bin
POST/clone-streamVoice clone with streaming binary frame response
POST/cache/clearClear in-memory prompt cache

Streaming protocol

/clone-stream returns chunked binary frames (big-endian u32):

[chunkIndex: u32] [marker: u32] [sampleRate: u32] [dataLen: u32] [data bytes]
MarkerValueMeaning
AUDIO0xFFFFFFFEAudio chunk (~0.8s of 24kHz 16-bit PCM)
END0xFFFFFFFDEnd of stream
ERROR0xFFFFFFFFError with message payload

Platform Support

PlatformFeature FlagCompute dtype
CPU (any)cpu (default)F32
macOS Apple SiliconmetalBF16 (talker + code_predictor), F32 (decoder)
macOS AccelerateaccelerateF32 + BLAS
Intel MKLmklF32 + BLAS
NVIDIA CUDAcudaBF16
NVIDIA Flash Attentionflash-attnBF16 + 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

Project Structure

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

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

License

This project provides a Rust inference implementation for the Qwen3-TTS model. Please refer to Qwen3-TTS for model license terms.

Contributors

HeiSir2014

4 commits

HeiSir2014/qwen3-tts-candle

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

See the code

README

qwen3-tts-candle

Pure Rust inference implementation for Qwen3-TTS (Alibaba's text-to-speech model), powered by Candle ML framework.

Features

  • Pure Rust — no Python runtime or C++ dependencies required
  • Multi-platform GPU — Apple Silicon (Metal), NVIDIA CUDA, Flash Attention 2
  • CPU fallback — with optional MKL / Accelerate BLAS acceleration
  • Voice cloning — ECAPA-TDNN speaker encoder extracts voice embeddings from reference audio
  • Streaming synthesis — binary frame protocol delivers ~0.8s audio chunks incrementally
  • HTTP server — production-ready Axum server with health checks, prompt caching, and request tracing
  • Voice Recorder UI — React + Bun full-stack app for recording, managing speaker profiles, and voice clone jobs
  • Model auto-detection — automatically identifies model variant (0.6B / 1.7B, Base / CustomVoice / VoiceDesign) from config.json
  • HuggingFace Hub — downloads model weights automatically if not found locally

Architecture

Three-stage TTS pipeline:

Text ──→ [TalkerModel] ──→ semantic tokens (1 per frame)
              │
              ▼
         [CodePredictor] ──→ 15 acoustic codes per semantic token
              │
              ▼
         [Decoder12Hz] ──→ 24kHz mono WAV audio
StageDescription
TalkerModel28-layer transformer with GQA + MRoPE. 0.6B (hidden=1024) or 1.7B (hidden=2048).
CodePredictor5-layer autoregressive decoder (hidden=1024). Generates 15 acoustic codes per semantic frame.
Decoder12HzConvNeXt + transposed convolution. 16-codebook residual VQ → 24kHz f32 PCM.

Supported Models

HuggingFace IDSizeDescription
Qwen/Qwen3-TTS-12Hz-0.6B-Base1.8 GBVoice cloning via speaker encoder
Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice1.8 GB9 preset speakers
Qwen/Qwen3-TTS-12Hz-1.7B-Base3.9 GBVoice cloning (larger model)
Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice3.9 GB9 preset speakers (larger model)
Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign3.8 GBText-described voice design

Quick Start

Prerequisites

  • Rust (1.75+) — rustup.rs
  • Bun (optional, for Voice Recorder UI) — bun.sh

One-command startup (server + UI)

git clone https://github.com/HeiSir2014/qwen3-tts-candle.git
cd qwen3-tts-candle
bash scripts/start.sh

This will:

  1. Build the Rust TTS server in release mode
  2. Install Bun dependencies for the Voice Recorder UI
  3. Start the Bun server (auto-launches the Rust TTS server)

Once running:

Model weights are downloaded automatically from HuggingFace Hub on first run.

Run TTS server only

# 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:

FlagDefaultDescription
--port5001HTTP listen port
--model-pathQwen/Qwen3-TTS-12Hz-0.6B-BaseLocal path or HuggingFace model ID
--deviceautoauto, cpu, cuda, cuda:N, metal
--log-dirlogsRolling daily log output directory

Use as a Rust library

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))?;

API Endpoints

MethodPathDescription
GET/healthModel status, backend, queue size, uptime
POST/extract-embeddingExtract speaker embedding from reference audio, save to .bin
POST/clone-streamVoice clone with streaming binary frame response
POST/cache/clearClear in-memory prompt cache

Streaming protocol

/clone-stream returns chunked binary frames (big-endian u32):

[chunkIndex: u32] [marker: u32] [sampleRate: u32] [dataLen: u32] [data bytes]
MarkerValueMeaning
AUDIO0xFFFFFFFEAudio chunk (~0.8s of 24kHz 16-bit PCM)
END0xFFFFFFFDEnd of stream
ERROR0xFFFFFFFFError with message payload

Platform Support

PlatformFeature FlagCompute dtype
CPU (any)cpu (default)F32
macOS Apple SiliconmetalBF16 (talker + code_predictor), F32 (decoder)
macOS AccelerateaccelerateF32 + BLAS
Intel MKLmklF32 + BLAS
NVIDIA CUDAcudaBF16
NVIDIA Flash Attentionflash-attnBF16 + 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

Project Structure

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

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

License

This project provides a Rust inference implementation for the Qwen3-TTS model. Please refer to Qwen3-TTS for model license terms.

Contributors

HeiSir2014

4 commits

Languages

Rust

71.2%

TypeScript

16.6%

Python

5.5%

Shell

3.2%

CSS

2.7%