basic qwen3 tts working

This commit is contained in:
Concedo
2026-02-21 12:03:53 +08:00
parent 1af7095cb5
commit 72219fdbf5
9 changed files with 137 additions and 119 deletions
+1 -1
View File
@@ -483,7 +483,7 @@ set_target_properties(whisper_adapter PROPERTIES POSITION_INDEPENDENT_CODE ON)
add_library(tts_adapter
otherarch/tts_adapter.cpp)
target_include_directories(tts_adapter PUBLIC . ./ggml/include ./ggml/src ./ggml/src/ggml-cpu ./include ./otherarch ./otherarch/tools ./vendor/stb ./vendor ./otherarch/ttscpp/include ./otherarch/ttscpp/src ./tools ./common)
target_include_directories(tts_adapter PUBLIC . ./ggml/include ./ggml/src ./ggml/src/ggml-cpu ./include ./otherarch ./otherarch/tools ./vendor/stb ./vendor ./otherarch/ttscpp/include ./otherarch/ttscpp/src ./otherarch/qwen3tts ./tools ./common)
target_compile_features(tts_adapter PUBLIC cxx_std_17) # don't bump
target_link_libraries(tts_adapter PRIVATE common2 ggml ${LLAMA_EXTRA_LIBS})
set_target_properties(tts_adapter PROPERTIES POSITION_INDEPENDENT_CODE ON)
+3 -3
View File
@@ -55,8 +55,8 @@ ifdef KCPP_SANITIZE
CFLAGS += -fsanitize=undefined -fsanitize-undefined-trap-on-error
CXXFLAGS += -fsanitize=undefined -fsanitize-undefined-trap-on-error
endif
CFLAGS += -I. -Iggml/include -Iggml/src -Iggml/src/ggml-cpu -Iinclude -Isrc -I./common -I./vendor -I./vendor/stb -I./include -I./otherarch -I./otherarch/tools -I./otherarch/sdcpp -I./otherarch/ttscpp/include -I./otherarch/ttscpp/src -I./otherarch/sdcpp/thirdparty -I./include/vulkan -O3 -fno-finite-math-only -std=c11 -fPIC -DLOG_DISABLE_LOGS -D_GNU_SOURCE -DGGML_USE_CPU -DGGML_USE_CPU_REPACK
CXXFLAGS += -I. -Iggml/include -Iggml/src -Iggml/src/ggml-cpu -Iinclude -Isrc -I./common -I./vendor -I./vendor/stb -I./include -I./otherarch -I./otherarch/tools -I./otherarch/sdcpp -I./otherarch/ttscpp/include -I./otherarch/ttscpp/src -I./otherarch/sdcpp/thirdparty -I./include/vulkan -O3 -fno-finite-math-only -std=c++17 -fPIC -DLOG_DISABLE_LOGS -D_GNU_SOURCE -DGGML_USE_CPU -DGGML_USE_CPU_REPACK
CFLAGS += -I. -Iggml/include -Iggml/src -Iggml/src/ggml-cpu -Iinclude -Isrc -I./common -I./vendor -I./vendor/stb -I./include -I./otherarch -I./otherarch/tools -I./otherarch/sdcpp -I./otherarch/ttscpp/include -I./otherarch/ttscpp/src -I./otherarch/qwen3tts -I./otherarch/sdcpp/thirdparty -I./include/vulkan -O3 -fno-finite-math-only -std=c11 -fPIC -DLOG_DISABLE_LOGS -D_GNU_SOURCE -DGGML_USE_CPU -DGGML_USE_CPU_REPACK
CXXFLAGS += -I. -Iggml/include -Iggml/src -Iggml/src/ggml-cpu -Iinclude -Isrc -I./common -I./vendor -I./vendor/stb -I./include -I./otherarch -I./otherarch/tools -I./otherarch/sdcpp -I./otherarch/ttscpp/include -I./otherarch/ttscpp/src -I./otherarch/qwen3tts -I./otherarch/sdcpp/thirdparty -I./include/vulkan -O3 -fno-finite-math-only -std=c++17 -fPIC -DLOG_DISABLE_LOGS -D_GNU_SOURCE -DGGML_USE_CPU -DGGML_USE_CPU_REPACK
ifndef KCPP_DEBUG
CFLAGS += -DNDEBUG -s
@@ -740,7 +740,7 @@ embeddingvk: examples/embedding/embedding.cpp common/arg.cpp common/speculative.
$(CXX) $(CXXFLAGS) -DGGML_USE_VULKAN -DSD_USE_VULKAN $(filter-out %.h,$^) -o $@ $(LDFLAGS)
ttscppmain: otherarch/ttscpp/cli/cli.cpp otherarch/ttscpp/cli/playback.cpp otherarch/ttscpp/cli/playback.h otherarch/ttscpp/cli/write_file.cpp otherarch/ttscpp/cli/write_file.h otherarch/ttscpp/cli/vad.cpp otherarch/ttscpp/cli/vad.h otherarch/ttscpp/src/ttscpp.cpp otherarch/ttscpp/src/ttstokenizer.cpp otherarch/ttscpp/src/ttssampler.cpp otherarch/ttscpp/src/parler_model.cpp otherarch/ttscpp/src/dac_model.cpp otherarch/ttscpp/src/ttsutil.cpp otherarch/ttscpp/src/ttsargs.cpp otherarch/ttscpp/src/ttst5_encoder_model.cpp otherarch/ttscpp/src/phonemizer.cpp otherarch/ttscpp/src/tts_model.cpp otherarch/ttscpp/src/kokoro_model.cpp otherarch/ttscpp/src/dia_model.cpp otherarch/ttscpp/src/orpheus_model.cpp otherarch/ttscpp/src/snac_model.cpp otherarch/ttscpp/src/general_neural_audio_codec.cpp ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o console.o llavaclip_default.o llava.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS)
$(CXX) $(CXXFLAGS) $(filter-out %.h,$^) -o $@ $(LDFLAGS)
qwen3tts: otherarch/qwen3tts/main.cpp otherarch/qwen3tts/qwen3_tts.cpp otherarch/qwen3tts/text_tokenizer.cpp otherarch/qwen3tts/gguf_loader.cpp otherarch/qwen3tts/tts_transformer.cpp otherarch/qwen3tts/audio_tokenizer_decoder.cpp otherarch/qwen3tts/audio_tokenizer_encoder.cpp otherarch/qwen3tts/coreml_code_predictor_stub.cpp ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o console.o llavaclip_default.o llava.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS)
qwen3tts: otherarch/qwen3tts/q3ttsmain.cpp otherarch/qwen3tts/qwen3_tts.cpp otherarch/qwen3tts/text_tokenizer.cpp otherarch/qwen3tts/gguf_loader.cpp otherarch/qwen3tts/tts_transformer.cpp otherarch/qwen3tts/audio_tokenizer_decoder.cpp otherarch/qwen3tts/audio_tokenizer_encoder.cpp otherarch/qwen3tts/coreml_code_predictor_stub.cpp ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o console.o llavaclip_default.o llava.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS)
$(CXX) $(CXXFLAGS) $(filter-out %.h,$^) -o $@ $(LDFLAGS)
ggml/src/ggml-vulkan-shaders.cpp:
+1 -1
View File
@@ -6452,7 +6452,7 @@ def show_gui():
makelabelentry(audio_tab, "TTS Max Tokens:" , ttsmaxlen_var, 7, 50,padx=290,singleline=True,tooltip="Max allowed audiotokens to generate per TTS request.")
makecheckbox(audio_tab, "TTS Use GPU", ttsgpu_var, 9, 0,tooltiptxt="Uses the GPU for TTS.")
ttsgpu_var.trace_add("write", gui_changed_modelfile)
makefileentry(audio_tab, "WavTokenizer Model (Required for OuteTTS):", "Select WavTokenizer GGUF Model File", wavtokenizer_var, 11, width=280, filetypes=[("*.gguf","*.gguf")], tooltiptxt="Select a WavTokenizer GGUF model file on disk to be loaded for Narration.")
makefileentry(audio_tab, "WavTokenizer Model (Required for some models):", "Select WavTokenizer GGUF Model File", wavtokenizer_var, 11, width=280, filetypes=[("*.gguf","*.gguf")], tooltiptxt="Select a WavTokenizer GGUF model file on disk to be loaded for Narration.")
wavtokenizer_var.trace_add("write", gui_changed_modelfile)
admin_tab = tabcontent["Admin"]
+7 -3
View File
@@ -69,6 +69,13 @@ Qwen3TTS::Qwen3TTS() = default;
Qwen3TTS::~Qwen3TTS() = default;
bool Qwen3TTS::load_models(const std::string & model_dir) {
// Construct model paths
std::string tts_model_path = model_dir + "/qwen3-tts-0.6b-f16.gguf";
std::string tokenizer_model_path = model_dir + "/qwen3-tts-tokenizer-f16.gguf";
return load_models(tts_model_path,tokenizer_model_path);
}
bool Qwen3TTS::load_models(const std::string & tts_model_path, const std::string & tokenizer_model_path) {
int64_t t_start = get_time_ms();
log_memory_usage("load/start");
@@ -77,9 +84,6 @@ bool Qwen3TTS::load_models(const std::string & model_dir) {
transformer_loaded_ = false;
decoder_loaded_ = false;
// Construct model paths
std::string tts_model_path = model_dir + "/qwen3-tts-0.6b-f16.gguf";
std::string tokenizer_model_path = model_dir + "/qwen3-tts-tokenizer-f16.gguf";
tts_model_path_ = tts_model_path;
decoder_model_path_ = tokenizer_model_path;
encoder_loaded_ = false;
+25 -24
View File
@@ -16,44 +16,44 @@ namespace qwen3_tts {
struct tts_params {
// Maximum number of audio tokens to generate
int32_t max_audio_tokens = 4096;
// Temperature for sampling (0 = greedy)
float temperature = 0.9f;
// Top-p sampling
float top_p = 1.0f;
// Top-k sampling (0 = disabled)
int32_t top_k = 50;
// Number of threads
int32_t n_threads = 4;
// Print progress during generation
bool print_progress = false;
// Print timing information
bool print_timing = true;
// Repetition penalty for CB0 token generation (HuggingFace style)
float repetition_penalty = 1.05f;
};
// TTS generation result
struct tts_result {
// Generated audio samples (24kHz, mono)
std::vector<float> audio;
// Sample rate
int32_t sample_rate = 24000;
// Success flag
bool success = false;
// Error message if failed
std::string error_msg;
// Timing info (in milliseconds)
int64_t t_load_ms = 0;
int64_t t_tokenize_ms = 0;
@@ -69,7 +69,7 @@ struct tts_result {
uint64_t mem_phys_start_bytes = 0;
uint64_t mem_phys_end_bytes = 0;
uint64_t mem_phys_peak_bytes = 0;
};
// Progress callback type
@@ -80,17 +80,18 @@ class Qwen3TTS {
public:
Qwen3TTS();
~Qwen3TTS();
// Load all models from directory
// model_dir should contain: transformer.gguf, tokenizer.gguf, vocoder.gguf
bool load_models(const std::string & model_dir);
bool load_models(const std::string & model,const std::string & tokenizer);
// Generate speech from text
// text: input text to synthesize
// params: generation parameters
tts_result synthesize(const std::string & text,
const tts_params & params = tts_params());
// Generate speech with voice cloning
// text: input text to synthesize
// reference_audio: path to reference audio file (WAV, 24kHz)
@@ -98,7 +99,7 @@ public:
tts_result synthesize_with_voice(const std::string & text,
const std::string & reference_audio,
const tts_params & params = tts_params());
// Generate speech with voice cloning from samples
// text: input text to synthesize
// ref_samples: reference audio samples (24kHz, mono, normalized to [-1, 1])
@@ -107,27 +108,27 @@ public:
tts_result synthesize_with_voice(const std::string & text,
const float * ref_samples, int32_t n_ref_samples,
const tts_params & params = tts_params());
// Set progress callback
void set_progress_callback(tts_progress_callback_t callback);
// Get error message
const std::string & get_error() const { return error_msg_; }
// Check if models are loaded
bool is_loaded() const { return models_loaded_; }
private:
tts_result synthesize_internal(const std::string & text,
const float * speaker_embedding,
const tts_params & params,
tts_result & result);
TextTokenizer tokenizer_;
TTSTransformer transformer_;
AudioTokenizerEncoder audio_encoder_;
AudioTokenizerDecoder audio_decoder_;
bool models_loaded_ = false;
bool encoder_loaded_ = false;
bool transformer_loaded_ = false;
@@ -140,7 +141,7 @@ private:
};
// Utility: Load audio file (WAV format)
bool load_audio_file(const std::string & path, std::vector<float> & samples,
bool load_audio_file(const std::string & path, std::vector<float> & samples,
int & sample_rate);
// Utility: Save audio file (WAV format)
+95 -82
View File
@@ -42,6 +42,15 @@
#include "snac_model.cpp"
#include "general_neural_audio_codec.cpp"
//imports required for qwen3tts to work
#include "qwen3_tts.cpp"
#include "text_tokenizer.cpp"
#include "gguf_loader.cpp"
#include "tts_transformer.cpp"
#include "audio_tokenizer_decoder.cpp"
#include "audio_tokenizer_encoder.cpp"
#include "coreml_code_predictor_stub.cpp"
enum TTS_VER
{
TTS_VER_2,
@@ -64,84 +73,6 @@ struct wav_header {
uint32_t data_size;
};
// #include <vector>
// #include <cstdio>
// #include <cmath>
// static void audio_post_clean(std::vector<float>& data) { // detect clicks
// const float silenceThreshold = 1e-5f;
// const float noiseThreshold = 1e-3f;
// const size_t minSilence = 100; // samples
// const size_t noiseSpan = 150; // samples
// const size_t minSilence2 = 100; // samples
// size_t len = data.size();
// int silencecounterA = 0;
// int noisecounterA = 0;
// int silencecounterB = 0;
// int state = 0; // 0 = finding first silence, 1 = measuring noise, 2 = finding second silence
// size_t noiseStart = 0;
// for (size_t i = 0; i < len; ++i) {
// float sample = std::fabs(data[i]);
// if (state == 0) { // finding first silence
// if (sample < silenceThreshold) {
// silencecounterA++;
// } else {
// if (silencecounterA >= minSilence) {
// state = 1;
// noisecounterA = 1;
// noiseStart = i;
// } else {
// silencecounterA = 0;
// noisecounterA = 0;
// silencecounterB = 0;
// }
// }
// }
// if (state == 1) { // measuring noise span
// noisecounterA++;
// if(sample>noiseThreshold)
// {
// state = 0;
// silencecounterA = 0;
// noisecounterA = 0;
// silencecounterB = 0;
// }
// else if(noisecounterA>noiseSpan)
// {
// state = 2;
// }
// }
// if (state == 2) { // finding second silence
// if (sample < silenceThreshold) {
// silencecounterB++;
// if (silencecounterB >= minSilence2) {
// // full click detected
// size_t noiseend = noiseStart + noisecounterA - 1;
// //printf("Click detected from %zu to %zu\n", noiseStart, noiseend);
// for(size_t j=noiseStart;j<noiseend;++j)
// {
// data[j] *= 0.01f; //greatly suppress noise
// }
// // reset to search again
// state = 0;
// silencecounterA = 0;
// noisecounterA = 0;
// silencecounterB = 0;
// }
// } else {
// state = 0;
// silencecounterA = 0;
// noisecounterA = 0;
// silencecounterB = 0;
// }
// }
// }
// }
static std::string save_wav16_base64(const std::vector<float> &data, int sample_rate) {
std::ostringstream oss;
@@ -609,6 +540,10 @@ static generation_configuration * ttscpp_config = nullptr;
static struct tts_runner * ttscpp_runner = nullptr;
static std::string detectedarch = "";
//qwen3tts specific
static bool is_qwen3tts_file = false;
static qwen3_tts::Qwen3TTS qwen3tts_runner;
int total_tts_gens = 0;
static std::string tts_executable_path = "";
@@ -646,7 +581,13 @@ bool ttstype_load_model(const tts_load_model_inputs inputs)
detectedarch = gguf_get_model_arch(modelfile_ttc);
is_ttscpp_file = false;
if (detectedarch!="" && SUPPORTED_ARCHITECTURES.find(detectedarch) != SUPPORTED_ARCHITECTURES.end()) {
is_qwen3tts_file = false;
if (detectedarch=="qwen3-tts")
{
is_qwen3tts_file = true;
printf("\nLoading Qwen3-TTS Model: %s, Arch: %s \n",modelfile_ttc.c_str(), detectedarch.c_str());
}
else if (detectedarch!="" && TTSCPP_SUPPORTED_ARCHITECTURES.find(detectedarch) != TTSCPP_SUPPORTED_ARCHITECTURES.end()) {
is_ttscpp_file = true;
printf("\nLoading TTS.CPP Model: %s, Arch: %s \n",modelfile_ttc.c_str(), detectedarch.c_str());
if(detectedarch=="kokoro")
@@ -674,7 +615,16 @@ bool ttstype_load_model(const tts_load_model_inputs inputs)
printf("\nTTS Load Error: Failed to initialize TTSCPP!\n");
return false;
}
} else { //outetts only
}
else if(is_qwen3tts_file)
{
if (!qwen3tts_runner.load_models(modelfile_ttc,modelfile_cts)) {
printf("\nQwen3TTS Load Error: %s\n", qwen3tts_runner.get_error().c_str());
return false;
}
}
else //outetts only
{
llama_model_params tts_model_params = llama_model_default_params();
llama_context_params tts_ctx_params = llama_context_default_params();
@@ -1253,11 +1203,74 @@ static tts_generation_outputs ttstype_generate_outetts(const tts_generation_inpu
}
}
static tts_generation_outputs ttstype_generate_qwen3tts(const tts_generation_inputs inputs)
{
tts_generation_outputs output;
if(!qwen3tts_runner.is_loaded())
{
printf("\nWarning: KCPP TTS not initialized! Make sure both TTS and WavTokenizer models are loaded.\n");
output.data = "";
output.status = 0;
return output;
}
else
{
qwen3_tts::tts_result result;
std::string prompt = inputs.prompt;
qwen3_tts::tts_params qwen3tts_params;
double ttstime = 0;
timer_start();
if(!tts_is_quiet)
{
printf("\nTTS Generating...");
}
// if (reference_audio.empty()) {
result = qwen3tts_runner.synthesize(prompt, qwen3tts_params);
// } else {
// fprintf(stderr, "Synthesizing with voice cloning: \"%s\"\n", text.c_str());
// fprintf(stderr, "Reference audio: %s\n", reference_audio.c_str());
// result = tts.synthesize_with_voice(text, reference_audio, params);
// }
if (!result.success) {
printf("\nError: TTS vocoder generation failed : %s\n", result.error_msg.c_str());
output.data = "";
output.status = 0;
return output;
}
ttstime = timer_check();
printf("\nTTS Generated audio in %.2fs.\n",ttstime);
last_generated_audio = save_wav16_base64(result.audio, result.sample_rate);
output.data = last_generated_audio.c_str();
output.status = 1;
last_generation_settings_audio_seed = 0;
last_generation_settings_speaker_seed = 0; //todo: add seed support
last_generation_settings_prompt = std::string(prompt);
total_tts_gens += 1;
return output;
// if (!qwen3_tts::save_audio_file(output_file, result.audio, result.sample_rate)) {
// fprintf(stderr, "Error: failed to save output file: %s\n", output_file.c_str());
// return 1;
// }
}
}
tts_generation_outputs ttstype_generate(const tts_generation_inputs inputs)
{
if (is_ttscpp_file) {
if (is_ttscpp_file)
{
return ttstype_generate_ttscpp(inputs);
} else {
}
else if(is_qwen3tts_file)
{
return ttstype_generate_qwen3tts(inputs);
}
else
{
return ttstype_generate_outetts(inputs);
}
}
+2 -2
View File
@@ -21,7 +21,7 @@ enum tts_arch {
ORPHEUS_ARCH = 3,
};
const std::map<std::string, tts_arch> SUPPORTED_ARCHITECTURES = {
const std::map<std::string, tts_arch> TTSCPP_SUPPORTED_ARCHITECTURES = {
{ "parler-tts", PARLER_TTS_ARCH },
{ "kokoro", KOKORO_ARCH },
{ "dia", DIA_ARCH },
@@ -38,7 +38,7 @@ static std::map<V, K> reverse_map(const std::map<K, V>& m) {
return r;
}
const std::map<tts_arch, std::string> ARCHITECTURE_NAMES = reverse_map(SUPPORTED_ARCHITECTURES);
const std::map<tts_arch, std::string> ARCHITECTURE_NAMES = reverse_map(TTSCPP_SUPPORTED_ARCHITECTURES);
struct generation_configuration {
generation_configuration(
+3 -3
View File
@@ -142,11 +142,11 @@ struct tts_runner * runner_from_file(const std::string & fname, int n_threads, g
return nullptr;
}
std::string arch = std::string(gguf_get_val_str(meta_ctx, arch_key));
if (SUPPORTED_ARCHITECTURES.find(arch) == SUPPORTED_ARCHITECTURES.end()) {
if (TTSCPP_SUPPORTED_ARCHITECTURES.find(arch) == TTSCPP_SUPPORTED_ARCHITECTURES.end()) {
fprintf(stdout,"%s failed for file %s. The architecture '%s' is not supported.", __func__, fname.c_str(), arch.c_str());
return nullptr;
}
tts_arch arch_type = SUPPORTED_ARCHITECTURES.at(arch);
tts_arch arch_type = TTSCPP_SUPPORTED_ARCHITECTURES.at(arch);
switch(arch_type) {
case PARLER_TTS_ARCH:
return parler_tts_from_file(meta_ctx, weight_ctx, n_threads, config, arch_type, cpu_only);
@@ -356,7 +356,7 @@ void quantize_gguf(const std::string & ifile, const std::string & ofile, struct
if (arch_key != -1) {
arch = std::string(gguf_get_val_str(meta_ctx, arch_key));
}
tts_arch arch_type = SUPPORTED_ARCHITECTURES.at(arch);
tts_arch arch_type = TTSCPP_SUPPORTED_ARCHITECTURES.at(arch);
if (params->quantize_type != GGML_TYPE_Q5_0 && params->quantize_type != GGML_TYPE_Q8_0 && params->quantize_type != GGML_TYPE_F16 && params->quantize_type != GGML_TYPE_Q4_0) {
fprintf(stdout, "Warning, %s is untested for quantization type '%d'. Use at your own risk.\n", arch.c_str(), params->quantize_type);