From ffb2dc46660b397b8ab516101a034e1f0073ce26 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 7 Jan 2026 16:18:27 +0100 Subject: [PATCH] chore(detection): detect GPU vendor from files present in the system (#7908) Signed-off-by: Ettore Di Giacinto --- pkg/system/capabilities.go | 28 ++--------- pkg/system/state.go | 6 +-- pkg/xsysinfo/gpu.go | 98 ++++++++++++++++++++++++++++++++++---- 3 files changed, 94 insertions(+), 38 deletions(-) diff --git a/pkg/system/capabilities.go b/pkg/system/capabilities.go index 0713cc349..9984dce62 100644 --- a/pkg/system/capabilities.go +++ b/pkg/system/capabilities.go @@ -8,7 +8,6 @@ import ( "runtime" "strings" - "github.com/jaypipes/ghw/pkg/gpu" "github.com/mudler/xlog" ) @@ -19,8 +18,9 @@ const ( metal = "metal" nvidia = "nvidia" - amd = "amd" - intel = "intel" + amd = "amd" + intel = "intel" + vulkan = "vulkan" nvidiaCuda13 = "nvidia-cuda-13" nvidiaCuda12 = "nvidia-cuda-12" @@ -131,26 +131,6 @@ func (s *SystemState) getSystemCapabilities() string { return s.GPUVendor } -func detectGPUVendor(gpus []*gpu.GraphicsCard) (string, error) { - for _, gpu := range gpus { - if gpu.DeviceInfo != nil { - if gpu.DeviceInfo.Vendor != nil { - gpuVendorName := strings.ToUpper(gpu.DeviceInfo.Vendor.Name) - if strings.Contains(gpuVendorName, strings.ToUpper(nvidia)) { - return nvidia, nil - } - if strings.Contains(gpuVendorName, strings.ToUpper(amd)) { - return amd, nil - } - if strings.Contains(gpuVendorName, strings.ToUpper(intel)) { - return intel, nil - } - } - } - } - - return "", nil -} // BackendPreferenceTokens returns a list of substrings that represent the preferred // backend implementation order for the current system capability. Callers can use @@ -169,6 +149,8 @@ func (s *SystemState) BackendPreferenceTokens() []string { return []string{"metal", "cpu"} case strings.HasPrefix(capStr, darwinX86): return []string{"darwin-x86", "cpu"} + case strings.HasPrefix(capStr, vulkan): + return []string{"vulkan", "cpu"} default: return []string{"cpu"} } diff --git a/pkg/system/state.go b/pkg/system/state.go index c4afc47f6..7c6d8b724 100644 --- a/pkg/system/state.go +++ b/pkg/system/state.go @@ -1,7 +1,6 @@ package system import ( - "github.com/jaypipes/ghw/pkg/gpu" "github.com/mudler/LocalAI/pkg/xsysinfo" "github.com/mudler/xlog" ) @@ -19,7 +18,6 @@ type SystemState struct { GPUVendor string Backend Backend Model Model - gpus []*gpu.GraphicsCard VRAM uint64 } @@ -50,9 +48,7 @@ func GetSystemState(opts ...SystemStateOptions) (*SystemState, error) { } // Detection is best-effort here, we don't want to fail if it fails - state.gpus, _ = xsysinfo.GPUs() - xlog.Debug("GPUs", "gpus", state.gpus) - state.GPUVendor, _ = detectGPUVendor(state.gpus) + state.GPUVendor, _ = xsysinfo.DetectGPUVendor() xlog.Debug("GPU vendor", "gpuVendor", state.GPUVendor) state.VRAM, _ = xsysinfo.TotalAvailableVRAM() xlog.Debug("Total available VRAM", "vram", state.VRAM) diff --git a/pkg/xsysinfo/gpu.go b/pkg/xsysinfo/gpu.go index 43021aa5c..dcda6c4e6 100644 --- a/pkg/xsysinfo/gpu.go +++ b/pkg/xsysinfo/gpu.go @@ -89,21 +89,39 @@ func GPUs() ([]*gpu.GraphicsCard, error) { } func TotalAvailableVRAM() (uint64, error) { + // First, try ghw library detection gpus, err := GPUs() - if err != nil { - return 0, err - } - - var totalVRAM uint64 - for _, gpu := range gpus { - if gpu != nil && gpu.Node != nil && gpu.Node.Memory != nil { - if gpu.Node.Memory.TotalUsableBytes > 0 { - totalVRAM += uint64(gpu.Node.Memory.TotalUsableBytes) + if err == nil { + var totalVRAM uint64 + for _, gpu := range gpus { + if gpu != nil && gpu.Node != nil && gpu.Node.Memory != nil { + if gpu.Node.Memory.TotalUsableBytes > 0 { + totalVRAM += uint64(gpu.Node.Memory.TotalUsableBytes) + } } } + // If we got valid VRAM from ghw, return it + if totalVRAM > 0 { + return totalVRAM, nil + } } - return totalVRAM, nil + // Fallback to binary-based detection via GetGPUMemoryUsage() + // This works even when ghw dependencies are missing from the base image + gpuMemoryInfo := GetGPUMemoryUsage() + if len(gpuMemoryInfo) > 0 { + var totalVRAM uint64 + for _, gpu := range gpuMemoryInfo { + totalVRAM += gpu.TotalVRAM + } + if totalVRAM > 0 { + xlog.Debug("VRAM detected via binary tools", "total_vram", totalVRAM) + return totalVRAM, nil + } + } + + // No VRAM detected + return 0, nil } func HasGPU(vendor string) bool { @@ -122,6 +140,66 @@ func HasGPU(vendor string) bool { return false } +// DetectGPUVendor detects the GPU vendor using multiple methods with fallbacks. +// First tries ghw library, then falls back to binary detection. +// Returns vendor string (VendorNVIDIA, VendorAMD, VendorIntel, VendorVulkan) or empty string if not detected. +// Priority order: NVIDIA > AMD > Intel > Vulkan +func DetectGPUVendor() (string, error) { + // First, try ghw library detection + gpus, err := GPUs() + if err == nil && len(gpus) > 0 { + for _, gpu := range gpus { + if gpu.DeviceInfo != nil && gpu.DeviceInfo.Vendor != nil { + vendorName := strings.ToUpper(gpu.DeviceInfo.Vendor.Name) + if strings.Contains(vendorName, strings.ToUpper(VendorNVIDIA)) { + xlog.Debug("GPU vendor detected via ghw", "vendor", VendorNVIDIA) + return VendorNVIDIA, nil + } + if strings.Contains(vendorName, strings.ToUpper(VendorAMD)) { + xlog.Debug("GPU vendor detected via ghw", "vendor", VendorAMD) + return VendorAMD, nil + } + if strings.Contains(vendorName, strings.ToUpper(VendorIntel)) { + xlog.Debug("GPU vendor detected via ghw", "vendor", VendorIntel) + return VendorIntel, nil + } + } + } + } + + // Fallback to binary detection (priority: NVIDIA > AMD > Intel > Vulkan) + // Check for nvidia-smi + if _, err := exec.LookPath("nvidia-smi"); err == nil { + xlog.Debug("GPU vendor detected via binary", "vendor", VendorNVIDIA, "binary", "nvidia-smi") + return VendorNVIDIA, nil + } + + // Check for rocm-smi (AMD) + if _, err := exec.LookPath("rocm-smi"); err == nil { + xlog.Debug("GPU vendor detected via binary", "vendor", VendorAMD, "binary", "rocm-smi") + return VendorAMD, nil + } + + // Check for xpu-smi or intel_gpu_top (Intel) + if _, err := exec.LookPath("xpu-smi"); err == nil { + xlog.Debug("GPU vendor detected via binary", "vendor", VendorIntel, "binary", "xpu-smi") + return VendorIntel, nil + } + if _, err := exec.LookPath("intel_gpu_top"); err == nil { + xlog.Debug("GPU vendor detected via binary", "vendor", VendorIntel, "binary", "intel_gpu_top") + return VendorIntel, nil + } + + // Check for vulkaninfo (Vulkan - lowest priority as it can detect any GPU) + if _, err := exec.LookPath("vulkaninfo"); err == nil { + xlog.Debug("GPU vendor detected via binary", "vendor", VendorVulkan, "binary", "vulkaninfo") + return VendorVulkan, nil + } + + // No vendor detected + return "", nil +} + // isUnifiedMemoryDevice checks if the given GPU name matches any known unified memory device func isUnifiedMemoryDevice(gpuName string) bool { gpuNameUpper := strings.ToUpper(gpuName)