# Rebuild Kazeia-Engine en CPU-only — guide dev **Pourquoi** : la lib actuelle `libkazeia_engine.so` + `libkazeia_tts.so` linke `libllama.so` qui dépend de `libggml-hexagon.so`, qui dlopen `libcdsprpc.so` (FastRPC). La policy SELinux Oplus/Qualcomm sur SM8750 interdit à `untrusted_app` d'ouvrir `/dev/fastrpc-cdsp` → crash in-app au load du backend hexagon. Détails dans `LLM_INTEGRATION.md §0` et `TTS_INTEGRATION.md §0`. **Objectif** : rebuild `libllama.so` + `libggml*.so` + `libkazeia_engine.so` + `libkazeia_tts.so` **sans backend Hexagon**. Coût perf mesuré : LLM +1 s par tour conversation, TTS +0 s perceptible (le talker prefill HTP n'apportait que ~50 ms vs CPU pur). **STT** : non concerné, déjà sur voie ORT-QAIRT autorisée pour `untrusted_app`. `libkazeia_stt.so` peut rester comme livrée. --- ## Sources Tout est dans le repo Kazeia-Engine : ``` /opt/Kazeia-engine/ ├── ql/ # fork ggml-org/llama.cpp commit f0fe1058b │ ├── ggml/ # ggml core + backends (CPU + Hexagon) │ └── (le reste = llama.cpp upstream + qwen35_compile patch HTP) ├── dist/ │ ├── jni/ │ │ ├── kazeia_engine_jni.cpp # LLM bridge │ │ ├── kazeia_tts_jni.cpp # TTS bridge │ │ ├── tts_engine.{h,cpp} # TTS core │ │ ├── speaker_encoder.{h,cpp} # clonage vocal (CPU-only de base) │ │ ├── kazeia_mel.{h,cpp} # FFT mel partagé │ │ ├── cp_inference.cpp / sampler.cpp / kazeia_text_tokenizer.cpp │ │ ├── EngineLlmEngine.kt # façade LLM │ │ └── TtsEngine.kt # façade TTS │ ├── build_kazeia_tts.sh # build script TTS (linke libllama + ggml de dist/lib/) │ ├── CMakeLists.txt # build script LLM (libkazeia_engine.so) │ └── lib/ # libs précompilées AVEC Hexagon (à ne PAS utiliser in-app) └── CLAUDE.md # contexte projet (perf, modèles, pièges) ``` ## Étape 1 — Rebuild ggml + llama.cpp en CPU-only L'option `GGML_HEXAGON=OFF` existe déjà dans le CMakeLists (`/opt/Kazeia-engine/ql/ggml/CMakeLists.txt` ligne 268, default OFF mais activée par les scripts de build précédents). ```bash cd /opt/Kazeia-engine/ql rm -rf build-android-cpu mkdir build-android-cpu && cd build-android-cpu NDK=/opt/Kazeia/android-ndk-r27d cmake .. \ -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ -DANDROID_PLATFORM=android-31 \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS="-O3 -march=armv8.6-a+i8mm+bf16+dotprod+fp16" \ -DCMAKE_C_FLAGS="-O3 -march=armv8.6-a+i8mm+bf16+dotprod+fp16" \ -DGGML_HEXAGON=OFF \ -DGGML_OPENMP=OFF \ -DGGML_VULKAN=OFF \ -DLLAMA_BUILD_EXAMPLES=OFF \ -DLLAMA_BUILD_TESTS=OFF \ -DLLAMA_BUILD_SERVER=OFF \ -DBUILD_SHARED_LIBS=ON cmake --build . -j$(nproc) --target llama ggml ggml-base ggml-cpu ``` Résultat dans `ql/build-android-cpu/`: - `bin/libllama.so` - `ggml/src/libggml.so` - `ggml/src/libggml-base.so` - `ggml/src/libggml-cpu.so` **Plus de** `libggml-hexagon.so` ni `libggml-htp-v79.so` à pousser dans `jniLibs/`. ## Étape 2 — Rebuild libkazeia_engine.so et libkazeia_tts.so ```bash cd /opt/Kazeia-engine/dist NDK=/opt/Kazeia/android-ndk-r27d CXX=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android31-clang++ # Pointer le linker vers les nouvelles libs CPU-only NEW_LIB=/opt/Kazeia-engine/ql/build-android-cpu mkdir -p b-jni-cpu # LLM $CXX -std=c++17 -O3 -fPIC \ -march=armv8.6-a+i8mm+bf16+dotprod+fp16 \ -I./include -shared jni/kazeia_engine_jni.cpp \ -L$NEW_LIB/bin -L$NEW_LIB/ggml/src \ -lllama -lggml -lggml-base \ -llog -ldl -lm \ -o b-jni-cpu/libkazeia_engine.so # TTS DEC=/opt/Kazeia/kazeia-tts-decoder-ggml $CXX -std=c++17 -O3 -fPIC \ -march=armv8.6-a+i8mm+bf16+dotprod+fp16 \ -I./include -I$DEC/src \ -shared \ jni/kazeia_tts_jni.cpp jni/tts_engine.cpp jni/cp_inference.cpp \ jni/sampler.cpp jni/kazeia_text_tokenizer.cpp \ jni/speaker_encoder.cpp jni/kazeia_mel.cpp \ $DEC/build-android-engine/libqwen3tts-decoder.a \ -L$NEW_LIB/bin -L$NEW_LIB/ggml/src \ -lllama -lggml -lggml-base -lggml-cpu \ -llog -ldl -lm \ -o b-jni-cpu/libkazeia_tts.so ``` ## Étape 3 — Vérifier les DT_NEEDED (sanity) ```bash NDK=/opt/Kazeia/android-ndk-r27d $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readelf -d \ b-jni-cpu/libkazeia_engine.so | grep NEEDED ``` Attendu : `libllama.so + libggml.so + libggml-base.so + libc++_shared.so + libdl/libm/libc/liblog`. **Pas de** `libggml-hexagon.so` ni `libggml-cpu.so` direct (chargé via libggml en runtime). ## Étape 4 — jniLibs/arm64-v8a/ pour APK Pousser **6 libs au lieu de 8** : | Fichier | Origine | Taille approx (CPU-only sera plus petit) | |---|---|---:| | `libkazeia_engine.so` | `b-jni-cpu/` | 60 KB | | `libkazeia_tts.so` | `b-jni-cpu/` | ~900 KB | | `libllama.so` | `ql/build-android-cpu/bin/` | ~25 MB (sans Hexagon) | | `libggml.so` | `ql/build-android-cpu/ggml/src/` | ~500 KB | | `libggml-base.so` | idem | ~6 MB | | `libggml-cpu.so` | idem | ~4.5 MB | | `libc++_shared.so` | NDK r27d ou existant projet | 1.8 MB | **Plus** `libggml-hexagon.so`, **plus** `libggml-htp-v79.so` à pousser. Pour STT, `libkazeia_stt.so` + `libonnxruntime.so` (via Maven `onnxruntime-android-qnn:1.24.3`) restent inchangés. ## Étape 5 — Tester in-app 1. `System.loadLibrary("kazeia_engine")` doit passer (déjà testé OK même avec libllama legacy, donc avec la nouvelle ça doit l'être aussi) 2. `EngineLlmEngine.load(...)` doit passer **sans SIGABRT** (c'est le test critique vs avant) 3. `generate(...)` doit produire du texte cohérent et tenir le `nThreads` demandé (vérifier avec `top -H -p ` que 6 threads sont actifs ~90 % CPU) 4. Idem TTS : `TtsEngine(...)` charge sans crash, `synthesize(...)` produit un WAV audible Si étape 2 plante quand même, capturer logcat + dmesg (filtrer `avc:` pour SELinux) et remonter. ## Étape 6 — Mesure perf attendue (pour valider la voie) Sur SM8750, workload Kazeia typique (prompt 24 tok / réponse 64 tok, Qwen3.5-4B Q4_0) : | Métrique | HTP (CLI standalone) | CPU-only (attendu in-app) | Delta | |---|---:|---:|---:| | Prefill | 0.4 s | ~1.3 s | +0.9 s | | Decode 64 tok @ 9.8 t/s | 6.5 s | 6.5 s | 0 | | **Tour LLM** | **6.9 s** | **7.8 s** | **+0.9 s** | | Tour TTS (3 s audio) | 8.0 s | 8.1 s | +0.1 s | | RAM peak LLM | 2.4 GB | ~2.0 GB | -400 MB (pas de contexte HTP) | Si vos mesures s'éloignent significativement (decode < 5 t/s ou prefill > 3 s), c'est probable le piège affinity Android (cf `LLM_INTEGRATION.md §8`). ## Étape 7 — Flipper les flags quand validé ```kotlin // adb shell content update ... ou setprop selon mécanisme flag retenu llm_engine = "lib" tts_engine = "lib" ``` Le fallback gracieux que tu as câblé reste actif tant que le default est sain. --- ## Points d'attention 1. **`libcdsprpc.so` et `libQnnHtp*` doivent rester dans jniLibs** pour STT (voie ORT-QAIRT autorisée). Mais ils ne seront pas utilisés par LLM/TTS rebuildés. 2. **Modèles GGUF** : aucun changement, les `.gguf` actuels (Qwen3.5-4B-Q4_0 etc.) marchent à l'identique en CPU pur. 3. **TTS clonage vocal** : le speaker encoder ECAPA-TDNN est en C++ ggml CPU déjà, jamais touché par cette histoire HTP. Toutes les fonctionnalités clonage préservées. 4. **Pas de régression STT** : `libkazeia_stt.so` reste comme livrée, voie QNN Maven autorisée. 5. **CLAUDE.md du projet engine** documente les chiffres HTP : ils restent pertinents pour le bench standalone (CLI) mais **pas pour l'in-app CPU-only**. Le `+1 s par tour` est le chiffre à retenir pour l'utilisateur final. ## Si question Code source contact : `/opt/Kazeia-engine/dist/jni/` + `/opt/Kazeia-engine/ql/`. Mémoires assistant pertinentes : `project_engine_selinux_fastrpc_blocker`, `project_engine_unification_3of3`, `project_stt_2_bugs_fixed`.