diff --git a/dist/HANDOFF.md b/dist/HANDOFF.md index 7f3b372..e527620 100644 --- a/dist/HANDOFF.md +++ b/dist/HANDOFF.md @@ -19,23 +19,24 @@ Config déjà correcte et alignée : - 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.) +## 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 : CPU-only vs HTP (à toi).** Le JNI est CPU-only (`ngl0`) → prefill **14 t/s** (t4). + Le HTP fait **189 t/s** avec q35-lmq4 (≈2× le Q4_0=98 : ses embeds Q4 restent sur NPU au lieu de + retomber CPU). Câbler le prefill HTP vaut donc le coup (prompt 500 tok : ~2,6 s HTP vs ~35 s CPU). + Obstacle : ping-pong du mono-contexte ngl99 (decode GDN rebondit NPU↔CPU = 0,2 t/s). Vrai fix = + 2 contextes (prefill HTP → transfert d'état → decode CPU ngl0) ou état partagé. **CPU-only suffit + pour des tours courts ; câbler HTP si prompts longs / RAG.** -## Perf (chiffres sains — la tablette throttlait à 11% ce soir, à reconfirmer) +## Perf (sains, 27/05, device froid) | | 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 | +| q35-lmq4 — JNI actuel (CPU-only) | 14 (CPU t4) | **10.9** (t4+fa, KV f16) | 2.4 GB | +| q35-lmq4 — capacité HTP (si câblé) | **189** | — | +~3.3 ION | +| dense Qwen3-4B (repli) | 98 HTP / ~14 CPU | 11.7 | 2.2 GB | ## Système (prompt) — `system_fr.txt` fourni Inclut les garde-fous (3114, pas de prescription) + 2 correctifs issus de l'éval : diff --git a/dist/PERF.md b/dist/PERF.md index 9c117f2..b8d1974 100644 --- a/dist/PERF.md +++ b/dist/PERF.md @@ -1,20 +1,22 @@ -# Perf mesurée SM8750/V79 — MAJ 26/05 (fork ql=qualcomm/llama.cpp) -Decode optimum = **t4 / fa1** (PAS t8 : contention, 3.5-4B chute). Prefill HTP : t8 / b512. +# Perf mesurée SM8750/V79 — MAJ 27/05 (fork ql=qualcomm/llama.cpp), batterie pleine device froid +Decode optimum = **t4 / fa1 / KV f16** (PAS t8 : contention ; PAS KV q8_0 : déquant coûteux). Prefill HTP : ngl99 / t8 / b512. -| Modèle | prefill HTP (t8/b512) | prefill CPU (t4) | decode CPU (t4+fa) | RAM | +| Modèle | prefill HTP | prefill CPU (t4) | decode CPU (t4+fa, KV f16) | RAM | |---|--:|--:|--:|--:| -| Qwen3-4B dense | 285 | ~19 | ~11.7 | 2.2 | -| Qwen3.5-4B (q35-lmq4) | 89.5 | ~18-19 | **~10.8** | 2.4 | +| Qwen3-4B dense | 98 | ~14 | 11.7 | 2.2 | +| **q35-lmq4** (Qwen3.5-4B, embeds Q4) | **189** | 14 | **10.9** | 2.4 | +| Qwen3.5-4B-Q4_0 (embeds Q6_K) | 98 | ~14 | 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é ≈. +Notes : +- **KV f16 ≫ q8_0 au 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é. → JNI en `type_k/v=F16`. q8_0 seulement si OOM KV long. +- **q35-lmq4 prefill HTP = 189 ≈ 2× le Q4_0 (98)** : ses embeds/output Q4 restent sur NPU, alors que + le Q6_K par défaut retombe sur CPU (une partie du « mur prefill » était ce fallback lm_head, pas le GDN). +- **GDN-compute n'est PAS le goulot prefill** : serial f32 / fp16 / WY chunkwise = même pp512. Piste + kernel GDN **close** (RAPPORT_RD §7). Decode = plafond BW CPU ~25 GB/s. +- **Bridge JNI = CPU-only (ngl0)** → prefill 14 (pas 189). Le 189 = capacité HTP, non câblée (ping-pong ngl99). - 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. +Leviers MORTS (mesurés) : spec-decode net-négatif ; ngram idem ; IQ4_NL = mêmes octets ; +**quant sous-Q4 = decode CPU plus lent** (Q3_K_M −20%) ; **KV q8_0 = decode −40%** ; kernel GDN (chunkwise/HMX/fp16) = 0 sur prefill. diff --git a/dist/jni/kazeia_engine_jni.cpp b/dist/jni/kazeia_engine_jni.cpp index 137eef9..37f8521 100644 --- a/dist/jni/kazeia_engine_jni.cpp +++ b/dist/jni/kazeia_engine_jni.cpp @@ -17,8 +17,11 @@ Java_com_kazeia_llm_EngineJni_load(JNIEnv* e, jobject, jstring path, jint nctx) auto m = llama_model_load_from_file(p, mp); e->ReleaseStringUTFChars(path, p); if (!m) return 0; auto cp = llama_context_default_params(); cp.n_ctx = nctx; cp.n_threads = 4; cp.n_batch = 512; - cp.flash_attn_type = LLAMA_FLASH_ATTN_TYPE_ENABLED; // t4+fa = 14t/s decode (t8=9.8 contention; fa coupe trafic KV) - cp.type_k = GGML_TYPE_Q8_0; cp.type_v = GGML_TYPE_Q8_0; // KV q8: 14->15.5 dense (>pte), 8.5->9.8 hyb, quasi-lossless + cp.flash_attn_type = LLAMA_FLASH_ATTN_TYPE_ENABLED; // t4+fa optimum decode (t8=contention) + // KV f16 (PAS q8_0): mesuré 27/05 batterie pleine, q35-lmq4 decode f16=10.9 vs q8_0=6.5 (-40%, + // déquant KV en flash-attn coûte plus que le BW à court/moyen contexte; idem d=512). q8_0 ne + // servirait que la RAM KV en très long contexte. Repasser q8_0 seulement si OOM KV avéré. + cp.type_k = GGML_TYPE_F16; cp.type_v = GGML_TYPE_F16; auto* k = new KEngine{m, llama_init_from_model(m, cp), llama_model_get_vocab(m), llama_sampler_init_greedy()}; return (jlong)k;