chantier B engine #2 : bloqueur SELinux découvert + guide rebuild CPU-only pour dev

Le dev Kazeia a découvert au test de dégel TTS / toggle llm_engine=lib :
SIGABRT in-app au tts_engine_load / kazeia_engine_load, jamais en standalone.

Trace dmesg :
  avc: denied { open } for path="/dev/fastrpc-cdsp"
    scontext=u:r:untrusted_app  tcontext=u:object_r:vendor_qdsp_device

Cause : libggml-hexagon.so dlopen libcdsprpc.so (FastRPC) au init backend HTP,
qui ouvre /dev/fastrpc-cdsp. Policy SELinux Oplus/Qualcomm sur SM8750 interdit
cet accès à untrusted_app (UID 10xxx). Seul shell (UID 2000) y a accès.

Conséquence : tout mon harness CLI standalone via adb shell était un FAUX
positif systématique. Le test "loadLibrary OK" + "calls C++ OK" ne révèle
rien du comportement in-app pour le backend HTP/DSP.

Pourquoi STT marche : passe par libQnnHtp.so Maven (delegation autorisée
untrusted_app), pas FastRPC direct.

Pourquoi ExecuTorch .pte marche : idem libQnn Maven.

3 voies évaluées (cf project_engine_selinux_fastrpc_blocker memoire) :
 1. Rebuild CPU-only (GGML_HEXAGON=OFF) -- réaliste, +1s/tour LLM, +0 TTS
 2. Port ORT-QAIRT pour LLM -- non réaliste (pas de Qwen QAIRT context)
 3. Statu quo .pte -- régression ambition projet

Décision user : le dev rebuilde de son côté en environnement APK réel
(évite mon harness biaisé). Reprise des sources Kazeia-Engine et build.

