Skip to content

[flat index] Flat Search Interface#983

Open
arkrishn94 wants to merge 29 commits into
mainfrom
u/adkrishnan/flat-index
Open

[flat index] Flat Search Interface#983
arkrishn94 wants to merge 29 commits into
mainfrom
u/adkrishnan/flat-index

Conversation

@arkrishn94
Copy link
Copy Markdown
Contributor

@arkrishn94 arkrishn94 commented Apr 28, 2026

This PR introduces a trait interface and a light index to support brute-force search for providers that can be used as/are a flat-index. There is an associated RFC that walks through the interface and associated implementation in diskann as a new flat module.

Rendered RFC link.

Motivation

The repo has no first-class surface for brute-force search today. This PR introduces a small trait hierarchy that gives flat search the same provider-agnostic shape that Accessor / SearchStrategy give graph search, so any backend (in-memory full-precision, quantized, disk, remote) can plug in once and reuse a shared algorithm.

This allows implementations of algorithms - diverse search, knn search, range search, pre-filtered search - to live in the repo and let consumers only worry about defining the the data is accessed / provided; just like we do for graph search.

Important refactor

We start with an important refactor of BuildQueryComputer and its associated ElementRef<'a> that it acts on in diskann::providers.

HasElementRef - a new minimal shape trait

This zero-method traits was extracted so the streaming flat traits and the random-access Accessor can share their associated types without one inheriting from the other:

pub trait HasElementRef { type ElementRef<'a>; }

Accessor: HasId + HasElementRef, and the flat traits below depend on the same two pieces independently.

BuildQueryComputer<T> - shared query preprocessing

We split the trait in diskann::provider to into:

  1. a core trait that has the build_query_computer method and the QueryComputer associated type, and,
  2. a sub-trait titled DistancesUnordered that has the distances_unordered method.

This split allows both graph and flat indexes to require building of a computer without dragging in random access.

Flat search - core components

flat::DistancesUnordered<T> — the single required trait

Implemented in flat/iterator.rs, this is the only trait a backend must implement for flat search. It fuses iteration and scoring into one method: the implementation drives an entire scan over its data, scoring each element with the supplied query computer and invoking a callback with (id, distance) pairs. Implementations choose iteration order, prefetching, and any bulk reads; algorithms see only (Id, f32) pairs.

pub trait DistancesUnordered<T>: HasId + BuildQueryComputer<T> + Send + Sync {
    type Error: ToRanked + Debug + Send + Sync + 'static;

    fn distances_unordered<F>(
        &mut self,
        computer: &<Self as BuildQueryComputer<T>>::QueryComputer,
        f: F,
    ) -> impl SendFuture<Result<(), Self::Error>>
    where
        F: Send + FnMut(<Self as HasId>::Id, f32);
}

flat::SearchStrategy<P, T> — the glue

Implemented in flat/strategy.rs. Mirrors graph::glue::SearchStrategy (disambiguated by module path) and simply creates a concrete type that implements the scan:

pub trait SearchStrategy<P, T>: Send + Sync where P: DataProvider {
    type Visitor<'a>: DistancesUnordered<T> where Self: 'a, P: 'a;
    type Error: StandardError;

    fn create_visitor<'a>(
        &'a self,
        provider: &'a P,
        context: &'a P::Context,
    ) -> Result<Self::Visitor<'a>, Self::Error>;
}

Strategies are stateless per-call config — constructed at the call site, used for one search and then dropped.

FlatIndex<P> — the top-level handle

Implemented in flat/index.rs, this is a thin 'static wrapper around a DataProvider. The search is implemented as:

  1. asks the strategy for a per-query visitor,
  2. builds the query computer from the visitor (BuildQueryComputer::build_query_computer),
  3. drives visitor.distances_unordered(&computer, ...) through a NeighborPriorityQueue,
  4. hands the survivors to a graph::glue::SearchPostProcess to write into the output buffer.

Note the SearchPostProcess bound: the same trait used by graph search!

FlatIterator + Iterated<I> — opt-in convenience

