Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,19 @@ audiocpp_add_model(ace_step
LOADERS
engine::models::ace_step::make_ace_step_loader
)
audiocpp_add_model(soprano_tts
SOURCES
src/community_models/soprano_tts/assets.cpp
src/community_models/soprano_tts/generator.cpp
src/community_models/soprano_tts/session.cpp
src/community_models/soprano_tts/tokenizer_text.cpp
src/community_models/soprano_tts/vocoder.cpp
INCLUDES
engine/community_models/soprano_tts/session.h
LOADERS
engine::community_models::soprano_tts::make_soprano_tts_loader
)


audiocpp_add_model(midashenglm_gen
SOURCES
Expand Down Expand Up @@ -1895,6 +1908,10 @@ if (ENGINE_BUILD_WARMBENCH)
add_engine_warmbench(qwen3_tts_warm_bench tests/qwen3_tts/qwen3_tts_warm_bench.cpp)
add_engine_warmbench(seed_vc_warm_bench tests/seed_vc/seed_vc_warm_bench.cpp)
add_engine_warmbench(silero_vad_warm_bench tests/silero_vad/silero_vad_warm_bench.cpp)
add_engine_warmbench(soprano_warm_bench tests/soprano_tts/soprano_warm_bench.cpp)
target_compile_definitions(soprano_warm_bench PRIVATE
ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}"
)
add_engine_warmbench(sortformer_diar_warm_bench tests/sortformer_diar/sortformer_diar_warm_bench.cpp)
add_engine_warmbench(supertonic_warm_bench tests/supertonic/supertonic_warm_bench.cpp)
add_engine_warmbench(vevo2_warm_bench tests/vevo2/vevo2_warm_bench.cpp)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,12 @@ Community model ports live under `community_models` to make the ownership bounda
| **minimax_music3** | Music | auto | GGUF Q4/Q8 | [@0xShug0](https://github.com/0xShug0), [@JoeMattie](https://github.com/JoeMattie) | [MiniMax Music 3](docs/community_models/minimax_music3.md) text-to-music generation with lyrics conditioning |
| **mms_forced_aligner** | Align | nl (nld), en (eng); pre-romanized Latin | Safetensors, GGUF 16/Q8 | Community | [MMS-300M-1130 Forced Aligner](docs/community_models/mms_forced_aligner.md) word-timestamp alignment from a wav2vec2 CTC checkpoint (safetensors or local GGUF) |
| **moss_tts_local** | TTS, Clone, Ctrl | auto, optional language hint | GGUF | [@justinjohn0306](https://github.com/justinjohn0306) | MOSS-TTS-Local Transformer v1.5 support |
| **moss_tts_local** | TTS, Clone, Ctrl | auto, optional language hint | GGUF | [@justinjohn0306](https://github.com/justinjohn0306) | MOSS-TTS-Local Transformer v1.5 support |
| **moss_voicegen** | Voice Design | en, zh | GGUF | Joost [@jrohde](https://github.com/jrohde) | [MOSS-VoiceGenerator](docs/community_models/moss_voicegen.md) speech in a voice designed from a written instruction |
| **outetts** | TTS, Clone | en, ar, zh, nl, fr, de, it, ja, ko, lt, ru, es, pt, be, bn, ka, hu, lv, fa, pl, sw, ta, uk | GGUF | Mirek [@mirek190](https://github.com/mirek190) | Llama-OuteTTS-1.0-1B TTS and voice cloning support |
| **parakeet_tdt** | ASR | auto, bg, cs, da, de, el, en, es, et, fi, fr, hr, hu, it, lt, lv, mt, nl, pl, pt, ro, ru, sk, sl, sv, uk | GGUF F32/16/Q8, Stream | [@dleiferives](https://github.com/dleiferives) | [Parakeet-TDT 0.6B v3](docs/community_models/parakeet_tdt.md) offline, long-form, and buffered-streaming ASR support |
| **sense_asr** | ASR | auto, zh, en, yue, ja, ko, pt, ru, es, it, fr, de, nl, pl, tr, ar, hi, vi, th, id, ms, fa, nospeech | GGUF Q8, Stream | Jason Chen [@jasonchen31](https://github.com/jasonchen31), [@LauraGPT](https://github.com/LauraGPT) / FunASR | [SenseVoice-Small](docs/community_models/sense_asr.md) offline/streaming SAN-M + CTC transcription with event/emotion/language tags and ITN |
| **soprano_tts** | TTS | en | GGUF Q8, Stream | [@WalkingCat](https://github.com/WalkingCat) | [Soprano-1.1-80M](https://huggingface.co/WalkingCat/Soprano-1.1-80M-GGUF) ultra-lightweight TTS with Qwen3 LM + Vocos decoder |
| **vietneu_tts** | TTS, Clone | vi, en | GGUF | Phuoc [@phuocnguyen90](https://github.com/phuocnguyen90) | [VieNeu-TTS-v3-Turbo](docs/community_models/vietneu_tts.md) TTS and voice cloning support |

## Docker
Expand Down
1 change: 1 addition & 0 deletions docs/gguf.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Status labels:
| `qwen3_tts` voice design | Done | Pass | --- | Pass (ASR match, drift) | Pass (ASR match, drift) |
| `rvc` | Done | --- | --- | Pass | --- |
| `seed_vc` | Done | Pass | --- | Pass (drift) | Pass (drift) |
| `soprano_tts` | Done | Pass | --- | Pass | Pass (drift) |
| `silero_vad` | Skip (tiny model) | --- | --- | --- | --- |
| `sortformer_diar` | Done | Pass | --- | Pass | Pass |
| `stable_audio` | Done | Pass | --- | Pass (drift) | Pass (drift) |
Expand Down
292 changes: 292 additions & 0 deletions docs/soprano_tts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
# Soprano TTS

Soprano is an ultra-lightweight (~80M parameter) English-only text-to-speech model
using a two-stage architecture: a Qwen3-style causal LM (17 layers, hidden 512,
vocab 8192) that autoregressively emits per-frame 512-dimensional features, and a
non-iterative Vocos-style decoder (ConvNeXt backbone + single ISTFT head, n_fft 2048 /
hop 512) that turns those features into 32 kHz audio. No diffusion refinement is
performed in the decoder.

| Field | Value |
|---|---|
| Family | `soprano_tts` |
| Task | `tts` |
| Mode | `offline`, `streaming` |
| Languages | `en` |
| Audio | WAV; 32 kHz mono |
| Streaming | Pull events (per-chunk audio) |

---

## Install

The model-spec manager installs the original safetensors package from the official
Hugging Face repository:

```bash
python3 tools/model_manager_v2.py install soprano_1_1_80m_original
```

Or download the checkpoint directly and convert the decoder manually:

```bash
# Download the official checkpoint
git lfs install
git clone https://huggingface.co/ekwek/Soprano-1.1-80M models/Soprano-1.1-80M

# Convert (folds weight-norm from decoder.pth, emits combined.safetensors)
pip install torch numpy safetensors
python3 tools/soprano_tts/convert_soprano.py \
--input-dir models/Soprano-1.1-80M \
--output-dir models/Soprano-1.1-80M-converted
```

---

## Build

Build audio.cpp with Soprano support:

```bash
# Soprano only (avoids OOM from 45-model parallel compilation)
cmake -B build -DCMAKE_BUILD_TYPE=Release \
-DAUDIOCPP_MODEL_SET=custom -DAUDIOCPP_MODELS=soprano_tts
cmake --build build --target audiocpp_cli --parallel

# With Vulkan backend
cmake -B build -DCMAKE_BUILD_TYPE=Release \
-DAUDIOCPP_MODEL_SET=custom -DAUDIOCPP_MODELS=soprano_tts \
-DENGINE_ENABLE_VULKAN=ON
cmake --build build --target audiocpp_cli --parallel
```

---

## CLI

### Basic inference

```bash
build/bin/audiocpp_cli --task tts --family soprano_tts \
--model models/Soprano-1.1-80M-converted \
--text "Soprano is an extremely lightweight text to speech model." \
--out soprano.wav
```

### With Vulkan backend

```bash
build/bin/audiocpp_cli --task tts --family soprano_tts \
--model models/Soprano-1.1-80M-converted \
--backend vulkan \
--text "Soprano runs on CPU and Vulkan backends." \
--out soprano_vulkan.wav
```

### Custom generation parameters

```bash
build/bin/audiocpp_cli --task tts --family soprano_tts \
--model models/Soprano-1.1-80M-converted \
--text "Warmer temperature and higher max tokens produce longer audio." \
--request-option temperature=0.5 \
--request-option max_tokens=256 \
--seed 42 \
--out custom.wav
```

### Long-form with custom chunk size

```bash
build/bin/audiocpp_cli --task tts --family soprano_tts \
--model models/Soprano-1.1-80M-converted \
--text "This is a longer text that will be split into sentence-aware chunks by the framework text chunker. Each chunk is generated and decoded separately, then concatenated into the final audio output." \
--session-option text_chunk_size=320 \
--out longform.wav
```

### Streaming mode

```bash
build/bin/audiocpp_cli --task tts --mode streaming --family soprano_tts \
--model models/Soprano-1.1-80M-converted \
--text "Streaming mode emits audio chunks as they are generated." \
--out stream.wav \
--out-dir stream_chunks
```

---

## Options

### Request options

| Option | Values | Default | Meaning |
|---|---|---:|---|
| `--request-option max_tokens=<n>` | integer | `512` | Maximum generated audio frames per chunk. |
| `--temperature` / `--request-option temperature=<f>` | float | `0.3` | AR sampling temperature. |
| `--top-p` / `--request-option top_p=<f>` | float | `0.95` | Nucleus sampling threshold. |
| `--repetition-penalty` / `--request-option repetition_penalty=<f>` | float | `1.2` | Repetition penalty. |
| `--request-option eos_bias=<f>` | float | `0.0` | Additive bias on EOS logit; positive stops sooner. |
| `--seed` / `--request-option seed=<n>` | integer | random | AR sampling seed. |

### Session options

| Option | Values | Default | Meaning |
|---|---|---:|---|
| `--session-option text_chunk_size=<n>` | chars | `200` | Max codepoints per chunk. |

### Load options

| Option | Values | Default | Meaning |
|---|---|---:|---|
| `--session-option backbone_weight_type=<type>` | `native`, `f32`, `f16`, `bf16`, `q8_0` | `f32` | LM weight storage. `f16`/`q8_0` are faster (see Performance). |
| `--session-option decoder_weight_type=<type>` | `native`, `f32`, `f16` | `f32` | Decoder weight storage. |

---

## Server

```json
{
"host": "127.0.0.1",
"port": 8080,
"models": [
{
"id": "soprano",
"family": "soprano_tts",
"path": "models/Soprano-1.1-80M-converted",
"task": "tts",
"mode": "offline"
}
]
}
```

```bash
audiocpp_server --config server.json

# OpenAI-compatible TTS endpoint
curl http://127.0.0.1:8080/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"model": "soprano",
"input": "Soprano is an extremely lightweight text to speech model.",
"response_format": "wav"
}' \
-o server_output.wav
```

---

## GGUF package

Standalone GGUF packages are available on Hugging Face:

```bash
# Install with the model manager
python3 tools/model_manager_v2.py install soprano_1_1_80m_q8_0

# Or install the BF16 variant
python3 tools/model_manager_v2.py install soprano_1_1_80m_bf16
```

Inference with the GGUF package:

```bash
build/bin/audiocpp_cli --task tts --family soprano_tts \
--model models/Soprano-1.1-80M-GGUF/soprano-1.1-80m-q8_0.gguf \
--text "GGUF packages are standalone and self-describing." \
--out gguf_soprano.wav
```

To create a GGUF package from the converted safetensors yourself:

```bash
build/bin/audiocpp_gguf \
--input models/Soprano-1.1-80M-converted/combined.safetensors \
--output Soprano-1.1-80M-GGUF/soprano-1.1-80m-q8_0.gguf \
--type q8_0 \
--root models/Soprano-1.1-80M-converted \
--family soprano_tts \
--overwrite
```
## Performance

Measured on an Intel i5-10400 (6C/12T) CPU and an AMD Radeon RX Vega (8 GB) GPU,
Release build, warm cache, short/medium sentences:

| Backend | Backbone storage | RTF | Details |
|---------|------------------|----:|--------|
| CPU | F32 (default) | ~0.23 | ~4.3x realtime; LM decode dominates (~12.3 ms/frame) |
| CPU | F16 | ~0.16 | ~6x realtime; output statistically identical to F32 |
| CPU | Q8_0 | ~0.12 | ~8x realtime; sampling diverges slightly from F32 |
| Vulkan | F32 (default) | ~0.15-0.21 | ~5-7x realtime after one-time shader warmup |
| Vulkan | F16 | ~0.11-0.13 | ~8x realtime |
| Vulkan | Q8_0 | ~0.11-0.13 | same as F16; long-form text amortizes to ~0.08 |

The LM decode step is memory-bandwidth bound: halving weight traffic (F16)
speeds it up ~1.6x on CPU. Storage types are selected per session (see below);
F32 remains the bit-exact reference, while F16 measured numerically identical
output for this checkpoint, and Q8_0 trades a small sampling drift for the
fastest inference.

### Tuning storage types

```
# CPU: F16 backbone (recommended)
build/bin/audiocpp_cli --task tts --family soprano_tts --model models/soprano-1.1-80m-converted \
--text "..." --session-option backbone_weight_type=f16 --out out.wav

# CPU: Q8_0 backbone (fastest)
build/bin/audiocpp_cli --task tts --family soprano_tts --model models/soprano-1.1-80m-converted \
--text "..." --session-option backbone_weight_type=q8_0 --out out.wav

# GPU: pre-quantized GGUF packages already run at the q8_0 rate
```

Timing logs are available through `--log`:
- `soprano_tts.lm.generate_ms` -- LM AR decode time
- `soprano_tts.lm.frames` -- generated frames
- `soprano_tts.decoder.decode_ms` -- Vocos decoder time
- `soprano_tts.lm.decode.plan_cached` -- plan caching status

---

## Memory

| Metric | Value | Conditions |
|--------|-------|------------|
| Model size (safetensors) | ~380 MB (backbone BF16) + ~18 MB (decoder F32) | Original HF checkpoint |
| Peak RSS (CPU) | ~1.2 GB | Graph arena (512 MB) + weight context (256 MB) + runtime overhead |
| Peak VRAM (Vulkan) | Not measured | Vega ~1.2 GB reported system RAM usage |

---

## Known limitations

- English-only (model limitation)
- No voice cloning
- EOS sampling unreliable at low temperature (C++ RNG != PyTorch RNG)
- Full composite build may OOM; use AUDIOCPP_MODEL_SET=custom with AUDIOCPP_MODELS=soprano_tts

---

## Architecture

Soprano uses a two-stage architecture:

1. **Qwen3 causal LM** (17 layers, hidden 512, 4 heads, 1 KV head, head_dim 128, vocab 8192,
intermediate 2304, rope_theta 10000). Takes prompt `[STOP][TEXT]<text>[START]` and
autoregressively generates tokens. Each step's last-layer hidden state (512-dim) equals
one audio frame.

2. **Vocos decoder** (non-iterative): Interpolate x4 linear align_corners -> Conv1d(512->768,k=1)
-> LN -> 8x ConvNeXt(dwconv k=3 groups, LN, Linear->2304, GELU, Linear->768, gamma) -> LN ->
Linear(768->2050) -> split mag/phase -> exp*exp(i*phi) -> istft(center=True) with Hann window
(n_fft=2048, hop=512).

Output: 32 kHz mono. Token ~ 2048 samples ~ 64 ms.

Reference: https://github.com/ekwek1/soprano
Weights: https://huggingface.co/ekwek/Soprano-1.1-80M
Loading
Loading