ollama/x/create/inventory_test.go
Patrick Devine 964ea42c09
mlx: x/create rewrite (#16919)
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.
2026-07-03 18:30:45 -07:00

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)
}
}