Skip to content

Commit 646f48f

Browse files
Jordan MaplesCopilot
andcommitted
Fix bftree direct streaming: size provider to dataset ID space
The direct (non-Managed) streaming path uses absolute runbook tag IDs directly as provider slot IDs. The provider was sized to the runbook's max concurrent point count (max_pts), but absolute tag IDs span the full dataset, so an insert with id >= capacity failed with "Vector id is out of boundary in the dataset". Size the provider to data.nrows() + num_start_points instead. Drop the redundant capacity parameter from build_direct_streamer and derive it from the loaded dataset in each bftree streaming closure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent daca8e7 commit 646f48f

3 files changed

Lines changed: 69 additions & 73 deletions

File tree

diskann-benchmark/src/index/bftree/full_precision_streaming.rs

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ where
7474

7575
crate::index::streaming::run_streaming::<T, BfTreeFullPrecisionStream<T>, _>(
7676
input.runbook_params(),
77-
|max_points| bftree_streaming::<T>(input, max_points),
77+
|_max_points| bftree_streaming::<T>(input),
7878
output,
7979
)
8080
}
@@ -85,41 +85,38 @@ type BfTreeFullPrecisionStream<T> =
8585

8686
fn bftree_streaming<T>(
8787
input: &BfTreeStreamingRun,
88-
max_points: usize,
8988
) -> anyhow::Result<bigann::WithData<T, u32, BfTreeFullPrecisionStream<T>>>
9089
where
9190
T: bytemuck::Pod + VectorRepr + WithApproximateNorm + SampleableForStart,
9291
{
9392
let search = input.search();
9493

9594
let num_start_points = input.build().start_point_strategy().count();
96-
let capacity = max_points + num_start_points;
97-
98-
crate::index::streaming::build_direct_streamer(
99-
input.build().data(),
100-
search,
101-
capacity,
102-
|data, capacity| {
103-
let config = input.try_as_config()?.build()?;
104-
let params = input.bftree_parameters(capacity, data.ncols())?;
105-
let start_points = input
106-
.build()
107-
.start_point_strategy()
108-
.compute(data.as_view())?;
109-
let provider = BfTreeProvider::new(params, start_points.as_view(), NoStore)?;
110-
let index = Arc::new(DiskANNIndex::new(config, provider, None));
111-
112-
let num_threads_and_tasks = NonZeroUsize::new(input.build().num_threads()).unwrap();
113-
Ok(StreamRunner::new(
114-
index,
115-
FullPrecision,
116-
search.clone(),
117-
benchmark_core::tokio::runtime(num_threads_and_tasks.get())?,
118-
num_threads_and_tasks,
119-
input.runbook_params().ip_delete_num_to_replace,
120-
input.runbook_params().ip_delete_method.into(),
121-
BfTreeMaintainer,
122-
))
123-
},
124-
)
95+
96+
crate::index::streaming::build_direct_streamer(input.build().data(), search, |data| {
97+
// The direct (non-Managed) path uses absolute runbook tag IDs as slot IDs,
98+
// so the provider must span the full dataset ID space rather than the
99+
// runbook's max concurrent point count.
100+
let capacity = data.nrows() + num_start_points;
101+
let config = input.try_as_config()?.build()?;
102+
let params = input.bftree_parameters(capacity, data.ncols())?;
103+
let start_points = input
104+
.build()
105+
.start_point_strategy()
106+
.compute(data.as_view())?;
107+
let provider = BfTreeProvider::new(params, start_points.as_view(), NoStore)?;
108+
let index = Arc::new(DiskANNIndex::new(config, provider, None));
109+
110+
let num_threads_and_tasks = NonZeroUsize::new(input.build().num_threads()).unwrap();
111+
Ok(StreamRunner::new(
112+
index,
113+
FullPrecision,
114+
search.clone(),
115+
benchmark_core::tokio::runtime(num_threads_and_tasks.get())?,
116+
num_threads_and_tasks,
117+
input.runbook_params().ip_delete_num_to_replace,
118+
input.runbook_params().ip_delete_method.into(),
119+
BfTreeMaintainer,
120+
))
121+
})
125122
}

diskann-benchmark/src/index/bftree/spherical_streaming.rs

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl Benchmark for StreamingSpherical {
6868

6969
crate::index::streaming::run_streaming::<f32, BfTreeSphericalStream, _>(
7070
input.runbook_params(),
71-
|max_points| bftree_sq_streaming_impl(input, max_points),
71+
|_max_points| bftree_sq_streaming_impl(input),
7272
output,
7373
)
7474
}
@@ -83,45 +83,42 @@ type BfTreeSphericalStream = StreamRunner<
8383

