Ctrl K
ComunidadGuías…

internal/autogen/cmdknobs_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package autogen

import (
	"math"
	"strings"
	"testing"
)

// boolptr is a tiny helper for the *bool DRY toggle.
func boolptr(b bool) *bool { return &b }

func joinCmd(s Settings, ov *Override) string {
	s.applyDefaults()
	prof := profile{Name: "t", Ctx: 8192}
	return strings.Join(
		buildCmdLines(s, Metadata{}, GgufRow{FullPath: "/m.gguf"}, prof, 8192, 99, 0, "q8_0", "q8_0", false, ov),
		" ",
	)
}

// DRY defaults off; Dry=true emits the canonical values; custom values replace
// the defaults independently.
func TestBuildCmdLines_dry(t *testing.T) {
	s := Settings{}

	if got := joinCmd(s, &Override{}); strings.Contains(got, "--dry-") {
		t.Fatalf("DRY should be off by default: %s", got)
	}
	if got := joinCmd(s, &Override{Dry: boolptr(true)}); !strings.Contains(got, "--dry-multiplier 0.8 --dry-base 1.75 --dry-allowed-length 3") {
		t.Fatalf("Dry=true should emit canonical DRY flags: %s", got)
	}
	got := joinCmd(s, &Override{Dry: boolptr(true), DryMultiplier: 0.5, DryBase: 2, DryAllowedLength: 4})
	if !strings.Contains(got, "--dry-multiplier 0.5 --dry-base 2 --dry-allowed-length 4") {
		t.Fatalf("custom DRY values not emitted: %s", got)
	}
}

func f64ptr(f float64) *float64 { return &f }
func intptr(i int) *int         { return &i }

// joinCmdMeta is joinCmd with the gguf metadata under test (the sampler baseline
// is arch-derived, so it can't use the zero Metadata).
func joinCmdMeta(s Settings, meta Metadata, ov *Override) string {
	s.applyDefaults()
	prof := profile{Name: "t", Ctx: 8192}
	return strings.Join(
		buildCmdLines(s, meta, GgufRow{FullPath: "/m.gguf"}, prof, 8192, 99, 0, "q8_0", "q8_0", false, ov),
		" ",
	)
}

// No sampler flags for an unknown arch; the Qwen3 family gets the top-k/min-p
// baseline its model cards specify (neither is reachable through the OpenAI API,
// so the launch flag is the only thing that can set them).
func TestBuildCmdLines_samplerBaseline(t *testing.T) {
	s := Settings{}

	for _, flag := range []string{"--temp", "--top-k", "--top-p", "--min-p", "--presence-penalty"} {
		if got := joinCmdMeta(s, Metadata{Architecture: "llama"}, &Override{}); strings.Contains(got, flag) {
			t.Fatalf("unknown arch should emit no sampler flags, got %s in: %s", flag, got)
		}
	}
	for _, arch := range []string{"qwen3", "qwen3moe", "qwen35", "qwen35moe"} {
		got := joinCmdMeta(s, Metadata{Architecture: arch}, &Override{})
		if !strings.Contains(got, "--top-k 20") || !strings.Contains(got, "--min-p 0") {
			t.Fatalf("%s should get the Qwen sampler baseline: %s", arch, got)
		}
		// Mode-dependent values stay unset: one process serves both thinking and
		// instruct traffic, and the client sends these anyway.
		if strings.Contains(got, "--temp") || strings.Contains(got, "--presence-penalty") {
			t.Fatalf("%s should not seed mode-dependent samplers: %s", arch, got)
		}
	}
}

