Skip to content

QVAC-21928: CosyVoice3 native C++/ggml TTS engine (Qwen2 LM + DiT flow + CausalHiFT)#99

Open
mexxik wants to merge 3 commits into
masterfrom
feat/QVAC-21928-cosyvoice3-tts-engine-scaffold
Open

QVAC-21928: CosyVoice3 native C++/ggml TTS engine (Qwen2 LM + DiT flow + CausalHiFT)#99
mexxik wants to merge 3 commits into
masterfrom
feat/QVAC-21928-cosyvoice3-tts-engine-scaffold

Conversation

@mexxik

@mexxik mexxik commented Jul 21, 2026

Copy link
Copy Markdown

What problem does this PR solve?

QVAC needs an on-device CosyVoice3 TTS engine. tts-cpp shipped Chatterbox and
Supertonic; CosyVoice3 adds strong multilingual (esp. CJK) synthesis and a zero-shot
voice-cloning lineage. There was no native (non-PyTorch) CosyVoice3 path.

How does it solve it?

Adds tts_cpp::cosyvoice::Engine — a pure C++/ggml, CPU implementation of the
Fun-CosyVoice3-0.5B back-half, under the same public Engine API as the sibling
engines (include/tts-cpp/cosyvoice/engine.h).

Pipeline: text → Qwen2 BPE tokenizer → Qwen2.5-0.5B speech LM → speech tokens →
DiT conditional-flow-matching (10-step Euler) → mel → CausalHiFT vocoder →
24 kHz PCM.

  • Shared core src/cosyvoice_pipeline.{h,cpp} (in-memory, ggml-only) drives both
    the Engine and the parity CLIs (cosyvoice-{llm,flow,hift,cli}).
  • KV cache for the LM decode (~25×/token, bit-identical output).
  • Self-contained voices: voice.gguf bakes the reference transcript
    (voice.prompt_text); the engine reads it, so switching accent is a one-file swap.
  • Instruct mode (EngineOptions::instruct_text, cosyvoice-cli --instruct):
    natural-language control of Chinese dialect / emotion / speed / volume, mirroring
    CosyVoice3 inference_instruct2 (== zero_shot minus prompt speech tokens; the
    instruction sits in the LM prompt before <|endofprompt|>, the baked voice keeps
    the timbre). Supports the 17 Chinese dialects + emotion/speed/volume from
    cosyvoice/utils/common.py.
  • GGUF converters (scripts/convert-cosyvoice3-*-to-gguf.py) — LM supports
    --dtype f32|f16|q8_0|q4_0 (LM: 1.9 GB → 928 MB q8 / 758 MB q4); plus
    bake-cosyvoice3-voice.py, extract_voice.py, assemble-cosyvoice3-model.py.
  • Reference tooling under models_evaluation/CosyVoice/ (parity dumps).

Validation

Numerically parity-gated against the PyTorch reference on identical inputs:

  • LM prefill logits, flow_mel, dit_out all at cosine 1.000000.
  • HiFT vocoder: after fixing a phase artifact (causal nearest-mode source
    excitation + a missing ReflectionPad1d, commit 0e4b8df), our HiFT tracks
    PyTorch at waveform corr 0.996 on the same mel (was 0.136); the residual is
    the random unvoiced excitation (different RNG). Removed an audible metallic ring
    that the log-mel gate was blind to.
  • Instruct path correctness: with greedy decoding forced on both sides (f32 LM,
    identical inputs), the Qwen2 BPE text_ids and the generated speech-token
    trajectory are bit-identical to PyTorch inference_instruct2 — verified on
    Cantonese (45/45 ids, 400/400 steps) and Sichuan (45/45 ids, 176/176 tokens, same
    EOS step). Tokenization (incl. <|endofprompt|>), instruct-prompt construction,
    LM forward, and EOS handling all match exactly.

End-to-end (LM → flow → HiFT) Whisper-verified intelligible. Quantized LM (q8/q4)
verified intelligible with zero engine changes (ggml mul_mat runs quantized
weights natively).

Scope / not in this PR

CPU-only (iteration 1). Zero-shot uses a baked voice; native S3 tokenizer + CAM++
for arbitrary reference audio is a follow-up. No GPU path yet. Flow/HiFT remain f32.

Breaking changes

None — purely additive (new engine + new CLIs + converter scripts). Existing
Chatterbox/Supertonic/LavaSR APIs and outputs are unchanged.

Follow-ups

  • Native S3 tokenizer + CAM++ (zero-shot from arbitrary audio, no PyTorch).
  • embed_tokens get-rows gather → quantize it (LM well under 500 MB); flow quant.
  • GPU backend; wetext text normalization; flow Euler-step / latency reduction.

