Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tools/omnivoice/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ if(TARGET mtmd)
endif()
add_dependencies(elizainference_static omnivoice-version)

# FFI tests (ABI v17).
# FFI tests (ABI v18).
# eliza-inference-test-abi links the real libelizainference: ABI
# string, probes, cancel/evict no-ops, and
# (opt-in, ELIZA_TEST_ASR_DIR) a real-model
Expand Down
29 changes: 26 additions & 3 deletions tools/omnivoice/include/eliza-inference-ffi.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* libelizainference FFI ABI v17.
* libelizainference FFI ABI v18.
*
* (Banner tracks ELIZA_INFERENCE_ABI_VERSION below; the per-version history is
* at the end of this header preamble, newest first.)
Expand Down Expand Up @@ -134,6 +134,16 @@ extern "C" {
* load and refuses to bind if they disagree.
*
* Changelog:
* v18: text-model context capacity resolved before stream admission.
* - `eliza_inference_llm_context_capacity(ctx, requested_context,
* n_gpu_layers, &out_capacity, &err)` loads the resident text model
* (if needed) and returns the context size `llm_stream_open` would
* use: `requested_context`, else ELIZA_LLM_N_CTX, else 8192, clamped
* to the GGUF training context. No decode context is allocated.
* - `llm_stream_open` now shares that resolution and rejects a
* non-positive capacity instead of creating a zero-sized context.
* Additive symbol; no existing signature changes. A v17 library lacks
* the symbol, so loaders must treat it as absent.
* v17: Paradee-8M TTS with native misaki G2P, plus cancellation and
* eviction for every TTS head and for ASR. All additions are new
* symbols; no existing signature changes.
Expand Down Expand Up @@ -254,9 +264,9 @@ extern "C" {
* v7: real Silero VAD (same symbol surface as v6).
* v6: fused wake-word, speaker, diarizer.
*/
#define ELIZA_INFERENCE_ABI_VERSION 17
#define ELIZA_INFERENCE_ABI_VERSION 18

/* Returns a static, NUL-terminated string of the form "17" matching
/* Returns a static, NUL-terminated string of the form "18" matching
* ELIZA_INFERENCE_ABI_VERSION at the time the library was built. The
* pointer is owned by the library — do NOT free. */
const char * eliza_inference_abi_version(void);
Expand Down Expand Up @@ -1152,6 +1162,19 @@ typedef struct {
/* Opaque streaming-LLM session. One per active generation. */
typedef struct EliLlmStream EliLlmStream;

/* Resolve the streaming context capacity against the resident text model
* before token admission (ABI v18). Loads the model with n_gpu_layers when
* necessary. requested_context <=0 uses ELIZA_LLM_N_CTX or 8192; the result
* is clamped to the GGUF training context exactly as stream_open does.
* On success, pass *out_capacity explicitly to stream_open to preserve this
* decision even if the environment changes. No decode context is allocated. */
int eliza_inference_llm_context_capacity(
EliInferenceContext * ctx,
int32_t requested_context,
int32_t n_gpu_layers,
int32_t * out_capacity,
char ** out_error);

/* Open a session anchored to `ctx`. Loads the bundle's text GGUF on first
* open and reuses it across sessions. Returns NULL on failure with
* `*out_error` populated. Close exactly once via `_close`. */
Expand Down
46 changes: 41 additions & 5 deletions tools/omnivoice/src/eliza-inference-ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3796,6 +3796,40 @@ int eliza_inference_llm_kv_quant_supported(void) {
return 1;
}

static int eliza_llm_context_capacity(
const llama_model * model, int32_t requested_context) {
const int n_ctx_train = llama_model_n_ctx_train(model);
int n_ctx = requested_context > 0
? requested_context
: eliza_int_env_or_default("ELIZA_LLM_N_CTX", 8192);
if (n_ctx_train > 0 && n_ctx > n_ctx_train) n_ctx = n_ctx_train;
return n_ctx;
}

int eliza_inference_llm_context_capacity(
EliInferenceContext * ctx,
int32_t requested_context,
int32_t n_gpu_layers,
int32_t * out_capacity,
char ** out_error) {
if (!ctx || !out_capacity) {
eliza_set_error(out_error,
"[libelizainference] llm_context_capacity: ctx and out_capacity are required");
return ELIZA_ERR_INVALID_ARG;
}
std::lock_guard<std::mutex> lock(ctx->llm_mutex);
const int rc = eliza_load_llm_model_locked(ctx, n_gpu_layers, out_error);
if (rc != ELIZA_OK) return rc;
const int capacity = eliza_llm_context_capacity(ctx->llm_model, requested_context);
if (capacity <= 0) {
eliza_set_error(out_error,
"[libelizainference] llm_context_capacity: context capacity must be positive");
return ELIZA_ERR_INVALID_ARG;
}
*out_capacity = capacity;
return ELIZA_OK;
}

EliLlmStream * eliza_inference_llm_stream_open(
EliInferenceContext * ctx,
const eliza_llm_stream_config_t * cfg,
Expand Down Expand Up @@ -3827,11 +3861,13 @@ EliLlmStream * eliza_inference_llm_stream_open(
* MTP engine's target context so both decode identically (same n_ctx /
* batch / threads / flash-attn / KV-quant). */
llama_context_params cparams = llama_context_default_params();
const int n_ctx_train = llama_model_n_ctx_train(model);
int n_ctx = cfg->context_size > 0
? cfg->context_size
: eliza_int_env_or_default("ELIZA_LLM_N_CTX", 8192);
if (n_ctx_train > 0 && n_ctx > n_ctx_train) n_ctx = n_ctx_train;
const int n_ctx = eliza_llm_context_capacity(model, cfg->context_size);
if (n_ctx <= 0) {
eliza_set_error(out_error,
"[libelizainference] llm_stream_open: context capacity must be positive");
delete stream;
return nullptr;
}
cparams.n_ctx = (uint32_t) n_ctx;
cparams.n_batch = (uint32_t) eliza_int_env_or_default("ELIZA_LLM_N_BATCH", 512);
cparams.n_ubatch = cparams.n_batch;
Expand Down
19 changes: 16 additions & 3 deletions tools/omnivoice/tests/ffi-abi.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* tools/omnivoice/tests/ffi-abi.c
*
* C11 test against the real libelizainference (ABI v17). Always runs:
* - ABI string "17" and the v17 capability probes
* C11 test against the real libelizainference (ABI v18). Always runs:
* - ABI string "18" and the v17 capability probes
* - v18 llm_context_capacity argument validation and the no-text-GGUF error
* - cancel_tts / asr_cancel are no-ops with nothing in flight
* - mmap_evict / mmap_acquire("tts") on a bundle with no TTS weights
* - Paradee entries before any load (or NOT_IMPLEMENTED when not built)
Expand Down Expand Up @@ -49,7 +50,7 @@ static double now_ms(void) {

static void test_probes_and_noops(void) {
char * err = NULL;
CHECK(strcmp(eliza_inference_abi_version(), "17") == 0);
CHECK(strcmp(eliza_inference_abi_version(), "18") == 0);
CHECK_RC(eliza_inference_asr_supported(), 1);
#if defined(ELIZA_TEST_EXPECT_PARADEE)
CHECK_RC(eliza_inference_paradee_supported(), 1);
Expand All @@ -72,6 +73,18 @@ static void test_probes_and_noops(void) {
clear_err(&err);
CHECK_RC(eliza_inference_mmap_evict(ctx, "tts", &err), ELIZA_OK);
CHECK_RC(eliza_inference_mmap_evict(ctx, "asr", &err), ELIZA_OK);
/* v18: capacity resolution validates its arguments and reports a bundle
* without a text GGUF instead of returning a capacity. */
int32_t capacity = -12345;
CHECK_RC(eliza_inference_llm_context_capacity(NULL, 0, 0, &capacity, &err), ELIZA_ERR_INVALID_ARG);
CHECK(capacity == -12345);
clear_err(&err);
CHECK_RC(eliza_inference_llm_context_capacity(ctx, 0, 0, NULL, &err), ELIZA_ERR_INVALID_ARG);
clear_err(&err);
CHECK_RC(eliza_inference_llm_context_capacity(ctx, 4096, 0, &capacity, &err), ELIZA_ERR_BUNDLE_INVALID);
CHECK(capacity == -12345);
CHECK(err != NULL);
clear_err(&err);
/* No TTS weights and no configured Kokoro/Paradee head. */
CHECK_RC(eliza_inference_mmap_acquire(ctx, "tts", &err), ELIZA_ERR_BUNDLE_INVALID);
clear_err(&err);
Expand Down
4 changes: 2 additions & 2 deletions tools/omnivoice/tests/ffi-paradee-asr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// - eliza_inference_asr_cancel during prefill and during decode, over a
// scripted ASR engine that polls the same abort callback llama_decode
// uses
// - ABI string "17"
// - ABI string "18"
//
// The bridge is compiled into this TU (ELIZA_INFERENCE_TEST_HOOKS) against
// the Paradee / misaki doubles in tests/paradee-contract/, so no weights are
Expand Down Expand Up @@ -86,7 +86,7 @@ static int synth_text(EliInferenceContext * ctx, const std::string & text, size_
}

static void test_abi_and_probes() {
CHECK(std::string(eliza_inference_abi_version()) == "17");
CHECK(std::string(eliza_inference_abi_version()) == "18");
CHECK_RC(eliza_inference_paradee_supported(), 1);
CHECK_RC(eliza_inference_asr_supported(), 1);
CHECK_RC(eliza_inference_asr_stream_supported(), 0);
Expand Down
Loading