mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-17 21:40:07 -04:00
fix(gpu): better detection for MacOS and Thor (#9263)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
committed by
GitHub
parent
17215f6fbc
commit
505c417fa7
1
core/http/react-ui/src/utils/format.js
vendored
1
core/http/react-ui/src/utils/format.js
vendored
@@ -42,5 +42,6 @@ export function vendorColor(vendor) {
|
||||
if (v.includes('nvidia')) return '#76b900'
|
||||
if (v.includes('amd')) return '#ed1c24'
|
||||
if (v.includes('intel')) return '#0071c5'
|
||||
if (v.includes('apple')) return '#a2aaad'
|
||||
return 'var(--color-accent)'
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -39,7 +39,7 @@ require (
|
||||
github.com/mudler/cogito v0.9.5-0.20260315222927-63abdec7189b
|
||||
github.com/mudler/edgevpn v0.31.1
|
||||
github.com/mudler/go-processmanager v0.1.0
|
||||
github.com/mudler/memory v0.0.0-20251216220809-d1256471a6c2
|
||||
github.com/mudler/memory v0.0.0-20260406210934-424c1ecf2cf8
|
||||
github.com/mudler/xlog v0.0.6
|
||||
github.com/nats-io/nats.go v1.50.0
|
||||
github.com/onsi/ginkgo/v2 v2.28.1
|
||||
|
||||
2
go.sum
2
go.sum
@@ -723,6 +723,8 @@ github.com/mudler/localrecall v0.5.9-0.20260321005011-810084e9369b h1:XeAnOEOOSK
|
||||
github.com/mudler/localrecall v0.5.9-0.20260321005011-810084e9369b/go.mod h1:xuPtgL9zUyiQLmspYzO3kaboYrGbWmwi8BQPt1aCAcs=
|
||||
github.com/mudler/memory v0.0.0-20251216220809-d1256471a6c2 h1:+WHsL/j6EWOMUiMVIOJNKOwSKiQt/qDPc9fePCf87fA=
|
||||
github.com/mudler/memory v0.0.0-20251216220809-d1256471a6c2/go.mod h1:EA8Ashhd56o32qN7ouPKFSRUs/Z+LrRCF4v6R2Oarm8=
|
||||
github.com/mudler/memory v0.0.0-20260406210934-424c1ecf2cf8 h1:Ry8RiWy8fZ6Ff4E7dPmjRsBrnHOnPeOOj2LhCgyjQu0=
|
||||
github.com/mudler/memory v0.0.0-20260406210934-424c1ecf2cf8/go.mod h1:EA8Ashhd56o32qN7ouPKFSRUs/Z+LrRCF4v6R2Oarm8=
|
||||
github.com/mudler/skillserver v0.0.6 h1:ixz6wUekLdTmbnpAavCkTydDF6UdXAG3ncYufSPK9G0=
|
||||
github.com/mudler/skillserver v0.0.6/go.mod h1:z3yFhcL9bSykmmh6xgGu0hyoItd4CnxgtWMEWw8uFJU=
|
||||
github.com/mudler/water v0.0.0-20250808092830-dd90dcf09025 h1:WFLP5FHInarYGXi6B/Ze204x7Xy6q/I4nCZnWEyPHK0=
|
||||
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
VendorNVIDIA = "nvidia"
|
||||
VendorAMD = "amd"
|
||||
VendorIntel = "intel"
|
||||
VendorApple = "apple"
|
||||
VendorVulkan = "vulkan"
|
||||
VendorUnknown = "unknown"
|
||||
)
|
||||
@@ -29,7 +30,8 @@ const (
|
||||
var UnifiedMemoryDevices = []string{
|
||||
"NVIDIA GB10",
|
||||
"GB10",
|
||||
// Add more unified memory devices here as needed
|
||||
"NVIDIA Thor",
|
||||
"Thor",
|
||||
}
|
||||
|
||||
// GPUMemoryInfo contains real-time GPU memory usage information
|
||||
@@ -196,6 +198,12 @@ func DetectGPUVendor() (string, error) {
|
||||
return VendorVulkan, nil
|
||||
}
|
||||
|
||||
// Check for Apple Silicon (macOS)
|
||||
if appleGPUs := getAppleGPUMemory(); len(appleGPUs) > 0 {
|
||||
xlog.Debug("GPU vendor detected via system_profiler", "vendor", VendorApple)
|
||||
return VendorApple, nil
|
||||
}
|
||||
|
||||
// No vendor detected
|
||||
return "", nil
|
||||
}
|
||||
@@ -258,6 +266,12 @@ func GetGPUMemoryUsage() []GPUMemoryInfo {
|
||||
gpus = append(gpus, vulkanGPUs...)
|
||||
}
|
||||
|
||||
// Try Apple Silicon (macOS only)
|
||||
if len(gpus) == 0 {
|
||||
appleGPUs := getAppleGPUMemory()
|
||||
gpus = append(gpus, appleGPUs...)
|
||||
}
|
||||
|
||||
return gpus
|
||||
}
|
||||
|
||||
@@ -351,18 +365,44 @@ func getNVIDIAGPUMemory() []GPUMemoryInfo {
|
||||
usagePercent = float64(usedBytes) / float64(totalBytes) * 100
|
||||
}
|
||||
} else if isNA {
|
||||
// Unknown device with N/A values - skip memory info
|
||||
xlog.Debug("nvidia-smi returned N/A for unknown device", "device", name)
|
||||
gpus = append(gpus, GPUMemoryInfo{
|
||||
Index: idx,
|
||||
Name: name,
|
||||
Vendor: VendorNVIDIA,
|
||||
TotalVRAM: 0,
|
||||
UsedVRAM: 0,
|
||||
FreeVRAM: 0,
|
||||
UsagePercent: 0,
|
||||
})
|
||||
continue
|
||||
// Check if this is a Tegra/Jetson device — if so, it uses unified memory
|
||||
if isTegraDevice() {
|
||||
xlog.Debug("nvidia-smi returned N/A on Tegra device, using system RAM", "device", name)
|
||||
sysInfo, err := GetSystemRAMInfo()
|
||||
if err != nil {
|
||||
xlog.Debug("failed to get system RAM for Tegra device", "error", err, "device", name)
|
||||
gpus = append(gpus, GPUMemoryInfo{
|
||||
Index: idx,
|
||||
Name: name,
|
||||
Vendor: VendorNVIDIA,
|
||||
TotalVRAM: 0,
|
||||
UsedVRAM: 0,
|
||||
FreeVRAM: 0,
|
||||
UsagePercent: 0,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
totalBytes = sysInfo.Total
|
||||
usedBytes = sysInfo.Used
|
||||
freeBytes = sysInfo.Free
|
||||
if totalBytes > 0 {
|
||||
usagePercent = float64(usedBytes) / float64(totalBytes) * 100
|
||||
}
|
||||
} else {
|
||||
// Truly unknown device with N/A values - skip memory info
|
||||
xlog.Debug("nvidia-smi returned N/A for unknown device", "device", name)
|
||||
gpus = append(gpus, GPUMemoryInfo{
|
||||
Index: idx,
|
||||
Name: name,
|
||||
Vendor: VendorNVIDIA,
|
||||
TotalVRAM: 0,
|
||||
UsedVRAM: 0,
|
||||
FreeVRAM: 0,
|
||||
UsagePercent: 0,
|
||||
})
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Normal GPU with dedicated VRAM
|
||||
totalMB, _ := strconv.ParseFloat(totalStr, 64)
|
||||
@@ -790,3 +830,84 @@ func getVulkanGPUMemory() []GPUMemoryInfo {
|
||||
|
||||
return gpus
|
||||
}
|
||||
|
||||
// getAppleGPUMemory detects Apple Silicon GPUs using system_profiler (macOS only).
|
||||
// Apple Silicon uses unified memory, so GPU memory is reported as system RAM.
|
||||
func getAppleGPUMemory() []GPUMemoryInfo {
|
||||
if _, err := exec.LookPath("system_profiler"); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := exec.Command("system_profiler", "SPDisplaysDataType", "-json")
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
xlog.Debug("system_profiler failed", "error", err, "stderr", stderr.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
var result struct {
|
||||
SPDisplaysDataType []struct {
|
||||
Name string `json:"_name"`
|
||||
Model string `json:"sppci_model"`
|
||||
Cores string `json:"sppci_cores"`
|
||||
DeviceType string `json:"sppci_device_type"`
|
||||
Vendor string `json:"spdisplays_vendor"`
|
||||
} `json:"SPDisplaysDataType"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
|
||||
xlog.Debug("failed to parse system_profiler output", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var gpus []GPUMemoryInfo
|
||||
for i, display := range result.SPDisplaysDataType {
|
||||
if display.DeviceType != "spdisplays_gpu" {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(display.Vendor), "apple") {
|
||||
continue
|
||||
}
|
||||
|
||||
name := display.Model
|
||||
if name == "" {
|
||||
name = display.Name
|
||||
}
|
||||
if name == "" {
|
||||
name = "Apple GPU"
|
||||
}
|
||||
|
||||
// Apple Silicon uses unified memory — report system RAM
|
||||
ramInfo, err := GetSystemRAMInfo()
|
||||
if err != nil {
|
||||
xlog.Debug("Apple GPU detected but failed to get system RAM", "error", err)
|
||||
gpus = append(gpus, GPUMemoryInfo{
|
||||
Index: i,
|
||||
Name: name,
|
||||
Vendor: VendorApple,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
usagePercent := 0.0
|
||||
if ramInfo.Total > 0 {
|
||||
usagePercent = float64(ramInfo.Used) / float64(ramInfo.Total) * 100
|
||||
}
|
||||
|
||||
xlog.Debug("Apple Silicon GPU detected (unified memory)", "device", name, "total_ram", ramInfo.Total)
|
||||
gpus = append(gpus, GPUMemoryInfo{
|
||||
Index: i,
|
||||
Name: name,
|
||||
Vendor: VendorApple,
|
||||
TotalVRAM: ramInfo.Total,
|
||||
UsedVRAM: ramInfo.Used,
|
||||
FreeVRAM: ramInfo.Free,
|
||||
UsagePercent: usagePercent,
|
||||
})
|
||||
}
|
||||
|
||||
return gpus
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user