Skip to content

[Bug]: @qvac/diffusion-cpp still DT_NEEDED-links libvulkan.so.1 on linux-arm64; eager addon load blocks all CPU inference (follow-up to #1823) #3853

Description

@hevans714

🐛 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-audiobare-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 }
      }
    }
  }
}
  1. Build and run on linux/arm64.
  2. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions