server: classify mmproj GGUFs as projector layers (#16472)
This commit is contained in:
@@ -1232,8 +1232,7 @@ func ggufLayersWithMediaType(digest, sourceName, mediaType string, fn func(resp
|
||||
mediaType = "application/vnd.ollama.image.model"
|
||||
if f.KV().Kind() == "adapter" {
|
||||
mediaType = "application/vnd.ollama.image.adapter"
|
||||
} else if (f.KV().Uint("block_count") == 0 && f.KV().Uint("vision.block_count") > 0) || f.KV().Kind() == "projector" {
|
||||
// if a model has vision.block_count but not block_count, it is a standalone vision model
|
||||
} else if isProjectorGGUF(f.KV()) {
|
||||
mediaType = "application/vnd.ollama.image.projector"
|
||||
}
|
||||
}
|
||||
@@ -1249,6 +1248,20 @@ func ggufLayersWithMediaType(digest, sourceName, mediaType string, fn func(resp
|
||||
return layers, nil
|
||||
}
|
||||
|
||||
func isProjectorGGUF(kv ggml.KV) bool {
|
||||
switch kv.Kind() {
|
||||
case "projector", "mmproj":
|
||||
return true
|
||||
}
|
||||
|
||||
// If a model has vision.block_count but not block_count, it is a standalone vision model.
|
||||
if kv.Uint("block_count") == 0 && kv.Uint("vision.block_count") > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return kv.Architecture() == "clip" && kv.Uint("block_count") == 0 && (kv.Bool("has_vision_encoder") || kv.Bool("has_audio_encoder"))
|
||||
}
|
||||
|
||||
func removeLayer(layers []manifest.Layer, mediatype string) []manifest.Layer {
|
||||
return slices.DeleteFunc(layers, func(layer manifest.Layer) bool {
|
||||
if layer.MediaType != mediatype {
|
||||
|
||||
@@ -465,6 +465,44 @@ func TestCreateModelAddsDefaultLlavaProjectorType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGGUFLayersClassifiesMMProjAsProjector(t *testing.T) {
|
||||
t.Setenv("OLLAMA_MODELS", t.TempDir())
|
||||
|
||||
_, digest := createBinFile(t, map[string]any{
|
||||
"general.architecture": "clip",
|
||||
"general.type": "mmproj",
|
||||
"clip.has_vision_encoder": true,
|
||||
"clip.vision.block_count": uint32(0),
|
||||
"clip.vision.projector_type": "gemma4uv",
|
||||
"clip.has_audio_encoder": true,
|
||||
"clip.audio.block_count": uint32(0),
|
||||
"clip.audio.projector_type": "gemma4ua",
|
||||
"clip.vision.projection_dim": uint32(3840),
|
||||
"clip.vision.embedding_length": uint32(3840),
|
||||
"clip.audio.projection_dim": uint32(3840),
|
||||
"clip.audio.embedding_length": uint32(3840),
|
||||
"clip.vision.feed_forward_length": uint32(0),
|
||||
"clip.audio.feed_forward_length": uint32(0),
|
||||
}, []*ggml.Tensor{
|
||||
{
|
||||
Name: "mm.input_projection.weight",
|
||||
Kind: uint32(ggml.TensorTypeF32),
|
||||
Shape: []uint64{1, 1},
|
||||
WriterTo: bytes.NewReader(make([]byte, 4)),
|
||||
},
|
||||
})
|
||||
layers, err := ggufLayers(digest, "mmproj-gemma-4-12B-it-bf16.gguf", func(api.ProgressResponse) {})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(layers) != 1 {
|
||||
t.Fatalf("layers = %d, want 1", len(layers))
|
||||
}
|
||||
if got := layers[0].MediaType; got != "application/vnd.ollama.image.projector" {
|
||||
t.Fatalf("media type = %q, want projector", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateModelQuantizeRestoresEmbeddedCompatibilityTensors(t *testing.T) {
|
||||
t.Setenv("OLLAMA_MODELS", t.TempDir())
|
||||
oldRun := runLlamaQuantize
|
||||
|
||||
Reference in New Issue
Block a user