Skip to content

Commit ffb2dc4

Browse files
authored
chore(detection): detect GPU vendor from files present in the system (#7908)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent cfc2225 commit ffb2dc4

3 files changed

Lines changed: 93 additions & 37 deletions

File tree

pkg/system/capabilities.go

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"runtime"
99
"strings"
1010

11-
"github.com/jaypipes/ghw/pkg/gpu"
1211
"github.com/mudler/xlog"
1312
)
1413

@@ -19,8 +18,9 @@ const (
1918
metal = "metal"
2019
nvidia = "nvidia"
2120

22-
amd = "amd"
23-
intel = "intel"
21+
amd = "amd"
22+
intel = "intel"
23+
vulkan = "vulkan"
2424

2525
nvidiaCuda13 = "nvidia-cuda-13"
2626
nvidiaCuda12 = "nvidia-cuda-12"
@@ -131,26 +131,6 @@ func (s *SystemState) getSystemCapabilities() string {
131131
return s.GPUVendor
132132
}
133133

134-
func detectGPUVendor(gpus []*gpu.GraphicsCard) (string, error) {
135-
for _, gpu := range gpus {
136-
if gpu.DeviceInfo != nil {
137-
if gpu.DeviceInfo.Vendor != nil {
138-
gpuVendorName := strings.ToUpper(gpu.DeviceInfo.Vendor.Name)
139-
if strings.Contains(gpuVendorName, strings.ToUpper(nvidia)) {
140-
return nvidia, nil
141-
}
142-
if strings.Contains(gpuVendorName, strings.ToUpper(amd)) {
143-
return amd, nil
144-
}
145-
if strings.Contains(gpuVendorName, strings.ToUpper(intel)) {
146-
return intel, nil
147-
}
148-
}
149-
}
150-
}
151-
152-
return "", nil
153-
}
154134

155135
// BackendPreferenceTokens returns a list of substrings that represent the preferred
156136
// backend implementation order for the current system capability. Callers can use
@@ -169,6 +149,8 @@ func (s *SystemState) BackendPreferenceTokens() []string {
169149
return []string{"metal", "cpu"}
170150
case strings.HasPrefix(capStr, darwinX86):
171151
return []string{"darwin-x86", "cpu"}
152+
case strings.HasPrefix(capStr, vulkan):
153+
return []string{"vulkan", "cpu"}
172154
default:
173155
return []string{"cpu"}
174156
}

pkg/system/state.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package system
22

33
import (
4-
"github.com/jaypipes/ghw/pkg/gpu"
54
"github.com/mudler/LocalAI/pkg/xsysinfo"
65
"github.com/mudler/xlog"
76
)
@@ -19,7 +18,6 @@ type SystemState struct {
1918
GPUVendor string
2019
Backend Backend
2120
Model Model
22-
gpus []*gpu.GraphicsCard
2321
VRAM uint64
2422
}
2523

@@ -50,9 +48,7 @@ func GetSystemState(opts ...SystemStateOptions) (*SystemState, error) {
5048
}
5149

5250
// Detection is best-effort here, we don't want to fail if it fails
53-
state.gpus, _ = xsysinfo.GPUs()
54-
xlog.Debug("GPUs", "gpus", state.gpus)
55-
state.GPUVendor, _ = detectGPUVendor(state.gpus)
51+
state.GPUVendor, _ = xsysinfo.DetectGPUVendor()
5652
xlog.Debug("GPU vendor", "gpuVendor", state.GPUVendor)
5753
state.VRAM, _ = xsysinfo.TotalAvailableVRAM()
5854
xlog.Debug("Total available VRAM", "vram", state.VRAM)

pkg/xsysinfo/gpu.go

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,39 @@ func GPUs() ([]*gpu.GraphicsCard, error) {
8989
}
9090

9191
func TotalAvailableVRAM() (uint64, error) {
92+
// First, try ghw library detection
9293
gpus, err := GPUs()
93-
if err != nil {
94-
return 0, err
94+
if err == nil {
95+
var totalVRAM uint64
96+
for _, gpu := range gpus {
97+
if gpu != nil && gpu.Node != nil && gpu.Node.Memory != nil {
98+
if gpu.Node.Memory.TotalUsableBytes > 0 {
99+
totalVRAM += uint64(gpu.Node.Memory.TotalUsableBytes)
100+
}
101+
}
102+
}
103+
// If we got valid VRAM from ghw, return it
104+
if totalVRAM > 0 {
105+
return totalVRAM, nil
106+
}
95107
}
96108

97-
var totalVRAM uint64
98-
for _, gpu := range gpus {
99-
if gpu != nil && gpu.Node != nil && gpu.Node.Memory != nil {
100-
if gpu.Node.Memory.TotalUsableBytes > 0 {
101-
totalVRAM += uint64(gpu.Node.Memory.TotalUsableBytes)
102-
}
109+
// Fallback to binary-based detection via GetGPUMemoryUsage()
110+
// This works even when ghw dependencies are missing from the base image
111+
gpuMemoryInfo := GetGPUMemoryUsage()
112+
if len(gpuMemoryInfo) > 0 {
113+
var totalVRAM uint64
114+
for _, gpu := range gpuMemoryInfo {
115+
totalVRAM += gpu.TotalVRAM
116+
}
117+
if totalVRAM > 0 {
118+
xlog.Debug("VRAM detected via binary tools", "total_vram", totalVRAM)
119+
return totalVRAM, nil
103120
}
104121
}
105122

106-
return totalVRAM, nil
123+
// No VRAM detected
124+
return 0, nil
107125
}
108126

109127
func HasGPU(vendor string) bool {
@@ -122,6 +140,66 @@ func HasGPU(vendor string) bool {
122140
return false
123141
}
124142

143+
// DetectGPUVendor detects the GPU vendor using multiple methods with fallbacks.
144+
// First tries ghw library, then falls back to binary detection.
145+
// Returns vendor string (VendorNVIDIA, VendorAMD, VendorIntel, VendorVulkan) or empty string if not detected.
146+
// Priority order: NVIDIA > AMD > Intel > Vulkan
147+
func DetectGPUVendor() (string, error) {
148+
// First, try ghw library detection
149+
gpus, err := GPUs()
150+
if err == nil && len(gpus) > 0 {
151+
for _, gpu := range gpus {
152+
if gpu.DeviceInfo != nil && gpu.DeviceInfo.Vendor != nil {
153+
vendorName := strings.ToUpper(gpu.DeviceInfo.Vendor.Name)
154+
if strings.Contains(vendorName, strings.ToUpper(VendorNVIDIA)) {
155+
xlog.Debug("GPU vendor detected via ghw", "vendor", VendorNVIDIA)
156+
return VendorNVIDIA, nil
157+
}
158+
if strings.Contains(vendorName, strings.ToUpper(VendorAMD)) {
159+
xlog.Debug("GPU vendor detected via ghw", "vendor", VendorAMD)
160+
return VendorAMD, nil
161+
}
162+
if strings.Contains(vendorName, strings.ToUpper(VendorIntel)) {
163+
xlog.Debug("GPU vendor detected via ghw", "vendor", VendorIntel)
164+
return VendorIntel, nil
165+
}
166+
}
167+
}
168+
}
169+
170+
// Fallback to binary detection (priority: NVIDIA > AMD > Intel > Vulkan)
171+
// Check for nvidia-smi
172+
if _, err := exec.LookPath("nvidia-smi"); err == nil {
173+
xlog.Debug("GPU vendor detected via binary", "vendor", VendorNVIDIA, "binary", "nvidia-smi")
174+
return VendorNVIDIA, nil
175+
}
176+
177+
// Check for rocm-smi (AMD)
178+
if _, err := exec.LookPath("rocm-smi"); err == nil {
179+
xlog.Debug("GPU vendor detected via binary", "vendor", VendorAMD, "binary", "rocm-smi")
180+
return VendorAMD, nil
181+
}
182+
183+
// Check for xpu-smi or intel_gpu_top (Intel)
184+
if _, err := exec.LookPath("xpu-smi"); err == nil {
185+
xlog.Debug("GPU vendor detected via binary", "vendor", VendorIntel, "binary", "xpu-smi")
186+
return VendorIntel, nil
187+
}
188+
if _, err := exec.LookPath("intel_gpu_top"); err == nil {
189+
xlog.Debug("GPU vendor detected via binary", "vendor", VendorIntel, "binary", "intel_gpu_top")
190+
return VendorIntel, nil
191+
}
192+
193+
// Check for vulkaninfo (Vulkan - lowest priority as it can detect any GPU)
194+
if _, err := exec.LookPath("vulkaninfo"); err == nil {
195+
xlog.Debug("GPU vendor detected via binary", "vendor", VendorVulkan, "binary", "vulkaninfo")
196+
return VendorVulkan, nil
197+
}
198+
199+
// No vendor detected
200+
return "", nil
201+
}
202+
125203
// isUnifiedMemoryDevice checks if the given GPU name matches any known unified memory device
126204
func isUnifiedMemoryDevice(gpuName string) bool {
127205
gpuNameUpper := strings.ToUpper(gpuName)

0 commit comments

Comments
 (0)