mirror of
https://github.com/ollama/ollama.git
synced 2026-07-10 01:41:52 +00:00
This is a rewrite of the create functionality for the MLX engine. The core idea behind the create functionality is to break the import/convert into a pipeline of distinct phases: * Read (scan the safetensors directory for the various bits of metadata) * Classify (determine what the import type) * Plan (determine any transforms that need to be done) * Write (transform any data as necessary and write out the blobs) * Create the manifest Each architecture has a "policy" which determines how to convert the model correctly. A number of different formats for safetensors are supported including: * nvfp4 (two formats: model optimized, torch) * fp8 datatypes (convert to mxfp8) * standard bf16 based weights A number of cleanups/simplifications have been done including: * using the baked in names for the tensors instead of munging them into something else * unified 3d expert tensors (instead of separate per expert tensors) * fewer unnecessary transforms to the various tensors in a model (keep a model as close to the source as possible) * unified capability checking * draft model handling (for MTP) is done on the same path Image generation has been intentionally removed.
134 lines
5.1 KiB
Go
134 lines
5.1 KiB
Go
package create
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
st "github.com/ollama/ollama/x/safetensors"
|
|
)
|
|
|
|
func writeConfigJSON(t *testing.T, dir, content string) {
|
|
t.Helper()
|
|
if err := os.WriteFile(filepath.Join(dir, "config.json"), []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestReadInventory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeConfigJSON(t, dir, `{"architectures":["TestModel"]}`)
|
|
createTestSafetensors(t, filepath.Join(dir, "model.safetensors"), []*st.TensorData{
|
|
st.NewTensorDataFromBytes("model.embed.weight", "BF16", []int32{4, 8}, make([]byte, 4*8*2)),
|
|
st.NewTensorDataFromBytes("model.layers.0.weight", "BF16", []int32{8, 8}, make([]byte, 8*8*2)),
|
|
})
|
|
|
|
inv, err := ReadInventory(dir)
|
|
if err != nil {
|
|
t.Fatalf("ReadInventory() error = %v", err)
|
|
}
|
|
if len(inv.Tensors) != 2 {
|
|
t.Fatalf("got %d tensors, want 2", len(inv.Tensors))
|
|
}
|
|
embed, ok := inv.Tensors["model.embed.weight"]
|
|
if !ok {
|
|
t.Fatal("missing model.embed.weight")
|
|
}
|
|
if embed.Dtype != "BF16" || !slices.Equal(embed.Shape, []int32{4, 8}) || embed.File != "model.safetensors" {
|
|
t.Errorf("embed tensor = %+v", embed)
|
|
}
|
|
if !inv.Has("model.layers.0.weight") {
|
|
t.Error("Has(model.layers.0.weight) = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestReadInventoryIncomplete(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeConfigJSON(t, dir, `{"architectures":["TestModel"]}`)
|
|
createTestSafetensors(t, filepath.Join(dir, "model-00001-of-00002.safetensors"), []*st.TensorData{
|
|
st.NewTensorDataFromBytes("model.a.weight", "BF16", []int32{4, 4}, make([]byte, 4*4*2)),
|
|
})
|
|
// The index references a second shard that is not present on disk.
|
|
index := `{"weight_map":{"model.a.weight":"model-00001-of-00002.safetensors","model.b.weight":"model-00002-of-00002.safetensors"}}`
|
|
if err := os.WriteFile(filepath.Join(dir, "model.safetensors.index.json"), []byte(index), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := ReadInventory(dir)
|
|
if err == nil || !strings.Contains(err.Error(), "incomplete") {
|
|
t.Fatalf("ReadInventory() error = %v, want substring %q", err, "incomplete")
|
|
}
|
|
}
|
|
|
|
func TestReadInventorySkipsConsolidated(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeConfigJSON(t, dir, `{"architectures":["TestModel"]}`)
|
|
// Standard HF weights — imported.
|
|
createTestSafetensors(t, filepath.Join(dir, "model.safetensors"), []*st.TensorData{
|
|
st.NewTensorDataFromBytes("model.layers.0.weight", "BF16", []int32{8, 8}, make([]byte, 8*8*2)),
|
|
})
|
|
// Mistral consolidated weights shipped in the same repo — must be ignored so
|
|
// they can't shadow or pollute the model tensors.
|
|
createTestSafetensors(t, filepath.Join(dir, "consolidated-00001-of-00001.safetensors"), []*st.TensorData{
|
|
st.NewTensorDataFromBytes("layers.0.attention.wq.weight", "BF16", []int32{8, 8}, make([]byte, 8*8*2)),
|
|
})
|
|
|
|
inv, err := ReadInventory(dir)
|
|
if err != nil {
|
|
t.Fatalf("ReadInventory() error = %v", err)
|
|
}
|
|
if len(inv.Tensors) != 1 || !inv.Has("model.layers.0.weight") {
|
|
t.Errorf("tensors = %v, want only model.layers.0.weight", inv.Tensors)
|
|
}
|
|
if inv.Has("layers.0.attention.wq.weight") {
|
|
t.Error("consolidated tensor was imported; consolidated-*.safetensors must be skipped")
|
|
}
|
|
}
|
|
|
|
func TestReadInventoryRejectsMonolithicPlusShards(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeConfigJSON(t, dir, `{"architectures":["TestModel"]}`)
|
|
createTestSafetensors(t, filepath.Join(dir, "model.safetensors"), []*st.TensorData{
|
|
st.NewTensorDataFromBytes("model.a.weight", "BF16", []int32{4, 4}, make([]byte, 4*4*2)),
|
|
})
|
|
createTestSafetensors(t, filepath.Join(dir, "model-00001-of-00001.safetensors"), []*st.TensorData{
|
|
st.NewTensorDataFromBytes("model.a.weight", "BF16", []int32{4, 4}, make([]byte, 4*4*2)),
|
|
})
|
|
|
|
_, err := ReadInventory(dir)
|
|
if err == nil || !strings.Contains(err.Error(), "ambiguous") {
|
|
t.Fatalf("ReadInventory() error = %v, want an 'ambiguous source' error", err)
|
|
}
|
|
}
|
|
|
|
func TestReadInventoryRejectsDuplicateTensorAcrossShards(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeConfigJSON(t, dir, `{"architectures":["TestModel"]}`)
|
|
createTestSafetensors(t, filepath.Join(dir, "model-00001-of-00002.safetensors"), []*st.TensorData{
|
|
st.NewTensorDataFromBytes("model.a.weight", "BF16", []int32{4, 4}, make([]byte, 4*4*2)),
|
|
})
|
|
createTestSafetensors(t, filepath.Join(dir, "model-00002-of-00002.safetensors"), []*st.TensorData{
|
|
st.NewTensorDataFromBytes("model.a.weight", "BF16", []int32{4, 4}, make([]byte, 4*4*2)),
|
|
})
|
|
|
|
_, err := ReadInventory(dir)
|
|
if err == nil || !strings.Contains(err.Error(), "duplicate tensor") {
|
|
t.Fatalf("ReadInventory() error = %v, want a 'duplicate tensor' error", err)
|
|
}
|
|
}
|
|
|
|
func TestReadInventoryNoModelWeights(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeConfigJSON(t, dir, `{"architectures":["TestModel"]}`)
|
|
// Only consolidated weights present — nothing importable.
|
|
createTestSafetensors(t, filepath.Join(dir, "consolidated.safetensors"), []*st.TensorData{
|
|
st.NewTensorDataFromBytes("layers.0.attention.wq.weight", "BF16", []int32{8, 8}, make([]byte, 8*8*2)),
|
|
})
|
|
|
|
_, err := ReadInventory(dir)
|
|
if err == nil || !strings.Contains(err.Error(), "no model") {
|
|
t.Fatalf("ReadInventory() error = %v, want a 'no model ... weights' error", err)
|
|
}
|
|
}
|