🐛 Bug Description
On a headless, CPU-only linux-arm64 host, no model can be loaded with @qvac/sdk@0.17.0. The bare worker aborts during startup and the server reports only RPC initialization timed out after 30000ms.
The cause is libvulkan.so.1, which is the same class of problem as #1823 ("device: cpu should not be affected by GPU dependencies"). That issue was closed on 2026-08-07 on the basis of an artifact audit of @qvac/llm-llamacpp and @qvac/decoder-audio → bare-ffmpeg.
I can confirm both of those are now clean. The remaining offender is a third addon that the audit did not cover: @qvac/diffusion-cpp, which still DT_NEEDED-links libvulkan.so.1 and which the default worker loads eagerly at startup — even when the config declares only an LLM and image generation is never called.
readelf -d on the linux-arm64 prebuilds from a clean install of @qvac/cli (SDK 0.17.0):
| addon |
version |
DT_NEEDED vulkan/va/drm |
@qvac/llm-llamacpp |
0.39.4 |
none ✅ (fix from #1823 confirmed landed) |
@qvac/embed-llamacpp |
0.30.1 |
none ✅ |
@qvac/diffusion-cpp |
0.17.0 |
libvulkan.so.1 ❌ |
So the lazy-backend work done for llm-llamacpp achieves nothing for a CPU-only host as long as the worker eagerly requires diffusion-cpp: one unused modality engine takes down all inference, including plain LLM completion.
🔄 Steps to Reproduce
Minimal, from a clean image with no GPU stack (no Vulkan loader, no ICD):
FROM node:22-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 build-essential ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g @qvac/cli
WORKDIR /app
CMD ["qvac", "serve", "openai", "--host", "0.0.0.0", "--port", "11434"]
qvac.config.json declares a single LLM and nothing else:
{
"serve": {
"models": {
"default": {
"model": "QWEN3_600M_INST_Q4",
"default": true,
"config": { "ctx_size": 8192 }
}
}
}
}
- Build and run on
linux/arm64.
- The server starts and serves routes, but
GET /v1/models returns {"object":"list","data":[]} and every completion returns 503.
✅ Expected Behavior
A config that declares only an LLM should not load the image-generation engine, and a host with no Vulkan loader should still run CPU inference — the same outcome #1823 established for llm-llamacpp. Either load diffusion-cpp lazily when an image request first arrives, or dlopen its Vulkan backend as a separate module the way llm-llamacpp now does.
❌ Actual Behavior
The worker aborts before it can answer the RPC handshake, so no model ever loads.
Server-side, the only visible symptom is:
Preloading 1 model(s): default
Loading model "default" from registry://hf/unsloth/Qwen3-0.6B-GGUF/blob/.../Qwen3-0.6B-Q4_0.gguf...
Failed to preload "default": RPC initialization timed out after 30000ms — the worker process may have failed to start
📜 Stack Trace / Error Output
The real error is only visible by running the worker entry directly — node-rpc-client.js spawns it with stdio: ['inherit','inherit','pipe'], so the worker's stderr never reaches the user:
bare .../@qvac/sdk/dist/server/worker.js /root '{"QVAC_IPC_SOCKET_PATH":"/tmp/x.sock","HOME_DIR":"/root"}'
Uncaught AddonError: CANNOT_LOAD: Cannot load addon 'file:///usr/local/lib/node_modules/@qvac/cli/node_modules/@qvac/diffusion-cpp/prebuilds/linux-arm64/qvac__diffusion-cpp.bare'
at new Addon (bare:/bare.js:2613:24)
at Addon.load (bare:/bare.js:2652:31)
at require.addon (bare:/bare.bundle/node_modules/bare-module/index.js:822:30)
at file:///usr/local/lib/node_modules/@qvac/cli/node_modules/@qvac/diffusion-cpp/binding.js:1:26
at Module._evaluate (bare:/bare.bundle/node_modules/bare-module/index.js:215:7)
at Module.load (bare:/bare.bundle/node_modules/bare-module/index.js:477:21)
at require (bare:/bare.bundle/node_modules/bare-module/index.js:807:27)
at file:///usr/local/lib/node_modules/@qvac/cli/node_modules/@qvac/diffusion-cpp/addonLogging.js:4:17 {
[cause]: Error: libvulkan.so.1: cannot open shared object file: No such file or directory,
code: 'CANNOT_LOAD'
}
Aborted
🩹 Workaround
Install the Vulkan loader only — apt-get install -y libvulkan1. With no ICD present Vulkan enumerates zero devices and the CPU path proceeds normally. This is the same shape as the workaround reported in #1823 by the embedded-ARM evaluation.
After that single change, on the same host: model loads, and qvac_benchmark-style throughput on Qwen3-0.6B-Q4_0 is ~15.8 tok/s warm median (~16s for a 250-token generation), which is a perfectly reasonable CPU result. The engine works fine — it was only the eager addon load standing in the way.
⚠️ Secondary: qvac doctor reports this as non-fatal
Worth fixing alongside, because it actively points people away from the cause. On the broken host, doctor prints:
⚠️ GPU acceleration — Vulkan ICD not found
... Without a Vulkan ICD, GPU acceleration is unavailable and inference runs on
CPU, which is significantly slower.
...
✅ All required checks passed.
Both statements are wrong in this configuration: inference does not run on CPU, it does not run at all, and the required checks have not passed. Something like a DT_NEEDED check across the addons the default worker eagerly loads would turn this into an accurate, fatal error.
I also spent a while chasing ⚠️ @qvac/sdk resolvable from project — not found, which doctor reports when the CLI is installed globally with an empty WORKDIR. That one is accurate but is not the cause of the failure — making it resolvable changes nothing. Only mentioning it because it is the natural first suspect when the worker won't start.
Happy to test a patched build on this host if that is useful — it is a reproducible CPU-only arm64 environment.
💻 Platform / OS
Linux (arm64) — node:22-slim container on Apple Silicon (Docker, linux/arm64)
⚙️ Runtime Environment
Node.js (@qvac/cli, qvac serve openai)
📦 Runtime Version
Node.js v22.23.2 · bare v1.31.0 · bare-runtime-linux-arm64 present
🏷️ SDK Version
0.17.0
🔁 Frequency
Always (100%)
🔥 Severity
Critical - Complete blocker, no workaround (before the loader is installed)
🐛 Bug Description
On a headless, CPU-only
linux-arm64host, no model can be loaded with@qvac/sdk@0.17.0. The bare worker aborts during startup and the server reports onlyRPC initialization timed out after 30000ms.The cause is
libvulkan.so.1, which is the same class of problem as #1823 ("device: cpu should not be affected by GPU dependencies"). That issue was closed on 2026-08-07 on the basis of an artifact audit of@qvac/llm-llamacppand@qvac/decoder-audio→bare-ffmpeg.I can confirm both of those are now clean. The remaining offender is a third addon that the audit did not cover:
@qvac/diffusion-cpp, which stillDT_NEEDED-linkslibvulkan.so.1and which the default worker loads eagerly at startup — even when the config declares only an LLM and image generation is never called.readelf -don thelinux-arm64prebuilds from a clean install of@qvac/cli(SDK 0.17.0):DT_NEEDEDvulkan/va/drm@qvac/llm-llamacpp@qvac/embed-llamacpp@qvac/diffusion-cpplibvulkan.so.1❌So the lazy-backend work done for
llm-llamacppachieves nothing for a CPU-only host as long as the worker eagerly requiresdiffusion-cpp: one unused modality engine takes down all inference, including plain LLM completion.🔄 Steps to Reproduce
Minimal, from a clean image with no GPU stack (no Vulkan loader, no ICD):
qvac.config.jsondeclares a single LLM and nothing else:{ "serve": { "models": { "default": { "model": "QWEN3_600M_INST_Q4", "default": true, "config": { "ctx_size": 8192 } } } } }linux/arm64.GET /v1/modelsreturns{"object":"list","data":[]}and every completion returns 503.✅ Expected Behavior
A config that declares only an LLM should not load the image-generation engine, and a host with no Vulkan loader should still run CPU inference — the same outcome #1823 established for
llm-llamacpp. Either loaddiffusion-cpplazily when an image request first arrives, ordlopenits Vulkan backend as a separate module the wayllm-llamacppnow does.❌ Actual Behavior
The worker aborts before it can answer the RPC handshake, so no model ever loads.
Server-side, the only visible symptom is:
📜 Stack Trace / Error Output
The real error is only visible by running the worker entry directly —
node-rpc-client.jsspawns it withstdio: ['inherit','inherit','pipe'], so the worker's stderr never reaches the user:🩹 Workaround
Install the Vulkan loader only —
apt-get install -y libvulkan1. With no ICD present Vulkan enumerates zero devices and the CPU path proceeds normally. This is the same shape as the workaround reported in #1823 by the embedded-ARM evaluation.After that single change, on the same host: model loads, and
qvac_benchmark-style throughput onQwen3-0.6B-Q4_0is ~15.8 tok/s warm median (~16s for a 250-token generation), which is a perfectly reasonable CPU result. The engine works fine — it was only the eager addon load standing in the way.qvac doctorreports this as non-fatalWorth fixing alongside, because it actively points people away from the cause. On the broken host,
doctorprints:Both statements are wrong in this configuration: inference does not run on CPU, it does not run at all, and the required checks have not passed. Something like a
DT_NEEDEDcheck across the addons the default worker eagerly loads would turn this into an accurate, fatal error.I also spent a while chasing
⚠️ @qvac/sdk resolvable from project — not found, whichdoctorreports when the CLI is installed globally with an emptyWORKDIR. That one is accurate but is not the cause of the failure — making it resolvable changes nothing. Only mentioning it because it is the natural first suspect when the worker won't start.Happy to test a patched build on this host if that is useful — it is a reproducible CPU-only arm64 environment.
💻 Platform / OS
Linux (arm64) —
node:22-slimcontainer on Apple Silicon (Docker,linux/arm64)⚙️ Runtime Environment
Node.js (
@qvac/cli,qvac serve openai)📦 Runtime Version
Node.js v22.23.2 ·
barev1.31.0 ·bare-runtime-linux-arm64present🏷️ SDK Version
0.17.0
🔁 Frequency
Always (100%)
🔥 Severity
Critical - Complete blocker, no workaround (before the loader is installed)