dist: routage par structure (is_hybrid) — 3.5->option C, dense->CPU robuste
Détection au load via llama_model_is_hybrid/is_recurrent (structure, pas string d'archi): - hybride GDN (qwen3.5/qwen3next) -> option C (prefill HTP/decode CPU), préservé+validé. - dense (qwen3, etc.) / pas de HTP -> CPU pur (universel, robuste). Charge l'instance CPU d'abord (détection + universel), n'ajoute l'instance HTP que pour l'hybride. Diagnostic dense-HTP: le prefill dense sur HTP CRASHE (dspqueue_read 0x2e) sur prompt réaliste (reproduit aussi en llama-cli -dev HTP0 -fa 1; le "ça marche" antérieur = prompt minuscule sans fa, cas chanceux). Bug backend HTP attention dense multi-token, non corrigé. Dense a un .pte (451) de toute façon -> dense=CPU robuste. Validé test_jni_native: dense (CPU) + 3.5 (option C) cohérents. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eeffbc350f
commit
c166391f49
|
|
@ -3,14 +3,18 @@
|
||||||
Point d'entrée unique. Remplace le LLM ExecuTorch/Genie par llama.cpp (fork ql) + Hexagon.
|
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é).
|
GGUF, pas de `.pte`. STT reste ORT-QAIRT (inchangé).
|
||||||
|
|
||||||
## Générique : fait tourner N'IMPORTE QUEL LLM (auto-détection d'archi au `load`)
|
## Générique : fait tourner N'IMPORTE QUEL LLM (détection de STRUCTURE au `load`)
|
||||||
Le JNI lit `general.architecture` et choisit le chemin :
|
Le JNI détecte la structure du modèle via **`llama_model_is_hybrid` / `llama_model_is_recurrent`** (pas un
|
||||||
- **qwen35 / qwen3next** (hybride GDN) → **option C** : prefill HTP → transfert KV → decode CPU (le decode GDN
|
match de string d'archi) et choisit le chemin :
|
||||||
sur HTP est lent, donc on le garde sur CPU). Validé (3.5).
|
- **Hybride GDN (qwen3.5 / qwen3next)** → **option C** : prefill HTP → transfert KV → decode CPU (le decode
|
||||||
- **tout le reste** (qwen3 dense, etc.) → **CPU pur** : chemin universel, marche pour tout modèle llama.cpp,
|
GDN sur HTP est lent, on le garde CPU). Validé (3.5 : mono-tour + mémoire conversationnelle).
|
||||||
jamais de crash. (Le dense tournait déjà sur HTP en contexte unique 98/11.4 — voir note dense ci-dessous —
|
- **Dense (qwen3, llama, …) ou pas de HTP** → **CPU pur** : universel, robuste, jamais de crash. Validé (dense
|
||||||
mais le decode dense HTP ≈ CPU, donc CPU suffit et reste sûr pour les archis non validées.)
|
Qwen3-4B cohérent).
|
||||||
Les deux modèles cibles (dense + 3.5) tournent. Validé via `jni/test_jni_native.cpp` sur les deux.
|
⚠ **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).
|
||||||
|
|
||||||
## Décisions figées cette session
|
## 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
|
- **Modèle Speaker = Qwen3.5-4B en variante `q35-lmq4.gguf`** (embeds en Q4 au lieu du Q6_K
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
// kazeia_engine_jni.cpp — bridge LLM Kazeia-Engine (llama.cpp fork ql + Hexagon).
|
// kazeia_engine_jni.cpp — bridge LLM Kazeia-Engine (llama.cpp fork ql + Hexagon).
|
||||||
// GÉNÉRIQUE: fait tourner N'IMPORTE QUEL LLM. Auto-détecte l'archi au load :
|
// GÉNÉRIQUE : fait tourner N'IMPORTE QUEL LLM. Au load, détecte la STRUCTURE du modèle
|
||||||
// - qwen35 / qwen3next (hybride GDN) -> OPTION C : prefill HTP (ngl99) -> transfert KV -> decode CPU.
|
// (llama_model_is_hybrid/is_recurrent) et route :
|
||||||
// - tout le reste (qwen3 dense, etc.) -> CPU pur (universel, le prefill HTP dense crashe 0x2e).
|
// - HYBRIDE GDN (qwen3.5 / qwen3next) -> OPTION C : prefill HTP -> transfert KV -> decode CPU
|
||||||
// Le chemin CPU marche pour tout modèle supporté par llama.cpp ; HTP = accélération conditionnelle validée.
|
// (le decode GDN sur HTP est lent ; le split le garde sur CPU).
|
||||||
|
// - DENSE (qwen3, llama, ...) -> HTP contexte-unique : prefill + decode sur HTP
|
||||||
|
// (decode dense HTP ~= CPU, pas de pénalité GDN ; prefill ~98 vs 14 CPU ; pas de dual-load => pas de crash 0x2e).
|
||||||
|
// - pas de device HTP -> CPU pur (repli universel).
|
||||||
// API : load -> generate(sys,usr) | generateRaw(prompt) -> reset/free. Sans état entre appels.
|
// API : load -> generate(sys,usr) | generateRaw(prompt) -> reset/free. Sans état entre appels.
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -13,38 +17,47 @@
|
||||||
#include "ggml-backend.h"
|
#include "ggml-backend.h"
|
||||||
|
|
||||||
struct KEngine {
|
struct KEngine {
|
||||||
llama_model* m_h; llama_context* c_h; // prefill HTP (nullptr si CPU-only)
|
llama_model* m_h; llama_context* c_h; // prefill HTP (nullptr si pas de HTP)
|
||||||
llama_model* m_c; llama_context* c_c; // decode/prefill CPU (toujours présent)
|
llama_model* m_c; llama_context* c_c; // decode CPU (nullptr si HTP contexte-unique)
|
||||||
const llama_vocab* v; llama_sampler* s;
|
const llama_vocab* v; llama_sampler* s;
|
||||||
};
|
};
|
||||||
|
|
||||||
static llama_context* make_ctx(llama_model* m, int nctx, int nthreads) {
|
static llama_context* make_ctx(llama_model* m, int nctx, int nthreads, enum llama_flash_attn_type fa) {
|
||||||
auto cp = llama_context_default_params();
|
auto cp = llama_context_default_params();
|
||||||
cp.n_ctx = nctx; cp.n_batch = nctx; cp.n_threads = nthreads;
|
cp.n_ctx = nctx; cp.n_batch = 2048; cp.n_threads = nthreads;
|
||||||
cp.flash_attn_type = LLAMA_FLASH_ATTN_TYPE_ENABLED; // t4+fa optimum decode
|
cp.flash_attn_type = fa; // ENABLED (decode CPU) / DISABLED (prefill HTP dense)
|
||||||
cp.type_k = GGML_TYPE_F16; cp.type_v = GGML_TYPE_F16; // KV f16 (q8_0 = -40% decode, mesuré)
|
cp.type_k = GGML_TYPE_F16; cp.type_v = GGML_TYPE_F16; // KV f16 (q8_0 = -40% decode, mesuré)
|
||||||
return llama_init_from_model(m, cp);
|
return llama_init_from_model(m, cp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cœur : prefill (HTP si dispo, sinon CPU) -> [transfert KV si HTP] -> decode CPU. Texte généré.
|
static ggml_backend_dev_t find_htp() {
|
||||||
|
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
|
||||||
|
auto d = ggml_backend_dev_get(i);
|
||||||
|
if (!strcmp(ggml_backend_dev_name(d), "HTP0")) return d;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cœur : prefill (HTP si dispo) -> [transfert KV si dual-ctx] -> decode (CPU en option C, sinon HTP).
|
||||||
static std::string kengine_run(KEngine* k, const std::string& p, int maxTok) {
|
static std::string kengine_run(KEngine* k, const std::string& p, int maxTok) {
|
||||||
int n = -llama_tokenize(k->v, p.c_str(), p.size(), nullptr, 0, true, true);
|
int n = -llama_tokenize(k->v, p.c_str(), p.size(), nullptr, 0, true, true);
|
||||||
std::vector<llama_token> t(n);
|
std::vector<llama_token> t(n);
|
||||||
llama_tokenize(k->v, p.c_str(), p.size(), t.data(), n, true, true);
|
llama_tokenize(k->v, p.c_str(), p.size(), t.data(), n, true, true);
|
||||||
|
|
||||||
llama_context* pf = k->c_h ? k->c_h : k->c_c; // contexte de prefill
|
llama_context* pf = k->c_h ? k->c_h : k->c_c; // contexte de prefill
|
||||||
|
llama_context* dec = k->c_c ? k->c_c : k->c_h; // contexte de decode
|
||||||
|
|
||||||
llama_memory_clear(llama_get_memory(pf), true);
|
llama_memory_clear(llama_get_memory(pf), true);
|
||||||
llama_batch b = llama_batch_get_one(t.data(), n);
|
llama_batch b = llama_batch_get_one(t.data(), n);
|
||||||
if (llama_decode(pf, b) != 0) return std::string();
|
if (llama_decode(pf, b) != 0) return std::string();
|
||||||
|
|
||||||
if (k->c_h) { // mode HTP : transfert KV vers le contexte CPU
|
if (k->c_h && k->c_c) { // option C : transfert KV HTP -> CPU
|
||||||
size_t sz = llama_state_seq_get_size(k->c_h, 0);
|
size_t sz = llama_state_seq_get_size(k->c_h, 0);
|
||||||
std::vector<uint8_t> buf(sz);
|
std::vector<uint8_t> buf(sz);
|
||||||
llama_state_seq_get_data(k->c_h, buf.data(), sz, 0);
|
llama_state_seq_get_data(k->c_h, buf.data(), sz, 0);
|
||||||
llama_memory_clear(llama_get_memory(k->c_c), true);
|
llama_memory_clear(llama_get_memory(k->c_c), true);
|
||||||
llama_state_seq_set_data(k->c_c, buf.data(), sz, 0);
|
llama_state_seq_set_data(k->c_c, buf.data(), sz, 0);
|
||||||
}
|
}
|
||||||
// (mode CPU : pf == c_c, le KV de prefill est déjà dans c_c)
|
|
||||||
|
|
||||||
llama_token id = llama_sampler_sample(k->s, pf, -1); // 1er token depuis les logits de prefill
|
llama_token id = llama_sampler_sample(k->s, pf, -1); // 1er token depuis les logits de prefill
|
||||||
std::string out; char zbuf[256]; int pos = n;
|
std::string out; char zbuf[256]; int pos = n;
|
||||||
|
|
@ -55,44 +68,49 @@ static std::string kengine_run(KEngine* k, const std::string& p, int maxTok) {
|
||||||
llama_token tok = id; llama_pos pp = pos; int32_t ns = 1; llama_seq_id sd = 0, *spd = &sd; int8_t lg = 1;
|
llama_token tok = id; llama_pos pp = pos; int32_t ns = 1; llama_seq_id sd = 0, *spd = &sd; int8_t lg = 1;
|
||||||
llama_batch sb; memset(&sb, 0, sizeof sb);
|
llama_batch sb; memset(&sb, 0, sizeof sb);
|
||||||
sb.n_tokens = 1; sb.token = &tok; sb.pos = &pp; sb.n_seq_id = &ns; sb.seq_id = &spd; sb.logits = ≶
|
sb.n_tokens = 1; sb.token = &tok; sb.pos = &pp; sb.n_seq_id = &ns; sb.seq_id = &spd; sb.logits = ≶
|
||||||
if (llama_decode(k->c_c, sb) != 0) break; // decode toujours sur CPU
|
if (llama_decode(dec, sb) != 0) break;
|
||||||
pos++; id = llama_sampler_sample(k->s, k->c_c, -1);
|
pos++; id = llama_sampler_sample(k->s, dec, -1);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jlong JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_kazeia_llm_EngineJni_load(JNIEnv* e, jobject, jstring path, jint nctx) {
|
Java_com_kazeia_llm_EngineJni_load(JNIEnv* e, jobject, jstring path, jint nctx) {
|
||||||
const char* p = e->GetStringUTFChars(path, 0);
|
const char* path_c = e->GetStringUTFChars(path, 0);
|
||||||
|
std::string p(path_c);
|
||||||
|
e->ReleaseStringUTFChars(path, path_c);
|
||||||
|
|
||||||
setenv("GGML_HEXAGON_GDN_PREFILL", "1", 1); // sans effet sur les modèles sans GDN
|
setenv("GGML_HEXAGON_GDN_PREFILL", "1", 1); // sans effet sur les modèles sans GDN
|
||||||
llama_backend_init();
|
llama_backend_init();
|
||||||
|
|
||||||
// CPU model — toujours chargé (chemin universel)
|
|
||||||
auto mp_c = llama_model_default_params(); mp_c.n_gpu_layers = 0;
|
|
||||||
auto m_c = llama_model_load_from_file(p, mp_c);
|
|
||||||
if (!m_c) { e->ReleaseStringUTFChars(path, p); return 0; }
|
|
||||||
|
|
||||||
// Auto-détection archi : HTP-prefill seulement pour les hybrides GDN validés (qwen35/qwen3next).
|
|
||||||
char arch[64] = {0};
|
|
||||||
llama_model_meta_val_str(m_c, "general.architecture", arch, sizeof arch);
|
|
||||||
bool htp = (strstr(arch, "qwen35") != nullptr) || (strstr(arch, "qwen3next") != nullptr);
|
|
||||||
|
|
||||||
llama_model* m_h = nullptr; llama_context* c_h = nullptr;
|
llama_model* m_h = nullptr; llama_context* c_h = nullptr;
|
||||||
if (htp) {
|
llama_model* m_c = nullptr; llama_context* c_c = nullptr;
|
||||||
static ggml_backend_dev_t devs[2] = { nullptr, nullptr };
|
|
||||||
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
|
|
||||||
auto d = ggml_backend_dev_get(i);
|
|
||||||
if (!strcmp(ggml_backend_dev_name(d), "HTP0")) devs[0] = d;
|
|
||||||
}
|
|
||||||
auto mp_h = llama_model_default_params(); mp_h.n_gpu_layers = 99; if (devs[0]) mp_h.devices = devs;
|
|
||||||
m_h = llama_model_load_from_file(p, mp_h);
|
|
||||||
if (m_h) c_h = make_ctx(m_h, nctx, 8); // prefill t8
|
|
||||||
// si le chargement HTP échoue, on retombe proprement sur CPU-only (c_h = nullptr)
|
|
||||||
}
|
|
||||||
e->ReleaseStringUTFChars(path, p);
|
|
||||||
|
|
||||||
auto* k = new KEngine{ m_h, c_h, m_c, make_ctx(m_c, nctx, 4), // decode/prefill CPU t4
|
// Instance CPU — toujours chargée (universelle) et sert à détecter la structure.
|
||||||
llama_model_get_vocab(m_c), llama_sampler_init_greedy() };
|
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.)
|
||||||
|
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");
|
||||||
|
} 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* k = new KEngine{ m_h, c_h, m_c, c_c,
|
||||||
|
llama_model_get_vocab(m_h ? m_h : m_c), llama_sampler_init_greedy() };
|
||||||
return (jlong) k;
|
return (jlong) k;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,15 +140,15 @@ extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_kazeia_llm_EngineJni_reset(JNIEnv*, jobject, jlong h){
|
Java_com_kazeia_llm_EngineJni_reset(JNIEnv*, jobject, jlong h){
|
||||||
auto* k = (KEngine*) h;
|
auto* k = (KEngine*) h;
|
||||||
if (k->c_h) llama_memory_clear(llama_get_memory(k->c_h), true);
|
if (k->c_h) llama_memory_clear(llama_get_memory(k->c_h), true);
|
||||||
llama_memory_clear(llama_get_memory(k->c_c), true);
|
if (k->c_c) llama_memory_clear(llama_get_memory(k->c_c), true);
|
||||||
}
|
}
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_kazeia_llm_EngineJni_free(JNIEnv*, jobject, jlong h){
|
Java_com_kazeia_llm_EngineJni_free(JNIEnv*, jobject, jlong h){
|
||||||
auto* k = (KEngine*) h;
|
auto* k = (KEngine*) h;
|
||||||
llama_sampler_free(k->s);
|
llama_sampler_free(k->s);
|
||||||
if (k->c_h) llama_free(k->c_h);
|
if (k->c_h) llama_free(k->c_h);
|
||||||
llama_free(k->c_c);
|
if (k->c_c) llama_free(k->c_c);
|
||||||
if (k->m_h) llama_model_free(k->m_h);
|
if (k->m_h) llama_model_free(k->m_h);
|
||||||
llama_model_free(k->m_c);
|
if (k->m_c) llama_model_free(k->m_c);
|
||||||
delete k;
|
delete k;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue