Skip to content

First ctx.completion() throws Exception in HostFunction: <unknown> on Samsung Exynos (qwen3-0.6b / TinyLlama / gemma4-e2b-lite, both with and without no_extra_bufts: true) #336

Description

@eckonode

Repro environment

  • llama.rn versions tested: 0.12.0-rc.4 (cpp pin: build b8665, commit b863507) and 0.12.0-rc.9 (cpp pin: build b8827, commit 5e6c0e1). All RC versions in the rc.5 → rc.9 range reproduce the same JS-layer error.
  • React Native: 0.81.5, Hermes engine
  • Expo SDK: 54
  • Devices reproducing the crash (both Samsung Exynos):
    • Samsung Galaxy A50 (SM-A505U), Exynos 9610 (Cortex-A73 + A53, ARMv8.0-A baseline), 3.5 GB RAM, Android 11 / API 30
    • Samsung Galaxy A25 5G (SM-A256U), Exynos 1280 (s5e8825, Cortex-A78 + A55, ARMv8.2-A + dotprod), 5.5 GB RAM
  • APK type: debug build, sideloaded via adb install -r. JS bundle served by npx expo start Metro at runtime (no in-APK bundle).
  • Snapdragon / MediaTek devices: not yet tested. We are explicitly trying to make the Exynos path work rather than gate it off, so cross-platform falsification is pending hardware.

Failure mode

The very first call to ctx.completion(...) after a successful cold init fails on the JS side with:

[ReasoningService] completeWithReasoning failed: [Error: Exception in HostFunction: <unknown>]
[ReasoningService] JSI HostFunction crash detected — disabling reasoning for the rest of this session.

After this throw, the process stays alive but every subsequent ctx.completion(...) returns immediately because the consuming app trips a JSI-corruption circuit-breaker (our F5 — see "What's been ruled out" below). Any ctx.completion(...) calls already in flight at the moment of the throw eventually time out at our 30 s wrapper timeout.

