feat: anti-loop guards + KV cache configurabile
Docker / build (cpu, cpu) (push) Canceled after 0s
Docker / build (nvidia/cuda:12.9.2-devel-ubuntu22.04, nvidia/cuda:12.9.2-runtime-ubuntu22.04, cuda, cuda12) (push) Canceled after 0s
Docker / build (nvidia/cuda:13.3.1-devel-ubuntu22.04, nvidia/cuda:13.3.1-runtime-ubuntu22.04, cuda, cuda13) (push) Canceled after 0s
Docker / build (vulkan, vulkan) (push) Canceled after 0s

Prevenzione allucinazioni/loop infiniti (Qwen3-TTS autoregressivo):
- block_repeated_ngrams: maschera i token che ricreerebbero un n-gram gia visto (n=4)
- has_repeating_cycle: ferma la generazione su cicli periodici (periodo 1-16, 4 ripetizioni)
- stuck detector: token dominante nella finestra recente (4 occorrenze in 8 token)
- fallback EOS quando tutti i logits sono mascherati (evita NaN)
- KV cache talker configurabile (--kv-cache, default 8192): 4096 overflowava con
  reference lunghe + testi lunghi ("decode would overflow cache")
- parametri esposti via API (no_repeat_ngram_size, loop_max_period, loop_repeats,
  loop_window) e CLI (--no-repeat-ngram, --loop-period, --loop-repeats, --loop-window)
- Docker: TTS_KV_CACHE env (default 8192)

Validato: testo 2788 char che prima degenerava in loop ora sintetizza pulito
(141s via API, nessuna ripetizione).
This commit is contained in:
enne2
2026-08-18 15:10:02 +02:00
parent 18742ea656
commit 7942e61b3a
11 changed files with 250 additions and 10 deletions
+30
View File
@@ -53,6 +53,7 @@ static void print_usage(const char * prog) {
" --ref-spk and --ref-text, enables ICL clone mode)\n"
" --ref-text <path> Transcript file for the reference (enables ICL clone mode)\n"
" --max-new <n> Max new audio frames (default: 2048)\n"
" --kv-cache <n> Talker KV cache size in positions (default: 8192)\n"
" --codec-chunk-dur <f> Codec decode chunk duration in seconds (default: 24.0)\n"
" --stream-by-line Flush synthesis at each newline, one WAV header per line (-o '-')\n\n"
"Sampling:\n"
@@ -62,6 +63,10 @@ static void print_usage(const char * prog) {
" --top-k <n> Talker top-k (default: 50, 0 disables)\n"
" --top-p <f> Talker top-p (default: 1.0)\n"
" --rep-pen <f> Talker repetition penalty (default: 1.05)\n"
" --no-repeat-ngram <n> Block repeated n-grams (default: 4, 0 disables)\n"
" --loop-period <n> Cycle detector max period (default: 8, 0 disables)\n"
" --loop-repeats <n> Cycle detector repeats required (default: 4)\n"
" --loop-window <n> Cycle detector lookback window (default: 64)\n"
" --sub-temp <f> Sub-talker temperature (default: 0.9)\n"
" --sub-top-k <n> Sub-talker top-k (default: 50)\n"
" --sub-top-p <f> Sub-talker top-p (default: 1.0)\n\n"
@@ -92,6 +97,10 @@ struct Args {
int top_k;
float top_p;
float repetition_penalty;
int no_repeat_ngram_size;
int loop_max_period;
int loop_repeats;
int loop_window;
int subtalker_top_k;
float subtalker_top_p;
float subtalker_temperature;
@@ -100,6 +109,7 @@ struct Args {
bool clamp_fp16;
bool stream_by_line;
float codec_chunk_sec;
int talker_kv_size;
};
// Read all of stdin into a string. Binary mode on Windows so UTF-16 input
@@ -188,6 +198,10 @@ static bool parse_args(int argc, char ** argv, Args & a) {
a.top_k = 50;
a.top_p = 1.0f;
a.repetition_penalty = 1.05f;
a.no_repeat_ngram_size = 4;
a.loop_max_period = 16;
a.loop_repeats = 4;
a.loop_window = 64;
a.subtalker_do_sample = true;
a.subtalker_top_k = 50;
a.subtalker_top_p = 1.0f;
@@ -198,6 +212,7 @@ static bool parse_args(int argc, char ** argv, Args & a) {
// Chunk sentinel : qt_init resolves a non positive value to the
// library default.
a.codec_chunk_sec = 0.0f;
a.talker_kv_size = 8192;
for (int i = 1; i < argc; i++) {
const char * arg = argv[i];
if (std::strcmp(arg, "-h") == 0 || std::strcmp(arg, "--help") == 0) {
@@ -245,6 +260,14 @@ static bool parse_args(int argc, char ** argv, Args & a) {
a.top_p = (float) std::atof(argv[++i]);
} else if (std::strcmp(arg, "--rep-pen") == 0 && i + 1 < argc) {
a.repetition_penalty = (float) std::atof(argv[++i]);
} else if (std::strcmp(arg, "--no-repeat-ngram") == 0 && i + 1 < argc) {
a.no_repeat_ngram_size = std::atoi(argv[++i]);
} else if (std::strcmp(arg, "--loop-period") == 0 && i + 1 < argc) {
a.loop_max_period = std::atoi(argv[++i]);
} else if (std::strcmp(arg, "--loop-repeats") == 0 && i + 1 < argc) {
a.loop_repeats = std::atoi(argv[++i]);
} else if (std::strcmp(arg, "--loop-window") == 0 && i + 1 < argc) {
a.loop_window = std::atoi(argv[++i]);
} else if (std::strcmp(arg, "--sub-temp") == 0 && i + 1 < argc) {
a.subtalker_temperature = (float) std::atof(argv[++i]);
} else if (std::strcmp(arg, "--sub-top-k") == 0 && i + 1 < argc) {
@@ -259,6 +282,8 @@ static bool parse_args(int argc, char ** argv, Args & a) {
a.stream_by_line = true;
} else if (std::strcmp(arg, "--codec-chunk-dur") == 0 && i + 1 < argc) {
a.codec_chunk_sec = (float) std::atof(argv[++i]);
} else if (std::strcmp(arg, "--kv-cache") == 0 && i + 1 < argc) {
a.talker_kv_size = std::atoi(argv[++i]);
} else if (std::strcmp(arg, "-o") == 0 && i + 1 < argc) {
a.out_wav = argv[++i];
} else {
@@ -281,6 +306,7 @@ static int run(const Args & a) {
iparams.use_fa = a.use_fa;
iparams.clamp_fp16 = a.clamp_fp16;
iparams.codec_chunk_sec = a.codec_chunk_sec;
iparams.talker_kv_size = a.talker_kv_size;
qt_context * q = qt_init(&iparams);
if (!q) {
@@ -407,6 +433,10 @@ static int run(const Args & a) {
params.top_k = a.top_k;
params.top_p = a.top_p;
params.repetition_penalty = a.repetition_penalty;
params.no_repeat_ngram_size = a.no_repeat_ngram_size;
params.loop_max_period = a.loop_max_period;
params.loop_repeats = a.loop_repeats;
params.loop_window = a.loop_window;
params.subtalker_do_sample = a.subtalker_do_sample;
params.subtalker_temperature = a.subtalker_temperature;
params.subtalker_top_k = a.subtalker_top_k;