// Muse Glimmer pins top-k 64 (wider than llama's 40) but documents no min-p, so
// the baseline must emit the one and leave the other to llama rather than
// inventing a pairing. Prefix-matched, like the Qwen family.
func TestBuildCmdLines_samplerMuseGlimmer(t *testing.T) {
	s := Settings{}

	for _, arch := range []string{"muse-glimmer", "muse-glimmermoe"} {
		got := joinCmdMeta(s, Metadata{Architecture: arch}, &Override{})
		if !strings.Contains(got, "--top-k 64") {
			t.Fatalf("%s should get top-k 64: %s", arch, got)
		}
		if strings.Contains(got, "--min-p") {
			t.Fatalf("%s documents no min-p, so none should be emitted: %s", arch, got)
		}
		for _, flag := range []string{"--temp", "--top-p", "--presence-penalty"} {
			if strings.Contains(got, flag) {
				t.Fatalf("%s should not seed client-settable %s: %s", arch, flag, got)
			}
		}
	}

	// An override still reaches the min-p the baseline deliberately left unset.
	got := joinCmdMeta(s, Metadata{Architecture: "muse-glimmer"}, &Override{MinP: f64ptr(0)})
	if !strings.Contains(got, "--min-p 0") || !strings.Contains(got, "--top-k 64") {
		t.Fatalf("override min-p should apply alongside the baseline top-k: %s", got)
	}
}

// An override wins over the baseline, and an explicit 0 is a value (greedy temp,
// min-p disabled) rather than "unset" — the whole reason these fields are
// pointers.
func TestBuildCmdLines_samplerOverride(t *testing.T) {
	s := Settings{}

	got := joinCmdMeta(s, Metadata{Architecture: "qwen35"}, &Override{TopK: intptr(64), MinP: f64ptr(0.1)})
	if !strings.Contains(got, "--top-k 64") || !strings.Contains(got, "--min-p 0.1") {
		t.Fatalf("override should beat the arch baseline: %s", got)
	}

	got = joinCmdMeta(s, Metadata{Architecture: "llama"}, &Override{
		Temp: f64ptr(0), TopP: f64ptr(0.8), MinP: f64ptr(0), PresencePenalty: f64ptr(1.5),
	})
	for _, want := range []string{"--temp 0", "--top-p 0.8", "--min-p 0", "--presence-penalty 1.5"} {
		if !strings.Contains(got, want) {
			t.Fatalf("missing %q in: %s", want, got)
		}
	}
	if strings.Contains(got, "--top-k") {
		t.Fatalf("unset top-k should stay unset on a non-Qwen arch: %s", got)
	}
}

// Speculative sub-knobs emit only for the matching backend; draft-n-max defaults
// to 2 and is overridable; ngram-map-k4v knobs emit when set.
func TestBuildCmdLines_specKnobs(t *testing.T) {
	s := Settings{}

	got := joinCmd(s, &Override{Spec: "draft-mtp"})
	if !strings.Contains(got, "--spec-draft-n-max 2") {
		t.Fatalf("draft-mtp default n-max missing: %s", got)
	}
	got = joinCmd(s, &Override{Spec: "draft-mtp", SpecDraftNMax: 5})
	if !strings.Contains(got, "--spec-draft-n-max 5") {
		t.Fatalf("draft-n-max override not applied: %s", got)
	}

	// draft-dflash defaults to a longer draft chain (5, vs mtp's 2) since a
	// diffusion block proposes many tokens per pass; 5 is the measured optimum
	// on Qwen3.6-35B-A3B (own n-max sweep; higher over-drafts and slows TG).
	got = joinCmd(s, &Override{Spec: "draft-dflash"})
	if !strings.Contains(got, "--spec-draft-n-max 5") {
		t.Fatalf("draft-dflash default n-max missing: %s", got)
	}
	got = joinCmd(s, &Override{Spec: "draft-dflash", SpecDraftNMax: 8})
	if !strings.Contains(got, "--spec-draft-n-max 8") {
		t.Fatalf("draft-dflash n-max override not applied: %s", got)
	}

	// A paired draft gguf (DraftPath+DraftKind set at discovery) emits -md + -ngld
	// 99 when its kind matches the active backend (draft-dflash <-> "dflash").
	s2 := Settings{}
	s2.applyDefaults()
	prof := profile{Name: "t", Ctx: 8192}
	got = strings.Join(buildCmdLines(s2, Metadata{}, GgufRow{FullPath: "/m.gguf", DraftPath: "/draft.gguf", DraftKind: "dflash"}, prof, 8192, 99, 0, "q8_0", "q8_0", false, &Override{Spec: "draft-dflash"}), " ")
	for _, want := range []string{"-md /draft.gguf", "-ngld 99"} {
		if !strings.Contains(got, want) {
			t.Fatalf("draft-dflash with DraftPath missing %q: %s", want, got)
		}
	}

	got = joinCmd(s, &Override{Spec: "ngram-map-k4v", SpecDefault: true, SpecNgramSizeN: 16, SpecNgramSizeM: 24, SpecNgramMinHits: 1})
	for _, want := range []string{
		"--spec-default",
		"--spec-ngram-map-k4v-size-n 16",
		"--spec-ngram-map-k4v-size-m 24",
		"--spec-ngram-map-k4v-min-hits 1",
	} {
		if !strings.Contains(got, want) {
			t.Fatalf("ngram knob %q missing: %s", want, got)
		}
	}
	// Unset ngram knobs on a non-ngram backend stay absent.
	if got := joinCmd(s, &Override{Spec: "none"}); strings.Contains(got, "--spec-ngram") || strings.Contains(got, "--spec-draft-n-max") {
		t.Fatalf("spec sub-knobs leaked on spec=none: %s", got)
	}
}

