Kazeia-engine/dist/HANDOFF.md

98 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Kazeia-Engine — handoff intégration (MAJ 26/05/2026)
Point d'entrée unique. Remplace le LLM ExecuTorch/Genie par llama.cpp (fork ql) + Hexagon.
GGUF, pas de `.pte`. STT reste ORT-QAIRT (inchangé).
**`.pte` retiré (plus de pré-compilé) → l'engine est le SEUL runtime LLM.** Le dense (qwen3) tourne donc
sur l'engine en **HVX** (~40 prefill, stable) ; le passer en **HMX** (~295) = chantier `CHANTIER_HMX_DENSE.md`
(crash k=4096 localisé, non-bloquant). Le Speaker retenu (Qwen3.5 `q35-lmq4`) tourne en option C (HMX), inchangé.
## Générique : fait tourner N'IMPORTE QUEL LLM (détecté à l'archi, au `load`, les deux sur HTP)
Le JNI lit `general.architecture` dans l'en-tête GGUF **avant l'init backend** (pour régler `USE_HMX`), puis route :
- **Hybride GDN (qwen3.5 / qwen3next)** → **option C** : prefill HTP (**HMX on**) → transfert KV → decode CPU
(le decode GDN sur HTP est lent, on le garde CPU). Prefill ~110-180. Validé (mono-tour + mémoire).
- **Dense (qwen3, …)** → **HTP contexte-unique, matmul HVX (`USE_HMX=0`)** : prefill+decode sur HTP, ~40 prefill
(≈3× le CPU 14), decode ~11. Validé (qwen3-4B cohérent, plus de crash).
- **pas de HTP / échec** → CPU pur (repli universel).
**Root cause du crash dense-HTP (élucidé)** : le **matmul HMX (fp16 tile)** faute (`dspqueue_read 0x2e`) sur les
**activations réelles** du dense (« massive activations » de Qwen3 > plage fp16 ; invisible en bench synthétique,
déclenché par du vrai texte > ~14 tokens). Confirmé : `GGML_HEXAGON_USE_HMX=0` (matmul HVX) supprime le crash,
`=1` le reproduit ; et `OPFILTER=MUL_MAT` (matmuls→CPU) aussi. → dense en HVX sur HTP. La 3.5 garde HMX (elle ne
faute pas). TODO backend possible : rendre le matmul HMX robuste aux activations hors plage fp16 (clamp/scale).
Les deux cibles tournent sur HTP, validées via `jni/test_jni_native.cpp`.
## Décisions figées cette session
- **Modèle Speaker = Qwen3.5-4B en variante `q35-lmq4.gguf`** (embeds en Q4 au lieu du Q6_K
par défaut → 2.38 GB, decode +5%, qualité ≈ Q4_0 mesurée). Choisi sur l'éval qualité
(`../eval/VERDICT.md`) : gagne le SAFETY (cite 3114/15) et la majorité du SUPPORT vs le dense.
- **9B écarté** (decode ~4-5 t/s réel, trop lent ; pas de gain qualité justifiant). Cascade
éventuelle = Guard-4B (thinker) + 4B (speaker), pas 9B.
- **Kernel GDN = chantier clos** : son calcul n'est PAS le goulot (cf RAPPORT_RD §7). Ne pas
y retoucher pour la perf. Decode au plafond BW CPU (~25 GB/s).
## État du bridge JNI (`jni/kazeia_engine_jni.cpp`) — OPTION C, validé end-to-end (27/05)
Le JNI implémente **C** (prefill HTP / decode CPU). **Validé bout-en-bout sur device** via `jni/test_jni_native.cpp`
(dlopen + JNIEnv mock → `load`/`generate`/`generateRaw`/`free` : sortie FR cohérente, mémoire conversationnelle OK)
et `jni/dual_ctx_mt.cpp` (3 tours, prefill HTP 103-144 t/s, decode CPU ~7-9).
- `load()` : pose `GGML_HEXAGON_GDN_PREFILL=1` (le fallback CPU du conv1d multi-token est dans le backend, pas
d'OPFILTER), charge le modèle **2×** : instance HTP (ngl99, device HTP0, t8) + instance CPU (ngl0, t4). ~4.7 GB.
- **API** : `generate(sys,usr,max)` (mono-tour) **OU `generateRaw(prompt,max)`** (multi-tour, prompt pré-formaté).
`EngineLlmEngine.kt` fournit **`ChatSession`** (accumule l'historique + construit le ChatML validé → l'app n'a
pas à connaître le template). Cœur : prefill HTP → `llama_state_seq_get/set_data` (transfert KV) → decode CPU.
Sans état entre appels (re-prefill de l'historique complet sur HTP, rapide).
- KV **f16**, flash_attn ON, thinking OFF (`<think></think>` vide), greedy. API : `load/generate/generateRaw/reset/free`.
- `lib/libkazeia_engine.so` **à jour (option C, 5 symboles JNI, SHARED)** + `libggml-hexagon.so`
(fix SSM_CONV) + `libggml-htp-v79.so` à jour. Prebuilts utilisables tels quels (NDK r27d) ; **rebuild depuis
`jni/` dans ton build app recommandé** pour garantir l'ABI/STL (CMakeLists.txt fourni, linke llama+ggml+ggml-base+log).
## Décisions (vérifiées 27/05, batterie 90%, device froid)
1. **KV = f16 (RÉSOLU, corrigé dans le JNI).** Mesuré : decode q35-lmq4 f16=**10.9** vs q8_0=**6.5**
(40%, idem à d=512). Le déquant KV en flash-attn coûte plus que le BW épargné. `type_k/v`
repassé en `GGML_TYPE_F16`. → **rebuild `libkazeia_engine.so` avant de shipper** (le .so livré
contient encore l'ancien q8_0). q8_0 seulement si OOM KV en très long contexte.
2. **Prefill HTP : CORRIGÉ (fix intégré au backend).** Cause du charabia localisée à **une seule op,
SSM_CONV (conv1d), cassée sur HTP en multi-token (prefill) pour certains d_inner (1024/2048 ; 1536 OK)**
— bug données/marshalling côté DSP (HVX ET scalaire HTP faux, ggml-cpu correct), non localisé à la ligne.
**Fix livré dans `libggml-hexagon.so`** (`ggml_hexagon_supported_ssm_conv` : `n_t>1 → CPU`) : le conv1d
prefill tourne sur ggml-cpu (correct, bon marché), le reste du prefill sur HTP, decode conv sur HTP (n_t=1).
Plus besoin de `OPFILTER`. Validé : oracle SSM_CONV 18/18, cli `-dev HTP0` cohérent, multi-tour cohérent+mémoire.
Mesuré : **prefill HTP 110-180 t/s** (×8-13 vs CPU 14), sortie correcte. Libs prebuilts à jour dans `lib/`.
Trois options (le JNI implémente C) :
- **A CPU-only** : prefill 14, decode 10.9, 2.4 GB. Simple.
- **B mono-contexte HTP + OPFILTER** : prefill **181**, decode HTP **6.4**, 2.4 GB (+ION). Gros gain prefill,
decode + lent, aucune RAM en plus, peu de code (ngl99 + env). **Recommandé** pour prompts longs/multi-tour.
- **C dual-contexte (prefill HTP+OPFILTER → transfert KV → decode CPU)** : prefill 181, decode **10.9**,
**+2.3 GB** RAM (2e instance). Optimal mais + complexe. Harness prouvé : `jni/dual_ctx.cpp`.
TODO propre : corriger SSM_CONV HTP (gate `test-backend-ops -o SSM_CONV` = 45/45) → enlèverait l'OPFILTER.
## Perf (sains, 27/05, device froid)
| | prefill | decode | RAM |
|---|--:|--:|--:|
| q35-lmq4 — **C: HTP-prefill / CPU-decode (CÂBLÉ JNI)** | **103-180** (HTP, monte avec ctx) | **~7-9** (CPU, profondeur) | 4.7 GB |
| q35-lmq4 — A: CPU-only | 14 | 10.9 (ctx court) | 2.4 GB |
| q35-lmq4 — B: HTP+OPFILTER mono-ctx | 181 | 6.4 (HTP) | 2.4 GB +ION |
| q35-lmq4 — HTP sans OPFILTER | 189 *(sortie CASSÉE: SSM_CONV)* | — | — |
Multi-tour mesuré (C) : tour1 63tok→prefill 103/decode 9 ; tour3 268tok→prefill 144/decode 6.7 ; mémoire OK.
Decode CPU baisse avec la profondeur de contexte (normal). Un tour ~80 tok ≈ prefill 1-3 s + decode 9-12 s.
Decode = ce que l'utilisateur ressent (10.9, OK). Prefill CPU 14 t/s → prompt 200 tok ≈ 14 s ;
garder le system prompt + l'historique courts. Le prefill HTP rapide existe mais sort du charabia (cf décision 2).
## Système (prompt) — `system_fr.txt` fourni
Inclut les garde-fous (3114, pas de prescription) + 2 correctifs issus de l'éval :
tutoiement constant, et « ne présume pas du pire, fais préciser » (le modèle lisait
« le départ de ma fille » comme un décès). À passer tel quel en `sys` de `generate()`.
## Paquet à intégrer
- `lib/*.so``app/src/main/jniLibs/arm64-v8a/` (libkazeia_engine.so **statique** + ggml/htp ;
htp-v79 = celui de cette session, fp16 derrière env `KZ_F16` OFF par défaut = comportement inchangé).
- `jni/kazeia_engine_jni.cpp` + `jni/EngineLlmEngine.kt` + `include/` → projet app.
- `q35-lmq4.gguf` → external storage (2.38 GB, hors git ; le pousser ou le reproduire, cf MODELS.md).
- `./package.sh` réassemble `lib/` depuis le build `ql/b` + recopie le prompt.
## Détails
INTEGRATION.md (build/CMake), MODELS.md (modèle + recette), PERF.md (mesures+leviers morts),
PITFALLS.md (pièges vécus), STATUS.md (limites). Verdict qualité : `../eval/VERDICT.md`.