kazeia/kazeia-android/docs/SAMPLING_ENGINE_SPEC.md

4.1 KiB

Spec — API d'échantillonnage natif (kazeia-engine) pour les presets Kazeia

Date : 2026-06-18 Demandeur : app Kazeia (refonte presets admin) Cible : kazeia-engine (lib libkazeia_engine, JNI EngineJni)

Contexte

L'app admin permet désormais de régler, par modèle (Speaker / Thinker) et via des presets nommés, les paramètres d'échantillonnage : temperature, top_p, top_k, repeat_penalty, presence_penalty, frequency_penalty, max_tokens.

Côté app, ces valeurs sont persistées et transmises jusqu'au pont JNI (UnifiedLlmAdapterEngineLlmEngine/LlmSession). MAIS le JNI actuel n'expose que maxTok :

external fun generate(h: Long, sys: String, usr: String, maxTok: Int): String
external fun sessionAsk(h: Long, usr: String, maxTok: Int, cb: TokenCallback)
external fun generateStream(h: Long, sys: String, usr: String, maxTok: Int, cb: TokenCallback)

Donc aujourd'hui seul max_tokens agit ; température/top-p/k/pénalités sont inertes (l'app l'indique honnêtement dans l'UI : « effectif après MAJ moteur »).

Demande — exposer le sampling au JNI

Ajouter une variante échantillonnée des entrées de génération (sans casser les signatures existantes), qui prend une struct de sampling et la câble sur la chaîne de samplers llama.cpp. Le GGUF Qwen3.5-4B (Speaker par défaut) est la cible prioritaire.

Signatures proposées

// Struct portée côté natif (ou 7 args primitifs si plus simple en JNI) :
//   temperature: Float, topP: Float, topK: Int,
//   repeatPenalty: Float, presencePenalty: Float, frequencyPenalty: Float, maxTokens: Int
external fun generateSampled(
    h: Long, sys: String, usr: String,
    maxTokens: Int, temperature: Float, topP: Float, topK: Int,
    repeatPenalty: Float, presencePenalty: Float, frequencyPenalty: Float,
    cb: TokenCallback
)
external fun sessionAskSampled(
    h: Long, usr: String,
    maxTokens: Int, temperature: Float, topP: Float, topK: Int,
    repeatPenalty: Float, presencePenalty: Float, frequencyPenalty: Float,
    cb: TokenCallback
)

Mapping llama.cpp (sampler chain)

Construire la chaîne par requête (ou réutiliser un llama_sampler reconfiguré) :

auto * chain = llama_sampler_chain_init(llama_sampler_chain_default_params());
llama_sampler_chain_add(chain, llama_sampler_init_top_k(top_k));
llama_sampler_chain_add(chain, llama_sampler_init_top_p(top_p, 1));
llama_sampler_chain_add(chain, llama_sampler_init_penalties(
    /*penalty_last_n*/ 64, repeat_penalty, frequency_penalty, presence_penalty));
llama_sampler_chain_add(chain, llama_sampler_init_temp(temperature));
llama_sampler_chain_add(chain, llama_sampler_init_dist(/*seed*/ LLAMA_DEFAULT_SEED));
  • temperature <= 0 ⇒ greedy (llama_sampler_init_greedy), ignorer les autres.
  • Conserver le thinking-off et le template ChatML actuels inchangés.
  • presence/frequency_penalty = 0 ⇒ no-op (comportement neutre).

Comportement attendu

  • Valeurs de référence (preset « Kaz chaleureux ») : temp 0.4, top_p 0.85, top_k 40, repeat 1.05, presence 0.2, frequency 0.2, max 256 — doit produire des sorties visiblement moins répétitives / plus chaleureuses que le greedy actuel.
  • Déterminisme : exposer un seed optionnel plus tard si besoin (pas requis v1).

.pte (NPU) — hors scope v1

Le runner ExecuTorch .pte décode greedy. Le sampling y est une évolution distincte (échantillonnage post-logits côté runner). Pour l'instant les presets n'affectent que le chemin GGUF ; documenter la limite suffit.

Côté app — déjà prêt

  • SamplingParams (com.kazeia.core) porte les 7 knobs.
  • UnifiedLlmAdapter.generateWithSystem reçoit les params ; il suffira de router vers *Sampled au lieu de ask(max) / generateStream(max) quand le JNI existe.
  • ConfigStore.ModelConfig + presets persistent les valeurs ; l'admin les édite.

Quand le JNI échantillonné est livré : ~10 lignes à changer côté UnifiedLlmAdapter (brancher sessionAskSampled / generateSampled) et retirer la mention « effectif après MAJ moteur » de l'UI admin.