Skip to content

Run the cleanup model and the Windows engine on processors without AVX2 - #62

Merged
watzon merged 5 commits into
mainfrom
claude/project-thread-n8hoyv
Nov 1, 2026
Merged

watzon merged 5 commits into
mainfrom
claude/project-thread-n8hoyv

Conversation

@watzon

@watzon watzon commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

What changes

Before: the local cleanup model crashed on any x86_64 processor without AVX2 (#37). llama.cpp in sayso-cleanup is built with GGML_NATIVE=OFF, and ggml then turns on AVX, AVX2, FMA, F16C and BMI2 by default, so the runtime died with SIGILL at the first load and Settings showed only "signal 4".

After: x86_64 packages ship three builds of the cleanup runtime, sayso-cleanup (AVX2, FMA, F16C, BMI2), sayso-cleanup-avx (AVX, SSE 4.2) and sayso-cleanup-baseline (plain x86-64), and the app starts the best one this processor runs. If a runtime still stops on an illegal instruction, Settings says the processor can't run the local model in this version and suggests updating or using a cloud provider.

How:

  • crates/sayso-core/src/cpu.rs names the builds and picks one with is_x86_feature_detected! (shared by the cleanup and engine clients). runtime.rs maps SIGILL (signal 4 on Unix, 0xC000001D on Windows) to a new RuntimeError::UnsupportedCpu, and refuses up front to start a known build this CPU can't run (unless SAYSO_CLEANUP_PATH is set).
  • scripts/build-cleanup.sh builds the three variants with GGML_*=OFF env vars, each in its own target dir. bundle-linux.sh uses it; bundle-windows.ps1 does the same for the three .exe files. The deb/rpm, AUR, Nix, install.sh, Inno Setup uninstall and AppImage glibc check all take sayso-cleanup*.
  • The Linux engine builds parakeet.cpp's ggml with GGML_NATIVE=OFF too, so it had the same AVX2 default. Its CPU backend is now built for plain x86-64 (the models run on Vulkan, so the CPU side matters little for speed).
  • scripts/test-cleanup-cpus.sh runs each build under qemu-x86_64 -cpu (Haswell, SandyBridge, Nehalem, qemu64) on a tiny random GGUF from scripts/tiny-gguf.py, and checks that the AVX2 build does stop on SandyBridge.
  • New string cleanup_test_failed_cpu_detail in all 10 locales.

Windows speech engine

Before: whisper-rs-sys leaves GGML_NATIVE on, so MSVC built whisper.cpp in sayso-engine.exe for the CI runner's CPU. The 0.13.0 exe uses AVX2, FMA and F16C instructions, so Whisper models on Windows probably crash the engine on a processor without AVX2, and the supervisor reloads the model after each restart.

After: the Windows bundle has sayso-engine.exe (AVX2, FMA, F16C, BMI2), sayso-engine-avx.exe and sayso-engine-baseline.exe, and the app starts the best one this processor runs. If a model still stops the engine on an illegal instruction, it shows as failed with a new reason, "The processor of this computer cannot run this model in this version of Sayso", and the engine isn't asked to load it again.

How:

  • bundle-windows.ps1 sets GGML_NATIVE=OFF and builds the engine three times, each in its own target dir, then copies and signs all three. package-engine.ps1 puts all three in the engine zip, and the Inno Setup uninstaller quits all three.
  • sayso-engine-client picks the build with sayso_core::cpu::best_in (unless SAYSO_ENGINE_PATH is set). On an illegal-instruction exit it marks the model that was running Failed { reason: UnsupportedCpu }, drops it from the models it reloads after a restart, and fails later requests for it without touching the engine.
  • New reason code unsupported_cpu (set by the client, never sent by an engine) with its string in all 10 locales.
  • sayso-engine-sdk's find_engine picks the best build in a Windows release folder. It keeps its own copy of the CPU check, since the SDK may only depend on sayso-protocol.
  • The fake engine can run a real illegal instruction (ud2) for a marked model, and a new test drives the whole crash, restart and fail-fast path.

Fixes #37.

How you checked it

These ran on Linux x86_64 in a cloud container, not on a Mac.

  • cargo test -p sayso-cleanup-client and cargo test -p sayso-i18n; the client CPU tests also under qemu-x86_64 -cpu SandyBridge and qemu64
  • cargo test -p sayso-engine-client (one test, vad_and_diarization_models_download_with_their_engine_object, fails here on the base commit too), cargo test -p sayso-core cpu::, the sayso-engine-sdk lib tests and the sayso-app engine_text tests
  • cargo clippy -p sayso-core -p sayso-engine-client -p sayso-cleanup-client -p sayso-app -p sayso-engine-sdk --all-targets -- -D warnings
  • cargo check --target x86_64-pc-windows-gnu for sayso-core, sayso-engine-sdk, sayso-engine-client and sayso-cleanup-client
  • scripts/check-deps.sh
  • I ran the app and used the changed feature

Reproduced #37 with the 0.13.0 release sayso-cleanup under qemu-x86_64 -cpu SandyBridge (hello works, load dies with signal 4), then built the three variants with scripts/build-cleanup.sh and ran scripts/test-cleanup-cpus.sh against them.

For the Windows engine, I built whisper.cpp's ggml (the copy vendored in whisper-rs-sys 0.15.0) with the same three flag sets on Linux and ran F16, Q8_0, Q5_0 and Q4_K matrix products under QEMU: each build matched an f32 reference on the oldest CPU it's for, the AVX2 build died with SIGILL on SandyBridge and the AVX build on Nehalem. objdump shows no ymm in the baseline build and no FMA or F16C in the AVX build.

Not verified: no real pre-AVX2 hardware (QEMU only); the ggml check used GCC, not MSVC, so the MSVC flag mapping is untested until the Windows CI job; sayso-engine couldn't be built here (its native downloads are blocked in this container), so the parakeet.cpp change is untested until CI; bundle-windows.ps1, package-engine.ps1 and the Inno Setup changes weren't run here. The Windows job now builds the engine three times, so it takes longer.

Workflow change to add by hand

The push token can't change files under .github/workflows, so this part isn't in the branch. It makes the Linux workflow check that every sayso-cleanup* build is installed and answers hello, and runs scripts/test-cleanup-cpus.sh on x86_64. Apply it with git apply on this branch.

.github/workflows/linux.yml
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 7817c9c..c4a52e5 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -32,6 +32,9 @@ on:
       - '.github/workflows/linux.yml'
       - 'packaging/linux/**'
       - 'scripts/bundle-linux.sh'
+      - 'scripts/build-cleanup.sh'
+      - 'scripts/test-cleanup-cpus.sh'
+      - 'scripts/tiny-gguf.py'
       - 'scripts/package-linux.sh'
       - 'scripts/package-engine.sh'
       - 'packaging/engine/**'
@@ -218,20 +221,41 @@ jobs:
           done
 
       # The cleanup runtime needs no model to answer hello, and its backend
-      # is the CPU on Linux. It must not need libgomp (OpenMP is off).
+      # is the CPU on Linux. It must not need libgomp (OpenMP is off). On
+      # x86_64 there are three builds (scripts/build-cleanup.sh).
       - name: Start the installed cleanup runtime
         shell: bash
         run: |
-          runtime=/usr/lib/sayso/sayso-cleanup
-          deps=$(ldd "$runtime")
-          echo "$deps"
-          if grep -qE 'libgomp|not found' <<< "$deps"; then
-            echo "::error::The cleanup runtime has a library that it must not have, or one that is missing."
-            exit 1
+          for runtime in /usr/lib/sayso/sayso-cleanup*; do
+            echo "== $runtime"
+            deps=$(ldd "$runtime")
+            echo "$deps"
+            if grep -qE 'libgomp|not found' <<< "$deps"; then
+              echo "::error::$runtime has a library that it must not have, or one that is missing."
+              exit 1
+            fi
+            reply=$(printf '{"v":1,"id":"1","type":"hello"}\n' | timeout 20 "$runtime")
+            echo "$reply"
+            [[ "$reply" == *'"type":"hello"'*'"backend":"cpu"'* ]] || { echo "::error::$runtime gave no hello reply with the cpu backend."; exit 1; }
+          done
+          if [[ "${{ matrix.arch }}" == x86_64 ]]; then
+            for build in sayso-cleanup-avx sayso-cleanup-baseline; do
+              [[ -x /usr/lib/sayso/$build ]] || { echo "::error::The package has no $build."; exit 1; }
+            done
           fi
-          reply=$(printf '{"v":1,"id":"1","type":"hello"}\n' | timeout 20 "$runtime")
-          echo "$reply"
-          [[ "$reply" == *'"type":"hello"'*'"backend":"cpu"'* ]] || { echo "::error::The cleanup runtime gave no hello reply with the cpu backend."; exit 1; }
+
+      # Each x86_64 build of the cleanup runtime loads a model and completes
+      # text on the oldest processor that it is for, under QEMU, and the AVX2
+      # build stops on a processor without AVX2 (issue #37).
+      - name: Run the cleanup runtime builds on older processors
+        if: matrix.arch == 'x86_64'
+        shell: bash
+        run: |
+          DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends qemu-user python3-venv
+          python3 -m venv "$RUNNER_TEMP/venv"
+          "$RUNNER_TEMP/venv/bin/pip" install --quiet gguf==0.19.0 numpy
+          "$RUNNER_TEMP/venv/bin/python" scripts/tiny-gguf.py "$RUNNER_TEMP/tiny.gguf"
+          scripts/test-cleanup-cpus.sh /usr/lib/sayso "$RUNNER_TEMP/tiny.gguf"
 
       - name: Keep the packages
         uses: actions/upload-artifact@v4

Generated by Claude Code

llama.cpp in the cleanup runtime was built with ggml's default x86
options, which need AVX2, FMA, F16C and BMI2. On older processors the
runtime stopped with SIGILL at the first load (#37).

x86_64 packages now ship three builds (sayso-cleanup, sayso-cleanup-avx,
sayso-cleanup-baseline), and the client starts the best one that the
processor runs. A runtime that still stops on an illegal instruction is
reported as a processor that cannot run the local model, with a clear
message in Settings. The Linux engine builds parakeet.cpp's ggml CPU
backend for plain x86-64 for the same reason. scripts/test-cleanup-cpus.sh
runs each build under QEMU on the oldest processor it is for.

Fixes #37
whisper-rs-sys leaves GGML_NATIVE on, so MSVC built whisper.cpp for the
CPU of the CI runner, and the Windows engine stopped with an illegal
instruction on processors without AVX2 (issue #37), the same problem as
the cleanup runtime.

- bundle-windows.ps1 builds the engine three times with GGML_NATIVE=OFF:
  sayso-engine.exe (AVX2, FMA, F16C, BMI2), sayso-engine-avx.exe and
  sayso-engine-baseline.exe, signs them, and package-engine.ps1 ships all
  three in the engine zip. The installer quits all three on uninstall.
- The CPU detection of the cleanup client moves to sayso_core::cpu, and
  the engine client uses it to start the best engine build in the app
  folder.
- When a model stops the engine with an illegal instruction, the engine
  client marks it Failed with the new reason unsupported_cpu, answers its
  later requests without the engine, and does not load it again after the
  restart, so the engine does not crash in a loop.
- sayso-engine-sdk's find_engine picks the best build in a Windows release
  folder, with its own copy of the check.
@watzon watzon changed the title Run the cleanup model on processors without AVX2 Run the cleanup model and the Windows engine on processors without AVX2 Oct 29, 2026
Resolve the conflicts with the static sayso-bridge build (bundle-linux.sh, releasing.md) and the new fake engine files, and apply the linux.yml change from the description of #62.
whisper-rs-sys does not run its build script again when a GGML_* variable changes, so the AVX2 engine in target could keep a whisper.cpp built before with GGML_NATIVE on, from a cargo build or the CI cache (issue #37).
The QEMU 6.2 of Ubuntu 22.04 does not emulate AVX or AVX2, so the AVX2 build stopped on Haswell and the AVX build failed to load on SandyBridge. A job on Ubuntu 24.04 (QEMU 8.2) runs the builds of the tarball, and a failure there no longer keeps the packages from being uploaded.
@watzon
watzon merged commit 1f316c5 into main Nov 1, 2026
15 checks passed
@watzon
watzon deleted the claude/project-thread-n8hoyv branch November 1, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: sayso-cleanup crashes with SIGILL

2 participants