|
8 | 8 | use std::num::NonZeroUsize; |
9 | 9 |
|
10 | 10 | use diskann::ANNError; |
11 | | -#[cfg(feature = "pipnn")] |
12 | | -use diskann::ANNResult; |
13 | 11 | use thiserror::Error; |
14 | 12 |
|
15 | 13 | #[cfg(feature = "pipnn")] |
@@ -111,8 +109,8 @@ impl NumPQChunks { |
111 | 109 | /// Parameters specific for disk index construction. |
112 | 110 | #[derive(Clone, PartialEq, Debug)] |
113 | 111 | pub struct DiskIndexBuildParameters { |
114 | | - /// Limit on graph-construction memory. PiPNN falls back to Vamana when its |
115 | | - /// estimated one-shot peak exceeds this value. |
| 112 | + /// Memory budget for disk-index pipeline stages that support bounded work. |
| 113 | + /// Explicit one-shot PiPNN selection is never silently replaced. |
116 | 114 | build_memory_limit: MemoryBudget, |
117 | 115 |
|
118 | 116 | /// Number of PQ chunks stored in-memory for search and to be generated during build. |
@@ -146,8 +144,8 @@ impl DiskIndexBuildParameters { |
146 | 144 |
|
147 | 145 | /// Create parameters for one-shot PiPNN graph construction. |
148 | 146 | /// |
149 | | - /// PiPNN uses the common search-PQ and disk-layout pipeline. The memory |
150 | | - /// budget selects Vamana when the estimated PiPNN peak does not fit. |
| 147 | + /// PiPNN uses the common search-PQ and disk-layout pipeline. Its one-shot |
| 148 | + /// graph build is not governed by the pipeline memory budget. |
151 | 149 | #[cfg(feature = "pipnn")] |
152 | 150 | pub fn new_pipnn( |
153 | 151 | build_memory_limit: MemoryBudget, |
@@ -204,116 +202,6 @@ impl DiskIndexBuildParameters { |
204 | 202 | BuildAlgorithm::Vamana => None, |
205 | 203 | } |
206 | 204 | } |
207 | | - |
208 | | - #[cfg(feature = "pipnn")] |
209 | | - pub(crate) fn use_vamana_if_pipnn_exceeds( |
210 | | - &mut self, |
211 | | - npoints: usize, |
212 | | - dimensions: usize, |
213 | | - element_size: usize, |
214 | | - num_threads: usize, |
215 | | - ) -> ANNResult<usize> { |
216 | | - let parameters = match &self.build_algorithm { |
217 | | - BuildAlgorithm::PiPNN(parameters) => parameters, |
218 | | - BuildAlgorithm::Vamana => { |
219 | | - return Err(ANNError::log_index_error( |
220 | | - "memory selection requires PiPNN parameters", |
221 | | - )); |
222 | | - } |
223 | | - }; |
224 | | - let estimate = |
225 | | - estimate_pipnn_peak_memory(parameters, npoints, dimensions, element_size, num_threads); |
226 | | - if estimate.is_none_or(|bytes| bytes > self.build_memory_limit.in_bytes()) { |
227 | | - self.build_algorithm = BuildAlgorithm::Vamana; |
228 | | - } |
229 | | - Ok(estimate.unwrap_or(usize::MAX)) |
230 | | - } |
231 | | -} |
232 | | - |
233 | | -#[cfg(feature = "pipnn")] |
234 | | -fn estimate_pipnn_peak_memory( |
235 | | - config: &PiPNNParameters, |
236 | | - npoints: usize, |
237 | | - dimensions: usize, |
238 | | - element_size: usize, |
239 | | - num_threads: usize, |
240 | | -) -> Option<usize> { |
241 | | - const PROPORTIONAL_HEADROOM_PERCENT: u128 = 108; |
242 | | - const PROCESS_HEADROOM: u128 = 16 * 1024 * 1024; |
243 | | - const PER_WORKER_HEADROOM: u128 = 28 * 1024 * 1024; |
244 | | - |
245 | | - let workers = if num_threads == 0 { |
246 | | - std::thread::available_parallelism().map_or(1, NonZeroUsize::get) |
247 | | - } else { |
248 | | - num_threads |
249 | | - } as u128; |
250 | | - let copies = config |
251 | | - .fanout |
252 | | - .iter() |
253 | | - .try_fold(config.replicas as u128, |copies, &fanout| { |
254 | | - copies.checked_mul(fanout as u128) |
255 | | - })?; |
256 | | - let dataset = (dimensions as u128).checked_mul(element_size as u128)?; |
257 | | - let leaf_ids = copies.checked_mul(size_of::<u32>() as u128)?; |
258 | | - let offered = copies.checked_mul(config.k as u128)?.checked_mul(2)?; |
259 | | - let candidate_capacity = offered.checked_next_power_of_two()?; |
260 | | - let candidate_storage = candidate_capacity.checked_mul(size_of::<u32>() as u128)?; |
261 | | - let candidate_metadata = |
262 | | - size_of::<std::sync::Mutex<diskann::graph::AdjacencyList<u32>>>() as u128; |
263 | | - |
264 | | - let partition_per_point = dataset |
265 | | - .checked_add(16)? |
266 | | - .checked_add(leaf_ids.checked_mul(2)?)?; |
267 | | - let leaf_per_point = dataset |
268 | | - .checked_add(candidate_metadata)? |
269 | | - .checked_add(candidate_storage)? |
270 | | - .checked_add(leaf_ids)?; |
271 | | - let finalization_per_point = dataset |
272 | | - .checked_add(candidate_metadata)? |
273 | | - .checked_add(size_of::<Vec<u32>>() as u128)? |
274 | | - .checked_add(candidate_storage)?; |
275 | | - |
276 | | - let leaf_size = config.c_max.min(npoints).max(1) as u128; |
277 | | - let leaf_scratch = leaf_size |
278 | | - .checked_mul(dimensions as u128)? |
279 | | - .checked_mul(size_of::<f32>() as u128)? |
280 | | - .checked_add(leaf_size.checked_mul(leaf_size)?.checked_mul(5)?)? |
281 | | - .checked_add(leaf_size.checked_mul(config.k as u128)?.checked_mul(24)?)? |
282 | | - .checked_add(leaf_size.checked_mul(20)?)? |
283 | | - .checked_add(68)?; |
284 | | - let partition_rows = (npoints as u128).min(1_024); |
285 | | - let partition_leaders = (npoints as u128).min(1_000); |
286 | | - let partition_scratch = partition_leaders |
287 | | - .checked_mul(dimensions as u128)? |
288 | | - .checked_mul(size_of::<f32>() as u128)? |
289 | | - .checked_add( |
290 | | - partition_rows |
291 | | - .checked_mul(dimensions as u128)? |
292 | | - .checked_mul(size_of::<f32>() as u128)?, |
293 | | - )? |
294 | | - .checked_add( |
295 | | - partition_rows |
296 | | - .checked_mul(partition_leaders)? |
297 | | - .checked_mul(size_of::<f32>() as u128)?, |
298 | | - )?; |
299 | | - |
300 | | - let points = npoints as u128; |
301 | | - let structural = points |
302 | | - .checked_mul(partition_per_point)? |
303 | | - .checked_add(workers.checked_mul(partition_scratch)?)? |
304 | | - .max( |
305 | | - points |
306 | | - .checked_mul(leaf_per_point)? |
307 | | - .checked_add(workers.checked_mul(leaf_scratch)?)?, |
308 | | - ) |
309 | | - .max(points.checked_mul(finalization_per_point)?); |
310 | | - let proportional = structural |
311 | | - .checked_mul(PROPORTIONAL_HEADROOM_PERCENT)? |
312 | | - .div_ceil(100); |
313 | | - let allocator_floor = structural |
314 | | - .checked_add(PROCESS_HEADROOM)? |
315 | | - .checked_add(workers.checked_mul(PER_WORKER_HEADROOM)?)?; |
316 | | - usize::try_from(proportional.max(allocator_floor)).ok() |
317 | 205 | } |
318 | 206 |
|
319 | 207 | #[cfg(test)] |
@@ -407,47 +295,12 @@ mod dataset_test { |
407 | 295 | let params = DiskIndexBuildParameters::new_pipnn(budget, pq, parameters); |
408 | 296 |
|
409 | 297 | assert_eq!(params.pipnn_config(), Some(config)); |
| 298 | + assert_eq!(params.build_memory_limit(), budget); |
410 | 299 | assert_eq!(params.search_pq_chunks(), pq); |
411 | 300 | assert_eq!( |
412 | 301 | params.data_compression_chunk_vector_count(), |
413 | 302 | DEFAULT_DATA_COMPRESSION_CHUNK_VECTOR_COUNT |
414 | 303 | ); |
415 | 304 | assert!(matches!(params.build_algorithm(), BuildAlgorithm::PiPNN(_))); |
416 | 305 | } |
417 | | - |
418 | | - #[cfg(feature = "pipnn")] |
419 | | - #[test] |
420 | | - fn pipnn_memory_estimate_covers_measured_bigann10m_peak() { |
421 | | - const MEASURED_PEAK: usize = 8_656_004 * 1024; |
422 | | - let parameters = PiPNNParameters { |
423 | | - c_max: 512, |
424 | | - c_min: 64, |
425 | | - p_samp: 0.01, |
426 | | - fanout: vec![10, 3], |
427 | | - k: 2, |
428 | | - replicas: 1, |
429 | | - }; |
430 | | - |
431 | | - let estimate = estimate_pipnn_peak_memory(¶meters, 10_000_000, 128, 2, 16).unwrap(); |
432 | | - |
433 | | - assert!(estimate >= MEASURED_PEAK); |
434 | | - assert!(estimate <= MEASURED_PEAK * 120 / 100); |
435 | | - } |
436 | | - |
437 | | - #[cfg(feature = "pipnn")] |
438 | | - #[test] |
439 | | - fn pipnn_falls_back_to_vamana_above_budget() { |
440 | | - let budget = MemoryBudget::try_from_gb(1.0).unwrap(); |
441 | | - let pq = NumPQChunks::new_with(1, 128).unwrap(); |
442 | | - let mut params = |
443 | | - DiskIndexBuildParameters::new_pipnn(budget, pq, PiPNNParameters::default()); |
444 | | - |
445 | | - let estimate = params |
446 | | - .use_vamana_if_pipnn_exceeds(10_000_000, 128, 2, 16) |
447 | | - .unwrap(); |
448 | | - |
449 | | - assert!(estimate > budget.in_bytes()); |
450 | | - assert!(matches!(params.build_algorithm(), BuildAlgorithm::Vamana)); |
451 | | - assert_eq!(params.build_quantization(), &QuantizationType::FP); |
452 | | - } |
453 | 306 | } |
0 commit comments