8.4 KiB
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(): poseGGML_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) OUgenerateRaw(prompt,max)(multi-tour, prompt pré-formaté).EngineLlmEngine.ktfournitChatSession(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 depuisjni/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)
- 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/vrepassé enGGML_TYPE_F16(vérifiéjni/kazeia_engine_jni.cpp:41). →lib/libkazeia_engine.so(27/05) déjà rebuildé en f16, prêt. q8_0 seulement si OOM KV en très long contexte. - 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 deOPFILTER. Validé : oracle SSM_CONV 18/18, cli-dev HTP0cohérent, multi-tour cohérent+mémoire. Mesuré : prefill HTP 110-180 t/s (×8-13 vs CPU 14), sortie correcte. Libs prebuilts à jour danslib/. Trois options (le JNI implémente C) :- A CPU-only : prefill 14, decode 10.9, 2.4 GB. Simple.
- B mono-contexte HTP : prefill 181, decode HTP 6.4, 2.4 GB (+ION). Gros gain prefill,
decode + lent, aucune RAM en plus, peu de code (ngl99 +
GDN_PREFILL=1). Recommandé pour prompts longs/multi-tour. - C dual-contexte (prefill HTP → transfert KV → decode CPU) : prefill 181, decode 10.9,
+2.3 GB RAM (2e instance). Optimal mais + complexe — c'est ce que le JNI implémente. Harness :
jni/dual_ctx.cpp. (Plus d'OPFILTER: le conv1d multi-token tombe sur CPU via le gate backendsupported_ssm_conv.) TODO backend (optionnel) : corriger SSM_CONV HTP multi-token (gatetest-backend-ops -o SSM_CONV= 45/45) pour faire tourner le conv1d prefill sur HTP au lieu du fallback CPU actuel.
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 mono-ctx | 181 | 6.4 (HTP) | 2.4 GB +ION |
| q35-lmq4 — HTP sans le fix SSM_CONV (historique) | 189 (sortie CASSÉE) | — | — |
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 = SHARED, 40 KB, NEEDED libllama/libggml/libggml-base/libc++_shared → shipper TOUT le setlib/ensemble, l'ABI doit matcher, sinon crash ABI-drift comme #277 ; rebuild app-side recommandé pour le garantir). htp-v79 = celui de cette session, fp16 derrière envKZ_F16OFF 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.shréassemblelib/depuis le buildql/b+ recopie le prompt.
ℹ️ libggml-hexagon.so embarque l'instrumentation dormante du chantier dense-HMX (OPLOG/DUMP,
gâchées par GGML_HEXAGON_OPLOG/GGML_HEXAGON_DUMP, off par défaut). Coût quand off = 2 getenv/op
(microsecondes, négligeable). Rien à retirer ; utile pour rouvrir CHANTIER_HMX_DENSE.md.
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.