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
4 changes: 3 additions & 1 deletion docs/get-started/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ Use these details when your first-run path needs more control.
curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash -s -- --yes-i-accept-third-party-software
```

For a non-interactive first run, also set the provider, credential, and sandbox name.
For a non-interactive first run, set the sandbox name.
Set the provider and matching credential unless you want DGX Spark to select local vLLM automatically.

```bash
curl -fsSL https://www.nvidia.com/nemoclaw.sh | \
Expand All @@ -113,6 +114,7 @@ Use these details when your first-run path needs more control.
The example uses NVIDIA Endpoints.
Set `NEMOCLAW_AGENT` to `hermes` or `langchain-deepagents-code` to install another agent.
Set `NEMOCLAW_PROVIDER` and the matching credential variable for another provider, then use a sandbox name that does not depend on a previous onboarding session.
On DGX Spark, omit `NEMOCLAW_PROVIDER` only when you want the automatic local selection described in [Set Up vLLM](../inference/local-inference/set-up-vllm#run-non-interactive-onboarding).
To select a specific NemoClaw release, replace `vX.Y.Z` with its versioned release tag.
`NEMOCLAW_INSTALL_REF` is a higher-priority development override, so clear it when pinning a release tag.

Expand Down
6 changes: 6 additions & 0 deletions docs/inference/set-up-vllm.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ For the current support status and direct GPU policy boundaries, see [Platform S

## Run Non-Interactive Onboarding

On DGX Spark, non-interactive onboarding can select local vLLM when `NEMOCLAW_PROVIDER` is unset and no provider is recorded for the sandbox.
It reuses a running local vLLM server first.
If no server is running, it selects the managed install or start entry.
If neither local entry is available, it selects NVIDIA Endpoints.
On DGX Station and other hosts, an unset provider keeps NVIDIA Endpoints as the automatic default.

Use an already-running server.

```bash
Expand Down
42 changes: 42 additions & 0 deletions src/lib/onboard/provider-selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const remoteProviderConfig = {
hermesProvider: { providerName: "hermes-provider" },
};

// Ternary accessor (no `if`, per the changed-test-file conditionals guardrail).
const selectedKey = (result: ReturnType<typeof resolveRequestedProviderSelection>) =>
result.kind === "selected" ? result.selected.key : null;

function resolve(overrides: Partial<Parameters<typeof resolveRequestedProviderSelection>[0]> = {}) {
return resolveRequestedProviderSelection({
options: [option("build")],
Expand Down Expand Up @@ -128,4 +132,42 @@ describe("resolveRequestedProviderSelection", () => {
assert.equal(result.recoveredFromSandbox, false);
}
});

it("auto-selects managed vLLM on a DGX managed-vLLM platform when no provider is given (#7293)", () => {
const result = resolve({
options: [option("build"), option("install-vllm")],
preferManagedVllmDefault: true,
});

assert.equal(selectedKey(result), "install-vllm");
});

it("auto-selects an already-running local vLLM on a managed-vLLM platform (#7293)", () => {
// When vLLM is already running, the menu exposes only `vllm` (not install-vllm).
const result = resolve({
options: [option("build"), option("vllm")],
preferManagedVllmDefault: true,
});

assert.equal(selectedKey(result), "vllm");
});

it("keeps the cloud default when the caller does not prefer managed vLLM (#7293)", () => {
// The menu can expose managed vLLM without changing the automatic selection.
const result = resolve({
options: [option("build"), option("install-vllm")],
preferManagedVllmDefault: false,
});

assert.equal(selectedKey(result), "build");
});

it("keeps the cloud default when no managed-vLLM entry is available (#7293)", () => {
const result = resolve({
options: [option("build"), option("openai")],
preferManagedVllmDefault: true,
});

assert.equal(selectedKey(result), "build");
});
});
26 changes: 25 additions & 1 deletion src/lib/onboard/provider-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,34 @@ export interface ResolveRequestedProviderSelectionInput<T extends ProviderOption
isWindowsHostOllama: boolean;
windowsHostOllamaSupported: boolean;
hermesProviderAvailable: boolean;
/**
* On a platform where managed vLLM is the approved non-interactive default,
* an onboard with no requested/recorded provider should auto-select local
* vLLM instead of falling back to cloud `build` (#7293).
*/
preferManagedVllmDefault?: boolean;
}

function findOption<T extends ProviderOption>(options: T[], key: string): T | undefined {
return options.find((option) => option.key === key);
}

/**
* On a managed-vLLM-default platform (#7293), pick the available local vLLM menu
* option: `vllm` when a server is already running (the menu exposes only that
* entry), otherwise the managed install `install-vllm`. Returns null when the
* preference is off or neither entry is present, so the caller falls back to
* cloud `build`.
*/
function resolveManagedVllmDefaultKey<T extends ProviderOption>(
input: ResolveRequestedProviderSelectionInput<T>,
): string | null {
if (!input.preferManagedVllmDefault) return null;
if (findOption(input.options, "vllm")) return "vllm";
if (findOption(input.options, "install-vllm")) return "install-vllm";
return null;
}

function findWindowsHostKey(options: ProviderOption[]): string | null {
return (
options.find((option) => option.key === "start-windows-ollama")?.key ||
Expand Down Expand Up @@ -118,7 +140,9 @@ export function resolveRequestedProviderSelection<T extends ProviderOption>(
recoveredFromSandbox = true;
recoveredModel = input.readRecordedModel(input.sandboxName);
} else {
providerKey = "build";
// Prefer managed local vLLM when the caller has approved that platform
// default; otherwise fall back to cloud NVIDIA Endpoints (#7293).
providerKey = resolveManagedVllmDefaultKey(input) ?? "build";
}
}

Expand Down
130 changes: 130 additions & 0 deletions src/lib/onboard/setup-nim-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,136 @@ describe("createSetupNim", () => {
});
});

it("auto-selects managed vLLM on a DGX Spark non-interactive run with no requested provider (#7293)", async () => {
const profile = { name: "DGX Spark" } as VllmProfile;
const prompt = vi.fn(async () => unexpected("provider prompt"));
const detectInferenceProviderHostState = vi.fn(() =>
makeHostState({
vllmProfile: profile,
hasVllmImage: true,
vllmEntries: [{ key: "install-vllm", label: "Start vLLM (DGX Spark)" }],
}),
);
const installVllm = vi.fn<SetupNimFlowDeps["installVllm"]>(async (_profile, options) => {
options.beforeInstall?.("vllm-model");
return { ok: true };
});
const routeGuard = vi.fn(() => ({
requiredModel: null,
requiredEndpointUrl: null,
requiredInferenceApi: null,
}));
const handleVllmSelection = vi.fn<SetupNimFlowDeps["handleVllmSelection"]>(async (state) => {
state.provider = "vllm";
state.endpointUrl = "http://127.0.0.1:8000/v1";
state.credentialEnv = null;
state.preferredInferenceApi = "openai-completions";
return "selected";
});
const setupNim = createSetupNim(
makeDeps({
isNonInteractive: () => true,
// No explicit provider: the DGX Spark platform default must still pick
// managed vLLM instead of falling back to the cloud `build` handler
// (handleRemoteProviderSelection stays `unexpected`, so a fallback throws).
getNonInteractiveProvider: () => null,
prompt,
detectInferenceProviderHostState,
installVllm,
handleVllmSelection,
}),
);

const sparkGpu = { platform: "spark" } as unknown as Parameters<typeof setupNim>[0];
const result = await setupNim(sparkGpu, null, null, true, null, "nemoclaw", routeGuard);

expect(installVllm).toHaveBeenCalledWith(
profile,
expect.objectContaining({ hasImage: true, nonInteractive: true }),
);
expect(handleVllmSelection).toHaveBeenCalledOnce();
expect(prompt).not.toHaveBeenCalled();
expect(result).toMatchObject({ provider: "vllm" });
});

it("reuses an already-running local vLLM on a DGX Spark non-interactive run with no requested provider (#7293)", async () => {
const profile = { name: "DGX Spark" } as VllmProfile;
// vLLM already running → the menu exposes only `vllm`; the no-provider
// default must reuse it (handleVllmSelection) and never reinstall
// (installVllm stays `unexpected`) or fall back to the cloud handler.
const detectInferenceProviderHostState = vi.fn(() =>
makeHostState({
vllmRunning: true,
vllmProfile: profile,
hasVllmImage: true,
vllmEntries: [{ key: "vllm", label: "Local vLLM (localhost:8000) — running (suggested)" }],
}),
);
const routeGuard = vi.fn(() => ({
requiredModel: null,
requiredEndpointUrl: null,
requiredInferenceApi: null,
}));
const handleVllmSelection = vi.fn<SetupNimFlowDeps["handleVllmSelection"]>(async (state) => {
state.provider = "vllm";
state.model = "vllm-model";
state.endpointUrl = "http://127.0.0.1:8000/v1";
state.credentialEnv = null;
state.preferredInferenceApi = "openai-completions";
return "selected";
});
const setupNim = createSetupNim(
makeDeps({
isNonInteractive: () => true,
getNonInteractiveProvider: () => null,
detectInferenceProviderHostState,
handleVllmSelection,
}),
);

const sparkGpu = { platform: "spark" } as unknown as Parameters<typeof setupNim>[0];
const result = await setupNim(sparkGpu, null, null, true, null, "nemoclaw", routeGuard);

expect(handleVllmSelection).toHaveBeenCalledOnce();
expect(handleVllmSelection).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ managedInstall: false }),
);
expect(result).toMatchObject({ provider: "vllm" });
});

it("does not extend the Spark automatic default to DGX Station (#7293)", async () => {
const handleRemoteProviderSelection = vi.fn<SetupNimFlowDeps["handleRemoteProviderSelection"]>(
async ({ selected }, state) => {
expect(selected.key).toBe("build");
state.model = "nvidia/nemotron-3-ultra-550b-a55b";
state.provider = "nvidia-prod";
state.endpointUrl = "https://integrate.api.nvidia.com/v1";
state.credentialEnv = "NVIDIA_INFERENCE_API_KEY";
state.preferredInferenceApi = "openai-completions";
return "selected";
},
);
const setupNim = createSetupNim(
makeDeps({
isNonInteractive: () => true,
getNonInteractiveProvider: () => null,
detectInferenceProviderHostState: () =>
makeHostState({
vllmProfile: { name: "DGX Station" } as VllmProfile,
vllmEntries: [{ key: "install-vllm", label: "Start vLLM (DGX Station)" }],
}),
handleRemoteProviderSelection,
}),
);

const stationGpu = { platform: "station" } as unknown as Parameters<typeof setupNim>[0];
const result = await setupNim(stationGpu);

expect(handleRemoteProviderSelection).toHaveBeenCalledOnce();
expect(result).toMatchObject({ provider: "nvidia-prod" });
});

it("threads the DGX Station express model through the standard managed-vLLM selection contract", async () => {
const profile = { name: "DGX Station", platform: "station" } as VllmProfile;
const servedModel = "nvidia/nemotron-3-ultra-550b-a55b";
Expand Down
1 change: 1 addition & 0 deletions src/lib/onboard/setup-nim-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ export function createSetupNim(
isWindowsHostOllama,
windowsHostOllamaSupported: windowsHostOllamaDockerRequirement.supported,
hermesProviderAvailable,
preferManagedVllmDefault: gpu?.platform === "spark",
...recordedProviderReaders,
});
if (providerSelection.kind === "failure") {
Expand Down
16 changes: 15 additions & 1 deletion src/lib/onboard/vllm-menu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import assert from "node:assert/strict";

import { describe, it } from "vitest";

import { buildVllmMenuEntries } from "./vllm-menu";
import { buildVllmMenuEntries, isManagedVllmDefaultPlatform } from "./vllm-menu";

describe("buildVllmMenuEntries", () => {
it("returns no entries when nothing is running, no profile, and no opt-in", () => {
Expand Down Expand Up @@ -182,3 +182,17 @@ describe("buildVllmMenuEntries", () => {
assert.deepEqual(logs, []);
});
});

describe("isManagedVllmDefaultPlatform (#7293)", () => {
it("is true for the DGX managed-vLLM default platforms", () => {
assert.equal(isManagedVllmDefaultPlatform("spark"), true);
assert.equal(isManagedVllmDefaultPlatform("station"), true);
});

it("is false for other or missing platforms", () => {
assert.equal(isManagedVllmDefaultPlatform("linux"), false);
assert.equal(isManagedVllmDefaultPlatform("jetson"), false);
assert.equal(isManagedVllmDefaultPlatform(null), false);
assert.equal(isManagedVllmDefaultPlatform(undefined), false);
});
});
11 changes: 7 additions & 4 deletions src/lib/onboard/vllm-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ interface VllmProfileShape {

const MANAGED_VLLM_DEFAULT_PLATFORMS = new Set<NvidiaPlatform>(["spark", "station"]);

/** DGX platforms where the provider menu exposes managed vLLM without `experimental`. */
export function isManagedVllmDefaultPlatform(platform: NvidiaPlatform | null | undefined): boolean {
return platform != null && MANAGED_VLLM_DEFAULT_PLATFORMS.has(platform);
}

export interface VllmMenuEntry {
key: "vllm" | "install-vllm";
label: string;
Expand Down Expand Up @@ -62,8 +67,7 @@ export function buildVllmMenuEntries(opts: BuildVllmMenuOptions): VllmMenuEntry[
` Note: NEMOCLAW_PROVIDER=install-vllm requested, but vLLM is already running on localhost:${VLLM_PORT} — selecting the running instance.`,
);
}
const experimentalLabel =
opts.platform && MANAGED_VLLM_DEFAULT_PLATFORMS.has(opts.platform) ? "" : " [experimental]";
const experimentalLabel = isManagedVllmDefaultPlatform(opts.platform) ? "" : " [experimental]";
return [
{
key: "vllm",
Expand All @@ -73,8 +77,7 @@ export function buildVllmMenuEntries(opts: BuildVllmMenuOptions): VllmMenuEntry[
}
if (
userChoseManagedVllm ||
(opts.vllmProfile &&
(opts.experimental || (opts.platform && MANAGED_VLLM_DEFAULT_PLATFORMS.has(opts.platform))))
(opts.vllmProfile && (opts.experimental || isManagedVllmDefaultPlatform(opts.platform)))
) {
const verb = opts.hasVllmImage ? "Start" : "Install";
const profileLabel = opts.vllmProfile?.name ?? "no profile detected";
Expand Down
Loading