fix(kokoro, kitten_tts): match PyTorch reference semantics in five decoder paths (-2.5 dB level error, F0-path misalignment, window mismatch) - #859
Merged
Conversation
mchen04
force-pushed
the
fix/kokoro-port-fidelity
branch
from
July 26, 2026 06:53
c3a61f2 to
4b32e10
Compare
mchen04
force-pushed
the
fix/kokoro-port-fidelity
branch
from
July 28, 2026 22:56
4b32e10 to
cd64ce2
Compare
lucasnewman
reviewed
Jul 31, 2026
lucasnewman
reviewed
Jul 31, 2026
lucasnewman
requested changes
Jul 31, 2026
lucasnewman
left a comment
Collaborator
There was a problem hiding this comment.
@mchen04 Thanks! This looks reasonable, but please see the comments inline.
Collaborator
|
Note you'll also need to run the formatter with |
mchen04
pushed a commit
to mchen04/mlx-audio
that referenced
this pull request
Jul 31, 2026
mchen04
force-pushed
the
fix/kokoro-port-fidelity
branch
from
July 31, 2026 19:56
cd64ce2 to
bdf87fc
Compare
…coder paths 1. MLXSTFT.inverse: use window-squared (COLA) overlap-add normalization (normalized=True), the division torch.istft always performs. The plain-window default attenuates output by sum(w^2)/sum(w) = 0.75 (-2.5 dB constant). (kokoro; kitten_tts already had this) 2. MLXSTFT: use a periodic hann window for BOTH analysis and synthesis, as the torch reference does. The string path in dsp.stft resolves 'hann' to a symmetric window while dsp.istft builds a periodic one; the mismatch breaks exact COLA inversion (~3% ripple) and skews the harmonic-source STFT features. (kokoro + kitten_tts) 3. AdainResBlk1d: emulate ConvTranspose1d(padding=1, output_padding=1) with an unpadded transpose conv (built with padding=0) sliced [1:]. Left-zero-padding a padding=1 output shifted the residual branch one frame against the shortcut in predictor.F0[1], predictor.N[1] and decoder.decode[3] (F0_pred relRMSE 0.13 vs reference; 0.0000 after fix). kitten_tts right-padded (aligned) but zeroed the computed tail sample; both now use the exact form. 4. SineGen._f02sine: initial harmonic phase offsets are uniform [0,1) in the reference (torch.rand), not normal. (kokoro + kitten_tts) 5. interpolate1d(align_corners=False): clamp source coordinates at 0 like torch; negative coords made floor(x) = -1 and the gather read an out-of-range index (observed as the last frame) instead of repeating the first. (shared utility; affects kokoro + kitten_tts) Adds value-pinned regression tests (torch-derived constants) for the interpolation clamp, the transpose-conv alignment, and the iSTFT round-trip at unity gain, parametrized over both model ports. Also corrects the dsp.istft docstring, which stated default: True for normalized while the signature default is False.
mchen04
force-pushed
the
fix/kokoro-port-fidelity
branch
from
July 31, 2026 20:20
bdf87fc to
2c48bfb
Compare
Contributor
Author
|
this was my first open-source contribution, so thank for the guidance :) I'm glad to be able to help out with some issues i found |
This was referenced Aug 7, 2026
Merged
mchen04
added a commit
to mchen04/mchen04.github.io
that referenced
this pull request
Aug 7, 2026
The mlx-audio decoder fixes (Blaizzy/mlx-audio#859) had nowhere to live: the site only showed repositories I own. Adds an 'upstream' section between books and friends, and moves Valence there from the friends directory, where it was the one entry that was not mine. Seven nav items overflow the page at 390px, so the nav scrolls itself instead.
mchen04
added a commit
to mchen04/mchen04
that referenced
this pull request
Aug 7, 2026
Contributions to repositories I do not own were not represented: the mlx-audio decoder fixes (Blaizzy/mlx-audio#859) and the Valence patches.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Five places where the MLX iSTFTNet ports (kokoro, kitten_tts) diverge from the PyTorch reference (
hexgrad/kokoro). Each was found by instrumenting both stacks layer-by-layer on identical phoneme/style inputs and comparing every intermediate tensor. Two are clearly audible; the rest are smaller semantic mismatches found on the way. Value-pinned regression tests included for the three numerically-checkable fixes.1. Constant −2.5 dB output attenuation (
MLXSTFT.inverse, kokoro)dsp.istftdefaults tonormalized=False(overlap-add division by Σw).torch.istftalways divides by Σw² (least-squares inversion — unrelated to torch.istft's ownnormalizedargument, which is FFT scaling). For the periodic-hann, win = 4×hop configuration this is a constant amplitude factor of Σw²/Σw = 1.5/2.0 = 0.75 (−2.50 dB).Measured: MLX/torch waveform RMS ratio 0.738 (−2.64 dB) before, −0.16 dB after (residual = stochastic noise paths). This also explains why downstream users compensate with hardcoded gain hacks.
normalized=True.2. Symmetric/periodic window mismatch (
MLXSTFT, kokoro + kitten_tts)The torch reference uses
hann_window(win, periodic=True)for both analysis and synthesis. The MLX string path resolves"hann"to a symmetric window indsp.stftwhiledsp.istftbuilds a periodic one. The mismatch breaks exact COLA inversion (~3% reconstruction ripple — caught by the new round-trip test) and skews the harmonic-source STFT features fed to the generator.MLXSTFTnow materializes the periodic window once and passes it to both directions;dsp.stft's string behavior is untouched (no blast radius onto other callers).3. One-frame misalignment in
AdainResBlk1dupsample path (kokoro + kitten_tts)torch uses
ConvTranspose1d(k=3, stride=2, groups=dim_in, padding=1, output_padding=1), which maps T→2T by trimming one sample from the left of the unpadded (2T+1) transpose-conv output. The kokoro port ran the transpose conv withpadding=1(→ 2T−1) and left-zero-padded — shifting the residual branch one frame against the shortcut and replacing a computed tail sample with 0. kitten_tts right-padded (aligned) but still zeroed the computed tail sample.Both now build the pool conv with
padding=0(construction-time, no runtime state mutation) and slice[:, 1:, :]— exact by derivation and pinned against torch constants in the new test.This block sits in
predictor.F0[1],predictor.N[1], anddecoder.decode[3]— directly in the pitch/energy contour path. Measured on identical inputs in fp32 (kokoro):F0_predrelRMSE 0.134 (corr 0.975) before → 0.0000 (corr 1.0000) after.4.
SineGeninitial harmonic phase distribution (kokoro + kitten_tts)Reference draws initial phase offsets with
torch.rand(uniform [0,1)); the ports usedmx.random.normal. Uniform is the correct full-circle random phase.5.
interpolate1dnegative source coordinates (sharedtts/models/interpolate.py)With
align_corners=False, the source coordinate for the first outputs is negative (e.g. −0.498 at scale 300, as used by the harmonic-source upsampler). torch clamps to 0; herefloor(x) = −1and the gather read an out-of-range index (observed as the last frame), so the first half-frame interpolated against the end of the signal. Clamped to 0.Note: this is a shared utility — the clamp corrects output for every linear/
align_corners=Falsecaller (kokoro, kitten_tts, soprano), not just kokoro.Tests
mlx_audio/tts/tests/test_istftnet_fidelity.py(new, parametrized over kokoro + kitten_tts):torch.nn.ConvTranspose1d(k=3, s=2, p=1, op=1)constantsMLXSTFTtransform→inverse round trip at unity gain (old code reconstructs at 0.75×)test_interpolate.py: the existingalign_corners=Falsecase asserted only the output shape (which is how the wrap bug survived); it now pins values fromtorch.nn.functional.interpolate— first element must be 1.0, old code produced 2.5.Full
mlx_audio/tts/testssuite passes (586 passed;TestSparkTTSModel::test_initfails identically on clean main — pre-existing, unrelated).Validation beyond unit tests
kokoro(fp32 weightsprince-canuma/Kokoro-82M, torch 2.12.1, kokoro 0.9.4, misaki 0.9.4): all decoder-upstream tensors match at relRMSE ≤ 2e-3 (bf16) / 0.0 (fp32); predicted durations identical.Scope notes
dsp.istftcallers (soprano, vocos, deepfilternet, lfm_audio, …) still use the plain-Σw default and were not audited here; if they were trained againsttorch.istft, they may carry the same 0.75× attenuation. Flagging rather than changing, since each model has its own training convention.