Repository navigation
piper_tts: RKNN duration-encoder ladder - #994
Merged
Merged
Conversation
Slice the voice's encoder + duration discriminator at enc_p.proj/dp.proj, right-pad to fixed token widths (export masks are pad-safe: 117-130 dB fp32), constant-fold the pad/mask shape chains with ort's BASIC optimizer (the toolkit's rewriter aborts on them), rewrite the folded attention layers' per-row relative-shift chains (Pad+Reshape+Slice, unrolled per row) into exact constant-index gathers -- the RKNN kernels compute those chains as uninitialized memory on the board (one layer alone: 0.4 dB end to end, 56 dB in the simulator), the gathers compute 40-48 dB on real silicon -- compile per-bin fp16 engines, and voice them from a sidecar that picks the smallest bin >= token_count and falls back to the ggml encoder above the widest. The slice is deterministic: all three piper 'scales' scalars feed sampling, the duration ceiling and the discriminator's flow noise, strictly downstream of the seam.
Owner
|
@wOvAN Thanks! All three PRs merged. |
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.
What
Moves the Piper duration encoder (text encoder + duration discriminator) onto the NPU, next to the already-merged RKNN decoder. The voice's own
enc_p.proj/dp.projseam is sliced, padded to fixed token widths, constant-folded, compiled per-bin, and served from a sidecar (rknn_duration_encoder.{h,cpp}) selected through the existingpiper_tts.exec_planoption:{"decoder":{"impl":"rknn","artifacts":{"main":"/root/tts-rknn/piper-lessac-w93.rknn"}}, "duration_encoder":{"impl":"rknn","artifacts":{"main":"enc_n128.rknn,enc_n192.rknn"}}}The sidecar walks the sorted ladder to the smallest engine
>= token_count; chunks above the widest engine fall back to the ggml encoder. Default ({}) is unchanged, no model_spec or catalog changes.Why the graph rewrite is the core of this PR
The first ladder (slice + fold, no rewrite) compiled and ran — and computed garbage: 9.9–16 dB against fp32 on the real NPU for every bin, while the PC simulator said 55.5–61.4 dB. Bisecting per-layer: one
attn_layers.0alone lands at 0.4 dB on the NPU and 56 dB in the simulator; a probe over just that layer's rel-value chain returns 506 NaN and fp16-saturated±65504values exactly in the last row's window (the flatten-Padtails are filled with uninitialized memory by the kernel lowering), and the rel-logit chain probe segfaults the runtime (rc=139). Standard Conv/MatMul/Softmax probes are fine; the breakage is exactly the unrolled per-row shift chains (Pad + Reshape + Slice, N copies) that the padded fixed-width fold leaves in every attention layer.So the converter now rewrites the two chains into exact constant-index gathers (
matrix_form_encoder), verified identical (maxdiff 0.0numerically, 121–131 dB fp32 gate against the raw dynamic slice):The pad-safety self-check runs on the rewritten graph, so the existing gate protects the surgery too.
Measured, RK3588 board (
en_US-lessac-medium, 557-char RU/EN text,--text-chunk-size 60, 8 chunks of 109–459 tokens)Engine fidelity vs fp32 (stats / conditioning, n ∈ {17, 61, N−1, N}):
The gathers' index initializers are constants, so the toolkit folds them into per-row Toeplitz expansions — O(N³) weight bytes. Narrow bins only; the docs say so.
Runtime, duration stage:
Quality: the fp16 duration noise sums across a chunk, so drift grows with width — latent frames move +7/+8/−6 % on bin-128 chunks (the scale of the flow's own seeded sampling: ggml-vs-ggml reruns at the same seed are byte-identical, so this is the engine) against −21…−23 % on the 384-bin chunks — a systematic ~25 % tempo shift. Whisper (
small) transcribes the baseline and ladder renders to the same text. That is why the recommended ladder is{128, 192}with the ggml fallback above 192 tokens: the fallback is a quality gate, not a convenience.Reproduce
Board matrix used above: run b = RKNN decoder only, run c = decoder +
duration_encoderladder; per-chunkduration_ms/latent_framescome from--logTIMING lines. The board probe (librknnrtdirectly, float pass_through inputs) is what gates this export — the PC simulator is explicitly not a gate for graphs containing these chains, per the docs section added here.Notes
scalesscalars feed sampling, the duration ceiling and the discriminator's flow noise — strictly downstream of the seam.exec_planstays opt-in;ctestsuites are unaffected.