diff --git a/dist/jni/EngineLlmEngine.kt b/dist/jni/EngineLlmEngine.kt index 88b4a07..57c4bd8 100644 --- a/dist/jni/EngineLlmEngine.kt +++ b/dist/jni/EngineLlmEngine.kt @@ -8,6 +8,17 @@ class EngineJni { external fun generateRaw(h: Long, prompt: String, maxTok: Int): String // prompt complet déjà formaté external fun reset(h: Long) external fun free(h: Long) + + // -- TTS Talker (Qwen3-TTS) : I/O en embeddings, pas en tokens (vocab=3072 codes audio, pas de BPE). + // Le caller (cf. TalkerEngine.kt) construit les embeds de prefill (text+x-vector) et de step + // (sum 16 codecs + tts_pad + trailing_text_hidden), fait l'échantillonnage, et compose le + // pipeline Talker→CP→Decoder. Retours conventionnels : 0 = OK, négatif = erreur. + external fun nEmbd(h: Long): Int // taille d'un embed (1024 pour Talker-0.6B) + external fun nVocab(h: Long): Int // 3072 pour le Talker + external fun resetEmbeds(h: Long) // KV clear + pos=0, à appeler en début de génération TTS + external fun prefillEmbeds(h: Long, embdFlat: FloatArray, t: Int, outHidden: FloatArray): Int + external fun decodeEmbed(h: Long, embd: FloatArray, outLogits: FloatArray, outHidden: FloatArray): Int + companion object { init { System.loadLibrary("kazeia_engine") } } } diff --git a/dist/jni/kazeia_engine_jni.cpp b/dist/jni/kazeia_engine_jni.cpp index 031125b..8abc448 100644 --- a/dist/jni/kazeia_engine_jni.cpp +++ b/dist/jni/kazeia_engine_jni.cpp @@ -32,6 +32,7 @@ struct KEngine { llama_model* m_h; llama_context* c_h; // prefill HTP (nullptr si pas de HTP) llama_model* m_c; llama_context* c_c; // decode CPU (nullptr si HTP contexte-unique) const llama_vocab* v; llama_sampler* s; + int pos_embd; // position courante en mode talker embeds-only (TTS) }; static llama_context* make_ctx(llama_model* m, int nctx, int nthreads, enum llama_flash_attn_type fa) { @@ -132,7 +133,8 @@ Java_com_kazeia_llm_EngineJni_load(JNIEnv* e, jobject, jstring path, jint nctx) } 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() }; + llama_model_get_vocab(m_h ? m_h : m_c), llama_sampler_init_greedy(), + /*pos_embd=*/0 }; return (jlong) k; } @@ -174,3 +176,124 @@ Java_com_kazeia_llm_EngineJni_free(JNIEnv*, jobject, jlong h){ if (k->m_c) llama_model_free(k->m_c); delete k; } + +// ============================================================================ +// TTS Talker API : I/O en embeddings, pas en tokens. +// Le Talker (Qwen3-TTS) a vocab = 3072 codes audio (pas de BPE), entrée = embeds +// pré-mélangés (text+x-vector au prefill, sum 16 codecs + pad au decode), sortie +// = logits sur les 3072 codes audio + hidden state (1024) pour le Code Predictor. +// +// Contrainte : le contexte est partagé avec generate/generateRaw (mêmes KV-cache). +// Appeler resetEmbeds() avant de switcher entre mode texte et mode TTS, idem en +// début de génération TTS pour repartir d'un KV propre. +// +// Routing : le talker est qwen3 dense -> charge via le chemin DENSE (HTP HVX, +// HMX off), donc c_h non-null et c_c null. pf == dec == c_h, exactement comme +// kengine_run. +// ============================================================================ + +extern "C" JNIEXPORT jint JNICALL +Java_com_kazeia_llm_EngineJni_nEmbd(JNIEnv*, jobject, jlong h) { + auto* k = (KEngine*) h; + return llama_model_n_embd(k->m_h ? k->m_h : k->m_c); +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_kazeia_llm_EngineJni_nVocab(JNIEnv*, jobject, jlong h) { + auto* k = (KEngine*) h; + return llama_vocab_n_tokens(k->v); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_kazeia_llm_EngineJni_resetEmbeds(JNIEnv*, jobject, jlong h) { + auto* k = (KEngine*) h; + if (k->c_h) llama_memory_clear(llama_get_memory(k->c_h), true); + if (k->c_c) llama_memory_clear(llama_get_memory(k->c_c), true); + k->pos_embd = 0; +} + +// Helper interne : décode un batch d'embeddings, ne demande la sortie que pour la +// dernière position (économie KV + logits), récupère hidden[n_embd] dans out_hidden +// si fourni. Avance k->pos_embd de T. Renvoie 0 si OK. +static int decode_embd_batch(KEngine* k, const float* embd, int T, float* out_hidden) { + llama_context* ctx = k->c_h ? k->c_h : k->c_c; + llama_set_embeddings(ctx, true); + + std::vector pos (T); + std::vector nsd (T, 1); + std::vector sid0(T, 0); + std::vector sids(T); + std::vector lg (T, 0); + for (int i = 0; i < T; ++i) { pos[i] = k->pos_embd + i; sids[i] = &sid0[i]; } + lg[T-1] = 1; // n'output logits/embeddings que pour la dernière position + + llama_batch b{}; + b.n_tokens = T; + b.token = nullptr; + b.embd = const_cast(embd); + b.pos = pos.data(); + b.n_seq_id = nsd.data(); + b.seq_id = sids.data(); + b.logits = lg.data(); + + if (llama_decode(ctx, b) != 0) return -1; + k->pos_embd += T; + + if (out_hidden) { + const float* eh = llama_get_embeddings_ith(ctx, -1); + if (!eh) return -2; + int n_embd = llama_model_n_embd(k->m_h ? k->m_h : k->m_c); + memcpy(out_hidden, eh, sizeof(float) * n_embd); + } + return 0; +} + +// Prefill embeds : T positions, renvoie le hidden state de la dernière dans out_hidden[n_embd]. +// embd_flat doit être de longueur T * n_embd (float, row-major : position 0 d'abord). +extern "C" JNIEXPORT jint JNICALL +Java_com_kazeia_llm_EngineJni_prefillEmbeds(JNIEnv* e, jobject, jlong h, + jfloatArray embd_flat, jint T, + jfloatArray out_hidden) { + auto* k = (KEngine*) h; + if (T <= 0) return -10; + int n_embd = llama_model_n_embd(k->m_h ? k->m_h : k->m_c); + if (e->GetArrayLength(embd_flat) != T * n_embd) return -11; + if (e->GetArrayLength(out_hidden) != n_embd) return -12; + + jfloat* embd = e->GetFloatArrayElements(embd_flat, nullptr); + std::vector hidden(n_embd); + int rc = decode_embd_batch(k, embd, T, hidden.data()); + e->ReleaseFloatArrayElements(embd_flat, embd, JNI_ABORT); + if (rc != 0) return rc; + e->SetFloatArrayRegion(out_hidden, 0, n_embd, hidden.data()); + return 0; +} + +// Decode one embed step : avance une position. Renvoie logits[vocab] + hidden[n_embd]. +// Le caller fait l'échantillonnage (greedy / temp / top_k / rep_penalty) et compose +// l'embed suivant (sum des 16 codec_embs + tts_pad_embed + trailing_text_hidden). +extern "C" JNIEXPORT jint JNICALL +Java_com_kazeia_llm_EngineJni_decodeEmbed(JNIEnv* e, jobject, jlong h, + jfloatArray embd, + jfloatArray out_logits, + jfloatArray out_hidden) { + auto* k = (KEngine*) h; + int n_embd = llama_model_n_embd(k->m_h ? k->m_h : k->m_c); + int n_vocab = llama_vocab_n_tokens(k->v); + if (e->GetArrayLength(embd) != n_embd) return -11; + if (e->GetArrayLength(out_logits) != n_vocab) return -12; + if (e->GetArrayLength(out_hidden) != n_embd) return -13; + + jfloat* ev = e->GetFloatArrayElements(embd, nullptr); + std::vector hidden(n_embd); + int rc = decode_embd_batch(k, ev, 1, hidden.data()); + e->ReleaseFloatArrayElements(embd, ev, JNI_ABORT); + if (rc != 0) return rc; + + llama_context* ctx = k->c_h ? k->c_h : k->c_c; + const float* lg = llama_get_logits_ith(ctx, -1); + if (!lg) return -14; + e->SetFloatArrayRegion(out_logits, 0, n_vocab, lg); + e->SetFloatArrayRegion(out_hidden, 0, n_embd, hidden.data()); + return 0; +} diff --git a/dist/jni/test_talker.cpp b/dist/jni/test_talker.cpp new file mode 100644 index 0000000..a205eb2 --- /dev/null +++ b/dist/jni/test_talker.cpp @@ -0,0 +1,118 @@ +// Smoke test : charge le Talker (qwen3 dense + M-RoPE) via le DENSE path engine, +// puis exerce les primitives embeds (prefillEmbeds/decodeEmbed) avec un input bidon. +// +// But : prouver que (a) Patch 2 active M-RoPE (log "mrope sections = [24,20,20,0]"), +// (b) Patch 1 fait tourner llama_decode embeds-only + ressort hidden state + logits. +// Pas de vérification de qualité ici — c'est le job du câblage CP+Decoder (Patch 3). +// +// Usage : ./test_talker +// +// Build : copie dans dist/jni/, link avec libkazeia_engine.so dépendances. +#include +#include +#include +#include +#include "llama.h" +#include "ggml-backend.h" + +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; +} + +int main(int argc, char** argv) { + if (argc < 2) { printf("usage: %s \n", argv[0]); return 1; } + + // dense path : HMX off (cf. kazeia_engine_jni) + setenv("GGML_HEXAGON_USE_HMX", "0", 1); + llama_backend_init(); + + auto mp = llama_model_default_params(); + // 2e argument optionnel : "cpu" force le path CPU même si HTP dispo (utile pour itérer + // sans payer l'upload HTP de 1.66GB f32 à chaque smoke test). + const bool force_cpu = (argc >= 3 && !strcmp(argv[2], "cpu")); + ggml_backend_dev_t devs[2] = { force_cpu ? nullptr : find_htp(), nullptr }; + if (devs[0]) { mp.n_gpu_layers = 99; mp.devices = devs; printf("HTP0 -> ngl=99\n"); } + else { mp.n_gpu_layers = 0; printf("CPU only%s\n", force_cpu ? " (force)" : " (pas de HTP)"); } + + auto m = llama_model_load_from_file(argv[1], mp); + if (!m) { printf("model load FAILED\n"); return 1; } + printf("model loaded. n_embd=%d, n_vocab=%d\n", + llama_model_n_embd(m), llama_vocab_n_tokens(llama_model_get_vocab(m))); + + auto cp = llama_context_default_params(); + cp.n_ctx = 1024; cp.n_batch = 1024; cp.n_threads = 4; + cp.flash_attn_type = LLAMA_FLASH_ATTN_TYPE_ENABLED; + cp.embeddings = true; // équivaut à llama_set_embeddings(ctx, true) + + auto ctx = llama_init_from_model(m, cp); + if (!ctx) { printf("ctx init FAILED\n"); return 1; } + + const int n_embd = llama_model_n_embd(m); + const int n_vocab = llama_vocab_n_tokens(llama_model_get_vocab(m)); + + // --- prefill : 4 positions d'embeds aléatoires (juste pour valider le pipeline, + // PAS pour produire du français sensé) --- + const int T = 4; + std::vector embd(T * n_embd); + srand(42); + for (auto& x : embd) x = (rand() / (float)RAND_MAX - 0.5f) * 0.01f; + + std::vector pos(T); + std::vector nsd(T, 1); + std::vector sid0(T, 0); + std::vector sids(T); + std::vector lg(T, 0); + for (int i = 0; i < T; ++i) { pos[i] = i; sids[i] = &sid0[i]; } + lg[T-1] = 1; + + llama_batch b{}; + b.n_tokens = T; b.token = nullptr; b.embd = embd.data(); + b.pos = pos.data(); b.n_seq_id = nsd.data(); b.seq_id = sids.data(); b.logits = lg.data(); + + if (llama_decode(ctx, b) != 0) { printf("prefill embeds FAILED\n"); return 1; } + printf("prefill OK (T=%d)\n", T); + + const float* h_prefill = llama_get_embeddings_ith(ctx, -1); + if (!h_prefill) { printf("get_embeddings_ith FAILED\n"); return 1; } + float h_sum = 0, h_min = h_prefill[0], h_max = h_prefill[0]; + for (int i = 0; i < n_embd; ++i) { + h_sum += h_prefill[i]; + if (h_prefill[i] < h_min) h_min = h_prefill[i]; + if (h_prefill[i] > h_max) h_max = h_prefill[i]; + } + printf("hidden[prefill] : mean=%.6f min=%.6f max=%.6f\n", h_sum / n_embd, h_min, h_max); + + const float* logits = llama_get_logits_ith(ctx, -1); + if (!logits) { printf("get_logits_ith FAILED\n"); return 1; } + int argmax = 0; float lmax = logits[0]; + for (int i = 1; i < n_vocab; ++i) if (logits[i] > lmax) { lmax = logits[i]; argmax = i; } + printf("logits[prefill] : argmax=%d (val=%.3f)\n", argmax, lmax); + + // --- decode step : un seul embed de plus --- + std::vector embd1(n_embd); + for (auto& x : embd1) x = (rand() / (float)RAND_MAX - 0.5f) * 0.01f; + + llama_pos p1 = T; + int32_t n1 = 1; + llama_seq_id s1 = 0; llama_seq_id* sp1 = &s1; + int8_t l1 = 1; + llama_batch b1{}; + b1.n_tokens = 1; b1.token = nullptr; b1.embd = embd1.data(); + b1.pos = &p1; b1.n_seq_id = &n1; b1.seq_id = &sp1; b1.logits = &l1; + + if (llama_decode(ctx, b1) != 0) { printf("decode embeds FAILED\n"); return 1; } + const float* h_step = llama_get_embeddings_ith(ctx, -1); + const float* l_step = llama_get_logits_ith(ctx, -1); + int argmax_s = 0; float lmax_s = l_step[0]; + for (int i = 1; i < n_vocab; ++i) if (l_step[i] > lmax_s) { lmax_s = l_step[i]; argmax_s = i; } + printf("step OK : argmax=%d (val=%.3f), hidden[0..2]=%.4f %.4f %.4f\n", + argmax_s, lmax_s, h_step[0], h_step[1], h_step[2]); + + llama_free(ctx); + llama_model_free(m); + return 0; +} diff --git a/dist/lib/libggml-base.so b/dist/lib/libggml-base.so index 4689750..59fee4d 100755 Binary files a/dist/lib/libggml-base.so and b/dist/lib/libggml-base.so differ diff --git a/dist/lib/libkazeia_engine.so b/dist/lib/libkazeia_engine.so index 8443112..53d5295 100755 Binary files a/dist/lib/libkazeia_engine.so and b/dist/lib/libkazeia_engine.so differ diff --git a/dist/lib/libllama.so b/dist/lib/libllama.so index 49032ef..c369208 100755 Binary files a/dist/lib/libllama.so and b/dist/lib/libllama.so differ diff --git a/ql b/ql index 956f72f..c1609a1 160000 --- a/ql +++ b/ql @@ -1 +1 @@ -Subproject commit 956f72ffe4fe2758e7c5236ac8ea84752a48f7e3 +Subproject commit c1609a141d1c372b6f08a00fc272c56ce0fb5e6b