server: hide duplicate and downgrade guards from list (#18874)

When a legacy GGUF which requires our llama.cpp patch set is loaded, we do a
lazy conversion on disk to make it llama.cpp compatible.  To support
downgrades, we temporarily hold both the new and old GGUFs and create a shadow
v1 manifest tag to prevent the downgraded server from deleting the new blobs.
Showing these in the list output is confusing to users and API consumers.  The
legacy copies will be removed in a future release to recover the disk space.
This commit is contained in:
Daniel Hiltgen 2026-10-08 08:54:06 -07:00 • committed by GitHub
parent e3cddc3e89
commit 531946e996
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 19 additions and 9 deletions

View file

@ -18,7 +18,7 @@ import (
// listModels builds /api/tags from the manifests and the per-blob metadata
// files, extracting for any blob that has none yet. Manifest lists contribute
// one row per child runner.
// one row for the locally preferred child.
func listModels(ctx context.Context) ([]api.ListModelResponse, error) {
manifests, err := manifest.Manifests(true)
if err != nil {
@ -32,6 +32,10 @@ func listModels(ctx context.Context) ([]api.ListModelResponse, error) {
return nil, err
}
}
// Suppress showing the downgrade guard tag
if name.EqualFold(model.ParseName(manifest.RunnerLlamaCPP + ":" + mf.Digest())) {
continue
}
rows, err := describeModelRows(name, mf)
if err != nil {
@ -46,7 +50,7 @@ func listModels(ctx context.Context) ([]api.ListModelResponse, error) {
}
// describeModelRows describes one named manifest for /api/tags. A manifest
// list describes one row per child runner, keyed by the child digest and
// list describes its selected child, keyed by the child digest and
// carrying the parent's modification time; every other manifest describes
// itself.
func describeModelRows(name model.Name, mf *manifest.Manifest) ([]api.ListModelResponse, error) {
@ -79,6 +83,9 @@ func describeModelRows(name model.Name, mf *manifest.Manifest) ([]api.ListModelR
if err != nil {
return nil, err
}
if !manifest.SameDigest(digest, mf.SelectedDigest()) {
continue
}
resolved, ok, err := resolveLocalShowManifestChild(child)
if err != nil {
return nil, err

View file

@ -235,7 +235,7 @@ func writeManifestListFixture(t *testing.T, name string, children ...manifestLis
return manifests
}
func TestListIncludesManifestListChildrenAsSeparateRows(t *testing.T) {
func TestListShowsPreferredManifestListChild(t *testing.T) {
gin.SetMode(gin.TestMode)
t.Setenv("OLLAMA_MODELS", t.TempDir())
@ -281,6 +281,10 @@ func TestListIncludesManifestListChildrenAsSeparateRows(t *testing.T) {
manifestListFixtureChild{name: "test-mlx", runner: manifest.RunnerMLX, format: manifest.FormatSafetensors},
)
ggufManifest, mlxManifest := children[0], children[1]
selected, err := manifest.ParseNamedManifest(model.ParseName("test-list"))
if err != nil {
t.Fatal(err)
}
parentPath, err := manifest.ResolvePathForName(model.ParseName("test-list"))
if err != nil {
t.Fatal(err)
@ -309,8 +313,11 @@ func TestListIncludesManifestListChildrenAsSeparateRows(t *testing.T) {
listed = append(listed, resp.Models[i])
}
}
if len(listed) != 2 {
t.Fatalf("test-list:latest rows = %d, want 2: %+v", len(listed), listed)
if len(listed) != 1 {
t.Fatalf("test-list:latest rows = %d, want 1: %+v", len(listed), listed)
}
if listed[0].Digest != selected.SelectedDigest() {
t.Fatalf("listed digest = %s, want selected digest %s", listed[0].Digest, selected.SelectedDigest())
}
wantSizes := map[string]int64{
@ -335,10 +342,6 @@ func TestListIncludesManifestListChildrenAsSeparateRows(t *testing.T) {
if row.Details.Runner != wantRunners[row.Digest] {
t.Fatalf("runner for %s = %q, want %q", row.Digest, row.Details.Runner, wantRunners[row.Digest])
}
delete(wantSizes, row.Digest)
}
if len(wantSizes) != 0 {
t.Fatalf("missing list rows for digests: %+v", wantSizes)
}
}