// A model carrying BOTH a baked-in MTP nextn layer (IsMTP) and a paired DFlash
// sidecar (both Qwen3.6-27B/35B do) auto-defaults to draft-mtp: dflash wins a
// short flat-prompt bench but craters over a long real session (resident draft
// weights + own full-context KV crowd VRAM), so it is never auto-picked — only
// an explicit `spec: draft-dflash` override selects it. A draft-mtp backend
// must never attach a dflash-kind sidecar as its -md (arch mismatch = broken
// draft).
func TestBuildCmdLines_dflashNotAutoDefault(t *testing.T) {
	meta := Metadata{IsMTP: true}
	// Baked MTP head always wins the auto-default (chained with ngram-mod),
	// dflash sidecar or not.
	if got := effectiveSpec(meta, nil, "dflash"); got != "draft-mtp+ngram-mod" {
		t.Fatalf("effectiveSpec = %q, want draft-mtp+ngram-mod (dflash never auto-defaults)", got)
	}
	if got := effectiveSpec(meta, nil, ""); got != "draft-mtp+ngram-mod" {
		t.Fatalf("effectiveSpec = %q, want draft-mtp+ngram-mod (baked, no sidecar)", got)
	}
	// No baked MTP, dflash sidecar present but not auto-selected → model-less ngram.
	if got := effectiveSpec(Metadata{}, nil, "dflash"); got != "ngram-mod" {
		t.Fatalf("effectiveSpec = %q, want ngram-mod (no baked MTP, dflash not auto-picked)", got)
	}
	// Explicit override still selects dflash.
	if got := effectiveSpec(meta, &Override{Spec: "draft-dflash"}, "dflash"); got != "draft-dflash" {
		t.Fatalf("effectiveSpec = %q, want draft-dflash (explicit override)", got)
	}
	s := Settings{}
	s.applyDefaults()
	prof := profile{Name: "t", Ctx: 8192}
	row := GgufRow{FullPath: "/m.gguf", DraftPath: "/m-DFlash.gguf", DraftKind: "dflash"}
	got := strings.Join(buildCmdLines(s, meta, row, prof, 8192, 99, 0, "q8_0", "q8_0", false, &Override{Spec: "draft-mtp"}), " ")
	if strings.Contains(got, "-md ") {
		t.Fatalf("draft-mtp must not attach a dflash-kind sidecar as -md: %s", got)
	}
}

