mirror of
https://github.com/ollama/ollama.git
synced 2026-10-21 19:11:17 +00:00
Safetensors gemma4 imports served by the MLX engine now answer image and audio chats. Images run through both vision architectures: the transformer tower (26B, 31B, e-series) and the 12B's encoder-free unified embedder. Audio arrives through the same intake the ollama API already accepts for gemma4 GGUFs — WAV bytes in the images field, OpenAI input_audio parts, and /v1/audio/transcriptions uploads — with the e2b/e4b checkpoints running clips through their conformer audio encoder and the 12b unified checkpoint embedding the raw waveform directly. Clips longer than 30 seconds are split evenly into chunks of at most 30 seconds, cut at pauses, and encoded independently. Each modality serves only checkpoints that carry it: 26B/31B have no audio config and reject audio input, and checkpoints with an unrecognized vision architecture still load as text-only models and reject image requests. The server previously hid the vision and audio capabilities for gemma4 safetensors because the engine served neither. Both suppressions are removed, and existing imports start advertising the capabilities without re-importing since import already records them.
101 lines
2.2 KiB
Go
101 lines
2.2 KiB
Go
package server
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/ollama/ollama/format"
|
|
)
|
|
|
|
const (
|
|
gemma4RendererLegacy = "gemma4"
|
|
gemma4RendererSmall = "gemma4-small"
|
|
gemma4RendererLarge = "gemma4-large"
|
|
|
|
// Gemma 4 small templates cover the e2b/e4b family, while 12b/26b/31b use
|
|
// the large template. Default to the small prompt unless the model is
|
|
// clearly in the large range.
|
|
gemma4LargeMinParameterCount = 12_000_000_000
|
|
)
|
|
|
|
func resolveRendererName(m *Model) string {
|
|
if m == nil || m.Config.Renderer == "" {
|
|
return ""
|
|
}
|
|
|
|
switch m.Config.Renderer {
|
|
case gemma4RendererLegacy:
|
|
return resolveGemma4Renderer(m)
|
|
default:
|
|
return m.Config.Renderer
|
|
}
|
|
}
|
|
|
|
func resolveGemma4Renderer(m *Model) string {
|
|
if m == nil || m.Config.Renderer != gemma4RendererLegacy {
|
|
if m == nil {
|
|
return gemma4RendererLegacy
|
|
}
|
|
return m.Config.Renderer
|
|
}
|
|
|
|
if renderer, ok := gemma4RendererFromName(m.ShortName); ok {
|
|
return renderer
|
|
}
|
|
|
|
if renderer, ok := gemma4RendererFromName(m.Name); ok {
|
|
return renderer
|
|
}
|
|
|
|
if parameterCount, ok := parseHumanParameterCount(m.Config.ModelType); ok {
|
|
return gemma4RendererForParameterCount(parameterCount)
|
|
}
|
|
|
|
return gemma4RendererSmall
|
|
}
|
|
|
|
func gemma4RendererForParameterCount(parameterCount uint64) string {
|
|
if parameterCount >= gemma4LargeMinParameterCount {
|
|
return gemma4RendererLarge
|
|
}
|
|
|
|
return gemma4RendererSmall
|
|
}
|
|
|
|
func gemma4RendererFromName(name string) (string, bool) {
|
|
lower := strings.ToLower(name)
|
|
switch {
|
|
case strings.Contains(lower, "e2b"), strings.Contains(lower, "e4b"):
|
|
return gemma4RendererSmall, true
|
|
case strings.Contains(lower, "12b"), strings.Contains(lower, "26b"), strings.Contains(lower, "31b"):
|
|
return gemma4RendererLarge, true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
func parseHumanParameterCount(s string) (uint64, bool) {
|
|
if s == "" {
|
|
return 0, false
|
|
}
|
|
|
|
unit := strings.ToUpper(s[len(s)-1:])
|
|
var multiplier float64
|
|
switch unit {
|
|
case "B":
|
|
multiplier = float64(format.Billion)
|
|
case "M":
|
|
multiplier = float64(format.Million)
|
|
case "K":
|
|
multiplier = float64(format.Thousand)
|
|
default:
|
|
return 0, false
|
|
}
|
|
|
|
value, err := strconv.ParseFloat(s[:len(s)-1], 64)
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
|
|
return uint64(value * multiplier), true
|
|
}
|