Fix Windows CUDA context parallelism
This commit is contained in:
parent
dff6513693
commit
e8229d8bfb
@ -12,6 +12,7 @@
|
||||
- Hybrid search: weight RRF lists by query type so original FTS and original vector evidence get the intended 2x boost, instead of accidentally boosting the first lexical expansion. #591
|
||||
- MCP: seed llama.cpp/GGML quiet env vars before launching `qmd mcp` so native logs cannot pollute stdio JSON-RPC framing. #593
|
||||
- CLI: remove CommonJS `require()` calls from ESM index path normalization so `qmd --index <path>` no longer crashes with `ERR_AMBIGUOUS_MODULE_SYNTAX` on Node 22+. #634
|
||||
- Windows CUDA: serialize llama.cpp embedding/reranking contexts by default to avoid intermittent `ggml-cuda.cu:98` crashes in `qmd query`; set `QMD_EMBED_PARALLELISM` to opt back into parallel contexts if your driver is stable. #519
|
||||
- GPU: respect explicit `QMD_LLAMA_GPU=metal|vulkan|cuda` backend overrides instead of always using auto GPU selection. #529
|
||||
- Fix: preserve original filename case in `handelize()`. The previous
|
||||
`.toLowerCase()` call made indexed paths unreachable on case-sensitive
|
||||
|
||||
@ -797,6 +797,8 @@ llm_cache -- Cached LLM responses (query expansion, rerank scores)
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `XDG_CACHE_HOME` | `~/.cache` | Cache directory location |
|
||||
| `QMD_LLAMA_GPU` | `auto` | Force llama.cpp GPU backend (`metal`, `vulkan`, `cuda`) or disable GPU with `false` |
|
||||
| `QMD_EMBED_PARALLELISM` | automatic | Override embedding/reranking context parallelism (1-8). Windows CUDA defaults to `1` because parallel CUDA contexts can crash with `ggml-cuda.cu:98`; use Vulkan or raise this only if your driver is stable. |
|
||||
|
||||
## How It Works
|
||||
|
||||
|
||||
44
src/llm.ts
44
src/llm.ts
@ -451,7 +451,41 @@ export type LlamaCppConfig = {
|
||||
const DEFAULT_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
const DEFAULT_EXPAND_CONTEXT_SIZE = 2048;
|
||||
|
||||
type LlamaGpuMode = "auto" | "metal" | "vulkan" | "cuda" | false;
|
||||
export type LlamaGpuMode = "auto" | "metal" | "vulkan" | "cuda" | false;
|
||||
|
||||
type ParallelismOptions = {
|
||||
gpu: string | false;
|
||||
platform?: NodeJS.Platform;
|
||||
computed: number;
|
||||
envValue?: string;
|
||||
};
|
||||
|
||||
export function resolveParallelismOverride(envValue = process.env.QMD_EMBED_PARALLELISM): number | undefined {
|
||||
const normalized = envValue?.trim() ?? "";
|
||||
if (!normalized) return undefined;
|
||||
|
||||
const parsed = Number(normalized);
|
||||
if (!Number.isInteger(parsed) || parsed < 1) {
|
||||
process.stderr.write(`QMD Warning: invalid QMD_EMBED_PARALLELISM="${envValue}", using automatic parallelism.\n`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Math.min(8, parsed);
|
||||
}
|
||||
|
||||
export function resolveSafeParallelism(options: ParallelismOptions): number {
|
||||
const override = resolveParallelismOverride(options.envValue);
|
||||
if (override !== undefined) return override;
|
||||
|
||||
// node-llama-cpp/llama.cpp CUDA on Windows is unstable with multiple
|
||||
// simultaneous contexts (ggml-cuda.cu:98 in #519). Vulkan and CPU do not
|
||||
// show the same failure mode, so only serialize Windows CUDA by default.
|
||||
if ((options.platform ?? process.platform) === "win32" && options.gpu === "cuda") {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Math.max(1, options.computed);
|
||||
}
|
||||
|
||||
export function resolveLlamaGpuMode(envValue = process.env.QMD_LLAMA_GPU): LlamaGpuMode {
|
||||
const normalized = envValue?.trim().toLowerCase() ?? "";
|
||||
@ -726,16 +760,18 @@ export class LlamaCpp implements LLM {
|
||||
const vram = await llama.getVramState();
|
||||
const freeMB = vram.free / (1024 * 1024);
|
||||
const maxByVram = Math.floor((freeMB * 0.25) / perContextMB);
|
||||
return Math.max(1, Math.min(8, maxByVram));
|
||||
const computed = Math.max(1, Math.min(8, maxByVram));
|
||||
return resolveSafeParallelism({ gpu: llama.gpu, computed });
|
||||
} catch {
|
||||
return 2;
|
||||
return resolveSafeParallelism({ gpu: llama.gpu, computed: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
// CPU: split cores across contexts. At least 4 threads per context.
|
||||
const cores = llama.cpuMathCores || 4;
|
||||
const maxContexts = Math.floor(cores / 4);
|
||||
return Math.max(1, Math.min(4, maxContexts));
|
||||
const computed = Math.max(1, Math.min(4, maxContexts));
|
||||
return resolveSafeParallelism({ gpu: false, computed });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -13,6 +13,8 @@ import {
|
||||
getDefaultLlamaCpp,
|
||||
disposeDefaultLlamaCpp,
|
||||
resolveLlamaGpuMode,
|
||||
resolveParallelismOverride,
|
||||
resolveSafeParallelism,
|
||||
withLLMSession,
|
||||
canUnloadLLM,
|
||||
SessionReleasedError,
|
||||
@ -88,6 +90,44 @@ describe("QMD_LLAMA_GPU resolution", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("LLM context parallelism safety", () => {
|
||||
test("defaults Windows CUDA to one context to avoid ggml-cuda.cu:98 crashes", () => {
|
||||
expect(resolveSafeParallelism({
|
||||
gpu: "cuda",
|
||||
platform: "win32",
|
||||
computed: 8,
|
||||
envValue: undefined,
|
||||
})).toBe(1);
|
||||
});
|
||||
|
||||
test("keeps non-Windows and non-CUDA backends on computed parallelism", () => {
|
||||
expect(resolveSafeParallelism({ gpu: "cuda", platform: "linux", computed: 8 })).toBe(8);
|
||||
expect(resolveSafeParallelism({ gpu: "vulkan", platform: "win32", computed: 8 })).toBe(8);
|
||||
expect(resolveSafeParallelism({ gpu: false, platform: "win32", computed: 4 })).toBe(4);
|
||||
});
|
||||
|
||||
test("QMD_EMBED_PARALLELISM overrides the Windows CUDA safety default", () => {
|
||||
expect(resolveSafeParallelism({
|
||||
gpu: "cuda",
|
||||
platform: "win32",
|
||||
computed: 8,
|
||||
envValue: "2",
|
||||
})).toBe(2);
|
||||
});
|
||||
|
||||
test("QMD_EMBED_PARALLELISM clamps invalid values and warns", () => {
|
||||
const stderrSpy = vi.spyOn(process.stderr, "write").mockReturnValue(true);
|
||||
try {
|
||||
expect(resolveParallelismOverride("0")).toBeUndefined();
|
||||
expect(resolveParallelismOverride("bad")).toBeUndefined();
|
||||
expect(stderrSpy).toHaveBeenCalledTimes(2);
|
||||
expect(String(stderrSpy.mock.calls[0]?.[0] || "")).toContain("QMD_EMBED_PARALLELISM");
|
||||
} finally {
|
||||
stderrSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("LlamaCpp expand context size config", () => {
|
||||
const defaultExpandContextSize = 2048;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user