1use std::collections::HashMap;
5use std::sync::Arc;
6
7use arrow::array::AsArray;
8use arrow::datatypes::{Float16Type, Float32Type, Float64Type, UInt8Type, UInt64Type};
9use arrow_array::{
10 Array, FixedSizeListArray, Float32Array, RecordBatch, UInt8Array, UInt32Array, UInt64Array,
11};
12use arrow_schema::{DataType, SchemaRef};
13use async_trait::async_trait;
14use bytes::{Bytes, BytesMut};
15use deepsize::DeepSizeOf;
16use itertools::Itertools;
17use lance_arrow::{ArrowFloatType, FixedSizeListArrayExt, FloatArray, RecordBatchExt};
18use lance_core::{Error, ROW_ID, Result};
19use lance_file::previous::reader::FileReader as PreviousFileReader;
20use lance_linalg::distance::{DistanceType, Dot};
21use lance_linalg::simd::dist_table::{BATCH_SIZE, PERM0, PERM0_INVERSE};
22use lance_linalg::simd::{self};
23use lance_table::utils::LanceIteratorExtension;
24use num_traits::AsPrimitive;
25use prost::Message;
26use serde::{Deserialize, Serialize};
27
28use crate::frag_reuse::FragReuseIndex;
29use crate::pb;
30use crate::vector::bq::RQRotationType;
31use crate::vector::bq::rotation::apply_fast_rotation;
32use crate::vector::bq::transform::{ADD_FACTORS_COLUMN, SCALE_FACTORS_COLUMN};
33use crate::vector::pq::storage::transpose;
34use crate::vector::quantizer::{QuantizerMetadata, QuantizerStorage};
35use crate::vector::storage::{DistCalculator, VectorStore};
36
37pub const RABIT_METADATA_KEY: &str = "lance:rabit";
38pub const RABIT_CODE_COLUMN: &str = "_rabit_codes";
39pub const SEGMENT_LENGTH: usize = 4;
40pub const SEGMENT_NUM_CODES: usize = 1 << SEGMENT_LENGTH;
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct RabitQuantizationMetadata {
44 #[serde(skip)]
48 pub rotate_mat: Option<FixedSizeListArray>,
49 #[serde(default)]
50 pub rotate_mat_position: Option<u32>,
51 #[serde(default)]
52 pub fast_rotation_signs: Option<Vec<u8>>,
53 #[serde(default = "default_rotation_type_compat")]
54 pub rotation_type: RQRotationType,
55 #[serde(default)]
56 pub code_dim: u32,
57 pub num_bits: u8,
58 pub packed: bool,
59}
60
61fn default_rotation_type_compat() -> RQRotationType {
62 RQRotationType::Matrix
64}
65
66impl DeepSizeOf for RabitQuantizationMetadata {
67 fn deep_size_of_children(&self, _context: &mut deepsize::Context) -> usize {
68 self.rotate_mat
69 .as_ref()
70 .map(|inv_p| inv_p.get_array_memory_size())
71 .unwrap_or(0)
72 + self
73 .fast_rotation_signs
74 .as_ref()
75 .map(|signs| signs.len())
76 .unwrap_or(0)
77 }
78}
79
80#[async_trait]
81impl QuantizerMetadata for RabitQuantizationMetadata {
82 fn buffer_index(&self) -> Option<u32> {
83 match self.rotation_type {
84 RQRotationType::Matrix => self.rotate_mat_position,
85 RQRotationType::Fast => None,
86 }
87 }
88
89 fn set_buffer_index(&mut self, index: u32) {
90 self.rotate_mat_position = Some(index);
91 }
92
93 fn parse_buffer(&mut self, bytes: Bytes) -> Result<()> {
94 if self.rotation_type != RQRotationType::Matrix {
95 return Ok(());
96 }
97 debug_assert!(!bytes.is_empty());
98 let codebook_tensor: pb::Tensor = pb::Tensor::decode(bytes)?;
99 self.rotate_mat = Some(FixedSizeListArray::try_from(&codebook_tensor)?);
100 if self.code_dim == 0 {
101 self.code_dim = self
102 .rotate_mat
103 .as_ref()
104 .map(|rotate_mat| rotate_mat.len() as u32)
105 .unwrap_or(0);
106 }
107 Ok(())
108 }
109
110 fn extra_metadata(&self) -> Result<Option<Bytes>> {
111 match self.rotation_type {
112 RQRotationType::Matrix => {
113 if let Some(inv_p) = &self.rotate_mat {
114 let inv_p_tensor = pb::Tensor::try_from(inv_p)?;
115 let mut bytes = BytesMut::new();
116 inv_p_tensor.encode(&mut bytes)?;
117 Ok(Some(bytes.freeze()))
118 } else {
119 Ok(None)
120 }
121 }
122 RQRotationType::Fast => Ok(None),
123 }
124 }
125
126 async fn load(reader: &PreviousFileReader) -> Result<Self> {
127 let metadata_str = reader
128 .schema()
129 .metadata
130 .get(RABIT_METADATA_KEY)
131 .ok_or(Error::index(format!(
132 "Reading Rabit metadata: metadata key {} not found",
133 RABIT_METADATA_KEY
134 )))?;
135 serde_json::from_str(metadata_str)
136 .map_err(|_| Error::index(format!("Failed to parse index metadata: {}", metadata_str)))
137 }
138}
139
140#[derive(Debug, Clone)]
141pub struct RabitQuantizationStorage {
142 metadata: RabitQuantizationMetadata,
143 batch: RecordBatch,
144 distance_type: DistanceType,
145
146 row_ids: UInt64Array,
148 codes: FixedSizeListArray,
149 add_factors: Float32Array,
150 scale_factors: Float32Array,
151}
152
153impl DeepSizeOf for RabitQuantizationStorage {
154 fn deep_size_of_children(&self, context: &mut deepsize::Context) -> usize {
155 self.metadata.deep_size_of_children(context) + self.batch.get_array_memory_size()
156 }
157}
158
159impl RabitQuantizationStorage {
160 fn rotate_query_vector_dense<T: ArrowFloatType>(
161 rotate_mat: &FixedSizeListArray,
162 qr: &dyn Array,
163 ) -> Vec<f32>
164 where
165 T::Native: Dot,
166 {
167 let d = qr.len();
168 let code_dim = rotate_mat.len();
169 let rotate_mat = rotate_mat
170 .values()
171 .as_any()
172 .downcast_ref::<T::ArrayType>()
173 .unwrap()
174 .as_slice();
175
176 let qr = qr
177 .as_any()
178 .downcast_ref::<T::ArrayType>()
179 .unwrap()
180 .as_slice();
181
182 rotate_mat
183 .chunks_exact(code_dim)
184 .map(|chunk| lance_linalg::distance::dot(&chunk[..d], qr))
185 .collect()
186 }
187
188 fn rotate_query_vector_fast<T: ArrowFloatType>(
189 code_dim: usize,
190 signs: &[u8],
191 qr: &dyn Array,
192 ) -> Vec<f32>
193 where
194 T::Native: AsPrimitive<f32>,
195 {
196 let qr = qr
197 .as_any()
198 .downcast_ref::<T::ArrayType>()
199 .unwrap()
200 .as_slice();
201
202 let mut output = vec![0.0f32; code_dim];
203 apply_fast_rotation(qr, &mut output, signs);
204 output
205 }
206}
207
208pub struct RabitDistCalculator<'a> {
209 dim: usize,
210 num_bits: u8,
213 codes: &'a [u8],
215 dist_table: Vec<f32>,
219 add_factors: &'a [f32],
220 scale_factors: &'a [f32],
221 query_factor: f32,
222
223 sum_q: f32,
224 sqrt_d: f32,
225}
226
227impl<'a> RabitDistCalculator<'a> {
228 #[allow(clippy::too_many_arguments)]
229 pub fn new(
230 dim: usize,
231 num_bits: u8,
232 dist_table: Vec<f32>,
233 sum_q: f32,
234 codes: &'a [u8],
235 add_factors: &'a [f32],
236 scale_factors: &'a [f32],
237 query_factor: f32,
238 ) -> Self {
239 Self {
240 dim,
241 num_bits,
242 codes,
243 dist_table,
244 add_factors,
245 scale_factors,
246 query_factor,
247 sqrt_d: (dim as f32 * num_bits as f32).sqrt(),
248 sum_q,
249 }
250 }
251}
252
253#[inline]
254fn lowbit(x: usize) -> usize {
255 1 << x.trailing_zeros()
256}
257
258#[inline]
259pub fn build_dist_table_direct<T: ArrowFloatType>(qc: &[T::Native]) -> Vec<f32>
260where
261 T::Native: AsPrimitive<f32>,
262{
263 let mut dist_table = vec![0.0; qc.len() * 4];
267 qc.chunks_exact(SEGMENT_LENGTH)
268 .zip(dist_table.chunks_exact_mut(SEGMENT_NUM_CODES))
269 .for_each(|(sub_vec, dist_table)| build_dist_table_for_subvec::<T>(sub_vec, dist_table));
270 dist_table
271}
272
273#[inline(always)]
274fn build_dist_table_for_subvec<T: ArrowFloatType>(sub_vec: &[T::Native], dist_table: &mut [f32])
275where
276 T::Native: AsPrimitive<f32>,
277{
278 (1..SEGMENT_NUM_CODES).for_each(|j| {
280 dist_table[j] = dist_table[j - lowbit(j)] + sub_vec[LOWBIT_IDX[j]].as_();
293 })
294}
295
296#[inline]
298fn quantize_dist_table(dist_table: &[f32]) -> (f32, f32, Vec<u8>) {
299 let (qmin, qmax) = dist_table
300 .iter()
301 .cloned()
302 .minmax_by(|a, b| a.total_cmp(b))
303 .into_option()
304 .unwrap();
305 if qmin == qmax {
307 return (qmin, qmax, vec![0; dist_table.len()]);
308 }
309 let factor = 255.0 / (qmax - qmin);
310 let quantized_dist_table = dist_table
311 .iter()
312 .map(|&d| ((d - qmin) * factor).round() as u8)
313 .collect();
314
315 (qmin, qmax, quantized_dist_table)
316}
317
318#[inline]
319fn compute_rq_distance_flat(
320 dist_table: &[f32],
321 codes: &[u8],
322 offset: usize,
323 length: usize,
324 dists: &mut [f32],
325) {
326 let d = dist_table.len() / 4;
327 let code_len = d / u8::BITS as usize;
328 let codes = &codes[offset * code_len..(offset + length) * code_len];
329 let dists = &mut dists[offset..offset + length];
330
331 for (sub_vec_idx, codes) in codes.chunks_exact(length).enumerate() {
332 let current_dist_table = &dist_table
333 [sub_vec_idx * 2 * SEGMENT_NUM_CODES..(sub_vec_idx * 2 + 1) * SEGMENT_NUM_CODES];
334 let next_dist_table = &dist_table
335 [(sub_vec_idx * 2 + 1) * SEGMENT_NUM_CODES..(sub_vec_idx * 2 + 2) * SEGMENT_NUM_CODES];
336
337 codes.iter().zip(dists.iter_mut()).for_each(|(code, dist)| {
338 let current_code = (code & 0x0F) as usize;
339 let next_code = (code >> 4) as usize;
340 *dist += current_dist_table[current_code] + next_dist_table[next_code];
341 });
342 }
343}
344
345impl DistCalculator for RabitDistCalculator<'_> {
346 #[inline(always)]
347 fn distance(&self, id: u32) -> f32 {
348 let id = id as usize;
349 let code_len = self.dim * (self.num_bits as usize) / u8::BITS as usize;
350 let num_vectors = self.codes.len() / code_len;
351 let code = get_rq_code(self.codes, id, num_vectors, code_len);
352 let dist = code
353 .zip(self.dist_table.chunks_exact(SEGMENT_NUM_CODES).tuples())
354 .map(|(code_byte, (dist_table, next_dist_table))| {
355 let current_code = (code_byte & 0x0F) as usize;
358 let next_code = (code_byte >> 4) as usize;
359 dist_table[current_code] + next_dist_table[next_code]
360 })
361 .sum::<f32>();
362
363 let dist_vq_qr = (2.0 * dist - self.sum_q) / self.sqrt_d;
365 dist_vq_qr * self.scale_factors[id] + self.add_factors[id] + self.query_factor
366 }
367
368 #[inline(always)]
369 fn distance_all(&self, _: usize) -> Vec<f32> {
370 let code_len = self.dim * (self.num_bits as usize) / u8::BITS as usize;
371 let n = self.codes.len() / code_len;
372 if n == 0 {
373 return Vec::new();
374 }
375
376 let mut dists = vec![0.0; n];
377
378 let (qmin, qmax, quantized_dists_table) = quantize_dist_table(&self.dist_table);
379 let mut quantized_dists = vec![0; n];
380
381 let remainder = n % BATCH_SIZE;
382 simd::dist_table::sum_4bit_dist_table(
383 n - remainder,
384 code_len,
385 self.codes,
386 &quantized_dists_table,
387 &mut quantized_dists,
388 );
389 if remainder > 0 {
390 compute_rq_distance_flat(
391 &self.dist_table,
392 self.codes,
393 n - remainder,
394 remainder,
395 &mut dists,
396 );
397 }
398
399 let range = (qmax - qmin) / 255.0;
400 let num_tables = quantized_dists_table.len() / 16;
401 let sum_min = num_tables as f32 * qmin;
402 dists
403 .iter_mut()
404 .take(n - remainder)
405 .zip(quantized_dists.into_iter().take(n - remainder))
406 .for_each(|(dist, q_dist)| {
407 *dist = (q_dist as f32) * range + sum_min;
408 });
409
410 dists
411 .into_iter()
412 .enumerate()
413 .map(|(id, dist)| {
414 let dist_vq_qr = (2.0 * dist - self.sum_q) / self.sqrt_d;
415 dist_vq_qr * self.scale_factors[id] + self.add_factors[id] + self.query_factor
416 })
417 .collect()
418 }
419}
420
421impl VectorStore for RabitQuantizationStorage {
422 type DistanceCalculator<'a> = RabitDistCalculator<'a>;
423
424 fn as_any(&self) -> &dyn std::any::Any {
425 self
426 }
427
428 fn schema(&self) -> &SchemaRef {
429 self.batch.schema_ref()
430 }
431
432 fn to_batches(&self) -> Result<impl Iterator<Item = RecordBatch> + Send> {
433 Ok(std::iter::once(self.batch.clone()))
434 }
435
436 fn append_batch(&self, _batch: RecordBatch, _vector_column: &str) -> Result<Self> {
437 unimplemented!("RabitQ does not support append_batch")
438 }
439
440 fn len(&self) -> usize {
441 self.batch.num_rows()
442 }
443
444 fn row_id(&self, id: u32) -> u64 {
445 self.row_ids.value(id as usize)
446 }
447
448 fn row_ids(&self) -> impl Iterator<Item = &u64> {
449 self.row_ids.values().iter()
450 }
451
452 fn distance_type(&self) -> DistanceType {
453 self.distance_type
454 }
455
456 #[inline(never)]
458 fn dist_calculator(&self, qr: Arc<dyn Array>, dist_q_c: f32) -> Self::DistanceCalculator<'_> {
459 let codes = self.codes.values().as_primitive::<UInt8Type>().values();
460 let code_dim = if self.metadata.code_dim > 0 {
461 self.metadata.code_dim as usize
462 } else {
463 self.metadata
464 .rotate_mat
465 .as_ref()
466 .map(|rotate_mat| rotate_mat.len())
467 .unwrap_or_default()
468 };
469
470 let rotated_qr = match self.metadata.rotation_type {
471 RQRotationType::Matrix => {
472 let rotate_mat = self
473 .metadata
474 .rotate_mat
475 .as_ref()
476 .expect("RabitQ dense rotation metadata not loaded");
477
478 match rotate_mat.value_type() {
479 DataType::Float16 => {
480 Self::rotate_query_vector_dense::<Float16Type>(rotate_mat, &qr)
481 }
482 DataType::Float32 => {
483 Self::rotate_query_vector_dense::<Float32Type>(rotate_mat, &qr)
484 }
485 DataType::Float64 => {
486 Self::rotate_query_vector_dense::<Float64Type>(rotate_mat, &qr)
487 }
488 dt => unimplemented!("RabitQ does not support data type: {}", dt),
489 }
490 }
491 RQRotationType::Fast => {
492 let signs = self
493 .metadata
494 .fast_rotation_signs
495 .as_ref()
496 .expect("RabitQ fast rotation metadata not loaded");
497 match qr.data_type() {
498 DataType::Float16 => {
499 Self::rotate_query_vector_fast::<Float16Type>(code_dim, signs, &qr)
500 }
501 DataType::Float32 => {
502 Self::rotate_query_vector_fast::<Float32Type>(code_dim, signs, &qr)
503 }
504 DataType::Float64 => {
505 Self::rotate_query_vector_fast::<Float64Type>(code_dim, signs, &qr)
506 }
507 dt => unimplemented!("RabitQ does not support data type: {}", dt),
508 }
509 }
510 };
511
512 let dist_table = build_dist_table_direct::<Float32Type>(&rotated_qr);
513 let sum_q = rotated_qr.into_iter().sum();
514
515 let q_factor = match self.distance_type {
516 DistanceType::L2 => dist_q_c,
517 DistanceType::Cosine | DistanceType::Dot => dist_q_c - 1.0,
518 _ => unimplemented!(
519 "RabitQ does not support distance type: {}",
520 self.distance_type
521 ),
522 };
523 RabitDistCalculator::new(
524 qr.len(),
525 self.metadata.num_bits,
526 dist_table,
527 sum_q,
528 codes,
529 self.add_factors.values(),
530 self.scale_factors.values(),
531 q_factor,
532 )
533 }
534
535 fn dist_calculator_from_id(&self, _: u32) -> Self::DistanceCalculator<'_> {
538 unimplemented!("RabitQ does not support dist_calculator_from_id")
539 }
540}
541
542const LOWBIT_IDX: [usize; 16] = {
543 let mut array = [0; 16];
544 let mut i = 1;
545 while i < 16 {
546 array[i] = i.trailing_zeros() as usize;
547 i += 1;
548 }
549 array
550};
551
552fn get_column(
553 quantization_code: &[u8],
554 code_len: usize,
555 row: usize,
556 col_idx: usize,
557 codes: &mut [u8; 32],
558) {
559 for (i, code) in codes.iter_mut().enumerate() {
560 let vec_idx = row + i;
561 *code = quantization_code[vec_idx * code_len + col_idx];
562 }
563}
564
565pub fn pack_codes(codes: &FixedSizeListArray) -> FixedSizeListArray {
566 let code_len = codes.value_length() as usize;
567
568 let num_blocks = codes.len() / BATCH_SIZE;
570 let num_packed_vectors = num_blocks * BATCH_SIZE;
571
572 let mut blocks = vec![0u8; codes.values().len()];
578
579 let codes_values = codes
580 .slice(0, num_packed_vectors)
581 .values()
582 .as_primitive::<UInt8Type>()
583 .clone();
584 let codes_values = codes_values.values();
585
586 let mut col = [0u8; 32];
589 let mut col_0 = [0u8; 32]; let mut col_1 = [0u8; 32]; for row in (0..num_packed_vectors).step_by(BATCH_SIZE) {
592 for i in 0..code_len {
596 get_column(codes_values, code_len, row, i, &mut col);
597
598 for j in 0..32 {
599 col_0[j] = col[j] & 0xF;
600 col_1[j] = col[j] >> 4;
601 }
602
603 let block_offset = (row / BATCH_SIZE) * code_len * BATCH_SIZE + i * BATCH_SIZE;
604 for j in 0..16 {
605 let val0 = col_0[PERM0[j]] | (col_0[PERM0[j] + 16] << 4);
608 let val1 = col_1[PERM0[j]] | (col_1[PERM0[j] + 16] << 4);
609 blocks[block_offset + j] = val0;
610 blocks[block_offset + j + 16] = val1;
611 }
612 }
613 }
614
615 let transposed_codes = transpose(
617 &codes.values().as_primitive::<UInt8Type>().slice(
618 num_packed_vectors * code_len,
619 (codes.len() - num_packed_vectors) * code_len,
620 ),
621 codes.len() - num_packed_vectors,
622 code_len,
623 );
624
625 let offset = codes.values().len() - transposed_codes.len();
626 for (i, v) in transposed_codes.values().iter().enumerate() {
627 blocks[offset + i] = *v;
628 }
629
630 assert_eq!(blocks.len(), codes.values().len());
631 FixedSizeListArray::try_new_from_values(UInt8Array::from(blocks), code_len as i32).unwrap()
632}
633
634pub fn unpack_codes(codes: &FixedSizeListArray) -> FixedSizeListArray {
636 let code_len = codes.value_length() as usize;
637 let num_vectors = codes.len();
638
639 let num_blocks = num_vectors / BATCH_SIZE;
641 let num_packed_vectors = num_blocks * BATCH_SIZE;
642
643 let mut unpacked = vec![0u8; codes.values().len()];
644
645 let codes_values = codes.values().as_primitive::<UInt8Type>().values();
646
647 for batch_idx in 0..num_blocks {
649 let block_start = batch_idx * code_len * BATCH_SIZE;
650
651 for i in 0..code_len {
652 let block_offset = block_start + i * BATCH_SIZE;
653 let block = &codes_values[block_offset..block_offset + BATCH_SIZE];
654
655 for j in 0..16 {
657 let val0 = block[j];
658 let val1 = block[j + 16];
659
660 let low_0 = val0 & 0xF;
661 let high_0 = val0 >> 4;
662 let low_1 = val1 & 0xF;
663 let high_1 = val1 >> 4;
664
665 let vec_idx_0 = batch_idx * BATCH_SIZE + PERM0[j];
666 let vec_idx_1 = batch_idx * BATCH_SIZE + PERM0[j] + 16;
667
668 unpacked[vec_idx_0 * code_len + i] = low_0 | (low_1 << 4);
669 unpacked[vec_idx_1 * code_len + i] = high_0 | (high_1 << 4);
670 }
671 }
672 }
673
674 if num_packed_vectors < num_vectors {
676 let remainder = num_vectors - num_packed_vectors;
677 let offset = num_packed_vectors * code_len;
678 let transposed_data = &codes_values[offset..];
679
680 for row in 0..remainder {
682 for col in 0..code_len {
683 unpacked[offset + row * code_len + col] = transposed_data[col * remainder + row];
684 }
685 }
686 }
687
688 FixedSizeListArray::try_new_from_values(UInt8Array::from(unpacked), code_len as i32).unwrap()
689}
690
691#[async_trait]
692impl QuantizerStorage for RabitQuantizationStorage {
693 type Metadata = RabitQuantizationMetadata;
694
695 fn try_from_batch(
696 batch: RecordBatch,
697 metadata: &Self::Metadata,
698 distance_type: DistanceType,
699 _fri: Option<Arc<FragReuseIndex>>,
700 ) -> Result<Self> {
701 let row_ids = batch[ROW_ID].as_primitive::<UInt64Type>().clone();
702 let codes = batch[RABIT_CODE_COLUMN].as_fixed_size_list().clone();
703 let add_factors = batch[ADD_FACTORS_COLUMN]
704 .as_primitive::<Float32Type>()
705 .clone();
706 let scale_factors = batch[SCALE_FACTORS_COLUMN]
707 .as_primitive::<Float32Type>()
708 .clone();
709
710 let (batch, codes) = if !metadata.packed {
711 let codes = pack_codes(&codes);
712 let batch = batch.replace_column_by_name(RABIT_CODE_COLUMN, Arc::new(codes))?;
713 let codes = batch[RABIT_CODE_COLUMN].as_fixed_size_list().clone();
714 (batch, codes)
715 } else {
716 (batch, codes)
717 };
718
719 let mut metadata = metadata.clone();
720 metadata.packed = true;
721
722 Ok(Self {
723 metadata,
724 batch,
725 distance_type,
726 row_ids,
727 codes,
728 add_factors,
729 scale_factors,
730 })
731 }
732
733 fn metadata(&self) -> &Self::Metadata {
734 &self.metadata
735 }
736
737 async fn load_partition(
738 reader: &PreviousFileReader,
739 range: std::ops::Range<usize>,
740 distance_type: DistanceType,
741 metadata: &Self::Metadata,
742 frag_reuse_index: Option<Arc<FragReuseIndex>>,
743 ) -> Result<Self> {
744 let schema = reader.schema();
745 let batch = reader.read_range(range, schema).await?;
746 Self::try_from_batch(batch, metadata, distance_type, frag_reuse_index)
747 }
748
749 fn remap(&self, mapping: &HashMap<u64, Option<u64>>) -> Result<Self> {
750 let num_vectors = self.codes.len();
751 let num_code_bytes = self.codes.value_length() as usize;
752 let codes = self.codes.values().as_primitive::<UInt8Type>().values();
753 let mut indices = Vec::with_capacity(num_vectors);
754 let mut new_row_ids = Vec::with_capacity(num_vectors);
755 let mut new_codes = Vec::with_capacity(codes.len());
756
757 let row_ids = self.row_ids.values();
758 for (i, row_id) in row_ids.iter().enumerate() {
759 match mapping.get(row_id) {
760 Some(Some(new_id)) => {
761 indices.push(i as u32);
762 new_row_ids.push(*new_id);
763 new_codes.extend(get_rq_code(codes, i, num_vectors, num_code_bytes));
764 }
765 Some(None) => {}
766 None => {
767 indices.push(i as u32);
768 new_row_ids.push(*row_id);
769 new_codes.extend(get_rq_code(codes, i, num_vectors, num_code_bytes));
770 }
771 }
772 }
773
774 let new_row_ids = UInt64Array::from(new_row_ids);
775 let new_codes = FixedSizeListArray::try_new_from_values(
776 UInt8Array::from(new_codes),
777 num_code_bytes as i32,
778 )?;
779 let batch = if new_row_ids.is_empty() {
780 RecordBatch::new_empty(self.schema().clone())
781 } else {
782 let codes = Arc::new(pack_codes(&new_codes));
783 self.batch
784 .take(&UInt32Array::from(indices))?
785 .replace_column_by_name(ROW_ID, Arc::new(new_row_ids.clone()))?
786 .replace_column_by_name(RABIT_CODE_COLUMN, codes)?
787 };
788 let codes = batch[RABIT_CODE_COLUMN].as_fixed_size_list().clone();
789
790 Ok(Self {
791 metadata: self.metadata.clone(),
792 distance_type: self.distance_type,
793 batch,
794 codes,
795 add_factors: self.add_factors.clone(),
796 scale_factors: self.scale_factors.clone(),
797 row_ids: new_row_ids,
798 })
799 }
800}
801
802#[inline]
803fn get_rq_code(
804 codes: &[u8],
805 id: usize,
806 num_vectors: usize,
807 num_code_bytes: usize,
808) -> impl Iterator<Item = u8> + '_ {
809 let remainder = num_vectors % BATCH_SIZE;
810
811 if id < num_vectors - remainder {
812 let codes = &codes[id / BATCH_SIZE * BATCH_SIZE * num_code_bytes
814 ..(id / BATCH_SIZE + 1) * BATCH_SIZE * num_code_bytes];
815
816 let id_in_batch = id % BATCH_SIZE;
817 if id_in_batch < 16 {
818 let idx = PERM0_INVERSE[id_in_batch];
819 codes
820 .chunks_exact(BATCH_SIZE)
821 .map(|block| (block[idx] & 0xF) | (block[idx + 16] << 4))
822 .exact_size(num_code_bytes)
823 .collect_vec()
824 .into_iter()
825 } else {
826 let idx = PERM0_INVERSE[id_in_batch - 16];
827 codes
828 .chunks_exact(BATCH_SIZE)
829 .map(|block| (block[idx] >> 4) | (block[idx + 16] & 0xF0))
830 .exact_size(num_code_bytes)
831 .collect_vec()
832 .into_iter()
833 }
834 } else {
835 let id = id - (num_vectors - remainder);
836 let codes = &codes[(num_vectors - remainder) * num_code_bytes..];
837 codes
838 .iter()
839 .skip(id)
840 .step_by(remainder)
841 .copied()
842 .exact_size(num_code_bytes)
843 .collect_vec()
844 .into_iter()
845 }
846}
847
848#[cfg(test)]
849mod tests {
850 use super::*;
851
852 fn build_dist_table_not_optimized<T: ArrowFloatType>(
853 sub_vec: &[T::Native],
854 dist_table: &mut [f32],
855 ) where
856 T::Native: AsPrimitive<f32>,
857 {
858 for (j, dist) in dist_table.iter_mut().enumerate().take(SEGMENT_NUM_CODES) {
859 for (k, v) in sub_vec.iter().enumerate().take(SEGMENT_LENGTH) {
860 if j & (1 << k) != 0 {
861 *dist += v.as_();
862 }
863 }
864 }
865 }
866
867 #[test]
868 fn test_build_dist_table_not_optimized() {
869 let sub_vec = vec![1.0, 2.0, 3.0, 4.0];
870 let mut expected = vec![0.0; SEGMENT_NUM_CODES];
871 build_dist_table_not_optimized::<Float32Type>(&sub_vec, &mut expected);
872 let mut dist_table = vec![0.0; SEGMENT_NUM_CODES];
873 build_dist_table_for_subvec::<Float32Type>(&sub_vec, &mut dist_table);
874 assert_eq!(dist_table, expected);
875 }
876
877 #[test]
878 fn test_pack_unpack_codes() {
879 for num_vectors in [10, 32, 50, 64, 100] {
881 let code_len = 8;
882
883 let mut codes_data = Vec::new();
885 for i in 0..num_vectors {
886 for j in 0..code_len {
887 codes_data.push((i * code_len + j) as u8);
888 }
889 }
890
891 let original_codes = FixedSizeListArray::try_new_from_values(
892 UInt8Array::from(codes_data.clone()),
893 code_len,
894 )
895 .unwrap();
896
897 let packed = pack_codes(&original_codes);
899 let unpacked = unpack_codes(&packed);
900
901 assert_eq!(original_codes.len(), unpacked.len());
903 assert_eq!(original_codes.value_length(), unpacked.value_length());
904
905 let original_values = original_codes.values().as_primitive::<UInt8Type>().values();
906 let unpacked_values = unpacked.values().as_primitive::<UInt8Type>().values();
907
908 assert_eq!(
909 original_values, unpacked_values,
910 "Mismatch for num_vectors={}",
911 num_vectors
912 );
913 }
914 }
915}