Skip to content

Commit ff5cff6

Browse files
Jordan MaplesCopilot
andcommitted
Make Managed opt-in: bftree streams bypass ID management layer
Introduce StreamingOutput trait and make run_streaming generic over the stream type. This allows: - bftree: implements Stream<DataArgs> directly on StreamRunner, using tags as slot IDs without translation (no Managed wrapper) - inmem: continues using Managed for tag-to-slot ID translation When inmem 2.0 lands with native ID management, Managed can be deleted entirely with no infrastructure changes needed. Also: - Remove SlotReclaim::Immediate and recycle_tags (bftree-only paths) - Simplify PhantomData<fn() -> T> to PhantomData<T> on StreamRunner Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f9a9213 commit ff5cff6

7 files changed

Lines changed: 201 additions & 76 deletions

File tree

diskann-benchmark/src/index/benchmarks.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ where
329329
) -> anyhow::Result<Vec<managed::Stats<StreamStats>>> {
330330
writeln!(output, "{}", input)?;
331331

332-
streaming::run_streaming::<T, _>(
332+
streaming::run_streaming::<T, Managed<T, StreamStats>, _>(
333333
input.runbook_params(),
334334
|max_points| full_precision_streaming::<T>(input, max_points),
335335
output,
@@ -742,7 +742,7 @@ where
742742
.ok_or_else(|| anyhow::anyhow!("consolidate_threshold is required for inmem streaming"))?;
743743
let capacity = ((max_points as f32) * (1.0 + 2.0 * consolidate_threshold)).ceil() as usize;
744744

745-
streaming::build_streamer(
745+
streaming::build_managed_streamer(
746746
input.build().data(),
747747
search,
748748
streaming::managed::SlotReclaim::Deferred(consolidate_threshold),

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,7 @@ use diskann_providers::model::graph::provider::async_::common::FullPrecision;
2020
use diskann_utils::sampling::WithApproximateNorm;
2121

2222
use crate::{
23-
index::streaming::{
24-
managed::{self, Managed},
25-
runner::BfTreeMaintainer,
26-
stats::StreamStats,
27-
StreamRunner,
28-
},
23+
index::streaming::{runner::BfTreeMaintainer, stats::StreamStats, StreamRunner},
2924
inputs::bftree::{BfTreeStreamingRun, QuantConfig},
3025
utils,
3126
};
@@ -52,7 +47,7 @@ where
5247
T: VectorRepr + WithApproximateNorm + SampleableForStart + AsDataType + bytemuck::Pod,
5348
{
5449
type Input = BfTreeStreamingRun;
55-
type Output = Vec<managed::Stats<StreamStats>>;
50+
type Output = Vec<StreamStats>;
5651

5752
fn try_match(&self, input: &Self::Input) -> Result<MatchScore, FailureScore> {
5853
let mut failure_score: Option<u32> = None;
@@ -91,18 +86,21 @@ where
9186
) -> anyhow::Result<Self::Output> {
9287
writeln!(output, "{}", input)?;
9388

94-
crate::index::streaming::run_streaming::<T, _>(
89+
crate::index::streaming::run_streaming::<T, BfTreeFullPrecisionStream<T>, _>(
9590
input.runbook_params(),
9691
|max_points| bftree_streaming::<T>(input, max_points),
9792
output,
9893
)
9994
}
10095
}
10196

97+
type BfTreeFullPrecisionStream<T> =
98+
StreamRunner<BfTreeProvider<T, NoStore>, T, FullPrecision, BfTreeMaintainer>;
99+
102100
fn bftree_streaming<T>(
103101
input: &BfTreeStreamingRun,
104102
max_points: usize,
105-
) -> anyhow::Result<bigann::WithData<T, u32, Managed<T, StreamStats>>>
103+
) -> anyhow::Result<bigann::WithData<T, u32, BfTreeFullPrecisionStream<T>>>
106104
where
107105
T: bytemuck::Pod + VectorRepr + WithApproximateNorm + SampleableForStart,
108106
{
@@ -111,10 +109,9 @@ where
111109
let num_start_points = input.build().start_point_strategy().count();
112110
let capacity = max_points + num_start_points;
113111

114-
crate::index::streaming::build_streamer(
112+
crate::index::streaming::build_direct_streamer(
115113
input.build().data(),
116114
search,
117-
crate::index::streaming::managed::SlotReclaim::Immediate,
118115
capacity,
119116
|data, capacity| {
120117
let config = input.try_as_config()?.build()?;

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ use diskann_bftree::BfTreeProvider;
2121
use diskann_providers::model::graph::provider::async_::common::Quantized;
2222

2323
use crate::{
24-
index::streaming::{
25-
managed::{self, Managed},
26-
stats::StreamStats,
27-
BfTreeMaintainer, StreamRunner,
28-
},
24+
index::streaming::{stats::StreamStats, BfTreeMaintainer, StreamRunner},
2925
inputs::bftree::{BfTreeStreamingRun, QuantConfig},
3026
utils,
3127
};
@@ -43,7 +39,7 @@ impl StreamingSpherical {
4339

4440
impl Benchmark for StreamingSpherical {
4541
type Input = BfTreeStreamingRun;
46-
type Output = Vec<managed::Stats<StreamStats>>;
42+
type Output = Vec<StreamStats>;
4743

4844
fn try_match(&self, input: &Self::Input) -> Result<MatchScore, FailureScore> {
4945
let mut failure_score: Option<u32> = None;
@@ -87,27 +83,33 @@ impl Benchmark for StreamingSpherical {
8783
) -> anyhow::Result<Self::Output> {
8884
writeln!(output, "{}", input)?;
8985

90-
crate::index::streaming::run_streaming::<f32, _>(
86+
crate::index::streaming::run_streaming::<f32, BfTreeSphericalStream, _>(
9187
input.runbook_params(),
9288
|max_points| bftree_sq_streaming_impl(input, max_points),
9389
output,
9490
)
9591
}
9692
}
9793

94+
type BfTreeSphericalStream = StreamRunner<
95+
BfTreeProvider<f32, diskann_bftree::quant::QuantVectorProvider>,
96+
f32,
97+
Quantized,
98+
BfTreeMaintainer,
99+
>;
100+
98101
fn bftree_sq_streaming_impl(
99102
input: &BfTreeStreamingRun,
100103
max_points: usize,
101-
) -> anyhow::Result<bigann::WithData<f32, u32, Managed<f32, StreamStats>>> {
104+
) -> anyhow::Result<bigann::WithData<f32, u32, BfTreeSphericalStream>> {
102105
let search = input.search();
103106

104107
let num_start_points = input.build().start_point_strategy().count();
105108
let capacity = max_points + num_start_points;
106109

107-
crate::index::streaming::build_streamer(
110+
crate::index::streaming::build_direct_streamer(
108111
input.build().data(),
109112
search,
110-
crate::index::streaming::managed::SlotReclaim::Immediate,
111113
capacity,
112114
|data, capacity| {
113115
let quantizer_poly = super::quantizer_util::build_quantizer(

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@ use crate::utils::streaming::TagSlotManager;
2121
/// while soft-delete providers need a deferred consolidation pass.
2222
#[derive(Debug, Clone)]
2323
pub(crate) enum SlotReclaim {
24-
/// Slots are recycled to `empty_slots` immediately during delete.
25-
/// No maintenance pass is needed.
26-
#[cfg(feature = "bftree")]
27-
Immediate,
28-
2924
/// Slots are held in `deleted_slots` until maintenance fires.
3025
/// Maintenance triggers when `num_deleted > num_active * threshold`.
3126
Deferred(f32),
@@ -148,8 +143,6 @@ where
148143
let (overhead_slots, slots) = timed!(self.book_keeping.find_slots_by_tags(tags.clone())?);
149144
let output = self.stream.delete(&slots)?;
150145
let (overhead_reclaim, _) = timed!(match &self.reclaim {
151-
#[cfg(feature = "bftree")]
152-
SlotReclaim::Immediate => self.book_keeping.recycle_tags(tags)?,
153146
SlotReclaim::Deferred(_) => self.book_keeping.mark_tags_deleted(tags)?,
154147
});
155148
Ok(Stats::new(overhead_slots + overhead_reclaim, output))
@@ -163,8 +156,6 @@ where
163156

164157
fn needs_maintenance(&mut self) -> bool {
165158
match &self.reclaim {
166-
#[cfg(feature = "bftree")]
167-
SlotReclaim::Immediate => false,
168159
SlotReclaim::Deferred(threshold) => {
169160
let num_active = self.book_keeping.num_active();
170161
let limit = (num_active as f32 * threshold) as usize;

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

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use std::{io::Write, sync::Arc};
77

88
use diskann::utils::VectorRepr;
9-
use diskann_benchmark_core::streaming::{executors::bigann, Executor};
9+
use diskann_benchmark_core::streaming::{self, executors::bigann, Executor};
1010
use diskann_benchmark_runner::output::Output;
1111
use diskann_utils::views::Matrix;
1212

@@ -24,14 +24,34 @@ use crate::{
2424
utils::datafiles,
2525
};
2626

27+
/// Trait for streaming benchmark outputs that wrap or produce [`stats::StreamStats`].
28+
///
29+
/// This allows [`run_streaming`] to work with both direct streams (which produce
30+
/// `StreamStats` directly) and managed streams (which produce `managed::Stats<StreamStats>`).
31+
pub(crate) trait StreamingOutput: std::fmt::Display + 'static {
32+
fn stream_stats(&self) -> &stats::StreamStats;
33+
}
34+
35+
impl StreamingOutput for stats::StreamStats {
36+
fn stream_stats(&self) -> &stats::StreamStats {
37+
self
38+
}
39+
}
40+
41+
impl StreamingOutput for managed::Stats<stats::StreamStats> {
42+
fn stream_stats(&self) -> &stats::StreamStats {
43+
self.inner()
44+
}
45+
}
46+
2747
/// Construct the streaming stack: load data/queries, create the managed stream via the
2848
/// closure, then wrap in [`Managed`] and [`bigann::WithData`].
2949
///
3050
/// `capacity` is the pre-computed slot count passed to [`Managed::new`]. Each backend
3151
/// computes this differently (inmem applies headroom, bf_tree adds start points).
3252
///
3353
/// The closure receives `(&data, capacity)` so it can use `data.ncols()` for provider params.
34-
pub(crate) fn build_streamer<T, M, F>(
54+
pub(crate) fn build_managed_streamer<T, M, F>(
3555
data_path: &diskann_benchmark_runner::files::InputFile,
3656
search: &StreamingSearchParams,
3757
reclaim: managed::SlotReclaim,
@@ -62,19 +82,57 @@ where
6282
Ok(layered)
6383
}
6484

85+
/// Construct a direct streaming stack (no ID management layer).
86+
///
87+
/// For providers where external IDs match internal slots (e.g., bf-tree), the
88+
/// [`Managed`] layer is unnecessary. This function creates the stack without it.
89+
///
90+
/// The closure receives `(&data, capacity)` so it can use `data.ncols()` for provider params.
91+
#[cfg(feature = "bftree")]
92+
pub(crate) fn build_direct_streamer<T, S, F>(
93+
data_path: &diskann_benchmark_runner::files::InputFile,
94+
search: &StreamingSearchParams,
95+
capacity: usize,
96+
make_stream: F,
97+
) -> anyhow::Result<bigann::WithData<T, u32, S>>
98+
where
99+
T: bytemuck::Pod + VectorRepr + 'static,
100+
S: streaming::Stream<bigann::DataArgs<T, u32>> + 'static,
101+
F: FnOnce(&Matrix<T>, usize) -> anyhow::Result<S>,
102+
{
103+
let data = datafiles::load_dataset::<T>(datafiles::BinFile(data_path))?;
104+
let queries = Arc::new(datafiles::load_dataset::<T>(datafiles::BinFile(
105+
&search.queries,
106+
))?);
107+
108+
let stream = make_stream(&data, capacity)?;
109+
110+
let max_k = search.max_k();
111+
let layered = bigann::WithData::new(stream, data, queries, move |path| {
112+
Ok(Box::new(datafiles::load_groundtruth(
113+
datafiles::BinFile(path),
114+
Some(max_k),
115+
)?))
116+
});
117+
118+
Ok(layered)
119+
}
120+
65121
/// Run a streaming benchmark using the given runbook parameters.
66122
///
67123
/// `make_streamer` receives `max_points` from the loaded runbook and returns the
68124
/// constructed streamer. This is shared across all streaming benchmarks (inmem, bftree)
69125
/// to avoid duplicating the runbook load → run_with → stage banner → summary logic.
70-
pub(crate) fn run_streaming<T, F>(
126+
pub(crate) fn run_streaming<T, S, F>(
71127
runbook_params: &StreamingRunbookParams,
72128
make_streamer: F,
73129
mut output: &mut dyn Output,
74-
) -> anyhow::Result<Vec<managed::Stats<stats::StreamStats>>>
130+
) -> anyhow::Result<Vec<S::Output>>
75131
where
76132
T: 'static,
77-
F: FnOnce(usize) -> anyhow::Result<bigann::WithData<T, u32, Managed<T, stats::StreamStats>>>,
133+
S: streaming::Stream<bigann::DataArgs<T, u32>>,
134+
S::Output: StreamingOutput,
135+
F: FnOnce(usize) -> anyhow::Result<bigann::WithData<T, u32, S>>,
78136
{
79137
let groundtruth_directory = runbook_params
80138
.resolved_gt_directory
@@ -95,22 +153,24 @@ where
95153
let stages = runbook.len();
96154
let mut i = 1;
97155

98-
runbook.run_with(
99-
&mut streamer,
100-
|o: managed::Stats<stats::StreamStats>| -> anyhow::Result<()> {
101-
if o.inner().is_maintain() {
102-
let message = format!("Ran maintenance before stage {}", i);
103-
write!(output, "{}", crate::utils::SmallBanner(&message))?;
104-
} else {
105-
let message = format!("Finished stage {} of {}: {}", i, stages, o.inner().kind());
106-
write!(output, "{}", crate::utils::SmallBanner(&message))?;
107-
i += 1;
108-
}
109-
writeln!(output, "{}", o)?;
110-
results.push(o);
111-
Ok(())
112-
},
113-
)?;
156+
runbook.run_with(&mut streamer, |o: S::Output| -> anyhow::Result<()> {
157+
if o.stream_stats().is_maintain() {
158+
let message = format!("Ran maintenance before stage {}", i);
159+
write!(output, "{}", crate::utils::SmallBanner(&message))?;
160+
} else {
161+
let message = format!(
162+
"Finished stage {} of {}: {}",
163+
i,
164+
stages,
165+
o.stream_stats().kind()
166+
);
167+
write!(output, "{}", crate::utils::SmallBanner(&message))?;
168+
i += 1;
169+
}
170+
writeln!(output, "{}", o)?;
171+
results.push(o);
172+
Ok(())
173+
})?;
114174

115175
write!(
116176
output,
@@ -121,7 +181,7 @@ where
121181
writeln!(
122182
output,
123183
"{}",
124-
stats::Summary::new(results.iter().map(|r| r.inner()))
184+
stats::Summary::new(results.iter().map(|r| r.stream_stats()))
125185
)?;
126186

127187
Ok(results)

0 commit comments

Comments
 (0)