From 9930bfa392f8f4e1b2473dec6c9e462b960c6254 Mon Sep 17 00:00:00 2001 From: Kazeia Team Date: Mon, 13 Apr 2026 23:00:25 +0200 Subject: [PATCH] LLM: enable Qwen3-4B NPU (21 tok/s) in service pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ExecuTorchLlmEngine: eval_mode 0 (our .pte is kv-mode, not hybrid) - KazeiaService: call llm.load() after TTS init; try/catch falls back to echo mode if the runner or .pte are missing. Pipeline on device: STT(WhisperHybridEngine) → [VoiceCommands → LLM] → TTS(Qwen3TtsEngine). Validated on OnePlus Pad 3: LLM ready in ~8 s, gen 21.3 tok/s, RSS 1.76 GB in the qnn_llama_runner subprocess (out-of-process from the Kazeia app). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../main/java/com/kazeia/llm/ExecuTorchLlmEngine.kt | 7 ++++--- .../main/java/com/kazeia/service/KazeiaService.kt | 13 +++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/kazeia-android/app/src/main/java/com/kazeia/llm/ExecuTorchLlmEngine.kt b/kazeia-android/app/src/main/java/com/kazeia/llm/ExecuTorchLlmEngine.kt index 833c47c..29bb0b0 100644 --- a/kazeia-android/app/src/main/java/com/kazeia/llm/ExecuTorchLlmEngine.kt +++ b/kazeia-android/app/src/main/java/com/kazeia/llm/ExecuTorchLlmEngine.kt @@ -9,7 +9,8 @@ import java.io.File /** * LLM Engine using ExecuTorch + QNN backend via subprocess. * Calls qnn_llama_runner binary with root access. - * Qwen3-0.6B at ~90 tok/s on NPU (Snapdragon 8 Elite). + * Current tablet config: Qwen3-4B KV-mode, ~18-20 tok/s on Hexagon V79 (Snapdragon 8 Elite), + * TTFT 0.9 s, RSS 1.76 GB. Previously tested Qwen3-0.6B at ~76 tok/s. */ class ExecuTorchLlmEngine( private val onLog: ((String) -> Unit)? = null @@ -179,7 +180,7 @@ if [ -n "${'$'}SYSTEM_ARGS" ]; then --prompt "${'$'}PROMPT" \ --temperature ${'$'}TEMP \ --seq_len ${'$'}SEQ_LEN \ - --eval_mode 1 + --eval_mode 0 else exec ./qnn_llama_runner \ --model_path hybrid_llama_qnn.pte \ @@ -191,7 +192,7 @@ else --prompt "${'$'}PROMPT" \ --temperature ${'$'}TEMP \ --seq_len ${'$'}SEQ_LEN \ - --eval_mode 1 + --eval_mode 0 fi """.trimIndent() diff --git a/kazeia-android/app/src/main/java/com/kazeia/service/KazeiaService.kt b/kazeia-android/app/src/main/java/com/kazeia/service/KazeiaService.kt index e973a7d..82eaa24 100644 --- a/kazeia-android/app/src/main/java/com/kazeia/service/KazeiaService.kt +++ b/kazeia-android/app/src/main/java/com/kazeia/service/KazeiaService.kt @@ -516,10 +516,14 @@ class KazeiaService : Service() { )) } - // LLM: disabled for debugging — echo mode - _loadingState.value = LoadingState(50, "LLM (echo mode)…") + // LLM: Qwen3-4B on Hexagon V79 via qnn_llama_runner. + _loadingState.value = LoadingState(50, "LLM Qwen3-4B NPU…") llm = ExecuTorchLlmEngine { msg -> log(msg) } - log("LLM: disabled for debug, echo mode active") + try { + llm.load("${KazeiaApplication.MODELS_DIR}/qwen3-4b", com.kazeia.core.LlmConfig()) + } catch (e: Exception) { + log("LLM load failed: ${e.message} — falling back to echo mode") + } _loadingState.value = LoadingState(80, "Audio…") // Audio @@ -539,7 +543,8 @@ class KazeiaService : Service() { addMessage(ChatMessage( role = ChatMessage.Role.KAZEIA, - text = "Bonjour, je suis Kazeia. Mode perroquet actif." + text = if (llm.isLoaded()) "Bonjour, je suis Kazeia." + else "Bonjour, je suis Kazeia. Mode perroquet actif." )) _pipelineState.value = PipelineState.Idle