For backends that naturally expose element-at-a-time access, FlatIterator is a lending async iterator with a single next(). Iterated<I> wraps any FlatIterator and implements DistancesUnordered<T> (when the inner type also implements BuildQueryComputer<T>) by looping over next(), reborrowing each element, and scoring it with the supplied query computer.

A backend opts in at exactly the right layer: bulk-friendly backends implement DistancesUnordered directly; element-at-a-time backends implement FlatIterator and use Iterated for the rest.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 90.90909% with 57 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.52%. Comparing base (d00cac0) to head (e322565).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
diskann/src/flat/test/provider.rs 71.06% 46 Missing ⚠️
diskann/src/flat/test/cases/flat_knn_search.rs 93.97% 5 Missing ⚠️
diskann/src/flat/iterator.rs 98.07% 4 Missing ⚠️
diskann/src/flat/index.rs 98.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##             main     #983    +/-   ##
========================================
  Coverage   89.51%   89.52%            
========================================
  Files         461      467     +6     
  Lines       85920    86547   +627     
========================================
+ Hits        76914    77479   +565     
- Misses       9006     9068    +62     
Flag Coverage Δ
miri 89.52% <90.90%> (+<0.01%) ⬆️
unittests 89.15% <90.90%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
diskann-disk/src/search/provider/disk_provider.rs 90.88% <ø> (ø)
diskann-garnet/src/provider.rs 83.36% <ø> (ø)
...rc/inline_beta_search/encoded_document_accessor.rs 0.00% <ø> (ø)
...ilter/src/inline_beta_search/inline_beta_filter.rs 0.00% <ø> (ø)
...odel/graph/provider/async_/inmem/full_precision.rs 98.39% <ø> (ø)
...s/src/model/graph/provider/async_/inmem/product.rs 90.17% <ø> (ø)
...rs/src/model/graph/provider/async_/inmem/scalar.rs 92.78% <ø> (ø)
...src/model/graph/provider/async_/inmem/spherical.rs 88.13% <ø> (ø)
...ders/src/model/graph/provider/async_/inmem/test.rs 69.34% <ø> (ø)
...ers/src/model/graph/provider/async_/postprocess.rs 100.00% <ø> (ø)
... and 10 more

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@arkrishn94 arkrishn94 marked this pull request as ready for review April 28, 2026 16:02
@arkrishn94 arkrishn94 requested review from a team and Copilot April 28, 2026 16:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an RFC plus an initial “flat” (sequential scan) search surface in diskann, analogous to the existing graph/random-access search pipeline built around DataProvider/Accessor.

Changes:

  • Added an RFC describing the flat iterator/strategy/index abstraction and trade-offs.
  • Added a new diskann::flat module with FlatIterator, FlatSearchStrategy, FlatIndex::knn_search, and FlatPostProcess (+ CopyFlatIds).
  • Exported the new flat module from the crate root.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
rfcs/00983-flat-search.md RFC describing the design for sequential (“flat”) index search APIs.
diskann/src/lib.rs Exposes the new flat module publicly.
diskann/src/flat/mod.rs New module root + re-exports for the flat search surface.
diskann/src/flat/iterator.rs Defines the async lending iterator primitive FlatIterator.
diskann/src/flat/strategy.rs Defines FlatSearchStrategy to create per-query iterators and query computers.
diskann/src/flat/index.rs Implements FlatIndex and the brute-force knn_search scan algorithm.
diskann/src/flat/post_process.rs Defines FlatPostProcess and a basic CopyFlatIds post-processor.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread diskann/src/flat/index.rs Outdated
Comment thread diskann/src/flat/post_process.rs Outdated
Comment thread rfcs/00983-flat-search.md
Comment thread diskann/src/flat/strategy.rs Outdated
Comment thread diskann/src/flat/index.rs Outdated
Comment thread diskann/src/flat/index.rs
Comment thread rfcs/00983-flat-search.md
Comment thread rfcs/00983-flat-search.md Outdated
Comment thread rfcs/00983-flat-search.md
@arkrishn94 arkrishn94 changed the title [RFC] Flat Index Search [flat index] Flat Search Interface Apr 29, 2026
Comment thread diskann/src/flat/index.rs
Comment thread diskann/src/flat/iterator.rs Outdated
Comment thread diskann/src/lib.rs
Copy link
Copy Markdown
Contributor

