Skip to content

libelizainference ABI v17: Paradee + misaki TTS entries, ASR cancel/supported, TTS cancel and evict for Kokoro/Paradee - #59

Merged
lalalune merged 2 commits into
eliza/develop-pin-ea8b3f2from
claude/alpha-voice-ffi-v17
Oct 14, 2026
Merged

lalalune merged 2 commits into
eliza/develop-pin-ea8b3f2from
claude/alpha-voice-ffi-v17

Conversation

@lalalune

Copy link
Copy Markdown
Member

ABI v17 for the fused libelizainference. This PR changes only tools/omnivoice/ (FFI header, bridge, CMake, tests). The base is eliza/develop-pin-ea8b3f2, the branch the 7efee003 pin is on.

What changes

  • Paradee entries (ELIZA_ENABLE_PARADEE):
    • New entries: eliza_inference_paradee_{supported,load,sample_rate,g2p_kind,phonemize,synthesize_alloc,synthesize_phonemes_alloc}. PCM is released with eliza_inference_free_pcm and phonemes with eliza_inference_free_string.
    • Text goes through native misaki G2P. The phoneme string is cut into chunks of at most 510 ids: at sentence ends first, then clause punctuation, then spaces, then a hard slice. Each chunk is padded with 0 at both ends, synthesized, and the PCM is concatenated.
    • The model and lexicon sit on the ctx under paradee_mutex.
    • paradee_lib and misaki_g2p are linked into both the shared and the static elizainference, but only when both targets exist.
  • TTS cancel:
    • eliza_inference_cancel_tts now also stops Kokoro and Paradee.
    • Each engine has its own flag and clears it when its own call starts. An OmniVoice call that starts while a Paradee call is running cannot clear a cancel aimed at the Paradee call.
    • Paradee checks the flag through the paradee_synth_options.cancel callback and between chunks.
    • kokoro_lib has no hook inside the graph, so Kokoro checks the flag before and after its forward pass. A cancelled Kokoro call returns ELIZA_ERR_CANCELLED with no audio.
  • ASR:
    • New eliza_inference_asr_cancel(ctx, err) sets an atomic flag without taking asr_mutex. The flag is checked:
      • by the llama abort callback on asr_lctx (prefill and decode graphs)
      • between mtmd chunks (the chunk loop now uses mtmd_helper_eval_chunk_single)
      • before every decode token
    • The flag is cleared at the start of each call, once that call holds asr_mutex. So a cancel sent while a second call is waiting for the lock stops the call that holds it, not the waiting one. The tests cover this.
    • eliza_inference_asr_supported() returns 1, because this library is only built with mtmd.
    • The streaming asr_stream_* stubs are unchanged.
  • mmap_evict("tts"):
    • Also frees the Kokoro model and voice, the Paradee model, and the misaki lexicon. Their paths are kept.
    • The next synth or mmap_acquire("tts") reloads them. mmap_acquire("tts") now succeeds with no OmniVoice weights present when a Kokoro or Paradee head is configured.
    • The OmniVoice GGUF picker now skips tts/paradee/ and paradee* files.
    • mmap_evict("asr") is unchanged.
  • Header: the banner says v17, the changelog has v17 (and a note for v16), and there is a region-semantics block and a table with one probe per capability.

Contract with the sibling PRs

This PR builds against the paradee-native and misaki-g2p public headers. Those are not merged yet. The exact declarations the bridge uses are in tools/omnivoice/tests/paradee-contract/{paradee.h,misaki-g2p.h}:

  • eliza_paradee::paradee_load_model, paradee_sample_rate, paradee_phonemes_to_ids, paradee_synthesize(model, input_ids_with_pads, paradee_synth_options{speed, cancel, cancel_user_data}, audio, err), PARADEE_MAX_INPUT_IDS = 512, and the status codes PARADEE_E_CANCELLED and PARADEE_E_OOM.
  • eliza_misaki::misaki_load_lexicon(path, espeak_dir_or_null, err), misaki_get_fallback_kind, misaki_phonemize, and MISAKI_FALLBACK_{NONE,RULES,ESPEAK}.

If the real headers differ, the Paradee section of the bridge has to be adjusted when this is rebased. Also, tools/CMakeLists.txt and the root CMakeLists.txt must add paradee and misaki-g2p before omnivoice, the same way kokoro is ordered, or ELIZA_ENABLE_PARADEE stays off.

