diff --git a/.cargo/nextest.toml b/.cargo/nextest.toml new file mode 100644 index 000000000..22b5d6980 --- /dev/null +++ b/.cargo/nextest.toml @@ -0,0 +1,28 @@ +[profile.default] + +# terminate the test run it takes more than 10 minutes. +slow-timeout = { period = "600s", terminate-after = 1 } + +# "retries" defines the number of times a test should be retried. +retries = 3 + +# The number of threads to run tests with. +test-threads = "num-cpus" + +# The number of threads required for each test. +threads-required = 1 + +# Show these test statuses in the output. +status-level = "all" + +# Similar to status-level, show these test statuses at the end of the run. +final-status-level = "flaky" + +# "failure-output" defines when standard output and standard error for failing tests are produced. +failure-output = "immediate" + +# "success-output" controls production of standard output and standard error on success. +success-output = "never" + +# Cancel the test run on the first failure. For CI runs, this should be false. +fail-fast = false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..ccf812649 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,231 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT license. + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +name: CI + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +env: + RUSTFLAGS: -Dwarnings + RUST_BACKTRACE: 1 + # Use the Rust version specified in rust-toolchain.toml + rust_stable: "1.90" + +defaults: + run: + shell: bash + +permissions: + contents: read + +jobs: + # Basic checks that must pass before we kick off more expensive tests. + basics: + name: basic checks + runs-on: ubuntu-latest + needs: + - clippy + - fmt + # TODO: Re-enable docs check later + # - docs + steps: + - run: exit 0 + + fmt: + name: format check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust ${{ env.rust_stable }} + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.rust_stable }} + components: rustfmt + - uses: Swatinem/rust-cache@v2 + # Check fmt + - name: "cargo fmt --check" + run: | + if ! cargo fmt --all --check; then + printf "Please run \`cargo fmt --all\` to fix rustfmt errors.\n" >&2 + exit 1 + fi + + clippy: + name: clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust ${{ env.rust_stable }} + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.rust_stable }} + components: clippy + - uses: Swatinem/rust-cache@v2 + # Run clippy on workspace + - name: "clippy --workspace --all-targets" + run: cargo clippy --workspace --all-targets --no-deps + + # TODO: Re-enable docs check later + # docs: + # name: docs + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # - name: Install Rust ${{ env.rust_stable }} + # uses: dtolnay/rust-toolchain@stable + # with: + # toolchain: ${{ env.rust_stable }} + # - uses: Swatinem/rust-cache@v2 + # - name: "doc --workspace --no-deps" + # run: cargo doc --workspace --no-deps --document-private-items + # env: + # RUSTDOCFLAGS: -Dwarnings + + test-workspace: + needs: basics + name: test workspace + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - windows-latest + - ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + lfs: true + + - name: Install Rust ${{ env.rust_stable }} + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.rust_stable }} + + - name: Install cargo-nextest + uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest + + - uses: Swatinem/rust-cache@v2 + + - name: test workspace with nextest + run: | + set -euxo pipefail + cargo nextest run --workspace --cargo-profile ci + cargo test --doc --workspace --profile ci + + test-workspace-features: + needs: basics + name: test workspace + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - windows-latest + - ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + lfs: true + + - name: Install Rust ${{ env.rust_stable }} + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.rust_stable }} + + - name: Install cargo-nextest + uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest + + - uses: Swatinem/rust-cache@v2 + + - name: test workspace with nextest + run: | + set -euxo pipefail + cargo nextest run --workspace --cargo-profile ci \ + --features \ + virtual_storage,bf_tree,spherical-quantization,product-quantization,tracing,experimental_diversity_search + + cargo test --doc --workspace --profile ci + + # coverage: + # needs: basics + # name: code coverage + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # with: + # lfs: true + + # - name: Install Rust ${{ env.rust_stable }} + # uses: dtolnay/rust-toolchain@stable + # with: + # toolchain: ${{ env.rust_stable }} + # components: llvm-tools-preview + + # - name: Install cargo-llvm-cov + # uses: taiki-e/install-action@v2 + # with: + # tool: cargo-llvm-cov + + # - name: Install cargo-nextest + # uses: taiki-e/install-action@v2 + # with: + # tool: cargo-nextest + + # - uses: Swatinem/rust-cache@v2 + # - name: Generate code coverage + # run: | + # cargo llvm-cov nextest --cargo-profile ci \ + # --package diskann-wide \ + # --package diskann-vector \ + # --package diskann-quantization \ + # --package diskann \ + # --package diskann-linalg \ + # --package diskann-utils \ + # --package diskann-disk \ + # --lcov --output-path lcov.info + + # - name: Upload coverage to Codecov + # uses: codecov/codecov-action@v4 + # with: + # files: lcov.info + # fail_ci_if_error: false + # env: + # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + # miri: + # needs: basics + # name: miri-test + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # with: + # lfs: true + + # - name: Install Rust nightly with miri + # uses: dtolnay/rust-toolchain@stable + # with: + # toolchain: nightly + # components: miri + + # - name: Install cargo-nextest + # uses: taiki-e/install-action@v2 + # with: + # tool: cargo-nextest + + # - uses: Swatinem/rust-cache@v2 + # - name: miri + # run: cargo +nightly miri nextest run --package diskann-quantization + # env: + # MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance diff --git a/Cargo.toml b/Cargo.toml index 589533d2a..8f4c20aeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,3 +114,10 @@ opt-level = 3 codegen-units = 1 debug = true split-debuginfo = "packed" + +[profile.ci] +inherits = "dev" +opt-level = 1 +debug = true +debug-assertions = true +overflow-checks = true diff --git a/diskann-providers/src/model/graph/provider/async_/table_delete_provider.rs b/diskann-providers/src/model/graph/provider/async_/table_delete_provider.rs index 64a2985f8..8595f7f1d 100644 --- a/diskann-providers/src/model/graph/provider/async_/table_delete_provider.rs +++ b/diskann-providers/src/model/graph/provider/async_/table_delete_provider.rs @@ -69,6 +69,7 @@ impl TableDeleteProviderAsync { } /// Serialize the delete bitmap to bytes (little-endian u32 values) + #[cfg(feature = "bf_tree")] pub(crate) fn to_bytes(&self) -> Vec { let mut bytes = Vec::with_capacity(std::mem::size_of_val(self.delete_table.as_slice())); for atomic_val in &self.delete_table { @@ -79,6 +80,7 @@ impl TableDeleteProviderAsync { } /// Create a TableDeleteProviderAsync from serialized bytes + #[cfg(feature = "bf_tree")] pub(crate) fn from_bytes(bytes: &[u8], max_size: usize) -> Result { let expected_len = max_size.div_ceil(32); @@ -234,6 +236,7 @@ mod tests { } #[test] + #[cfg(feature = "bf_tree")] fn test_save_load_roundtrip() { let original = get_test_delete_table_provider(50, &[0, 5, 20, 34, 48]); let bytes = original.to_bytes(); @@ -246,6 +249,7 @@ mod tests { } #[test] + #[cfg(feature = "bf_tree")] fn test_from_bytes_size_mismatch() { // max_size=50 requires ceil(50/32) = 2 u32 values = 8 bytes // Provide wrong number of bytes (e.g., 4 bytes = 1 u32) diff --git a/diskann-providers/src/model/pq/distance/dynamic.rs b/diskann-providers/src/model/pq/distance/dynamic.rs index cb854ff52..108fe0b30 100644 --- a/diskann-providers/src/model/pq/distance/dynamic.rs +++ b/diskann-providers/src/model/pq/distance/dynamic.rs @@ -601,7 +601,7 @@ mod tests { assert_relative_eq!( cosine_normalized.evaluate_similarity(&*code0, &*code1), expected, - max_relative = 2.0e-6, + max_relative = 4.0e-6, ); } } diff --git a/diskann-quantization/src/algorithms/transforms/padding_hadamard.rs b/diskann-quantization/src/algorithms/transforms/padding_hadamard.rs index 72d8c63fc..ed6d3b27f 100644 --- a/diskann-quantization/src/algorithms/transforms/padding_hadamard.rs +++ b/diskann-quantization/src/algorithms/transforms/padding_hadamard.rs @@ -455,7 +455,7 @@ mod tests { let natural_errors = test_utils::ErrorSetup { norm: test_utils::Check::ulp(4), l2: test_utils::Check::ulp(4), - ip: test_utils::Check::absrel(3.0e-6, 2e-4), + ip: test_utils::Check::absrel(5.0e-6, 2e-4), }; // NOTE: Subsampling introduces high variance in the norm and L2, so our error diff --git a/diskann-wide/src/test_utils/common.rs b/diskann-wide/src/test_utils/common.rs index b566e711f..c8845c410 100644 --- a/diskann-wide/src/test_utils/common.rs +++ b/diskann-wide/src/test_utils/common.rs @@ -124,9 +124,8 @@ impl ScalarTraits for f32 { } fn exact_eq(self, other: Self) -> bool { - // Miri does not seem to handle `total_cmp` correctly when it comes to `NAN`s - so - // we special case this comparison when running with Miri. - #[cfg(miri)] + // NAN handling can be dependent on environment. For testing purposes, we just care + // that NANs are produced. if self.is_nan() && other.is_nan() { return true; }