1#![warn(missing_debug_implementations, missing_docs)]
6
7use std::{cmp::Ordering, collections::BinaryHeap};
14
15use diskann::{ANNError, ANNResult};
16use diskann_linalg::{self, Transpose};
17use diskann_providers::{
18 forward_threadpool,
19 utils::{AsThreadPool, ParallelIteratorInPool, RayonThreadPool},
20};
21use rayon::prelude::*;
22
23const POINTS_PER_CHUNK: usize = 1200;
45
46struct PivotContainer {
47 piv_id: usize,
48 piv_dist: f32,
49}
50
51impl PartialOrd for PivotContainer {
54 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
55 Some(self.cmp(other))
56 }
57}
58
59impl Ord for PivotContainer {
61 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
62 other
65 .piv_dist
66 .partial_cmp(&self.piv_dist)
67 .unwrap_or(Ordering::Less)
68 }
69}
70
71impl PartialEq for PivotContainer {
72 fn eq(&self, other: &Self) -> bool {
73 self.piv_dist == other.piv_dist
74 }
75}
76
77impl Eq for PivotContainer {}
78
79fn compute_vec_l2sq(data: &[f32], index: usize, dim: usize) -> f32 {
81 let start = index * dim;
82 let slice = unsafe { std::slice::from_raw_parts(data.as_ptr().add(start), dim) };
83 let mut sum_squared = 0.0;
84 for &value in slice {
85 sum_squared += value * value;
86 }
87
88 sum_squared
89}
90
91pub fn compute_vecs_l2sq<Pool: AsThreadPool>(
94 vecs_l2sq: &mut [f32],
95 data: &[f32],
96 num_points: usize,
97 dim: usize,
98 pool: Pool,
99) -> ANNResult<()> {
100 if data.len() != num_points * dim {
101 return Err(ANNError::log_pq_error(format_args!(
102 "data.len() {} should be num_points {} * dim {}",
103 data.len(),
104 num_points,
105 dim
106 )));
107 }
108
109 if dim < 5 {
110 for (i, vec_l2sq) in vecs_l2sq.iter_mut().enumerate() {
111 *vec_l2sq = compute_vec_l2sq(data, i, dim);
112 }
113 } else {
114 forward_threadpool!(pool = pool);
115 vecs_l2sq
116 .par_iter_mut()
117 .enumerate()
118 .for_each_in_pool(pool, |(i, vec_l2sq)| {
119 *vec_l2sq = compute_vec_l2sq(data, i, dim);
120 });
121 }
122
123 Ok(())
124}
125
126#[allow(clippy::too_many_arguments)]
135pub fn compute_closest_centers_in_block(
136 data: &[f32],
137 num_points: usize,
138 dim: usize,
139 centers: &[f32],
140 num_centers: usize,
141 docs_l2sq: &[f32],
142 centers_l2sq: &[f32],
143 center_index: &mut [u32],
144 dist_matrix: &mut [f32],
145 k: usize,
146 pool: &RayonThreadPool,
147) -> ANNResult<()> {
148 if k > num_centers {
149 return Err(ANNError::log_index_error(format_args!(
150 "ERROR: k ({}) > num_centers({})",
151 k, num_centers
152 )));
153 }
154
155 let ones_a: Vec<f32> = vec![1.0; num_centers];
156 let ones_b: Vec<f32> = vec![1.0; num_points];
157
158 diskann_linalg::sgemm(
159 Transpose::None,
160 Transpose::Ordinary,
161 num_points,
162 num_centers,
163 1,
164 1.0,
165 docs_l2sq,
166 &ones_a,
167 None, dist_matrix,
169 );
170
171 diskann_linalg::sgemm(
172 Transpose::None,
173 Transpose::Ordinary,
174 num_points,
175 num_centers,
176 1,
177 1.0,
178 &ones_b,
179 centers_l2sq,
180 Some(1.0), dist_matrix,
182 );
183
184 diskann_linalg::sgemm(
185 Transpose::None,
186 Transpose::Ordinary,
187 num_points,
188 num_centers,
189 dim,
190 -2.0,
191 data,
192 centers,
193 Some(1.0), dist_matrix,
195 );
196
197 if k == 1 {
198 center_index
199 .par_iter_mut()
200 .enumerate()
201 .for_each_in_pool(pool, |(i, center_idx)| {
202 let mut min = f32::MAX;
203 let current = &dist_matrix[i * num_centers..(i + 1) * num_centers];
204 let mut min_idx = 0;
205 for (j, &distance) in current.iter().enumerate() {
206 if distance < min {
207 min = distance;
208 min_idx = j;
209 }
210 }
211 *center_idx = min_idx as u32;
212 });
213 } else {
214 center_index
215 .par_chunks_mut(k)
216 .enumerate()
217 .for_each_in_pool(pool, |(i, center_chunk)| {
218 let current = &dist_matrix[i * num_centers..(i + 1) * num_centers];
219 let mut top_k_queue = BinaryHeap::new();
220 for (j, &distance) in current.iter().enumerate() {
221 let this_piv = PivotContainer {
222 piv_id: j,
223 piv_dist: distance,
224 };
225 top_k_queue.push(this_piv);
226 }
227 for center_idx in center_chunk.iter_mut() {
228 if let Some(this_piv) = top_k_queue.pop() {
229 *center_idx = this_piv.piv_id as u32;
230 } else {
231 break;
232 }
233 }
234 });
235 }
236
237 Ok(())
238}
239
240#[allow(clippy::too_many_arguments)]
249pub fn compute_closest_centers<Pool: AsThreadPool>(
250 data: &[f32],
251 num_points: usize,
252 dim: usize,
253 pivot_data: &[f32],
254 num_centers: usize,
255 k: usize,
256 closest_centers_ivf: &mut [u32],
257 mut inverted_index: Option<&mut Vec<Vec<usize>>>,
258 pts_norms_squared: Option<&[f32]>,
259 pool: Pool,
260) -> ANNResult<()> {
261 if k > num_centers {
262 return Err(ANNError::log_index_error(format_args!(
263 "ERROR: k ({}) > num_centers({})",
264 k, num_centers
265 )));
266 }
267
268 forward_threadpool!(pool = pool);
269
270 let pts_norms_squared = if let Some(pts_norms) = pts_norms_squared {
271 pts_norms.to_vec()
272 } else {
273 let mut norms_squared = vec![0.0; num_points];
274 compute_vecs_l2sq(&mut norms_squared, data, num_points, dim, pool)?;
275 norms_squared
276 };
277
278 let mut pivs_norms_squared = vec![0.0; num_centers];
279 compute_vecs_l2sq(&mut pivs_norms_squared, pivot_data, num_centers, dim, pool)?;
280
281 let mut distance_matrix = vec![0.0; POINTS_PER_CHUNK * num_centers];
282 let mut closest_center_indices = vec![0; POINTS_PER_CHUNK * k];
283 let pts_norms_squared_chunks = pts_norms_squared.chunks(POINTS_PER_CHUNK);
284
285 for (chunk_index, (data_chunk, pts_norms_squared_chunk)) in data
286 .chunks(dim * POINTS_PER_CHUNK)
287 .zip(pts_norms_squared_chunks)
288 .enumerate()
289 {
290 let chunk_size = data_chunk.len() / dim;
292
293 let this_distance_matrix = &mut distance_matrix[..num_centers * chunk_size];
295 let this_closest_center_indices = &mut closest_center_indices[..k * chunk_size];
296
297 compute_closest_centers_in_block(
298 data_chunk,
299 chunk_size,
300 dim,
301 pivot_data,
302 num_centers,
303 pts_norms_squared_chunk,
304 &pivs_norms_squared,
305 this_closest_center_indices,
306 this_distance_matrix,
307 k,
308 pool,
309 )?;
310
311 let point_start_index = chunk_index * POINTS_PER_CHUNK;
312
313 for point_index in point_start_index..point_start_index + chunk_size {
314 for l in 0..k {
315 let center_chunk_index = (point_index - point_start_index) * k + l;
316 let ivf_index = point_index * k + l;
317
318 let this_center_index = closest_center_indices[center_chunk_index];
319 closest_centers_ivf[ivf_index] = this_center_index;
320
321 if let Some(inverted_index) = &mut inverted_index {
322 inverted_index[this_center_index as usize].push(point_index);
323 }
324 }
325 }
326 }
327 Ok(())
328}
329
330#[cfg(test)]
331mod math_util_test {
332 use approx::assert_abs_diff_eq;
333
334 use super::*;
335 use diskann_providers::utils::create_thread_pool_for_test;
336
337 #[test]
338 fn partial_ord_test() {
339 let pviot1 = PivotContainer {
340 piv_id: 2,
341 piv_dist: f32::NAN,
342 };
343 let pivot2 = PivotContainer {
344 piv_id: 1,
345 piv_dist: 1.0,
346 };
347
348 assert_eq!(pviot1.partial_cmp(&pivot2), Some(Ordering::Less));
349 }
350
351 #[test]
352 fn ord_test() {
353 let pviot1 = PivotContainer {
354 piv_id: 1,
355 piv_dist: f32::NAN,
356 };
357 let pivot2 = PivotContainer {
358 piv_id: 2,
359 piv_dist: 1.0,
360 };
361
362 assert_eq!(pviot1.cmp(&pivot2), Ordering::Less);
363 }
364
365 #[test]
366 fn compute_vecs_l2sq_small_dim_test() {
367 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
368 let num_points = 2;
369 let dim = 3;
370 let mut vecs_l2sq = vec![0.0; num_points];
371 let pool = create_thread_pool_for_test();
372
373 compute_vecs_l2sq(&mut vecs_l2sq, &data, num_points, dim, &pool).unwrap();
374
375 let expected = [14.0, 77.0];
376
377 assert_eq!(vecs_l2sq.len(), num_points);
378 assert_abs_diff_eq!(vecs_l2sq[0], expected[0], epsilon = 1e-6);
379 assert_abs_diff_eq!(vecs_l2sq[1], expected[1], epsilon = 1e-6);
380 }
381
382 #[test]
383 fn compute_vecs_l2sq_large_dim_test() {
384 let data = vec![
385 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
386 ];
387 let num_points = 2;
388 let dim = 8;
389 let mut vecs_l2sq = vec![0.0; num_points];
390 let pool = create_thread_pool_for_test();
391 compute_vecs_l2sq(&mut vecs_l2sq, &data, num_points, dim, &pool).unwrap();
392
393 let expected = [204.0, 1292.0];
394
395 assert_eq!(vecs_l2sq.len(), num_points);
396 assert_abs_diff_eq!(vecs_l2sq[0], expected[0], epsilon = 1e-6);
397 assert_abs_diff_eq!(vecs_l2sq[1], expected[1], epsilon = 1e-6);
398 }
399
400 #[test]
401 fn compute_closest_centers_in_block_test() {
402 let num_points = 10;
403 let dim = 5;
404 let num_centers = 3;
405 let data = vec![
406 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
407 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0,
408 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0,
409 45.0, 46.0, 47.0, 48.0, 49.0, 50.0,
410 ];
411 let centers = vec![
412 1.0, 2.0, 3.0, 4.0, 5.0, 21.0, 22.0, 23.0, 24.0, 25.0, 31.0, 32.0, 33.0, 34.0, 35.0,
413 ];
414 let mut docs_l2sq = vec![0.0; num_points];
415 let pool = create_thread_pool_for_test();
416 compute_vecs_l2sq(&mut docs_l2sq, &data, num_points, dim, &pool).unwrap();
417 let mut centers_l2sq = vec![0.0; num_centers];
418 compute_vecs_l2sq(&mut centers_l2sq, ¢ers, num_centers, dim, &pool).unwrap();
419 let mut center_index = vec![0; num_points];
420 let mut dist_matrix = vec![0.0; num_points * num_centers];
421 let k = 1;
422
423 compute_closest_centers_in_block(
424 &data,
425 num_points,
426 dim,
427 ¢ers,
428 num_centers,
429 &docs_l2sq,
430 ¢ers_l2sq,
431 &mut center_index,
432 &mut dist_matrix,
433 k,
434 &pool,
435 )
436 .unwrap();
437
438 assert_eq!(center_index.len(), num_points);
439 let expected_center_index = vec![0, 0, 0, 1, 1, 1, 2, 2, 2, 2];
440 assert_abs_diff_eq!(*center_index, expected_center_index);
441
442 assert_eq!(dist_matrix.len(), num_points * num_centers);
443 let expected_dist_matrix = vec![
444 0.0, 2000.0, 4500.0, 125.0, 1125.0, 3125.0, 500.0, 500.0, 2000.0, 1125.0, 125.0,
445 1125.0, 2000.0, 0.0, 500.0, 3125.0, 125.0, 125.0, 4500.0, 500.0, 0.0, 6125.0, 1125.0,
446 125.0, 8000.0, 2000.0, 500.0, 10125.0, 3125.0, 1125.0,
447 ];
448 assert_abs_diff_eq!(*dist_matrix, expected_dist_matrix, epsilon = 1e-2);
449 }
450
451 #[test]
452 fn compute_closest_centers_in_block_test_k_equals_two() {
453 let num_points = 2;
454 let dim = 5;
455 let num_centers = 4;
456 let data = vec![41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0];
457 let centers = vec![
458 1.0, 2.0, 3.0, 4.0, 5.0, 21.0, 22.0, 23.0, 24.0, 25.0, 31.0, 32.0, 33.0, 34.0, 35.0,
459 46.0, 47.0, 48.0, 49.0, 50.0,
460 ];
461 let mut docs_l2sq = vec![0.0; num_points];
462 let pool = create_thread_pool_for_test();
463 compute_vecs_l2sq(&mut docs_l2sq, &data, num_points, dim, &pool).unwrap();
464 let mut centers_l2sq = vec![0.0; num_centers];
465 compute_vecs_l2sq(&mut centers_l2sq, ¢ers, num_centers, dim, &pool).unwrap();
466 let k = 2;
467 let mut center_index = vec![0; num_points * k];
468 let mut dist_matrix = vec![0.0; num_points * num_centers];
469
470 compute_closest_centers_in_block(
471 &data,
472 num_points,
473 dim,
474 ¢ers,
475 num_centers,
476 &docs_l2sq,
477 ¢ers_l2sq,
478 &mut center_index,
479 &mut dist_matrix,
480 k,
481 &pool,
482 )
483 .unwrap();
484
485 assert_eq!(center_index.len(), num_points * k);
486 let expected_center_index = vec![3, 2, 3, 2];
487 assert_abs_diff_eq!(*center_index, expected_center_index);
488
489 assert_eq!(dist_matrix.len(), num_points * num_centers);
490 let expected_dist_matrix = vec![8000.0, 2000.0, 500.0, 125.0, 10125.0, 3125.0, 1125.0, 0.0];
495 assert_abs_diff_eq!(*dist_matrix, expected_dist_matrix, epsilon = 1e-2);
496 }
497
498 #[test]
499 fn test_compute_closest_centers() {
500 let num_points = 4;
501 let dim = 3;
502 let num_centers = 2;
503 let data = vec![
504 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0,
505 ];
506 let pivot_data = vec![1.0, 2.0, 3.0, 10.0, 11.0, 12.0];
507 let k = 1;
508
509 let mut closest_centers_ivf = vec![0u32; num_points * k];
510 let mut inverted_index: Vec<Vec<usize>> = vec![vec![], vec![]];
511 let pool = create_thread_pool_for_test();
512 compute_closest_centers(
513 &data,
514 num_points,
515 dim,
516 &pivot_data,
517 num_centers,
518 k,
519 &mut closest_centers_ivf,
520 Some(&mut inverted_index),
521 None,
522 &pool,
523 )
524 .unwrap();
525
526 assert_eq!(closest_centers_ivf, vec![0, 0, 1, 1]);
527
528 for vec in inverted_index.iter_mut() {
529 vec.sort_unstable();
530 }
531 assert_eq!(inverted_index, vec![vec![0, 1], vec![2, 3]]);
532 }
533}