Environment
- OS: Windows 11 (10.0.26200)
- Node: v24.14.0
- node-llama-cpp: 3.18.1 and 3.19.1 (both affected)
- GPU: Intel Iris Xe Graphics (Vulkan available, `vulkan-1.dll` present in System32)
Problem
`getLlama({ gpu: "vulkan" })` fails with:
```
Failed to load a prebuilt binary for platform "win" "x64" with Vulkan support, falling back to building from source.
Error: Binding binary test failed to run a test process via file ".../testBindingBinary.js"
```
The Vulkan prebuilt binary itself is fine — `require(llama-addon.node)` + `loadBackends()` + `init()` + `getGpuType()` + `ensureGpuDeviceIsSupported()` all succeed when called directly in a child process. The failure is in `testBindingBinary.js`'s child process orchestration, not the binary.
Root cause
`testBindingBinary` forks a child process to test the binary before loading it. When `pipeOutputOnNode=false` (the default), the child is spawned with `stdio: ["ignore","ignore","ignore","ipc"]` (testBindingBinary.js:96-98). On Windows, the forked child process exits without sending the `ready` IPC message, so `forkSucceeded` stays `false` and the parent reports `"Binding binary test failed to run a test process"` (testBindingBinary.js:167).
When `pipeOutputOnNode=true` (pipe stdio instead of ignore), the child sends `ready` normally and the test passes — confirmed by calling `getLlamaForOptions({gpu:"vulkan"}, {pipeBinaryTestErrorLogs: true})`, which succeeds.
Workaround that works
`getLlamaForOptions({ gpu: "vulkan" }, { pipeBinaryTestErrorLogs: true })` succeeds — `pipeBinaryTestErrorLogs` is forwarded as `pipeOutputOnNode` to `testBindingBinary`, switching to pipe stdio.
However, the public `getLlama()` API does not pass `pipeBinaryTestErrorLogs` to `getLlamaForOptions` — getLlama.js:94 calls `getLlamaForOptions(options ?? {})` with no second argument, so the option defaults to `false`. Users cannot benefit from the workaround through the public API.
An odd detail: calling `getLlamaForOptions` directly (via file:// import) with the same options succeeds, but going through `getLlama` (which calls `getLlamaForOptions` with the same args) still fails — suggesting the issue may be sensitive to the ESM module instance / call context, not just the option value.
Suggested fix
Either:
- Default `pipeBinaryTestErrorLogs` to `true` on Windows in `getLlama` (so the public API benefits from pipe stdio), or
- Investigate why `stdio: "ignore"` causes the forked child to not send `ready` on Windows — possibly a Node.js fork/IPC issue with ignored stdio on Windows.
Reproduction
```js
import { getLlama } from "node-llama-cpp";
// Fails on Windows (falls back to building from source, or to CPU with gpu:"auto"):
await getLlama({ gpu: "vulkan" });
// Works (uses the pipeBinaryTestErrorLogs workaround):
const path = require("path");
const indexPath = require.resolve("node-llama-cpp");
const glPath = path.join(path.dirname(path.dirname(indexPath)), "dist", "bindings", "getLlama.js").split(path.sep).join("/");
const mod = await import("file:///" + glPath);
const llama = await mod.getLlamaForOptions({ gpu: "vulkan" }, { pipeBinaryTestErrorLogs: true });
```
`inspect gpu` confirms Vulkan is available and detected (Intel Iris Xe, 15.8GB VRAM), so this is not a driver/runtime issue.
Environment
Problem
`getLlama({ gpu: "vulkan" })` fails with:
```
Failed to load a prebuilt binary for platform "win" "x64" with Vulkan support, falling back to building from source.
Error: Binding binary test failed to run a test process via file ".../testBindingBinary.js"
```
The Vulkan prebuilt binary itself is fine — `require(llama-addon.node)` + `loadBackends()` + `init()` + `getGpuType()` + `ensureGpuDeviceIsSupported()` all succeed when called directly in a child process. The failure is in `testBindingBinary.js`'s child process orchestration, not the binary.
Root cause
`testBindingBinary` forks a child process to test the binary before loading it. When `pipeOutputOnNode=false` (the default), the child is spawned with `stdio: ["ignore","ignore","ignore","ipc"]` (testBindingBinary.js:96-98). On Windows, the forked child process exits without sending the `ready` IPC message, so `forkSucceeded` stays `false` and the parent reports `"Binding binary test failed to run a test process"` (testBindingBinary.js:167).
When `pipeOutputOnNode=true` (pipe stdio instead of ignore), the child sends `ready` normally and the test passes — confirmed by calling `getLlamaForOptions({gpu:"vulkan"}, {pipeBinaryTestErrorLogs: true})`, which succeeds.
Workaround that works
`getLlamaForOptions({ gpu: "vulkan" }, { pipeBinaryTestErrorLogs: true })` succeeds — `pipeBinaryTestErrorLogs` is forwarded as `pipeOutputOnNode` to `testBindingBinary`, switching to pipe stdio.
However, the public `getLlama()` API does not pass `pipeBinaryTestErrorLogs` to `getLlamaForOptions` — getLlama.js:94 calls `getLlamaForOptions(options ?? {})` with no second argument, so the option defaults to `false`. Users cannot benefit from the workaround through the public API.
An odd detail: calling `getLlamaForOptions` directly (via file:// import) with the same options succeeds, but going through `getLlama` (which calls `getLlamaForOptions` with the same args) still fails — suggesting the issue may be sensitive to the ESM module instance / call context, not just the option value.
Suggested fix
Either:
Reproduction
```js
import { getLlama } from "node-llama-cpp";
// Fails on Windows (falls back to building from source, or to CPU with gpu:"auto"):
await getLlama({ gpu: "vulkan" });
// Works (uses the pipeBinaryTestErrorLogs workaround):
const path = require("path");
const indexPath = require.resolve("node-llama-cpp");
const glPath = path.join(path.dirname(path.dirname(indexPath)), "dist", "bindings", "getLlama.js").split(path.sep).join("/");
const mod = await import("file:///" + glPath);
const llama = await mod.getLlamaForOptions({ gpu: "vulkan" }, { pipeBinaryTestErrorLogs: true });
```
`inspect gpu` confirms Vulkan is available and detected (Intel Iris Xe, 15.8GB VRAM), so this is not a driver/runtime issue.