@hildebrandmw hildebrandmw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Aditya. Left a few general comments with some ideas on how we might improve our code sharing. In general, I'm not a fan of prefixing everything with Flat. We already have the flat module so flat::SearchStrategy reads fine to me as opposed to flat::FlatSearchStrategy, which is a little redundant.

Comment thread diskann/src/flat/iterator.rs Outdated
Comment thread diskann/src/flat/post_process.rs Outdated
Comment thread diskann/src/flat/strategy.rs Outdated
Comment thread diskann/src/flat/iterator.rs Outdated
Comment thread diskann/src/flat/index.rs Outdated
Comment thread diskann/src/flat/index.rs
/// - `context`: per-request context threaded through to the provider.
/// - `query`: the query.
/// - `output`: caller-owned output buffer.
pub fn knn_search<S, T, O, OB, PP>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We recently went through a whole thing of adding the Search trait to the graph index to avoid the proliferation of search methods on the index. We should probably do the same here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, makes sense.

Comment thread diskann/src/flat/index.rs Outdated
Comment thread diskann/src/flat/iterator.rs Outdated
Comment thread diskann/src/flat/mod.rs
//! family. It is designed for backends whose natural access pattern is a one-pass scan over
//! their data — for example append-only buffered stores, on-disk shards streamed via I/O,
//! or any provider where random access is significantly more expensive than sequential.
//!
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice description comparing traits that enable algorithms based on random access vs sequential scans. Could this be in a higher level directory, either in providers.rs file or in diskann/src/agents.md

Comment thread rfcs/00983-flat-search.md Outdated


### The glue: `FlatSearchStrategy`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are introducing substantial new machinery, can we think about whether we can reuse some of this for IVF/SPANN type of indices that rely on clustering and then scanning entire data in specific clusters to support queries.

I can imagine that the OnElementsUnordered and DistancesUnordered could be adapted to the scope of a cluster.

And SearchStrategies for IVF would need to be added.

Even if we dont have a fully fleshed our proposal for clustering based indices, it would be ideal to document how the abstractions can be reused or adapted in the near future and avoid another set of abstractions for clustering based indices

Comment thread rfcs/00983-flat-search.md
5. Return search stats.

Other algorithms (filtered, range, diverse) can be added later as additional methods on
`FlatIndex`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we support predicates on flat index?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a good point - type-1 bitmap filters can be supported by extending the scan trait (DistancesUnordered) with a typed predicate applied before computing distances. This can then be wired through at the top-level search API depending on whether we're doing brute-force or filtered search.

Comment thread diskann/src/flat/mod.rs Outdated
//! | [`crate::provider::Accessor`] | [`FlatIterator`] |
//! | [`crate::graph::glue::SearchStrategy`] | [`FlatSearchStrategy`] |
//! | [`crate::graph::glue::SearchPostProcess`] | [`FlatPostProcess`] |
//! | [`crate::graph::Search`] | [`FlatIndex::knn_search`] |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This table is very useful.
Could this description be upleveled to diskann/src/agents.md or readme.

Comment thread diskann/src/flat/mod.rs Outdated
//! # Hot loop
//!
//! Algorithms drive the scan via [`FlatIterator::next`] (lending iterator) or override
//! [`FlatIterator::on_elements_unordered`] when batching/prefetching wins. The default
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this paragraph supposed to be here. Seems a bit out of place.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I need to clean up.

Comment thread diskann/src/flat/post_process.rs Outdated
provider::HasId,
};

/// Post-process the survivor candidates produced by a flat search and
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the intention to support filters with post_process? If so where is the clause?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post-processing for flat is now common with diskann::graph::glue::SearchPostProcess. So filter will have to live in the object implementing this trait like in the graph case - e.g. inline_beta_filter. Or it can live in the Visitor<'_> itself.

