Kazeia-engine/dist/REBUILD_CPU_ONLY.md

186 lines
8.0 KiB
Markdown

# 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 <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é
```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`.