dist: ROOT CAUSE crash dense-HTP = matmul HMX (fp16) ; fix = HVX pour le dense

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) <noreply@anthropic.com>
This commit is contained in:
Richard Loyer 2026-05-27 12:42:15 +02:00
parent c166391f49
commit 0407ac8b71
3 changed files with 52 additions and 28 deletions

26
dist/HANDOFF.md vendored
View File

@ -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

View File

@ -15,6 +15,18 @@
#include <cstring>
#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,

Binary file not shown.