Reviewer notes

  • New public header: include/tts-cpp/cosyvoice/engine.h.
  • PROGRESS_COSYVOICE.md documents the staged bring-up + the 5 numerical bugs found.
  • Does not touch the ggml-speech port; consumes it like the other engines.

@mexxik
mexxik requested review from a team as code owners July 21, 2026 08:18
@github-actions

Copy link
Copy Markdown

Review Status

Current Status: ❌ PENDING
Approvals so far: none

Pending reviews: Needs 1 Management or Team Lead, and 1 more from Management, Team Lead, or Member.

Comment thread engines/tts/src/cosyvoice_flow.cpp Fixed
Comment thread engines/tts/src/cosyvoice_flow.cpp Fixed
Comment thread engines/tts/src/cosyvoice_flow.cpp Fixed
Comment thread engines/tts/src/cosyvoice_flow.cpp Fixed
Comment thread engines/tts/src/cosyvoice_flow.cpp Fixed
Comment thread engines/tts/src/cosyvoice_llm.cpp Fixed
Comment thread engines/tts/src/cosyvoice_llm.cpp Fixed
Comment thread engines/tts/src/cosyvoice_llm.cpp Fixed
Comment thread engines/tts/src/cosyvoice_pipeline.cpp Fixed
Comment thread engines/tts/src/cosyvoice_pipeline.cpp Fixed
…w + CausalHiFT)

Adds tts_cpp::cosyvoice::Engine — a pure C++/ggml, CPU implementation of the
Fun-CosyVoice3-0.5B back-half, under the same public Engine API as the sibling
engines (engines/tts/include/tts-cpp/cosyvoice/engine.h).

Pipeline: text -> Qwen2 BPE tokenizer -> Qwen2.5-0.5B speech LM -> speech tokens
-> DiT conditional-flow-matching (10-step Euler) -> mel -> CausalHiFT vocoder
-> 24 kHz PCM.

- Shared core src/cosyvoice_pipeline.{h,cpp} (in-memory, ggml-only) drives both
  the Engine and the parity CLIs (cosyvoice-{llm,flow,hift,cli}).
- KV cache for the LM decode (~25x/token, bit-identical output).
- Self-contained voices: voice.gguf bakes the reference transcript
  (voice.prompt_text); switching accent is a one-file swap.
- Instruct mode (EngineOptions::instruct_text, cosyvoice-cli --instruct): Chinese
  dialect / emotion / speed / volume control, mirroring inference_instruct2.
- GGUF converters (scripts/convert-cosyvoice3-*-to-gguf.py) — LM --dtype
  f32|f16|q8_0|q4_0 (1.9 GB -> 928 MB q8 / 758 MB q4); bake/extract/assemble tools.
- Reference tooling under models_evaluation/CosyVoice/ (parity dumps).

Validation (vs the PyTorch reference, identical inputs):
- LM prefill logits, flow_mel, dit_out all at cosine 1.000000.
- HiFT: after a phase-artifact fix (causal nearest-mode source excitation +
  ReflectionPad1d), tracks PyTorch at waveform corr 0.996 on the same mel
  (was 0.136); removed an audible metallic ring the log-mel gate missed.
- Instruct path: greedy text_ids + speech-token trajectory bit-identical to
  inference_instruct2 (Cantonese 400/400, Sichuan 176/176, same EOS step).
End-to-end Whisper-verified intelligible. Quantized LM (q8/q4) runs unchanged
(ggml mul_mat consumes quantized weights natively).

Scope: CPU-only (iteration 1); zero-shot uses a baked voice (native S3 tokenizer
+ CAM++ for arbitrary reference audio is a follow-up); no GPU path; flow/HiFT f32.
Purely additive — Chatterbox/Supertonic/LavaSR/Parler APIs unchanged. Does not
touch the ggml-speech port; consumes it like the other engines. Staged bring-up
+ the numerical bugs found are documented in engines/tts/PROGRESS_COSYVOICE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mexxik
mexxik force-pushed the feat/QVAC-21928-cosyvoice3-tts-engine-scaffold branch from b4275fe to 0eb7b44 Compare July 21, 2026 15:14
Comment thread engines/tts/src/cosyvoice_pipeline.cpp Outdated
ggml_tensor * o = ggml_mul_mat(c, vt, sc);
o = ggml_cont(c, ggml_permute(c, o, 0, 3, 1, 2));
o = ggml_reshape_3d(c, o, HD, NH, L);
o = ggml_reshape_2d(c, o, HD * NH, L);
Comment thread engines/tts/src/cosyvoice_pipeline.cpp Fixed
mexxik and others added 2 commits July 21, 2026 17:36
…t converted to larger type'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…t converted to larger type'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants