2024-02-16 01:15:09 +00:00
|
|
|
package gpu
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"log/slog"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"slices"
|
2024-06-05 19:07:20 +00:00
|
|
|
"strconv"
|
2024-02-16 01:15:09 +00:00
|
|
|
"strings"
|
2024-03-30 16:50:05 +00:00
|
|
|
|
2024-05-08 18:11:50 +00:00
|
|
|
"github.com/ollama/ollama/envconfig"
|
2024-03-30 16:50:05 +00:00
|
|
|
"github.com/ollama/ollama/format"
|
2024-02-16 01:15:09 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
|
|
// TODO We're lookinng for this exact name to detect iGPUs since hipGetDeviceProperties never reports integrated==true
|
|
|
|
iGPUName = "AMD Radeon(TM) Graphics"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// Used to validate if the given ROCm lib is usable
|
2024-07-10 18:01:22 +00:00
|
|
|
ROCmLibGlobs = []string{"hipblas.dll", "rocblas"} // This is not sufficient to discern v5 vs v6
|
|
|
|
RocmStandardLocations = []string{"C:\\Program Files\\AMD\\ROCm\\6.1\\bin"} // TODO glob?
|
2024-02-16 01:15:09 +00:00
|
|
|
)
|
|
|
|
|
2024-05-15 22:13:16 +00:00
|
|
|
func AMDGetGPUInfo() []RocmGPUInfo {
|
|
|
|
resp := []RocmGPUInfo{}
|
2024-02-16 01:15:09 +00:00
|
|
|
hl, err := NewHipLib()
|
|
|
|
if err != nil {
|
|
|
|
slog.Debug(err.Error())
|
2024-03-30 16:50:05 +00:00
|
|
|
return nil
|
2024-02-16 01:15:09 +00:00
|
|
|
}
|
|
|
|
defer hl.Release()
|
|
|
|
|
2024-06-18 23:22:47 +00:00
|
|
|
driverMajor, driverMinor, err := hl.AMDDriverVersion()
|
|
|
|
if err != nil {
|
|
|
|
// For now this is benign, but we may eventually need to fail compatibility checks
|
|
|
|
slog.Debug("error looking up amd driver version", "error", err)
|
|
|
|
}
|
2024-02-16 01:15:09 +00:00
|
|
|
|
2024-03-30 16:50:05 +00:00
|
|
|
// Note: the HIP library automatically handles subsetting to any HIP_VISIBLE_DEVICES the user specified
|
2024-02-16 01:15:09 +00:00
|
|
|
count := hl.HipGetDeviceCount()
|
|
|
|
if count == 0 {
|
2024-03-30 16:50:05 +00:00
|
|
|
return nil
|
2024-02-16 01:15:09 +00:00
|
|
|
}
|
|
|
|
libDir, err := AMDValidateLibDir()
|
|
|
|
if err != nil {
|
2024-03-30 16:50:05 +00:00
|
|
|
slog.Warn("unable to verify rocm library, will use cpu", "error", err)
|
|
|
|
return nil
|
2024-02-16 01:15:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var supported []string
|
2024-05-08 18:11:50 +00:00
|
|
|
gfxOverride := envconfig.HsaOverrideGfxVersion
|
2024-02-16 01:15:09 +00:00
|
|
|
if gfxOverride == "" {
|
|
|
|
supported, err = GetSupportedGFX(libDir)
|
|
|
|
if err != nil {
|
2024-03-30 16:50:05 +00:00
|
|
|
slog.Warn("failed to lookup supported GFX types, falling back to CPU mode", "error", err)
|
|
|
|
return nil
|
2024-02-16 01:15:09 +00:00
|
|
|
}
|
|
|
|
} else {
|
2024-05-07 21:54:26 +00:00
|
|
|
slog.Info("skipping rocm gfx compatibility check", "HSA_OVERRIDE_GFX_VERSION", gfxOverride)
|
2024-02-16 01:15:09 +00:00
|
|
|
}
|
|
|
|
|
2024-05-07 21:54:26 +00:00
|
|
|
slog.Debug("detected hip devices", "count", count)
|
2024-03-30 16:50:05 +00:00
|
|
|
// TODO how to determine the underlying device ID when visible devices is causing this to subset?
|
2024-05-22 16:26:45 +00:00
|
|
|
for i := range count {
|
2024-02-16 01:15:09 +00:00
|
|
|
err = hl.HipSetDevice(i)
|
|
|
|
if err != nil {
|
2024-03-30 16:50:05 +00:00
|
|
|
slog.Warn("set device", "id", i, "error", err)
|
2024-02-16 01:15:09 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
props, err := hl.HipGetDeviceProperties(i)
|
|
|
|
if err != nil {
|
2024-03-30 16:50:05 +00:00
|
|
|
slog.Warn("get properties", "id", i, "error", err)
|
2024-02-16 01:15:09 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
n := bytes.IndexByte(props.Name[:], 0)
|
|
|
|
name := string(props.Name[:n])
|
2024-03-30 16:50:05 +00:00
|
|
|
// TODO is UUID actually populated on windows?
|
|
|
|
// Can luid be used on windows for setting visible devices (and is it actually set?)
|
2024-02-16 01:15:09 +00:00
|
|
|
n = bytes.IndexByte(props.GcnArchName[:], 0)
|
|
|
|
gfx := string(props.GcnArchName[:n])
|
2024-05-07 21:54:26 +00:00
|
|
|
slog.Debug("hip device", "id", i, "name", name, "gfx", gfx)
|
2024-02-16 01:15:09 +00:00
|
|
|
//slog.Info(fmt.Sprintf("[%d] Integrated: %d", i, props.iGPU)) // DOESN'T REPORT CORRECTLY! Always 0
|
|
|
|
// TODO Why isn't props.iGPU accurate!?
|
|
|
|
if strings.EqualFold(name, iGPUName) {
|
2024-05-07 21:54:26 +00:00
|
|
|
slog.Info("unsupported Radeon iGPU detected skipping", "id", i, "name", name, "gfx", gfx)
|
2024-02-16 01:15:09 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if gfxOverride == "" {
|
|
|
|
if !slices.Contains[[]string, string](supported, gfx) {
|
2024-03-30 16:50:05 +00:00
|
|
|
slog.Warn("amdgpu is not supported", "gpu", i, "gpu_type", gfx, "library", libDir, "supported_types", supported)
|
2024-02-16 01:15:09 +00:00
|
|
|
// TODO - consider discrete markdown just for ROCM troubleshooting?
|
|
|
|
slog.Warn("See https://github.com/ollama/ollama/blob/main/docs/troubleshooting.md for HSA_OVERRIDE_GFX_VERSION usage")
|
|
|
|
continue
|
|
|
|
} else {
|
2024-05-07 21:54:26 +00:00
|
|
|
slog.Debug("amdgpu is supported", "gpu", i, "gpu_type", gfx)
|
2024-02-16 01:15:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-30 16:50:05 +00:00
|
|
|
freeMemory, totalMemory, err := hl.HipMemGetInfo()
|
2024-02-16 01:15:09 +00:00
|
|
|
if err != nil {
|
2024-03-30 16:50:05 +00:00
|
|
|
slog.Warn("get mem info", "id", i, "error", err)
|
2024-02-16 01:15:09 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-03-30 16:50:05 +00:00
|
|
|
// iGPU detection, remove this check once we can support an iGPU variant of the rocm library
|
|
|
|
if totalMemory < IGPUMemLimit {
|
|
|
|
slog.Info("amdgpu appears to be an iGPU, skipping", "gpu", i, "total", format.HumanBytes2(totalMemory))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-05-07 21:54:26 +00:00
|
|
|
slog.Debug("amdgpu memory", "gpu", i, "total", format.HumanBytes2(totalMemory))
|
|
|
|
slog.Debug("amdgpu memory", "gpu", i, "available", format.HumanBytes2(freeMemory))
|
2024-05-15 22:13:16 +00:00
|
|
|
gpuInfo := RocmGPUInfo{
|
|
|
|
GpuInfo: GpuInfo{
|
|
|
|
Library: "rocm",
|
|
|
|
memInfo: memInfo{
|
|
|
|
TotalMemory: totalMemory,
|
|
|
|
FreeMemory: freeMemory,
|
|
|
|
},
|
2024-06-19 20:35:38 +00:00
|
|
|
// Free memory reporting on Windows is not reliable until we bump to ROCm v6.2
|
|
|
|
UnreliableFreeMemory: true,
|
|
|
|
|
2024-06-05 19:07:20 +00:00
|
|
|
ID: strconv.Itoa(i), // TODO this is probably wrong if we specify visible devices
|
2024-05-15 22:13:16 +00:00
|
|
|
DependencyPath: libDir,
|
|
|
|
MinimumMemory: rocmMinimumMemory,
|
|
|
|
Name: name,
|
|
|
|
Compute: gfx,
|
2024-06-18 23:22:47 +00:00
|
|
|
DriverMajor: driverMajor,
|
|
|
|
DriverMinor: driverMinor,
|
2024-03-30 16:50:05 +00:00
|
|
|
},
|
2024-05-15 22:13:16 +00:00
|
|
|
index: i,
|
2024-03-30 16:50:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
resp = append(resp, gpuInfo)
|
2024-02-16 01:15:09 +00:00
|
|
|
}
|
2024-03-30 16:50:05 +00:00
|
|
|
|
|
|
|
return resp
|
2024-02-16 01:15:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func AMDValidateLibDir() (string, error) {
|
2024-03-30 16:50:05 +00:00
|
|
|
libDir, err := commonAMDValidateLibDir()
|
2024-02-16 01:15:09 +00:00
|
|
|
if err == nil {
|
2024-03-30 16:50:05 +00:00
|
|
|
return libDir, nil
|
2024-02-16 01:15:09 +00:00
|
|
|
}
|
|
|
|
|
2024-03-08 17:45:55 +00:00
|
|
|
// Installer payload (if we're running from some other location)
|
|
|
|
localAppData := os.Getenv("LOCALAPPDATA")
|
|
|
|
appDir := filepath.Join(localAppData, "Programs", "Ollama")
|
|
|
|
rocmTargetDir := filepath.Join(appDir, "rocm")
|
2024-02-16 01:15:09 +00:00
|
|
|
if rocmLibUsable(rocmTargetDir) {
|
2024-03-08 17:45:55 +00:00
|
|
|
slog.Debug("detected ollama installed ROCm at " + rocmTargetDir)
|
2024-02-16 01:15:09 +00:00
|
|
|
return rocmTargetDir, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should not happen on windows since we include it in the installer, but stand-alone binary might hit this
|
2024-03-08 17:45:55 +00:00
|
|
|
slog.Warn("amdgpu detected, but no compatible rocm library found. Please install ROCm")
|
2024-02-16 01:15:09 +00:00
|
|
|
return "", fmt.Errorf("no suitable rocm found, falling back to CPU")
|
|
|
|
}
|
2024-05-15 22:13:16 +00:00
|
|
|
|
|
|
|
func (gpus RocmGPUInfoList) RefreshFreeMemory() error {
|
|
|
|
if len(gpus) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
hl, err := NewHipLib()
|
|
|
|
if err != nil {
|
|
|
|
slog.Debug(err.Error())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
defer hl.Release()
|
|
|
|
|
|
|
|
for i := range gpus {
|
|
|
|
err := hl.HipSetDevice(gpus[i].index)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
freeMemory, _, err := hl.HipMemGetInfo()
|
|
|
|
if err != nil {
|
|
|
|
slog.Warn("get mem info", "id", i, "error", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
slog.Debug("updating rocm free memory", "gpu", gpus[i].ID, "name", gpus[i].Name, "before", format.HumanBytes2(gpus[i].FreeMemory), "now", format.HumanBytes2(freeMemory))
|
|
|
|
gpus[i].FreeMemory = freeMemory
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|