llm.go 4.28 KB
Newer Older
1
2
3
package llm

import (
4
	"context"
5
	"fmt"
6
	"log"
7
	"os"
8
	"runtime"
9
10

	"github.com/jmorganca/ollama/api"
11
	"github.com/jmorganca/ollama/gpu"
12
13
14
)

type LLM interface {
Bruce MacDonald's avatar
Bruce MacDonald committed
15
	Predict(context.Context, PredictOpts, func(PredictResult)) error
16
17
18
	Embedding(context.Context, string) ([]float64, error)
	Encode(context.Context, string) ([]int, error)
	Decode(context.Context, []int) (string, error)
19
20
21
	Close()
}

22
var AvailableShims = map[string]string{}
23

Michael Yang's avatar
Michael Yang committed
24
func New(workDir, model string, adapters, projectors []string, opts api.Options) (LLM, error) {
25
26
27
28
29
30
31
32
	if _, err := os.Stat(model); err != nil {
		return nil, err
	}

	f, err := os.Open(model)
	if err != nil {
		return nil, err
	}
Michael Yang's avatar
Michael Yang committed
33
	defer f.Close()
34

Bruce MacDonald's avatar
Bruce MacDonald committed
35
	ggml, err := DecodeGGML(f)
36
37
38
39
	if err != nil {
		return nil, err
	}

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
	if opts.NumCtx < 4 {
		opts.NumCtx = 4
	}

	fmt.Println("size", ggml.Size)
	fmt.Println("filetype", ggml.FileType())
	fmt.Println("architecture", ggml.ModelFamily())
	fmt.Println("type", ggml.ModelType())
	fmt.Println("name", ggml.Name())
	fmt.Println("embd", ggml.NumEmbed())
	fmt.Println("head", ggml.NumHead())
	fmt.Println("head_kv", ggml.NumHeadKv())
	fmt.Println("gqa", ggml.NumGQA())

	available, _ := gpu.CheckVRAM()

	// For now assume filesize = model size
	// TODO: use actual model size
	requiredModel := ggml.Size

	// fp16 k,v matrices require = n_ctx * n_layer * n_embd / n_head * n_head_kv * 2 bytes each * 2 key and value
	requiredKv := 2 * 2 * int64(opts.NumCtx) * int64(ggml.NumLayers()) * int64(ggml.NumEmbed()) * int64(ggml.NumHeadKv()) / int64(ggml.NumHead())
62

63
64
	// this amount is the overhead + tensors in memory
	// TODO: get this from the llama.cpp's graph calcluations instead of
65
66
	// estimating it's 1/6 * kv_cache_size * num_gqa
	requiredAlloc := int64(ggml.NumGQA()) * requiredKv / 6
67

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
	requiredTotal := requiredModel + requiredKv + requiredAlloc

	log.Println("system memory bytes:", available)
	log.Println("required model bytes:", requiredModel)
	log.Println("required kv bytes:", requiredKv)
	log.Println("required alloc bytes:", requiredAlloc)
	log.Println("required total bytes:", requiredTotal)

	info := gpu.GetGPUInfo()
	library := info.Library

	if opts.NumGPU == -1 {
		// default to offloading all layers
		opts.NumGPU = int(ggml.NumLayers()) + 1
	}

	// decide how many layers to put on the GPU
	if opts.NumGPU > 0 {
		switch runtime.GOOS {
		case "darwin":
			if requiredTotal > available {
				log.Println("not enough vram available, falling back to CPU only")
				opts.NumGPU = 0
			}
		default:
			if library == "cpu" || library == "default" {
				opts.NumGPU = 0
				break
			}

			// no offloading required
			if requiredTotal <= available {
				break
			}

			// This handles two cases:
			// 1. overhead + tensors are always loaded into scratch memory even with num_gpu 0
			// 2. it seems llama.cpp always tries to allocate the entire kv cache (even if later split into layers) into vram or crashes
			if requiredAlloc > available || requiredKv > available {
				log.Printf("not enough vram available, falling back to CPU only")
				library = "cpu"
				opts.NumGPU = 0
				break
			}

			available -= requiredAlloc

			// fill remaining vram with layers
			log.Println("splitting", available, "of available memory bytes into layers")
			bytesPerLayer := int64((requiredModel + requiredKv) / int64(ggml.NumLayers()))
			log.Println("bytes per layer:", bytesPerLayer)
			layers := available / bytesPerLayer
			if layers < int64(opts.NumGPU) {
				opts.NumGPU = int(layers)
			}
123
		}
124
125
	}

Bruce MacDonald's avatar
Bruce MacDonald committed
126
127
128
	opts.NumGQA = 0
	opts.RopeFrequencyBase = 0.0
	opts.RopeFrequencyScale = 0.0
129
	gpuInfo := gpu.GetGPUInfo()
130
	return newLlmServer(gpuInfo.Library, model, adapters, projectors, opts)
131
132
133
134
135
}

// Give any native cgo implementations an opportunity to initialize
func Init(workdir string) error {
	return nativeInit(workdir)
136
}
137

138
func newLlmServer(library, model string, adapters, projectors []string, opts api.Options) (extServer, error) {
139
	if _, libPresent := AvailableShims[library]; libPresent && library != "default" {
140
		srv, err := newDynamicShimExtServer(AvailableShims[library], model, adapters, projectors, opts)
141
142
143
		if err == nil {
			return srv, nil
		}
144
145
		log.Printf("Failed to load dynamic library %s - falling back to CPU mode %s", library, err)
		// TODO - update some state to indicate we were unable to load the GPU library for future "info" ux
146
147
	}

148
	return newDefaultExtServer(model, adapters, projectors, opts)
149
}