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 {