Skip to content

Commit 19dfa96

Browse files
committed
test(sq8): reproduce Sterbenz assumption accuracy edge case in CI
1 parent 838a30d commit 19dfa96

2 files changed

Lines changed: 126 additions & 4 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright (c) 2006-Present, Redis Ltd.
3+
* All rights reserved.
4+
*
5+
* Licensed under your choice of the Redis Source Available License 2.0
6+
* (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
7+
* GNU Affero General Public License v3 (AGPLv3).
8+
*/
9+
10+
#include "VecSim/spaces/L2/L2.h"
11+
#include "VecSim/spaces/computer/preprocessors.h"
12+
#include <array>
13+
#include <cfenv>
14+
#include <cstdio>
15+
#include <cstdlib>
16+
#include <limits>
17+
18+
int main(int argc, char **argv) {
19+
static_assert(std::numeric_limits<float>::is_iec559 &&
20+
std::numeric_limits<float>::digits == 24);
21+
if (argc != 4 || std::fegetround() != FE_TONEAREST) {
22+
std::fprintf(stderr, "Expected three coordinates and round-to-nearest mode.\n");
23+
return 1;
24+
}
25+
26+
// Runtime inputs keep the production preprocessing path in the executed reproduction.
27+
constexpr size_t dim = 3;
28+
std::array<float, dim> input;
29+
for (size_t i = 0; i < dim; ++i) {
30+
input[i] = std::strtof(argv[i + 1], nullptr);
31+
}
32+
auto allocator = VecSimAllocator::newVecsimAllocator();
33+
QuantPreprocessor<float, VecSimMetric_L2> preprocessor(allocator, dim);
34+
void *storage = nullptr;
35+
size_t storage_bytes = sizeof(input);
36+
preprocessor.preprocessForStorage(input.data(), storage, storage_bytes, 0);
37+
const auto *bytes = static_cast<const uint8_t *>(storage);
38+
using sq8 = vecsim_types::sq8;
39+
const float min_val = load_unaligned<float>(bytes + dim + sq8::MIN_VAL * sizeof(float));
40+
const float delta = load_unaligned<float>(bytes + dim + sq8::DELTA * sizeof(float));
41+
std::printf("Production metadata: min=%.9g delta=%.9g bytes=[%u,%u,%u]\n", min_val, delta,
42+
unsigned(bytes[0]), unsigned(bytes[1]), unsigned(bytes[2]));
43+
bool reproduced = min_val == 1.0f && delta == 262144.0f && bytes[0] == 0 && bytes[1] == 128 &&
44+
bytes[2] == 255;
45+
46+
auto check = [&](const char *label, const std::array<float, dim> &query_input,
47+
double expected_reference, float expected_actual) {
48+
void *query = nullptr;
49+
size_t query_bytes = sizeof(query_input);
50+
preprocessor.preprocessQuery(query_input.data(), query, query_bytes, 0);
51+
const auto *values = static_cast<const float *>(query);
52+
double reference = 0.0;
53+
std::printf("%s:\n", label);
54+
for (size_t i = 0; i < dim; ++i) {
55+
// Match the PR's independent double reference, using the actual stored metadata.
56+
const double reconstructed = double(min_val) + double(delta) * bytes[i];
57+
const double diff = reconstructed - double(values[i]);
58+
reference += diff * diff;
59+
const float min_minus_y = min_val - values[i];
60+
std::printf(" i=%zu q=%u y=%.9g reconstructed=%.17g exact_diff=%.17g "
61+
"fp32_min_minus_y=%.9g fma_diff=%.9g\n",
62+
i, unsigned(bytes[i]), values[i], reconstructed, diff, min_minus_y,
63+
std::fma(delta, float(bytes[i]), min_minus_y));
64+
}
65+
const float actual = SQ8_FP32_L2Sqr(storage, query, dim);
66+
std::printf(" Production scalar L2^2=%.9g; double-reference L2^2=%.17g\n", actual,
67+
reference);
68+
allocator->free_allocation(query);
69+
return reference == expected_reference && actual == expected_actual;
70+
};
71+
72+
reproduced &= check("Original query", input, 2.0, 0.0f);
73+
auto shifted_query = input;
74+
shifted_query[1] += 4.0f;
75+
reproduced &= check("Middle query coordinate increased by 4", shifted_query, 10.0, 16.0f);
76+
allocator->free_allocation(storage);
77+
std::puts(reproduced ? "REPRODUCED both predicted distance errors using production code."
78+
: "NOT REPRODUCED: at least one prediction did not match.");
79+
return reproduced ? 0 : 1;
80+
}

‎.github/workflows/flow-temp.yml‎

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: temporary testing
1+
name: SQ8 Sterbenz reproduction
22

33
# This file is useful for triggering actions when you implement them.
44
# When the `branches-ignore` line is commented out, this workflow will run on every push.
@@ -9,15 +9,57 @@ name: temporary testing
99

1010
on:
1111
push:
12-
branches-ignore: ['**'] # ignore all branches. Comment this line to run your workflow below on every push.
12+
branches: [omer-lerman-sq8-sterbenz-repro]
13+
workflow_dispatch:
14+
15+
permissions:
16+
contents: read
17+
1318
jobs:
1419
# jammy:
1520
# uses: ./.github/workflows/task-unit-test.yml
1621
# with:
1722
# container: ubuntu:jammy
1823
# run-valgrind: true
19-
alpine3:
20-
uses: ./.github/workflows/alpine3.yml
24+
reproduce:
25+
runs-on: ubuntu-24.04
26+
timeout-minutes: 5
27+
steps:
28+
- uses: actions/checkout@v6
29+
with:
30+
persist-credentials: false
31+
- name: Get the CPU feature headers used by the production preprocessor
32+
uses: actions/checkout@v6
33+
with:
34+
repository: google/cpu_features
35+
ref: v0.10.1
36+
path: .repro-deps/cpu_features
37+
persist-credentials: false
38+
- name: Build unchanged production quantizer and scalar kernel
39+
shell: bash
40+
run: |
41+
set -o pipefail
42+
git rev-parse HEAD
43+
g++ --version
44+
g++ -std=c++20 -O3 -fno-fast-math -ffp-contract=off \
45+
-Isrc -I.repro-deps/cpu_features/include \
46+
.github/scripts/sq8-sterbenz-repro.cpp \
47+
src/VecSim/spaces/L2/L2.cpp \
48+
src/VecSim/spaces/IP/IP.cpp \
49+
src/VecSim/memory/vecsim_malloc.cpp \
50+
src/VecSim/memory/vecsim_base.cpp \
51+
-o /tmp/sq8-sterbenz-repro 2>&1 | tee /tmp/sq8-sterbenz-build.log
52+
- name: Verify the numerical counterexample
53+
shell: bash
54+
run: |
55+
set -o pipefail
56+
/tmp/sq8-sterbenz-repro 1 33554432 66846720 2>&1 | tee /tmp/sq8-sterbenz-result.log
57+
{
58+
echo 'The job succeeds only if both predicted numerical discrepancies are reproduced.'
59+
echo '```text'
60+
cat /tmp/sq8-sterbenz-result.log
61+
echo '```'
62+
} >> "$GITHUB_STEP_SUMMARY"
2163
# focal:
2264
# uses: ./.github/workflows/task-unit-test.yml
2365
# with:

0 commit comments

Comments
 (0)