From 0407ac8b71b80060d420f8f8a04c80e6af53f1dc Mon Sep 17 00:00:00 2001 From: Richard Loyer Date: Wed, 27 May 2026 12:42:15 +0200 Subject: [PATCH] dist: ROOT CAUSE crash dense-HTP = matmul HMX (fp16) ; fix = HVX pour le dense MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Analyse complète (méthode SSM_CONV) du crash dense-HTP: - bench HTP synthétique (pp512, +tg8) = OK ; cli/engine texte réel crashe 0x2e > ~14 tokens. - OPFILTER bisect: SOFT_MAX/ROPE/RMS_NORM/GET_ROWS/CPY/CONT/ADD n'arrêtent PAS le crash ; MUL_MAT->CPU OUI. - GGML_HEXAGON_USE_HMX=0 (matmul HVX) supprime le crash, =1 le reproduit. => le matmul HMX (fp16 tile) faute sur les activations réelles du dense (massive activations Qwen3 > plage fp16 ; bench synthétique ne déclenche pas l'overflow). Analogue du SSM_CONV pour la 3.5, mais ici c'est le matmul HMX coeur. Câblage: lecture general.architecture dans le GGUF AVANT init backend -> hybride (qwen35/qwen3next): HMX on + option C (préservé) ; dense: USE_HMX=0 + HTP contexte-unique (HVX, ~40 prefill =3x CPU, decode ~11, stable). Repli CPU si pas de HTP. Validé test_jni_native: dense (HVX HTP) + 3.5 (option C) cohérents. TODO backend: matmul HMX robuste aux activations hors plage fp16. Co-Authored-By: Claude Opus 4.7 (1M context) --- dist/HANDOFF.md | 26 ++++++++-------- dist/jni/kazeia_engine_jni.cpp | 54 +++++++++++++++++++++++---------- dist/lib/libkazeia_engine.so | Bin 39728 -> 40808 bytes 3 files changed, 52 insertions(+), 28 deletions(-) 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 2869b49673ffaff9f1219a077d839ca83e49014a..8443112717be35dc953952ec354945b24eb95cfe 100755 GIT binary patch delta 13602 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�)I6J+!xIy!W@Eu$ga4 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 Mc_V(Z{-XAO0C@SC1poj5