Files
ollama/llm/model_split_test.go
Daniel Hiltgen 98acec40ae create: add server-side MLX imports and drop GGUF conversion (#14969)
* create: add server-side MLX imports and drop GGUF conversion

Support safetensors imports through the MLX create pipeline both locally and on the server, including remote upload/staging, draft layer handling, cancellation propagation, transfer limits, and shared manifest/blob writing.

Limit GGUF create to wrapping existing GGUF inputs into Ollama manifests. Remove the in-tree safetensors-to-GGUF converter, server quantization path, and converter-only dependencies so GGUF conversion and quantization stay in llama.cpp tooling.

Keep the MLX path focused on supported safetensors model creation with validation before MLX work, and expose that flow without the --experimental CLI gate.

* address comments

* add client side gguf create fast path

* address comments

* rebase adjustments
2026-09-14 20:32:36 -07:00

186 lines
5.7 KiB
Go

package llm
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
func TestMaterializeSplitModel(t *testing.T) {
dir := t.TempDir()
manifestDigest := strings.Repeat("a", 64)
first := filepath.Join(dir, "first")
second := filepath.Join(dir, "second")
if err := os.WriteFile(first, []byte("first shard"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(second, []byte("second shard"), 0o600); err != nil {
t.Fatal(err)
}
projectors := []string{first, "projector.gguf"}
launch, err := materializeSplitModels([]string{first, second}, projectors, LlamaServerConfig{ManifestDigest: manifestDigest})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _, _ = removeSplitModelDirs(launch.dirs) })
modelPath, splitDir := launch.modelPath, launch.dirs[0]
if want := filepath.Join(dir, splitModelDirPrefix+manifestDigest[:12]+"-"); !strings.HasPrefix(splitDir, want) {
t.Fatalf("split directory = %q, want prefix %q", splitDir, want)
}
if launch.projectors[0] != modelPath || launch.projectors[1] != projectors[1] {
t.Fatalf("projectors = %q, want model alias and %q", launch.projectors, projectors[1])
}
if projectors[0] != first {
t.Fatalf("input projectors modified: %q", projectors)
}
other, err := materializeSplitModels([]string{first, second}, nil, LlamaServerConfig{ManifestDigest: manifestDigest})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _, _ = removeSplitModelDirs(other.dirs) })
if other.dirs[0] == splitDir {
t.Fatalf("concurrent materializations share directory %q", splitDir)
}
if got, want := modelPath, filepath.Join(splitDir, "model-00001-of-00002.gguf"); got != want {
t.Fatalf("model path = %q, want %q", got, want)
}
for i, source := range []string{first, second} {
alias := filepath.Join(splitDir, []string{"model-00001-of-00002.gguf", "model-00002-of-00002.gguf"}[i])
got, err := os.ReadFile(alias)
if err != nil {
t.Fatal(err)
}
want, err := os.ReadFile(source)
if err != nil {
t.Fatal(err)
}
if string(got) != string(want) {
t.Fatalf("alias %d contents = %q, want %q", i, got, want)
}
if runtime.GOOS != "windows" {
sourceInfo, err := os.Stat(source)
if err != nil {
t.Fatal(err)
}
aliasInfo, err := os.Stat(alias)
if err != nil {
t.Fatal(err)
}
if !os.SameFile(sourceInfo, aliasInfo) {
t.Fatalf("alias %d is not a hard link to its source", i)
}
}
}
}
func TestMaterializeSplitModelsDraft(t *testing.T) {
dir := t.TempDir()
model := filepath.Join(dir, "model")
draft := filepath.Join(dir, "draft-first")
draftShard := filepath.Join(dir, "draft-second")
for _, path := range []string{model, draft, draftShard} {
if err := os.WriteFile(path, []byte(path), 0o600); err != nil {
t.Fatal(err)
}
}
launch, err := materializeSplitModels([]string{model}, nil, LlamaServerConfig{
ManifestDigest: strings.Repeat("b", 64),
DraftModelPath: draft,
DraftModelShardPaths: []string{draftShard},
})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _, _ = removeSplitModelDirs(launch.dirs) })
if launch.modelPath != model {
t.Fatalf("model path = %q, want %q", launch.modelPath, model)
}
if got, want := launch.draftModelPath, filepath.Join(launch.dirs[0], "model-00001-of-00002.gguf"); got != want {
t.Fatalf("draft path = %q, want %q", got, want)
}
}
func TestMaterializeSplitModelSingleFile(t *testing.T) {
modelPath := filepath.Join(t.TempDir(), "model.gguf")
gotPath, gotDir, err := materializeSplitModel(modelPath, nil, strings.Repeat("a", 64))
if err != nil {
t.Fatal(err)
}
if gotPath != modelPath || gotDir != "" {
t.Fatalf("materializeSplitModel() = %q, %q, want %q, empty", gotPath, gotDir, modelPath)
}
}
func TestSplitModelDirPattern(t *testing.T) {
digest := strings.Repeat("0123456789abcdef", 4)
if got, want := splitModelDirPattern(digest), splitModelDirPrefix+digest[:12]+"-"; got != want {
t.Fatalf("splitModelDirPattern() = %q, want %q", got, want)
}
for _, digest := range []string{"", "sha256:" + digest, strings.Repeat("g", 64), "../../models"} {
if got := splitModelDirPattern(digest); got != splitModelDirPrefix {
t.Errorf("splitModelDirPattern(%q) = %q, want %q", digest, got, splitModelDirPrefix)
}
}
}
func TestPruneSplitModelDirs(t *testing.T) {
root := t.TempDir()
stale := filepath.Join(root, splitModelDirPrefix+"stale")
if err := os.Mkdir(stale, 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(stale, "model-00001-of-00002.gguf"), nil, 0o600); err != nil {
t.Fatal(err)
}
old := time.Now().Add(-2 * time.Hour)
if err := os.Chtimes(stale, old, old); err != nil {
t.Fatal(err)
}
active := filepath.Join(root, splitModelDirPrefix+"active")
if err := os.Mkdir(active, 0o700); err != nil {
t.Fatal(err)
}
keep := filepath.Join(root, "models")
if err := os.Mkdir(keep, 0o700); err != nil {
t.Fatal(err)
}
if err := PruneSplitModelDirs(root, time.Now().Add(-time.Hour)); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(stale); !os.IsNotExist(err) {
t.Fatalf("split model directory still exists after cleanup: %v", err)
}
if _, err := os.Stat(keep); err != nil {
t.Fatalf("unrelated directory was removed: %v", err)
}
if _, err := os.Stat(active); err != nil {
t.Fatalf("recent split model directory was removed: %v", err)
}
}
func TestLlamaServerRunnerRemovesSplitDirs(t *testing.T) {
dir := filepath.Join(t.TempDir(), "split")
if err := os.Mkdir(dir, 0o700); err != nil {
t.Fatal(err)
}
runner := &llamaServerRunner{splitDirs: []string{dir}}
if err := runner.removeSplitDirs(); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(dir); !os.IsNotExist(err) {
t.Fatalf("split directory still exists after cleanup: %v", err)
}
if len(runner.splitDirs) != 0 {
t.Fatalf("runner retained %d cleaned split directories", len(runner.splitDirs))
}
}