Ctrl K
ComunidadGuías…

internal/autogen/checkpoint_test.go

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

Archivo original ↗Preguntar a KiBot
package autogen

import (
	"math"
	"testing"
)

// Recurrent/hybrid models (FullAttnInterval>0) get 2 (upstream #21831 restore
// fixed, verified on qwen3.6-27b); SWA (kvConst>0, non-recurrent) and plain
// attention both get 3. SWA buys its rewind coverage back with a wider spacing
// instead of more (expensive) snapshots.
func TestDefaultCtxCheckpoints_Recurrent(t *testing.T) {
	if n := defaultCtxCheckpoints(0.05, true); n != 2 {
		t.Errorf("recurrent checkpoints = %d, want 2", n)
	}
	if n := defaultCtxCheckpoints(0.05, false); n != 3 {
		t.Errorf("SWA default got %d want 3", n)
	}
	if n := defaultCtxCheckpoints(0, false); n != 3 {
		t.Errorf("plain default got %d want 3", n)
	}
}

// Spacing is arch-keyed: SWA widens to swaCheckpointMinStep so 3 snapshots still
// cover a broad rewind range; recurrent and plain attention get 256. Neither is
// llama's own default (8192, LlamaDefaultCheckpointMinStep) — both are always
// emitted so a launch runs at the spacing checkpointReserveGB charged for. An
// explicit pin always wins.
func TestCheckpointMinStep(t *testing.T) {
	if s := defaultCheckpointMinStep(0.05, false); s != swaCheckpointMinStep {
		t.Errorf("SWA step = %d, want %d", s, swaCheckpointMinStep)
	}
	if s := defaultCheckpointMinStep(0.05, true); s != checkpointMinStep {
		t.Errorf("recurrent step = %d, want %d", s, checkpointMinStep)
	}
	if s := defaultCheckpointMinStep(0, false); s != checkpointMinStep {
		t.Errorf("plain step = %d, want %d", s, checkpointMinStep)
	}
	if s := effectiveCheckpointMinStep(profile{CheckpointMinStep: 512}, 0.05, false); s != 512 {
		t.Errorf("pinned step = %d, want 512", s)
	}
}

// checkpointReserveGB scales with the resolved checkpoint count: nil => the
// llama default (32), an explicit value is used verbatim, 0 disables, and a
// model with no VRAM-resident KV reserves nothing.
func TestCheckpointReserveGB(t *testing.T) {
	// kcg>0 with recurrent=false is SWA, so the charged spacing is the wide one.
	const ptg, kcg = 0.00002, 0.05
	per := kcg + ptg*float64(swaCheckpointMinStep)

	// Large ctx ceiling: the count cap never binds, so the resolved count is used.
	const bigCtx = 1 << 20

	zero := 0
	if g := checkpointReserveGB(profile{CtxCheckpoints: &zero}, ptg, kcg, bigCtx, false); g != 0 {
		t.Errorf("disabled reserve=%v want 0", g)
	}
	// nil count => the arch-aware default; kcg>0 here (SWA) => 3.
	def := defaultCtxCheckpoints(kcg, false)
	if g := checkpointReserveGB(profile{}, ptg, kcg, bigCtx, false); math.Abs(g-float64(def)*per) > 1e-9 {
		t.Errorf("nil reserve=%v want %v (default %d checkpoints)", g, float64(def)*per, def)
	}
	eight := 8
	if g := checkpointReserveGB(profile{CtxCheckpoints: &eight}, ptg, kcg, bigCtx, false); math.Abs(g-8*per) > 1e-9 {
		t.Errorf("n=8 reserve=%v want %v", g, 8*per)
	}
	if g := checkpointReserveGB(profile{}, 0, 0, bigCtx, false); g != 0 {
		t.Errorf("no-kv reserve=%v want 0", g)
	}

	// Small ctx caps the count: a 4k ctx at the SWA min-step holds at most
	// 4096/step checkpoints, so an explicit 32 is clamped to that.
	n32 := 32
	wantN := 4096 / swaCheckpointMinStep
	if g := checkpointReserveGB(profile{CtxCheckpoints: &n32}, ptg, kcg, 4096, false); math.Abs(g-float64(wantN)*per) > 1e-9 {
		t.Errorf("ctx-capped reserve=%v want %v (%d checkpoints)", g, float64(wantN)*per, wantN)
	}
	// Ctx smaller than one min-step holds no checkpoints.
	if g := checkpointReserveGB(profile{CtxCheckpoints: &n32}, ptg, kcg, 100, false); g != 0 {
		t.Errorf("sub-step reserve=%v want 0", g)
	}
}

// sizeProfile must subtract the checkpoint VRAM from the budget: with the
// default 32 checkpoints enabled, a KV-limited dense model gets a smaller ctx
// than the same model with checkpoints disabled.
func TestSizeProfile_CheckpointsShrinkCtx(t *testing.T) {
	meta := Metadata{Architecture: "llama", BlockCount: 32, HeadCountKv: 8, KeyLength: 128, ValueLength: 128, FileSizeGB: 4}
	s := Settings{MaxRamGB: 32, DenseCtxLadder: []int{131072, 65536, 32768}, DenseMinCtx: 4096}
	const ptg, kcg = 0.00002, 0.0

	zero := 0
	many := 32
	off := profile{Name: "x", Target: 5, CtxCheckpoints: &zero}
	on := profile{Name: "x", Target: 5, CtxCheckpoints: &many}

	ctxOff, _, _, _, err := sizeProfile(meta, s, off, ptg, kcg, 131072, false)
	if err != nil {
		t.Fatal(err)
	}
	ctxOn, _, _, _, err := sizeProfile(meta, s, on, ptg, kcg, 131072, false)
	if err != nil {
		t.Fatal(err)
	}
	if !(ctxOn < ctxOff) {
		t.Errorf("ctx with checkpoints=%d should be < without=%d", ctxOn, ctxOff)
	}
}

// On a dense model that can't fit whole in VRAM, CPU-offloaded layers keep their
// KV (and checkpoint snapshots) in RAM. sizeProfile must fold the checkpoint
// reserve into the per-layer KV reserve so densePlacement splits it across the
// GPU/CPU layers, instead of charging it whole to VRAM up front (the old
// behaviour drove -ngl toward 0). The returned kvReserve stays the pure KV cost
// (checkpoints are reported separately), and the placement keeps more layers on
// the GPU than the whole-VRAM reservation would.
func TestSizeProfile_CheckpointsSplitAtPartialOffload(t *testing.T) {
	meta := Metadata{Architecture: "llama", BlockCount: 32, HeadCountKv: 8, KeyLength: 128, ValueLength: 128, FileSizeGB: 20}
	s := Settings{MaxRamGB: 64, DenseCtxLadder: []int{131072, 65536, 32768}, DenseMinCtx: 4096}
	const ptg, kcg = 0.0001, 0.2
	prof := profile{Name: "x", Target: 8, Overhead: 0.5} // tight budget -> partial offload

	ctx, plan, kvReserve, _, err := sizeProfile(meta, s, prof, ptg, kcg, 131072, false)
	if err != nil {
		t.Fatal(err)
	}

	// Reported kvReserve excludes the checkpoint cost (folded only into placement).
	if want := KvReserveGB(ctx, ptg, kcg); math.Abs(kvReserve-want) > 1e-9 {
		t.Errorf("kvReserve=%v want pure KV %v (checkpoints must not bloat the reported reserve)", kvReserve, want)
	}

	// Old behaviour: checkpoint charged whole to VRAM overhead.
	ckpt := checkpointReserveGB(prof, ptg, kcg, min(131072, ctx), false)
	oldPlan, err := GetLoadPlan(meta, planOpt(prof.Target, s.MaxRamGB, kvReserve, prof.Overhead+ckpt))
	if err != nil {
		t.Fatal(err)
	}
	if !(plan.Ngl > oldPlan.Ngl) {
		t.Errorf("split placement ngl=%d should exceed whole-VRAM ngl=%d (ckpt=%.2fGB)", plan.Ngl, oldPlan.Ngl, ckpt)
	}
}