Repository: ollama/ollama
Pull request: fix: skip removing layers that no longer exist

some models, such as `wizardcoder:34b-python`, incorrectly includes the config layer as an item in layers. this causes `RemoveLayers` to try to remove the same layer more than once, failing the second time since it's already removed

```json
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "digest": "sha256:a168bedb9a09640289c5174690a6221adae48b75dc431a219923f052ef20d0af",
    "size": 456
  },
  "layers": [
    {
      "mediaType": "application/vnd.ollama.image.model",
      "digest": "sha256:c8ad30822293b3c24f02265a42e2879b9725bd19a54d048fbe2c38487ee0ea84",
      "size": 19052059872
    },
    {
      "mediaType": "application/vnd.ollama.image.template",
      "digest": "sha256:2d836d77287d85ac3d2ea87f4d765db6aaabc98543442072111b3d9831cdf9f1",
      "size": 61
    },
    {
      "mediaType": "application/vnd.ollama.image.system",
      "digest": "sha256:8fadb9ad1206f2f130b54004893a2a7f76b1ff41a78049d69d797df2ee67fe94",
      "size": 106
    },
    {
      "mediaType": "application/vnd.ollama.image.params",
      "digest": "sha256:bf6237d30a42b25b196a7a178dc566e113cf2f193aa11e7302c6d61880be6028",
      "size": 30
    },
    {
      "mediaType": "application/vnd.docker.container.image.v1+json",
      "digest": "sha256:a168bedb9a09640289c5174690a6221adae48b75dc431a219923f052ef20d0af",
      "size": 456
    }
  ]
}
```

resolves #4898 

Changed files:
filename: server/manifest.go
patch: @@ -3,6 +3,7 @@ package server
 import (
 	"crypto/sha256"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"log/slog"
@@ -43,7 +44,9 @@ func (m *Manifest) Remove() error {
 
 func (m *Manifest) RemoveLayers() error {
 	for _, layer := range append(m.Layers, m.Config) {
-		if err := layer.Remove(); err != nil {
+		if err := layer.Remove(); errors.Is(err, os.ErrNotExist) {
+			slog.Debug("layer does not exist", "digest", layer.Digest)
+		} else if err != nil {
 			return err
 		}
 	}

filename: server/routes_delete_test.go
patch: @@ -1,12 +1,15 @@
 package server
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"path/filepath"
 	"testing"
 
 	"github.com/ollama/ollama/api"
+	"github.com/ollama/ollama/types/model"
 )
 
 func TestDelete(t *testing.T) {
@@ -69,3 +72,33 @@ func TestDelete(t *testing.T) {
 	checkFileExists(t, filepath.Join(p, "manifests", "*", "*", "*", "*"), []string{})
 	checkFileExists(t, filepath.Join(p, "blobs", "*"), []string{})
 }
+
+func TestDeleteDuplicateLayers(t *testing.T) {
+	p := t.TempDir()
+	t.Setenv("OLLAMA_MODELS", p)
+	var s Server
+
+	n := model.ParseName("test")
+
+	var b bytes.Buffer
+	if err := json.NewEncoder(&b).Encode(&ConfigV2{}); err != nil {
+		t.Fatal(err)
+	}
+
+	config, err := NewLayer(&b, "application/vnd.docker.container.image.v1+json")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// create a manifest with duplicate layers
+	if err := WriteManifest(n, config, []*Layer{config}); err != nil {
+		t.Fatal(err)
+	}
+
+	w := createRequest(t, s.DeleteModelHandler, api.DeleteRequest{Name: "test"})
+	if w.Code != http.StatusOK {
+		t.Errorf("expected status code 200, actual %d", w.Code)
+	}
+
+	checkFileExists(t, filepath.Join(p, "manifests", "*", "*", "*", "*"), []string{})
+}