Tags: fastio/DiskANN
Tags
[diskann-garnet] Fix handling of missing quant vectors during delete() ( microsoft#1130) Deleting an ID from the index removes several different keys associated with a vector: the internal and external ID mappings, the vector and quant vector data, the neighbor data, and the attributes. For each of these associated keys, we returned an error from `delete()` whenever the key was not found, but for attributes and quant vectors, these errors are benign since it is normal for those keys to be missing. It is cheaper to attempt to delete a key than to check whether it exists and then delete it if it does. A corresponding test for delete is added, although other tests would have also caught this had the `Store` test storage utility not unconditionally trued true for deletes. It now better models Garnet's behavior where deletes to non-existent keys returns false. Co-authored-by: Jack Moffitt <jackmoffitt@microsoft.com>
[diskann-garnet] Implement BIN and Q8 quantizers (microsoft#1050) This implements the `BIN` and `Q8` quantizers for diskann-garnet. This PR includes the following: - Vectors are now stored as `Poly<[u8], AlignOfEight>` instead of `Vec<T>`. This means that the same type can be used for both quantized and full precision vectors. - A new `GarnetQuantizer` trait is inroduced which allows us to type-erase the different quantizers instead of parameterizing the index/provider types. - Two new FFI calls were added to diskann-garnet. `insert()` now returns a success flag which can also signal that an associated quantizer is ready for training. `build_quant_table()` is called by Garnet asynchronously after `insert()` signals readiness for training to build quantization tables, and `backfill_quant_vectors` is called when training is complete to quantize previously inserted vectors. These new calls allow Garnet to control the training and backfill threads. - The accessor is now called `DynamicAccessor` because it dynamically switches between full precision only and quantized only operation depending on availability and readiness of the associated quantizer. - A new `visit_used()` call was added to the FSM to facilitate walking all stored vectors. - The FSM is now lockable, which prevents it reusing IDs during quantization backfill. This means once backfilling starts, new inserts will be assigned IDs above any that need quantization. - vectorset now supports quantization and distance metric settings, and a concurrency bug with multi-threaded inserts was fixed which caused IDs to be assigned incorrectly. Disk persistence will be done in a follow along with more extensive testing. Because of the FFI changes, the version has been bumped to 2.0.0.
Bump version to 0.53.0 (microsoft#1111) # DiskANN v0.53.0 Release Notes ## Breaking Changes An AI generated, human reviewed list of changes is summarized below. ### Paged search overhauled — channel-based API ([microsoft#1078](microsoft#1078)) `PagedSearchState` and its `'static`-bound pause/resume model have been replaced with an async, channel-based interface. The recommended way to drive paged search is now via a `tokio::sync::mpsc` channel, with the searcher embedded in an otherwise-`'static` future. See the [rendered RFC](https://github.com/microsoft/DiskANN/blob/main/rfcs/01078-paged-search.md) for the new shape. Callers wired against `PagedSearchState` must migrate to the channel API. Users of paged search via `wrapped_async::DiskANNIndex` that know their inner futures will never suspend can use the new `wrapped_async::DiskANNIndex::paged_search_no_await`; this will efficiently run paged searches with minimal synchronization overhead. ### `DiskANNIndex::flat_search` removed ([microsoft#1076](microsoft#1076)) `DiskANNIndex::flat_search` and the `IdIterator` trait have been removed from the `diskann` crate. Equivalent functionality lives on the new inherent method `DiskIndexSearcher::flat_search` in `diskann-disk`. This unblocks the experimental directions in microsoft#1067 and microsoft#983. ```rust // Before diskann_index.flat_search(query, ...)?; // After disk_index_searcher.flat_search(query, ...).await?; ``` ### `DiskIndexSearcher::flat_search` now batched ([microsoft#1097](microsoft#1097)) The new `DiskIndexSearcher::flat_search` uses the bulk `pq_distances` path instead of one-vector-at-a-time `Accessor::build_query_computer` + `evaluate_similarity`. Downstream behavior is equivalent but tighter resource bounds apply. ### `centroid` removed from PQ interfaces ([microsoft#1010](microsoft#1010)) The dataset-centroid argument has been removed from `FixedChunkPQTable` construction, `populate`, and most other PQ APIs. The shift only ever worked for L2 distance and was silently ignored for inner-product / cosine, so passing it was a footgun. When an L2 shift is required, fold it into the PQ pivots instead (the library now does this internally). ```rust // Before let table = FixedChunkPQTable::new(.., centroid, ..); // After — drop the centroid argument let table = FixedChunkPQTable::new(.., ..); ``` ### Flat search interface ([microsoft#983](microsoft#983)) A new `flat` module in `diskann` adds a provider-agnostic brute-force search surface, mirroring the shape of graph search. Backends implement a single trait, `DistancesUnordered<C>` (in `flat/strategy.rs`), which fuses iteration and distance computation, allowing any backend (in-memory, quantized, disk, remote) to plug into a shared algorithm. See the [rendered RFC](https://github.com/microsoft/DiskANN/blob/main/rfcs/00983-flat-search.md). This is additive but is the new canonical surface — direct ad-hoc flat-search call sites should migrate. ### `bf_tree` extracted into `diskann-bftree` crate ([microsoft#1020](microsoft#1020)) The bf_tree provider has been moved out of `diskann-providers` (previously at `diskann-providers/src/model/graph/provider/async_/bf_tree/`) into a new standalone `diskann-bftree` crate. Along with the move: - Switched from PQ to spherical quantization. - Dropped dependencies on `DeletionCheck`, `AsDeletionCheck`, and `RemoveDeletedIdsAndCopy`. - Simplified generics. Consumers must update their `Cargo.toml` to depend on `diskann-bftree` and update import paths. ### `direct_distance_impl` and `inner_product_raw` re-exposed ([microsoft#1081](microsoft#1081)) `direct_distance_impl` (free function) and `FixedChunkPQTable::inner_product_raw` are `pub` again after being privatized in microsoft#1044. Restored to unblock a downstream user. Not breaking in the typical direction — this restores previously available API surface. ### MinMax `recompress` takes a grid-scale parameter ([microsoft#1109](microsoft#1109)) The MinMax `recompress` API now accepts a grid-scale parameter. ## New Features - SIMD-optimized L2-squared norm ([microsoft#1107](microsoft#1107)) - Significantly faster bitmap computation ([microsoft#1099](microsoft#1099)) - Large speedup on the bitmap construction path used by filtered search. - LLVM IR bloat regression check in CI ([microsoft#1083](microsoft#1083)) - CI now flags regressions in generated LLVM IR size, helping catch unintended monomorphization blow-ups. - Recall computation fix for under-k groundtruth ([microsoft#1069](microsoft#1069)) ## Merged PRs * Revise README for DiskANN3 by @harsha-simhadri in microsoft#1046 * [CI] Try to fix publishing step by @hildebrandmw in microsoft#1057 * [benchmark] Remove `DispatchRule` by @hildebrandmw in microsoft#1064 * [benchmark] Automatic Input Registration by @hildebrandmw in microsoft#1066 * Remove centroid from most PQ interfaces by @hildebrandmw in microsoft#1010 * [diskann/disk] Remove `flat_search` from `DiskANNIndex` by @hildebrandmw in microsoft#1076 * macos build and miri check to nightly by @harsha-simhadri in microsoft#1058 * [API] Make some methods public again by @hildebrandmw in microsoft#1081 * [benchmark] Simply `Inputs` more by @hildebrandmw in microsoft#1077 * Turn on stack protection for the diskann-garnet NuGet build by @jackmoffitt in microsoft#1082 * Fix options for diskann-garnet nuget pipeline by @jackmoffitt in microsoft#1091 * [CI] add LLVM IR bloat regression check by @arazumov in microsoft#1083 * Bump openssl from 0.10.79 to 0.10.80 by @dependabot[bot] in microsoft#1093 * [Disk CI benchmarks] Use 1ES.Pool=diskann-github by @arazumov in microsoft#869 * Fix recall computation for fewer than k groundtruth results by @magdalendobson in microsoft#1069 * bf_tree migration away from diskann-providers by @JordanMaples in microsoft#1020 * [RFC/diskann] Overhaul paged search by @hildebrandmw in microsoft#1078 * Remove unsafe code from compute_vec_l2sq by @arazumov in microsoft#1094 * Remove direct accessor call in `diskann-garnet` by @hildebrandmw in microsoft#1098 * Refactor `DiskIndexSearcher::flat_search` to use batching by @hildebrandmw in microsoft#1097 * [flat index] Flat Search Interface by @arkrishn94 in microsoft#983 * migrating multi-hop tests from diskann-providers to diskann by @JordanMaples in microsoft#928 * Significantly speed up bitmap computation by @magdalendobson in microsoft#1099 * `compute_vecs_l2sq`: Replace scalar L2 Squared norm with SIMD-optimized FastL2NormSquared by @arazumov in microsoft#1107 * [minmax] Add grid scaling to recompress API by @arkrishn94 in microsoft#1109 **Full Changelog**: microsoft/DiskANN@v0.52.0...v0.53.0
Fix options for diskann-garnet nuget pipeline (microsoft#1091) `-Z` flags must be specified individually and can't be combined. This corrects the syntax. Co-authored-by: Jack Moffitt <jackmoffitt@microsoft.com>
Set version to v0.52.0 (microsoft#1056) # DiskANN v0.52.0 Release Notes ## Breaking Changes An AI generated, human reviewed list of changes is summarized below. ### `get_degree_stats` signature changed ([microsoft#998](microsoft#998)) `DiskANNIndex::get_degree_stats` now takes an explicit iterator of IDs instead of requiring the data provider to implement `IntoIterator`. ```rust // Before — provider had to impl IntoIterator index.get_degree_stats(&mut accessor)?; // After — caller supplies the ID iterator index.get_degree_stats(&mut accessor, id_iter)?; ``` ### PQ dimension contract tightened; entries now `&[f32]` only ([microsoft#1044](microsoft#1044)) With `AlignedBoxWithSlice` removed from the PQ path, the dimension handling has been refactored into a three-layer contract: | Layer | Where | Contract | |---|---|---| | **Boundary (inmem)** | `QueryComputer::new`, `MultiQueryComputer::new`, `DistanceComputer::evaluate_similarity` | `len == dim` (returns `Err` on mismatch) | | **Boundary (disk)** | `PQScratch::set` | `len >= dim`, slices to `[..dim]` | | **Internal** | `TableL2/IP/Cosine::{new, populate}` | Trusted — no re-validation | **Other changes:** - PQ table populate/distance methods now accept `&[f32]` instead of `<U: Into<f32>>`. Callers must pre-decode quantized vectors via `VectorRepr::as_f32`. - Generic trampoline impls (`&Vec<u8>`, `&&[u8]`) on `QueryComputer` / `DistanceComputer` have been removed. ### `calculate_chunk_offsets` relocated to `ChunkOffsets` constructors ([microsoft#976](microsoft#976)) The free functions `calculate_chunk_offsets` and `calculate_chunk_offsets_auto` have been moved into constructors on `ChunkOffsets` / `ChunkOffsetsView` in `diskann-quantization::views`. ```rust // Before let offsets = calculate_chunk_offsets(dim, num_chunks); // After (allocating) let offsets = ChunkOffsets::partition(dim, num_chunks)?; // After (zero-alloc, borrows caller-owned scratch) let view = ChunkOffsetsView::partition_into(dim, &mut scratch)?; ``` Additionally, `get_chunk_from_training_data` has been moved from public API. ### `CachingProvider` removed ([microsoft#1052](microsoft#1052)) The entire `diskann_providers::model::graph::provider::async_::caching` module has been deleted. **Why:** The `CachingProvider` was an experiment in transparent caching over `DataProvider`. In practice it required double monomorphization of the indexing code, didn't save integration work for bulk methods like `on_elements_unordered`/`distances_unordered`, and was complex to maintain. An internal user who …migrated off it removed ~1,000 lines of code, improved compile times by ~20%, and substantially reduced complexity. **Upgrade:** Manage caching directly in your `DataProvider` implementation. ## New Features ### AVX-512 4-bit distance kernels ([microsoft#1045](microsoft#1045)) Native V4 (AVX-512) specializations for 4-bit packed vector distance computations: - **`SquaredL2`** — 16 × `u32` lanes per iteration via `_mm512_madd_epi16`. - **`InnerProduct`** — AVX-512 VNNI (`_mm512_dpbusd_epi32`) over `u8x64` / `i8x64` operands. Previously, V4 hardware fell back to two AVX2 (V3) kernel invocations per 512-bit chunk. The native kernels double per-instruction throughput. No API changes — existing code benefits automatically on AVX-512 capable hardware. ## Merged PRs * Deprecate 32-bit targets by @suhasjs in microsoft#1022 * Add a fast path to `Map::prepare`. by @hildebrandmw in microsoft#1023 * Add boundary checks in gen_associated_data_from_range() by @Copilot in microsoft#847 * [deps] Don't pull `rayon` as a dependency of `diskann`. by @hildebrandmw in microsoft#1024 * Bump openssl from 0.10.78 to 0.10.79 by @dependabot[bot] in microsoft#1026 * Cleaning up test work and changing the get_degree_stats signature. by @JordanMaples in microsoft#998 * Reduce scalar-quantization benchmark monomorphization by @suri-kumkaran in microsoft#1041 * [diskann-vector] Support truly unaligned distances. by @hildebrandmw in microsoft#981 * rename spherical.json to graph index with spherical quantization by @harsha-simhadri in microsoft#1042 * [PQ Cleanup] Part 2: Consolidate `calculate_chunk_offsets*` by @arkrishn94 in microsoft#976 * PQ: tighten dim contract; right-size scratch buffer by @wuw92 in microsoft#1044 * Add v4 distance kernels (4-bit SquaredL2 / InnerProduct) by @m3hm3t in microsoft#1045 * Remove the Caching Provider by @hildebrandmw in microsoft#1052 ## New Contributors * @suhasjs made their first contribution in microsoft#1022 * @m3hm3t made their first contribution in microsoft#1045 **Full Changelog**: microsoft/DiskANN@v0.51.0...v0.52.0 Co-authored-by: Mark Hildebrand <mhildebrand@microsoft.com>
[v0.51.0] Bump version to 0.51.0 (microsoft#1013) Bump version to 0.51.0 due to propagate changes to downstream consumers ## Breaking API changes (AI Generated) - **`ObjectPool` moved** (microsoft#975): now lives in `diskann-utils`. Update imports from `diskann::...::ObjectPool` → `diskann_utils::ObjectPool`. - **`AlignedSlice` removed** (microsoft#994): the `AlignedSlice` abstraction in `diskann-vector` is gone. Code that converted between vector representations through `AlignedSlice` should now use the `Poly` / `CastFromSlice` polymorphic interfaces directly (see `diskann-vector::conversion` and `diskann-quantization::alloc::poly`). Storage that previously held `AlignedSlice` values should hold `Poly<T, A>` instead. - **`AsThreadPool` generic removed** (microsoft#967): functions that previously took `pool: impl AsThreadPool` now take `pool: &RayonThreadPool`. Pass a borrow of an existing pool; remove the generic parameter from your call sites. - **`sgemm()` returns `Result`** (microsoft#997): in `diskann-linalg`, the new signature is: ```rust pub fn sgemm( atranspose: Transpose, btranspose: Transpose, m: usize, n: usize, k: usize, alpha: f32, a: &[f32], b: &[f32], beta: Option<f32>, c: &mut [f32], ) -> Result<(), SgemmError> ``` `SgemmError` has variants `InvalidMatrixDimensions { matrix_name, expected_rows, expected_cols, actual_len }` and `DimensionOverflow { matrix_name, rows, cols }`. Replace previous panic-on-bad-input assumptions with explicit handling. - **Benchmarks are stateful** (microsoft#995): the `Benchmark` impls in `diskann-benchmark` are no longer stateless unit structs. Each benchmark type now has a `::new()` constructor (often holding `PhantomData<T>` or plugin state), and registration uses an instance: ```rust // before benchmarks.register("name", MyBench); // after benchmarks.register("name", MyBench::<T>::new()); ``` If you wrote a custom benchmark, give it a `new()` and register an instance. Combined with microsoft#996, search-side benchmarks now compose `Plugins<Provider, Phase, Strategy>` and expose builder methods like `.search(plugin)` to register search plugins on the instance. - **`diskann-benchmark`: `async` → `graph-index`** (microsoft#1009): the benchmark category previously named `async` was renamed to `graph-index`. JSON config `type` values and example file names changed accordingly: - `async-build` → `graph-index-build` - `async-dynamic-run` → `graph-index-dynamic-run` - and the same prefix swap for `*-pq`, `*-sq`, `*-spherical-quantization`, etc. Update any benchmark config files, scripts, or CI that reference the old `async-*` names. - **`diskann-disk` buffer alignment decoupled from `block_size`** (microsoft#984): code that assumed I/O buffer alignment equals the disk block size should now configure alignment explicitly. ## Non-breaking - New cache-aware block-transposed Chamfer/MaxSim distance for f32/f16 (microsoft#863). - A/A benchmark documentation (microsoft#974); CI publish workflow improvements (microsoft#755, microsoft#1017); openssl bump (microsoft#973); `compute_closest_centers` allocation reduction (microsoft#980). - **`DistanceComputer` `'static` bound relaxed** (microsoft#1007) and **redundant `DistanceFunction` impls removed** (microsoft#1008) **Full Changelog**: microsoft/DiskANN@v0.50.1...v0.51.0
Bump to 0.50.1 (microsoft#970) Bumping to 0.50.1 to propagate changes to consumers. Changes since previous bump: ## What's Changed * Add more agentic guard rails by @hildebrandmw in microsoft#871 * Cleanup `diskann-benchmark-runner` and friends. by @hildebrandmw in microsoft#865 * Use `--all-targets` for the no-default-features CI run. by @hildebrandmw in microsoft#874 * Remove unused `normalizing_util.rs` from `diskann-providers` by @Copilot in microsoft#902 * Benchmark Support for A/B Tests by @hildebrandmw in microsoft#900 * [diskann-garnet] Bump diskann-garnet to 1.0.26 by @tiagonapoli in microsoft#925 * Remove the `AdjacencyList` from `diskann-providers` by @hildebrandmw in microsoft#915 * [PQ cleanup] Part 1: Move pq_scratch, quantizer_preprocess and pq_dataset to `diskann-disk` by @arkrishn94 in microsoft#930 * Forbid Debug in diskann-benchmark by @arrayka in microsoft#914 * Remove DebugProvider by @JordanMaples in microsoft#923 * [diskann-garnet] Create workflow to publish to nuget by @tiagonapoli in microsoft#926 * Move k-means implementation from diskann-providers to diskann-disk by @Copilot in microsoft#933 * Inline minmax distance evaluations by @arkrishn94 in microsoft#935 * Use `rust-toolchain.toml` in CI by @hildebrandmw in microsoft#934 * Add a globally blocking CI gate. by @hildebrandmw in microsoft#932 * Remove `utils/math_util.rs` from `diskann-providers` by @Copilot in microsoft#921 * Bump rand from 0.9.2 to 0.9.3 by @dependabot[bot] in microsoft#945 * Remove OPQ and friends by @arkrishn94 in microsoft#947 * Migrate test_flaky_consolidate from diskann_providers to diskann by @JordanMaples in microsoft#942 * Remove GraphDataType from diskann-providers by @wuw92 in microsoft#950 * Remove unused method extract_best_l_candidates in NeighborPriorityQueue by @doliawu in microsoft#951 * Add `Debug` bounds to `VectorRepr`'s distance GATs. by @hildebrandmw in microsoft#948 * Add benchmark pipeline with Rust-native A/B validation by @YuanyuanTian-hh in microsoft#912 * Remove unnecessary `Default` bound from `Neighbor`'s `VectorIdType` by @doliawu in microsoft#956 * Replace `AlignedBoxWithSlice` with plain `Vec` / `Matrix` where alignment is unused by @wuw92 in microsoft#955 * [minmax] 8-bit benchmark by @arkrishn94 in microsoft#959 * Add `MultiInsertStrategy` implementations for `BfTreeProvider` by @hildebrandmw in microsoft#949 * Replace `AlignedBoxWithSlice` with `Vec` in PQScratch and disk fp vector caches by @wuw92 in microsoft#960 * Adding unit tests for paged_search by @JordanMaples in microsoft#962 * Remove AlignedBoxWithSlice wrapper and add alias to Poly<[T], AlignedAllocator> by @JordanMaples in microsoft#965 * Remove synthetic/structured data generation from diskann-providers by @JordanMaples in microsoft#963 * added tests and some baselines for range_search by @JordanMaples in microsoft#961 ## New Contributors * @JordanMaples made their first contribution in microsoft#923 * @wuw92 made their first contribution in microsoft#950 * @doliawu made their first contribution in microsoft#951 * @YuanyuanTian-hh made their first contribution in microsoft#912 **Full Changelog**: microsoft/DiskANN@v0.50.0...v0.50.1
[diskann-garnet] Create workflow to publish to nuget (microsoft#926) Create Github Actions workflow to publish to Nuget using NUGET_API_KEY added to repository - Publishes will be triggered by `diskann-garnet-v*` tags --------- Co-authored-by: Tiago Napoli <tiagonapoli@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bump to 0.50.0 (microsoft#870) # Version 0.50.0 Release notes generated with the help of Copilot. ## API Breaking Changes This release contains several large API changes that should be mechanical to apply to existing code but that will enable forward evolution of our integrations. ### WorkingSet/Fillset/Strategy API Changes (microsoft#859) #### Removals The following items have been removed: | Removed | Replacement | |--------|--------------| | `Fill` | `diskann::graph::workingset::Fill<WorkingSet>` and `diskann::graph::workingset::Map` | | `AsElement<T>` | `diskann::graph::glue::MultiInsertStrategy::finish` and `diskann::graph::workingset::AsWorkingSet` | | `Accessor::Extended` | Just gone! | | `VectorIdBoxSlice` | `diskann::graph::glue::Batch` (and `Matrix<T>`) | | `reborrow::Lower`/`AsyncLower` | No longer needed | #### Strategy Type Parameters (no longer `?Sized`) The `T` parameter on `SearchStrategy`, `InsertStrategy`, and `SetElement` is no longer `?Sized`. ```rust // Before impl SearchStrategy<DP, [f32]> for MyStrategy { ... } impl SetElement<[f32]> for MyProvider { ... } // After impl SearchStrategy<DP, &[f32]> for MyStrategy { ... } impl SetElement<&[f32]> for MyProvider { ... } ``` HRTB syntax is now generally needed, but makes contraints far more uniform. Similarly, `InplaceDeleteStrategy::DeleteElement<'a>` has been tightened from `Send + Sync + ?Sized` to `Copy + Send + Sync` (sized). If your delete element was an unsized type like `[f32]`, change it to a reference like `&'a [f32]`. #### `Guard` moved from `SetElement` to `DataProvider` ```rust // Before trait SetElement<T>: DataProvider { type Guard: Guard<Id = Self::InternalId>; ... } // After trait DataProvider { type Guard: Guard<Id = Self::InternalId> + 'static; ... } trait SetElement<T>: DataProvider { // Returns Self::Guard from DataProvider fn set_element(...) -> Result<Self::Guard, Self::SetError>; } ``` Move your `Guard` associated type from your `SetElement` impl to your `DataProvider` impl. #### `PruneStrategy` gains `WorkingSet` ```rust // Before trait PruneStrategy<Provider> { type PruneAccessor<'a>: Accessor + ... + FillSet; ... } // After trait PruneStrategy<Provider> { type WorkingSet: Send + Sync; type PruneAccessor<'a>: Accessor + ... + workingset::Fill<Self::WorkingSet>; fn create_working_set(&self, capacity: usize) -> Self::WorkingSet; ... } ``` **What to do:** 1. Add a `WorkingSet` associated type. Use `workingset::Map<Id, Box<[T]>>` as the default. Users of multi-insert will want to use `workingset::Map<Id, Box<[T]>, workingset::map::Ref<[T]>>`. 3. Add `create_working_set` returning a `Map` via `Builder::new(Capacity::Default).build(capacity)`. 4. Replace `FillSet` bound on `PruneAccessor` with `workingset::Fill<Self::WorkingSet>`. #### `FillSet` to `Fill<WorkingSet>` ```rust // Before impl FillSet for MyAccessor { fn fill_set(&mut self, ids: &[Id], map: &mut HashMap<Id, Extended>) -> Result<()> { ... } } // After impl Fill<MyWorkingSet> for MyAccessor { type Error = ...; type View<'a> = ... where Self: 'a, MyWorkingSet: 'a; fn fill<'a, Itr>( &'a mut self, working_set: &'a mut MyWorkingSet, itr: Itr, ) -> impl SendFuture<Result<Self::View<'a>, Self::Error>> where Itr: ExactSizeIterator<Item = Self::Id> + Clone + Send + Sync, { ... } } ``` **Shortcut:** If your `WorkingSet` is `workingset::Map<K, V, P>` and your accessor's `ElementRef` converts `Into<V>`, the blanket `Fill` impl works automatically, then you don't need to implement `Fill` at all. #### New `MultiInsertStrategy` (replaces multi-insert portion of `InsertStrategy`) If you use `multi_insert`, you now need a `MultiInsertStrategy` implementation: ```rust trait MultiInsertStrategy<Provider, B: Batch>: Send + Sync { type WorkingSet: Send + Sync + 'static; type Seed: AsWorkingSet<Self::WorkingSet> + Send + Sync + 'static; type FinishError: Into<ANNError> + std::fmt::Debug + Send + Sync; type InsertStrategy: for<'a> InsertStrategy< Provider, B::Element<'a>, PruneStrategy: PruneStrategy<Provider, WorkingSet = Self::WorkingSet>, >; fn insert_strategy(&self) -> Self::InsertStrategy; fn finish<Itr>(&self, provider: &Provider, context: &Provider::Context, batch: &Arc<B>, ids: Itr, ) -> impl Future<Output = Result<Self::Seed, Self::FinishError>> + Send where Itr: ExactSizeIterator<Item = Provider::InternalId> + Send; } ``` **What to do:** - `finish` is called once after the batch is inserted. Use it to pre-populate working set state (e.g., fetch quantized representations). Return a `Seed` that implements `AsWorkingSet`. - For the common case, use `workingset::map::Builder` as your `Seed` type. - `multi_insert` now takes `Arc<B>` where `B: Batch` instead of `VectorIdBoxSlice`. `Matrix<T>` implements `Batch` out of the box. Using `diskann::graph::workingset::Map` and its associated infrastructure should largely work. #### `multi_insert` call site ```rust // Before index.multi_insert(strategy, context, vectors, ids).await?; // where vectors: VectorIdBoxSlice // After index.multi_insert::<S, B>(strategy, context, vectors, ids).await?; // where vectors: Arc<B>, B: Batch // DP: for<'a> SetElement<B::Element<'a>> is required ``` The turbofish `<S, B>` is often needed because HRTB bounds make inference conservative. ### Search Post Processing (microsoft#828) The `Search` trait now requires `S: SearchStrategy<DP, T>` at the trait level, and introduces two new generic parameters on the `search` method: - `O` - the output type written to the buffer (used to be a parameter of `SearchStrategy`) - `PP` — the post-processor, bounded by `for<'a> SearchPostProcess<S::SearchAccessor<'a>, T, O> + Send + Sync` - `OB` — the output buffer, bounded by `SearchOutputBuffer<O> + Send + ?Sized` Previously, `OB` was a trait-level generic and the post-processor was obtained internally from the strategy. Now both are method-level generics, giving callers control over each independently. The output "id" type `O` has been removed from `SearchStrategy` entirely, which greatly simplifies things. At the `DiskANNIndex` level, two entry points are provided: - `search()` — requires `S: DefaultPostProcessor`, creates the strategy's default processor automatically, then delegates to `search_with()`. This is the common path. - `search_with()` — requires only `S: SearchStrategy`, accepts any compatible `PP`. This is the extension point for custom post-processing. #### DefaultPostProcessor Strategies opt in to default post-processing by implementing `DefaultPostProcessor`: ```rust pub trait DefaultPostProcessor<Provider, T, O>: SearchStrategy<Provider, T> { type Processor: for<'a> SearchPostProcess<Self::SearchAccessor<'a>, T, O> + Send + Sync; fn default_post_processor(&self) -> Self::Processor; } ``` The `default_post_processor!` macro covers the common case where the processor is `Default`-constructible. `DefaultSearchStrategy` is a convenience trait (with a blanket impl…) for `SearchStrategy + DefaultPostProcessor`. #### Start-point filtering The in-mem code used to rely on a special `Internal` type to customize post-processing for the inplace delete pipeline. Now that post-processing is customizable, this workaround is no longer needed. Start-point filtering is composed at the type level using the existing `Pipeline` mechanism: - `HasDefaultProcessor::Processor = Pipeline<FilterStartPoints, RemoveDeletedIdsAndCopy>` — filters start points during regular search - `InplaceDeleteStrategy::SearchPostProcessor = RemoveDeletedIdsAndCopy` — no start-point filtering during delete re-pruning #### Range search To respond to moving the output buffer to a method level geneirc, range search now writes results directly through a `DistanceFiltered` output buffer wrapper that applies radius and inner-radius filtering inline during post-processing. This replaces the previous approach of allocating intermediate `Vec`s and copying results through an `IdDistance` buffer. `SearchOutputBuffer` is now implemented for `Vec<Neighbor<I>>`, providing an unbounded growable buffer suitable for range search and other cases where the result count is not known in advance. The `RangeSearchResults` type has been removed now that a `SearchOutputBuffer` is properly used. #### Migration guide **If you implement `SearchStrategy`** — minimal changes required. Remove the `O` parameter (if needed). Implement `DefaultPostProcessor` with your current post-processor if you want your strategy to work with `index.search()`. Use the `default_post_processor!` macro for simple cases: ```rust impl DefaultPostProcessor<MyProvider, [f32]> for MyStrategy { default_post_processor!(Pipeline<FilterStartPoints, RemoveDeletedIdsAndCopy>); } ``` **If you call `index.search()`** — no API change for strategies that implement `DefaultPostProcessor` (all built-in strategies do). **If you need custom post-processing** — use `index.search_with()` and pass your post-processor directly: ```rust let processor = MyCustomPostProcessor::new(/* ... */); let stats = index.search_with(params, &strategy, processor, &context, &query, &mut output).await?; ``` **If you implement `Search`** — the method signature has changed. `PP` and `OB` are now method-level generics, and `processor` is an explicit parameter: ```rust fn search_with<O, PP, OB>( self, index: &DiskANNIndex<DP>, strategy: &S, processor: PP, // new context: &DP::Context, query: T, output: &mut OB, ) -> impl SendFuture<ANNResult<Self::Output>> where P: Search<DP, S, T>, S: glue::SearchStrategy<DP, T>, PP: for<'a> glue::SearchPostProcess<S::SearchAccessor<'a>, T, O> + Send + Sync, O: Send, OB: search_output_buffer::SearchOutputBuffer<O> + Send + ?Sized, ``` **If you use range search results** — `RangeSearchOutput` no longer carries `ids` and `distances` fields. Results are written to the output buffer passed to `search()`. Use `Vec<Neighbor<u32>>` (or any `SearchOutputBuffer` impl) to collect them. **If you implement `InplaceDeleteStrategy`**: Add the `DeleteSearchAccessor` associated type, ensuring it is the same as the accessor selected by the `SearchStrategy`. This is redundant information, but needed to help constrain downstream trait bounds. ### V4 Backend Raised to Ice Lake / Zen4+ (microsoft#861) The `V4` SIMD backend in `diskann-wide` now requires additional AVX-512 extensions beyond the baseline `x86-64-v4` feature set: | New requirement | Description | |-----------------|-------------| | `avx512vnni` | Vector Neural Network Instructions | | `avx512bitalg` | Bit algorithm instructions (VPOPCNT on bytes/words) | | `avx512vpopcntdq` | POPCNT on dword/qword elements | | `avx512vbmi` | Vector Byte Manipulation Instructions (cross-lane byte permutes) | This effectively raises the minimum CPU for the `V4` tier from Cascade Lake to **Ice Lake Server (Intel)** or **Zen4+ (AMD)**. **Impact:** On CPUs with baseline AVX-512 but without these extensions (e.g., Skylake-X, Cascade Lake), runtime dispatch will select the `V3` backend instead. No code changes are required. ## What's Changed * Remove dead code from diskann-tools by @Copilot in microsoft#829 * CI maintenance by @hildebrandmw in microsoft#838 * Add baseline tests for insert/multi-insert. by @hildebrandmw in microsoft#837 * Remove unused file by @ChenSunriseJiaBao in microsoft#850 * Add compatibility tests for spherical flatbuffer serialization by @hildebrandmw in microsoft#839 * Overhaul Post-Processing by @hildebrandmw in microsoft#828 * Bump the `V4` backend to IceLake/Zen4+ by @hildebrandmw in microsoft#861 * Remove `ConcurrentQueue` from `diskann-providers` by @hildebrandmw in microsoft#862 * Enhance code review instructions with additional checks by @harsha-simhadri in microsoft#866 * Add synchronous `load_with` constructors and `run` escape hatch to `wrapped_async` by @suri-kumkaran in microsoft#864 * Overhaul WorkingSet/FillSet and its interaction with Multi-Insert by @hildebrandmw in microsoft#859 ## New Contributors * @ChenSunriseJiaBao made their first contribution in microsoft#850 **Full Changelog**: microsoft/DiskANN@v0.49.1...v0.50.0
Remove `ConcurrentQueue` from `diskann-providers` (microsoft#862) The concurrent queue tests have been flaky on MacOS. It turns out that no-one is using this data structure anyways, so we can fix the flaky tests be removing it entirely.
PreviousNext