Skip to main content

lance_index/vector/flat/
storage.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4use std::{borrow::Cow, sync::Arc};
5
6use super::index::FlatMetadata;
7use crate::frag_reuse::FragReuseIndex;
8use crate::vector::quantizer::QuantizerStorage;
9use crate::vector::storage::{DistCalculator, VectorStore};
10use crate::vector::utils::do_prefetch;
11use arrow::array::AsArray;
12use arrow::compute::concat_batches;
13use arrow::datatypes::{Float16Type, Float64Type, UInt8Type};
14use arrow_array::ArrowPrimitiveType;
15use arrow_array::{
16    Array, ArrayRef, FixedSizeListArray, RecordBatch, UInt64Array,
17    types::{Float32Type, UInt64Type},
18};
19use arrow_schema::{DataType, SchemaRef};
20use lance_core::deepsize::DeepSizeOf;
21use lance_core::{Error, ROW_ID, Result};
22use lance_file::previous::reader::FileReader as PreviousFileReader;
23use lance_linalg::distance::hamming::hamming;
24use lance_linalg::distance::{Cosine, DistanceType, Dot, L2};
25
26pub const FLAT_COLUMN: &str = "flat";
27
28/// All data are stored in memory
29#[derive(Debug, Clone)]
30pub struct FlatFloatStorage {
31    metadata: FlatMetadata,
32    batch: RecordBatch,
33    distance_type: DistanceType,
34
35    // helper fields
36    pub(super) row_ids: Arc<UInt64Array>,
37    vectors: Arc<FixedSizeListArray>,
38}
39
40impl DeepSizeOf for FlatFloatStorage {
41    fn deep_size_of_children(&self, _: &mut lance_core::deepsize::Context) -> usize {
42        self.batch.get_array_memory_size()
43    }
44}
45
46#[async_trait::async_trait]
47impl QuantizerStorage for FlatFloatStorage {
48    type Metadata = FlatMetadata;
49
50    fn try_from_batch(
51        batch: RecordBatch,
52        metadata: &Self::Metadata,
53        distance_type: DistanceType,
54        frag_reuse_index: Option<Arc<FragReuseIndex>>,
55    ) -> Result<Self> {
56        let batch = if let Some(frag_reuse_index_ref) = frag_reuse_index.as_ref() {
57            frag_reuse_index_ref.remap_row_ids_record_batch(batch, 0)?
58        } else {
59            batch
60        };
61
62        let row_ids = Arc::new(
63            batch
64                .column_by_name(ROW_ID)
65                .ok_or(Error::schema(format!("column {} not found", ROW_ID)))?
66                .as_primitive::<UInt64Type>()
67                .clone(),
68        );
69        let vectors = Arc::new(
70            batch
71                .column_by_name(FLAT_COLUMN)
72                .ok_or(Error::schema("column flat not found".to_string()))?
73                .as_fixed_size_list()
74                .clone(),
75        );
76        Ok(Self {
77            metadata: metadata.clone(),
78            batch,
79            distance_type,
80            row_ids,
81            vectors,
82        })
83    }
84
85    fn metadata(&self) -> &Self::Metadata {
86        &self.metadata
87    }
88
89    async fn load_partition(
90        _: &PreviousFileReader,
91        _: std::ops::Range<usize>,
92        _: DistanceType,
93        _: &Self::Metadata,
94        _: Option<Arc<FragReuseIndex>>,
95    ) -> Result<Self> {
96        unimplemented!("Flat will be used in new index builder which doesn't require this")
97    }
98}
99
100impl FlatFloatStorage {
101    // used for only testing
102    pub fn new(vectors: FixedSizeListArray, distance_type: DistanceType) -> Self {
103        let row_ids = Arc::new(UInt64Array::from_iter_values(0..vectors.len() as u64));
104        let vectors = Arc::new(vectors);
105
106        let batch = RecordBatch::try_from_iter_with_nullable(vec![
107            (ROW_ID, row_ids.clone() as ArrayRef, true),
108            (FLAT_COLUMN, vectors.clone() as ArrayRef, true),
109        ])
110        .unwrap();
111
112        Self {
113            metadata: FlatMetadata {
114                dim: vectors.value_length() as usize,
115            },
116            batch,
117            distance_type,
118            row_ids,
119            vectors,
120        }
121    }
122
123    pub fn vector(&self, id: u32) -> ArrayRef {
124        self.vectors.value(id as usize)
125    }
126}
127
128impl VectorStore for FlatFloatStorage {
129    type DistanceCalculator<'a> = FlatFloatDistanceCalc<'a>;
130
131    fn to_batches(&self) -> Result<impl Iterator<Item = RecordBatch>> {
132        Ok([self.batch.clone()].into_iter())
133    }
134
135    fn append_batch(&self, batch: RecordBatch, _vector_column: &str) -> Result<Self> {
136        // TODO: use chunked storage
137        let new_batch = concat_batches(&batch.schema(), vec![&self.batch, &batch].into_iter())?;
138        let mut storage = self.clone();
139        storage.row_ids = Arc::new(
140            new_batch
141                .column_by_name(ROW_ID)
142                .ok_or(Error::schema(format!("column {} not found", ROW_ID)))?
143                .as_primitive::<UInt64Type>()
144                .clone(),
145        );
146        storage.vectors = Arc::new(
147            new_batch
148                .column_by_name(FLAT_COLUMN)
149                .ok_or(Error::schema("column flat not found".to_string()))?
150                .as_fixed_size_list()
151                .clone(),
152        );
153        storage.batch = new_batch;
154        Ok(storage)
155    }
156
157    fn schema(&self) -> &SchemaRef {
158        self.batch.schema_ref()
159    }
160
161    fn as_any(&self) -> &dyn std::any::Any {
162        self
163    }
164
165    fn len(&self) -> usize {
166        self.vectors.len()
167    }
168
169    fn distance_type(&self) -> DistanceType {
170        self.distance_type
171    }
172
173    fn row_id(&self, id: u32) -> u64 {
174        self.row_ids.values()[id as usize]
175    }
176
177    fn row_ids(&self) -> impl Iterator<Item = &u64> {
178        self.row_ids.values().iter()
179    }
180
181    fn dist_calculator(&self, query: ArrayRef, _dist_q_c: f32) -> Self::DistanceCalculator<'_> {
182        Self::DistanceCalculator::new(self.vectors.as_ref(), query, self.distance_type)
183    }
184
185    fn dist_calculator_from_id(&self, id: u32) -> Self::DistanceCalculator<'_> {
186        Self::DistanceCalculator::new_from_id(self.vectors.as_ref(), id, self.distance_type)
187    }
188}
189
190/// All data are stored in memory
191#[derive(Debug, Clone)]
192pub struct FlatBinStorage {
193    metadata: FlatMetadata,
194    batch: RecordBatch,
195    distance_type: DistanceType,
196
197    // helper fields
198    pub(super) row_ids: Arc<UInt64Array>,
199    vectors: Arc<FixedSizeListArray>,
200}
201
202impl DeepSizeOf for FlatBinStorage {
203    fn deep_size_of_children(&self, _: &mut lance_core::deepsize::Context) -> usize {
204        self.batch.get_array_memory_size()
205    }
206}
207
208#[async_trait::async_trait]
209impl QuantizerStorage for FlatBinStorage {
210    type Metadata = FlatMetadata;
211
212    fn try_from_batch(
213        batch: RecordBatch,
214        metadata: &Self::Metadata,
215        distance_type: DistanceType,
216        frag_reuse_index: Option<Arc<FragReuseIndex>>,
217    ) -> Result<Self> {
218        let batch = if let Some(frag_reuse_index_ref) = frag_reuse_index.as_ref() {
219            frag_reuse_index_ref.remap_row_ids_record_batch(batch, 0)?
220        } else {
221            batch
222        };
223
224        let row_ids = Arc::new(
225            batch
226                .column_by_name(ROW_ID)
227                .ok_or(Error::schema(format!("column {} not found", ROW_ID)))?
228                .as_primitive::<UInt64Type>()
229                .clone(),
230        );
231        let vectors = Arc::new(
232            batch
233                .column_by_name(FLAT_COLUMN)
234                .ok_or(Error::schema("column flat not found".to_string()))?
235                .as_fixed_size_list()
236                .clone(),
237        );
238        Ok(Self {
239            metadata: metadata.clone(),
240            batch,
241            distance_type,
242            row_ids,
243            vectors,
244        })
245    }
246
247    fn metadata(&self) -> &Self::Metadata {
248        &self.metadata
249    }
250
251    async fn load_partition(
252        _: &PreviousFileReader,
253        _: std::ops::Range<usize>,
254        _: DistanceType,
255        _: &Self::Metadata,
256        _: Option<Arc<FragReuseIndex>>,
257    ) -> Result<Self> {
258        unimplemented!("Flat will be used in new index builder which doesn't require this")
259    }
260}
261
262impl FlatBinStorage {
263    // used for only testing
264    pub fn new(vectors: FixedSizeListArray, distance_type: DistanceType) -> Self {
265        let row_ids = Arc::new(UInt64Array::from_iter_values(0..vectors.len() as u64));
266        let vectors = Arc::new(vectors);
267
268        let batch = RecordBatch::try_from_iter_with_nullable(vec![
269            (ROW_ID, row_ids.clone() as ArrayRef, true),
270            (FLAT_COLUMN, vectors.clone() as ArrayRef, true),
271        ])
272        .unwrap();
273
274        Self {
275            metadata: FlatMetadata {
276                dim: vectors.value_length() as usize,
277            },
278            batch,
279            distance_type,
280            row_ids,
281            vectors,
282        }
283    }
284
285    pub fn vector(&self, id: u32) -> ArrayRef {
286        self.vectors.value(id as usize)
287    }
288}
289
290impl VectorStore for FlatBinStorage {
291    type DistanceCalculator<'a> = FlatDistanceCal<'a, UInt8Type>;
292
293    fn to_batches(&self) -> Result<impl Iterator<Item = RecordBatch>> {
294        Ok([self.batch.clone()].into_iter())
295    }
296
297    fn append_batch(&self, batch: RecordBatch, _vector_column: &str) -> Result<Self> {
298        // TODO: use chunked storage
299        let new_batch = concat_batches(&batch.schema(), vec![&self.batch, &batch].into_iter())?;
300        let mut storage = self.clone();
301        storage.row_ids = Arc::new(
302            new_batch
303                .column_by_name(ROW_ID)
304                .ok_or(Error::schema(format!("column {} not found", ROW_ID)))?
305                .as_primitive::<UInt64Type>()
306                .clone(),
307        );
308        storage.vectors = Arc::new(
309            new_batch
310                .column_by_name(FLAT_COLUMN)
311                .ok_or(Error::schema("column flat not found".to_string()))?
312                .as_fixed_size_list()
313                .clone(),
314        );
315        storage.batch = new_batch;
316        Ok(storage)
317    }
318
319    fn schema(&self) -> &SchemaRef {
320        self.batch.schema_ref()
321    }
322
323    fn as_any(&self) -> &dyn std::any::Any {
324        self
325    }
326
327    fn len(&self) -> usize {
328        self.vectors.len()
329    }
330
331    fn distance_type(&self) -> DistanceType {
332        self.distance_type
333    }
334
335    fn row_id(&self, id: u32) -> u64 {
336        self.row_ids.values()[id as usize]
337    }
338
339    fn row_ids(&self) -> impl Iterator<Item = &u64> {
340        self.row_ids.values().iter()
341    }
342
343    fn dist_calculator(&self, query: ArrayRef, _dist_q_c: f32) -> Self::DistanceCalculator<'_> {
344        Self::DistanceCalculator::new_binary(self.vectors.as_ref(), query, self.distance_type)
345    }
346
347    fn dist_calculator_from_id(&self, id: u32) -> Self::DistanceCalculator<'_> {
348        Self::DistanceCalculator::new_binary_from_id(self.vectors.as_ref(), id, self.distance_type)
349    }
350}
351
352pub struct FlatDistanceCal<'a, T: ArrowPrimitiveType> {
353    vectors: &'a [T::Native],
354    query: Cow<'a, [T::Native]>,
355    dimension: usize,
356    #[allow(clippy::type_complexity)]
357    distance_fn: fn(&[T::Native], &[T::Native]) -> f32,
358}
359
360impl<'a, T> FlatDistanceCal<'a, T>
361where
362    T: ArrowPrimitiveType,
363    T::Native: L2 + Cosine + Dot,
364{
365    fn new(vectors: &'a FixedSizeListArray, query: ArrayRef, distance_type: DistanceType) -> Self {
366        // Gained significant performance improvement by using strong typed primitive slice.
367        let flat_array = vectors.values().as_primitive::<T>();
368        let dimension = vectors.value_length() as usize;
369        Self {
370            vectors: flat_array.values(),
371            query: Cow::Owned(query.as_primitive::<T>().values().to_vec()),
372            dimension,
373            distance_fn: distance_type.func(),
374        }
375    }
376
377    fn new_from_id(vectors: &'a FixedSizeListArray, id: u32, distance_type: DistanceType) -> Self {
378        let flat_array = vectors.values().as_primitive::<T>();
379        let dimension = vectors.value_length() as usize;
380        let vectors = flat_array.values();
381        let id = id as usize;
382        Self {
383            vectors,
384            query: Cow::Borrowed(&vectors[dimension * id..dimension * (id + 1)]),
385            dimension,
386            distance_fn: distance_type.func(),
387        }
388    }
389}
390
391impl<'a> FlatDistanceCal<'a, UInt8Type> {
392    fn new_binary(
393        vectors: &'a FixedSizeListArray,
394        query: ArrayRef,
395        _distance_type: DistanceType,
396    ) -> Self {
397        // Gained significant performance improvement by using strong typed primitive slice.
398        // TODO: to support other data types other than `f32`, make FlatDistanceCal a generic struct.
399        let flat_array = vectors.values().as_primitive::<UInt8Type>();
400        let dimension = vectors.value_length() as usize;
401        Self {
402            vectors: flat_array.values(),
403            query: Cow::Owned(query.as_primitive::<UInt8Type>().values().to_vec()),
404            dimension,
405            distance_fn: hamming,
406        }
407    }
408
409    fn new_binary_from_id(
410        vectors: &'a FixedSizeListArray,
411        id: u32,
412        _distance_type: DistanceType,
413    ) -> Self {
414        let flat_array = vectors.values().as_primitive::<UInt8Type>();
415        let dimension = vectors.value_length() as usize;
416        let vectors = flat_array.values();
417        let id = id as usize;
418        Self {
419            vectors,
420            query: Cow::Borrowed(&vectors[dimension * id..dimension * (id + 1)]),
421            dimension,
422            distance_fn: hamming,
423        }
424    }
425}
426
427impl<T: ArrowPrimitiveType> FlatDistanceCal<'_, T> {
428    #[inline]
429    fn get_vector(&self, id: u32) -> &[T::Native] {
430        &self.vectors[self.dimension * id as usize..self.dimension * (id + 1) as usize]
431    }
432}
433
434impl<T: ArrowPrimitiveType> DistCalculator for FlatDistanceCal<'_, T> {
435    #[inline]
436    fn distance(&self, id: u32) -> f32 {
437        let query = self.query.as_ref();
438        let vector = self.get_vector(id);
439        (self.distance_fn)(query, vector)
440    }
441
442    fn distance_all(&self, _k_hint: usize) -> Vec<f32> {
443        let query = self.query.as_ref();
444        self.vectors
445            .chunks_exact(self.dimension)
446            .map(|vector| (self.distance_fn)(query, vector))
447            .collect()
448    }
449
450    #[inline]
451    fn prefetch(&self, id: u32) {
452        let vector = self.get_vector(id);
453        do_prefetch(vector.as_ptr_range())
454    }
455}
456
457pub enum FlatFloatDistanceCalc<'a> {
458    Float16(FlatDistanceCal<'a, Float16Type>),
459    Float32(FlatDistanceCal<'a, Float32Type>),
460    Float64(FlatDistanceCal<'a, Float64Type>),
461}
462
463impl<'a> FlatFloatDistanceCalc<'a> {
464    fn new(vectors: &'a FixedSizeListArray, query: ArrayRef, distance_type: DistanceType) -> Self {
465        match vectors.value_type() {
466            DataType::Float16 => Self::Float16(FlatDistanceCal::<Float16Type>::new(
467                vectors,
468                query,
469                distance_type,
470            )),
471            DataType::Float32 => Self::Float32(FlatDistanceCal::<Float32Type>::new(
472                vectors,
473                query,
474                distance_type,
475            )),
476            DataType::Float64 => Self::Float64(FlatDistanceCal::<Float64Type>::new(
477                vectors,
478                query,
479                distance_type,
480            )),
481            dt => panic!("flat float storage does not support data type {dt}"),
482        }
483    }
484
485    fn new_from_id(vectors: &'a FixedSizeListArray, id: u32, distance_type: DistanceType) -> Self {
486        match vectors.value_type() {
487            DataType::Float16 => Self::Float16(FlatDistanceCal::<Float16Type>::new_from_id(
488                vectors,
489                id,
490                distance_type,
491            )),
492            DataType::Float32 => Self::Float32(FlatDistanceCal::<Float32Type>::new_from_id(
493                vectors,
494                id,
495                distance_type,
496            )),
497            DataType::Float64 => Self::Float64(FlatDistanceCal::<Float64Type>::new_from_id(
498                vectors,
499                id,
500                distance_type,
501            )),
502            dt => panic!("flat float storage does not support data type {dt}"),
503        }
504    }
505}
506
507impl DistCalculator for FlatFloatDistanceCalc<'_> {
508    fn distance(&self, id: u32) -> f32 {
509        match self {
510            Self::Float16(calc) => calc.distance(id),
511            Self::Float32(calc) => calc.distance(id),
512            Self::Float64(calc) => calc.distance(id),
513        }
514    }
515
516    fn distance_all(&self, k_hint: usize) -> Vec<f32> {
517        match self {
518            Self::Float16(calc) => calc.distance_all(k_hint),
519            Self::Float32(calc) => calc.distance_all(k_hint),
520            Self::Float64(calc) => calc.distance_all(k_hint),
521        }
522    }
523
524    fn prefetch(&self, id: u32) {
525        match self {
526            Self::Float16(calc) => calc.prefetch(id),
527            Self::Float32(calc) => calc.prefetch(id),
528            Self::Float64(calc) => calc.prefetch(id),
529        }
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536
537    use arrow_array::{Float16Array, Float64Array};
538    use half::f16;
539    use lance_arrow::FixedSizeListArrayExt;
540
541    fn make_f16_storage() -> FlatFloatStorage {
542        let values = Float16Array::from(vec![
543            f16::from_f32(1.0),
544            f16::from_f32(2.0),
545            f16::from_f32(4.0),
546            f16::from_f32(6.0),
547        ]);
548        let vectors = FixedSizeListArray::try_new_from_values(values, 2).unwrap();
549        FlatFloatStorage::new(vectors, DistanceType::L2)
550    }
551
552    fn make_f64_storage() -> FlatFloatStorage {
553        let values = Float64Array::from(vec![1.0, 2.0, 4.0, 6.0]);
554        let vectors = FixedSizeListArray::try_new_from_values(values, 2).unwrap();
555        FlatFloatStorage::new(vectors, DistanceType::L2)
556    }
557
558    #[test]
559    fn test_flat_float_storage_distance_f16() {
560        let storage = make_f16_storage();
561        let query: ArrayRef = Arc::new(Float16Array::from(vec![
562            f16::from_f32(1.0),
563            f16::from_f32(2.0),
564        ]));
565
566        let calc = storage.dist_calculator(query, 0.0);
567        let distances = calc.distance_all(2);
568
569        assert_eq!(distances.len(), 2);
570        assert_eq!(distances[0], 0.0);
571        assert!((distances[1] - 25.0).abs() < 1e-4);
572    }
573
574    #[test]
575    fn test_flat_float_storage_distance_f64() {
576        let storage = make_f64_storage();
577        let query: ArrayRef = Arc::new(Float64Array::from(vec![1.0, 2.0]));
578
579        let calc = storage.dist_calculator(query, 0.0);
580        let distances = calc.distance_all(2);
581
582        assert_eq!(distances.len(), 2);
583        assert_eq!(distances[0], 0.0);
584        assert!((distances[1] - 25.0).abs() < 1e-6);
585    }
586}