8484
fn bftree_sq_streaming_impl(
8585
input: &BfTreeStreamingRun,
86-
max_points: usize,
8786
) -> anyhow::Result<bigann::WithData<f32, u32, BfTreeSphericalStream>> {
8887
let search = input.search();
8988

9089
let num_start_points = input.build().start_point_strategy().count();
91-
let capacity = max_points + num_start_points;
92-
93-
crate::index::streaming::build_direct_streamer(
94-
input.build().data(),
95-
search,
96-
capacity,
97-
|data, capacity| {
98-
let quantizer_poly = super::quantizer_util::build_quantizer(
99-
input.quantization(),
100-
data.as_view(),
101-
input.build().distance(),
102-
)?
103-
.expect("spherical quantization config guaranteed by try_match");
104-
105-
let config = input.try_as_config()?.build()?;
106-
let params = input.bftree_parameters(capacity, data.ncols())?;
107-
let start_points = input
108-
.build()
109-
.start_point_strategy()
110-
.compute(data.as_view())?;
111-
let provider = BfTreeProvider::new(params, start_points.as_view(), quantizer_poly)?;
112-
let index = Arc::new(DiskANNIndex::new(config, provider, None));
113-
114-
let num_threads_and_tasks = NonZeroUsize::new(input.build().num_threads()).unwrap();
115-
Ok(StreamRunner::new(
116-
index,
117-
Quantized,
118-
search.clone(),
119-
benchmark_core::tokio::runtime(num_threads_and_tasks.get())?,
120-
num_threads_and_tasks,
121-
input.runbook_params().ip_delete_num_to_replace,
122-
input.runbook_params().ip_delete_method.into(),
123-
BfTreeMaintainer,
124-
))
125-
},
126-
)
90+
91+
crate::index::streaming::build_direct_streamer(input.build().data(), search, |data| {
92+
// The direct (non-Managed) path uses absolute runbook tag IDs as slot IDs,
93+
// so the provider must span the full dataset ID space rather than the
94+
// runbook's max concurrent point count.
95+
let capacity = data.nrows() + num_start_points;
96+
let quantizer_poly = super::quantizer_util::build_quantizer(
97+
input.quantization(),
98+
data.as_view(),
99+
input.build().distance(),
100+
)?
101+
.expect("spherical quantization config guaranteed by try_match");
102+
103+
let config = input.try_as_config()?.build()?;
104+
let params = input.bftree_parameters(capacity, data.ncols())?;
105+
let start_points = input
106+
.build()
107+
.start_point_strategy()
108+
.compute(data.as_view())?;
109+
let provider = BfTreeProvider::new(params, start_points.as_view(), quantizer_poly)?;
110+
let index = Arc::new(DiskANNIndex::new(config, provider, None));
111+
112+
let num_threads_and_tasks = NonZeroUsize::new(input.build().num_threads()).unwrap();
113+
Ok(StreamRunner::new(
114+
index,
115+
Quantized,
116+
search.clone(),
117+
benchmark_core::tokio::runtime(num_threads_and_tasks.get())?,
118+
num_threads_and_tasks,
119+
input.runbook_params().ip_delete_num_to_replace,
120+
input.runbook_params().ip_delete_method.into(),
121+
BfTreeMaintainer,
122+
))
123+
})
127124
}

diskann-benchmark/src/index/streaming/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,25 +87,27 @@ where
8787
/// For providers where external IDs match internal slots (e.g., bf-tree), the
8888
/// [`Managed`] layer is unnecessary. This function creates the stack without it.
8989
///
90-
/// The closure receives `(&data, capacity)` so it can use `data.ncols()` for provider params.
90+
/// Because external runbook tag IDs are used directly as provider slot IDs (no
91+
/// remapping into a compact slot space), the provider must be sized to span the
92+
/// full dataset ID range rather than the runbook's max concurrent point count.
93+
/// The closure receives `&data` so it can size the provider from `data.nrows()`.
9194
#[cfg(feature = "bftree")]
9295
pub(crate) fn build_direct_streamer<T, S, F>(
9396
data_path: &diskann_benchmark_runner::files::InputFile,
9497
search: &StreamingSearchParams,
95-
capacity: usize,
9698
make_stream: F,
9799
) -> anyhow::Result<bigann::WithData<T, u32, S>>
98100
where
99101
T: bytemuck::Pod + VectorRepr + 'static,
100102
S: streaming::Stream<bigann::DataArgs<T, u32>> + 'static,
101-
F: FnOnce(&Matrix<T>, usize) -> anyhow::Result<S>,
103+
F: FnOnce(&Matrix<T>) -> anyhow::Result<S>,
102104
{
103105
let data = datafiles::load_dataset::<T>(datafiles::BinFile(data_path))?;
104106
let queries = Arc::new(datafiles::load_dataset::<T>(datafiles::BinFile(
105107
&search.queries,
106108
))?);
107109

108-
let stream = make_stream(&data, capacity)?;
110+
let stream = make_stream(&data)?;
109111

110112
let max_k = search.max_k();
111113
let layered = bigann::WithData::new(stream, data, queries, move |path| {

0 commit comments

Comments
 (0)