Skip to content

Releases: microsoft/DiskANN

DiskANN v0.53.0

28 May 16:01
f8bbf3e

Choose a tag to compare

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 (#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 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 (#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 #1067 and #983.

// Before
diskann_index.flat_search(query, ...)?;

// After
disk_index_searcher.flat_search(query, ...).await?;

DiskIndexSearcher::flat_search now batched (#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 (#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).

// Before
let table = FixedChunkPQTable::new(.., centroid, ..);

// After — drop the centroid argument
let table = FixedChunkPQTable::new(.., ..);

Flat search interface (#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. 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 (#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 (#1081)

direct_distance_impl (free function) and FixedChunkPQTable::inner_product_raw are pub again after being privatized in #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 (#1109)

The MinMax recompress API now accepts a grid-scale parameter.

New Features

  • SIMD-optimized L2-squared norm (#1107)
  • Significantly faster bitmap computation (#1099)
  • Large speedup on the bitmap construction path used by filtered search.
  • LLVM IR bloat regression check in CI (#1083)
  • CI now flags regressions in generated LLVM IR size, helping catch unintended monomorphization blow-ups.
  • Recall computation fix for under-k groundtruth (#1069)

Full List of Changes

New Contributors

Full Changelog: v0.52.0...v0.53.0

diskann-garnet v1.0.27

19 May 23:29
d7bf689

Choose a tag to compare

This release adds stack protectors to the diskann-garnet library.

DiskANN v0.52.0

12 May 21:07
c7dfae6

Choose a tag to compare

DiskANN v0.52.0 Release Notes

Breaking Changes

An AI generated, human reviewed list of changes is summarized below.

get_degree_stats signature changed (#998)

DiskANNIndex::get_degree_stats now takes an explicit iterator of IDs instead of requiring the data provider to implement IntoIterator.

// 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 (#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 (#976)

The free functions calculate_chunk_offsets and calculate_chunk_offsets_auto have been moved into constructors on ChunkOffsets / ChunkOffsetsView in diskann-quantization::views.

// 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 (#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 (#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

New Contributors

Full Changelog: v0.51.0...v0.52.0

DiskANN v0.51.0

05 May 20:21
d06369e

Choose a tag to compare

What's Changed

Full Changelog: v0.50.1...v0.51.0

DiskANN v0.50.1

27 Apr 20:19
56e3b7f

Choose a tag to compare

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 #871
  • Cleanup diskann-benchmark-runner and friends. by @hildebrandmw in #865
  • Use --all-targets for the no-default-features CI run. by @hildebrandmw in #874
  • Remove unused normalizing_util.rs from diskann-providers by @Copilot in #902
  • Benchmark Support for A/B Tests by @hildebrandmw in #900
  • [diskann-garnet] Bump diskann-garnet to 1.0.26 by @tiagonapoli in #925
  • Remove the AdjacencyList from diskann-providers by @hildebrandmw in #915
  • [PQ cleanup] Part 1: Move pq_scratch, quantizer_preprocess and pq_dataset to diskann-disk by @arkrishn94 in #930
  • Forbid Debug in diskann-benchmark by @arrayka in #914
  • Remove DebugProvider by @JordanMaples in #923
  • [diskann-garnet] Create workflow to publish to nuget by @tiagonapoli in #926
  • Move k-means implementation from diskann-providers to diskann-disk by @Copilot in #933
  • Inline minmax distance evaluations by @arkrishn94 in #935
  • Use rust-toolchain.toml in CI by @hildebrandmw in #934
  • Add a globally blocking CI gate. by @hildebrandmw in #932
  • Remove utils/math_util.rs from diskann-providers by @Copilot in #921
  • Bump rand from 0.9.2 to 0.9.3 by @dependabot[bot] in #945
  • Remove OPQ and friends by @arkrishn94 in #947
  • Migrate test_flaky_consolidate from diskann_providers to diskann by @JordanMaples in #942
  • Remove GraphDataType from diskann-providers by @wuw92 in #950
  • Remove unused method extract_best_l_candidates in NeighborPriorityQueue by @doliawu in #951
  • Add Debug bounds to VectorRepr's distance GATs. by @hildebrandmw in #948
  • Add benchmark pipeline with Rust-native A/B validation by @YuanyuanTian-hh in #912
  • Remove unnecessary Default bound from Neighbor's VectorIdType by @doliawu in #956
  • Replace AlignedBoxWithSlice with plain Vec / Matrix where alignment is unused by @wuw92 in #955
  • [minmax] 8-bit benchmark by @arkrishn94 in #959
  • Add MultiInsertStrategy implementations for BfTreeProvider by @hildebrandmw in #949
  • Replace AlignedBoxWithSlice with Vec in PQScratch and disk fp vector caches by @wuw92 in #960
  • Adding unit tests for paged_search by @JordanMaples in #962
  • Remove AlignedBoxWithSlice wrapper and add alias to Poly<[T], AlignedAllocator> by @JordanMaples in #965
  • Remove synthetic/structured data generation from diskann-providers by @JordanMaples in #963
  • added tests and some baselines for range_search by @JordanMaples in #961

New Contributors

Full Changelog: v0.50.0...v0.50.1

DiskANN-Garnet V1.0.26

09 Apr 22:10
ea37491

Choose a tag to compare

Tagging new release for Garnet.

DiskANN v0.50.0

31 Mar 16:14
db2a3fb

Choose a tag to compare

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 (#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.

// 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

// 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

// 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]>>.
  2. Add create_working_set returning a Map via Builder::new(Capacity::Default).build(capacity).
  3. Replace FillSet bound on PruneAccessor with workingset::Fill<Self::WorkingSet>.

FillSet to Fill<WorkingSet>

// 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:

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

// 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 (#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:

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 Vecs 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:

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:

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:

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>...
Read more

DiskANN v0.49.1

17 Mar 14:45
72fc467

Choose a tag to compare

What's Changed

  • Add block-transposed multi-vector representation for SIMD-friendly layouts by @suri-kumkaran in #805
  • [diskann-garnet] Allow configurable metric types by @tiagonapoli in #824
  • ci: use dtolnay/rust-toolchain@master instead of @stable by @Copilot in #827
  • Add macOS support to diskann-platform and diskann-disk by @Copilot in #781
  • diskann-disk benchmark : add ndims() and npoints() instead of accessing internal vars by @harsha-simhadri in #834
  • [minmax] Expose minmax heterogeneous distance api by @arkrishn94 in #833

New Contributors

Full Changelog: v0.49.0...v0.49.1

DiskANN v0.49.0

11 Mar 22:38
b628486

Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v0.48.0...v0.49.0

DiskANN v0.48.0

04 Mar 20:50
1016934

Choose a tag to compare

What's Changed

  • Expand BetaFilter for External SearchStrategy Implementations by @hailangx in #793
  • Enable Miri tests in CI with non-blocking execution by @Copilot in #697
  • Increase unit test coverage for diskann-disk crate by @Copilot in #761
  • Add code review instructions for license headers by @harsha-simhadri in #801
  • Consolidate load_bin somewhat by @hildebrandmw in #792
  • Add RFC process by @suri-kumkaran in #804
  • Fix variance issues with Mat and MatMut. by @hildebrandmw in #806
  • [Bugfix] NeighborPriorityQueue panics, when it is at capacity and NaN distance is inserted by @arrayka in #796
  • Enable cache_only mode for the BF-Tree cache by @hildebrandmw in #810
  • Enable customization of Tokio runtimes in diskann-benchmark-core by @hildebrandmw in #809

New Contributors

Full Changelog: v0.47.0...v0.48.0