Skip to content

Commit 61ca8f0

Browse files
marcobambiniclaude
andcommitted
ci: actually build and assert the SIMD kernels, including AVX-512
The unittest target builds every source in a single invocation, which leaves __AVX2__ and __AVX512F__ undefined: those kernels compile to nothing and the suite exercises the scalar fallback instead. Every green CI run so far has been testing the SIMD backends by not testing them - the same mechanism that made every shipped x86 build scalar. * unittest-simd compiles per translation unit the way the extension target does, so the SIMD kernels are actually linked in. RUNNER wraps the binaries for an emulator, EXPECT_BACKEND asserts which tier got installed. * test/backend.c prints the installed backends and, given an argument, exits non-zero unless that is the one that was installed. Without this a silent fallback is indistinguishable from a pass: the suite runs, 1447 tests go green, and the kernels under test were never reached. * A new avx512 job runs the suite on those kernels. GitHub's hosted fleet is mixed - some runners have AVX-512, some do not, and there is no way to request one (actions/runner#1069) - so the job uses the hardware when it is there and Intel SDE when it is not, emulating Skylake-X because that is exactly the F/BW/VL/DQ set cpu_supports_avx512() requires. SDE is pulled from Intel's own download mirror with a pinned SHA-256; a mismatch fails the job rather than running an unverified binary. Verified on linux/amd64: unittest-simd installs the AVX2 backend and passes all 1447 tests (the plain unittest target reports CPU there), and EXPECT_BACKEND fails loudly on a mismatch. The SDE path could not be exercised locally - Pin aborts under nested emulation on Apple Silicon - but by construction that path cannot pass without AVX-512 having run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 394e64f commit 61ca8f0

3 files changed

Lines changed: 152 additions & 0 deletions

File tree

.github/workflows/main.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,69 @@ jobs:
203203
path: dist/vector.*
204204
if-no-files-found: error
205205

206+
avx512:
207+
# The AVX-512 kernels are compiled out of every other job: the plain `unittest` target
208+
# builds all sources in one invocation, so __AVX512F__ is undefined and the suite
209+
# silently exercises the scalar fallback. This job builds them for real and asserts
210+
# which backend was installed, so a fallback fails instead of passing quietly.
211+
#
212+
# GitHub's hosted fleet is mixed - some runners have AVX-512, some do not, and there
213+
# is no way to request one (actions/runner#1069). When this runner has it we run the
214+
# kernels natively; otherwise we run them under Intel SDE, which emulates the ISA
215+
# deterministically. Either way EXPECT_BACKEND makes the job fail if AVX-512 is not
216+
# what actually ran.
217+
name: avx512 kernels
218+
if: ${{ !contains(github.event.head_commit.message, '[auto-update]') }}
219+
runs-on: ubuntu-24.04
220+
timeout-minutes: 30
221+
env:
222+
# Intel SDE, from Intel's own download mirror. Bump both together; the job fails on
223+
# a checksum mismatch rather than running an unverified binary.
224+
SDE_URL: https://downloadmirror.intel.com/924984/sde-external-10.13.1-2026-07-28-lin.tar.xz
225+
SDE_SHA256: 94e97d623fec54385686e1e7ba65ebc9941748c05ee451423948334892bf2b50
226+
steps:
227+
- uses: actions/checkout@v4.2.2
228+
229+
- name: does this runner have AVX-512?
230+
id: cpu
231+
run: |
232+
grep -m1 '^model name' /proc/cpuinfo
233+
missing=""
234+
for f in avx512f avx512bw avx512vl avx512dq; do
235+
grep -qw "$f" /proc/cpuinfo || missing="$missing $f"
236+
done
237+
if [ -z "$missing" ]; then
238+
echo "native=true" >> "$GITHUB_OUTPUT"
239+
echo "::notice title=AVX-512::this runner has AVX-512, running the kernels on hardware"
240+
else
241+
echo "native=false" >> "$GITHUB_OUTPUT"
242+
echo "::notice title=AVX-512::this runner is missing$missing, running the kernels under Intel SDE"
243+
fi
244+
245+
- name: install Intel SDE
246+
if: steps.cpu.outputs.native == 'false'
247+
run: |
248+
curl -fsSL -o /tmp/sde.tar.xz "$SDE_URL"
249+
echo "$SDE_SHA256 /tmp/sde.tar.xz" | sha256sum -c -
250+
mkdir -p /tmp/sde
251+
tar -xJf /tmp/sde.tar.xz -C /tmp/sde --strip-components=1
252+
/tmp/sde/sde64 --version | head -2
253+
254+
- name: run the suite on the AVX-512 kernels
255+
run: |
256+
if [ "${{ steps.cpu.outputs.native }}" = "true" ]; then
257+
make unittest-simd EXPECT_BACKEND=AVX512
258+
else
259+
make unittest-simd EXPECT_BACKEND=AVX512 RUNNER="/tmp/sde/sde64 -skx --"
260+
fi
261+
262+
- name: run the suite on the AVX2 kernels
263+
# Only meaningful where AVX-512 is absent: the runtime check prefers AVX-512
264+
# whenever the CPU has it, so asserting AVX2 there would correctly fail. Cheap
265+
# extra coverage either way - until now no job built these kernels at all.
266+
if: steps.cpu.outputs.native == 'false'
267+
run: make unittest-simd EXPECT_BACKEND=AVX2
268+
206269
release:
207270
runs-on: ubuntu-22.04
208271
name: release