// No flag appears twice in a draft-dflash cmd line, incl. a paired DraftPath.
func TestBuildCmdLines_dflashNoDoubledFlags(t *testing.T) {
	s := Settings{}
	s.applyDefaults()
	prof := profile{Name: "t", Ctx: 8192}
	row := GgufRow{FullPath: "/m.gguf", DraftPath: "/m-DFlash-Q8_0.gguf", DraftKind: "dflash", DraftSizeGB: 1.2}
	lines := buildCmdLines(s, Metadata{}, row, prof, 8192, 99, 0, "q8_0", "q8_0", false, &Override{Spec: "draft-dflash"})

	seen := map[string]int{}
	for _, l := range lines {
		flag := strings.Fields(l)[0]
		if strings.HasPrefix(flag, "-") {
			seen[flag]++
		}
	}
	for flag, n := range seen {
		if n > 1 {
			t.Fatalf("flag %q appears %d times: %v", flag, n, lines)
		}
	}
}

// The VRAM charge for a draft sidecar must follow the same kind gate the
// emitter uses for -md. Qwen3.8-27B pairs a ~1 GB DFlash drafter to a model
// that auto-defaults to its baked-in MTP head: the drafter is never attached,
// so charging its weights reserved ~0.8 GB of VRAM nothing loads into.
func TestDraftOverheadGB_kindGate(t *testing.T) {
	const dflashGB = 1.06
	mtpSpec := "draft-mtp+ngram-mod"

	// DFlash sidecar in the dir, running on the baked-in MTP head -> the baked-in
	// compute pad only (its KV is ctx-scaled by mtpDraftSlopeFor, not charged here).
	if got := draftOverheadGB(mtpSpec, matchedDraftSizeGB(mtpSpec, "dflash", dflashGB)); got != mtpDraftPadGB {
		t.Fatalf("draft-mtp with a dflash sidecar charged %.2f GB, want the baked-in pad %.2f", got, mtpDraftPadGB)
	}
	// Explicitly selected: the drafter really loads, charge its weights + pad.
	if got := draftOverheadGB("draft-dflash", matchedDraftSizeGB("draft-dflash", "dflash", dflashGB)); math.Abs(got-(dflashGB+0.1)) > 1e-9 {
		t.Fatalf("draft-dflash charged %.2f GB, want %.2f", got, dflashGB+0.1)
	}
	// Mirror case: an mtp sidecar must not be charged against a dflash spec.
	if got := draftOverheadGB("draft-dflash", matchedDraftSizeGB("draft-dflash", "mtp", 0.46)); got != 0 {
		t.Fatalf("draft-dflash with an mtp sidecar charged %.2f GB, want 0", got)
	}
	// A matched mtp sidecar (Gemma-4) still charges its real weights.
	if got := draftOverheadGB(mtpSpec, matchedDraftSizeGB(mtpSpec, "mtp", 0.46)); math.Abs(got-0.56) > 1e-9 {
		t.Fatalf("draft-mtp with an mtp sidecar charged %.2f GB, want %.2f", got, 0.46+0.1)
	}
	// No draft spec at all charges nothing, sidecar or not.
	if got := draftOverheadGB("ngram-mod", matchedDraftSizeGB("ngram-mod", "dflash", dflashGB)); got != 0 {
		t.Fatalf("ngram-mod charged %.2f GB, want 0", got)
	}
}

// The sizing charge and the emitted cmd must agree: whenever no -md is emitted,
// no sidecar weights may be charged.
func TestBuildCmdLines_draftChargeMatchesMd(t *testing.T) {
	s := Settings{ServerExe: "llama-server.exe"}
	prof := profile{Name: "test", Target: 24, Ctx: 8192}
	row := GgufRow{FullPath: "/m.gguf", DraftPath: "/m-DFlash-Q4_K_M.gguf", DraftKind: "dflash", DraftSizeGB: 1.06}
	meta := Metadata{IsMTP: true} // baked-in head -> auto spec is draft-mtp

	spec := effectiveSpec(meta, nil, row.DraftKind)
	got := strings.Join(buildCmdLines(s, meta, row, prof, 8192, 99, 0, "q8_0", "q8_0", false, nil), " ")
	hasMd := strings.Contains(got, "-md ")
	charged := matchedDraftSizeGB(spec, row.DraftKind, row.DraftSizeGB) > 0
	if hasMd != charged {
		t.Fatalf("-md emitted = %v but sidecar weights charged = %v (spec %q): %s", hasMd, charged, spec, got)
	}
}