From ad5260f76bd5015a21417e99309a5b6fd2b9c79a Mon Sep 17 00:00:00 2001 From: Kagura Chen Date: Wed, 18 Mar 2026 20:43:22 +0800 Subject: [PATCH 1/4] fix(gpu): detect Jetson Thor and Orin unified memory GPUs Closes #300 The unified-memory fallback in detectGpu() only checked for "GB10" (DGX Spark). Jetson Thor and Orin report different chip names, causing GPU detection to fail and fallback to cloud inference. Add Thor, Orin, and Xavier to the unified-memory chip name list so all current Jetson and DGX Spark platforms are detected correctly. --- bin/lib/nim.js | 14 +++++-- test/nim-jetson.test.js | 82 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 test/nim-jetson.test.js diff --git a/bin/lib/nim.js b/bin/lib/nim.js index 4f2233e435..1a2c641201 100644 --- a/bin/lib/nim.js +++ b/bin/lib/nim.js @@ -23,6 +23,9 @@ function listModels() { })); } +// Chip names that use unified memory (VRAM not separately queryable) +const UNIFIED_MEMORY_CHIPS = ["GB10", "Thor", "Orin", "Xavier"]; + function detectGpu() { // Try NVIDIA first — query VRAM try { @@ -46,14 +49,14 @@ function detectGpu() { } } catch {} - // Fallback: DGX Spark (GB10) — VRAM not queryable due to unified memory architecture + // Fallback: unified memory GPUs (DGX Spark GB10, Jetson Thor/Orin/Xavier) + // VRAM is not separately queryable — use system RAM instead try { const nameOutput = runCapture( "nvidia-smi --query-gpu=name --format=csv,noheader,nounits", { ignoreError: true } ); - if (nameOutput && nameOutput.includes("GB10")) { - // GB10 has 128GB unified memory shared with Grace CPU — use system RAM + if (nameOutput && UNIFIED_MEMORY_CHIPS.some((chip) => nameOutput.includes(chip))) { let totalMemoryMB = 0; try { const memLine = runCapture("free -m | awk '/Mem:/ {print $2}'", { ignoreError: true }); @@ -65,7 +68,9 @@ function detectGpu() { totalMemoryMB, perGpuMB: totalMemoryMB, nimCapable: true, - spark: true, + spark: nameOutput.includes("GB10"), + unifiedMemory: true, + name: nameOutput.split("\n")[0].trim(), }; } } catch {} @@ -204,4 +209,5 @@ module.exports = { waitForNimHealth, stopNimContainer, nimStatus, + UNIFIED_MEMORY_CHIPS, }; diff --git a/test/nim-jetson.test.js b/test/nim-jetson.test.js new file mode 100644 index 0000000000..ebc24823b1 --- /dev/null +++ b/test/nim-jetson.test.js @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); + +const nim = require("../bin/lib/nim"); + +describe("UNIFIED_MEMORY_CHIPS", () => { + it("is exported as an array", () => { + assert.ok(Array.isArray(nim.UNIFIED_MEMORY_CHIPS)); + }); + + it("includes GB10 (DGX Spark)", () => { + assert.ok(nim.UNIFIED_MEMORY_CHIPS.includes("GB10")); + }); + + it("includes Thor (Jetson Thor)", () => { + assert.ok(nim.UNIFIED_MEMORY_CHIPS.includes("Thor")); + }); + + it("includes Orin (Jetson Orin / Orin Nano / Orin NX)", () => { + assert.ok(nim.UNIFIED_MEMORY_CHIPS.includes("Orin")); + }); + + it("includes Xavier (Jetson Xavier)", () => { + assert.ok(nim.UNIFIED_MEMORY_CHIPS.includes("Xavier")); + }); + + it("matches Jetson Thor chip names via substring", () => { + const names = ["NVIDIA Thor", "Thor (nvgpu)", "Jetson Thor"]; + for (const name of names) { + const matched = nim.UNIFIED_MEMORY_CHIPS.some((chip) => name.includes(chip)); + assert.ok(matched, `should match "${name}"`); + } + }); + + it("matches Jetson Orin variants via substring", () => { + const names = ["Orin (nvgpu)", "Orin Nano", "Orin NX", "Jetson Orin"]; + for (const name of names) { + const matched = nim.UNIFIED_MEMORY_CHIPS.some((chip) => name.includes(chip)); + assert.ok(matched, `should match "${name}"`); + } + }); + + it("matches DGX Spark GB10 via substring", () => { + const matched = nim.UNIFIED_MEMORY_CHIPS.some((chip) => "NVIDIA GB10".includes(chip)); + assert.ok(matched); + }); + + it("does NOT match discrete GPUs", () => { + const discrete = [ + "NVIDIA GeForce RTX 4090", + "NVIDIA A100-SXM4-80GB", + "NVIDIA H100", + "Tesla V100-SXM2-16GB", + ]; + for (const name of discrete) { + const matched = nim.UNIFIED_MEMORY_CHIPS.some((chip) => name.includes(chip)); + assert.ok(!matched, `should NOT match "${name}"`); + } + }); + + it("spark flag is true only for GB10", () => { + // Verify the logic: spark = nameOutput.includes("GB10") + const testCases = [ + { name: "NVIDIA GB10", expectedSpark: true }, + { name: "NVIDIA Thor", expectedSpark: false }, + { name: "Orin (nvgpu)", expectedSpark: false }, + { name: "Orin Nano", expectedSpark: false }, + { name: "Xavier", expectedSpark: false }, + ]; + for (const { name, expectedSpark } of testCases) { + assert.equal(name.includes("GB10"), expectedSpark, `spark for "${name}"`); + } + }); + + it("name extraction takes first line", () => { + const multiLine = "Orin (nvgpu)\nSomething else"; + assert.equal(multiLine.split("\n")[0].trim(), "Orin (nvgpu)"); + }); +}); From 2e4a3213ac1b4ade5ba0abfea679e2780a4f2217 Mon Sep 17 00:00:00 2001 From: Kagura Chen Date: Wed, 18 Mar 2026 20:48:01 +0800 Subject: [PATCH 2/4] refactor: use case-insensitive matching for unified memory chip detection Address CodeRabbit review: nvidia-smi output casing may vary, so normalize both sides with toLowerCase() for robustness. --- bin/lib/nim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lib/nim.js b/bin/lib/nim.js index 1a2c641201..8be794cab6 100644 --- a/bin/lib/nim.js +++ b/bin/lib/nim.js @@ -56,7 +56,7 @@ function detectGpu() { "nvidia-smi --query-gpu=name --format=csv,noheader,nounits", { ignoreError: true } ); - if (nameOutput && UNIFIED_MEMORY_CHIPS.some((chip) => nameOutput.includes(chip))) { + if (nameOutput && UNIFIED_MEMORY_CHIPS.some((chip) => nameOutput.toLowerCase().includes(chip.toLowerCase()))) { let totalMemoryMB = 0; try { const memLine = runCapture("free -m | awk '/Mem:/ {print $2}'", { ignoreError: true }); From 41a8bee5d1a95548c36a6d3d885226f5306e3330 Mon Sep 17 00:00:00 2001 From: Kagura Chen Date: Thu, 19 Mar 2026 09:03:26 +0800 Subject: [PATCH 3/4] fix: case-insensitive spark flag matching for GB10 Address CodeRabbit review comments: - Use toLowerCase() for spark flag (consistent with chip detection) - Update test to verify case-insensitive GB10 matching --- bin/lib/nim.js | 2 +- test/nim-jetson.test.js | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bin/lib/nim.js b/bin/lib/nim.js index 8be794cab6..a9210fcc2d 100644 --- a/bin/lib/nim.js +++ b/bin/lib/nim.js @@ -68,7 +68,7 @@ function detectGpu() { totalMemoryMB, perGpuMB: totalMemoryMB, nimCapable: true, - spark: nameOutput.includes("GB10"), + spark: nameOutput.toLowerCase().includes("gb10"), unifiedMemory: true, name: nameOutput.split("\n")[0].trim(), }; diff --git a/test/nim-jetson.test.js b/test/nim-jetson.test.js index ebc24823b1..fc22e7a173 100644 --- a/test/nim-jetson.test.js +++ b/test/nim-jetson.test.js @@ -61,17 +61,19 @@ describe("UNIFIED_MEMORY_CHIPS", () => { } }); - it("spark flag is true only for GB10", () => { - // Verify the logic: spark = nameOutput.includes("GB10") + it("spark flag is true only for GB10 (case-insensitive)", () => { + // Verify the logic: spark = nameOutput.toLowerCase().includes("gb10") const testCases = [ { name: "NVIDIA GB10", expectedSpark: true }, + { name: "NVIDIA gb10", expectedSpark: true }, + { name: "NVIDIA Gb10", expectedSpark: true }, { name: "NVIDIA Thor", expectedSpark: false }, { name: "Orin (nvgpu)", expectedSpark: false }, { name: "Orin Nano", expectedSpark: false }, { name: "Xavier", expectedSpark: false }, ]; for (const { name, expectedSpark } of testCases) { - assert.equal(name.includes("GB10"), expectedSpark, `spark for "${name}"`); + assert.equal(name.toLowerCase().includes("gb10"), expectedSpark, `spark for "${name}"`); } }); From 752f1af6242470dd901d37fd5b4babaa0b34ecee Mon Sep 17 00:00:00 2001 From: Kagura Chen Date: Thu, 19 Mar 2026 13:06:37 +0800 Subject: [PATCH 4/4] refactor: freeze UNIFIED_MEMORY_CHIPS to prevent accidental mutation --- bin/lib/nim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lib/nim.js b/bin/lib/nim.js index a9210fcc2d..33f124de3e 100644 --- a/bin/lib/nim.js +++ b/bin/lib/nim.js @@ -24,7 +24,7 @@ function listModels() { } // Chip names that use unified memory (VRAM not separately queryable) -const UNIFIED_MEMORY_CHIPS = ["GB10", "Thor", "Orin", "Xavier"]; +const UNIFIED_MEMORY_CHIPS = Object.freeze(["GB10", "Thor", "Orin", "Xavier"]); function detectGpu() { // Try NVIDIA first — query VRAM