Tests (macOS arm64 host, Metal build)

  • eliza-inference-test-paradee-asr: PASS, 518 checks. It compiles the bridge with ELIZA_INFERENCE_TEST_HOOKS against scripted stand-ins for the contract. It covers:
    • load errors and idempotent load
    • g2p kind and phonemize
    • exact sample counts
    • chunking at the sentence, clause and hard-slice levels
    • cancel during a synth, plus checks that the flag does not carry into the next call and that one engine's flag does not affect another's
    • a race of load, synth, cancel, evict and acquire across 6 threads × 150 operations, with every return code either OK or CANCELLED
    • evict then reload via synth, phonemize and acquire; evict while a synth is in flight
    • ASR cancel during prefill (abort callback inside a chunk), during decode (per-token check), and inside one long decode step
    • cancel while a second call waits for the ASR lock
  • eliza-inference-test-abi (real libelizainference.dylib): PASS. ABI string "17", the probes, cancel and evict as no-ops, and Paradee returning NOT_IMPLEMENTED in a build without Paradee.
    • Opt-in real-model ASR cancel uses ELIZA_TEST_ASR_DIR. The pair used is eliza-1-asr.gguf (sha256 bca25981…a1971) and eliza-1-asr-mmproj.gguf (41a342b5…6603d), with LibriSpeech 1188-133604-0022.wav.
    • Metal and CPU both returned ELIZA_ERR_CANCELLED for an early cancel and a late cancel, and the next run gave the same transcript.
    • CPU cancel latency is usually under 70 ms, but in one run it was about 2 s. That is consistent with the mtmd audio-encoder pass, which has no abort hook (tools/mtmd is not changed here). The machine was heavily loaded (load average around 80), so these timings are noisy.
  • Android: the bridge passes an NDK 29 aarch64-linux-android26 -fsyntax-only -Wall -Wextra compile, both with and without ELIZA_ENABLE_PARADEE. No APK was built and nothing was run on a device or emulator.
  • Kokoro cancel and evict paths compile (kokoro_lib was linked with espeak-ng) but were not run: there is no Kokoro GGUF on this host.

Consumers

  • Loaders that pin the ABI string have to move to "17" together with the gitlink bump. That is the ffi-bindings.ts expected version and any JNI check.
  • The new symbols only add to the ABI.

🤖 Generated with Claude Code

…evict)

ABI v17 adds these symbols and changes no existing signature:

- Paradee-8M TTS with native misaki G2P, guarded by ELIZA_ENABLE_PARADEE.
  Paradee is linked when both the paradee_lib and misaki_g2p targets
  exist, into both the shared and the static elizainference. New entries:
  paradee_supported, load, sample_rate, g2p_kind, phonemize,
  synthesize_alloc and synthesize_phonemes_alloc. The phoneme string is
  split into chunks of at most 510 ids (sentence end, then clause, then
  space, then hard slice). Each chunk is padded with 0 at both ends, and
  the chunks' PCM is joined into one buffer. The model and lexicon live on
  the ctx under their own mutex.
- cancel_tts now also cancels Kokoro and Paradee. Each engine has its own
  flag and clears it when a call starts. Paradee checks its flag through
  the synth cancel callback and between chunks. Kokoro checks before and
  after the forward pass.
- New asr_cancel: an atomic flag set without asr_mutex. It is wired to the
  llama abort callback on asr_lctx and checked between mtmd chunks (which
  now use eval_chunk_single) and before each decode token. A cancelled
  call returns ELIZA_ERR_CANCELLED. New asr_supported returns 1.
- mmap_evict("tts") now also frees Kokoro, Paradee and the lexicon. It
  keeps their paths, and the next synth or mmap_acquire("tts") reloads
  them. acquire("tts") succeeds with no OmniVoice weights present when a
  Kokoro or Paradee head is configured. The OmniVoice GGUF picker skips
  tts/paradee/ and paradee* files.
- The header documents the regions and lists one probe per capability.

Tests:
- eliza-inference-test-paradee-asr builds the bridge against scripted
  stand-ins for the paradee/misaki contract. It covers chunking, a
  load/synth/cancel/evict race across threads, evict then reload, and ASR
  cancel during prefill and during decode.
- eliza-inference-test-abi checks the real library. It covers the ABI
  string "17", the probes, the no-op calls, and an opt-in real-model ASR
  cancel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cancel and race tests call CHECK from worker threads, but the
counters were plain ints. TSan reported the races, the check total
varied between runs (509/514/518), and a lost ++g_failures could turn a
failure into a PASS. The counters are now std::atomic<int>.

eliza-inference-test-abi and eliza-inference-test-paradee-asr were
registered with ctest but had no label, so CI (ctest -L main) built them
and never ran them. Both now carry the main label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lalalune
lalalune merged commit 236bfd3 into eliza/develop-pin-ea8b3f2 Oct 14, 2026
33 of 38 checks passed
lalalune pushed a commit that referenced this pull request Oct 14, 2026
…ABI v18)

Add eliza_inference_llm_context_capacity, which loads the resident text
model if needed and returns the context size llm_stream_open would use
(requested, else ELIZA_LLM_N_CTX, else 8192, clamped to the GGUF training
context) without allocating a decode context. llm_stream_open shares the
same resolution and rejects a non-positive capacity.

