mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 15:08:16 +02:00
MTP : MODEL_DRAFT égal au modèle principal ignoré (poids chargés deux fois)
Choisir le modèle lui-même comme brouillon dans l'éditeur de preset écrivait MODEL_DRAFT=<le modèle>, et Loki passait -md vers ce fichier : common_speculative_init_result charge alors un second exemplaire complet du modèle, au lieu de greffer le contexte MTP sur le modèle déjà en VRAM. Swift 27B : « allocating 9628.34 MiB on device 0 » pour le brouillon, moteur mort ; quand ça passait, couches renvoyées sur le CPU et 5 t/s au lieu de 22 (prefill 290 t/s au lieu de 890). probeSpec reconnaît ce cas (os.SameFile), specArgs l'ignore et le dit : la tête MTP intégrée sert de brouillon sans -md. L'éditeur ne propose plus le modèle principal dans la liste des brouillons. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wfx8uW1un1WwWJ6xzAbkV
This commit is contained in:
5 files changed
+68
-4
No files matched your search
@@ -527,6 +527,7 @@ type serveSysInfo struct {
|
|||||||
Draft string // MODEL_DRAFT résolu et vérifié ; vide = absent ou introuvable
|
Draft string // MODEL_DRAFT résolu et vérifié ; vide = absent ou introuvable
|
||||||
DraftErr string // pourquoi MODEL_DRAFT n'a pas été trouvé
|
DraftErr string // pourquoi MODEL_DRAFT n'a pas été trouvé
|
||||||
DraftGGUF *GGUFInfo // métadonnées du brouillon ; nil = illisibles
|
DraftGGUF *GGUFInfo // métadonnées du brouillon ; nil = illisibles
|
||||||
|
DraftIsModel bool // MODEL_DRAFT désigne le modèle principal lui-même (voir specArgs)
|
||||||
SpecAutoBlocked string // un essai automatique précédent a échoué : la raison
|
SpecAutoBlocked string // un essai automatique précédent a échoué : la raison
|
||||||
|
|
||||||
// Cartes listées par le moteur (--list-devices), lues seulement pour
|
// Cartes listées par le moteur (--list-devices), lues seulement pour
|
||||||
|
|||||||
@@ -113,6 +113,11 @@ func probeSpec(cfg map[string]string, si *serveSysInfo) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
si.Draft = p
|
si.Draft = p
|
||||||
|
if a, err := os.Stat(p); err == nil {
|
||||||
|
if b, err := os.Stat(si.Model); err == nil && os.SameFile(a, b) {
|
||||||
|
si.DraftIsModel = true
|
||||||
|
}
|
||||||
|
}
|
||||||
if g, err := ggufMeta(p); err == nil {
|
if g, err := ggufMeta(p); err == nil {
|
||||||
si.DraftGGUF = &g
|
si.DraftGGUF = &g
|
||||||
}
|
}
|
||||||
@@ -241,6 +246,23 @@ func specAutoBlocker(cfg map[string]string, extra []string, si serveSysInfo) str
|
|||||||
// et s'ils viennent de SPEC=auto (cmdServe pose alors le jeton de tentative).
|
// et s'ils viennent de SPEC=auto (cmdServe pose alors le jeton de tentative).
|
||||||
// Fonction pure ; cmdServe a déjà résolu MODEL_DRAFT et lu les GGUF.
|
// Fonction pure ; cmdServe a déjà résolu MODEL_DRAFT et lu les GGUF.
|
||||||
func specArgs(cfg map[string]string, extra []string, si serveSysInfo) (args, notes []string, auto bool) {
|
func specArgs(cfg map[string]string, extra []string, si serveSysInfo) (args, notes []string, auto bool) {
|
||||||
|
// MODEL_DRAFT = le modèle principal lui-même (choisi dans la liste des
|
||||||
|
// brouillons de l'éditeur) : -md recharge alors les 27B une seconde fois
|
||||||
|
// (common_speculative_init_result charge tout fichier -md comme un modèle
|
||||||
|
// à part), quand la tête MTP intégrée se greffe sur le modèle déjà en VRAM.
|
||||||
|
// Vécu : « allocating 9628.34 MiB on device 0 » pour le brouillon, moteur
|
||||||
|
// mort ; ou, quand ça passait, des couches renvoyées sur le CPU et 5 t/s au
|
||||||
|
// lieu de 22. On l'ignore : même tête, sans les poids en double.
|
||||||
|
if si.DraftIsModel && strings.TrimSpace(cfg["MODEL_DRAFT"]) != "" {
|
||||||
|
c := make(map[string]string, len(cfg))
|
||||||
|
for k, v := range cfg {
|
||||||
|
c[k] = v
|
||||||
|
}
|
||||||
|
delete(c, "MODEL_DRAFT")
|
||||||
|
args, notes, auto = specArgs(c, extra, si)
|
||||||
|
return args, append([]string{"MODEL_DRAFT est le modèle principal lui-même : ignoré — la tête MTP intégrée " +
|
||||||
|
"sert de brouillon sans recharger les poids (retire MODEL_DRAFT du preset)"}, notes...), auto
|
||||||
|
}
|
||||||
mode := specMode(cfg)
|
mode := specMode(cfg)
|
||||||
draftKey := strings.TrimSpace(cfg["MODEL_DRAFT"])
|
draftKey := strings.TrimSpace(cfg["MODEL_DRAFT"])
|
||||||
switch mode {
|
switch mode {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package loki
|
package loki
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -107,6 +108,13 @@ func TestSpecArgs(t *testing.T) {
|
|||||||
s.Draft = "/models/mtp-Qwen3.6-27B-Q8_0.gguf"
|
s.Draft = "/models/mtp-Qwen3.6-27B-Q8_0.gguf"
|
||||||
s.DraftGGUF = &GGUFInfo{Arch: "qwen35", BlockCount: 65, HasNextNTensor: true}
|
s.DraftGGUF = &GGUFInfo{Arch: "qwen35", BlockCount: 65, HasNextNTensor: true}
|
||||||
}},
|
}},
|
||||||
|
{name: "MODEL_DRAFT = le modèle lui-même : pas de -md, la tête intégrée, et on le dit",
|
||||||
|
cfg: with(forced, "MODEL_DRAFT", "Qwen3.6-27B-MTP.gguf"), notes: 2, want: mtp,
|
||||||
|
si: func(s *serveSysInfo) {
|
||||||
|
s.Draft = s.Model
|
||||||
|
s.DraftGGUF = s.GGUF
|
||||||
|
s.DraftIsModel = true
|
||||||
|
}},
|
||||||
{name: "MODEL_DRAFT petit modèle : -md + draft-simple, sinon chargé pour rien",
|
{name: "MODEL_DRAFT petit modèle : -md + draft-simple, sinon chargé pour rien",
|
||||||
cfg: with(forced, "MODEL_DRAFT", "Qwen3-0.6B.gguf"), notes: 1,
|
cfg: with(forced, "MODEL_DRAFT", "Qwen3-0.6B.gguf"), notes: 1,
|
||||||
want: []string{"-md", "/models/Qwen3-0.6B.gguf", "--spec-type", "draft-simple", "--spec-draft-sampling", "greedy"},
|
want: []string{"-md", "/models/Qwen3-0.6B.gguf", "--spec-type", "draft-simple", "--spec-draft-sampling", "greedy"},
|
||||||
@@ -493,3 +501,26 @@ func TestSynthAcceptanceWarned(t *testing.T) {
|
|||||||
t.Errorf("variable : %q", n)
|
t.Errorf("variable : %q", n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// probeSpec reconnaît un MODEL_DRAFT qui désigne le modèle principal.
|
||||||
|
func TestProbeSpecDraftIsModel(t *testing.T) {
|
||||||
|
t.Setenv("LOKI_HOME", t.TempDir())
|
||||||
|
dir := t.TempDir()
|
||||||
|
model := dir + "/Swift-27B.gguf"
|
||||||
|
other := dir + "/mtp-Swift.gguf"
|
||||||
|
for _, p := range []string{model, other} {
|
||||||
|
if err := os.WriteFile(p, []byte("GGUF"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
si := serveSysInfo{Model: model}
|
||||||
|
probeSpec(map[string]string{"MODEL_DRAFT": model}, &si)
|
||||||
|
if !si.DraftIsModel {
|
||||||
|
t.Errorf("MODEL_DRAFT = le modèle, non reconnu (Draft=%q, err=%q)", si.Draft, si.DraftErr)
|
||||||
|
}
|
||||||
|
si = serveSysInfo{Model: model}
|
||||||
|
probeSpec(map[string]string{"MODEL_DRAFT": other}, &si)
|
||||||
|
if si.DraftIsModel || si.Draft != other {
|
||||||
|
t.Errorf("tête à part prise pour le modèle : %+v", si)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6635,7 +6635,10 @@ async function populateDraft(){
|
|||||||
if(!sel) return;
|
if(!sel) return;
|
||||||
const list = await jget('/api/models');
|
const list = await jget('/api/models');
|
||||||
const cur = readEnvKey(document.getElementById('m-content').value, 'MODEL_DRAFT');
|
const cur = readEnvKey(document.getElementById('m-content').value, 'MODEL_DRAFT');
|
||||||
const items = (list||[]).filter(m => !isMmprojName(m.name));
|
// Le modèle principal n'est pas un brouillon : le choisir rechargeait tous
|
||||||
|
// ses poids une seconde fois (-md), quand sa tête MTP s'utilise sans.
|
||||||
|
const main = matchModel(currentModelInTextarea(), list);
|
||||||
|
const items = (list||[]).filter(m => !isMmprojName(m.name) && m !== main);
|
||||||
const hit = matchModel(cur, items);
|
const hit = matchModel(cur, items);
|
||||||
let html = '<option value="">— aucun —</option>';
|
let html = '<option value="">— aucun —</option>';
|
||||||
let matched = false;
|
let matched = false;
|
||||||
@@ -6645,7 +6648,9 @@ async function populateDraft(){
|
|||||||
html += '<option value="'+escHtml(m.value)+'"'+on+'>'+escHtml(m.name)+' ('+fmtSize(m.size)+')</option>';
|
html += '<option value="'+escHtml(m.value)+'"'+on+'>'+escHtml(m.name)+' ('+fmtSize(m.size)+')</option>';
|
||||||
}
|
}
|
||||||
if(cur && !matched){
|
if(cur && !matched){
|
||||||
html += '<option value="'+escHtml(cur)+'" selected>'+escHtml(baseName(cur)||cur)+' (introuvable)</option>';
|
const self = main && matchModel(cur, [main]);
|
||||||
|
html += '<option value="'+escHtml(cur)+'" selected>'+escHtml(baseName(cur)||cur)+
|
||||||
|
(self ? ' (le modèle lui-même : ignoré, choisis « aucun »)' : ' (introuvable)')+'</option>';
|
||||||
}
|
}
|
||||||
sel.innerHTML = html;
|
sel.innerHTML = html;
|
||||||
syncSpecRow();
|
syncSpecRow();
|
||||||
|
|||||||
@@ -1383,7 +1383,10 @@ async function populateDraft(){
|
|||||||
if(!sel) return;
|
if(!sel) return;
|
||||||
const list = await jget('/api/models');
|
const list = await jget('/api/models');
|
||||||
const cur = readEnvKey(document.getElementById('m-content').value, 'MODEL_DRAFT');
|
const cur = readEnvKey(document.getElementById('m-content').value, 'MODEL_DRAFT');
|
||||||
const items = (list||[]).filter(m => !isMmprojName(m.name));
|
// Le modèle principal n'est pas un brouillon : le choisir rechargeait tous
|
||||||
|
// ses poids une seconde fois (-md), quand sa tête MTP s'utilise sans.
|
||||||
|
const main = matchModel(currentModelInTextarea(), list);
|
||||||
|
const items = (list||[]).filter(m => !isMmprojName(m.name) && m !== main);
|
||||||
const hit = matchModel(cur, items);
|
const hit = matchModel(cur, items);
|
||||||
let html = '<option value="">— aucun —</option>';
|
let html = '<option value="">— aucun —</option>';
|
||||||
let matched = false;
|
let matched = false;
|
||||||
@@ -1393,7 +1396,9 @@ async function populateDraft(){
|
|||||||
html += '<option value="'+escHtml(m.value)+'"'+on+'>'+escHtml(m.name)+' ('+fmtSize(m.size)+')</option>';
|
html += '<option value="'+escHtml(m.value)+'"'+on+'>'+escHtml(m.name)+' ('+fmtSize(m.size)+')</option>';
|
||||||
}
|
}
|
||||||
if(cur && !matched){
|
if(cur && !matched){
|
||||||
html += '<option value="'+escHtml(cur)+'" selected>'+escHtml(baseName(cur)||cur)+' (introuvable)</option>';
|
const self = main && matchModel(cur, [main]);
|
||||||
|
html += '<option value="'+escHtml(cur)+'" selected>'+escHtml(baseName(cur)||cur)+
|
||||||
|
(self ? ' (le modèle lui-même : ignoré, choisis « aucun »)' : ' (introuvable)')+'</option>';
|
||||||
}
|
}
|
||||||
sel.innerHTML = html;
|
sel.innerHTML = html;
|
||||||
syncSpecRow();
|
syncSpecRow();
|
||||||
|
|||||||
Reference in new issue
Block a user