Skip to content

Pocket TTS (Kyutai zero-shot voice cloning): technical investigation — runtime & performance characteristics #263

Description

@jiangzhuo

Summary

Technical investigation into running Pocket TTS (Kyutai CALM, zero-shot voice cloning) on sokuji's local-inference stack. The same int8 ONNX models were measured across three runtimes:

  • Browser WASM (onnxruntime-web): ~0.6× realtime — not viable for real-time.
  • Native CPU (Python sherpa-onnx ~4×, Node onnxruntime-node ~2–2.4×): real-time with headroom.
  • The model is English-only.

PoC branches: feat/pocket-tts-playground-poc (browser WASM), feat/pocket-tts-electron-native-poc (native Node in Electron). Both dev-gated, not wired into the production pipeline.

What is Pocket TTS

Kyutai CALM (Continuous Audio LM), zero-shot voice cloning: a reference clip → voice embedding → conditioned autoregressive generation. Pipeline = mimi_encoder (reference → latents) → flow_lm_main (~100M AR backbone, per-frame) + flow_lm_flow (consistency/flow head) → mimi_decoder (latents → 24 kHz PCM), plus text_conditioner. int8 ONNX bundle ≈ 140 MB (5 onnx + tokenizer + bundle.json + BOS npy). BOS-prepend is required for faithful timbre.

What was tested

Same int8 graphs, three runtimes: (1) onnxruntime-web WASM in a dev playground; (2) sherpa-onnx native via Python; (3) onnxruntime-node native inside Electron (our TS pocketInferenceCore).

Performance

runtime host RTF (cached)
onnxruntime-web WASM (1 thread) browser ~0.6×
sherpa-onnx WASM (4 threads) browser ~0.65×
onnxruntime-node plain Node ~3.2×
onnxruntime-node Electron child_process ~2.0–2.4× headless · ~1.75–2.35× full app
sherpa-onnx plain Python ~4× (1–8 threads)

Per-frame, per-stage (same int8 graphs):

stage WASM native node ratio
flow_lm_main (AR backbone) ~56 ms ~22 ms ~2.5×
mimi_decoder (vocoder) ~38 ms ~6 ms ~6×

Why WASM ≪ native

int8 kernel quality dominates:

  1. SIMD / VNNI — native MLAS uses AVX2 / AVX-512 / AVX-VNNI (single-instruction int8 dot-products); WASM SIMD is fixed 128-bit, no VNNI.
  2. Kernels — hand-tuned assembly (native) vs generic Emscripten C++ (WASM).
  3. Threading — native multi-threads the encoder/decoder; WASM is forced single-thread (threaded WASM OOMs on Pocket's KV-caches).
  4. WASM overhead — linear-memory bounds-checks, growable memory, JS boundary.

The conv-heavy mimi_decoder is hit hardest (~6×). This is an intrinsic WASM↔native gap for int8 NN inference — not fixable in WASM; the only browser path to native-class speed is WebGPU (no Pocket/Mimi WebGPU port exists today).

Native host options (for future use)

A. Python — sherpa-onnx. Fastest (~4×), simplest API (OfflineTts + GenerationConfig), ships its own C++ engine + threading + model packaging. Would run as a sidecar (Python runtime to bundle).

B. Node — onnxruntime-node (this PoC). ~2–2.4×, reuses our TS pocketInferenceCore (one core; web + node hosts via Tensor dependency injection). Integrates into Electron with no extra runtime via child_process.fork + ELECTRON_RUN_AS_NODE=1 (Electron's own Node). Hard-won learnings:

  • Use child_process.fork, not utilityProcess — the latter intermittently SIGTRAP-crashed while hosting the native addon.
  • serialization: 'advanced' is required to pass Float32Array over IPC.
  • Cap intraOpNumThreads — the default (= all logical cores) oversubscribes the tiny per-frame matmuls and ~halves throughput.
  • onnxruntime-node ships napi-v6 → ABI-stable across Node & Electron; @electron/rebuild is not needed.

Platform / runtime viability

  • Electron desktop: ✅ native (Python or Node), real-time.
  • Browser extension: ❌ WASM ceiling ~0.6×; no WebGPU port.
  • Constraint: English-only model.

Not covered by this investigation

Production packaging (Forge/asar native .node unpack, ~140 MB model distribution), LocalInferenceClient pipeline wiring, reference-voice UX, GPU execution providers, non-English, streaming, and model licensing (Kyutai/CALM weights + the KevinAHM ONNX port — to verify before any distribution).

References

  • Spec: docs/superpowers/specs/2026-06-18-pocket-tts-electron-native-poc-design.md
  • Plan: docs/superpowers/plans/2026-06-18-pocket-tts-electron-native-poc.md
  • Branches: feat/pocket-tts-playground-poc (browser WASM), feat/pocket-tts-electron-native-poc (native Node)
  • Key commits: cfa07803 (Tensor DI), f46ad7e9 (node bench), 4adc1a0d (Electron host), 6008dc02 (child_process fix)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions