internal/autogen/rope_test.go
Rama main · réplica de lectura del repositorio original.
package autogen
import (
"strings"
"testing"
)
// The trained length is the ceiling unless rope scaling is explicitly on: a bare
// ctx override past context_length must still clamp, since a longer window over
// untrained positions is garbage, not more context.
func TestRopeCeiling(t *testing.T) {
meta := Metadata{ContextLength: 131072}
cases := []struct {
scaling string
ctx int
want int
}{
{"", 262144, 131072}, // no scaling -> clamp
{"none", 262144, 131072}, // "none" disables scaling, grants nothing
{"yarn", 262144, 262144}, // opted in -> ceiling follows the request
{"linear", 262144, 262144}, // linear too
{"yarn", 65536, 131072}, // asking for less never lowers the ceiling
{"yarn", 1 << 24, 131072 * 8}, // bounded by maxRopeFactor
}
for _, c := range cases {
if got := ropeCeiling(meta, c.scaling, c.ctx); got != c.want {
t.Errorf("ropeCeiling(%q, %d) = %d, want %d", c.scaling, c.ctx, got, c.want)
}
}
// No declared context_length falls back to the sizer's 32k assumption.
if got := ropeCeiling(Metadata{}, "", 0); got != 32768 {
t.Errorf("ropeCeiling(no ctxlen) = %d, want 32768", got)
}
}
// The factor rounds UP, or the tail of the window lands on untrained positions.
func TestRopeFactor(t *testing.T) {
meta := Metadata{ContextLength: 131072}
cases := []struct {
ctx int
want float64
}{
{131072, 0}, // at native: no scaling needed
{65536, 0}, // below native
{262144, 2}, // exactly 2x
{163840, 1.5}, // 1.25x -> next half step
{140000, 1.5},
}
for _, c := range cases {
if got := ropeFactor(meta, c.ctx); got != c.want {
t.Errorf("ropeFactor(%d) = %v, want %v", c.ctx, got, c.want)
}
}
}
// A scaling type with no explicit ropeScale must emit a DERIVED --rope-scale:
// llama.cpp otherwise takes the factor from gguf metadata (1.0 on a model never
// fine-tuned for extension) and the extra ctx does nothing.
func TestBuildCmdLines_ropeScaleDerived(t *testing.T) {
s := Settings{}
meta := Metadata{ContextLength: 32768}
row := GgufRow{FullPath: "/m.gguf"}
prof := profile{Name: "solo"}
got := strings.Join(buildCmdLines(s, meta, row, prof, 65536, 99, 0, "f16", "f16", false,
&Override{RopeScaling: "yarn", Ctx: 65536}), " ")
if !strings.Contains(got, "--rope-scaling yarn") || !strings.Contains(got, "--rope-scale 2") {
t.Fatalf("derived rope scale missing: %s", got)
}
// An explicit factor wins and is never doubled up.
got = strings.Join(buildCmdLines(s, meta, row, prof, 65536, 99, 0, "f16", "f16", false,
&Override{RopeScaling: "yarn", Ctx: 65536, RopeScale: 3}), " ")
if !strings.Contains(got, "--rope-scale 3") || strings.Count(got, "--rope-scale") != 1 {
t.Fatalf("explicit rope scale not respected: %s", got)
}
// At/below the trained length there is nothing to scale, so no flag.
got = strings.Join(buildCmdLines(s, meta, row, prof, 32768, 99, 0, "f16", "f16", false,
&Override{RopeScaling: "yarn", Ctx: 32768}), " ")
if strings.Contains(got, "--rope-scale ") {
t.Fatalf("rope scale emitted at native ctx: %s", got)
}
}
// EstimatePlan must honour the lifted ceiling too, or the cogwheel's live
// preview reports the KV reserve of a window the launch won't actually use.
func TestEstimatePlan_ropeExtendsCtx(t *testing.T) {
s := Settings{TargetVramGB: 24, VramOverheadGB: 1}
meta := Metadata{
Architecture: "qwen3", ContextLength: 32768, BlockCount: 32,
EmbeddingLength: 4096, HeadCount: 32, HeadCountKv: 8,
KeyLength: 128, ValueLength: 128, FileSizeGB: 4,
}
clamped, err := EstimatePlan(s, meta, EstimateInput{Ctx: 65536})
if err != nil {
t.Fatalf("estimate (clamped): %v", err)
}
if clamped.Ctx > 32768 {
t.Fatalf("ctx %d exceeded trained length without rope scaling", clamped.Ctx)
}
extended, err := EstimatePlan(s, meta, EstimateInput{Ctx: 65536, RopeScaling: "yarn"})
if err != nil {
t.Fatalf("estimate (extended): %v", err)
}
if extended.Ctx != 65536 {
t.Fatalf("rope-extended ctx = %d, want 65536", extended.Ctx)
}
if extended.EstVramGB <= clamped.EstVramGB {
t.Fatalf("extended ctx must cost more VRAM: %.2f vs %.2f", extended.EstVramGB, clamped.EstVramGB)
}
}