Skip to content

Make diskann-disk runtime-agnostic with async APIs#1184

Draft
doliawu wants to merge 1 commit into
microsoft:mainfrom
doliawu:refactor-async
Draft

Make diskann-disk runtime-agnostic with async APIs#1184
doliawu wants to merge 1 commit into
microsoft:mainfrom
doliawu:refactor-async

Conversation

@doliawu

@doliawu doliawu commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes diskann-disk runtime-agnostic. Previously the crate created and owned its own tokio runtime (a create_runtime helper, a Runtime field on DiskIndexSearcher, and block_on calls inside build()/search()). This forced a runtime on callers, risked nested-runtime panics, and pulled in tokio's full feature set. The crate now exposes async APIs and lets callers drive them on their own executor.

Library changes (diskann-disk)

  • DiskIndexBuilder::build() is now async; the redundant build_internal is inlined (BuildInterrupted handling preserved via an inner async block). Deleted build/builder/tokio.rs (create_runtime).
  • DiskIndexSearcher no longer holds a Runtime; new() drops the Option<Runtime> parameter; search()/search_internal() are async and await the inner futures directly.
  • Trimmed tokio features from ["full"] to ["rt", "rt-multi-thread", "macros", "sync"].
  • Tests: single-call search tests use #[tokio::test]; the two concurrent rayon tests share one runtime Handle; build helpers drive the async build via a local runtime.

Consumer changes (diskann-tools, diskann-benchmark)

  • Search loops replace rayon for_each_in_pool with futures_util::buffer_unordered(num_threads) on a caller-owned runtime.
  • Build paths wrap build() in block_on on a multi-thread runtime.

Validation

  • cargo check --workspace clean.
  • diskann-disk: 271 lib tests + 2 doc tests pass.
  • cargo clippy -D warnings clean on diskann-disk, diskann-tools, and diskann-benchmark (disk-index feature).
  • cargo fmt --all clean.

Note on performance

The consumer search loops now use single-task buffer_unordered concurrency rather than the previous num_threads-wide rayon pool. This yields concurrency (interleaving at await points) but not CPU parallelism; for the CPU-bound search path this may reduce multi-core QPS in the benchmark. Restoring true parallelism would require Arc<searcher> + JoinSet/tokio::spawn with Send + Sync + 'static bounds on the generic search functions.

🤖 Generated with GitHub Copilot CLI

Previously diskann-disk created and owned its own tokio runtime: a
create_runtime helper, a Runtime field on DiskIndexSearcher, and
block_on calls inside build()/search(). This forced a runtime on
callers, risked nested-runtime panics, and pulled in tokio's full
feature set.

Library:
- DiskIndexBuilder::build() is now async; the redundant build_internal
  is inlined (BuildInterrupted handling preserved via an inner async
  block). Deleted build/builder/tokio.rs (create_runtime).
- DiskIndexSearcher no longer holds a Runtime; new() drops the
  Option<Runtime> param; search()/search_internal() are async and await
  the inner futures directly.
- Trimmed tokio features from ["full"] to
  ["rt","rt-multi-thread","macros","sync"].
- Tests: single-call search tests use #[tokio::test]; the two concurrent
  rayon tests share one runtime Handle; build helpers drive the async
  build via a local runtime.

Consumers (diskann-tools, diskann-benchmark):
- Search loops replace rayon for_each_in_pool with
  futures_util::buffer_unordered(num_threads) on a caller-owned runtime.
- Build paths wrap build() in block_on on a multi-thread runtime.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@arkrishn94 arkrishn94 assigned wuw92 and unassigned wuw92 Jun 17, 2026
@arkrishn94 arkrishn94 requested a review from wuw92 June 17, 2026 21:29
@codecov-commenter

codecov-commenter commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.74510% with 57 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.45%. Comparing base (d9ce362) to head (ed6a871).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
diskann-tools/src/utils/search_disk_index.rs 0.00% 49 Missing ⚠️
diskann-tools/src/utils/build_disk_index.rs 0.00% 7 Missing ⚠️
diskann-disk/src/build/builder/build.rs 94.11% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1184      +/-   ##
==========================================
- Coverage   89.47%   89.45%   -0.02%     
==========================================
  Files         486      486              
  Lines       92161    92141      -20     
==========================================
- Hits        82458    82429      -29     
- Misses       9703     9712       +9     
Flag Coverage Δ
miri 89.45% <62.74%> (-0.02%) ⬇️
unittests 89.11% <62.74%> (-0.02%) ⬇️

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

Files with missing lines Coverage Δ
diskann-disk/src/build/builder/core.rs 95.37% <100.00%> (+0.01%) ⬆️
diskann-disk/src/search/provider/disk_provider.rs 94.19% <100.00%> (+0.51%) ⬆️
...n-disk/src/search/provider/disk_vertex_provider.rs 85.32% <100.00%> (+0.05%) ⬆️
diskann-disk/src/build/builder/build.rs 94.15% <94.11%> (-0.02%) ⬇️
diskann-tools/src/utils/build_disk_index.rs 60.16% <0.00%> (-3.09%) ⬇️
diskann-tools/src/utils/search_disk_index.rs 0.00% <0.00%> (ø)

... and 18 files with indirect coverage changes

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


let timer = Timer::new();
disk_index.build()?;
let runtime = tokio::runtime::Builder::new_multi_thread()

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.

For awareness — build_disk_index.rs and search_disk_index.rs are both dead code.
I've opened #1185 to remove them.

load_query_result(params.storage_provider, params.truth_result_file_path);

let pool = create_thread_pool(params.thread_num.into_usize()).unwrap();
let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap();

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 parallelism here comes from the rayon for_each_in_pool, and the search path never spawn or suspend, so the tokio workers stay idle.
new_current_thread() would be enough for search, since the runtime is only here to block_on.

}

pub fn build(&mut self) -> ANNResult<()> {
let runtime = create_runtime(self.index_configuration.num_threads)?;

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.

Worth noting: build parallelism is governed by two numbers — num_tasks (the tokio tasks spawned via JoinSet) and the runtime's worker-thread count. The old create_runtime(num_threads) derived both from num_threads, so they stayed in lockstep.
Now the runtime is caller-owned, so the two are independent: if the caller sizes worker threads below num_tasks, the build still runs but with reduced parallelism. Might be worth a note on build() about the expected worker-thread count.

logger.log_checkpoint("index_loaded");

let pool = create_thread_pool(search_params.num_threads)?;
let runtime = tokio::runtime::Builder::new_current_thread().build()?;

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 CI benchmark shows QPS dropping ~70% on this path (run).
The cause looks like the current_thread runtime + buffer_unordered: the search futures never suspend (get_element is future::ready), so on a single worker thread they run one after another — buffer_unordered(num_threads) gives no real parallelism here.

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 may want to consider going benchmark core. It will manage the tokio parallelism.

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.

4 participants