From 5aa3d0c9a86ee1a888d23bd56cd4f4e1922d5519 Mon Sep 17 00:00:00 2001 From: Jaret Burkett Date: Thu, 24 Sep 2026 17:55:11 -0600 Subject: [PATCH] Update ui to show DGX spark gpu usage correctly --- ui/src/components/GPUWidget.tsx | 2 +- ui/src/server/monitor.ts | 34 ++++++++++++++++++++++++++++++--- ui/src/types.ts | 2 ++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/ui/src/components/GPUWidget.tsx b/ui/src/components/GPUWidget.tsx index f717b91..08e9447 100644 --- a/ui/src/components/GPUWidget.tsx +++ b/ui/src/components/GPUWidget.tsx @@ -61,7 +61,7 @@ export default function GPUWidget({ gpu }: GPUWidgetProps) {
-

Memory

+

{gpu.memory.shared ? 'Shared Memory' : 'Memory'}

{((gpu.memory.used / gpu.memory.total) * 100).toFixed(1)}% diff --git a/ui/src/server/monitor.ts b/ui/src/server/monitor.ts index 8431be9..31a34bf 100644 --- a/ui/src/server/monitor.ts +++ b/ui/src/server/monitor.ts @@ -35,6 +35,13 @@ const NV_BATCH_FLUSH_MS = 100; // Temperature refresh is decoupled from the tick (see refreshCpuTemp) const CPU_TEMP_REFRESH_MS = 5000; +// Power limits for GPUs whose driver reports none (nvidia-smi gives [N/A]). +// GB10 (DGX Spark) is a unified SoC with no settable GPU limit; 140 W is +// NVIDIA's rated TDP for the GB10 superchip. +const KNOWN_POWER_LIMITS_W: Record = { + 'NVIDIA GB10': 140, +}; + function parseGpuLine(line: string): GpuInfo | null { const [ index, @@ -69,7 +76,7 @@ function parseGpuLine(line: string): GpuInfo | null { }, power: { draw: parseFloat(powerDraw), - limit: parseFloat(powerLimit), + limit: isNaN(parseFloat(powerLimit)) ? (KNOWN_POWER_LIMITS_W[name] ?? NaN) : parseFloat(powerLimit), }, clocks: { graphics: parseInt(clockGraphics), @@ -81,6 +88,23 @@ function parseGpuLine(line: string): GpuInfo | null { }; } +/** + * Unified-memory GPUs (GB10 / DGX Spark) have no dedicated VRAM, so + * nvidia-smi reports memory as [N/A]. Report the system RAM pool they share + * instead, like the Mac path does for Apple silicon. + */ +function withSharedMemory(gpu: GPUApiResponse, cpu: CpuInfo | null): GPUApiResponse { + if (!cpu || !gpu.gpus.some(g => isNaN(g.memory.total))) return gpu; + const total = Math.round(cpu.totalMemory); + const free = Math.round(cpu.availableMemory); + return { + ...gpu, + gpus: gpu.gpus.map(g => + isNaN(g.memory.total) ? { ...g, memory: { total, free, used: total - free, shared: true } } : g, + ), + }; +} + // Serialized once per tick and shared, so N subscribers don't stringify N times type Subscriber = (sample: MonitorSample, serialized: string) => void; @@ -138,7 +162,7 @@ class SystemMonitor { return { t: Date.now(), cpu: this.latestCpu, - gpu: this.latestGpu, + gpu: withSharedMemory(this.latestGpu, this.latestCpu), history: [...this.history], }; } @@ -175,7 +199,11 @@ class SystemMonitor { console.error('Monitor: GPU sample failed:', error); } - const sample: MonitorSample = { t, cpu: this.latestCpu, gpu: this.latestGpu }; + const sample: MonitorSample = { + t, + cpu: this.latestCpu, + gpu: withSharedMemory(this.latestGpu, this.latestCpu), + }; this.history.push(historyPointFromSample(sample)); if (this.history.length > MONITOR_HISTORY_LENGTH) { this.history.splice(0, this.history.length - MONITOR_HISTORY_LENGTH); diff --git a/ui/src/types.ts b/ui/src/types.ts index b27d22f..eb6b1bf 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -11,6 +11,8 @@ export interface GpuMemory { total: number; free: number; used: number; + // unified memory (e.g. GB10): figures are the system RAM pool the GPU shares + shared?: boolean; } export interface GpuPower {