@arrayka arrayka linked an issue May 5, 2026 that may be closed by this pull request
Comment thread diskann/src/flat/mod.rs Outdated
//!
//! The module mirrors the layering used by graph search:
//!
//! | Graph (random access) | Flat (sequential) | Shared? |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding Responsibility column and provide one-sentence description for each layer.

pub trait DistancesUnordered<T>: OnElementsUnordered + BuildQueryComputer<T> {
/// Drive the entire scan, scoring each element with `computer` and invoking `f` with
/// the resulting `(id, distance)` pair.
fn distances_unordered<F>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The algorithm only needs DistancesUnordered. Making it a supertrait of OnElementsUnordered forces all distance-capable providers to also expose raw element streaming, which leaks a lower-level capability and may block implementations that can compute distances without exposing element refs.

Could we decouple the traits and provide a blanket impl of DistancesUnordered for OnElementsUnordered + BuildQueryComputer instead:

Make DistancesUnordered independent, and provide a separate adapter/blanket impl when OnElementsUnordered is available. Conceptually:

  • trait DistancesUnordered<T> { fn distances_unordered(...) ... } (no supertrait)
  • impl<T, S> DistancesUnordered<T> for S where S: OnElementsUnordered + BuildQueryComputer<T> { ... }

That keeps the algorithm-facing surface minimal, preserves encapsulation, and still keeps the convenience default behavior for types that do implement OnElementsUnordered.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I'm a little confused; how would we define DistancedUnordered without the BuildQueryComputer trait bound?Specifically, the computer signature in the definition of distances_unordered. If possible, can you flesh this out a bit?
  • On your suggestion, if we implement the default implementation on any S : OnElementsUnordered + BuildQueryComputer then we won't allow a consumer to specialize the implementation for their S no?

I'm not sure I'm following why we might want to have implementations of DistancesUnordered that don't implement OnElementsUnordered :)

Comment thread diskann/src/flat/iterator.rs Outdated
Comment thread diskann/src/flat/index.rs Outdated
.into_ann_result()?;

let computer =
BuildQueryComputer::build_query_computer(&visitor, query).into_ann_result()?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please provide an example of why visitor is needed to build a query computer?

Copy link
Copy Markdown
Contributor Author

@arkrishn94 arkrishn94 May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple reasons -

  1. visitor implements DistancesUnordered so it is not unreasonable it should know how to construct the correct type to apply the streaming distance computation.
  2. Making the visitor implement DistancesUnordered has the advantage of being symmetric to the graph case: where the search_accessor implements ExpandBeam which must implement BuildQueryComputer<T>. This symmetry helps a bit with sharing the post-process trait SearchPostProcess for both graph and flat search - since trait bounds are identical for both paths.

type Id = u32;
}

impl provider::HasElementRef for Accessor<'_> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an option, consider extracting this prerequisite change into a separate PR to keep this one smaller and easier to review.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm open to separating this change to a pre-cursor PR.

My only worry is that, since this refactor is specific to enabling the new flat search trait structure I'm proposing here, I don't want to rush the refactor in an earlier PR only to change the flat search trait architecture.

/// brute-force ground-truth oracle.
#[derive(Debug, Clone)]
pub(crate) struct KnnOracleRun {
/// Top-`k` `(id, distance)` pairs in canonical `(distance asc, id asc)` order.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: shouldn't id be in the first position: (distance asc, id asc).

Comment thread diskann/src/flat/index.rs
/// many concurrent searches on a multi-threaded runtime, each producing the
/// correct top-k independently.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn knn_search() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would name the test something like this: flat_index_supports_multi_threading

Comment thread diskann/src/flat/test/provider.rs Outdated
}

