diff --git a/dist/HANDOFF.md b/dist/HANDOFF.md new file mode 100644 index 0000000..7f3b372 --- /dev/null +++ b/dist/HANDOFF.md @@ -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 + `` 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`. diff --git a/dist/MODELS.md b/dist/MODELS.md index 866b708..93b3dad 100644 --- a/dist/MODELS.md +++ b/dist/MODELS.md @@ -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 \ + 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. diff --git a/dist/PERF.md b/dist/PERF.md index 58a231b..9c117f2 100644 --- a/dist/PERF.md +++ b/dist/PERF.md @@ -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. diff --git a/dist/STATUS.md b/dist/STATUS.md index f82e086..15fcdcd 100644 --- a/dist/STATUS.md +++ b/dist/STATUS.md @@ -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. diff --git a/dist/package.sh b/dist/package.sh new file mode 100755 index 0000000..b286091 --- /dev/null +++ b/dist/package.sh @@ -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" diff --git a/dist/system_fr.txt b/dist/system_fr.txt new file mode 100644 index 0000000..631fad8 --- /dev/null +++ b/dist/system_fr.txt @@ -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).