mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-24 15:38:12 +02:00
Spéculation : SPEC=ngram et mtp+ngram, n-grammes du contexte en opt-in
En mode Code, le modèle recopie sans cesse ce qui est déjà dans le contexte : chemins, diffs, arguments JSON, fichiers réécrits. ngram-mod de llama.cpp y cherche la suite des 24 derniers jetons et propose d'un coup les 48 à 64 suivants, sans modèle brouillon. La clé SPEC (off par défaut, inchangé) gagne deux valeurs : ngram, et mtp+ngram qui y ajoute la tête MTP. Même vérification exacte que MTP : distribution inchangée, mais pas le même texte au bit près (vérification par lots), donc on compare des sessions rejouées, pas des diffs. - forme explicite seulement : --spec-type ngram-mod --spec-ngram-mod-n-match 24 --spec-ngram-mod-n-min 48 --spec-ngram-mod-n-max 64 ; jamais --spec-default (TODO amont, contenu susceptible de changer avec le moteur) - garde-fou d'aide sur « --spec-ngram-mod-n-match » (pas le simple mot ngram-mod, encore listé par les moteurs d'avant le renommage) ; absent = SPEC ignoré, note au journal - mtp+ngram : --spec-type draft-mtp,ngram-mod seulement avec draft-mtp dans l'aide ET une tête MTP réelle (tenseur nextn ou MODEL_DRAFT) ; sinon n-grammes seuls, avec la raison — jamais « failed to create MTP context » en boucle - EXTRA_ARGS/LLAMA_ARG_* : mêmes exclusions qu'avant, plus --spec_type et tout --spec-ngram-* (--spec-type s'additionne sans prévenir) ; pour ngram seul, un --spec-draft-* fait aussi taire Loki - poids sur CPU (experts MoE, -ot, NGL partiel) : refusé tant que GGML_OP_OFFLOAD_MIN_BATCH (env ou OP_OFFLOAD_MIN_BATCH) ne dépasse pas le lot de vérification de 65 jetons, avec la suggestion OP_OFFLOAD_MIN_BATCH=128 ; avertissements pour un modèle plus gros que la RAM, les points de reprise d'un hybride, NGL imposé (logits) - avec ngram, une tête MTP ou MODEL_DRAFT inutilisés sont signalés - --spec-synth-len/--spec-synth-rates (et LLAMA_ARG_SPEC_SYNTH_LEN) : acceptation au hasard, avertissement au lancement ; Loki ne les pose jamais - télémétrie : /api/perf/summary donne draft_n, draft_accepted et draft_rate par nature de requête - UI : deux options du sélecteur de décodage spéculatif ; README et configTemplate - tests : forme explicite, garde d'aide, repli de mtp+ngram, exclusions, seuil d'offload, ligne inchangée sans SPEC, acceptation par nature Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
28ba44eb46
commit
539e36ea49
11 files changed
+495
-67
No files matched your search
@@ -332,3 +332,164 @@ func TestLastOffload(t *testing.T) {
|
||||
t.Errorf("tout sur GPU : got %d/%d", n, m)
|
||||
}
|
||||
}
|
||||
|
||||
// helpNgram : un moteur qui connaît les drapeaux actuels de ngram-mod.
|
||||
const helpNgram = helpSpec + `--spec-ngram-mod-n-min N minimum number of ngram tokens (default: 48)
|
||||
--spec-ngram-mod-n-max N maximum number of ngram tokens (default: 64)
|
||||
--spec-ngram-mod-n-match N ngram-mod lookup length (default: 24)
|
||||
`
|
||||
|
||||
func TestSpecArgsNgram(t *testing.T) {
|
||||
ngram := []string{"--spec-type", "ngram-mod",
|
||||
"--spec-ngram-mod-n-match", "24", "--spec-ngram-mod-n-min", "48", "--spec-ngram-mod-n-max", "64"}
|
||||
both := []string{"--spec-type", "draft-mtp,ngram-mod",
|
||||
"--spec-ngram-mod-n-match", "24", "--spec-ngram-mod-n-min", "48", "--spec-ngram-mod-n-max", "64",
|
||||
"--spec-draft-sampling", "greedy"}
|
||||
cfgOf := func(kv ...string) map[string]string {
|
||||
m := map[string]string{}
|
||||
for i := 0; i+1 < len(kv); i += 2 {
|
||||
m[kv[i]] = kv[i+1]
|
||||
}
|
||||
return m
|
||||
}
|
||||
dense := func(s *serveSysInfo) { s.GGUF = &GGUFInfo{Arch: "llama", BlockCount: 32} }
|
||||
cases := []struct {
|
||||
name string
|
||||
cfg map[string]string
|
||||
extra []string
|
||||
si func(*serveSysInfo)
|
||||
want []string
|
||||
notes int
|
||||
}{
|
||||
{name: "ngram, modèle dense : forme explicite, jamais --spec-default",
|
||||
cfg: cfgOf("SPEC", "ngram"), si: dense, want: ngram, notes: 1},
|
||||
{name: "ngram+mtp s'écrit aussi dans l'autre sens",
|
||||
cfg: cfgOf("SPEC", "ngram+mtp"), want: both, notes: 2},
|
||||
{name: "ngram sur un modèle hybride à tête MTP : tête inutilisée et points de reprise dits",
|
||||
cfg: cfgOf("SPEC", "ngram"), want: ngram, notes: 3},
|
||||
{name: "ngram, moteur qui n'a que le type dans sa liste : rien",
|
||||
cfg: cfgOf("SPEC", "ngram"), notes: 1, si: func(s *serveSysInfo) { s.Help = helpSpec }},
|
||||
{name: "mtp+ngram, moteur sans drapeaux ngram-mod : rien",
|
||||
cfg: cfgOf("SPEC", "mtp+ngram"), notes: 1, si: func(s *serveSysInfo) { s.Help = helpSpec }},
|
||||
{name: "mtp+ngram avec la tête dans le fichier : une seule liste de types",
|
||||
cfg: cfgOf("SPEC", "mtp+ngram"), want: both, notes: 2},
|
||||
{name: "mtp+ngram sans tête MTP : n-grammes seuls, jamais « failed to create MTP context »",
|
||||
cfg: cfgOf("SPEC", "mtp+ngram"), si: dense, want: ngram, notes: 1},
|
||||
{name: "mtp+ngram, moteur sans draft-mtp : n-grammes seuls",
|
||||
cfg: cfgOf("SPEC", "mtp+ngram"), want: ngram, notes: 2,
|
||||
si: func(s *serveSysInfo) {
|
||||
s.Help = strings.ReplaceAll(s.Help, "draft-mtp", "draft-xxx")
|
||||
}},
|
||||
{name: "mtp+ngram avec un petit modèle brouillon : n-grammes seuls, pas de -md",
|
||||
cfg: cfgOf("SPEC", "mtp+ngram", "MODEL_DRAFT", "Qwen3-0.6B.gguf"), want: ngram, notes: 2,
|
||||
si: func(s *serveSysInfo) {
|
||||
s.Draft = "/models/Qwen3-0.6B.gguf"
|
||||
s.DraftGGUF = &GGUFInfo{Arch: "qwen3", BlockCount: 28}
|
||||
}},
|
||||
{name: "mtp+ngram avec la tête MTP publiée à part",
|
||||
cfg: cfgOf("SPEC", "mtp+ngram", "MODEL_DRAFT", "mtp-q.gguf"), notes: 2,
|
||||
want: append([]string{"-md", "/models/mtp-q.gguf"}, both...),
|
||||
si: func(s *serveSysInfo) {
|
||||
s.GGUF.HasNextNTensor = false
|
||||
s.Draft = "/models/mtp-q.gguf"
|
||||
s.DraftGGUF = &GGUFInfo{Arch: "qwen35", BlockCount: 65, HasNextNTensor: true}
|
||||
}},
|
||||
{name: "ngram + MODEL_DRAFT : brouillon ignoré, et dit",
|
||||
cfg: cfgOf("SPEC", "ngram", "MODEL_DRAFT", "mtp-q.gguf"), si: dense, want: ngram, notes: 2},
|
||||
{name: "ngram + SPEC_N_MAX : ignoré, et dit",
|
||||
cfg: cfgOf("SPEC", "ngram", "SPEC_N_MAX", "4"), si: dense, want: ngram, notes: 2},
|
||||
{name: "--spec-type dans EXTRA_ARGS : Loki se tait",
|
||||
cfg: cfgOf("SPEC", "ngram"), extra: []string{"--spec-type", "ngram-simple"}, notes: 1},
|
||||
{name: "--spec_type=… dans EXTRA_ARGS : Loki se tait",
|
||||
cfg: cfgOf("SPEC", "ngram"), extra: []string{"--spec_type=ngram-mod"}, notes: 1},
|
||||
{name: "--spec-default dans EXTRA_ARGS : Loki se tait",
|
||||
cfg: cfgOf("SPEC", "mtp+ngram"), extra: []string{"--spec-default"}, notes: 1},
|
||||
{name: "--spec-ngram-mod-n-max dans EXTRA_ARGS : Loki se tait",
|
||||
cfg: cfgOf("SPEC", "ngram"), extra: []string{"--spec-ngram-mod-n-max=16"}, notes: 1},
|
||||
{name: "-md dans EXTRA_ARGS : Loki se tait",
|
||||
cfg: cfgOf("SPEC", "mtp+ngram"), extra: []string{"-md", "d.gguf"}, notes: 1},
|
||||
{name: "LLAMA_ARG_SPEC_TYPE posée : Loki se tait", cfg: cfgOf("SPEC", "ngram"), notes: 1,
|
||||
si: func(s *serveSysInfo) { s.ArgEnv["LLAMA_ARG_SPEC_TYPE"] = "ngram-mod" }},
|
||||
{name: "ngram + --spec-draft-n-max à la main : rien",
|
||||
cfg: cfgOf("SPEC", "ngram"), si: dense, extra: []string{"--spec-draft-n-max", "8"}, notes: 1},
|
||||
{name: "mtp+ngram + --spec-draft-n-max à la main : la règle de SPEC=mtp",
|
||||
cfg: cfgOf("SPEC", "mtp+ngram"), extra: []string{"--spec-draft-n-max", "8"}, want: both, notes: 2},
|
||||
{name: "experts sur CPU, seuil par défaut : refusé, OP_OFFLOAD_MIN_BATCH suggéré",
|
||||
cfg: cfgOf("SPEC", "ngram"), si: dense, extra: []string{"--n-cpu-moe", "40"}, notes: 1},
|
||||
{name: "experts sur CPU, mtp+ngram : refusé aussi",
|
||||
cfg: cfgOf("SPEC", "mtp+ngram"), extra: []string{"-ot", "exps=CPU"}, notes: 1},
|
||||
{name: "experts sur CPU, OP_OFFLOAD_MIN_BATCH=64 : encore sous le lot de 65, refusé",
|
||||
cfg: cfgOf("SPEC", "ngram", "OP_OFFLOAD_MIN_BATCH", "64"), si: dense, extra: []string{"-cmoe"}, notes: 1},
|
||||
{name: "experts sur CPU, OP_OFFLOAD_MIN_BATCH=128 : accepté",
|
||||
cfg: cfgOf("SPEC", "ngram", "OP_OFFLOAD_MIN_BATCH", "128"), si: dense, extra: []string{"-cmoe"}, want: ngram, notes: 1},
|
||||
{name: "experts sur CPU, GGML_OP_OFFLOAD_MIN_BATCH=256 dans l'environnement : accepté",
|
||||
cfg: cfgOf("SPEC", "ngram"), extra: []string{"--n-cpu-moe", "40"}, want: ngram, notes: 1,
|
||||
si: func(s *serveSysInfo) {
|
||||
dense(s)
|
||||
s.UserEnv = map[string]string{"GGML_OP_OFFLOAD_MIN_BATCH": "256"}
|
||||
}},
|
||||
{name: "experts sur CPU, modèle plus gros que la RAM : accepté avec l'avertissement",
|
||||
cfg: cfgOf("SPEC", "ngram", "OP_OFFLOAD_MIN_BATCH", "128"), extra: []string{"--n-cpu-moe", "40"},
|
||||
want: ngram, notes: 2,
|
||||
si: func(s *serveSysInfo) {
|
||||
dense(s)
|
||||
s.ModelBytes, s.RAMMiB = 82<<30, 64<<10
|
||||
}},
|
||||
{name: "NGL imposé : logits en VRAM dits",
|
||||
cfg: cfgOf("SPEC", "ngram", "NGL", "40"), si: dense, want: ngram, notes: 2},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
si := mtpReady()
|
||||
si.Help = helpNgram
|
||||
if c.si != nil {
|
||||
c.si(&si)
|
||||
}
|
||||
got, notes, isAuto := specArgs(c.cfg, c.extra, si)
|
||||
if !reflect.DeepEqual(got, c.want) {
|
||||
t.Errorf("args = %q, attendu %q", got, c.want)
|
||||
}
|
||||
if isAuto {
|
||||
t.Error("ngram n'est jamais automatique")
|
||||
}
|
||||
if len(notes) != c.notes {
|
||||
t.Errorf("%d notes, attendu %d : %q", len(notes), c.notes, notes)
|
||||
}
|
||||
for _, a := range got {
|
||||
if a == "--spec-default" || strings.HasPrefix(a, "--spec-synth") {
|
||||
t.Errorf("drapeau interdit %q", a)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sans SPEC, la même ligne qu'avant, quel que soit le moteur.
|
||||
func TestBuildServeArgsNgramDefault(t *testing.T) {
|
||||
si := mtpReady()
|
||||
si.Help = helpNgram
|
||||
base, _, _ := buildServeArgs(map[string]string{}, nil, "/bin/llama-server", si)
|
||||
for _, a := range base {
|
||||
if strings.HasPrefix(a, "--spec") {
|
||||
t.Fatalf("SPEC absent : aucun drapeau de spéculation, %q", base)
|
||||
}
|
||||
}
|
||||
got, _, _ := buildServeArgs(map[string]string{"SPEC": "ngram"}, []string{"--jinja"}, "/bin/llama-server", si)
|
||||
i := slices.Index(got, "--spec-type")
|
||||
if i < 0 || got[i+1] != "ngram-mod" || slices.Index(got, "--jinja") < i {
|
||||
t.Fatalf("SPEC=ngram : %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSynthAcceptanceWarned(t *testing.T) {
|
||||
for _, extra := range [][]string{{"--spec-synth-len", "3"}, {"--spec-synth-rates=0.5,0.5"}} {
|
||||
if n := lossyCacheNotes(extra, serveSysInfo{ArgEnv: map[string]string{}}); len(n) != 1 ||
|
||||
!strings.Contains(n[0], "AU HASARD") {
|
||||
t.Errorf("%q : %q", extra, n)
|
||||
}
|
||||
}
|
||||
si := serveSysInfo{ArgEnv: map[string]string{"LLAMA_ARG_SPEC_SYNTH_LEN": "3"}}
|
||||
if n := lossyCacheNotes(nil, si); len(n) != 1 {
|
||||
t.Errorf("variable : %q", n)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user