Repository navigation
tools/paradee: native Paradee-8M v1.0 TTS with phase lock and PyTorch parity gate - #64
Merged
lalalune merged 5 commits intoOct 14, 2026
Conversation
convert_paradee_pt_to_gguf.py reads pytorch/text_side.pt and pytorch/decoder.pt from sahilmahendrakar/Paradee-8M-v1.0 at the revision in pins.json, after checking size and sha256 of both checkpoints, config.json and tokenizer.json, and loads them with torch.load(weights_only=True). - weight_norm pairs are folded with torch._weight_norm, so the GGUF weights equal the reference module's effective weights. - Every state-dict key must be consumed (only the unused ALBERT pooler is skipped) and every width is checked against the hparams it writes. - The GGUF carries general.architecture=paradee, all widths, both learned styles, the vocabulary (symbols + ids), the sample rate, the phase-lock parameters, the input pins and the converter's git blob sha. - F32 by default (32 MB). The output is byte-reproducible; pins.json holds its sha256 and --check-output enforces it. pins.json also pins the paradee source commit and the training scripts the parity reference imports, the reference environment, the published samples and the test fixtures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The predictor, decoder and generator had Kokoro-82M's widths as literals (hid 512 and style 128 in the predictor, 514/1090/1024/512 and style 128 in the decoder, 512 -> 256 -> 128 in the generator, asr width 512). They now come from the GGUF tensor shapes, so width-scaled students of Kokoro (such as Paradee-8M) run on the same code: - predictor_forward(ctx, PredictorConfig, ...) and decoder_forward(ctx, DecoderConfig, ...) take a tensor namespace and the student-only options: the asr_proj GELU MLP, the trained InstanceNorm affine inside AdaIN1d, torch.round durations, a separate predictor style, a double-precision harmonic source, a canonical source STFT (exact zeros below the DFT rounding floor), cancellation hooks and stage outputs. - generator_forward(..., GeneratorConfig, ...) replaces the literals; the old entry points keep Kokoro's defaults. - KokoroLayerOptions (opt-in, scoped): per-tap SGEMM convolutions on Accelerate, register-tiled NEON Conv1d/ConvTranspose1d on the portable path, and a polynomial Snake. test-kokoro-layer-options checks them against the default kernels. - The iSTFT evaluates its basis once instead of per sample (same values, same sums). Kokoro-82M is unchanged bit for bit: kokoro-tts output for three sentences at speed 1.0 and 1.3, with Accelerate and with KOKORO_FORCE_GGML, and the kokoro-stage-dump outputs compare equal byte for byte before and after. The predictor still reads its style at ref_s + 512 rather than ref_s + 128 (kokoro/model.py uses ref_s[:, 128:]). That is a separate fix with its own parity run; it is marked in kokoro_predictor_forward and left as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ty gate
paradee_lib runs Paradee-8M v1.0 on the generalised tools/kokoro layers:
ALBERT 6x256 -> duration encoder (style 32) -> integer durations ->
F0/N; TextEncoder -> asr_proj (192 -> 512 -> 512, exact GELU); AdaIN decoder
(256, style 16) -> iSTFTNet generator (128 -> 64 -> 32) with the
deterministic source; then the phase lock.
paradee-phase-lock.cpp ports training/scripts/phase_lock.py lock(): STFT
1024/256 with reflect padding, bins 2-8 kHz, voiced frames (F0 > 60 Hz),
33-frame Hann smoothing of S*conj(E)/|E|, phase = angle(E) + angle(smoothed),
magnitude kept, out = s + iSTFT(change). Radix-2 FFT in double; no ONNX.
API (include/paradee.h): paradee_load_model, paradee_sample_rate,
paradee_phonemes_to_ids (adds the pads, rejects >510 symbols and symbols
outside the vocabulary), paradee_phonemes_to_symbol_ids (no pads or limit,
for chunkers), paradee_synthesize_ids (cancel polled between stages and in
the generator and lock loops), paradee_synthesize (struct form), and the
staged/decode/lock entry points the parity gate uses. paradee-tts writes a
WAV from misaki phonemes; paradee-stage-dump writes every stage.
LLAMA_BUILD_PARADEE (default ON, needs LLAMA_BUILD_KOKORO) and the
LLAMA_BUILD_MISAKI_G2P hook are added to the root and tools CMake, both ahead
of tools/omnivoice so the fused library can fold them in.
Parity gate: tools/dump_paradee_reference.py runs the pinned PyTorch
Paradee with a deterministic source on the misaki 0.9.4 phonemes of the
model card's samples/sentences.txt (committed as
tests/fixtures/sentences.misaki.json) and dumps every stage. It also writes
the self-contained lock fixture, and reports the log-mel distance to the
published seeded samples and the whisper.cpp WER (report only).
Tests:
- test-paradee-phase-lock (always runs): the native lock is within 3e-8 of
phase_lock.lock() in float64 (gate 1e-4).
- test-paradee-api (PARADEE_GGUF): ids, vocabulary and length errors, input
validation, determinism, speed, cancellation.
- test-paradee-parity (PARADEE_GGUF + PARADEE_REF_DIR), all 5 sentences:
- integer durations are identical;
- bert/d/dur/F0/N/asr are within 7e-6 x RMS;
- source and decoder output, each fed the reference's inputs, are within
3e-5 x RMS (gate 1e-3);
- the lock alone is within 4e-9 of float64 phase_lock (gate 1e-4);
- end-to-end waveform SNR is 58-76 dB (gate 35 dB).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…UF params Review fixes on top of the native Paradee port: - The v17 FFI bridge chunked with paradee_phonemes_to_ids, which the real library pads, limits to 510 symbols and makes strict on unknown symbols. Against the real paradee_lib every text longer than 510 phonemes, and every text with a symbol outside the vocab, failed with INVALID_ARG, and chunks carried embedded pads. Add paradee_phonemes_to_symbol_ids_lenient (unpadded, unlimited, unknown symbols dropped and counted, the documented ABI behaviour) and use it in the bridge; sync the contract header and fake. - Speed is bounded to [0.25, 4] in the library (durations scale with 1/speed, so a tiny speed made the decoder buffers unbounded); the FFI clamps to that range as its header now documents. - The loader bounds the widths, upsample rates, sample rate and phase-lock parameters it reads from the GGUF. - paradee_phase_lock checks `s` before copying from it. - LLAMA_BUILD_PARADEE no longer fails the configure when Kokoro is off (metal-transfer-validation builds with LLAMA_BUILD_KOKORO=OFF); it skips. - Tests: lenient mapping, speed bounds, concurrent synthesis on one model, FFI speed clamping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lalalune
force-pushed
the
claude/alpha-voice-paradee-native
branch
from
October 14, 2026 03:39
5c4bd32 to
1ef617d
Compare
Member
Author
|
Review fixes pushed in 1ef617d (rebased on the current base, which now has the ABI v17 bridge from #59):
Re-verified on macOS arm64: 🤖 Generated with Claude Code |
Resolve kokoro-decoder against the stage-boundary cancel from #45: keep the shared decoder_forward and pass the caller's cancel callback through DecoderConfig. Keep both new kokoro tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 14, 2026
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.
Adds Paradee-8M v1.0 as a native TTS in the fork, so Alpha Phone can ship Paradee instead of Kokoro-82M without ONNX. The model is sahilmahendrakar/Paradee-8M-v1.0 (Apache-2.0): 8.07M parameters, one voice (
af_heart), 24 kHz, distilled from Kokoro-82M.Upstream publishes Paradee as ONNX and as PyTorch weights. This PR uses the PyTorch weights:
tools/kokorolayers, which now take their widths from the tensors.Base is
eliza/develop-pin-ea8b3f2(7efee00, the gitlink on elizadevelop).What changes
tools/paradee/convert_paradee_pt_to_gguf.pyandpins.json.pytorch/text_side.pt,pytorch/decoder.pt,config.jsonandtokenizer.json. The pinned HF revision isc7f83f86; thev1.0tag points at8f34b01e, which differs only in README.md.torch.load(weights_only=True)and folds weight_norm withtorch._weight_norm.9a18ae71…3a254, enforced by--check-output.tools/kokoro: widths from the tensor shapes. The predictor, decoder and generator no longer hard-code 512/128/1024/514/1090/256.predictor_forward/decoder_forward/generator_forwardtake a config with the student-only options:torch.rounddurationstest-kokoro-layer-optionschecks them against the defaults.Kokoro-82M is unchanged bit for bit.
kokoro-ttsWAVs for three sentences at speed 1.0 and 1.3, with Accelerate and withKOKORO_FORCE_GGML, and thekokoro-stage-dumpoutputs are byte-identical before and after. The existing Kokoro tests pass.tools/paradee: native Paradee.paradee_lib, with the API the ABI work consumes:paradee_load_model,paradee_sample_rate,paradee_synthesize_idsparadee_phonemes_to_ids: adds the pads, rejects more than 510 symbols and any symbol outside the vocabularyparadee_phonemes_to_symbol_ids: unpadded and unlimited, for chunkersparadee_synthesize: struct formparadee-phase-lock.cpp): STFT 1024/256, 2–8 kHz, voiced frames, 33-frame smoothing, radix-2 FFT. No ONNX.paradee-ttsCLI.LLAMA_BUILD_PARADEE(default ON, needs Kokoro) and theLLAMA_BUILD_MISAKI_G2Phook in the root andtools/CMake. Both come beforetools/omnivoice, so the fused library can fold them in.Parity gate.
tools/dump_paradee_reference.pyruns the pinned PyTorch reference: torch 2.14.0, kokoro 0.9.4, and paradee source9c8b4d75with the sha256 of each training script pinned.samples/sentences.txt, committed as a fixture.SineGenhas no random phase and no noise, as intools/kokoro, and runs in float64. The source STFT is canonical float64.Results (5 sentences, macOS arm64)
phase_lock.lock()in float64Notes:
phase_lock.lock()differs from its own float64 evaluation by up to 1.1e-3, at bins where the source is nearly silent. The native lock is exactly that far from it and no further, so the 1e-4 gate is against float64.Single-thread speed. M4 Max, best of several runs, with the machine heavily loaded (load average 70–80):
PARADEE_EXACT_LAYERS=1)The portable path was 1.8x before the tiled kernels. These figures are not a clean benchmark. Linux x86_64 was not measured: no Linux host or running Docker daemon was available.
Tests run
-DGGML_METAL=OFF -DLLAMA_BUILD_TESTS=ON -DPARADEE_GGUF=… -DPARADEE_REF_DIR=….ctest -R "kokoro|paradee"passes 10/10, both with Accelerate and with the portable path (-DKOKORO_NO_ACCELERATE).paradee_lib,paradee-ttsand the testsLLAMA_BUILD_TOOLS=OFF:paradee_libtools/omnivoice.Known issues, unchanged here
kokoro_predictor_forwardreads the predictor style atref_s + 512, notref_s[128:]. That is two voice rows further, and past the end of the voice pack for the longest inputs; with a 256-floatref_sfile,kokoro-stage-dumpoutput varies from run to run. Fixing it changes Kokoro's audio, so it needs its own PR and parity run. It is marked in the code.tests/test-chat.cppdoes not build withLLAMA_BUILD_SERVER=OFF(mtmd.hnot found). This is unrelated and pre-existing.For the ABI work (
claude/alpha-voice-ffi-v17)That branch's contract double expects
paradee_phonemes_to_idsto return unpadded ids and to drop unknown symbols. This header follows the agreed contract instead: it adds the pads and rejects unknown symbols. The chunker should callparadee_phonemes_to_symbol_ids, which is unpadded, unlimited, and rejects unknown symbols. The other names the bridge uses all exist here:PARADEE_MAX_INPUT_IDS,paradee_synth_options,paradee_audio,paradee_synthesize,PARADEE_E_CANCELLED,PARADEE_E_OOM.AI disclosure: Claude Code wrote this change for the elizaOS maintainers, as part of moving Alpha Phone's voice to llama.cpp.
🤖 Generated with Claude Code