mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-12 01:37:06 +02:00
Le fork est parti de la v0.9.4 ; l'amont en est à la v0.10.7. Reprise de ce qui manque VRAIMENT ici, en laissant de côté ce que loki a déjà résolu à sa façon (contexte MTP via --parallel 1, jauge de contexte, chrono de tour, vignettes d'images, ligne d'état de génération). Rendu du chat cadencé puis lissé (amont v0.9.5 issue #24, v0.10.5). Chaque token re-parsait le Markdown du bloc ENTIER : du O(n²) qui faisait ramer l'interface sur un long raisonnement — le moteur débitait toujours autant, mais les tokens semblaient arriver au ralenti et un simple rafraîchissement « réparait » tout. Le texte s'accumule désormais et n'est re-rendu qu'à intervalle adaptatif (16 ms sur un petit bloc, jusqu'à 500 ms sur un énorme), soldé à chaque frontière (outil, bascule de rôle, fin de tour, erreur, rejeu). Par-dessus, un lissage d'apparition découple l'arrivée de l'affichage : le décodage spéculatif rend les tokens par rafales, le texte sautait par paquets ; il s'écoule maintenant à cadence régulière. Rejeu exclu — relire un fil ne doit pas être une lente réécriture. Échantillonnage réglable par preset (amont v0.9.5/v0.9.6) : TEMP, TOP_P, TOP_K, MIN_P, PRESENCE_PENALTY, REPEAT_PENALTY, injectés dans chaque requête (donc sans redémarrage du moteur), vide = défaut du serveur. Sans ça seule la température voyageait et le reste retombait sur les défauts de llama.cpp, rarement ceux que recommande le modèle. Différence avec l'amont : REASONING_EFFORT n'est PAS traité là — loki lui réserve un chemin plus riche, et l'écrire ici écraserait `chat_template_kwargs`, donc la consigne « aucune ». Un seul message système, en tête, à l'envoi (amont v0.9.8, issue #26). steerSystem ne couvrait que les consignes de loki ; un historique venu d'ailleurs peut encore en porter deux, et Qwen3.x en --jinja répond alors « System message must be at the beginning ». Copie normalisée : l'historique affiché et persisté garde sa forme. Détection Vulkan multi-distro (amont issues #28, #29) : le chemin Debian codé en dur est invisible sur Fedora/RHEL/Atomic, où le plan de build retombait sur le CPU. ldconfig d'abord, puis les chemins connus. Dossier de travail (amont v0.10.2) : la consigne dit maintenant ce que le dossier EST — l'endroit par défaut de tout ce que le modèle produit — et nomme les dossiers système à ne pas toucher, au lieu d'interdire vaguement d'en sortir. Non repris : les tâches planifiées (~1200 lignes + interface, à décider), et le quoting cmd.exe par .bat temporaire (loki tourne en conteneur Linux).
57 lines
2.4 KiB
Go
57 lines
2.4 KiB
Go
package loki
|
|
|
|
import "testing"
|
|
|
|
// Le sampling du preset doit se retrouver dans le corps de la requête, avec les
|
|
// bons types (top_k entier, le reste flottant), et TEMP écrase la température
|
|
// déjà posée. Une clé vide n'ajoute rien : le serveur garde son défaut.
|
|
func TestApplySamplingFrom(t *testing.T) {
|
|
payload := map[string]any{"temperature": 0.7}
|
|
applySamplingFrom(payload, map[string]string{
|
|
"TEMP": "1.0",
|
|
"TOP_P": "0.95",
|
|
"TOP_K": "20",
|
|
"MIN_P": "0",
|
|
"REPEAT_PENALTY": "1.05",
|
|
"PRESENCE_PENALTY": "", // vide → ignoré
|
|
})
|
|
if payload["temperature"] != 1.0 {
|
|
t.Errorf("TEMP doit écraser la température : %v", payload["temperature"])
|
|
}
|
|
if payload["top_p"] != 0.95 || payload["min_p"] != 0.0 || payload["repeat_penalty"] != 1.05 {
|
|
t.Errorf("flottants attendus : %v %v %v", payload["top_p"], payload["min_p"], payload["repeat_penalty"])
|
|
}
|
|
if payload["top_k"] != 20 {
|
|
t.Errorf("top_k doit être un entier 20, pas %#v", payload["top_k"])
|
|
}
|
|
if _, ok := payload["presence_penalty"]; ok {
|
|
t.Error("une clé vide ne doit rien envoyer")
|
|
}
|
|
}
|
|
|
|
// L'échantillonnage ne touche PAS au raisonnement : ce chemin appartient à
|
|
// llm_client.go/llm_effort.go, qui y met aussi `enable_thinking`. L'amont écrit
|
|
// `chat_template_kwargs` ici ; le faire chez nous effacerait la consigne
|
|
// « aucune » — donc le modèle réfléchirait alors que l'interface dit l'inverse.
|
|
func TestApplySamplingNeTouchePasAuRaisonnement(t *testing.T) {
|
|
payload := map[string]any{"chat_template_kwargs": map[string]any{"enable_thinking": false}}
|
|
applySamplingFrom(payload, map[string]string{"REASONING_EFFORT": "medium", "TOP_K": "20"})
|
|
kw, ok := payload["chat_template_kwargs"].(map[string]any)
|
|
if !ok || kw["enable_thinking"] != false || len(kw) != 1 {
|
|
t.Errorf("chat_template_kwargs modifié : %v", payload["chat_template_kwargs"])
|
|
}
|
|
if _, ok := payload["reasoning_effort"]; ok {
|
|
t.Error("reasoning_effort ne doit pas être posé par l'échantillonnage")
|
|
}
|
|
}
|
|
|
|
// Sans aucune clé de sampling, le payload n'est pas modifié : on ne force pas de
|
|
// valeurs qui contrediraient le défaut du modèle.
|
|
func TestApplySamplingEmpty(t *testing.T) {
|
|
payload := map[string]any{"temperature": 0.7}
|
|
applySamplingFrom(payload, map[string]string{})
|
|
if len(payload) != 1 || payload["temperature"] != 0.7 {
|
|
t.Errorf("payload modifié à tort : %v", payload)
|
|
}
|
|
}
|