Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 27 additions & 1 deletion src/lib/onboard/docker-gpu-patch-wsl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

import { describe, expect, it } from "vitest";

import { shouldApplyDockerGpuPatch } from "../../../dist/lib/onboard/docker-gpu-patch";
import {
buildDockerGpuModeCandidates,
shouldApplyDockerGpuPatch,
} from "../../../dist/lib/onboard/docker-gpu-patch";

describe("shouldApplyDockerGpuPatch on Docker Desktop WSL", () => {
it("ignores NEMOCLAW_DOCKER_GPU_PATCH=0 on Docker Desktop WSL where the patch is required", () => {
Expand Down Expand Up @@ -53,3 +56,26 @@ describe("shouldApplyDockerGpuPatch on Docker Desktop WSL", () => {
).toBe(true);
});
});

describe("buildDockerGpuModeCandidates on Docker Desktop WSL (#5512)", () => {
it("skips the CDI mode on Docker Desktop WSL even when CDI is advertised", () => {
// Docker Desktop WSL advertises CDI dirs but has no usable nvidia.com/gpu
// spec, so CDI fails the real recreate; the patch must use --gpus instead.
const modes = buildDockerGpuModeCandidates("all", {
cdiAvailable: true,
dockerDesktopWsl: true,
});
expect(modes.map((mode) => mode.kind)).not.toContain("cdi");
expect(modes[0]?.kind).toBe("gpus");
expect(modes[0]?.args).toEqual(["--gpus", "all"]);
});

it("keeps CDI first on a non-Docker-Desktop-WSL host that advertises CDI", () => {
const modes = buildDockerGpuModeCandidates("all", {
cdiAvailable: true,
dockerDesktopWsl: false,
});
expect(modes[0]?.kind).toBe("cdi");
expect(modes[0]?.args).toEqual(["--device", "nvidia.com/gpu=all"]);
});
});
33 changes: 29 additions & 4 deletions src/lib/onboard/docker-gpu-patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,11 @@ export function buildDockerGpuMode(

export function buildDockerGpuModeCandidates(
device?: string | null,
options: { cdiAvailable?: boolean; backend?: DockerGpuPatchBackend } = {},
options: {
cdiAvailable?: boolean;
backend?: DockerGpuPatchBackend;
dockerDesktopWsl?: boolean;
} = {},
): DockerGpuPatchMode[] {
if (options.backend === "jetson") {
return [buildDockerGpuMode("nvidia-runtime", device, { backend: "jetson" })];
Expand All @@ -510,8 +514,16 @@ export function buildDockerGpuModeCandidates(
// gateway wiring and the supervisor never reconnects (#4948). Keep --gpus
// and the NVIDIA runtime as fallbacks until OpenShell exposes an
// authoritative GPU mode contract that can replace CDI-spec probing.
//
// #5512: Docker Desktop WSL advertises CDI spec directories (so cdiAvailable
// is true) while the WSL distro has no usable nvidia.com/gpu spec. There the
// CDI mode passes the create-only probe but fails the real recreate with
// "unresolvable CDI devices nvidia.com/gpu=all", so skip CDI and use the
// --gpus compatibility path that preflight already commits to on this runtime.
const candidates: DockerGpuPatchMode[] = [];
if (options.cdiAvailable) candidates.push(buildDockerGpuMode("cdi", device));
if (options.cdiAvailable && !options.dockerDesktopWsl) {
candidates.push(buildDockerGpuMode("cdi", device));
}
candidates.push(buildDockerGpuMode("gpus", device), buildDockerGpuMode("nvidia-runtime", device));
return candidates;
}
Expand Down Expand Up @@ -949,14 +961,20 @@ function probeDockerGpuMode(
}

export function selectDockerGpuPatchMode(
options: { image: string; device?: string | null; backend?: DockerGpuPatchBackend },
options: {
image: string;
device?: string | null;
backend?: DockerGpuPatchBackend;
dockerDesktopWsl?: boolean;
},
deps: DockerGpuPatchDeps = {},
): { mode: DockerGpuPatchMode | null; attempts: DockerGpuPatchModeAttempt[] } {
const cdiAvailable = options.backend === "jetson" ? false : dockerReportsNvidiaCdiDevices(deps);
const attempts: DockerGpuPatchModeAttempt[] = [];
for (const mode of buildDockerGpuModeCandidates(options.device, {
cdiAvailable,
backend: options.backend,
dockerDesktopWsl: options.dockerDesktopWsl,
})) {
const result = probeDockerGpuMode(mode, options.image, deps);
const attempt = { mode, ok: result.ok, error: result.error };
Expand Down Expand Up @@ -1014,6 +1032,7 @@ export function recreateOpenShellDockerSandboxWithGpu(
waitForSupervisor?: boolean;
openshellSandboxCommand?: readonly string[] | null;
backend?: DockerGpuPatchBackend;
dockerDesktopWsl?: boolean;
},
deps: DockerGpuPatchDeps = {},
): DockerGpuPatchResult {
Expand All @@ -1037,7 +1056,12 @@ export function recreateOpenShellDockerSandboxWithGpu(
if (!image) throw new Error("OpenShell sandbox container inspect did not include an image.");

const selection = selectDockerGpuPatchMode(
{ image, device: options.gpuDevice, backend: options.backend },
{
image,
device: options.gpuDevice,
backend: options.backend,
dockerDesktopWsl: options.dockerDesktopWsl,
},
deps,
);
context.modeAttempts = selection.attempts;
Expand Down Expand Up @@ -1184,6 +1208,7 @@ export function applyDockerGpuPatchOrExit(
// /dev/nvmap group access.
backend?: DockerGpuPatchBackend;
openshellSandboxCommand?: readonly string[] | null;
dockerDesktopWsl?: boolean;
},
deps: Pick<DockerGpuPatchDeps, "runOpenshell" | "runCaptureOpenshell" | "sleep">,
): DockerGpuPatchResult {
Expand Down
7 changes: 7 additions & 0 deletions src/lib/onboard/docker-gpu-sandbox-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ type DockerGpuSandboxCreatePatchOptions = {
openshellSandboxCommand?: readonly string[] | null;
timeoutSecs: number;
backend?: DockerGpuPatchBackend;
/**
* Whether the host is Docker Desktop WSL. Defaults to the cached
* `isDockerDesktopWslRuntime()` probe. When true, the GPU patch skips the CDI
* mode (unusable on this runtime) and uses `--gpus` instead (#5512).
*/
dockerDesktopWsl?: boolean;
deps: DockerGpuSandboxCreateDeps;
/**
* Test seams. The production composition uses the canonical
Expand Down Expand Up @@ -136,6 +142,7 @@ export function createDockerGpuSandboxCreatePatch(
openshellSandboxCommand: options.openshellSandboxCommand ?? null,
timeoutSecs: options.timeoutSecs,
backend: options.backend,
dockerDesktopWsl: options.dockerDesktopWsl ?? isDockerDesktopWslRuntime(),
};

return {
Expand Down