You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ICL voice cloning randomly degenerates (no EOS, 24s garbage) — root cause: ref_text not covering full ref_audio; confirmed not a port bug via official PyTorch A/B #921
ICL voice cloning (ref_audio + ref_text) randomly produces degenerate output: ~24s of noisy garbage instead of the expected ~5.6s, the decoder never emits EOS, and step-by-step logprob instrumentation shows it falls into a single-token attractor (token 302 sampled 59 consecutive times at >90% probability; EOS median rank 495/3072, entering top-50 in only 6 of 300 steps). When it happens, no sampling parameter rescues it (temperature 0.2–0.9, top_k, top_p, repetition_penalty all ineffective or worse).
Root cause found: it is not a port bug — it is misaligned ICL prefill caused by ref_text not covering the full reference audio. With text tokens aligned to only the first ~6s of a 10s clip while codec frames span the whole clip, the model sits at the edge of its input distribution and randomly collapses into the degenerate attractor.
Evidence (official PyTorch A/B)
I ran the official Qwen/Qwen3-TTS inference package (qwen_tts, pinned transformers==4.57.3, official Qwen/Qwen3-TTS-12Hz-1.7B-Base weights, CPU) with the same text, same ref_audio, same truncated ref_text:
Setup
ref_text
Result
Official PyTorch, ICL ×3
truncated (covers ~6s of 10s audio)
3/3 degenerate 23.92s
Official PyTorch, x-vector-only ×3
no ref_text
3/3 normal (6.2–6.6s)
MLX stock code, ICL ×5
full STT transcription of ref_audio
5/5 normal (5.2–6.2s)
The official implementation flies off more consistently than the MLX port under the same misaligned input (0/3 vs 1/3 success) — so the port is faithful, arguably slightly more robust. Everything else I compared line-by-line matched: prefill construction (text+codec_pad, codec+tts_pad, sum-of-codebooks, role tokens, codec_prefix offset), decode loop, RoPE position ids (official get_rope_index degenerates to 1D cumsum, same as MLX).
Fix / usage guidance
The fix is correct usage, not code: ref_text must be a complete transcription of ref_audio (or omitted so the built-in auto-transcription path handles it). Verified stable with 5/5 runs on stock qwen3_tts.py.
Possible upstream improvements (optional):
Document the requirement prominently in the voice-cloning docs/example.
Runtime sanity check: estimate spoken duration of ref_text vs ref_audio duration and warn on large mismatch.
Route "ref_audio without ref_text" through STT automatically (already exists in generate.py for some paths).
Possibly related: #910 (pace acceleration) and #914 — misaligned prefill may produce a spectrum of pathologies depending on how truncated the text is; my truncation was severe (~60% coverage), producing collapse rather than drift.
Environment
mlx-audio PyPI 0.5.0 (stock qwen3_tts.py, no local patches)
Activity
linear-code commentedon Aug 28, 2026
NEY-104