Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9b81b72
fix(onboard): prove native Linux ARM64 GPUs with denylisted names
latenighthackathon Aug 6, 2026
39b5449
test(onboard): prove generic ARM64 GPU failure boundary
apurvvkumaria Aug 6, 2026
3598e8c
merge: refresh PR #8425 from main
apurvvkumaria Aug 6, 2026
0390806
docs(onboard): scope ARM64 GPU proof guidance
apurvvkumaria Aug 6, 2026
0831365
docs(onboard): use ARM64 CUDA proof command
apurvvkumaria Aug 6, 2026
da42422
merge(main): refresh PR #8425
apurvvkumaria Aug 6, 2026
0c69435
merge(main): refresh PR #8425
apurvvkumaria Aug 6, 2026
1619263
docs(onboard): clarify ARM64 GPU proof effects
apurvvkumaria Aug 6, 2026
4c6944a
fix(onboard): trust immutable GPU proof image
apurvvkumaria Aug 6, 2026
7444ab6
merge(main): refresh PR #8425
apurvvkumaria Aug 7, 2026
b547507
fix(docs): refresh ARM64 support citation
apurvvkumaria Aug 7, 2026
3136773
merge(main): refresh PR #8425
apurvvkumaria Aug 7, 2026
1144bf0
Merge branch 'main' into fix/native-linux-arm64-gpu-proof
cv Aug 7, 2026
ca3fcc6
Merge branch 'main' into fix/native-linux-arm64-gpu-proof
cv Aug 7, 2026
e0646e4
merge(main): refresh PR #8425
jyaunches Aug 7, 2026
03e3f18
fix(onboard): harden ARM64 GPU proof inputs
jyaunches Aug 7, 2026
456359a
fix(onboard): escape GPU names at preflight
jyaunches Aug 7, 2026
fee2a7b
merge: refresh native ARM64 GPU proof from main
apurvvkumaria Aug 7, 2026
640b6f7
merge: refresh native ARM64 GPU proof from latest main
apurvvkumaria Aug 7, 2026
f913989
merge: refresh native ARM64 GPU proof from current main
apurvvkumaria Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/platform-matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@
{
"name": "Windows-on-ARM GPU passthrough",
"status": "unsupported",
"notes": "Windows-on-ARM CPU paths run under WSL2 'tested with limitations', but GPU passthrough on WOA is denylisted (`src/lib/onboard/wsl-docker-desktop-gpu.ts:188`, `src/lib/inference/gpu-trust.test.ts:70`). See closed issue #4565."
"notes": "Windows-on-ARM CPU paths run under WSL2 'tested with limitations', but GPU passthrough on WOA is denylisted (`src/lib/onboard/wsl-docker-desktop-gpu.ts:214`, `src/lib/inference/gpu-trust.test.ts:70`). See closed issue #4565."
},
{
"name": "Non-NVIDIA GPUs (AMD/ROCm, Intel Arc, Apple Metal)",
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,11 @@ $$nemoclaw onboard --from ./Dockerfile.custom

When `$$nemoclaw onboard` detects an NVIDIA GPU on the host, it enables OpenShell GPU passthrough at both the gateway and sandbox level by default.
Detection proceeds along two paths. The `nvidia-smi`-based paths (the primary `--query-gpu=name,memory.total,memory.free` probe and the unified-memory `--query-gpu=name` fallback) require `nvidia-smi` to succeed and, on hosts whose firmware does not classify as a known NVIDIA platform (DGX Spark, DGX Station, Jetson, or Tegra), additionally require that the GPU name does not match the placeholder family observed on the Windows-on-ARM WSL2 nvidia-smi shim (`JMJWOA-Generic-*`) and that either the host is not ARM64 Linux (the observed shim is Windows-on-ARM only) or the NVIDIA kernel driver is bound (`/proc/driver/nvidia/` present), so that placeholder shims on non-NVIDIA hardware are not mistaken for real GPUs.
On a native or Docker Desktop-backed WSL ARM64 Linux host without firmware-confirmed NVIDIA platform metadata, the primary memory-query probe can clear a `JMJWOA-Generic-*` name only after a bounded CUDA workload succeeds.
The workload runs `docker run --rm --gpus all nvcr.io/nvidia/k8s/cuda-sample@sha256:7c7540bdf1f942d4fb6db97069fd6c289471b54ac29e3c7fcdf914cf77af7d41`.
Docker may pull the CUDA sample image from `nvcr.io` and keeps the image in the local cache after the container exits.
The names-only unified-memory fallback does not run this workload and rejects denylisted names.
Other non-firmware-vouched hosts also reject denylisted names.
Jetson/Tegra hosts that ship without `nvidia-smi` continue to be detected via the devicetree firmware fallback (`/sys/firmware/devicetree/base/model`) or the Tegra device-node fallback (`/dev/nvhost-gpu`, `/dev/nvhost-ctrl-gpu`, `/dev/nvhost-ctrl`, or `/dev/nvmap`); both bypass the trust-tier gate above.
Use `--no-gpu` to opt out when you want host-side inference providers only and do not need direct GPU access inside the sandbox.
Use `--gpu` to require GPU passthrough and fail fast if an NVIDIA GPU is not detected.
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/platform-support.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ They are listed here so launch material, sales conversations, and support triage
| Non-Ubuntu/Debian Linux distros | Unsupported | Installer assumes `apt-get`. Fedora/Rocky/Alma/Arch/NixOS are not validated and the installer's package-manager probes do not cover them. See open issue #899 (Fedora hang). |
| Native Kubernetes or OpenShift deployments | Unsupported | NemoClaw runs the sandbox as a Docker container, not a Kubernetes pod. The default Docker-driver topology does not embed k3s. Operator-managed K8s/OpenShift deployments are out of scope; see issue #407 (community OpenShift through agent-sandbox CRD). |
| Air-gapped / offline installs | Unsupported | Onboard assumes network reachability for package fetches, container pulls, and provider validation. See open issues #4872 and #2218 (production-deployment epic covering air-gapped support, China network guidance, multi-host topology). |
| Windows-on-ARM GPU passthrough | Unsupported | Windows-on-ARM CPU paths run under WSL2 'tested with limitations', but GPU passthrough on WOA is denylisted (`src/lib/onboard/wsl-docker-desktop-gpu.ts:188`, `src/lib/inference/gpu-trust.test.ts:70`). See closed issue #4565. |
| Windows-on-ARM GPU passthrough | Unsupported | Windows-on-ARM CPU paths run under WSL2 'tested with limitations', but GPU passthrough on WOA is denylisted (`src/lib/onboard/wsl-docker-desktop-gpu.ts:214`, `src/lib/inference/gpu-trust.test.ts:70`). See closed issue #4565. |
| Non-NVIDIA GPUs (AMD/ROCm, Intel Arc, Apple Metal) | Unsupported | Local vLLM and NIM paths assert NVIDIA CDI presence with `assertCdiNvidiaGpuSpecPresent` (`src/lib/onboard/fatal-runtime-preflight.ts`). NemoClaw does not install non-NVIDIA accelerator drivers. |
| Other LangChain, AutoGen, CrewAI, or non-listed agent harnesses | Unsupported | LangChain Deep Agents Code is the only integrated LangChain-family agent runtime (refer to the Agents section above; status `Tested`). Other LangChain agent runtimes, AutoGen, CrewAI, and any agent runtime not listed in the Agents table are not integrated. Bringing more agent runtimes is tracked as a research epic (refer to open issue #4861) but is not on the current roadmap. |
| Multi-user host sharing | Unsupported | Sandboxes are scoped to a single host user. NemoClaw treats multi-user hosts as a risk and warns at onboard; see `docs/security/openclaw-controls.mdx` Multi-user detection. |
Expand Down
21 changes: 19 additions & 2 deletions docs/reference/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -831,8 +831,25 @@ The wizard cleans up stale port forwards and waits for gateway readiness automat

### GPU setup fails with a placeholder GPU name

On Windows or WSL hosts, some systems report a placeholder display adapter name even when no NVIDIA GPU firmware is present.
On Windows, WSL, and native Linux ARM64 hosts, some systems report a placeholder display adapter name even when no NVIDIA GPU firmware is present.
NVIDIA NIM and GPU-backed sandbox setup require a real NVIDIA GPU.

When the primary memory-query probe reports a placeholder name on a native or Docker Desktop-backed WSL ARM64 Linux host without firmware-confirmed NVIDIA platform metadata, onboarding runs one bounded Docker CUDA workload.
Docker may pull the CUDA sample image from `nvcr.io` and keeps the image in the local cache after the container exits.
The workload uses this command:

```bash
docker run --rm --gpus all nvcr.io/nvidia/k8s/cuda-sample@sha256:7c7540bdf1f942d4fb6db97069fd6c289471b54ac29e3c7fcdf914cf77af7d41
```

The run is bounded to 3 minutes.
Set `NEMOCLAW_WSL_GPU_PROOF_TIMEOUT_MS` to a positive millisecond value to change that bound.
Increase the value on slow links.
A passing workload qualifies the detected GPU for GPU passthrough.
A failed or timed-out workload leaves the GPU unproven and does not enable GPU passthrough.
The names-only unified-memory fallback does not run this workload and rejects denylisted names.
WSL hosts that are not Docker Desktop-backed do not run the workload and continue to report the GPU as unavailable.

If NemoClaw rejects the detected GPU name during preflight, select a CPU or remote inference provider, or move the setup to a host with a supported NVIDIA GPU and current drivers.

Jetson/Tegra hosts support sandbox GPU passthrough through the compatibility route.
Expand Down Expand Up @@ -2712,7 +2729,7 @@ nvidia-ctk cdi list
On WSL with Docker Desktop, confirm Docker Desktop WSL integration is enabled for your distro and verify Docker GPU access from WSL:

```bash
docker run --rm --gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark
docker run --rm --gpus all nvcr.io/nvidia/k8s/cuda-sample@sha256:7c7540bdf1f942d4fb6db97069fd6c289471b54ac29e3c7fcdf914cf77af7d41
```

If GPU passthrough is not required on this host, rerun onboarding with `--no-gpu` instead.
Expand Down
3 changes: 2 additions & 1 deletion src/lib/inference/gpu-trust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export interface DockerGpuProofResult {

// Optional accept-path used by `detectGpu()` when an ARM64 Linux host reports a
// denylisted `JMJWOA-Generic-*` placeholder. The prover returns `null` when the
// host is not a proof candidate (not ARM64 WSL Docker Desktop), preserving the
// host is not a proof candidate (not ARM64 Linux that is native or Docker
// Desktop-backed WSL, #8096), preserving the
// #3988 fail-closed default; otherwise it returns the bounded Docker GPU proof
// outcome so a passing real GPU can be trusted without trusting the name alone.
export type Arm64WslDockerDesktopGpuProver = (gpuNames: string[]) => DockerGpuProofResult | null;
Expand Down
8 changes: 4 additions & 4 deletions src/lib/inference/nim.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,10 +752,10 @@ describe("nim", () => {
}
});

// Snapdragon WoA fail-closed: the same placeholder name, but the bounded
// CUDA proof fails because there is no usable NVIDIA device. The detection
// must stay null so #3988/#4424 is not reopened.
it("keeps rejecting a denylisted ARM64 GPU when the Docker GPU proof fails (#4565/#3988)", () => {
// A generic ARM64 host can be native Linux or Windows on ARM with WSL2.
// When the bounded CUDA proof fails, public detection must return null so
// the native Linux path does not reopen #3988/#4424.
it("rejects a denylisted generic ARM64 GPU when the Docker proof fails (#4565/#8096/#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"))) {
Expand Down
33 changes: 17 additions & 16 deletions src/lib/inference/nim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,27 +84,27 @@ export interface GpuDetection {
// skips `computeIntensive` registry entries on these hosts.
computeConstrained?: boolean;
// Set when a denylisted `JMJWOA-Generic-*` placeholder name was accepted only
// because a bounded Docker `--gpus` CUDA proof passed (Windows-ARM N1X + WSL2
// + Docker Desktop, #4565). Diagnostic marker that this detection cleared a
// live proof rather than firmware/name trust. The sandbox GPU preflight still
// reaches the Docker Desktop WSL compatibility branch via its own
// `detectWslDockerDesktopStatus()` check (consistent because the proof itself
// requires Docker Desktop WSL); this flag does not gate that branch.
// because a bounded Docker `--gpus` CUDA proof passed, on Windows-ARM N1X
// (WSL2 + Docker Desktop, #4565) or native ARM64 Linux (#8096). Diagnostic
// marker that this detection cleared a live proof rather than firmware/name
// trust. The sandbox GPU preflight reaches the Docker Desktop WSL
// compatibility branch via its own `detectWslDockerDesktopStatus()` check,
// which stays false on a native Linux host; this flag does not gate that
// branch.
wslDockerDesktopGpuProofPassed?: boolean;
}

export interface DetectGpuDeps {
// Optional accept-path for ARM64 WSL Docker Desktop `JMJWOA-Generic-*` GPUs
// (#4565). Injected in tests; in production `detectGpu()` lazily builds the
// default prover from the onboard WSL Docker Desktop module only when it is
// about to reject a denylisted ARM64 name.
// Optional accept-path for native or Docker Desktop-backed WSL ARM64 Linux
// hosts that report a `JMJWOA-Generic-*` GPU (#4565/#8096). Injected in tests;
// in production `detectGpu()` lazily builds the default prover only when it
// is about to reject a denylisted ARM64 name.
proveArm64WslDockerDesktopGpu?: Arm64WslDockerDesktopGpuProver | null;
}

// Lazily construct the default ARM64 WSL Docker Desktop GPU prover. Kept lazy
// (and behind a require) so the inference layer does not statically depend on
// the onboard layer, and so the bounded Docker proof is only wired when we
// actually reach the denylist-reject path on an ARM64 host.
// Lazily construct the default ARM64 Linux GPU prover. Keep it behind a require
// so the inference layer does not statically depend on the onboard layer. The
// bounded Docker proof is wired only at the denylist-reject path.
function defaultArm64WslDockerDesktopGpuProver(): Arm64WslDockerDesktopGpuProver | null {
try {
return require("../onboard/wsl-docker-desktop-gpu").createArm64WslDockerDesktopGpuProver();
Expand Down Expand Up @@ -510,9 +510,10 @@ export function detectGpu(deps: DetectGpuDeps = {}): GpuDetection | null {
nimCapable: canRunNimWithMemory(totalMemoryMB),
platform,
spark: platform === "spark",
// The proof-passed Windows-ARM N1X iGPU is memory-shared like Jetson
// and cannot serve a computeIntensive model in-loop, so tag it
// The proof-passed ARM64 N1X GPU is memory-shared like Jetson and
// cannot serve a computeIntensive model in-loop, so tag it
// computeConstrained to exclude those Ollama bootstrap models (#3707).
// This covers the N1X part on WSL2 and on native Linux (#8096).
...(platform === "jetson" || wslDockerDesktopGpuProofPassed
? { computeConstrained: true }
: {}),
Expand Down
58 changes: 51 additions & 7 deletions src/lib/onboard/wsl-docker-desktop-gpu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,58 @@ describe("createArm64WslDockerDesktopGpuProver (#4565)", () => {
expect(runProof).not.toHaveBeenCalled();
});

it("returns null when the host is not Docker Desktop-backed WSL", () => {
it("proves a denylisted GPU name on native Linux ARM64 (#8096)", () => {
// A native Linux ARM64 host reports a genuine GPU as `JMJWOA-Generic-GPU`.
// Gating the proof on Docker Desktop left no way to verify that GPU, so
// onboarding reported "no NVIDIA GPU detected" on a host where
// `docker run --gpus all` runs a CUDA workload.
const runProof = vi.fn(() => passingProof);
const prover = createArm64WslDockerDesktopGpuProver({
platform: "linux",
arch: "arm64",
detectWslDockerDesktopStatus: () => "not-docker-desktop",
env: {},
release: "6.17.0-1029-nvidia",
procVersion: "Linux version 6.17.0-1029-nvidia",
runProof,
log: () => undefined,
});
expect(prover(["JMJWOA-Generic-GPU"])).toBeNull();
expect(runProof).not.toHaveBeenCalled();
expect(prover(["JMJWOA-Generic-GPU"])).toEqual(passingProof);
expect(runProof).toHaveBeenCalledTimes(1);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

it("returns the failed bounded proof result on native Linux ARM64", () => {
// The Snapdragon nvidia-smi shim reaches the same path; only the CUDA
// workload separates it from real hardware (#3988/#4565).
const failingProof = { passed: false, timedOut: false, exitCode: 1, diagnostic: "" };
const runProof = vi.fn(() => failingProof);
const prover = createArm64WslDockerDesktopGpuProver({
platform: "linux",
arch: "arm64",
env: {},
release: "6.17.0-1029-nvidia",
procVersion: "Linux version 6.17.0-1029-nvidia",
runProof,
log: () => undefined,
});
expect(prover(["JMJWOA-Generic-GPU"])).toEqual(failingProof);
expect(runProof).toHaveBeenCalledTimes(1);
});

it("leaves WSL hosts without Docker Desktop unproven (#8096)", () => {
// Windows-on-ARM passthrough scope is unchanged: only native Linux is added.
for (const status of ["not-docker-desktop", "unknown"] as const) {
const runProof = vi.fn(() => passingProof);
const prover = createArm64WslDockerDesktopGpuProver({
platform: "linux",
arch: "arm64",
env: { WSL_DISTRO_NAME: "Ubuntu" },
detectWslDockerDesktopStatus: () => status,
runProof,
log: () => undefined,
});
expect(prover(["JMJWOA-Generic-GPU"])).toBeNull();
expect(runProof).not.toHaveBeenCalled();
}
});

it("runs the bounded proof and reports the result on ARM64 Docker Desktop WSL", () => {
Expand All @@ -114,16 +155,19 @@ describe("createArm64WslDockerDesktopGpuProver (#4565)", () => {
expect(argv).toContain("--gpus");
});

it("uses an arch-correct CUDA sample image (not the amd64-only nbody) on this ARM64 path", () => {
it("uses the approved immutable multi-architecture CUDA sample image on this ARM64 path", () => {
// The proof only runs on ARM64, so the image must ship a real aarch64 CUDA
// binary. `cuda-sample:nbody` packs an x86-64 binary in its arm64 tag and
// fails with `exec format error` on the N1X target (#4565); the chosen
// vectorAdd image ships a genuine aarch64 binary.
expect(WSL_DOCKER_DESKTOP_GPU_PROOF_COMMAND).toContain("cuda-sample:vectoradd");
expect(WSL_DOCKER_DESKTOP_GPU_PROOF_COMMAND).toBe(
"docker run --rm --gpus all nvcr.io/nvidia/k8s/cuda-sample@sha256:7c7540bdf1f942d4fb6db97069fd6c289471b54ac29e3c7fcdf914cf77af7d41",
);
expect(WSL_DOCKER_DESKTOP_GPU_PROOF_COMMAND).not.toContain("vectoradd-cuda12.5.0");
expect(WSL_DOCKER_DESKTOP_GPU_PROOF_COMMAND).not.toContain("nbody");
});

it("propagates a failing proof so detection stays fail-closed", () => {
it("returns the failed bounded proof result on Docker Desktop WSL", () => {
const failing = { passed: false, timedOut: false, exitCode: 1, diagnostic: "no CUDA device" };
const prover = createArm64WslDockerDesktopGpuProver({
platform: "linux",
Expand Down
Loading
Loading