Makefile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,36 @@ unittest:
151151
$(CC) $(CFLAGS) -DSQLITE_CORE -O2 $(TEST_SRC) -o $(BUILD_DIR)/test_vector -lm -lpthread
152152
./$(BUILD_DIR)/test_vector
153153

154+
# The unittest target above builds every source in a single invocation, which leaves
155+
# __AVX2__ and __AVX512F__ undefined: those kernels compile to nothing and the suite
156+
# silently exercises the scalar fallback instead. This target compiles per translation
157+
# unit the way the extension does, so the SIMD backends are actually under test.
158+
#
159+
# make unittest-simd run on whatever this CPU supports
160+
# make unittest-simd EXPECT_BACKEND=AVX512 fail unless AVX-512 was installed
161+
# make unittest-simd RUNNER="sde64 -spr --" run under an emulator
162+
UNITTEST_OBJ = $(patsubst %.c, $(BUILD_DIR)/ut-%.o, $(notdir $(SRC_FILES))) $(BUILD_DIR)/ut-sqlite3.o
163+
164+
$(BUILD_DIR)/ut-distance-avx2.o: ISA_CFLAGS := $(AVX2_CFLAGS)
165+
$(BUILD_DIR)/ut-distance-avx512.o: ISA_CFLAGS := $(AVX512_CFLAGS)
166+
167+
$(BUILD_DIR)/ut-%.o: %.c
168+
$(CC) $(CFLAGS) $(ISA_CFLAGS) -DSQLITE_CORE -O2 -c $< -o $@
169+
170+
$(BUILD_DIR)/backend: test/backend.c $(UNITTEST_OBJ)
171+
$(CC) $(CFLAGS) -DSQLITE_CORE -O2 $< $(UNITTEST_OBJ) -o $@ -lm -lpthread
172+
173+
$(BUILD_DIR)/test_vector_simd: test/test_vector.c $(UNITTEST_OBJ)
174+
$(CC) $(CFLAGS) -DSQLITE_CORE -O2 $< $(UNITTEST_OBJ) -o $@ -lm -lpthread
175+
176+
# RUNNER wraps both binaries, so an emulator sees the same build the assertion checked
177+
RUNNER ?=
178+
EXPECT_BACKEND ?=
179+
180+
unittest-simd: $(BUILD_DIR)/backend $(BUILD_DIR)/test_vector_simd
181+
$(RUNNER) ./$(BUILD_DIR)/backend $(EXPECT_BACKEND)
182+
$(RUNNER) ./$(BUILD_DIR)/test_vector_simd
183+
154184
# Clean up generated files
155185
clean:
156186
rm -rf $(BUILD_DIR)/* $(DIST_DIR)/* *.gcda *.gcno *.gcov *.sqlite

test/backend.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// backend.c
3+
// sqlitevector
4+
//
5+
// Reports which distance kernels the extension actually installed, and optionally
6+
// asserts that it is the expected one. A scan that silently falls back to a lower tier
7+
// passes every test, so CI needs a way to tell "ran on AVX-512" from "meant to".
8+
//
9+
// ./backend print the installed backends
10+
// ./backend AVX512 print them, and exit non-zero unless the distance backend is AVX512
11+
//
12+
13+
#include <stdio.h>
14+
#include <string.h>
15+
16+
#include "sqlite3.h"
17+
18+
extern int sqlite3_vector_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi);
19+
20+
int main (int argc, char **argv) {
21+
sqlite3 *db = NULL;
22+
if (sqlite3_open(":memory:", &db) != SQLITE_OK) {
23+
fprintf(stderr, "unable to open an in-memory database\n");
24+
return 2;
25+
}
26+
27+
int rc = sqlite3_vector_init(db, NULL, NULL);
28+
if (rc != SQLITE_OK) {
29+
fprintf(stderr, "sqlite3_vector_init failed (%d)\n", rc);
30+
sqlite3_close(db);
31+
return 2;
32+
}
33+
34+
sqlite3_stmt *stmt = NULL;
35+
if (sqlite3_prepare_v2(db, "SELECT vector_backend(), vector_turboquant_backend();", -1, &stmt, NULL) != SQLITE_OK ||
36+
sqlite3_step(stmt) != SQLITE_ROW) {
37+
fprintf(stderr, "unable to read the installed backends: %s\n", sqlite3_errmsg(db));
38+
sqlite3_finalize(stmt);
39+
sqlite3_close(db);
40+
return 2;
41+
}
42+
43+
const char *distance = (const char *)sqlite3_column_text(stmt, 0);
44+
const char *turbo = (const char *)sqlite3_column_text(stmt, 1);
45+
if (!distance) distance = "?";
46+
if (!turbo) turbo = "?";
47+
printf("distance backend: %s\n", distance);
48+
printf("turboquant backend: %s\n", turbo);
49+
50+
int result = 0;
51+
if (argc > 1) {
52+
result = (strcmp(distance, argv[1]) == 0) ? 0 : 1;
53+
if (result) fprintf(stderr, "expected the %s backend, but %s was installed\n", argv[1], distance);
54+
}
55+
56+
sqlite3_finalize(stmt);
57+
sqlite3_close(db);
58+
return result;
59+
}

0 commit comments

Comments
 (0)