memory_test.go 4.38 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
package llm

import (
	"bytes"
	"fmt"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
Michael Yang's avatar
lint  
Michael Yang committed
11
12

	"github.com/ollama/ollama/api"
13
	"github.com/ollama/ollama/discover"
Michael Yang's avatar
Michael Yang committed
14
	"github.com/ollama/ollama/fs/ggml"
15
16
17
)

func TestEstimateGPULayers(t *testing.T) {
Michael Yang's avatar
Michael Yang committed
18
	t.Setenv("OLLAMA_DEBUG", "1")
19
	t.Setenv("OLLAMA_KV_CACHE_TYPE", "") // Ensure default f16
20
	t.Setenv("OLLAMA_CONTEXT_LENGTH", "2048")
Michael Yang's avatar
Michael Yang committed
21

22
23
	modelName := "dummy"
	f, err := os.CreateTemp(t.TempDir(), modelName)
Daniel Hiltgen's avatar
Daniel Hiltgen committed
24
	require.NoError(t, err)
25
26
	defer f.Close()
	inputLayerCount := 5
27

28
	tensors := []*ggml.Tensor{
29
30
31
32
33
34
		{Name: "blk.0.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))},
		{Name: "blk.1.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))},
		{Name: "blk.2.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))},
		{Name: "blk.3.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))},
		{Name: "blk.4.attn.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))},
		{Name: "output.weight", Kind: uint32(0), Offset: uint64(0), Shape: []uint64{1, 1, 1, 1}, WriterTo: bytes.NewReader(make([]byte, 32))},
35
	}
Daniel Hiltgen's avatar
Daniel Hiltgen committed
36
	assert.Len(t, tensors, inputLayerCount+1)
Michael Yang's avatar
Michael Yang committed
37
	err = ggml.WriteGGUF(f, ggml.KV{
38
39
40
41
42
43
44
45
46
47
48
49
		"general.architecture":          "llama",
		"llama.context_length":          uint32(32),
		"llama.embedding_length":        uint32(4096),
		"llama.block_count":             uint32(inputLayerCount),
		"llama.attention.head_count":    uint32(32),
		"llama.attention.head_count_kv": uint32(32),
		"tokenizer.ggml.tokens":         []string{" "},
		"tokenizer.ggml.scores":         []float32{0},
		"tokenizer.ggml.token_type":     []int32{0},
	}, tensors)
	require.NoError(t, err)

50
51
52
53
	ggml, err := LoadModel(f.Name(), 0)
	if err != nil {
		t.Fatal(err)
	}
54
55

	// Simple CPU scenario
56
	gpus := []discover.GpuInfo{
57
58
59
60
61
62
		{
			Library: "cpu",
		},
	}
	projectors := []string{}
	opts := api.DefaultOptions()
Daniel Hiltgen's avatar
Daniel Hiltgen committed
63
	t.Run("cpu", func(t *testing.T) {
Jesse Gross's avatar
Jesse Gross committed
64
		estimate := estimateGPULayers(gpus, ggml, projectors, opts, 1)
Daniel Hiltgen's avatar
Daniel Hiltgen committed
65
66
67
		assert.Equal(t, 0, estimate.Layers)
		assert.Equal(t, uint64(0), estimate.Graph)
	})
68
69
70
71
72
73
74
75

	// derived from the dummy ggml file above
	graphPartialOffload := uint64(202377216)
	graphFullOffload := uint64(171968512)
	layerSize := uint64(33554436)
	projectorSize := uint64(0)
	memoryLayerOutput := uint64(4)

76
	// Dual CUDA scenario with asymmetry
77
	gpuMinimumMemory := uint64(2048)
78
	gpus = []discover.GpuInfo{
79
80
81
82
83
84
85
86
87
88
		{
			Library:       "cuda",
			MinimumMemory: gpuMinimumMemory,
		},
		{
			Library:       "cuda",
			MinimumMemory: gpuMinimumMemory,
		},
	}
	// Nested array: GPU0 layer space, GPU1 layer space, expected gpu0, expected gpu1
Daniel Hiltgen's avatar
Daniel Hiltgen committed
89
90
	for i, s := range []struct {
		layer0, layer1   uint64
Jesse Gross's avatar
Jesse Gross committed
91
		expect0, expect1 int
Daniel Hiltgen's avatar
Daniel Hiltgen committed
92
	}{
93
94
95
96
97
98
99
100
101
		{1, 1, 1, 1},
		{2, 1, 2, 1},
		{2, 2, 2, 2},
		{1, 2, 1, 2},
		{3, 3, 3, 3},
		{4, 4, 3, 3},
		{6, 6, 3, 3},
		{0, 3, 0, 3},
	} {
Daniel Hiltgen's avatar
Daniel Hiltgen committed
102
103
104
105
106
107
108
109
110
111
112
113
114
		t.Run(fmt.Sprintf("%v", s), func(t *testing.T) {
			gpus[0].FreeMemory = 0
			gpus[1].FreeMemory = 0
			gpus[0].FreeMemory += projectorSize
			if s.layer0 > 0 {
				gpus[0].FreeMemory += memoryLayerOutput
			} else {
				gpus[1].FreeMemory += memoryLayerOutput
			}
			gpus[0].FreeMemory += gpuMinimumMemory + layerSize + s.layer0*layerSize + 1
			gpus[1].FreeMemory += gpuMinimumMemory + layerSize + s.layer1*layerSize + 1
			gpus[0].FreeMemory += max(graphFullOffload, graphPartialOffload)
			gpus[1].FreeMemory += max(graphFullOffload, graphPartialOffload)
Jesse Gross's avatar
Jesse Gross committed
115
116
117
			estimate := estimateGPULayers(gpus, ggml, projectors, opts, 1)
			assert.Equal(t, s.expect0+s.expect1, estimate.Layers, "scenario %d: %v", i, s)
			assert.Equal(t, []int{s.expect0, s.expect1}, estimate.TensorSplit, "scenario %d: %v", i, s)
Daniel Hiltgen's avatar
Daniel Hiltgen committed
118
119
120
121
122
123
124
125
126
127
128
129
			var layerSums uint64
			for _, b := range estimate.GPUSizes {
				layerSums += b
			}
			if estimate.Layers < inputLayerCount+1 {
				assert.Less(t, estimate.VRAMSize, estimate.TotalSize, "scenario %d: %v %+v", i, s, estimate)
				assert.Equal(t, estimate.VRAMSize, layerSums, "scenario %d: %v %+v", i, s, estimate)
			} else {
				assert.Equal(t, estimate.VRAMSize, estimate.TotalSize, "scenario %d: %v %+v", i, s, estimate)
				assert.Equal(t, estimate.TotalSize, layerSums, "scenario %d: %v %+v", i, s, estimate)
			}
		})
130
131
	}
}