# Kazeia-Engine — Rapport R&D complet (24-25/05/2026) Synthèse de toute la session. Matériel : OnePlus Pad3, SM8750 (Snapdragon 8 Elite, Hexagon V79), 16 GB, sans root. Mémoire LPDDR5x ~77 GB/s, CPU ~25. NDK r27d, Hexagon SDK 6.5.0.0. ## 1. Objectif Faire tourner Qwen3.5-4B (et 9B) en GGUF, sans conversion `.pte`, en exploitant le NPU pour le prefill et le CPU pour le decode. Cible : égaler/battre ExecuTorch (4B `.pte` = 15 tok/s, 2.2 GB), empreinte mémoire mini. ## 2. Le fait dur (mesuré, reproductible) Decode M=1 = **memory-bandwidth bound**, pas compute. Le NPU n'aide pas le decode (bus DDR saturé), il aide le prefill (batché, compute-bound). Donc : **prefill NPU / decode CPU**, verrouillé pour 4B et 9B. ## 3. Performances stables — MAJ 26/05, fork ql (qualcomm/llama.cpp), t4+fa decode | Modèle | prefill (t8/b512) | decode (t4+fa) | RAM | |---|---:|---:|---:| | Qwen3.5-4B-Q4_0 | 93 | 8.5 | 2.4 GB | | Qwen3.5-9B-Q4_0 | 70 | 5.7 | 5.0 GB | | Qwen3-4B dense | 285 | 14 | 2.2 GB | | 4B ExecuTorch .pte | ~451 | 15 | 3.3 GB | Decode dense 4B = parité pte à RAM −30%. NPU 9.82=CPU 9.8 → 77GB/s injoignable (GEMV M=1 latency-bound, ~32 réel). t4=optimum, t8=contention (3.5 chute 2.3). Fork qualcomm DOUBLE le prefill (50→93, 103→285) + sortie FR cohérente. Anciens 9.8/50/t8 obsolètes. Leviers decode morts (mesurés): spec-decode négatif, IQ4_NL=même octets, fusion GDN compute-bound. >9 decode = kernel GDN-HMX frontière. ## 4. Cascade Thinker+Guard : RAM OK 9B+Guard-4B = 7.5 GB ; 9B+Guard-8B = 9.5 GB ; +TTS/STT → 11.5. Tient sur 16 GB. 9B>4B en qualité FR. ## 5. Engine vs briques briques (stack NPU+lm_head CPU) : 2B 13, 4B 7, 9B 2.1 tok/s. **2B briques 13/2.5GB = sweet spot**, > engine 4B 9.8. Repo `/opt/Kazeia/to_delete/briques_qwen35`. Pour le 4B, `.pte` reste devant (15, 2.2GB). GGUF utile = 9B/modèles sans pte. ## 6. Intégration #277 (dev) — débloquée, 2 fixes hang in-app = thinking non bridé + 1 thread. FIX : signature `generate(sys,usr,maxTok)`, ChatML+``, ngl0 + n_threads=8 → 14 tok/s. Bridge STATIC (0 NEEDED libllama, coexiste TTS). dist `/opt/Kazeia-engine/dist/` : libkazeia_engine.so + htp-v79.so + EngineLlmEngine.kt + docs. ## 7. R&D pousser prefill GDN — PISTE FERMÉE (26/05, mesuré) Conclusion dure : **le calcul GDN n'est pas le goulot du prefill sur le fork ql**. A/B sur la même commande llama-bench (Qwen3.5-4B-Q4_0, pp512, ngl99/t8/b512, gate scalaire S_v=128) : | Schéma GDN (HTP) | flops GDN | pp512 | |---|---:|---:| | serial f32 (vrai baseline) | 1× | **89.5** | | serial fp16 (HVX 64-lane) | ~0.5× ALU | 89.65 | | WY chunkwise | ~2.5× | 89.6 | Trois schémas de calcul (0,5× / 1× / 2,5× flops) → pp512 **plat**. Si l'arithmétique GDN limitait, le 2,5× s'effondrerait et le 0,5× grimperait. Donc le mur prefill (~89,5) est **ailleurs** : matmuls denses / FFN MoE / attention / conv / overhead op-batch. Ça corrige la vieille prémisse « GDN = l'ancre » (vraie sur l'ancien tree GDN-CPU série, **fausse** sur le fork GDN-HTP). Le « 98 » d'avant était un fantôme de mesure ; vrai baseline = 89,5. Corollaire : **aucune optim du kernel GDN** (chunkwise, HMX, fp16) ne peut bouger le prefill. HMX-per-tête en plus = NO-GO (matmuls 32×128 latency-bound ~12 µs, 28 têtes × 24 couches sérialisées sur le moteur HMX unique). Pour réellement pousser pp512 il faut profiler/attaquer les ops **non-GDN** sur HTP — autre chantier, et prefill = « utile pas vital ». Artefacts (branche `ql` gdn-chunkwise-wy) : kernel fp16 serial validé (oracle 28/28, `hvx_vec_mpyacc_f32_f16`, état fp16 paddé-64, aucune dérive à 256 tok — le gate exp(g<0) borne), gardé derrière env `KZ_F16` (défaut OFF = baseline f32 inchangé) ; kernel WY chunkwise conservé inutilisé comme base d'un éventuel batch-têtes. `GGML_HEXAGON_OPFILTER` existe sur ce fork ; profiler `GGML_HEXAGON_PROFILE` masqué par llama-bench (sortie DEBUG). ## 8. À FAIRE : quantize 4B IQ4_NL on-device + bench, brancher JNI, valider qualité FR. STT reste ORT-QAIRT. Détail commits : `git log`.