Files
llmgw/internal/control/catalog/profilesource_test.go
T
Ilker ManapandClaude Opus 5.5 63198f2753 A profile may load weights of its own
The catalog had one source a model, so an embedding model served by vLLM
from its repository on a card and by llama.cpp from a GGUF on CPUs had its
entry swapped by hand halfway through the metallurgy import. A profile's
source is what its engine loads and placement looks for; the model's is
the default, and what updates and canaries move. ctl model add checks
every source, the GGUF check reads each llama.cpp profile's, and ctl
weights collects none a profile loads. An empty source leaves a profile's
fingerprint as it was, so running replicas do not read as stale.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 17:15:38 +02:00

91 lines
3.2 KiB
Go

package catalog
import (
"encoding/json"
"slices"
"strings"
"testing"
)
// embedModel is an embedding model the way it was wanted on the PoC: vLLM
// on the card from the repository, llama.cpp on CPUs from a GGUF.
func embedModel() Model {
return Model{Name: "qwen3-embed-4b", Source: "Qwen/Qwen3-Embedding-4B",
Profiles: []Profile{
{Name: "gpu", Engine: "vllm", GPUClass: "6000-24g", TP: 1},
{Name: "cpu", Engine: "llamacpp", Source: "hf:Qwen/Qwen3-Embedding-4B-GGUF/Qwen3-Embedding-4B-Q8_0.gguf"},
}}
}
func TestEachProfileLoadsItsOwnSource(t *testing.T) {
// The catalog had one source a model, so serving an embedding model by
// vLLM on a card and by llama.cpp on CPUs meant swapping the entry by
// hand halfway through an import.
cat, err := New([]Model{embedModel()})
if err != nil {
t.Fatal(err)
}
m, _ := cat.Resolve("qwen3-embed-4b")
gpu, _ := m.EngineProfile("gpu")
cpu, _ := m.EngineProfile("cpu")
if gpu.ModelPath != "Qwen/Qwen3-Embedding-4B" || !strings.HasSuffix(cpu.ModelPath, "Q8_0.gguf") {
t.Errorf("gpu loads %q, cpu %q", gpu.ModelPath, cpu.ModelPath)
}
for _, p := range m.PlacementProfiles() {
if want := map[string]string{"gpu": gpu.ModelPath, "cpu": cpu.ModelPath}[p.Name]; p.Source != want {
t.Errorf("placement for %s looks for %q, want %q", p.Name, p.Source, want)
}
}
if got := m.WeightSources(); len(got) != 2 || !slices.Contains(got, cpu.ModelPath) {
t.Errorf("weight sources %v: a profile's own must not be collected", got)
}
}
func TestAProfileWithoutASourceKeepsItsFingerprint(t *testing.T) {
// Replicas are compared with their profile by fingerprint; a field added
// to every profile must not make every running replica look stale.
p := Profile{Name: "gpu", Engine: "vllm", GPUClass: "6000-24g", TP: 1}
before := p.Fingerprint()
if strings.Contains(mustJSON(t, p), "source") {
t.Error("an empty source is written into the profile")
}
p.Source = "Qwen/Other"
if p.Fingerprint() == before {
t.Error("a profile whose source changed reads as unchanged")
}
}
func TestAProfileSourceIsCheckedAsTheModelsIs(t *testing.T) {
page := embedModel()
page.Profiles[1].Source = "https://huggingface.co/Qwen/Qwen3-Embedding-4B-GGUF"
if _, err := New([]Model{page}); err == nil || !strings.Contains(err.Error(), `profile "cpu"`) {
t.Errorf("a repository page for llama.cpp: %v", err)
}
// The model's own source may be a page vLLM reads as a repository, now
// that the llama.cpp profile loads something else.
repo := embedModel()
repo.Source = "https://huggingface.co/Qwen/Qwen3-Embedding-4B"
cat, err := New([]Model{repo})
if err != nil {
t.Fatalf("a repository page for the vLLM profile beside a llama.cpp one with its own GGUF: %v", err)
}
m, _ := cat.Resolve("qwen3-embed-4b")
if m.Source != "Qwen/Qwen3-Embedding-4B" {
t.Errorf("the page was not read as the repository: %q", m.Source)
}
trt := embedModel()
trt.Profiles[0] = Profile{Name: "gpu", Engine: "trtllm", GPUClass: "6000-24g", TP: 1, Source: "hf:a/b/m.gguf"}
if _, err := New([]Model{trt}); err == nil || !strings.Contains(err.Error(), "not a .gguf") {
t.Errorf("trtllm given a GGUF by its profile: %v", err)
}
}
func mustJSON(t *testing.T, v any) string {
t.Helper()
b, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
return string(b)
}