/// Snapshot of the per-provider counters.
pub fn metrics(&self) -> Metrics {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Metrics is a little bit confusing with Metric. Consider renaming Metrics to Counters to avoid confusion.

Alex Razumov (from Dev Box) and others added 5 commits May 11, 2026 17:09
Copy link
Copy Markdown
Contributor

@hildebrandmw hildebrandmw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Aditya,

Here's my take: The new HasElementRef and DistancesUnordered (the one in provider, not flat) add a hefty boilerplate burden to users of the graph index. Especially in light of something like #1067 which pushes in the other direction and while that PR is still experimental, I'm getting more convinced is the right API for the graph index. This puts us in kind of an awkward place. If we get this in, users consuming a new version will have churn for these little misc traits that will then need to be undone by #1067 (or some spiritual successor) which is not great.

If you want to start programming against this API, what if you introduce your own temporary TemporaryBuildQueryComputer (I believe this is the biggest reason why this PR needs to add HasElementRef and DistancesUnrodered) and we hold off on post processing for a short period of time to bottom out on #1067. If the latter doesn't pan out, then we can continue with these changes and accept it. Otherwise, the flat index implementations (which presumably will be less numerous than the current graph implementations) can be brought inline with the new trait organization and we'll cause less overall churn for our users.

I also remain unconvinced that the Iterator is any simpler than just implementing the new DistancesUnordered trait directly.

/// Implementations provide element-at-a-time access via [`Self::next`]. Providers that
/// only implement `FlatIterator` can be wrapped in [`Iterated`] to obtain a
/// default [`DistancesUnordered`] implementation.
pub trait FlatIterator: HasId + HasElementRef + Send + Sync {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not convinced that this is justified. If someone can write a coherent next implementation and all of the state-tracking shenanigans that that will require, why can they not write a handful more lines of code and simply implement all of DistancesUnordered?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't expect all (or even most) consumers to implement the flat index using the FlatIterator API. This is to provide an easy entry point for a naive implementation of the flat index for providers. Fwiw it also helps a bit with testing providers the flat index search path for providers that can implement an iteration pattern easily.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced that this is any easier an entry point than DistancesUnordered. You still need all the BuildQueryComputer implementations, there are more associated types, the return type is more complicated, users need to implement state tracking, and users have to know to use the Iterated proxy type. If one can manage to do all of that, then it's trivial to implement DistancesUnordered directly and be done with it.

/// element with the supplied [`BuildQueryComputer::QueryComputer`] and invoking
/// `f` with the resulting `(id, distance)` pair. The super-trait
/// [`BuildQueryComputer<T>`] supplies the computer type.
pub trait DistancesUnordered<T>: HasId + BuildQueryComputer<T> + Send + Sync {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this in iterator and not in strategy? Also, with the idea outlined in #1067, should we align on like SearchExt or something for these core "do the thing" traits? Not super set on names, but DistancesUnordered is a bit long.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, this should probably move to strategy. On the naming, I think DistancesUnordered is pretty good at describing what this trait does. But I'm open to reconsidering the name based on what we end with for graph search.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need to bikeshed for a bit. I'm not happy with SearchExt as a trait name. But if we think about the potential future of DistancesUnordered, I'm thinking there is going to have to be machinery around parallelism, right? For example splitting into chunks that are processed independently. At which point, the DistancesUnordered functionality is one facet of the overall process.

/// [`BuildQueryComputer<T>`] supplies the computer type.
pub trait DistancesUnordered<T>: HasId + BuildQueryComputer<T> + Send + Sync {
/// The error type yielded by [`Self::distances_unordered`].
type Error: ToRanked + Debug + Send + Sync + 'static;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the error bound on this super trait can be Into<ANNError> instead of ToRanked. Since it is coarse grained, implementations can decide internally to accept transient errors or now. I'm not sure I see a compelling reason to return a ToRanked error here.

//! pre-built query computer and a callback, applies the callback to every
//! `(id, distance)` pair in the provider, and is the only trait an in-memory
//! visitor (such as [`crate::flat::test::provider::Visitor`]) needs to implement.
//! The super-traits [`HasId`] and [`BuildQueryComputer`] define the id and
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this extra fluff that is already easily discoverable via the type system?

* Licensed under the MIT license.
*/

#![allow(dead_code)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we allowing dead_code?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good catch

assert!(
!items.is_empty(),
"flat::test::Provider needs at least one item"
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we follow the pattern of the test provider in graph and return Errors instead of panics? I've been treating the test provider in graph as an example of how to at least not do things wrong and to use as a general-purpose provider for downstream crates to rely on. I think it's worth making the API tight.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough

/// Per-call configuration that knows how to construct a per-query
/// [`DistancesUnordered<T>`] visitor for a provider.
///
/// `SearchStrategy` is the flat counterpart to [`crate::graph::glue::SearchStrategy`]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when graph::glue::SearchStrategy describes itself as the graph counterpart to flat::SearchStrategy :laugh:?

/// recipe; the per-query mutable state lives in the visitor it produces, so a single
/// strategy may be reused across many searches.
///
/// The strategy itself is a pure factory; the visitor it produces carries the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: I'm all for docs, but much of this fluffy and redundant.

let v2 = strategy.create_visitor(&provider, &context).unwrap();

// The two visitors must occupy distinct stack slots — i.e. holding `v1`
// does not preclude constructing `v2`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly is this testing? How could this ever not be the case?

Comment thread diskann/src/provider.rs
///
/// The default implementation uses [`Accessor::on_elements_unordered`] to iterate over the
/// elements and computes their distances using the provided `computer`.
pub trait DistancesUnordered<T>: Accessor + BuildQueryComputer<T> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That this somewhat general trait shares the same name as the much more specific trait in the flat index is quite confusing. I support traits having the same name if they serve the same purpose in different modules. For example, graph::SearchExt and flat::SearchExt - these (potentially hypothetical) traits serve the same purpose for different index types.

@arkrishn94
Copy link
Copy Markdown
Contributor Author

arkrishn94 commented May 15, 2026

If you want to start programming against this API, what if you introduce your own temporary TemporaryBuildQueryComputer (I believe this is the biggest reason why this PR needs to add HasElementRef and DistancesUnrodered) and we hold off on post processing for a short period of time to bottom out on #1067. If the latter doesn't pan out, then we can continue with these changes and accept it. Otherwise, the flat index implementations (which presumably will be less numerous than the current graph implementations) can be brought inline with the new trait organization and we'll cause less overall churn for our users.

Thanks for the feedback and thorough review @hildebrandmw. To make sure I understand, are you suggesting we temporarily create a disjoint trait structure for the flat index (avoiding supporting post-processing) and then once #1067 settles down, we can re-evaluate how much shared surface we can have for these two indexes (both on the building/construction side and the post-processing)?

@hildebrandmw
Copy link
Copy Markdown
Contributor

If you want to start programming against this API, what if you introduce your own temporary TemporaryBuildQueryComputer (I believe this is the biggest reason why this PR needs to add HasElementRef and DistancesUnrodered) and we hold off on post processing for a short period of time to bottom out on #1067. If the latter doesn't pan out, then we can continue with these changes and accept it. Otherwise, the flat index implementations (which presumably will be less numerous than the current graph implementations) can be brought inline with the new trait organization and we'll cause less overall churn for our users.

Thanks for the feedback and thorough review @hildebrandmw. To make sure I understand, are you suggesting we temporarily create a disjoint trait structure for the flat index (avoiding supporting post-processing) and then once #1067 settles down, we can re-evaluate how much shared surface we can have for these two indexes (both on the building/construction side and the post-processing)?

Yeah, that's what I'm proposing. Something to minimize code churn, or at least defer it temporarily until we know that we aren't going to introduce these intermediate traits (HasElementRef + provider::DistancesUnordered) just to remove them again immediately.

hildebrandmw added a commit that referenced this pull request May 16, 2026
Remove `flat_search` from `DiskANNIndex` and the `IdIterator` trait from
`diskann`. Since the only caller was from `diskann-disk`, add a new
`flat_search` inherent method to `DiskIndexSearcher`.

The flat search method is not compatible with the experimental direction
in #1067 and with #983 on the horizon, this is safe to move for now.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create RFC and initial implementation for flat-index search support

6 participants