The <unknown> is React Native + Hermes' generic fallback wrapper for any C++ throw whose .what() it can't translate (RN #34730). PR #318's resultGenerator try/catch does not catch this throw — <unknown> persists in rc.9. Either the throw is not in the resultGenerator path, is not std::exception-derived, or fires before the resultGenerator is reached.

Reproducible steps

  1. npm install llama.rn@^0.12.0-rc.9. Native artifacts download via the rc.5+ postinstall script.
  2. Call initLlamaContext with these params (matches what we use in production):
    await initLlamaContext({
      model: toNativeModelPath(modelUri),
      n_ctx: 2048,
      use_mmap: true,
      use_mlock: false,
      n_gpu_layers: 0,
      no_extra_bufts: true,   // optional; reproduces with `false` too
    });
  3. Load any of: qwen3-0.6b-Q4_K_M, tinyllama-1.1b-chat-v1.0.Q4_K_M, gemma-4-E2B-it-UD-IQ3_XXS. We've seen the same failure on all three.
  4. Cold init succeeds. loadModel logs Context initialized with n_seq_max = 1 and attachThreadpoolsIfAvailable: Attached ggml threadpool (n_threads=4, n_threads_batch=4). Latency: ~3.3 s on A50 with TinyLlama 1.1B Q4_K_M, ~10.8 s on A25 with gemma4-e2b-lite IQ3_XXS.
  5. Call ctx.completion({ messages: [{role:'system',content:...},{role:'user',content:...}], n_predict: 512, temperature: 0.1, stop: [...] }) with a ~150–250-token prompt.
  6. loadPrompt logs Input processed: n_past=N, embd.size=M for the prompt batch.
  7. ~30 ms after the prompt-processed log, the Promise rejects with [Error: Exception in HostFunction: <unknown>].

Expected: completion returns a token stream, OR if it errors, the JS-side error has a meaningful .what() string.

Observed: opaque <unknown> on every variant tested.

Native tombstone

The opaque <unknown> is the catchable-exception code path. There's a second path that surfaces the underlying abort directly: llama.rn 0.12.0-rc.4 + no_extra_bufts: true produces a SIGABRT in lm_ggml_abort from llm_graph_context::build_inp_embd, with the process dying. This is a separate bug specific to rc.4's older cpp pin (b8665 / b863507); it is fixed in rc.9's pin (b8827 / 5e6c0e1). I include it because (a) the abort site gives us the only native stack trace we have for this device/model combination, and (b) the call stack lands inside the same decode → process_ubatch → build_graph → build_inp_embd path that the <unknown> variant exits through.

05-02 10:07:54.019 F libc:  Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 13270 (mqt_v_native), pid 30806
05-02 10:07:54.578 F DEBUG: pid: 30806, tid: 13270, name: mqt_v_native  >>> com.eckonode.callibear <<<
05-02 10:07:54.578 F DEBUG: signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
05-02 10:07:54.578 F DEBUG: ABI: 'arm64'
05-02 10:07:54.578 F DEBUG: Build fingerprint: 'samsung/a50sq/a50:11/RP1A.200720.012/A505USQSKDWB1:user/release-keys'
05-02 10:07:54.589 F DEBUG: backtrace:
05-02 10:07:54.589 F DEBUG:   #00 pc 0000000000089cd0  /apex/com.android.runtime/lib64/bionic/libc.so (abort+164)
05-02 10:07:54.589 F DEBUG:   #01 pc 0000000000303e0c  base.apk!librnllama_v8.so (lm_ggml_abort+224) BuildId: 42fed8ee605fc9fabb2de8a1bb5d8e18c892eb6d
05-02 10:07:54.590 F DEBUG:   #02 pc 000000000044957c  base.apk!librnllama_v8.so (llm_graph_context::build_inp_embd(lm_ggml_tensor*) const+1612)
05-02 10:07:54.590 F DEBUG:   #03 pc 00000000007648a4  base.apk!librnllama_v8.so (llm_build_llama<false>::llm_build_llama(llama_model const&, llm_graph_params const&)+160)
05-02 10:07:54.590 F DEBUG:   #04 pc 00000000004fc798  base.apk!librnllama_v8.so
05-02 10:07:54.590 F DEBUG:   #05 pc 00000000004fc1bc  base.apk!librnllama_v8.so (llama_model::build_graph(llm_graph_params const&) const+1296)
05-02 10:07:54.590 F DEBUG:   #06 pc 000000000041d934  base.apk!librnllama_v8.so (llama_context::process_ubatch(llama_ubatch const&, llm_graph_type, llama_memory_context_i*, lm_ggml_status&)+908)
05-02 10:07:54.591 F DEBUG:   #07 pc 000000000041f2f0  base.apk!librnllama_v8.so (llama_context::decode(llama_batch const&)+2092)
05-02 10:07:54.591 F DEBUG:   #08 pc 0000000000717664  base.apk!librnllama_v8.so (rnllama::llama_rn_context_completion::nextToken()+200)
05-02 10:07:54.591 F DEBUG:   #09 pc 000000000071844c  base.apk!librnllama_v8.so (rnllama::llama_rn_context_completion::doCompletion()+56)
05-02 10:07:54.591 F DEBUG:   #10 pc 000000000006cda8  base.apk!librnllama_jni_v8.so  BuildId: d6357eb959de320ac4efc2c34747afc1eec2ae72
05-02 10:07:54.591 F DEBUG:   #11 pc 0000000000091cf0  base.apk!librnllama_jni_v8.so
05-02 10:07:54.592 F DEBUG:   #12 pc 000000000009af94  base.apk!librnllama_jni_v8.so
05-02 10:07:54.592 F DEBUG:   #13 pc 00000000000eb7a8  libc.so (__pthread_start(void*)+64)
05-02 10:07:54.592 F DEBUG:   #14 pc 000000000008bc8c  libc.so (__start_thread+64)
05-02 10:07:58.963 I Zygote: Process 30806 exited due to signal 6 (Aborted)

The crashing thread is named mqt_v_native (React Native's mqt JSI worker thread). On the A50 (Cortex-A53, ARMv8.0-A baseline) the picker correctly selects librnllama_v8.so (compiled with -march=armv8-a, no extensions) — confirmed via RNLlama.java:190 reading /proc/cpuinfo features fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid (no asimddp, no i8mm, no fphp, no atomics).

Variant matrix (every combination tested on the A50)

llama.rn no_extra_bufts Outcome JS error string
1 rc.4 not set F5 trip, app survives <unknown>
2 rc.4 true SIGABRT in build_inp_embd, process dies <unknown> (then abort)
3 rc.9 not set F5 trip, app survives <unknown>
4 rc.9 true F5 trip, app survives <unknown>

A25 5G reproduces variants 1, 3, 4 with the same JS-layer signature (different active model: gemma4-e2b-lite — confirms the bug is not model-specific).

What's been ruled out (single-variable on-device experiments)

  • Wrong .so variant: picker logic in RNLlama.java:190 correctly selects librnllama_jni_v8 for Cortex-A53 (no asimddp) and librnllama_jni_v8_2_dotprod for Cortex-A55. Confirmed live via nativeloader logs in logcat.
  • Hexagon backend leakage: isHexagonSupported() correctly returns false on Samsung Exynos (no Qualcomm hint, no Snapdragon SOC pattern, no Hexagon codename). HTP libs are extracted to app_rnllama-htp/ but never dlopen'd.
  • OOM-killed worker thread: no_extra_bufts: true measurably reduces the Android lmkd (lowmemorykiller) cascade severity around the trip from ~8 procs killed/second to ~4 procs killed/second on the A50, but the F5 trip persists. Memory pressure is a contributing factor, not the root cause.
  • Concurrent JSI calls: duplicate loadPrompt:215 [DEBUG] Input processed log lines from threads in the same millisecond come from llama.cpp's internal multi-threaded prompt batching (n_threads=4), not parallel ctx.completion() calls.
  • Model-specific bug: three different reasoning models reproduce — qwen3-0.6b-Q4_K_M, tinyllama-1.1b-chat-Q4_K_M, gemma-4-E2B-it-UD-IQ3_XXS.
  • RAM-tier issue: 3.5 GB (A50) and 5.5 GB (A25 5G) both reproduce.
  • llama.rn version range: rc.4 through rc.9 all reproduce. PR fix: handle exception in createPromiseTask resultGenerator #318 (rc.5+) is in the codepath but does not catch this throw.

Asks

  1. Is there a known crash on first ctx.completion() on Samsung Exynos that we've missed in the issue tracker? We searched but didn't find a direct match.
  2. Can the <unknown> wrapper be widened to expose the actual .what() for whatever throw path is firing here? PR fix: handle exception in createPromiseTask resultGenerator #318 was supposed to do that for resultGenerator throws — it doesn't seem to apply on this device class. We're happy to test a debug build that adds extra logging at any catch point you suggest.
  3. We haven't tested with n_threads: 1 yet (default appears to be 4 from attachThreadpoolsIfAvailable: Attached ggml threadpool (n_threads=4, n_threads_batch=4)). Worth testing whether this is a llama.cpp threadpool issue specific to multi-core Exynos? Happy to run that experiment if it'd be useful.
  4. Is anyone else hitting this on Exynos? We searched the issue tracker and PocketPal llama.cpp has updated its support for Android Gpus, can we follow up? #107 lists Galaxy Note10 Lite (Exynos 9810, sibling of our 9610) on its known-crash catalogue — suggesting this isn't device-unique.

I have detailed logs (full per-variant logcat, complete tombstone, /proc/cpuinfo for both devices, build IDs for the loaded .sos) ready to share if any of it would help narrow this. Happy to test patches against the Exynos hardware we have plugged in.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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