common.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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. filepath.Join(filepath.Dir(exe), "lib", "ollama", "runners"),
  64. }
  65. for _, path := range paths {
  66. if _, err := os.Stat(path); err == nil {
  67. runnersDir = path
  68. slog.Debug("runners located", "dir", runnersDir)
  69. return
  70. }
  71. }
  72. // Fall back to built-in
  73. slog.Debug("no dynamic runners detected, using only built-in")
  74. runnersDir = ""
  75. }
  76. // Return the well-known name of the builtin runner for the given platform
  77. func BuiltinName() string {
  78. if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
  79. return "metal"
  80. }
  81. return "cpu"
  82. }
  83. // directory names are the name of the runner and may contain an optional
  84. // variant prefixed with '_' as the separator. For example, "cuda_v11" and
  85. // "cuda_v12" or "cpu" and "cpu_avx2". Any library without a variant is the
  86. // lowest common denominator
  87. func GetAvailableServers() map[string]string {
  88. once.Do(locateRunnersOnce)
  89. servers := make(map[string]string)
  90. exe, err := os.Executable()
  91. if err == nil {
  92. servers[BuiltinName()] = exe
  93. }
  94. if runnersDir == "" {
  95. return servers
  96. }
  97. // glob runnersDir for files that start with ollama_
  98. pattern := filepath.Join(runnersDir, "*", "ollama_*")
  99. files, err := filepath.Glob(pattern)
  100. if err != nil {
  101. slog.Debug("could not glob", "pattern", pattern, "error", err)
  102. return nil
  103. }
  104. for _, file := range files {
  105. slog.Debug("availableServers : found", "file", file)
  106. runnerName := filepath.Base(filepath.Dir(file))
  107. // Special case for our GPU runners - if compiled with standard AVX flag
  108. // detect incompatible system
  109. // Custom builds will omit this and its up to the user to ensure compatibility
  110. parsed := strings.Split(runnerName, "_")
  111. if len(parsed) == 3 && parsed[2] == "avx" && !cpu.X86.HasAVX {
  112. slog.Info("GPU runner incompatible with host system, CPU does not have AVX", "runner", runnerName)
  113. continue
  114. }
  115. servers[runnerName] = file
  116. }
  117. return servers
  118. }
  119. // serversForGpu returns a list of compatible servers give the provided GPU library/variant
  120. func ServersForGpu(requested string) []string {
  121. // glob workDir for files that start with ollama_
  122. availableServers := GetAvailableServers()
  123. // Short circuit if the only option is built-in
  124. if _, ok := availableServers[BuiltinName()]; ok && len(availableServers) == 1 {
  125. return []string{BuiltinName()}
  126. }
  127. bestCPUVariant := GetCPUCapability()
  128. requestedLib := strings.Split(requested, "_")[0]
  129. servers := []string{}
  130. // exact match first
  131. for a := range availableServers {
  132. short := a
  133. parsed := strings.Split(a, "_")
  134. if len(parsed) == 3 {
  135. // Strip off optional _avx for comparison
  136. short = parsed[0] + "_" + parsed[1]
  137. }
  138. if a == requested || short == requested {
  139. servers = []string{a}
  140. }
  141. }
  142. // If no exact match, then try without variant
  143. if len(servers) == 0 {
  144. alt := []string{}
  145. for a := range availableServers {
  146. if requestedLib == strings.Split(a, "_")[0] && a != requested {
  147. alt = append(alt, a)
  148. }
  149. }
  150. slices.Sort(alt)
  151. servers = append(servers, alt...)
  152. }
  153. // Finally append the best CPU option if found, then builtin
  154. if bestCPUVariant != CPUCapabilityNone {
  155. for cmp := range availableServers {
  156. if cmp == "cpu_"+bestCPUVariant.String() {
  157. servers = append(servers, cmp)
  158. break
  159. }
  160. }
  161. }
  162. servers = append(servers, BuiltinName())
  163. return servers
  164. }
  165. // Return the optimal server for this CPU architecture
  166. func ServerForCpu() string {
  167. if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
  168. return BuiltinName()
  169. }
  170. variant := GetCPUCapability()
  171. availableServers := GetAvailableServers()
  172. if variant != CPUCapabilityNone {
  173. for cmp := range availableServers {
  174. if cmp == "cpu_"+variant.String() {
  175. return cmp
  176. }
  177. }
  178. }
  179. return BuiltinName()
  180. }