Skip to content

MLX Kokoro TTS produces NaN / silent audio on Apple Silicon — all voices, all languages #2

Description

@duck-tape

Summary

Environment:

Mac: M3 Max (48 GB), macOS Sequoia 15
mlx-audio 0.4.4
mlx 0.31.2
Model: mlx-community/Kokoro-82M-bf16 (also tested Unravler/Kokoro-82M-4bit-MLX)
Wrapper: kokoro-tts-mlx 0.1.0 (github.com/flight505/kokoro_tts_mlx)
Python 3.12
Symptoms:

All audio output is NaN + zeros — no actual audio content
np.isnan() shows ~20-25% of samples are NaN, rest are 0.0
Affects ALL language codes (a, b, e, etc.) and ALL voices
The same model (hexgrad/Kokoro-82M) via PyTorch kokoro.KPipeline works perfectly (zero NaN, real audio)
Root cause — two bugs:

NaN output via kokoro-tts-mlx wrapper: KokoroTTS.generate() returns arrays where np.isnan() is true for ~20% of samples and the rest are silent zeros
Shape broadcast crash via native mlx-audio / generate_audio(): intermittent ValueError: [broadcast_shapes] Shapes (1,N,1) and (1,N+300,9) cannot be broadcast in istftnet.py:620 — the F0 sine generator produces misaligned tensor shapes
Key clue: The shape error always shows a delta of exactly 300 samples between the two dimensions (72000 vs 72300, 1200000 vs 1200300), suggesting a frame-size rounding bug in the MLX port of the Kokoro decoder (istftnet.py).

Workaround: PyTorch kokoro.KPipeline(lang_code='e', repo_id='hexgrad/Kokoro-82M') works perfectly on CPU at ~11× realtime on M3 Max.

Steps to reproduce

"""Minimal reproducer: MLX Kokoro NaN + shape crash bugs."""
import numpy as np
import json
from pathlib import Path
from huggingface_hub import hf_hub_download
from mlx_audio.tts.models.kokoro import KokoroPipeline, ModelConfig, Model
import dacite

MODEL = "mlx-community/Kokoro-82M-bf16"
config = dacite.from_dict(
ModelConfig,
json.loads(Path(hf_hub_download(MODEL, "config.json")).read_text()),
)
model = Model(config, repo_id=MODEL)

── Bug 1: NaN output (all languages) ──

for code, label, voice in [("a", "English", "af_heart"), ("e", "Spanish", "ef_dora")]:
pipe = KokoroPipeline(lang_code=code, model=model, repo_id=MODEL)
for r in pipe("Hello." if code == "a" else "Hola.", voice=voice):
a = np.array(r.audio)
print(f"{label}: NaN={np.isnan(a).sum()}/{a.shape[-1]} mean_abs={np.abs(a).mean():.4f} min={a.min():.4f} max={a.max():.4f}")
break
del pipe

── Bug 2: broadcast shape crash on longer text ──

pipe = KokoroPipeline(lang_code="e", model=model, repo_id=MODEL)
try:
for r in pipe("Hola, esto es una prueba un poco mas larga.", voice="ef_dora"):
print(f"Bug 2: no crash, shape={np.array(r.audio).shape}")
break
except ValueError as e:
print(f"Bug 2: CRASH — {e}")

Expected behavior

pipeline:

import numpy as np
from kokoro import KPipeline

pipeline = KPipeline(lang_code='e', repo_id='hexgrad/Kokoro-82M')
for r in pipeline('Hola, esto es una prueba.', voice='ef_dora'):
a = r.audio.numpy()
print(f"NaN: {np.isnan(a).sum()}")
print(f"mean_abs: {np.abs(a).mean():.4f}")
print(f"min/max: {a.min():.4f} / {a.max():.4f}")
print(f"shape: {a.shape}")
break

expected output:

NaN: 0
mean_abs: 0.0271
min/max: -0.4099 / 0.3390
shape: (72000,)

Actual behavior

10MB files with noaudio in them

Package version

flight505/kokoro_tts_mlx

macOS version and chip

26.5.2 (25F84) on Apple M3 Max

Python and MLX versions

Python: 3.12 mlx: 0.31.2 mlx-audio: 0.4.4

Additional context

And the model variants tried:

mlx-community/Kokoro-82M-bf16 (327 MB, bf16)
Unravler/Kokoro-82M-4bit-MLX (4-bit quantized) — same bug on both

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions