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
4 changes: 2 additions & 2 deletions docs/community_models/voxcpm1.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ VoxCPM1 is a **tokenizer-free TTS model** from [OpenBMB](https://github.com/Open

```bash
# Via model manager (recommended)
python3 tools/model_manager_v2.py install voxcpm1_0.5b_q8_0 --models-root models
python3 tools/model_manager_v2.py install voxcpm1_0_5b_q8_0 --models-root models
```

This downloads the `voxcpm-0.5b-q8_0-audiovae-f16.gguf` package (~690 MB) to `models/VoxCPM1-GGUF/`.
Expand Down Expand Up @@ -150,7 +150,7 @@ The default package is the standalone GGUF:

| Package ID | Display Name | Format | Precision | Files |
|---|---|---|---|---|
| `voxcpm1_0.5b_q8_0` | VoxCPM 0.5B Q8_0 GGUF | gguf | q8_0 (LLM) + f16 (AudioVAE) | `VoxCPM1-GGUF/voxcpm-0.5b-q8_0-audiovae-f16.gguf` |
| `voxcpm1_0_5b_q8_0` | VoxCPM 0.5B Q8_0 GGUF | gguf | q8_0 (LLM) + f16 (AudioVAE) | `VoxCPM1-GGUF/voxcpm-0.5b-q8_0-audiovae-f16.gguf` |

The GGUF embeds:
- Hybrid-quantized model (LLM Q8_0, AudioVAE F16)
Expand Down
4 changes: 2 additions & 2 deletions model_specs/voxcpm1.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@
]
},
"ui": {
"recommended_package": "voxcpm1_0.5b_q8_0",
"recommended_package": "voxcpm1_0_5b_q8_0",
"tags": [
"TTS",
"Clone",
Expand All @@ -248,7 +248,7 @@
},
"packages": [
{
"id": "voxcpm1_0.5b_q8_0",
"id": "voxcpm1_0_5b_q8_0",
"display_name": "VoxCPM 0.5B Q8_0 GGUF",
"default": true,
"format": "gguf",
Expand Down
49 changes: 41 additions & 8 deletions src/community_models/voxcpm1/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "engine/framework/model_spec/metadata.h"
#include "engine/framework/model_spec/package.h"
#include "engine/framework/runtime/options.h"
#include "engine/framework/text/chinese_variant.h"
#include "engine/framework/text/chunking.h"

#include <algorithm>
Expand Down Expand Up @@ -147,6 +148,20 @@ int64_t product(const std::vector<int64_t> &values) {
return out;
}

std::optional<std::string> extract_request_language(const runtime::TaskRequest &request) {
if (request.text_input.has_value() && !request.text_input->language.empty()) {
return request.text_input->language;
}
if (request.voice.has_value() && request.voice->style.has_value() &&
request.voice->style->language.has_value()) {
return request.voice->style->language;
}
if (const auto lang = runtime::find_option(request.options, {"language", "lang"})) {
return *lang;
}
return std::nullopt;
}

} // namespace

bool VoxCPM1SessionBase::EncodedPromptCacheKeyEqual::operator()(
Expand Down Expand Up @@ -266,19 +281,31 @@ runtime::TaskResult VoxCPM1SessionBase::run_offline_request(const runtime::TaskR
release_guard(this, release_runtime_memory);

const auto wall_start = Clock::now();
// Traditional -> Simplified conversion (OpenCC TSCharacters) unless language is Cantonese/Yue.
// Mirrors audio8_tts fix: keep Traditional only for yue/cantonese/zh-HK/zh-MO.
const auto language = extract_request_language(request);
auto maybe_convert = [&](std::string_view text) -> std::string {
return engine::text::maybe_convert_traditional_to_simplified_opt(text, language);
};
const auto prompt_text_raw =
runtime::find_option(request.options, {"voxcpm1.prompt_text",
"voxcpm1.prompt_text",
"prompt_text", "reference_text"})
.value_or("");
const std::string prompt_text = maybe_convert(prompt_text_raw);
// Apply conversion to TTS text before chunking so word-budget chunking operates on converted text.
runtime::TaskRequest converted_request = request;
if (converted_request.text_input.has_value()) {
converted_request.text_input->text = maybe_convert(converted_request.text_input->text);
}
const int64_t text_chunk_size =
engine::text::parse_text_chunk_size_override(request.options).value_or(kDefaultTextChunkSize);
const auto text_chunk_mode =
engine::text::parse_text_chunk_mode_override(request.options)
.value_or(engine::text::TextChunkMode::TagAware);
const auto chunk_requests =
runtime::chunk_text_request(request, text_chunk_size, text_chunk_mode);
runtime::chunk_text_request(converted_request, text_chunk_size, text_chunk_mode);
const auto generation_options = generation_options_from_request(request);
const auto prompt_text =
runtime::find_option(request.options, {"voxcpm1.prompt_text",
"voxcpm1.prompt_text",
"prompt_text", "reference_text"})
.value_or("");
std::optional<runtime::AudioBuffer> reference_audio;
if (request.voice.has_value() && request.voice->speaker.has_value() &&
request.voice->speaker->audio.has_value()) {
Expand Down Expand Up @@ -365,12 +392,17 @@ VoxCPM1SessionBase::run_streaming_request(
release_guard(this, release_runtime_memory);

const auto wall_start = Clock::now();
const auto language = extract_request_language(request);
auto maybe_convert = [&](std::string_view text) -> std::string {
return engine::text::maybe_convert_traditional_to_simplified_opt(text, language);
};
auto generation_options = generation_options_from_request(request);
const auto prompt_text =
const auto prompt_text_raw =
runtime::find_option(request.options, {"voxcpm1.prompt_text",
"voxcpm1.prompt_text",
"prompt_text", "reference_text"})
.value_or("");
const std::string prompt_text = maybe_convert(prompt_text_raw);
std::optional<runtime::AudioBuffer> reference_audio;
if (request.voice.has_value() && request.voice->speaker.has_value() &&
request.voice->speaker->audio.has_value()) {
Expand Down Expand Up @@ -433,7 +465,8 @@ VoxCPM1SessionBase::run_streaming_request(
};

const auto generator_start = Clock::now();
(void)generator_->generate_streaming(request.text_input->text, prompt,
const std::string streaming_text = maybe_convert(request.text_input->text);
(void)generator_->generate_streaming(streaming_text, prompt,
generation_options, emit_chunk);
const auto generator_end = Clock::now();
const double generator_with_callbacks_ms =
Expand Down
2 changes: 1 addition & 1 deletion webui/configs/models_catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
{ "id": "miotts", "display_name": "MioTTS 1.7B (tts; needs MioCodec)", "family": "miotts", "path": "models/MioTTS-1.7B", "task": "tts", "mode": "offline", "download_id": "miotts_1_7b", "min_vram_gb": 8 },
{ "id": "soprano-tts", "display_name": "Soprano TTS (tts)", "family": "soprano_tts", "path": "models/Soprano-1.1-80M-GGUF", "task": "tts", "mode": "offline", "download_id": "soprano_1_1_80m_q8_0", "min_vram_gb": 1 },
{ "id": "voxcpm2", "display_name": "VoxCPM2 (tts)", "family": "voxcpm2", "path": "models/VoxCPM2", "task": "tts", "mode": "offline", "download_id": "voxcpm2", "session_options": { "voxcpm2.weight_type": "q8_0" }, "min_vram_gb": 6 },
{ "id": "voxcpm1", "display_name": "VoxCPM1 0.5B (tts + clone)", "family": "voxcpm1", "path": "models/VoxCPM1-GGUF", "task": "tts", "mode": "offline", "download_id": "voxcpm1_0.5b_q8_0", "min_vram_gb": 4 },
{ "id": "voxcpm1", "display_name": "VoxCPM1 0.5B (tts + clone)", "family": "voxcpm1", "path": "models/VoxCPM1-GGUF", "task": "tts", "mode": "offline", "download_id": "voxcpm1_0_5b_q8_0", "min_vram_gb": 4 },
{ "id": "vibevoice", "display_name": "VibeVoice 1.5B/7B (tts, long-form/multi-speaker)", "family": "vibevoice", "path": "models/VibeVoice-1.5B", "task": "tts", "mode": "offline", "download_id": "vibevoice_1_5b", "min_vram_gb": 7 },
{ "id": "vibevoice-7b", "display_name": "VibeVoice 7B (tts, long-form/multi-speaker)", "family": "vibevoice", "path": "models/VibeVoice-7B-GGUF", "task": "tts", "mode": "offline", "download_id": "vibevoice_7b_q8_0", "min_vram_gb": 16 },
{ "id": "index-tts2", "display_name": "IndexTTS2 (tts 中英克隆+情感)", "display_name_en": "IndexTTS2 (tts, zh/en clone + emotion)", "family": "index_tts2", "path": "models/IndexTTS-2", "task": "tts", "mode": "offline", "download_id": "index_tts2", "min_vram_gb": 8 },
Expand Down
Loading