mirror of
https://github.com/ollama/ollama.git
synced 2026-11-12 03:46:14 -06:00
Add manifest-list storage so runner-specific manifests can coexist under one tag while preserving existing v1 tags as best-effort downgrade anchors. Show/list/copy/remove/pull/push now understand runner and digest selection and transfer referenced child manifests and layers. Add lazy local compatibility migration for legacy Ollama GGUFs into llama.cpp-compatible children, covering the patched model families and preserving parser/renderer, templates, projectors, media metadata, split GGUFs, and MTP/draft tensors where applicable. Extend create/import/conversion paths for the same compatibility rules, harden manifest-list combine validation, and add focused manifest, migration, transfer, and integration coverage for first-load conversion plus chat/tools/vision/audio/embedding behavior.
122 lines
4.5 KiB
Go
122 lines
4.5 KiB
Go
package compatmigrate
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
type llama4Migrator struct{}
|
|
|
|
// Mirrors detect_ollama_llama4 in llama/compat/llama-ollama-compat.cpp; keep the two in sync.
|
|
func (llama4Migrator) NeedsMigration(src *SourceModel) bool {
|
|
if src.GGUF.KeyValue("general.architecture").String() != "llama4" {
|
|
return false
|
|
}
|
|
return sourceTensorHasPrefix(src, "v.") || sourceTensorHasPrefix(src, "mm.")
|
|
}
|
|
|
|
func (llama4Migrator) Migrate(src *SourceModel) (*Result, error) {
|
|
tensors, err := readAllSourceTensors(src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var modelTensors []*outTensor
|
|
var projectorTensors []*outTensor
|
|
for _, tensor := range tensors {
|
|
if llama4ProjectorTensor(tensor.name) {
|
|
projectorTensors = append(projectorTensors, copyTensor(llama4ProjectorTensorName(tensor.name), tensor))
|
|
} else {
|
|
modelTensors = append(modelTensors, copyTensor(tensor.name, tensor))
|
|
}
|
|
}
|
|
if len(projectorTensors) == 0 {
|
|
return nil, errUnsupportedFamily
|
|
}
|
|
|
|
modelKV := outKV{}
|
|
for _, keyValue := range src.GGUF.KeyValues() {
|
|
if !keyValue.Valid() {
|
|
continue
|
|
}
|
|
|
|
key := keyValue.Key
|
|
switch {
|
|
case strings.HasPrefix(key, "llama4.vision."):
|
|
continue
|
|
case key == "general.architecture", strings.HasPrefix(key, "llama4."), strings.HasPrefix(key, "general."), strings.HasPrefix(key, "tokenizer."):
|
|
modelKV[key] = normalizeGGUFValue(keyValue.Any())
|
|
}
|
|
}
|
|
|
|
projectorKV := outKV{
|
|
"general.architecture": "clip",
|
|
"clip.has_vision_encoder": true,
|
|
"clip.projector_type": "llama4",
|
|
"clip.vision.projector_type": "llama4",
|
|
"clip.vision.block_count": uint32(src.GGUF.KeyValue("vision.block_count").Uint()),
|
|
"clip.vision.embedding_length": uint32(src.GGUF.KeyValue("vision.embedding_length").Uint()),
|
|
"clip.vision.feed_forward_length": uint32(src.GGUF.KeyValue("vision.feed_forward_length").Uint()),
|
|
"clip.vision.attention.head_count": uint32(src.GGUF.KeyValue("vision.attention.head_count").Uint()),
|
|
"clip.vision.attention.layer_norm_epsilon": float32(src.GGUF.KeyValue("vision.layer_norm_epsilon").Float()),
|
|
"clip.vision.image_size": uint32(src.GGUF.KeyValue("vision.image_size").Uint()),
|
|
"clip.vision.patch_size": uint32(src.GGUF.KeyValue("vision.patch_size").Uint()),
|
|
"clip.vision.projection_dim": uint32(src.GGUF.KeyValue("embedding_length").Uint()),
|
|
"clip.vision.projector.scale_factor": llama4ProjectorScale(src),
|
|
"clip.vision.image_mean": []float32{0.5, 0.5, 0.5},
|
|
"clip.vision.image_std": []float32{0.5, 0.5, 0.5},
|
|
"clip.use_gelu": true,
|
|
}
|
|
|
|
return &Result{
|
|
ModelKV: modelKV,
|
|
ModelTensors: modelTensors,
|
|
ProjectorKV: projectorKV,
|
|
ProjectorTensors: projectorTensors,
|
|
}, nil
|
|
}
|
|
|
|
func llama4ProjectorTensor(name string) bool {
|
|
return strings.HasPrefix(name, "v.") || strings.HasPrefix(name, "mm.")
|
|
}
|
|
|
|
func llama4ProjectorTensorName(name string) string {
|
|
switch {
|
|
case name == "v.class_embedding":
|
|
return "v.class_embd"
|
|
case name == "v.positional_embedding_vlm":
|
|
return "v.position_embd.weight"
|
|
case strings.HasPrefix(name, "v.patch_embedding."):
|
|
return strings.Replace(name, "v.patch_embedding.", "v.patch_embd.", 1)
|
|
case strings.HasPrefix(name, "v.layernorm_pre."):
|
|
return strings.Replace(name, "v.layernorm_pre.", "v.pre_ln.", 1)
|
|
case strings.HasPrefix(name, "v.layernorm_post."):
|
|
return strings.Replace(name, "v.layernorm_post.", "v.post_ln.", 1)
|
|
case strings.HasPrefix(name, "v.vision_adapter.mlp.fc1."):
|
|
return strings.Replace(name, "v.vision_adapter.mlp.fc1.", "mm.model.mlp.1.", 1)
|
|
case strings.HasPrefix(name, "v.vision_adapter.mlp.fc2."):
|
|
return strings.Replace(name, "v.vision_adapter.mlp.fc2.", "mm.model.mlp.2.", 1)
|
|
case strings.HasPrefix(name, "mm.linear_1."):
|
|
return strings.Replace(name, "mm.linear_1.", "mm.model.fc.", 1)
|
|
}
|
|
|
|
name = strings.Replace(name, ".attn_output.", ".attn_out.", 1)
|
|
name = strings.Replace(name, ".attn_norm.", ".ln1.", 1)
|
|
name = strings.Replace(name, ".ffn_norm.", ".ln2.", 1)
|
|
name = strings.Replace(name, ".mlp.fc1.", ".ffn_up.", 1)
|
|
name = strings.Replace(name, ".mlp.fc2.", ".ffn_down.", 1)
|
|
return name
|
|
}
|
|
|
|
func llama4ProjectorScale(src *SourceModel) uint32 {
|
|
scale := uint32(2)
|
|
ratio := float32(src.GGUF.KeyValue("vision.pixel_shuffle_ratio").Float())
|
|
if ratio > 0 {
|
|
scale = uint32(math.Round(float64(1 / ratio)))
|
|
}
|
|
if scale == 0 {
|
|
return 2
|
|
}
|
|
return scale
|
|
}
|