Kazeia-engine/dist/REBUILD_CPU_ONLY.md

8.0 KiB

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).

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

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)

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 <pid> 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é

// 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.