Skip to content

Commit 505c417

Browse files
authored
fix(gpu): better detection for MacOS and Thor (#9263)
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 17215f6 commit 505c417

4 files changed

Lines changed: 138 additions & 14 deletions

File tree

core/http/react-ui/src/utils/format.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@ export function vendorColor(vendor) {
4242
if (v.includes('nvidia')) return '#76b900'
4343
if (v.includes('amd')) return '#ed1c24'
4444
if (v.includes('intel')) return '#0071c5'
45+
if (v.includes('apple')) return '#a2aaad'
4546
return 'var(--color-accent)'
4647
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ require (
3939
github.com/mudler/cogito v0.9.5-0.20260315222927-63abdec7189b
4040
github.com/mudler/edgevpn v0.31.1
4141
github.com/mudler/go-processmanager v0.1.0
42-
github.com/mudler/memory v0.0.0-20251216220809-d1256471a6c2
42+
github.com/mudler/memory v0.0.0-20260406210934-424c1ecf2cf8
4343
github.com/mudler/xlog v0.0.6
4444
github.com/nats-io/nats.go v1.50.0
4545
github.com/onsi/ginkgo/v2 v2.28.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,8 @@ github.com/mudler/localrecall v0.5.9-0.20260321005011-810084e9369b h1:XeAnOEOOSK
723723
github.com/mudler/localrecall v0.5.9-0.20260321005011-810084e9369b/go.mod h1:xuPtgL9zUyiQLmspYzO3kaboYrGbWmwi8BQPt1aCAcs=
724724
github.com/mudler/memory v0.0.0-20251216220809-d1256471a6c2 h1:+WHsL/j6EWOMUiMVIOJNKOwSKiQt/qDPc9fePCf87fA=
725725
github.com/mudler/memory v0.0.0-20251216220809-d1256471a6c2/go.mod h1:EA8Ashhd56o32qN7ouPKFSRUs/Z+LrRCF4v6R2Oarm8=
726+
github.com/mudler/memory v0.0.0-20260406210934-424c1ecf2cf8 h1:Ry8RiWy8fZ6Ff4E7dPmjRsBrnHOnPeOOj2LhCgyjQu0=
727+
github.com/mudler/memory v0.0.0-20260406210934-424c1ecf2cf8/go.mod h1:EA8Ashhd56o32qN7ouPKFSRUs/Z+LrRCF4v6R2Oarm8=
726728
github.com/mudler/skillserver v0.0.6 h1:ixz6wUekLdTmbnpAavCkTydDF6UdXAG3ncYufSPK9G0=
727729
github.com/mudler/skillserver v0.0.6/go.mod h1:z3yFhcL9bSykmmh6xgGu0hyoItd4CnxgtWMEWw8uFJU=
728730
github.com/mudler/water v0.0.0-20250808092830-dd90dcf09025 h1:WFLP5FHInarYGXi6B/Ze204x7Xy6q/I4nCZnWEyPHK0=

pkg/xsysinfo/gpu.go

Lines changed: 134 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const (
1919
VendorNVIDIA = "nvidia"
2020
VendorAMD = "amd"
2121
VendorIntel = "intel"
22+
VendorApple = "apple"
2223
VendorVulkan = "vulkan"
2324
VendorUnknown = "unknown"
2425
)
@@ -29,7 +30,8 @@ const (
2930
var UnifiedMemoryDevices = []string{
3031
"NVIDIA GB10",
3132
"GB10",
32-
// Add more unified memory devices here as needed
33+
"NVIDIA Thor",
34+
"Thor",
3335
}
3436

3537
// GPUMemoryInfo contains real-time GPU memory usage information
@@ -196,6 +198,12 @@ func DetectGPUVendor() (string, error) {
196198
return VendorVulkan, nil
197199
}
198200

201+
// Check for Apple Silicon (macOS)
202+
if appleGPUs := getAppleGPUMemory(); len(appleGPUs) > 0 {
203+
xlog.Debug("GPU vendor detected via system_profiler", "vendor", VendorApple)
204+
return VendorApple, nil
205+
}
206+
199207
// No vendor detected
200208
return "", nil
201209
}
@@ -258,6 +266,12 @@ func GetGPUMemoryUsage() []GPUMemoryInfo {
258266
gpus = append(gpus, vulkanGPUs...)
259267
}
260268

269+
// Try Apple Silicon (macOS only)
270+
if len(gpus) == 0 {
271+
appleGPUs := getAppleGPUMemory()
272+
gpus = append(gpus, appleGPUs...)
273+
}
274+
261275
return gpus
262276
}
263277

@@ -351,18 +365,44 @@ func getNVIDIAGPUMemory() []GPUMemoryInfo {
351365
usagePercent = float64(usedBytes) / float64(totalBytes) * 100
352366
}
353367
} else if isNA {
354-
// Unknown device with N/A values - skip memory info
355-
xlog.Debug("nvidia-smi returned N/A for unknown device", "device", name)
356-
gpus = append(gpus, GPUMemoryInfo{
357-
Index: idx,
358-
Name: name,
359-
Vendor: VendorNVIDIA,
360-
TotalVRAM: 0,
361-
UsedVRAM: 0,
362-
FreeVRAM: 0,
363-
UsagePercent: 0,
364-
})
365-
continue
368+
// Check if this is a Tegra/Jetson device — if so, it uses unified memory
369+
if isTegraDevice() {
370+
xlog.Debug("nvidia-smi returned N/A on Tegra device, using system RAM", "device", name)
371+
sysInfo, err := GetSystemRAMInfo()
372+
if err != nil {
373+
xlog.Debug("failed to get system RAM for Tegra device", "error", err, "device", name)
374+
gpus = append(gpus, GPUMemoryInfo{
375+
Index: idx,
376+
Name: name,
377+
Vendor: VendorNVIDIA,
378+
TotalVRAM: 0,
379+
UsedVRAM: 0,
380+
FreeVRAM: 0,
381+
UsagePercent: 0,
382+
})
383+
continue
384+
}
385+
386+
totalBytes = sysInfo.Total
387+
usedBytes = sysInfo.Used
388+
freeBytes = sysInfo.Free
389+
if totalBytes > 0 {
390+
usagePercent = float64(usedBytes) / float64(totalBytes) * 100
391+
}
392+
} else {
393+
// Truly unknown device with N/A values - skip memory info
394+
xlog.Debug("nvidia-smi returned N/A for unknown device", "device", name)
395+
gpus = append(gpus, GPUMemoryInfo{
396+
Index: idx,
397+
Name: name,
398+
Vendor: VendorNVIDIA,
399+
TotalVRAM: 0,
400+
UsedVRAM: 0,
401+
FreeVRAM: 0,
402+
UsagePercent: 0,
403+
})
404+
continue
405+
}
366406
} else {
367407
// Normal GPU with dedicated VRAM
368408
totalMB, _ := strconv.ParseFloat(totalStr, 64)
@@ -790,3 +830,84 @@ func getVulkanGPUMemory() []GPUMemoryInfo {
790830

791831
return gpus
792832
}
833+
834+
// getAppleGPUMemory detects Apple Silicon GPUs using system_profiler (macOS only).
835+
// Apple Silicon uses unified memory, so GPU memory is reported as system RAM.
836+
func getAppleGPUMemory() []GPUMemoryInfo {
837+
if _, err := exec.LookPath("system_profiler"); err != nil {
838+
return nil
839+
}
840+
841+
cmd := exec.Command("system_profiler", "SPDisplaysDataType", "-json")
842+
var stdout, stderr bytes.Buffer
843+
cmd.Stdout = &stdout
844+
cmd.Stderr = &stderr
845+
846+
if err := cmd.Run(); err != nil {
847+
xlog.Debug("system_profiler failed", "error", err, "stderr", stderr.String())
848+
return nil
849+
}
850+
851+
var result struct {
852+
SPDisplaysDataType []struct {
853+
Name string `json:"_name"`
854+
Model string `json:"sppci_model"`
855+
Cores string `json:"sppci_cores"`
856+
DeviceType string `json:"sppci_device_type"`
857+
Vendor string `json:"spdisplays_vendor"`
858+
} `json:"SPDisplaysDataType"`
859+
}
860+
861+
if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
862+
xlog.Debug("failed to parse system_profiler output", "error", err)
863+
return nil
864+
}
865+
866+
var gpus []GPUMemoryInfo
867+
for i, display := range result.SPDisplaysDataType {
868+
if display.DeviceType != "spdisplays_gpu" {
869+
continue
870+
}
871+
if !strings.Contains(strings.ToLower(display.Vendor), "apple") {
872+
continue
873+
}
874+
875+
name := display.Model
876+
if name == "" {
877+
name = display.Name
878+
}
879+
if name == "" {
880+
name = "Apple GPU"
881+
}
882+
883+
// Apple Silicon uses unified memory — report system RAM
884+
ramInfo, err := GetSystemRAMInfo()
885+
if err != nil {
886+
xlog.Debug("Apple GPU detected but failed to get system RAM", "error", err)
887+
gpus = append(gpus, GPUMemoryInfo{
888+
Index: i,
889+
Name: name,
890+
Vendor: VendorApple,
891+
})
892+
continue
893+
}
894+
895+
usagePercent := 0.0
896+
if ramInfo.Total > 0 {
897+
usagePercent = float64(ramInfo.Used) / float64(ramInfo.Total) * 100
898+
}
899+
900+
xlog.Debug("Apple Silicon GPU detected (unified memory)", "device", name, "total_ram", ramInfo.Total)
901+
gpus = append(gpus, GPUMemoryInfo{
902+
Index: i,
903+
Name: name,
904+
Vendor: VendorApple,
905+
TotalVRAM: ramInfo.Total,
906+
UsedVRAM: ramInfo.Used,
907+
FreeVRAM: ramInfo.Free,
908+
UsagePercent: usagePercent,
909+
})
910+
}
911+
912+
return gpus
913+
}

0 commit comments

Comments
 (0)