Livrables cette session :
 - dist/REBUILD_CPU_ONLY.md : guide complet 7 étapes (sources, flags CMake,
   build libllama/ggml CPU-only, rebuild kazeia_engine/tts, jniLibs allégé,
   tests in-app, perf attendue, points d'attention)
 - dist/LLM_INTEGRATION.md §0 : bloqueur SELinux documenté + référence
   rebuild CPU-only + chiffres perf attendus
 - dist/TTS_INTEGRATION.md §0 : idem côté TTS, noter que le clonage vocal
   (speaker encoder ECAPA-TDNN) est CPU-only de base, donc préservé sans
   impact
 - dist/KAZEIA_ENGINE_OVERVIEW.md : warning en tête + pointer REBUILD doc
 - project_engine_selinux_fastrpc_blocker.md : diagnostic complet pour
   contexte futur

Coût mesuré attendu (vs CLI HTP) :
 - LLM prefill : 0.4s -> 1.3s (+0.9s)
 - LLM decode : inchangé (déjà CPU NEON option C)
 - TTS talker prefill : 50ms -> 150ms (négligeable in-mix)
 - Tour LLM total : +1s (acceptable thérapeutique)
 - Tour TTS total : +0.1s (imperceptible)

Leçon de méthode gravée : un harness CLI via adb shell sera TOUJOURS
faux positif pour tout test impliquant FastRPC/DSP/contexte vendor.
Tout test validation engine pour intégration in-app DOIT passer par
APK debug ou androidTest instrumenté.
This commit is contained in:
Richard Loyer 2026-06-02 23:12:38 +02:00
parent 8c1e83be18
commit e55cd175c8
4 changed files with 257 additions and 3 deletions

View File

@ -1,6 +1,8 @@
# Kazeia-Engine — vue d'ensemble et entrée d'intégration # Kazeia-Engine — vue d'ensemble et entrée d'intégration
**Date** : 01/06/2026. Moteur d'inférence local-first pour Snapdragon 8 Elite (Pad3). Unifie **LLM + TTS + STT** dans une stack cohérente. Pas de cloud, pas de root, pas de Python runtime. > ⚠️ **02/06 : bloqueur SELinux découvert** sur les libs LLM/TTS in-app (`/dev/fastrpc-cdsp` refusé à `untrusted_app`). Les libs livrées dans `b-jni/` sont **inutilisables dans un APK Play Store/sideload** en l'état. **Rebuild CPU-only requis** côté dev avant intégration LLM/TTS. STT non affecté (passe par QNN Maven). Voir `REBUILD_CPU_ONLY.md` pour la procédure et `project_engine_selinux_fastrpc_blocker` en mémoire pour le détail.
**Date** : 02/06/2026. Moteur d'inférence local-first pour Snapdragon 8 Elite (Pad3). Unifie **LLM + TTS + STT** dans une stack cohérente. Pas de cloud, pas de root, pas de Python runtime.
## Documents d'intégration (3 indépendants) ## Documents d'intégration (3 indépendants)

View File

@ -1,6 +1,12 @@
# Intégration LLM Kazeia-Engine — Guide développeur # Intégration LLM Kazeia-Engine — Guide développeur
**Date** : 01/06/2026 > ⚠️ **BLOQUEUR SELinux découvert 02/06 — lire la section 0 avant toute intégration in-app.**
> La lib `libkazeia_engine.so` actuelle (avec backend Hexagon HTP) **NE FONCTIONNE PAS**
> dans un APK `untrusted_app` à cause d'une policy SELinux Android/Qualcomm qui interdit
> l'accès à `/dev/fastrpc-cdsp`. Tout test CLI standalone via `adb shell` est un faux positif
> systématique (uid `shell` a accès, pas l'app). **Rebuild CPU-only requis** pour usage in-app.
**Date** : 02/06/2026
**Cible** : remplacer le pipeline LLM ExecuTorch `.pte` par `libkazeia_engine.so` + façade Kotlin `EngineLlmEngine.kt`. Modèle GGUF chargé directement (pas de conversion .pte), prefill HTP / decode CPU automatique (option C). **Cible** : remplacer le pipeline LLM ExecuTorch `.pte` par `libkazeia_engine.so` + façade Kotlin `EngineLlmEngine.kt`. Modèle GGUF chargé directement (pas de conversion .pte), prefill HTP / decode CPU automatique (option C).
**Runtime** : libllama (fork ggml-org/llama.cpp commit `f0fe1058b`) + libggml fork ql (Qualcomm) avec backend Hexagon HTP V79. **Runtime** : libllama (fork ggml-org/llama.cpp commit `f0fe1058b`) + libggml fork ql (Qualcomm) avec backend Hexagon HTP V79.
@ -23,6 +29,35 @@
--- ---
## 0. Bloqueur SELinux découvert 02/06 — à lire en premier
**Symptôme** : `System.loadLibrary("kazeia_engine")` peut passer, mais le premier appel à `EngineLlmEngine.load(modelPath, nCtx, nThreads)` crashe avec SIGABRT (`ggml_backend_dev_name` → `ggml_abort`).
**Cause** : `libkazeia_engine.so` linke `libllama.so` + `libggml-hexagon.so` qui dlopen `libcdsprpc.so` (FastRPC) au démarrage du backend HTP. FastRPC ouvre `/dev/fastrpc-cdsp` pour parler au CDSP. La policy SELinux Oplus/Qualcomm sur SM8750 interdit cet accès à `untrusted_app` :
```
avc: denied { open } for path="/dev/fastrpc-cdsp"
scontext=u:r:untrusted_app tcontext=u:object_r:vendor_qdsp_device
```
→ Backend hexagon en état partiel → crash au premier accès.
**Pourquoi cette doc l'avait raté** : tous mes tests en `adb shell` (uid 2000) marchaient parfaitement. Le contexte SELinux `shell` a accès à `/dev/fastrpc-cdsp`. **Le contexte `untrusted_app` ne l'a pas**. Faux positif systématique du harness standalone.
**Pourquoi STT et ExecuTorch prod marchent** : ils utilisent QNN via `libQnnHtp.so` Maven, qui passe par une voie de delegation autorisée. Pas FastRPC direct.
**Solution** : **rebuild `libllama.so` + `libggml*.so` + `libkazeia_engine.so` en CPU-only** (`-DGGML_HEXAGON=OFF`). Le bridge devient CPU pur. Coût mesuré sur workload Kazeia (prompts 16-24 tok, réponses 64 tok) :
| | HTP prod | CPU-only |
|---|---:|---:|
| LLM prefill | ~0.4 s | ~1.3 s |
| LLM decode (64 tok à 9.8 t/s) | ~6.5 s | ~6.5 s (déjà CPU) |
| **Total tour LLM** | **~7 s** | **~8 s (+1 s)** |
Acceptable pour thérapeutique. Voir mémoire `project_engine_selinux_fastrpc_blocker` pour le détail des 3 voies évaluées.
**État jusqu'à rebuild CPU-only** : flag `llm_engine=lib` arme la bascule mais la lib ne fonctionnera pas in-app. Fallback gracieux sur `prod` (.pte) recommandé tant que le rebuild n'est pas fait.
## 1. Ce que la lib fait ## 1. Ce que la lib fait
``` ```

185
dist/REBUILD_CPU_ONLY.md vendored Normal file
View File

@ -0,0 +1,185 @@
# 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`.

View File

@ -1,6 +1,12 @@
# Intégration TTS Kazeia-Engine — Guide développeur # Intégration TTS Kazeia-Engine — Guide développeur
**Date** : 01/06/2026 > ⚠️ **BLOQUEUR SELinux découvert 02/06 — lire la section 0 avant toute intégration in-app.**
> La lib `libkazeia_tts.so` actuelle (linke `libllama.so` + `libggml-hexagon.so`) **NE FONCTIONNE PAS**
> dans un APK `untrusted_app` à cause d'une policy SELinux Android/Qualcomm qui interdit
> l'accès à `/dev/fastrpc-cdsp` requis par FastRPC. **Rebuild CPU-only requis** pour usage in-app.
> Le speaker encoder (clonage vocal) est lui CPU-only et n'est pas affecté.
**Date** : 02/06/2026
**Cible** : remplacer le pipeline TTS legacy par `libkazeia_tts.so` + façade Kotlin `TtsEngine.kt`. Inclut le **clonage vocal embarqué** via speaker encoder ECAPA-TDNN. **Cible** : remplacer le pipeline TTS legacy par `libkazeia_tts.so` + façade Kotlin `TtsEngine.kt`. Inclut le **clonage vocal embarqué** via speaker encoder ECAPA-TDNN.
**Runtime** : libllama + libggml (fork ql Qualcomm) avec backend Hexagon HTP V79 — décode ggml in-process. Pas d'ONNX, pas de Python, pas de root. **Runtime** : libllama + libggml (fork ql Qualcomm) avec backend Hexagon HTP V79 — décode ggml in-process. Pas d'ONNX, pas de Python, pas de root.
@ -19,6 +25,32 @@
--- ---
## 0. Bloqueur SELinux découvert 02/06 — à lire en premier
**Symptôme** : `tts_engine_load` crashe in-app (SIGABRT dans le backend hexagon), pas en standalone `adb shell`.
**Cause identique au LLM** : `libkazeia_tts.so` linke `libllama.so` qui dépend de `libggml-hexagon.so`, qui dlopen `libcdsprpc.so` (FastRPC). La policy SELinux interdit `untrusted_app` d'accéder à `/dev/fastrpc-cdsp` :
```
avc: denied { open } for path="/dev/fastrpc-cdsp"
scontext=u:r:untrusted_app tcontext=u:object_r:vendor_qdsp_device
```
**Spécificité TTS** : dans le pipeline TTS Qwen3, le talker fait son **prefill** sur HTP (option C). Mais en pratique, sur des prompts TTS courts (~20-30 tokens), le prefill HTP n'apporte que **~50 ms** vs CPU pur. Donc le rebuild CPU-only impacte **0 perceptiblement** le TTS :
| | Pipeline actuel | TTS CPU-only |
|---|---:|---:|
| Talker prefill | ~50 ms (HTP) | ~150 ms (CPU) |
| Talker loop (decode) | déjà CPU | déjà CPU |
| CP loop | déjà CPU | déjà CPU |
| Decoder chraac | déjà CPU | déjà CPU |
| **Total tour TTS** | **~8 s** | **~8.1 s (+0.1 s)** |
→ Coût négligeable pour TTS. Le rebuild CPU-only **préserve toutes les fonctionnalités** y compris le **clonage vocal embarqué** (le speaker encoder ECAPA-TDNN est CPU-only de toute façon, jamais touché par cette histoire HTP/SELinux).
**Solution** : même rebuild CPU-only (`-DGGML_HEXAGON=OFF`) que pour LLM. Une seule chaîne de libs partagée. Voir `project_engine_selinux_fastrpc_blocker` en mémoire.
**État jusqu'à rebuild** : flag `tts_engine=lib` arme la bascule mais la lib ne fonctionnera pas in-app. Garder `tts_engine=prod` jusqu'à rebuild CPU-only des libs.
## 1. Ce que la lib fait ## 1. Ce que la lib fait
``` ```