π Bug Description
@qvac/llm-llamacpp ships linux-x64 as a thin loader plus 14 dlopen-able per-microarch CPU backends, selected at runtime by ggml backend score β including ivybridge and sandybridge. It ships win32-x64 as a single statically-linked module with no CPU variant libraries and no runtime dispatch, built at an AVX2/FMA baseline.
Every Windows host older than Haswell therefore dies at first model load with STATUS_ILLEGAL_INSTRUCTION (0xC000001D). The same CPU works fine on Linux.
This is not #3222 / #3364. Those were an unrelated eagerly-loaded plugin (translation-nmtcpp) taking down the worker. We already use the recommended escape hatch β a custom bundle via qvac bundle sdk whose worker registers only llmPlugin, no other addon is ever loaded. The module crashing here is the LLM engine itself.
π Steps to Reproduce
Runtime repro (needs pre-Haswell x86 hardware):
- Any Windows x64 host with AVX but no AVX2/FMA (Sandy/Ivy Bridge, pre-Zen AMD, Goldmont/Tremont Celeron)
- Custom worker bundle registering only
llmPlugin
sdk.loadModel({ modelSrc: QWEN3_1_7B_INST_Q4, modelType: 'llm', modelConfig: { ctx_size: 8192 } })
- Worker dies with
0xC000001D before the RPC handshake
Static repro (no special hardware β runs on the published artifacts):
npm pack @qvac/llm-llamacpp@0.47.0 && tar xzf qvac-llm-llamacpp-0.47.0.tgz
# Their own AVX-only reference build: 486 xmm, 0 ymm
objdump -d package/prebuilds/linux-x64/qvac__llm-llamacpp/libqvac-ggml-cpu-ivybridge.so \
| grep -oE 'vpmaddubsw\s+.*' | grep -oE '%(y|x)mm' | sort | uniq -c
# Their own AVX2 build: 2234 ymm, 0 xmm
objdump -d package/prebuilds/linux-x64/qvac__llm-llamacpp/libqvac-ggml-cpu-haswell.so \
| grep -oE 'vpmaddubsw\s+.*' | grep -oE '%(y|x)mm' | sort | uniq -c
# win32 monolith: 1951 ymm, 0 xmm <-- the AVX-only path is absent, not outnumbered
objdump -d package/prebuilds/win32-x64/qvac__llm-llamacpp.bare \
| grep -oE 'vpmaddubsw\s+.*' | grep -oE '%(y|x)mm' | sort | uniq -c
ls package/prebuilds/win32-x64/ # no CPU variant libraries exist
β
Expected Behavior
win32-x64 selects a CPU backend matching the host, as linux-x64 already does β falling back to the AVX-only or SSE4.2 path on pre-AVX2 hardware, or at minimum failing with a clear "unsupported CPU" error instead of an illegal instruction.
β Actual Behavior
The worker process is killed by the OS with 0xC000001D before the RPC handshake. The SDK surfaces this as a generic init timeout, so the user sees a network-looking error with no hint that the CPU is the cause (error-surfacing side is tracked in #1821).
π Stack Trace / Error Output
Error: RPCInitTimeoutError: RPC initialization timed out after 30000ms
Caused by: Error: Worker process exited with code 3221225501, signal null before IPC connection was established
# 3221225501 = 0xC000001D = STATUS_ILLEGAL_INSTRUCTION
π» Platform / OS
Windows (x64)
π₯οΈ OS Version
Windows 10 x64 (reporter). CPU: Intel Xeon E5-2696 v2 (Ivy Bridge-EP β AVX, no AVX2, no FMA). GPU: AMD Radeon RX 580 (Vulkan).
βοΈ Runtime Environment
Node.js
π¦ Runtime Version
Electron 31.7.7 (Node 20.x) on the client side; worker runs bare-runtime 1.29.5
π·οΈ SDK Version
0.16.0
π Relevant Dependencies
{
"@qvac/sdk": "0.16.0",
"@qvac/llm-llamacpp": "0.38.2",
"bare-runtime": "1.29.5",
"electron": "31.7.7"
}
π Frequency
Always (100%)
π₯ Severity
Critical - Complete blocker, no workaround
π©Ή Workaround
None on Windows. The only escape is a different OS β the Linux build runs on the exact same hardware, because linux-x64 ships the ivybridge backend and Vulkan works on the RX 580 there.
Downstream we now decode the exit status ourselves and latch our circuit breaker so we stop respawning a worker that can never start, but that is error reporting, not a fix.
π Additional Context
Packaging asymmetry
prebuilds/linux-x64/ β loader + 14 variants:
qvac__llm-llamacpp.bare (12.7 MB loader) FMA/AVX2/AVX512: 0
libqvac-ggml-cpu-sandybridge.so vpmaddubsw: 486 x xmm
libqvac-ggml-cpu-ivybridge.so vpmaddubsw: 486 x xmm
libqvac-ggml-cpu-haswell.so vpmaddubsw: 2234 x ymm
+ sse42, x64, piledriver, skylakex, icelake, cannonlake, cascadelake,
cooperlake, sapphirerapids, alderlake, zen4
prebuilds/win32-x64/ β one module, no variants:
qvac__llm-llamacpp.bare (95 MB) vpmaddubsw: 1951 x ymm, 0 x xmm
vfmadd231ps 209, vfmadd213ps 66,
vpermd 67, vpbroadcastd 31, vpsravd 6
On the reliability of this measurement β #3364 correctly established that raw mnemonic counting false-positives on runtime-dispatched code, and that caveat applies here too: this binary also contains AVX-512 mnemonics (52 vmovdqu8) that are certainly dispatched (llamafile/tinyBLAS sgemm, CRT memcpy), since otherwise it would fault on nearly every consumer CPU.
That is why the argument above is not a raw count. It is the register width of the quantized dot-product kernels measured against your own reference builds: your AVX-only variants are 486 xmm / 0 ymm, your AVX2 variant is 2234 ymm / 0 xmm, and win32 is 1951 ymm / 0 xmm. The AVX-only code path is not outnumbered by a dispatched AVX2 path β it is absent from the binary. Those ggml quant kernels are compile-time selected, not runtime-dispatched.
Independently: GGML_CPU_ALL_VARIANTS requires GGML_BACKEND_DL, which emits variant backends as separate shared libraries. prebuilds/win32-x64/ contains none, so the CPU backend is necessarily statically linked at one baseline.
Vulkan does not rescue it. Vulkan is already compiled into the Windows module (ggml-vulkan, GGML_DISABLE_VULKAN, Vulkan 1.2 required are all present in the binary). It never gets a chance: ggml registers and initializes the CPU backend unconditionally at registry construction, before any GPU is consulted. The reporter has a working Vulkan GPU and is blocked by a code path they would never execute.
Not a regression β it has never shipped. Published file listings across the package's history:
| version |
published |
win32-x64 |
linux-x64 |
| 0.1.0 |
2025-08-26 |
1 module, no variants |
β |
| 0.31.2 |
2026-07-06 |
1 module, no variants |
14 variants |
| 0.38.2 |
2026-07-25 |
1 module, no variants |
14 variants |
| 0.47.0 |
2026-08-24 |
1 module, no variants |
14 variants |
So downstreams cannot fix this by upgrading β I verified 0.47.0 before filing.
Build provenance leaked in the binary:
C:\Users\actions-runner\vcpkg\buildtrees\qvac-fabric\src\v9840.0.0-f78517b95a.clean\ggml\src\ggml-cpu\repack.cpp
Ask: build win32-x64 with GGML_BACKEND_DL + GGML_CPU_ALL_VARIANTS, the same way linux-x64 already is. This is the x86 analogue of the BUILD_ARCH pinning that resolved #3364, and it fixes the whole pre-Haswell Windows tier at once β Xeon E5 v1/v2 (very common in homelab servers), pre-Zen AMD, and the Atom-line Celeron/Pentium range, where Goldmont and Tremont have no AVX at all.
Disclosure: I do not have the affected hardware. The 0xC000001D crash is from a downstream user report; the binary analysis above is mine, against the published artifacts, and is reproducible with the commands in the repro section.
β
Checklist
π Bug Description
@qvac/llm-llamacppshipslinux-x64as a thin loader plus 14 dlopen-able per-microarch CPU backends, selected at runtime by ggml backend score β includingivybridgeandsandybridge. It shipswin32-x64as a single statically-linked module with no CPU variant libraries and no runtime dispatch, built at an AVX2/FMA baseline.Every Windows host older than Haswell therefore dies at first model load with
STATUS_ILLEGAL_INSTRUCTION(0xC000001D). The same CPU works fine on Linux.This is not #3222 / #3364. Those were an unrelated eagerly-loaded plugin (
translation-nmtcpp) taking down the worker. We already use the recommended escape hatch β a custom bundle viaqvac bundle sdkwhose worker registers onlyllmPlugin, no other addon is ever loaded. The module crashing here is the LLM engine itself.π Steps to Reproduce
Runtime repro (needs pre-Haswell x86 hardware):
llmPluginsdk.loadModel({ modelSrc: QWEN3_1_7B_INST_Q4, modelType: 'llm', modelConfig: { ctx_size: 8192 } })0xC000001Dbefore the RPC handshakeStatic repro (no special hardware β runs on the published artifacts):
β Expected Behavior
win32-x64selects a CPU backend matching the host, aslinux-x64already does β falling back to the AVX-only or SSE4.2 path on pre-AVX2 hardware, or at minimum failing with a clear "unsupported CPU" error instead of an illegal instruction.β Actual Behavior
The worker process is killed by the OS with
0xC000001Dbefore the RPC handshake. The SDK surfaces this as a generic init timeout, so the user sees a network-looking error with no hint that the CPU is the cause (error-surfacing side is tracked in #1821).π Stack Trace / Error Output
Error: RPCInitTimeoutError: RPC initialization timed out after 30000ms Caused by: Error: Worker process exited with code 3221225501, signal null before IPC connection was established # 3221225501 = 0xC000001D = STATUS_ILLEGAL_INSTRUCTIONπ» Platform / OS
Windows (x64)
π₯οΈ OS Version
Windows 10 x64 (reporter). CPU: Intel Xeon E5-2696 v2 (Ivy Bridge-EP β AVX, no AVX2, no FMA). GPU: AMD Radeon RX 580 (Vulkan).
βοΈ Runtime Environment
Node.js
π¦ Runtime Version
Electron 31.7.7 (Node 20.x) on the client side; worker runs
bare-runtime1.29.5π·οΈ SDK Version
0.16.0
π Relevant Dependencies
{ "@qvac/sdk": "0.16.0", "@qvac/llm-llamacpp": "0.38.2", "bare-runtime": "1.29.5", "electron": "31.7.7" }π Frequency
Always (100%)
π₯ Severity
Critical - Complete blocker, no workaround
π©Ή Workaround
None on Windows. The only escape is a different OS β the Linux build runs on the exact same hardware, because
linux-x64ships theivybridgebackend and Vulkan works on the RX 580 there.Downstream we now decode the exit status ourselves and latch our circuit breaker so we stop respawning a worker that can never start, but that is error reporting, not a fix.
π Additional Context
Packaging asymmetry
prebuilds/linux-x64/β loader + 14 variants:prebuilds/win32-x64/β one module, no variants:On the reliability of this measurement β #3364 correctly established that raw mnemonic counting false-positives on runtime-dispatched code, and that caveat applies here too: this binary also contains AVX-512 mnemonics (52
vmovdqu8) that are certainly dispatched (llamafile/tinyBLAS sgemm, CRTmemcpy), since otherwise it would fault on nearly every consumer CPU.That is why the argument above is not a raw count. It is the register width of the quantized dot-product kernels measured against your own reference builds: your AVX-only variants are 486
xmm/ 0ymm, your AVX2 variant is 2234ymm/ 0xmm, and win32 is 1951ymm/ 0xmm. The AVX-only code path is not outnumbered by a dispatched AVX2 path β it is absent from the binary. Those ggml quant kernels are compile-time selected, not runtime-dispatched.Independently:
GGML_CPU_ALL_VARIANTSrequiresGGML_BACKEND_DL, which emits variant backends as separate shared libraries.prebuilds/win32-x64/contains none, so the CPU backend is necessarily statically linked at one baseline.Vulkan does not rescue it. Vulkan is already compiled into the Windows module (
ggml-vulkan,GGML_DISABLE_VULKAN,Vulkan 1.2 requiredare all present in the binary). It never gets a chance: ggml registers and initializes the CPU backend unconditionally at registry construction, before any GPU is consulted. The reporter has a working Vulkan GPU and is blocked by a code path they would never execute.Not a regression β it has never shipped. Published file listings across the package's history:
So downstreams cannot fix this by upgrading β I verified 0.47.0 before filing.
Build provenance leaked in the binary:
C:\Users\actions-runner\vcpkg\buildtrees\qvac-fabric\src\v9840.0.0-f78517b95a.clean\ggml\src\ggml-cpu\repack.cppAsk: build
win32-x64withGGML_BACKEND_DL+GGML_CPU_ALL_VARIANTS, the same waylinux-x64already is. This is the x86 analogue of theBUILD_ARCHpinning that resolved #3364, and it fixes the whole pre-Haswell Windows tier at once β Xeon E5 v1/v2 (very common in homelab servers), pre-Zen AMD, and the Atom-line Celeron/Pentium range, where Goldmont and Tremont have no AVX at all.Disclosure: I do not have the affected hardware. The
0xC000001Dcrash is from a downstream user report; the binary analysis above is mine, against the published artifacts, and is reproducible with the commands in the repro section.β Checklist