[Rust][C API] cuvsCagraBuild blocks calling thread for full build duration — add stream-based cuvsCagraBuildAsync
Nobody has claimed this yet.
- Dominant language
- Cuda
- Stars
- 854
- Forks
- 236
- Avg merge
- 3d 3h
- Merged PRs (30d)
- 62
Description
Environment
- cuVS: 26.2 (crates.io + C library)
- CUDA: 12.4
- GPU: T4 (sm_75, 16 GB HBM)
- Rust: 1.82 (MSRV)
Problem
cuvsCagraBuild — and its Rust wrapper Index::build() — synchronizes the
CUDA stream before returning. The call blocks the calling thread for the full
duration of graph construction.
use std::time::Instant;
let t = Instant::now();
let _index = Index::build(&res, ¶ms, &dataset)?;
println!("{:?}", t.elapsed());
// Measured on T4, 512K × 1024-D float32:
// 1.823s
//
// The thread is blocked. No other work on this CUDA context can proceed.
In a thread-per-GPU architecture — where one OS thread owns the CUDA context
for the lifetime of the process — this means search requests queue up and
receive no response for the entire build duration.
CAGRA's build kernels (NN-descent graph construction) and search kernels (beam
traversal) operate on entirely different data structures. The graph under
construction and the graph being searched are independent.
Root Cause
cuvsCagraBuild calls resource::sync_stream(res) at the end of
raft::neighbors::cagra::build(). This is correct semantics for the synchronous
case — callers can safely read the index after the call returns — but it removes
any possibility of enqueueing the build without blocking, even when the caller
intends to synchronize manually via a CUDA event or cudaStreamSynchronize.
(If cagra::build() also contains any cudaDeviceSynchronize() calls, the
fix is more involved — please confirm. From inspection of the public RAFT source,
it appears to be stream-synchronous only.)
Proposed Fix
C API (new function, additive)
/**
* @brief Enqueue CAGRA index build on a caller-provided CUDA stream.
*
* Returns immediately after submitting all graph construction kernels.
* The index is not safe to use for search until the caller synchronizes
* build_stream (e.g., cudaStreamSynchronize(build_stream) or a CUDA event).
*
* Concurrent CUDA work on other streams is unaffected.
*
* @param[in] res cuVS resources (memory pools, handles)
* @param[in] params Index build parameters
* @param[in] dataset Input dataset (host or device via DLPack)
* @param[out] index Output index (valid only after stream sync)
* @param[in] build_stream CUDA stream for build kernels
*/
cuvsError_t cuvsCagraBuildAsync(
cuvsResources_t res,
cuvsCagraIndexParams_t params,
DLManagedTensor* dataset,
cuvsCagraIndex_t index,
cudaStream_t build_stream
);
Rust binding (new API, additive — build() is unchanged)
/// Handle to an in-progress CAGRA index build.
pub struct AsyncBuildHandle {
index: Index,
build_stream: ffi::cudaStream_t,
}
impl AsyncBuildHandle {
/// Synchronize the build stream and return the completed index.
pub fn wait(self) -> Result<Index>;
}
impl Index {
/// Enqueue a CAGRA build on `build_stream` and return immediately.
pub fn build_async<T: Into<ManagedTensor>>(
res: &Resources,
params: &IndexParams,
dataset: T,
build_stream: ffi::cudaStream_t,
) -> Result<AsyncBuildHandle>;
}
Usage pattern
let build_stream: ffi::cudaStream_t = unsafe { create_cuda_stream()? };
// Enqueue build — returns in microseconds
let handle = Index::build_async(&res, ¶ms, &dataset, build_stream)?;
// Searches on an existing index continue on the default stream concurrently.
// When the new index is needed:
let new_index = handle.wait()?;
Backward Compatibility
Index::build() is unchanged. cuvsCagraBuildAsync is a new C symbol —
purely additive to the ABI. No existing call sites break.
Impact
Without an async build API, applications that need continuous search
availability must maintain multiple pre-built index copies to mask rebuild
latency, multiplying HBM usage by the number of copies. Stream-level
concurrency eliminates this trade-off entirely.
See also PR #1839, which addressed the related issue of Index::search()
consuming the index and forcing pool-based workarounds.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing cuvsCagraBuild through the CAGRA build path and checking where resource::sync_stream(res) is called. Compare the existing C API and Rust Index::build() entry points with the proposed cuvsCagraBuildAsync and AsyncBuildHandle interfaces. Done means the additive async APIs enqueue work on the caller-provided stream while preserving synchronous build behavior and safe completion through wait().
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, rust
- Domain
- api, backend-api-design, performance
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100