diff --git a/.github/workflows/arm.yml b/.github/workflows/arm.yml index c81d3d448..a9aa4d387 100644 --- a/.github/workflows/arm.yml +++ b/.github/workflows/arm.yml @@ -3,56 +3,7 @@ name: arm flow on: [workflow_call, workflow_dispatch] jobs: - start-runner: - name: Start self-hosted EC2 runner - runs-on: ubuntu-latest - outputs: - label: ${{ steps.start-ec2-runner.outputs.label }} - ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }} - steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v6 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ secrets.AWS_REGION }} - - name: Start EC2 runner - id: start-ec2-runner - uses: machulav/ec2-github-runner@v2.6.1 - with: - mode: start - github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} - # Ubuntu 22.04 region AMI for ARM - ec2-image-id: ami-062b37d89f25c958f - ec2-instance-type: t4g.medium - subnet-id: ${{ secrets.AWS_EC2_SUBNET_ID }} - security-group-id: ${{ secrets.AWS_EC2_SG_ID }} - ec2-volume-size: 30 - arm: - needs: start-runner # required to start the main job when the runner is ready uses: ./.github/workflows/task-unit-test.yml with: - env: ${{ needs.start-runner.outputs.label }} # run the job on the newly created runner - - stop-runner: - name: Stop self-hosted EC2 runner - needs: - - start-runner # required to get output from the start-runner job - - arm # required to wait when the main job is done - runs-on: ubuntu-latest - if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs - steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v6 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ secrets.AWS_REGION }} - - name: Stop EC2 runner - uses: machulav/ec2-github-runner@v2.6.1 - with: - mode: stop - github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} - label: ${{ needs.start-runner.outputs.label }} - ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }} + env: ubuntu-22.04-arm diff --git a/.github/workflows/benchmark-runner.yml b/.github/workflows/benchmark-runner.yml index 0c2f4c117..86c5739f9 100644 --- a/.github/workflows/benchmark-runner.yml +++ b/.github/workflows/benchmark-runner.yml @@ -29,11 +29,36 @@ jobs: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ secrets.AWS_REGION_BENCHMARK }} + - name: Prepare EC2 owner tag + id: ec2-tags + # Keep the original workflow actor as owner, including on reruns. + env: + OWNER_HANDLE: ${{ github.actor }} + shell: python + run: | + import os + import re + + # Allow only lowercase ASCII letters, digits, and underscores. + owner = os.environ["OWNER_HANDLE"].lower() + owner = re.sub(r"[^a-z0-9_]", "_", owner) + # Collapse repeated separators, cap at 63 characters, and trim edges. + owner = re.sub(r"_+", "_", owner)[:63].strip("_") + if not owner: + raise SystemExit("Cannot derive an EC2 owner tag from github.actor") + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: + output.write(f"owner={owner}\n") - name: Start EC2 runner id: start-ec2-runner uses: machulav/ec2-github-runner@v2.6.1 with: mode: start + aws-resource-tags: >- + [ + {"Key": "team", "Value": "ai_and_search"}, + {"Key": "owner", "Value": "${{ steps.ec2-tags.outputs.owner }}"} + ] github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} ec2-image-id: ${{ inputs.ami-id }} ec2-instance-type: ${{ inputs.instance-type }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d94f439cf..fc4d0e480 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -14,11 +14,36 @@ jobs: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ secrets.AWS_REGION_BENCHMARK }} + - name: Prepare EC2 owner tag + id: ec2-tags + # Keep the original workflow actor as owner, including on reruns. + env: + OWNER_HANDLE: ${{ github.actor }} + shell: python + run: | + import os + import re + + # Allow only lowercase ASCII letters, digits, and underscores. + owner = os.environ["OWNER_HANDLE"].lower() + owner = re.sub(r"[^a-z0-9_]", "_", owner) + # Collapse repeated separators, cap at 63 characters, and trim edges. + owner = re.sub(r"_+", "_", owner)[:63].strip("_") + if not owner: + raise SystemExit("Cannot derive an EC2 owner tag from github.actor") + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: + output.write(f"owner={owner}\n") - name: Start EC2 runner id: start-ec2-runner uses: machulav/ec2-github-runner@v2.6.1 with: mode: start + aws-resource-tags: >- + [ + {"Key": "team", "Value": "ai_and_search"}, + {"Key": "owner", "Value": "${{ steps.ec2-tags.outputs.owner }}"} + ] github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} # Ubuntu 22.04 AMI ec2-image-id: ami-09fabd03bb09b3704 diff --git a/.github/workflows/debian11.yml b/.github/workflows/debian13.yml similarity index 68% rename from .github/workflows/debian11.yml rename to .github/workflows/debian13.yml index ddafd26e2..eb7725679 100644 --- a/.github/workflows/debian11.yml +++ b/.github/workflows/debian13.yml @@ -1,11 +1,10 @@ -name: debian bullseye flow +name: debian trixie flow on: [workflow_dispatch, workflow_call] jobs: - bullseye: + trixie: uses: ./.github/workflows/task-unit-test.yml with: - container: gcc:11-bullseye + container: gcc:14-trixie pre-checkout-script: apt-get update && apt-get -y install git - diff --git a/.github/workflows/event-merge-to-queue.yml b/.github/workflows/event-merge-to-queue.yml index 21b892137..cdeb67d47 100644 --- a/.github/workflows/event-merge-to-queue.yml +++ b/.github/workflows/event-merge-to-queue.yml @@ -20,8 +20,8 @@ jobs: # uses: ./.github/workflows/task-unit-test.yml # with: # container: ubuntu:bionic - bullseye: - uses: ./.github/workflows/debian11.yml + trixie: + uses: ./.github/workflows/debian13.yml # amazonlinux2: # needs: [check-if-docs-only] # if: ${{ needs.check-if-docs-only.outputs.only-docs-changed == 'false' }} @@ -38,7 +38,6 @@ jobs: uses: ./.github/workflows/macos.yml arm: uses: ./.github/workflows/arm.yml - secrets: inherit coverage: uses: ./.github/workflows/coverage.yml @@ -49,7 +48,7 @@ jobs: - jammy # - focal # - bionic - - bullseye + - trixie # - amazonlinux2 - mariner2 - rocky8 diff --git a/.github/workflows/event-nightly.yml b/.github/workflows/event-nightly.yml index dbb66624c..e7d5e6f0d 100644 --- a/.github/workflows/event-nightly.yml +++ b/.github/workflows/event-nightly.yml @@ -23,8 +23,8 @@ jobs: # uses: ./.github/workflows/task-unit-test.yml # with: # container: ubuntu:bionic - bullseye: - uses: ./.github/workflows/debian11.yml + trixie: + uses: ./.github/workflows/debian13.yml # amazonlinux2: # needs: [check-if-docs-only] # if: ${{ needs.check-if-docs-only.outputs.only-docs-changed == 'false' }} @@ -41,7 +41,6 @@ jobs: uses: ./.github/workflows/macos.yml arm: uses: ./.github/workflows/arm.yml - secrets: inherit coverage: uses: ./.github/workflows/coverage.yml secrets: inherit @@ -56,7 +55,7 @@ jobs: - jammy # - focal # - bionic - - bullseye + - trixie # - amazonlinux2 - mariner2 - rocky8 diff --git a/.install/debian_gnu_linux_11.sh b/.install/debian_gnu_linux_13.sh similarity index 100% rename from .install/debian_gnu_linux_11.sh rename to .install/debian_gnu_linux_13.sh diff --git a/tests/unit/test_fp16.cpp b/tests/unit/test_fp16.cpp index abce4cd60..69fbc632c 100644 --- a/tests/unit/test_fp16.cpp +++ b/tests/unit/test_fp16.cpp @@ -502,36 +502,42 @@ template void FP16Test::test_override(params_t params) { size_t n = 100; size_t new_n = 250; + // Scale factor to avoid FP16 overflow. FP16 max value is 65504, and L2² = dim × diff². + // With scale=0.1 and max diff=250: L2² = 4 × (250×0.1)² = 10000 < 65504. + constexpr float scale = 0.1f; SetUp(params); // Insert n vectors. for (size_t i = 0; i < n; i++) { - ASSERT_EQ(GenerateAndAddVector(i, i), 1); + ASSERT_EQ(GenerateAndAddVector(i, i * scale), 1); } ASSERT_EQ(VecSimIndex_IndexSize(index), n); // Override n vectors, the first 100 will be overwritten (deleted first). for (size_t i = 0; i < n; i++) { - ASSERT_EQ(GenerateAndAddVector(i, i), 0); + ASSERT_EQ(GenerateAndAddVector(i, i * scale), 0); } // Add up to new_n vectors. for (size_t i = n; i < new_n; i++) { - ASSERT_EQ(GenerateAndAddVector(i, i), 1); + ASSERT_EQ(GenerateAndAddVector(i, i * scale), 1); } float16 query[dim]; - GenerateVector(query, new_n); + GenerateVector(query, new_n * scale); - // Vectors values equals their id, so we expect the larger the id the closest it will be to the - // query. + // Vectors values equals their id (scaled), so we expect the larger the id the closest it will + // be to the query. auto verify_res = [&](size_t id, double score, size_t index) { ASSERT_EQ(id, new_n - 1 - index) << "id: " << id << " score: " << score; - float16 a = vecsim_types::FP32_to_FP16(new_n); - float16 b = vecsim_types::FP32_to_FP16(id); + float16 a = vecsim_types::FP32_to_FP16(new_n * scale); + float16 b = vecsim_types::FP32_to_FP16(id * scale); float diff = vecsim_types::FP16_to_FP32(a) - vecsim_types::FP16_to_FP32(b); float exp_score = 4 * diff * diff; - ASSERT_EQ(score, exp_score) << "id: " << id << " score: " << score; + // Use tolerance-based comparison due to FP16 precision loss in SVE accumulation. + // FP16 has ~3 decimal digits of precision, so we allow ~0.2% relative tolerance. + float tolerance = std::max(1.0f, std::abs(exp_score) * 0.002f); + ASSERT_NEAR(score, exp_score, tolerance) << "id: " << id << " score: " << score; }; runTopKSearchTest(index, query, 300, verify_res); } @@ -666,18 +672,21 @@ TEST_F(FP16TieredTest, GetDistanceIPTest) { template void FP16Test::test_batch_iterator_basic(params_t params) { size_t n = params.initialCapacity; + // Scale factor to avoid FP16 overflow. FP16 max value is 65504, and L2² = dim × diff². + // With scale=0.1 and max diff=250: L2² = 4 × (250×0.1)² = 10000 < 65504. + constexpr float scale = 0.1f; SetUp(params); - // For every i, add the vector (i,i,i,i) under the label i. + // For every i, add the vector (i*scale, i*scale, i*scale, i*scale) under the label i. for (size_t i = 0; i < n; i++) { - ASSERT_EQ(GenerateAndAddVector(i, i), 1); + ASSERT_EQ(GenerateAndAddVector(i, i * scale), 1); } ASSERT_EQ(VecSimIndex_IndexSize(index), n); - // Query for (n,n,n,n) vector (recall that n-1 is the largest id in te index). + // Query for (n*scale, n*scale, n*scale, n*scale) vector (recall that n-1 is the largest id). float16 query[dim]; - GenerateVector(query, n); + GenerateVector(query, n * scale); VecSimBatchIterator *batchIterator = VecSimBatchIterator_New(index, query, nullptr); size_t iteration_num = 0; diff --git a/tests/unit/test_svs_fp16.cpp b/tests/unit/test_svs_fp16.cpp index cc0afd658..25a55b4fa 100644 --- a/tests/unit/test_svs_fp16.cpp +++ b/tests/unit/test_svs_fp16.cpp @@ -2764,6 +2764,8 @@ TYPED_TEST(FP16SVSTieredIndexTest, BatchIterator) { } constexpr size_t d = 4; constexpr size_t n = 1000; + // Keep FP16 squared L2 distances below 65504: 4 * (1000 * 0.1)^2 = 40000. + constexpr float scale = 0.1f; // Create TieredSVS index instance with a mock queue. SVSParams params = { @@ -2785,16 +2787,16 @@ TYPED_TEST(FP16SVSTieredIndexTest, BatchIterator) { auto *svs = tiered_index->GetBackendIndex(); auto *flat = tiered_index->GetFlatIndex(); - // For every i, add the vector (i,i,i,i) under the label i. + // For every i, add the vector (i*scale, i*scale, i*scale, i*scale) under the label i. for (size_t i = 0; i < n; i++) { auto cur = decider(i, n) ? svs : flat; - this->GenerateAndAddVector(cur, d, i, i); + this->GenerateAndAddVector(cur, d, i, i * scale); } ASSERT_EQ(VecSimIndex_IndexSize(tiered_index), n) << decider_name; - // Query for (n,n,n,n) vector (recall that n-1 is the largest id in te index). + // Query for the scaled vector; n-1 is the largest label in the index. float16 query[d]; - this->GenerateVector(query, d, n); + this->GenerateVector(query, d, n * scale); VecSimBatchIterator *batchIterator = VecSimBatchIterator_New(tiered_index, query, nullptr); size_t iteration_num = 0;