dist: handoff intégration aligné décisions 26/05 (modèle q35-lmq4, perf, prompt)

HANDOFF.md = point d'entrée: modèle Speaker tranché = q35-lmq4 (éval qualité), 9B écarté,
kernel GDN clos. État JNI: config OK (t4/fa/thinking-off/greedy) MAIS CPU-only (ngl0) ->
2 décisions ouvertes documentées (prefill CPU vs HTP non câblé; KV q8_0 à revérifier, peut
coûter au decode). system_fr.txt = prompt prod (garde-fous 3114 + tutoiement + ne-pas-présumer,
correctifs issus de l'éval). MODELS/PERF/STATUS réalignés (decode 10.8, prefill 89.5 HTP capacité
mais JNI=CPU ~19, quant sous-Q4 morte). package.sh réassemble lib/ depuis ql/b.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Richard Loyer 2026-05-26 23:33:41 +02:00
parent 21784d52de
commit 9417aba6cb
6 changed files with 137 additions and 19 deletions

54
dist/HANDOFF.md vendored Normal file
View File

@ -0,0 +1,54 @@
# 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é).
## 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`) — ce qui est CÂBLÉ
Config déjà correcte et alignée :
- `n_threads=4`, `flash_attn=ENABLED`, `n_batch=512`, sampler **greedy**.
- Thinking OFF déterministe (ChatML + `<think></think>` vide injecté) → pas de ramble.
- API : `load(path,nCtx) → generate(h,sys,usr,maxTok) → reset(h) / free(h)`.
- **`n_gpu_layers = 0` → tout CPU** (pas de HTP). Choix après le ping-pong ngl99 (decode 0.2 t/s).
## ⚠ Les 2 décisions ouvertes (à toi, sur batterie pleine)
1. **Prefill : CPU-only vs HTP.** Le JNI est CPU-only → prefill ~18-19 t/s (sain). Le HTP fait
89.5 t/s mais n'est PAS câblé (le ping-pong vient du mono-contexte ngl99 qui renvoie le
decode sur NPU). Pour des tours psy courts, CPU suffit (prompt ~100-300 tok → 5-15 s). Pour
du RAG/long, il faudra un vrai split (prefill HTP → bascule decode CPU), non trivial en
mono-contexte llama.cpp. **Garder CPU-only tant que les prompts restent courts.**
2. **KV `q8_0` : à revérifier.** Le JNI met `type_k/v=q8_0`. Mesure (batterie faible, throttle)
a montré decode q8_0=6.6 vs KV-f16=10.8 plus tôt → le déquant KV pourrait coûter plus que
le gain BW à court contexte (même effet que la quant poids sous-Q4). **A/B f16 vs q8_0 sur
batterie pleine ; si f16 gagne, repasse `type_k/v=GGML_TYPE_F16`.** (q8_0 ne sert que la RAM
KV en long contexte.)
## Perf (chiffres sains — la tablette throttlait à 11% ce soir, à reconfirmer)
| | prefill | decode | RAM |
|---|--:|--:|--:|
| q35-lmq4 (JNI CPU-only) | ~18-19 (CPU) | **~10.8** (t4+fa, KV f16) | 2.4 GB |
| capacité HTP (si câblé) | 89.5 | — | +~3.3 ION |
## 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`.

34
dist/MODELS.md vendored
View File

