amd_windows.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. package discover
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "log/slog"
  7. "os"
  8. "path/filepath"
  9. "slices"
  10. "strconv"
  11. "strings"
  12. "github.com/ollama/ollama/envconfig"
  13. "github.com/ollama/ollama/format"
  14. )
  15. const (
  16. // TODO We're lookinng for this exact name to detect iGPUs since hipGetDeviceProperties never reports integrated==true
  17. iGPUName = "AMD Radeon(TM) Graphics"
  18. )
  19. var (
  20. // Used to validate if the given ROCm lib is usable
  21. ROCmLibGlobs = []string{"hipblas.dll", "rocblas"} // This is not sufficient to discern v5 vs v6
  22. RocmStandardLocations = []string{"C:\\Program Files\\AMD\\ROCm\\6.1\\bin"} // TODO glob?
  23. )
  24. // Only called once during bootstrap
  25. func AMDGetGPUInfo() ([]RocmGPUInfo, error) {
  26. resp := []RocmGPUInfo{}
  27. hl, err := NewHipLib()
  28. if err != nil {
  29. slog.Debug(err.Error())
  30. return nil, err
  31. }
  32. defer hl.Release()
  33. driverMajor, driverMinor, err := hl.AMDDriverVersion()
  34. if err != nil {
  35. // For now this is benign, but we may eventually need to fail compatibility checks
  36. slog.Debug("error looking up amd driver version", "error", err)
  37. }
  38. // Note: the HIP library automatically handles subsetting to any HIP_VISIBLE_DEVICES the user specified
  39. count := hl.HipGetDeviceCount()
  40. if count == 0 {
  41. err := fmt.Errorf("no compatible amdgpu devices detected")
  42. slog.Info(err.Error())
  43. return nil, err
  44. }
  45. libDir, err := AMDValidateLibDir()
  46. if err != nil {
  47. err = fmt.Errorf("unable to verify rocm library: %w", err)
  48. slog.Warn(err.Error())
  49. return nil, err
  50. }
  51. var supported []string
  52. gfxOverride := envconfig.HsaOverrideGfxVersion()
  53. if gfxOverride == "" {
  54. supported, err = GetSupportedGFX(libDir)
  55. if err != nil {
  56. err = fmt.Errorf("failed to lookup supported GFX types: %w", err)
  57. slog.Warn(err.Error())
  58. return nil, err
  59. }
  60. } else {
  61. slog.Info("skipping rocm gfx compatibility check", "HSA_OVERRIDE_GFX_VERSION", gfxOverride)
  62. }
  63. slog.Debug("detected hip devices", "count", count)
  64. // TODO how to determine the underlying device ID when visible devices is causing this to subset?
  65. for i := range count {
  66. err = hl.HipSetDevice(i)
  67. if err != nil {
  68. slog.Warn("set device", "id", i, "error", err)
  69. continue
  70. }
  71. props, err := hl.HipGetDeviceProperties(i)
  72. if err != nil {
  73. slog.Warn("get properties", "id", i, "error", err)
  74. continue
  75. }
  76. n := bytes.IndexByte(props.Name[:], 0)
  77. name := string(props.Name[:n])
  78. // TODO is UUID actually populated on windows?
  79. // Can luid be used on windows for setting visible devices (and is it actually set?)
  80. n = bytes.IndexByte(props.GcnArchName[:], 0)
  81. gfx := string(props.GcnArchName[:n])
  82. slog.Debug("hip device", "id", i, "name", name, "gfx", gfx)
  83. // slog.Info(fmt.Sprintf("[%d] Integrated: %d", i, props.iGPU)) // DOESN'T REPORT CORRECTLY! Always 0
  84. // TODO Why isn't props.iGPU accurate!?
  85. freeMemory, totalMemory, err := hl.HipMemGetInfo()
  86. if err != nil {
  87. slog.Warn("get mem info", "id", i, "error", err)
  88. continue
  89. }
  90. gpuInfo := RocmGPUInfo{
  91. GpuInfo: GpuInfo{
  92. Library: "rocm",
  93. memInfo: memInfo{
  94. TotalMemory: totalMemory,
  95. FreeMemory: freeMemory,
  96. },
  97. // Free memory reporting on Windows is not reliable until we bump to ROCm v6.2
  98. UnreliableFreeMemory: true,
  99. ID: strconv.Itoa(i), // TODO this is probably wrong if we specify visible devices
  100. DependencyPath: libDir,
  101. MinimumMemory: rocmMinimumMemory,
  102. Name: name,
  103. Compute: gfx,
  104. DriverMajor: driverMajor,
  105. DriverMinor: driverMinor,
  106. },
  107. index: i,
  108. }
  109. // iGPU detection, remove this check once we can support an iGPU variant of the rocm library
  110. if strings.EqualFold(name, iGPUName) || totalMemory < IGPUMemLimit {
  111. reason := "unsupported Radeon iGPU detected skipping"
  112. slog.Info(reason, "id", gpuInfo.ID, "total", format.HumanBytes2(totalMemory))
  113. unsupportedGPUs = append(unsupportedGPUs, UnsupportedGPUInfo{
  114. GpuInfo: gpuInfo.GpuInfo,
  115. Reason: reason,
  116. })
  117. continue
  118. }
  119. // Strip off Target Features when comparing
  120. if !slices.Contains[[]string, string](supported, strings.Split(gfx, ":")[0]) {
  121. reason := fmt.Sprintf("amdgpu is not supported (supported types:%s)", supported)
  122. slog.Warn(reason, "gpu_type", gfx, "gpu", gpuInfo.ID, "library", libDir)
  123. unsupportedGPUs = append(unsupportedGPUs, UnsupportedGPUInfo{
  124. GpuInfo: gpuInfo.GpuInfo,
  125. Reason: reason,
  126. })
  127. // HSA_OVERRIDE_GFX_VERSION not supported on windows
  128. continue
  129. } else {
  130. slog.Debug("amdgpu is supported", "gpu", i, "gpu_type", gfx)
  131. }
  132. slog.Debug("amdgpu memory", "gpu", i, "total", format.HumanBytes2(totalMemory))
  133. slog.Debug("amdgpu memory", "gpu", i, "available", format.HumanBytes2(freeMemory))
  134. resp = append(resp, gpuInfo)
  135. }
  136. return resp, nil
  137. }
  138. func AMDValidateLibDir() (string, error) {
  139. libDir, err := commonAMDValidateLibDir()
  140. if err == nil {
  141. return libDir, nil
  142. }
  143. // Installer payload (if we're running from some other location)
  144. localAppData := os.Getenv("LOCALAPPDATA")
  145. appDir := filepath.Join(localAppData, "Programs", "Ollama")
  146. rocmTargetDir := filepath.Join(appDir, envconfig.LibRelativeToExe(), "lib", "ollama")
  147. if rocmLibUsable(rocmTargetDir) {
  148. slog.Debug("detected ollama installed ROCm at " + rocmTargetDir)
  149. return rocmTargetDir, nil
  150. }
  151. // Should not happen on windows since we include it in the installer, but stand-alone binary might hit this
  152. slog.Warn("amdgpu detected, but no compatible rocm library found. Please install ROCm")
  153. return "", errors.New("no suitable rocm found, falling back to CPU")
  154. }
  155. func (gpus RocmGPUInfoList) RefreshFreeMemory() error {
  156. if len(gpus) == 0 {
  157. return nil
  158. }
  159. hl, err := NewHipLib()
  160. if err != nil {
  161. slog.Debug(err.Error())
  162. return nil
  163. }
  164. defer hl.Release()
  165. for i := range gpus {
  166. err := hl.HipSetDevice(gpus[i].index)
  167. if err != nil {
  168. return err
  169. }
  170. freeMemory, _, err := hl.HipMemGetInfo()
  171. if err != nil {
  172. slog.Warn("get mem info", "id", i, "error", err)
  173. continue
  174. }
  175. slog.Debug("updating rocm free memory", "gpu", gpus[i].ID, "name", gpus[i].Name, "before", format.HumanBytes2(gpus[i].FreeMemory), "now", format.HumanBytes2(freeMemory))
  176. gpus[i].FreeMemory = freeMemory
  177. }
  178. return nil
  179. }