The new symbol bumps ELIZA_INFERENCE_ABI_VERSION to 18 (v17 is the
Paradee/ASR-cancel surface from #59) with a v18 changelog entry. The
FFI ABI tests expect "18" and cover the new entry's argument validation
and missing-text-GGUF error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lalalune added a commit to elizaOS/eliza that referenced this pull request Oct 14, 2026
…rd empty phonemize output

Move the native/llama.cpp gitlink from 7efee0037 to 236bfd34c (elizaOS/llama.cpp#59),
a fast-forward of three additive ABI v17 commits. The TypeScript binding now
declares ABI 17, so the pinned header has to match; with the old pin the
header-alignment test fails in lanes that check out the submodule.

paradeePhonemize no longer asks toArrayBuffer for a zero-length view when the
library returns an empty phoneme string; the buffer is still freed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lalalune

Copy link
Copy Markdown
Member Author

Review before merge (ABI v17 bridge).

I checked:

  • Lifetimes and locking. Each head is loaded, evicted and synthesized under its own mutex. Evict waits for an in-flight synth. The cancel flags are per-engine atomics, and nothing takes a lock on the cancel path. The ASR abort callback only reads an atomic on the ctx.
  • Logging. The new code does not log transcripts or text. out_error gets the misaki/paradee err string, so the real misaki library must not put input words into it.
  • ONNX. There is none on this path.
  • Other consumers. Kokoro behaviour changes only after mmap_evict("tts"): the model is freed and reloads lazily on the next call. mmap_acquire("tts") still fails on an empty bundle, which the iOS smoke test expects.

Sanitizer runs on macOS arm64, CPU build:

  • ThreadSanitizer: 5 runs of eliza-inference-test-paradee-asr. The bridge is clean. The only reports were races on the test's own g_checks/g_failures counters, which CHECK updates from worker threads. Because of that the check total varied (509/514/518), and a lost failure increment could have reported PASS.
  • ASan+UBSan: eliza-inference-test-paradee-asr and eliza-inference-test-abi are clean.

Pushed 76ed981:

  • The counters are now std::atomic<int>.
  • Both FFI tests now carry the ctest main label. Before this they were built in CI but never run, because CI runs ctest -L main. They now pass in CI, for example ubuntu-cpu x64: abi 19 checks, paradee-asr 518 checks.

CI on this head:

For the sibling integration: the in-progress misaki header uses a C API (misaki_g2p_open / misaki_g2p_phonemize). It emits \n-separated chunks of at most 510 symbols, and its fallback values are SPELL=0 and ESPEAK=1. That differs from tests/paradee-contract/misaki-g2p.h. Whichever PR lands second has to reconcile the bridge with that header and keep the doubles in sync.

🤖 Generated with Claude Code

lalalune added a commit to elizaOS/eliza that referenced this pull request Oct 14, 2026
…tegration

The fork commit ce4dacc2 (elizaOS/llama.cpp branch
claude/alpha-voice-android-integration) is fork main with ABI v17
(elizaOS/llama.cpp#59, merged) plus the open misaki-g2p (#63) and
paradee-native branches. Move it to the merged fork commit once those
merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lalalune added a commit to elizaOS/eliza that referenced this pull request Oct 14, 2026
…on libelizainference ABI v17 (#36158)

* feat(local-inference): standalone Qwen3-ASR and Paradee host workers on libelizainference ABI v17

Add host-asr-worker (Qwen3-ASR-0.6B Q8_0, no text model) and switch
host-tts-worker from Kokoro-82M to Paradee-8M with native misaki G2P.
Both workers pin their assets by SHA-256, run native calls on an engine
thread so {op:"cancel"} reaches eliza_inference_asr_cancel /
eliza_inference_cancel_tts mid-pass, and evict their region when idle.

Bind every ABI v17 symbol (paradee_*, free_pcm, asr_supported,
asr_cancel) through a separate optional dlopen and accept v16 only when
the family is absent. ffi-stub/ffi.h is now the v17 header.

The app serves POST /api/tts/paradee (libelizainference-paradee) and
POST /api/asr/libelizainference (local-inference) from one owner-session
route module; /api/tts/kokoro is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(local-inference): pin llama.cpp to the ABI v17 fork merge and guard empty phonemize output

Move the native/llama.cpp gitlink from 7efee0037 to 236bfd34c (elizaOS/llama.cpp#59),
a fast-forward of three additive ABI v17 commits. The TypeScript binding now
declares ABI 17, so the pinned header has to match; with the old pin the
header-alignment test fails in lanes that check out the submodule.

paradeePhonemize no longer asks toArrayBuffer for a zero-length view when the
library returns an empty phoneme string; the buffer is still freed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(local-inference): gate host ASR with pinned Silero VAD

---------

Co-authored-by: Shaw <thefutureisaweirdplace@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Shaw <shawgotbags@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant