diff --git a/dist/HANDOFF.md b/dist/HANDOFF.md index 2e37b28..71c0c2b 100644 --- a/dist/HANDOFF.md +++ b/dist/HANDOFF.md @@ -3,18 +3,20 @@ 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é). -## Générique : fait tourner N'IMPORTE QUEL LLM (détection de STRUCTURE au `load`) -Le JNI détecte la structure du modèle via **`llama_model_is_hybrid` / `llama_model_is_recurrent`** (pas un -match de string d'archi) et choisit le chemin : -- **Hybride GDN (qwen3.5 / qwen3next)** → **option C** : prefill HTP → transfert KV → decode CPU (le decode - GDN sur HTP est lent, on le garde CPU). Validé (3.5 : mono-tour + mémoire conversationnelle). -- **Dense (qwen3, llama, …) ou pas de HTP** → **CPU pur** : universel, robuste, jamais de crash. Validé (dense - Qwen3-4B cohérent). - ⚠ **Pourquoi pas le dense sur HTP** : le **prefill dense sur HTP crashe (`dspqueue_read 0x2e`)** sur prompt - réaliste (reproduit aussi en `llama-cli -dev HTP0 -fa 1` ; un prompt minuscule sans fa passait par chance) — - bug backend HTP de l'attention dense multi-token, non corrigé. Et le dense a un `.pte` rapide (prefill 451) - de toute façon. Donc dense → CPU = le bon choix robuste. -Les deux cibles (dense + 3.5) tournent, validées via `jni/test_jni_native.cpp` (détection + sortie cohérente). +## 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 diff --git a/dist/jni/kazeia_engine_jni.cpp b/dist/jni/kazeia_engine_jni.cpp index db5aa64..031125b 100644 --- a/dist/jni/kazeia_engine_jni.cpp +++ b/dist/jni/kazeia_engine_jni.cpp @@ -15,6 +15,18 @@ #include #include "llama.h" #include "ggml-backend.h" +#include "gguf.h" + +// Lit general.architecture dans l'en-tête GGUF SANS init backend (pour décider USE_HMX avant). +static std::string read_arch(const char* path) { + struct gguf_init_params gp = { /*no_alloc=*/true, /*ctx=*/nullptr }; + gguf_context* g = gguf_init_from_file(path, gp); + if (!g) return ""; + int64_t kid = gguf_find_key(g, "general.architecture"); + std::string arch = (kid >= 0) ? gguf_get_val_str(g, kid) : ""; + gguf_free(g); + return arch; +} struct KEngine { llama_model* m_h; llama_context* c_h; // prefill HTP (nullptr si pas de HTP) @@ -80,33 +92,43 @@ Java_com_kazeia_llm_EngineJni_load(JNIEnv* e, jobject, jstring path, jint nctx) std::string p(path_c); e->ReleaseStringUTFChars(path, path_c); + // Détection d'archi AVANT init backend (USE_HMX est latché à l'init). + std::string arch = read_arch(p.c_str()); + const bool hybrid = (arch.find("qwen3next") != std::string::npos) || (arch == "qwen35"); + + // Le matmul HMX faute (0x2e) sur les activations réelles du DENSE (massive activations > plage fp16). + // -> HMX OFF pour le dense (matmul HVX, stable, ~3x CPU). L'hybride (3.5) garde HMX (prefill rapide, OK). + if (!hybrid) setenv("GGML_HEXAGON_USE_HMX", "0", 1); setenv("GGML_HEXAGON_GDN_PREFILL", "1", 1); // sans effet sur les modèles sans GDN llama_backend_init(); llama_model* m_h = nullptr; llama_context* c_h = nullptr; llama_model* m_c = nullptr; llama_context* c_c = nullptr; - - // Instance CPU — toujours chargée (universelle) et sert à détecter la structure. - auto mp_c = llama_model_default_params(); mp_c.n_gpu_layers = 0; - m_c = llama_model_load_from_file(p.c_str(), mp_c); - if (!m_c) return 0; - c_c = make_ctx(m_c, nctx, 4, LLAMA_FLASH_ATTN_TYPE_ENABLED); // decode/prefill CPU - - const bool hybrid = llama_model_is_hybrid(m_c) || llama_model_is_recurrent(m_c); ggml_backend_dev_t htp = find_htp(); - if (hybrid && htp) { - // qwen3.5-like (GDN) -> OPTION C : ajoute une instance HTP pour le prefill, decode reste sur c_c (CPU). - // (decode GDN sur HTP = lent, donc on le garde CPU.) + if (htp) { ggml_backend_dev_t devs[2] = { htp, nullptr }; auto mp_h = llama_model_default_params(); mp_h.n_gpu_layers = 99; mp_h.devices = devs; m_h = llama_model_load_from_file(p.c_str(), mp_h); - if (m_h) c_h = make_ctx(m_h, nctx, 8, LLAMA_FLASH_ATTN_TYPE_ENABLED); // prefill HTP - fprintf(stderr, "kazeia-engine: modèle HYBRIDE (GDN) -> option C (prefill HTP / decode CPU)\n"); + } + if (m_h && hybrid) { + // qwen3.5-like (GDN) -> OPTION C : prefill HTP (HMX) + instance CPU pour le decode (decode GDN HTP = lent). + c_h = make_ctx(m_h, nctx, 8, LLAMA_FLASH_ATTN_TYPE_ENABLED); + auto mp_c = llama_model_default_params(); mp_c.n_gpu_layers = 0; + m_c = llama_model_load_from_file(p.c_str(), mp_c); + if (m_c) c_c = make_ctx(m_c, nctx, 4, LLAMA_FLASH_ATTN_TYPE_ENABLED); + fprintf(stderr, "kazeia-engine: HYBRIDE GDN (%s) -> option C, prefill HTP+HMX / decode CPU\n", arch.c_str()); + } else if (m_h) { + // dense -> HTP contexte-unique, matmul HVX (HMX off) : prefill+decode HTP, stable, ~3x CPU. + c_h = make_ctx(m_h, nctx, 4, LLAMA_FLASH_ATTN_TYPE_ENABLED); + fprintf(stderr, "kazeia-engine: DENSE (%s) -> HTP contexte-unique, matmul HVX (HMX off)\n", arch.c_str()); } else { - // dense (qwen3, llama, ...) ou pas de HTP -> CPU pur. Le prefill dense sur HTP crashe (0x2e, - // bug backend), et le dense a un .pte rapide ; le CPU est le chemin robuste et universel. - fprintf(stderr, "kazeia-engine: modèle DENSE / autre -> CPU pur (prefill+decode CPU)\n"); + // pas de HTP (ou échec du load HTP) -> CPU pur universel. + auto mp_c = llama_model_default_params(); mp_c.n_gpu_layers = 0; + m_c = llama_model_load_from_file(p.c_str(), mp_c); + if (!m_c) return 0; + c_c = make_ctx(m_c, nctx, 4, LLAMA_FLASH_ATTN_TYPE_ENABLED); + fprintf(stderr, "kazeia-engine: CPU pur (%s, pas de HTP)\n", arch.c_str()); } auto* k = new KEngine{ m_h, c_h, m_c, c_c, diff --git a/dist/lib/libkazeia_engine.so b/dist/lib/libkazeia_engine.so index 2869b49..8443112 100755 Binary files a/dist/lib/libkazeia_engine.so and b/dist/lib/libkazeia_engine.so differ