jamiepine/voicebox · error · RuntimeError

TTS output remained unstable after retrying smaller text chu

Error message

TTS output remained unstable after retrying smaller text chunks

What it means

Raised by generate_one() in backend/utils/chunked_tts.py when the runaway detector still flags a chunk's audio as unstable after the retry budget is exhausted — specifically when retry_depth >= MAX_RUNAWAY_RETRIES (2) or the chunk is already at/below MIN_RUNAWAY_RETRY_CHARS (100). It means the TTS backend keeps producing speech-then-silence-then-hallucination shapes even on small inputs, so further splitting is futile.

Source

Thrown at backend/utils/chunked_tts.py:268

    -------
    (audio, sample_rate) : Tuple[np.ndarray, int]
    """
    async def generate_one(
        chunk_text: str,
        chunk_seed: int | None,
        retry_depth: int = 0,
    ) -> tuple[np.ndarray, int]:
        chunk_audio, chunk_sr = await backend.generate(
            chunk_text,
            voice_prompt,
            language,
            chunk_seed,
            instruct,
        )

        if runaway_detector is not None and runaway_detector(chunk_audio, chunk_sr):
            if retry_depth >= MAX_RUNAWAY_RETRIES or len(chunk_text) <= MIN_RUNAWAY_RETRY_CHARS:
                raise RuntimeError(
                    "TTS output remained unstable after retrying smaller text chunks"
                )

            retry_max_chars = max(MIN_RUNAWAY_RETRY_CHARS, len(chunk_text) // 2)
            retry_chunks = split_text_into_chunks(chunk_text, retry_max_chars)
            if len(retry_chunks) <= 1:
                raise RuntimeError("Unable to split unstable TTS output for retry")

            logger.warning(
                "Detected unstable TTS output for %d chars; retrying as %d smaller chunks",
                len(chunk_text),
                len(retry_chunks),
            )
            retry_audio: list[np.ndarray] = []
            for i, retry_text in enumerate(retry_chunks):
                retry_seed = (
                    chunk_seed + ((retry_depth + 1) * 1000) + i
                    if chunk_seed is not None

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Shorten or rephrase the input text around the failing region and retry.
  2. Change the seed (per-request seed) so a different decoding path is taken.
  3. Inspect the voice prompt / reference audio quality; re-clone from cleaner samples if the cloned voice destabilizes generation.
  4. If recurring, report the failing text + engine + model version as a model bug; consider disabling the runaway_detector only if you accept unbounded output risk.
  5. Increase MAX_RUNAWAY_RETRIES in chunked_tts.py only as a last resort (it raises latency, not quality).

Example fix

// before: long input always trips runaway after retries
audio, sr = await generate_chunked_tts(backend, long_text, voice_prompt, ...)
// after: split the input externally and use a different seed
for part in manually_split(long_text):
    audio, sr = await generate_chunked_tts(backend, part, voice_prompt, seed=new_seed, ...)
Defensive patterns

Strategy: fallback

Validate before calling

from backend.utils.chunked_tts import MIN_RUNAWAY_RETRY_CHARS, DEFAULT_MAX_CHUNK_CHARS
import re

def chunk_is_safe_to_retry(text: str) -> bool:
    # avoid feeding chunks that are already at the retry floor or unsplittable
    if len(text) <= MIN_RUNAWAY_RETRY_CHARS:
        return False
    if len(text) > DEFAULT_MAX_CHUNK_CHARS:
        return False
    # must contain at least one breakable boundary so a retry split yields >1 piece
    return len(re.findall(r'\S{1,120}', text)) > 1 or ' ' in text

Try / catch

try:
    audio, sr = await generate_chunked_tts(backend, text, voice_prompt, ...)
except RuntimeError as e:
    if 'remained unstable' in str(e):
        # fall back to a different seed or shorter pre-split text
        audio, sr = await generate_chunked_tts(backend, shorten(text), voice_prompt, seed=alt_seed, ...)
    else:
        raise

Prevention

When it happens

Trigger: A pathological input chunk that triggers model EOS-miss/hallucination on every generation; a voice prompt that destabilizes the model; an engine bug producing runaway output; very long single sentences that cannot be split below 100 chars; deterministic seed always yielding runaway for the given text.

Common situations: Chatterbox/tada engines hallucinating after long silences; certain emoji/special-character inputs confusing the model; a cloned voice prompt with bad reference audio; model checkpoint regression; chunk that is one giant unbreakable token run.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/579f71527902b405. Report an issue: GitHub.