diff --git a/src/lib/inference/nim.test.ts b/src/lib/inference/nim.test.ts index 7c8516afb03..2ffb7ee7810 100644 --- a/src/lib/inference/nim.test.ts +++ b/src/lib/inference/nim.test.ts @@ -318,6 +318,94 @@ describe("nim", () => { } }); + // Regression #3988: WSL2 d3d12 shims (e.g. Snapdragon X "nvidia-smi.exe") + // return a generic name like "JMJWOA-Generic-GPU" for non-NVIDIA hardware. + // The primary path used to accept any name from nvidia-smi, which made the + // preflight report "NVIDIA GPU detected" on hosts with no NVIDIA hardware. + it("rejects WDDM placeholder names on hosts without NVIDIA firmware (#3988)", () => { + const runCapture = vi.fn((cmd: string | string[]) => { + if (!Array.isArray(cmd)) throw new Error("expected argv array"); + if ( + cmd[0] === "nvidia-smi" && + cmd.some((a: string) => a.includes("name,memory.total")) + ) { + return "JMJWOA-Generic-GPU, 65471\n"; + } + return ""; + }); + const { nimModule, restore } = loadNimWithMockedRunner(runCapture); + + try { + // Snapdragon X WSL2 has no DMI / devicetree NVIDIA platform marker, so + // the firmware classification falls through to "linux" and the + // placeholder name is not vouched for. + withFirmwareModel("Microsoft Corporation Virtual Machine", () => { + expect(nimModule.detectGpu()).toBeNull(); + }); + } finally { + restore(); + } + }); + + // Even when the WDDM shim returns the placeholder with an `NVIDIA ` prefix + // (e.g. "NVIDIA JMJWOA-Generic-GPU"), `\bNVIDIA\b` alone is not enough to + // vouch for the device on generic Linux firmware — the placeholder family + // must keep requiring a firmware platform vouch. Regression guard for the + // CodeRabbit review comment on #4062. + it("rejects vendor-prefixed WDDM placeholders on generic firmware (#3988)", () => { + const runCapture = vi.fn((cmd: string | string[]) => { + if (!Array.isArray(cmd)) throw new Error("expected argv array"); + if ( + cmd[0] === "nvidia-smi" && + cmd.some((a: string) => a.includes("name,memory.total")) + ) { + return "NVIDIA JMJWOA-Generic-GPU, 65471\n"; + } + return ""; + }); + const { nimModule, restore } = loadNimWithMockedRunner(runCapture); + + try { + withFirmwareModel("Microsoft Corporation Virtual Machine", () => { + expect(nimModule.detectGpu()).toBeNull(); + }); + } finally { + restore(); + } + }); + + // Real DGX Spark legitimately reports "NVIDIA JMJWOA-Generic-GPU" via the + // primary nvidia-smi path on some firmware revisions (#3510). The Spark + // firmware platform tag must continue to vouch for the device even when + // the name itself does not match a known NVIDIA family. + it("accepts placeholder names when firmware confirms NVIDIA platform (#3510)", () => { + const runCapture = vi.fn((cmd: string | string[]) => { + if (!Array.isArray(cmd)) throw new Error("expected argv array"); + if ( + cmd[0] === "nvidia-smi" && + cmd.some((a: string) => a.includes("name,memory.total")) + ) { + return "JMJWOA-Generic-GPU, 131072\n"; + } + return ""; + }); + const { nimModule, restore } = loadNimWithMockedRunner(runCapture); + + try { + withFirmwareModel("NVIDIA DGX Spark", () => { + expect(nimModule.detectGpu()).toMatchObject({ + type: "nvidia", + name: "JMJWOA-Generic-GPU", + count: 1, + totalMemoryMB: 131072, + platform: "spark", + }); + }); + } finally { + restore(); + } + }); + it("detects GB10 unified-memory GPUs as Spark-capable NVIDIA devices", () => { const runCapture = vi.fn((cmd: string | string[]) => { if (!Array.isArray(cmd)) throw new Error("expected argv array"); diff --git a/src/lib/inference/nim.ts b/src/lib/inference/nim.ts index cea6a8c944c..0ccd314b821 100644 --- a/src/lib/inference/nim.ts +++ b/src/lib/inference/nim.ts @@ -24,6 +24,30 @@ import { VLLM_PORT } from "../core/ports"; const UNIFIED_MEMORY_GPU_TAGS = ["GB10", "Thor", "Orin", "Xavier", "Jetson", "Tegra"]; const NIM_STATUS_PROBE_TIMEOUT_MS = 5000; +// On Windows-on-ARM (Snapdragon X) WSL2 hosts, a d3d12/WDDM shim publishes a +// `nvidia-smi.exe` that returns a placeholder name (e.g. "JMJWOA-Generic-GPU") +// even though the system has no NVIDIA hardware. Real DGX Spark legitimately +// reports the same string (see #3510), distinguished by the firmware platform. +// Accept a name as NVIDIA when it either advertises the vendor explicitly or +// matches a known NVIDIA product family; otherwise the caller must cross-check +// against `detectNvidiaPlatform()` before trusting the nvidia-smi output. +const NVIDIA_GPU_NAME_PATTERN = + /\bNVIDIA\b|\b(GeForce|Tesla|Quadro|RTX|GTX|TITAN|H100|H200|A100|A40|A10|L40|L4|GB1\d|GB200|GB300|Grace[\s_-]+Hopper)\b/i; + +// Names that have been observed both on legitimate NVIDIA unified-memory +// hardware (DGX Spark — #3510) and on Windows-on-ARM WSL2 d3d12 shims with no +// NVIDIA silicon. Even with an `NVIDIA ` vendor prefix the name alone is not +// sufficient — the caller must cross-check `detectNvidiaPlatform()`. +const NVIDIA_GPU_NAME_DENYLIST_PATTERN = /\bJMJWOA-Generic-GPU\b/i; + +function isPlausibleNvidiaGpuName(name: string): boolean { + return ( + !!name && + !NVIDIA_GPU_NAME_DENYLIST_PATTERN.test(name) && + NVIDIA_GPU_NAME_PATTERN.test(name) + ); +} + export interface NimModel { name: string; image: string; @@ -277,23 +301,36 @@ export function detectGpu(): GpuDetection | null { parsed.push({ name, memoryMB }); } if (parsed.length > 0) { - const totalMemoryMB = parsed.reduce( + const platform = detectNvidiaPlatform(); + // Reject WDDM/d3d12 placeholder names on hosts where firmware does not + // confirm an NVIDIA platform. Otherwise a Snapdragon X WSL2 nvidia-smi + // shim returning "JMJWOA-Generic-GPU" would be reported as a real + // NVIDIA GPU. Real DGX Spark uses the same placeholder but has + // firmware platform "spark", which keeps the #3510 path working. + const firmwareConfirmsNvidia = + platform === "spark" || platform === "station" || platform === "jetson"; + const trusted = firmwareConfirmsNvidia + ? parsed + : parsed.filter((p: ParsedGpu) => isPlausibleNvidiaGpuName(p.name)); + if (trusted.length === 0) { + return null; + } + const totalMemoryMB = trusted.reduce( (sum: number, p: ParsedGpu) => sum + p.memoryMB, 0, ); - const firstName = parsed[0].name; + const firstName = trusted[0].name; // Only surface a single name when every GPU reports the same model; // a mixed-GPU host would otherwise be misreported as `Nx `. const allSameName = - !!firstName && parsed.every((p: ParsedGpu) => p.name === firstName); - const platform = detectNvidiaPlatform(); + !!firstName && trusted.every((p: ParsedGpu) => p.name === firstName); return { type: "nvidia", ...(allSameName ? { name: firstName } : {}), - gpus: parsed.map((p) => ({ name: p.name, memoryMB: p.memoryMB })), - count: parsed.length, + gpus: trusted.map((p) => ({ name: p.name, memoryMB: p.memoryMB })), + count: trusted.length, totalMemoryMB, - perGpuMB: parsed[0].memoryMB, + perGpuMB: trusted[0].memoryMB, nimCapable: canRunNimWithMemory(totalMemoryMB), platform, spark: platform === "spark",