Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/event-sse-only-repro.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: SSE-only unit tests (loadr alignment repro)

# Demonstrates MOD-16730: builds the library with the AVX-family instruction flags disabled,
# which is the build any x86-64 machine without AVX gets. The dispatcher then selects the SSE
# tier, and the FP32/L2 kernel's _mm_loadr_ps residual path (dim % 4 == 3) executes movaps on
# the unaligned pointers produced by VecSimAllocator - crashing test_spaces / test_bruteforce.
# This job is EXPECTED TO FAIL until the kernel is fixed.

on:
pull_request:
workflow_dispatch:

jobs:
sse-only-unit-tests:
name: SSE-only build (AVX flags off) unit tests
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v6
with:
submodules: recursive
- name: install dependencies
run: .install/install_script.sh sudo
- name: Print CPU information
run: lscpu
- name: unit tests (SSE-only build)
env:
# Force the compiler-flag detection results off for every AVX-family tier, so no
# OPT_AVX*/OPT_F16C code is compiled and the dispatcher falls through to the SSE tiers
# at runtime - equivalent to running on a pre-AVX machine.
CMAKE_FLAGS: >-
-DCXX_AVX512VL=FALSE
-DCXX_AVX512BF16=FALSE
-DCXX_AVX512BW=FALSE
-DCXX_AVX512VBMI2=FALSE
-DCXX_AVX512FP16=FALSE
-DCXX_AVX512F=FALSE
-DCXX_AVX512VNNI=FALSE
-DCXX_AVX2=FALSE
-DCXX_AVX=FALSE
-DCXX_F16C=FALSE
-DCXX_FMA=FALSE
# ctest test names come from gtest_discover_tests, i.e. gtest case names like
# "SpacesTest.*" / "BruteForceTest/0.*". --no-tests=error guards against a regex
# matching nothing and silently passing.
run: make unit_test CTEST_ARGS="-R 'SpacesTest|BruteForce' --no-tests=error --output-on-failure"
28 changes: 28 additions & 0 deletions tests/unit/test_bruteforce.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,34 @@ class BruteForceTest : public ::testing::Test {

TYPED_TEST_SUITE(BruteForceTest, DataTypeSet);

// End-to-end repro for the _mm_loadr_ps alignment fault: full index flow - factory, dispatcher,
// preprocessor, allocator-placed vectors, TopK query. On a build/machine where the dispatcher
// selects the SSE tier for FP32/L2 (no AVX), dim 19 routes to the _mm_loadr_ps residual path
// with the allocator's 8-mod-16 pointers and crashes with SIGSEGV.
TEST(BruteForceFlowRepro, fp32_l2_dim19_sse_loadr_flow) {
constexpr size_t dim = 19; // dim % 16 == 3 -> residual 3 -> _mm_loadr_ps path on SSE tier
BFParams bfParams = {.type = VecSimType_FLOAT32, .dim = dim, .metric = VecSimMetric_L2};
VecSimParams params = CreateParams(bfParams);
VecSimIndex *index = VecSimIndex_New(&params);
ASSERT_NE(index, nullptr);

float v[dim];
for (size_t label = 0; label < 4; label++) {
for (size_t i = 0; i < dim; i++)
v[i] = float(label + i);
VecSimIndex_AddVector(index, v, label);
}
ASSERT_EQ(VecSimIndex_IndexSize(index), 4);

float q[dim];
for (size_t i = 0; i < dim; i++)
q[i] = float(i) + 0.5f;
VecSimQueryReply *res = VecSimIndex_TopKQuery(index, q, 2, nullptr, BY_SCORE);
ASSERT_EQ(VecSimQueryReply_Len(res), 2);
VecSimQueryReply_Free(res);
VecSimIndex_Free(index);
}

TYPED_TEST(BruteForceTest, brute_force_vector_add_test) {

size_t dim = 4;
Expand Down
22 changes: 22 additions & 0 deletions tests/unit/test_spaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,28 @@ TEST_F(SpacesTest, GetDistFuncSQ8FP16Asymmetric) {
}

#ifdef CPU_FEATURES_ARCH_X86_64
#ifdef OPT_SSE
// Reproduces the _mm_loadr_ps alignment fault in the FP32 L2 SSE kernel (residual % 4 == 3
// path). Buffers are placed at (16-aligned base) + 8 - exactly where VecSimAllocator::allocate()
// puts vector data when no alignment hint is set, which is always the case on this path since
// the dispatcher only sets a hint when dim % 4 == 0 and this path requires dim % 4 == 3.
TEST_F(SpacesTest, FP32_L2Sqr_SSE_loadr_misaligned) {
constexpr size_t dim = 19; // dim % 16 == 3 -> residual 3 -> _mm_loadr_ps path
alignas(16) static char raw1[16 + dim * sizeof(float)];
alignas(16) static char raw2[16 + dim * sizeof(float)];
float *v1 = reinterpret_cast<float *>(raw1 + 8); // address == 8 (mod 16)
float *v2 = reinterpret_cast<float *>(raw2 + 8);
for (size_t i = 0; i < dim; i++) {
v1[i] = float(i);
v2[i] = float(i) + 1.5f;
}
float baseline = FP32_L2Sqr(v1, v2, dim);
dist_func_t<float> arch_opt_func = spaces::Choose_FP32_L2_implementation_SSE(dim);
// SIGSEGV here: the kernel's residual path executes movaps on the 8-mod-16 addresses.
ASSERT_EQ(baseline, arch_opt_func(v1, v2, dim));
}
#endif // OPT_SSE

TEST_F(SpacesTest, smallDimChooser) {
// Verify that small dimensions gets the no optimization function.
for (size_t dim = 1; dim < 8; dim++) {
Expand Down
Loading