@ -1,9 +1,25 @@
# Modèles (tablette /data/local/tmp/kz-engine, ou external storage prod)
| Rôle | GGUF | Taille | Note |
|---|---|---|---|
| Speaker reco | Qwen3.5-9B-Q4_0 | 5.0GB | qualité>4B, decode 8.4 |
| Speaker léger | Qwen3.5-4B-Q4_0 | 2.4GB | decode 15, prefill55 |
| Thinker reco | Qwen3Guard-Gen-4B.Q4_K_M | 2.5GB | bullets, decode11 |
| Thinker+ | Qwen3Guard-Gen-8B.Q4_K_M | 4.5GB | analyse fine |
| Dense rapide | Qwen3-4B-Q4_0 | 2.2GB | prefill103, decode17 |
Cascade 9B+Guard4B=7.5GB OK (16GB). 30B-A3B/35B=OOM, exclus. Q4_0>K_M pour NPU (repack). Source: unsloth/*-GGUF, mradermacher Guard. tied embeds Q6_K.
# Modèles (external storage prod ; staging /data/local/tmp/kz-engine)
## Choisi (26/05) — Speaker
| Rôle | GGUF | Taille | Decode | Note |
|---|---|--:|--:|---|
| **Speaker** | **`q35-lmq4.gguf`** (Qwen3.5-4B, embeds Q4) | 2.38 GB | ~10.8 | **choisi** (éval qualité : gagne SAFETY+SUPPORT vs dense) |
| Alternative rapide | `Qwen3-4B-Q4_0` (dense) | 2.21 GB | ~11.7 | + rapide/simple mais perd le SAFETY ; repli si q35 pose souci |
| Thinker (cascade option.) | `Qwen3Guard-Gen-4B.Q4_K_M` | 2.5 GB | ~11 | bullets, si on garde une cascade |
**9B écarté** : decode réel ~4-5 t/s (contention), pas de gain qualité justifiant le coût. MoE 30B/35B = OOM.
## `q35-lmq4` — c'est quoi / comment le refaire
Variante de Qwen3.5-4B où **token_embd/output sont en Q4_0** (au lieu du Q6_K que llama.cpp
garde par défaut pour le gros vocab 248k). Gain : 0.2 GB et **+5% decode**, qualité ≈ Q4_0
(A/B FR identique). Recette (idéalement depuis un GGUF F16 ; `--allow-requantize` si depuis Q4_0) :
```
llama-quantize [--allow-requantize] --token-embedding-type q4_0 --output-tensor-type q4_0 \
<source.gguf> q35-lmq4.gguf Q4_0
```
Le fichier prêt est sur la tablette (`/data/local/tmp/kz-engine/q35-lmq4.gguf`) — `adb pull` pour le récupérer. Hors git (2.38 GB).
## Mesuré mort (ne pas refaire) : quant sous-Q4 = decode CPU PLUS lent
Q3_K_M global = **8.3** (20% vs Q4_0), Q2_K embeds = +3% seulement. Les k-quants ont un
déquant CPU qui mange le gain d'octets. Q4_0 simple reste l'optimum decode. (cf PERF.md.)
Source GGUF : unsloth/*-GGUF.

26
dist/PERF.md vendored
View File

@ -1,8 +1,20 @@
# Perf mesurée SM8750/V79 — MAJ 26/05 (fork ql=qualcomm/llama.cpp)
Decode optimum = **ngl99 / t4 / fa1** (PAS t8: contention, 3.5-4B chute à 2.3). Prefill optimum = t8 / b512 / fa1.
| Modèle | prefill (t8/b512) | decode (t4+fa) | RAM |
|---|---:|---:|---:|
| Qwen3-4B dense | 285 | 14 (15.5 KVq8) | 2.2 |
| Qwen3.5-4B | 93 | 8.5 (9.8 KVq8) | 2.4 |
| Qwen3.5-9B | 70 | 5.7 | 5.0 |
Dense 4B decode 14 = parité .pte (15) à RAM 30%. NPU 9.82=CPU 9.8: 77GB/s injoignable au decode (GEMV M=1 latency-bound, ~32 réel). Fork ql ~2x prefill vs ancien tree (50→93). RAM = GGUF mmap pageable + ~3.3GB ION/session NPU. Sortie FR cohérente prefill HTP. KV q8_0 (quasi-lossless) = +16% decode, dense 15.5 > pte, bridge l'active. Leviers morts: spec-decode net-négatif, ngram idem (M=k coûteux GDN), IQ4_NL=même octets, fusion GDN compute-bound. >10 decode 3.5 = kernel GDN-HMX (frontière). t8/9.8/50 obsolètes.
Decode optimum = **t4 / fa1** (PAS t8 : contention, 3.5-4B chute). Prefill HTP : t8 / b512.
| Modèle | prefill HTP (t8/b512) | prefill CPU (t4) | decode CPU (t4+fa) | RAM |
|---|--:|--:|--:|--:|
| Qwen3-4B dense | 285 | ~19 | ~11.7 | 2.2 |
| Qwen3.5-4B (q35-lmq4) | 89.5 | ~18-19 | **~10.8** | 2.4 |
| Qwen3.5-9B | 70 | — | ~4-5 | 5.0 |
Notes (chiffres sains ; reconfirmer hors throttle batterie) :
- **GDN-compute n'est PAS le goulot du prefill** : serial f32 / fp16 / WY chunkwise donnent le
même pp512 (~89.5). Piste kernel GDN **close** (RAPPORT_RD §7). Decode = plafond BW CPU ~25 GB/s.
- **Bridge JNI = CPU-only (ngl0)** → prefill ~18-19 (pas 89.5). Le 89.5 est la capacité HTP, non câblée.
- **KV q8_0 à revérifier** : mesure dégradée a donné decode q8_0 < f16 (déquant KV coûteux à court
contexte, comme la quant poids). A/B sur batterie pleine ; si f16 gagne, dropper q8_0 du JNI.
- `q35-lmq4` (embeds Q4) = +5% decode vs Q4_0, qualité ≈.
- NPU≈CPU au decode (GEMV M=1 latency-bound, 77 GB/s injoignable). RAM = GGUF mmap + ~3.3 GB ION/session.
Leviers MORTS (mesurés) : spec-decode net-négatif ; ngram idem (vérif GDN coûteuse) ; IQ4_NL =
mêmes octets ; **quant sous-Q4 = decode CPU plus lent** (Q3_K_M 20%) ; kernel GDN (chunkwise/HMX/fp16) = 0 sur prefill.

17
dist/STATUS.md vendored
View File

@ -1,3 +1,14 @@
# Statut & limites (24/05) — à lire avant intégration
VALIDÉ device: load 4B/9B HTP, prefill NPU + decode CPU, generate() bout-en-bout (test_native), 9B>4B qualité, cascade 9B+Guard4B 7.5GB. RAM -30% vs pte.
LIMITES: (1) prefill GDN-HTP non bit-exact (default OFF, GGML_HEXAGON_GDN_PREFILL=1 opt-in); decode CPU = exact. (2) TTS Talker GGUF chargeable mais audio e2e PAS validé (=JNI app). (3) generate() = prompt brut, app doit appliquer template chat + reasoning off. (4) STT inchangé ORT-QAIRT. (5) 30B/35B OOM. NON figé prefill>55 (HVX qf32 3-5sem).
# Statut & limites (MAJ 26/05) — à lire avant intégration
VALIDÉ device : load 4B, generate() bout-en-bout (test_native), decode CPU exact, FR cohérent,
thinking-off déterministe. Modèle tranché = `q35-lmq4` (éval qualité ../eval/VERDICT.md).
Perf : prefill clos côté kernel GDN (pas le goulot), decode au plafond CPU.
LIMITES / OUVERT :
1. **Bridge = CPU-only (ngl0)** : prefill sur CPU (~18-19 t/s), pas HTP. HTP (89.5) non câblé
(ping-pong mono-contexte). OK tours courts ; split HTP→CPU = non trivial, à faire si RAG/long.
2. **KV q8_0 à revérifier** sur batterie pleine (peut coûter au decode à court contexte).
3. `generate()` applique déjà ChatML + thinking-off. App fournit `sys`/`usr` (voir system_fr.txt).
4. TTS Talker GGUF chargeable mais **audio e2e non validé** (= intégration app).
5. STT inchangé (ORT-QAIRT). 9B/30B/35B écartés (lents/OOM).
6. Le tg/pp de ce soir étaient throttlés (batterie ~11%) — reconfirmer les chiffres sur batterie pleine.

24
dist/package.sh vendored Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Réassemble dist/lib/ depuis le build ql/b + recopie le prompt système.
# Ne rebuild PAS : lance d'abord le build dans ql/b (cmake+ninja, preset arm64-android-snapdragon).
# libkazeia_engine.so (bridge statique) se build à part via dist/CMakeLists.txt si jni/*.cpp change.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
B="$HERE/../ql/b"
[ -d "$B/bin" ] || { echo "build absent: $B (cmake+ninja d'abord)"; exit 1; }
mkdir -p "$HERE/lib"
# runtime .so (ggml/llama/cpu/hexagon)
for f in libggml.so libggml-base.so libggml-cpu.so libggml-hexagon.so libllama.so; do
cp -v "$B/bin/$f" "$HERE/lib/$f"
done
# skels HTP (sélection auto runtime -> V79 sur Pad3)
for v in 68 69 73 75 79 81; do
cp -v "$B/ggml/src/ggml-hexagon/libggml-htp-v$v.so" "$HERE/lib/libggml-htp-v$v.so"
done
# libc++ NDK (bench/cli ; l'app fournit normalement le sien)
CPP=$(find "${ANDROID_NDK_ROOT:-/opt/Kazeia/android-ndk-r27d}" -name libc++_shared.so -path '*aarch64*' | head -1)
[ -n "$CPP" ] && cp -v "$CPP" "$HERE/lib/libc++_shared.so" || echo "warn: libc++_shared introuvable"
echo "OK. Rappel: libkazeia_engine.so (statique) = build séparé si jni/ a changé (dist/CMakeLists.txt)."
echo "Modèle q35-lmq4.gguf NON copié (2.38 GB, hors git): adb pull /data/local/tmp/kz-engine/q35-lmq4.gguf"

1
dist/system_fr.txt vendored Normal file
View File

@ -0,0 +1 @@
Tu es Kazeia, un assistant de soutien psychologique bienveillant et prudent. Tu réponds toujours en français et tu tutoies systématiquement la personne (jamais de vouvoiement). Tes réponses sont chaleureuses, concrètes et concises (2 à 4 phrases). Tu ne présumes jamais du pire : si une situation est ambiguë (par exemple « le départ de quelqu'un »), tu ne supposes pas un décès ni un drame, tu restes neutre ou tu demandes doucement de préciser. Tu ne poses pas de diagnostic médical et ne prescris jamais de médicament : si on te le demande, tu orientes avec douceur vers un professionnel de santé. Si la personne exprime une détresse grave ou des idées suicidaires, tu prends ces propos au sérieux, tu exprimes de l'empathie et tu invites clairement à contacter une aide immédiate (proche de confiance, médecin, ou numéro d'urgence local comme le 3114 en France, ou le 15).