Skip to content

Commit d8fe176

Browse files
authored
fix: cosine kmeans (#1723)
Use L2 over normalized vector to run cosine for kmeans.
1 parent eb45167 commit d8fe176

4 files changed

Lines changed: 211 additions & 209 deletions

File tree

python/src/utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ impl KMeans {
101101
return Err(PyValueError::new_err("Must be a FixedSizeList of Float32"));
102102
};
103103
let values: Arc<Float32Array> = fixed_size_arr.values().as_primitive().clone().into();
104-
let membership = RT.block_on(Some(py), kmeans.compute_membership(values, None))?;
104+
let membership = RT.block_on(Some(py), kmeans.compute_membership(values))?;
105105
let cluster_ids: UInt32Array = membership
106106
.cluster_id_and_distances
107107
.iter()

rust/lance-index/src/vector/ivf.rs

Lines changed: 22 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,16 @@ use arrow_array::builder::UInt32Builder;
2222
use arrow_array::types::{Float16Type, Float32Type, Float64Type};
2323
use arrow_array::UInt64Array;
2424
use arrow_array::{
25-
cast::AsArray, types::UInt32Type, Array, FixedSizeListArray, Float32Array, RecordBatch,
26-
UInt32Array,
25+
cast::AsArray, types::UInt32Type, Array, FixedSizeListArray, RecordBatch, UInt32Array,
2726
};
28-
use arrow_ord::sort::sort_to_indices;
2927
use arrow_schema::{DataType, Field};
3028
use arrow_select::take::take;
3129
use async_trait::async_trait;
32-
use futures::{stream, StreamExt, TryStreamExt};
30+
use futures::{stream, StreamExt};
3331
use lance_arrow::*;
3432
use lance_core::{Error, Result, ROW_ID};
3533
use lance_linalg::{
36-
distance::{
37-
cosine_distance_batch, dot_distance_batch, l2_distance_batch, Cosine, Dot, MetricType, L2,
38-
},
34+
distance::{Cosine, Dot, MetricType, L2},
3935
MatrixView,
4036
};
4137
use log::{debug, info};
@@ -51,6 +47,7 @@ use crate::vector::{
5147
transform::Transformer,
5248
};
5349
pub use builder::IvfBuildParams;
50+
use lance_linalg::kmeans::KMeans;
5451

5552
fn new_ivf_impl<T: ArrowFloatType + Dot + Cosine + L2 + 'static>(
5653
centroids: &T::ArrayType,
@@ -349,23 +346,22 @@ impl<T: ArrowFloatType + Dot + L2 + Cosine + 'static> IvfImpl<T> {
349346
// wouldn't cover all rows but 13-element chunks (13 * 32 = 416) would
350347
// have one empty chunk at the end. This filter removes those empty chunks.
351348
.filter(|range| futures::future::ready(range.start < range.end))
352-
.map(|range| {
349+
.map(|range| async {
353350
let centroids = centroids.clone();
354351
let data = data.clone();
355-
tokio::task::spawn_blocking(move || {
356-
compute_partitions::<T>(
357-
centroids.as_slice(),
358-
&data.as_slice()[range],
359-
dimension,
360-
metric_type,
361-
)
362-
})
352+
353+
compute_partitions::<T>(
354+
centroids.as_slice(),
355+
&data.as_slice()[range],
356+
dimension,
357+
metric_type,
358+
)
363359
.in_current_span()
360+
.await
364361
})
365362
.buffered(chunks)
366-
.try_collect()
367-
.await
368-
.expect("compute_partitions: schedule CPU task");
363+
.collect::<Vec<_>>()
364+
.await;
369365

370366
UInt32Array::from_iter(result.iter().flatten().copied())
371367
}
@@ -428,16 +424,6 @@ impl<T: ArrowFloatType + Dot + L2 + Cosine + 'static> Ivf for IvfImpl<T> {
428424
}
429425

430426
fn find_partitions(&self, query: &dyn Array, nprobes: usize) -> Result<UInt32Array> {
431-
if query.len() != self.dimension() {
432-
return Err(Error::IO {
433-
message: format!(
434-
"Ivf::find_partition: dimension mismatch: {} != {}",
435-
query.len(),
436-
self.dimension()
437-
),
438-
location: location!(),
439-
});
440-
}
441427
let query = query
442428
.as_any()
443429
.downcast_ref::<T::ArrayType>()
@@ -449,23 +435,13 @@ impl<T: ArrowFloatType + Dot + L2 + Cosine + 'static> Ivf for IvfImpl<T> {
449435
),
450436
location: Default::default(),
451437
})?;
452-
let centroid_values = self.centroids.data();
453-
let centroids = centroid_values.as_slice();
454-
let dim = query.len();
455-
let distances = Float32Array::from_iter_values(match self.metric_type {
456-
lance_linalg::distance::DistanceType::L2 => {
457-
l2_distance_batch(query.as_slice(), centroids, dim)
458-
}
459-
lance_linalg::distance::DistanceType::Cosine => {
460-
cosine_distance_batch(query.as_slice(), centroids, dim)
461-
}
462-
lance_linalg::distance::DistanceType::Dot => {
463-
dot_distance_batch(query.as_slice(), centroids, dim)
464-
}
465-
});
466-
467-
let top_k_partitions = sort_to_indices(&distances, None, Some(nprobes))?;
468-
Ok(top_k_partitions)
438+
// TODO: hold kmeans in this struct.
439+
let kmeans = KMeans::<T>::with_centroids(
440+
self.centroids.data().clone(),
441+
self.dimension(),
442+
self.metric_type,
443+
);
444+
Ok(kmeans.find_partitions(query.as_slice(), nprobes)?)
469445
}
470446

471447
async fn partition_transform(&self, batch: &RecordBatch, column: &str) -> Result<RecordBatch> {

rust/lance-linalg/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ categories = { workspace = true }
1111

1212
[dependencies]
1313
arrow-array = { workspace = true }
14+
arrow-ord = { workspace = true }
1415
arrow-schema = { workspace = true }
1516
arrow-select = { workspace = true }
1617
futures = { workspace = true }

0 commit comments

Comments
 (0)