common.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. package runners
  2. import (
  3. "log/slog"
  4. "os"
  5. "path/filepath"
  6. "runtime"
  7. "slices"
  8. "strings"
  9. "sync"
  10. "golang.org/x/sys/cpu"
  11. "github.com/ollama/ollama/envconfig"
  12. )
  13. var (
  14. runnersDir = ""
  15. once = sync.Once{}
  16. )
  17. type CPUCapability uint32
  18. // Override at build time when building base GPU runners
  19. // var GPURunnerCPUCapability = CPUCapabilityAVX
  20. const (
  21. CPUCapabilityNone CPUCapability = iota
  22. CPUCapabilityAVX
  23. CPUCapabilityAVX2
  24. // TODO AVX512
  25. )
  26. func (c CPUCapability) String() string {
  27. switch c {
  28. case CPUCapabilityAVX:
  29. return "avx"
  30. case CPUCapabilityAVX2:
  31. return "avx2"
  32. default:
  33. return "no vector extensions"
  34. }
  35. }
  36. func GetCPUCapability() CPUCapability {
  37. if cpu.X86.HasAVX2 {
  38. return CPUCapabilityAVX2
  39. }
  40. if cpu.X86.HasAVX {
  41. return CPUCapabilityAVX
  42. }
  43. // else LCD
  44. return CPUCapabilityNone
  45. }
  46. // Return the location where runners were located
  47. // empty string indicates only builtin is present
  48. func Locate() string {
  49. once.Do(locateRunnersOnce)
  50. return runnersDir
  51. }
  52. // searches for runners in a prioritized set of locations
  53. // 1. local build, with executable at the top of the tree
  54. // 2. lib directory relative to executable
  55. func locateRunnersOnce() {
  56. exe, err := os.Executable()
  57. if err != nil {
  58. slog.Debug("runner locate", "error", err)
  59. }
  60. paths := []string{
  61. filepath.Join(filepath.Dir(exe), "llama", "build", runtime.GOOS+"-"+runtime.GOARCH, "runners"),
  62. filepath.Join(filepath.Dir(exe), envconfig.LibRelativeToExe(), "lib", "ollama", "runners"),
  63. }
  64. for _, path := range paths {
  65. if _, err := os.Stat(path); err == nil {
  66. runnersDir = path
  67. slog.Debug("runners located", "dir", runnersDir)
  68. return
  69. }
  70. }
  71. // Fall back to built-in
  72. slog.Debug("no dynamic runners detected, using only built-in")
  73. runnersDir = ""
  74. }
  75. // Return the well-known name of the builtin runner for the given platform
  76. func BuiltinName() string {
  77. if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
  78. return "metal"
  79. }
  80. return "cpu"
  81. }
  82. // directory names are the name of the runner and may contain an optional
  83. // variant prefixed with '_' as the separator. For example, "cuda_v11" and
  84. // "cuda_v12" or "cpu" and "cpu_avx2". Any library without a variant is the
  85. // lowest common denominator
  86. func GetAvailableServers() map[string]string {
  87. once.Do(locateRunnersOnce)
  88. servers := make(map[string]string)
  89. exe, err := os.Executable()
  90. if err == nil {
  91. servers[BuiltinName()] = exe
  92. }
  93. if runnersDir == "" {
  94. return servers
  95. }
  96. // glob runnersDir for files that start with ollama_
  97. pattern := filepath.Join(runnersDir, "*", "ollama_*")
  98. files, err := filepath.Glob(pattern)
  99. if err != nil {
  100. slog.Debug("could not glob", "pattern", pattern, "error", err)
  101. return nil
  102. }
  103. for _, file := range files {
  104. slog.Debug("availableServers : found", "file", file)
  105. runnerName := filepath.Base(filepath.Dir(file))
  106. // Special case for our GPU runners - if compiled with standard AVX flag
  107. // detect incompatible system
  108. // Custom builds will omit this and its up to the user to ensure compatibility
  109. parsed := strings.Split(runnerName, "_")
  110. if len(parsed) == 3 && parsed[2] == "avx" && !cpu.X86.HasAVX {
  111. slog.Info("GPU runner incompatible with host system, CPU does not have AVX", "runner", runnerName)
  112. continue
  113. }
  114. servers[runnerName] = file
  115. }
  116. return servers
  117. }
  118. // serversForGpu returns a list of compatible servers give the provided GPU library/variant
  119. func ServersForGpu(requested string) []string {
  120. // glob workDir for files that start with ollama_
  121. availableServers := GetAvailableServers()
  122. // Short circuit if the only option is built-in
  123. if _, ok := availableServers[BuiltinName()]; ok && len(availableServers) == 1 {
  124. return []string{BuiltinName()}
  125. }
  126. bestCPUVariant := GetCPUCapability()
  127. requestedLib := strings.Split(requested, "_")[0]
  128. servers := []string{}
  129. // exact match first
  130. for a := range availableServers {
  131. short := a
  132. parsed := strings.Split(a, "_")
  133. if len(parsed) == 3 {
  134. // Strip off optional _avx for comparison
  135. short = parsed[0] + "_" + parsed[1]
  136. }
  137. if a == requested || short == requested {
  138. servers = []string{a}
  139. }
  140. }
  141. // If no exact match, then try without variant
  142. if len(servers) == 0 {
  143. alt := []string{}
  144. for a := range availableServers {
  145. if requestedLib == strings.Split(a, "_")[0] && a != requested {
  146. alt = append(alt, a)
  147. }
  148. }
  149. slices.Sort(alt)
  150. servers = append(servers, alt...)
  151. }
  152. // Finally append the best CPU option if found, then builtin
  153. if bestCPUVariant != CPUCapabilityNone {
  154. for cmp := range availableServers {
  155. if cmp == "cpu_"+bestCPUVariant.String() {
  156. servers = append(servers, cmp)
  157. break
  158. }
  159. }
  160. }
  161. servers = append(servers, BuiltinName())
  162. return servers
  163. }
  164. // Return the optimal server for this CPU architecture
  165. func ServerForCpu() string {
  166. if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
  167. return BuiltinName()
  168. }
  169. variant := GetCPUCapability()
  170. availableServers := GetAvailableServers()
  171. if variant != CPUCapabilityNone {
  172. for cmp := range availableServers {
  173. if cmp == "cpu_"+variant.String() {
  174. return cmp
  175. }
  176. }
  177. }
  178. return BuiltinName()
  179. }