1use std::sync::Arc;
5
6use arrow::array::AsArray;
7use arrow::datatypes::{Float16Type, Float32Type, Float64Type};
8use arrow_array::{Array, ArrayRef, FixedSizeListArray, UInt8Array};
9use arrow_schema::{DataType, Field};
10use bitvec::prelude::{BitVec, Lsb0};
11use lance_arrow::{ArrowFloatType, FixedSizeListArrayExt, FloatArray, FloatType};
12use lance_core::deepsize::DeepSizeOf;
13use lance_core::{Error, Result};
14use ndarray::{Axis, ShapeBuilder, s};
15use num_traits::{AsPrimitive, FromPrimitive};
16use rand_distr::Distribution;
17use rayon::prelude::*;
18
19use crate::vector::bq::storage::{
20 RABIT_CODE_COLUMN, RABIT_METADATA_KEY, RabitQuantizationMetadata, RabitQuantizationStorage,
21 RabitQueryEstimator, rabit_binary_code_field, rabit_ex_code_field,
22};
23use crate::vector::bq::transform::{
24 ADD_FACTORS_FIELD, ERROR_FACTORS_FIELD, EX_ADD_FACTORS_FIELD, EX_SCALE_FACTORS_FIELD,
25 SCALE_FACTORS_FIELD,
26};
27use crate::vector::bq::{
28 RQBuildParams, RQRotationType, rabit_binary_code_bytes, rabit_ex_bits,
29 rotation::{apply_fast_rotation, fast_rotation_signs_len, random_fast_rotation_signs},
30 validate_rq_num_bits,
31};
32use crate::vector::quantizer::{Quantization, Quantizer, QuantizerBuildParams};
33
34pub struct RabitBuildParams {
38 pub num_bits: u8,
39 pub rotation_type: RQRotationType,
40}
41
42impl Default for RabitBuildParams {
43 fn default() -> Self {
44 Self {
45 num_bits: 1,
46 rotation_type: RQRotationType::default(),
47 }
48 }
49}
50
51impl QuantizerBuildParams for RabitBuildParams {
52 fn sample_size(&self) -> usize {
53 0
55 }
56}
57
58#[derive(Debug, Clone, DeepSizeOf)]
59pub struct RabitQuantizer {
60 metadata: RabitQuantizationMetadata,
61}
62
63pub(crate) struct RabitQuantizedBatch {
64 pub binary_codes: ArrayRef,
65 pub ex_codes: Option<ArrayRef>,
66 pub ex_res_dot_dists: Option<Vec<f32>>,
67 pub rotated_residuals: Option<Vec<f32>>,
68 pub ex_code_values: Option<Vec<u8>>,
69}
70
71#[inline]
72fn pack_sign_bits(codes: &mut [u8], rotated: &[f32]) {
73 codes.fill(0);
74 for (bit_idx, value) in rotated.iter().enumerate() {
75 if value.is_sign_positive() {
76 codes[bit_idx / u8::BITS as usize] |= 1u8 << (bit_idx % u8::BITS as usize);
77 }
78 }
79}
80
81const EX_QUANTIZATION_EPSILON: f32 = 1.0e-5;
82const EX_TIGHT_START: [f32; 9] = [0.0, 0.15, 0.20, 0.52, 0.59, 0.71, 0.75, 0.77, 0.81];
83
84fn best_ex_rescale_factor(abs_normalized: &[f32], ex_bits: u8) -> f32 {
85 let max_value = abs_normalized
86 .iter()
87 .copied()
88 .filter(|value| value.is_finite())
89 .fold(0.0f32, f32::max);
90 if max_value <= 0.0 {
91 return 0.0;
92 }
93
94 let max_code = (1usize << ex_bits) - 1;
95 let t_end = ((max_code + 10) as f32) / max_value;
96 let t_start = t_end * EX_TIGHT_START[ex_bits as usize];
97
98 let mut current_codes = Vec::with_capacity(abs_normalized.len());
99 let mut squared_denominator = abs_normalized.len() as f32 * 0.25;
100 let mut numerator = 0.0f32;
101 let mut thresholds = Vec::with_capacity(abs_normalized.len() * max_code);
102
103 for (idx, &value) in abs_normalized.iter().enumerate() {
104 if value <= 0.0 || !value.is_finite() {
105 current_codes.push(0usize);
106 continue;
107 }
108
109 let current = ((t_start * value) + EX_QUANTIZATION_EPSILON)
110 .floor()
111 .clamp(0.0, max_code as f32) as usize;
112 current_codes.push(current);
113 squared_denominator += (current * current + current) as f32;
114 numerator += (current as f32 + 0.5) * value;
115
116 let mut next = current + 1;
117 while next <= max_code {
118 let threshold = next as f32 / value;
119 if threshold < t_end {
120 thresholds.push((threshold, idx));
121 }
122 next += 1;
123 }
124 }
125
126 thresholds.sort_unstable_by(|(left, _), (right, _)| left.total_cmp(right));
127
128 let mut best_inner_product = numerator / squared_denominator.sqrt();
129 let mut best_t = t_start;
130 for (threshold, idx) in thresholds {
131 current_codes[idx] += 1;
132 let updated = current_codes[idx];
133 squared_denominator += (2 * updated) as f32;
134 numerator += abs_normalized[idx];
135
136 let current_inner_product = numerator / squared_denominator.sqrt();
137 if current_inner_product > best_inner_product {
138 best_inner_product = current_inner_product;
139 best_t = threshold;
140 }
141 }
142
143 best_t
144}
145
146fn quantize_ex_code(
147 rotated: &[f32],
148 ex_bits: u8,
149 ex_code_dst: &mut [u8],
150 ex_code_values_dst: &mut [u8],
151) -> f32 {
152 debug_assert_eq!(rotated.len(), ex_code_values_dst.len());
153 let norm_squared = rotated.iter().map(|value| value * value).sum::<f32>();
154 if norm_squared <= f32::EPSILON || !norm_squared.is_finite() {
155 ex_code_dst.fill(0);
156 ex_code_values_dst.fill(0);
157 return 0.0;
158 }
159
160 let norm = norm_squared.sqrt();
161 let abs_normalized = rotated
162 .iter()
163 .map(|value| value.abs() / norm)
164 .collect::<Vec<_>>();
165 let t = best_ex_rescale_factor(&abs_normalized, ex_bits);
166 let max_code = ((1u16 << ex_bits) - 1) as u8;
167 let mask = max_code;
168 let code_bias = -((1u32 << ex_bits) as f32 - 0.5);
169 let mut residual_dot_code = 0.0f32;
170
171 for ((&value, &abs_value), ex_code_value) in rotated
172 .iter()
173 .zip(abs_normalized.iter())
174 .zip(ex_code_values_dst.iter_mut())
175 {
176 let mut ex_code = ((t * abs_value) + EX_QUANTIZATION_EPSILON)
177 .floor()
178 .clamp(0.0, max_code as f32) as u8;
179 if value.is_sign_negative() {
180 ex_code = (!ex_code) & mask;
181 }
182 let sign_code = u8::from(value.is_sign_positive());
183 let full_code = ((sign_code as u32) << ex_bits) + ex_code as u32;
184 residual_dot_code += value * (full_code as f32 + code_bias);
185 *ex_code_value = ex_code;
186 }
187
188 crate::vector::bq::ex_dot::pack_blocked_row(ex_code_values_dst, ex_bits, ex_code_dst);
189 residual_dot_code
190}
191
192impl RabitQuantizer {
193 pub fn new<T: ArrowFloatType>(num_bits: u8, dim: i32) -> Self {
194 Self::new_with_rotation::<T>(num_bits, dim, RQRotationType::default())
195 }
196
197 pub fn new_with_rotation<T: ArrowFloatType>(
198 num_bits: u8,
199 dim: i32,
200 rotation_type: RQRotationType,
201 ) -> Self {
202 debug_assert!(dim >= 0, "RabitQ dimension should be non-negative");
203 let code_dim = dim as usize;
204 let metadata = match rotation_type {
205 RQRotationType::Matrix => {
206 let rotate_mat = random_orthogonal::<T>(code_dim);
208 let (rotate_mat, _) = rotate_mat.into_raw_vec_and_offset();
209 let rotate_mat = match T::FLOAT_TYPE {
210 FloatType::Float16 | FloatType::Float32 | FloatType::Float64 => {
211 let rotate_mat = <T::ArrayType as FloatArray<T>>::from_values(rotate_mat);
212 FixedSizeListArray::try_new_from_values(rotate_mat, code_dim as i32)
213 .unwrap()
214 }
215 _ => unimplemented!("RabitQ does not support data type: {:?}", T::FLOAT_TYPE),
216 };
217 RabitQuantizationMetadata {
218 rotate_mat: Some(rotate_mat),
219 rotate_mat_position: None,
220 fast_rotation_signs: None,
221 rotation_type,
222 code_dim: code_dim as u32,
223 num_bits,
224 packed: false,
225 query_estimator: RabitQueryEstimator::RawQuery,
226 }
227 }
228 RQRotationType::Fast => RabitQuantizationMetadata {
229 rotate_mat: None,
230 rotate_mat_position: None,
231 fast_rotation_signs: Some(random_fast_rotation_signs(code_dim)),
232 rotation_type,
233 code_dim: code_dim as u32,
234 num_bits,
235 packed: false,
236 query_estimator: RabitQueryEstimator::RawQuery,
237 },
238 };
239 Self { metadata }
240 }
241
242 pub fn num_bits(&self) -> u8 {
243 self.metadata.num_bits
244 }
245
246 pub fn rotation_type(&self) -> RQRotationType {
247 self.metadata.rotation_type
248 }
249
250 pub fn metadata_ref(&self) -> &RabitQuantizationMetadata {
251 &self.metadata
252 }
253
254 fn from_supplied_rotation(params: &RQBuildParams, dim: usize) -> Result<Option<Self>> {
255 let Some(metadata) = params.rotation.as_ref() else {
256 return Ok(None);
257 };
258
259 if metadata.num_bits != params.num_bits {
260 return Err(Error::invalid_input(format!(
261 "rabitq_model num_bits={} does not match requested num_bits={}",
262 metadata.num_bits, params.num_bits
263 )));
264 }
265
266 let rotated_dim = metadata.rotated_dim();
267 if rotated_dim != dim {
268 return Err(Error::invalid_input(format!(
269 "rabitq_model dimension={} does not match vector dimension={}",
270 rotated_dim, dim
271 )));
272 }
273
274 match metadata.rotation_type {
275 RQRotationType::Fast => {
276 let signs = metadata.fast_rotation_signs.as_ref().ok_or_else(|| {
277 Error::invalid_input(
278 "rabitq_model fast rotation is missing fast_rotation_signs".to_string(),
279 )
280 })?;
281 let expected_len = fast_rotation_signs_len(dim);
282 if signs.len() != expected_len {
283 return Err(Error::invalid_input(format!(
284 "rabitq_model fast_rotation_signs length={} does not match expected length={} for dimension={}",
285 signs.len(),
286 expected_len,
287 dim
288 )));
289 }
290 }
291 RQRotationType::Matrix => {
292 let rotate_mat = metadata.rotate_mat.as_ref().ok_or_else(|| {
293 Error::invalid_input(
294 "rabitq_model matrix rotation is missing rotate_mat".to_string(),
295 )
296 })?;
297 if rotate_mat.len() != dim || rotate_mat.value_length() != dim as i32 {
298 return Err(Error::invalid_input(format!(
299 "rabitq_model matrix rotation shape=({}, {}) does not match vector dimension={}",
300 rotate_mat.len(),
301 rotate_mat.value_length(),
302 dim
303 )));
304 }
305 }
306 }
307
308 Ok(Some(Self {
309 metadata: metadata.clone(),
310 }))
311 }
312
313 #[inline]
314 fn fast_rotation_signs(&self) -> &[u8] {
315 self.metadata
316 .fast_rotation_signs
317 .as_ref()
318 .expect("RabitQ fast rotation signs missing")
319 .as_slice()
320 }
321
322 #[inline]
323 fn rotate_mat_flat<T: ArrowFloatType>(&self) -> &[T::Native] {
324 let rotate_mat = self.metadata.rotate_mat.as_ref().unwrap();
325 rotate_mat
326 .values()
327 .as_any()
328 .downcast_ref::<T::ArrayType>()
329 .unwrap()
330 .as_slice()
331 }
332
333 #[inline]
334 fn rotate_mat<T: ArrowFloatType>(&'_ self) -> ndarray::ArrayView2<'_, T::Native> {
335 let code_dim = self.code_dim();
336 ndarray::ArrayView2::from_shape((code_dim, code_dim), self.rotate_mat_flat::<T>()).unwrap()
337 }
338
339 fn rotate_vectors<T: ArrowFloatType>(
340 &self,
341 vectors: ndarray::ArrayView2<'_, T::Native>,
342 ) -> ndarray::Array2<f32>
343 where
344 T::Native: AsPrimitive<f32>,
345 {
346 let dim = vectors.nrows();
347 let code_dim = self.code_dim();
348 match self.rotation_type() {
349 RQRotationType::Matrix => {
350 let rotate_mat = self.rotate_mat::<T>();
351 let rotate_mat = rotate_mat.slice(s![.., 0..dim]);
352 rotate_mat.dot(&vectors).mapv(|v| v.as_())
353 }
354 RQRotationType::Fast => {
355 let signs = self.fast_rotation_signs();
356 let ncols = vectors.ncols();
357 let mut rotated_data = vec![0.0f32; code_dim * ncols];
358 rotated_data
359 .par_chunks_mut(code_dim)
360 .enumerate()
361 .for_each_init(
362 || vec![0.0f32; code_dim],
363 |scratch, (col_idx, dst)| {
364 let column = vectors.column(col_idx);
365 let input = column
366 .as_slice()
367 .expect("RabitQ input vectors should be contiguous");
368 apply_fast_rotation(input, scratch, signs);
369 dst.copy_from_slice(scratch);
370 },
371 );
372
373 ndarray::Array2::from_shape_vec((code_dim, ncols).f(), rotated_data).unwrap()
374 }
375 }
376 }
377
378 pub fn rotate_fsl_to_f32(&self, vectors: &FixedSizeListArray) -> Result<Vec<f32>> {
379 match vectors.value_type() {
380 DataType::Float16 => self.rotate_fsl_to_f32_typed::<Float16Type>(vectors),
381 DataType::Float32 => self.rotate_fsl_to_f32_typed::<Float32Type>(vectors),
382 DataType::Float64 => self.rotate_fsl_to_f32_typed::<Float64Type>(vectors),
383 value_type => Err(Error::invalid_input(format!(
384 "Unsupported data type: {:?}",
385 value_type
386 ))),
387 }
388 }
389
390 fn rotate_fsl_to_f32_typed<T: ArrowFloatType>(
391 &self,
392 vectors: &FixedSizeListArray,
393 ) -> Result<Vec<f32>>
394 where
395 T::Native: AsPrimitive<f32> + Sync,
396 {
397 let dim = self.dim();
398 if vectors.value_length() as usize != dim {
399 return Err(Error::invalid_input(format!(
400 "Vector dimension mismatch: {} != {}",
401 vectors.value_length(),
402 dim
403 )));
404 }
405 let values = vectors
406 .values()
407 .as_any()
408 .downcast_ref::<T::ArrayType>()
409 .ok_or_else(|| {
410 Error::invalid_input(format!(
411 "Vector values have unexpected data type: {}",
412 vectors.value_type()
413 ))
414 })?
415 .as_slice();
416 let vec_mat = ndarray::ArrayView2::from_shape((vectors.len(), dim), values)
417 .map_err(|e| Error::invalid_input(e.to_string()))?;
418 let rotated = self.rotate_vectors::<T>(vec_mat.t());
419 let code_dim = self.code_dim();
420 let mut row_major = vec![0.0f32; vectors.len() * code_dim];
421 for row_idx in 0..vectors.len() {
422 for (dst, value) in row_major[row_idx * code_dim..(row_idx + 1) * code_dim]
423 .iter_mut()
424 .zip(rotated.column(row_idx).iter())
425 {
426 *dst = *value;
427 }
428 }
429 Ok(row_major)
430 }
431
432 pub fn dim(&self) -> usize {
433 self.code_dim()
434 }
435
436 pub fn codes_res_dot_dists<T: ArrowFloatType>(
438 &self,
439 residual_vectors: &FixedSizeListArray,
440 ) -> Result<Vec<f32>>
441 where
442 T::Native: AsPrimitive<f32> + Sync,
443 {
444 let dim = self.dim();
445 if residual_vectors.value_length() as usize != dim {
446 return Err(Error::invalid_input(format!(
447 "Vector dimension mismatch: {} != {}",
448 residual_vectors.value_length(),
449 dim
450 )));
451 }
452
453 let sqrt_dim = (dim as f32).sqrt();
454 let values = residual_vectors
455 .values()
456 .as_any()
457 .downcast_ref::<T::ArrayType>()
458 .unwrap()
459 .as_slice();
460
461 match self.rotation_type() {
462 RQRotationType::Matrix => {
463 let vec_mat =
465 ndarray::ArrayView2::from_shape((residual_vectors.len(), dim), values)
466 .map_err(|e| Error::invalid_input(e.to_string()))?;
467 let vec_mat = vec_mat.t();
468 let rotated_vectors = self.rotate_vectors::<T>(vec_mat);
469 let norm_dists = rotated_vectors.mapv(f32::abs).sum_axis(Axis(0)) / sqrt_dim;
470 debug_assert_eq!(norm_dists.len(), residual_vectors.len());
471 Ok(norm_dists.to_vec())
472 }
473 RQRotationType::Fast => {
474 let code_dim = self.code_dim();
475 let signs = self.fast_rotation_signs();
476 let mut norm_dists = vec![0.0f32; residual_vectors.len()];
477 norm_dists
478 .par_iter_mut()
479 .zip(values.par_chunks_exact(dim))
480 .for_each_init(
481 || vec![0.0f32; code_dim],
482 |scratch, (dst, input)| {
483 apply_fast_rotation(input, scratch, signs);
484 *dst = scratch.iter().map(|v| v.abs()).sum::<f32>() / sqrt_dim;
485 },
486 );
487 Ok(norm_dists)
488 }
489 }
490 }
491
492 fn transform<T: ArrowFloatType>(
493 &self,
494 residual_vectors: &FixedSizeListArray,
495 ) -> Result<ArrayRef>
496 where
497 T::Native: AsPrimitive<f32> + Sync,
498 {
499 let n = residual_vectors.len();
502 let dim = self.dim();
503 debug_assert_eq!(residual_vectors.values().len(), n * dim);
504 let values = residual_vectors
505 .values()
506 .as_any()
507 .downcast_ref::<T::ArrayType>()
508 .unwrap()
509 .as_slice();
510 let code_dim = self.code_dim();
511 let code_bytes = rabit_binary_code_bytes(code_dim);
512
513 match self.rotation_type() {
514 RQRotationType::Matrix => {
515 let vectors = ndarray::ArrayView2::from_shape((n, dim), values)
516 .map_err(|e| Error::invalid_input(e.to_string()))?;
517 let vectors = vectors.t();
518 let rotated_vectors = self.rotate_vectors::<T>(vectors);
519
520 let quantized_vectors = rotated_vectors.t().mapv(|v| v.is_sign_positive());
521 let bv: BitVec<u8, Lsb0> = BitVec::from_iter(quantized_vectors);
522
523 let codes = UInt8Array::from(bv.into_vec());
524 debug_assert_eq!(codes.len(), n * code_bytes);
525 Ok(Arc::new(FixedSizeListArray::try_new_from_values(
526 codes,
527 code_bytes as i32,
528 )?))
529 }
530 RQRotationType::Fast => {
531 let signs = self.fast_rotation_signs();
532 let mut encoded_codes = vec![0u8; n * code_bytes];
533 encoded_codes
534 .par_chunks_mut(code_bytes)
535 .zip(values.par_chunks_exact(dim))
536 .for_each_init(
537 || vec![0.0f32; code_dim],
538 |scratch, (code_dst, input)| {
539 apply_fast_rotation(input, scratch, signs);
540 pack_sign_bits(code_dst, scratch);
541 },
542 );
543 let codes = UInt8Array::from(encoded_codes);
544 debug_assert_eq!(codes.len(), n * code_bytes);
545 Ok(Arc::new(FixedSizeListArray::try_new_from_values(
546 codes,
547 code_bytes as i32,
548 )?))
549 }
550 }
551 }
552
553 pub(crate) fn quantize_split(
554 &self,
555 vectors: &FixedSizeListArray,
556 ) -> Result<RabitQuantizedBatch> {
557 match vectors.value_type() {
558 DataType::Float16 => self.transform_split::<Float16Type>(vectors),
559 DataType::Float32 => self.transform_split::<Float32Type>(vectors),
560 DataType::Float64 => self.transform_split::<Float64Type>(vectors),
561 value_type => Err(Error::invalid_input(format!(
562 "Unsupported data type: {:?}",
563 value_type
564 ))),
565 }
566 }
567
568 fn transform_split<T: ArrowFloatType>(
569 &self,
570 residual_vectors: &FixedSizeListArray,
571 ) -> Result<RabitQuantizedBatch>
572 where
573 T::Native: AsPrimitive<f32> + Sync,
574 {
575 let ex_bits = rabit_ex_bits(self.metadata.num_bits)?;
576 let n = residual_vectors.len();
577 let dim = self.dim();
578 debug_assert_eq!(residual_vectors.values().len(), n * dim);
579 let values = residual_vectors
580 .values()
581 .as_any()
582 .downcast_ref::<T::ArrayType>()
583 .unwrap()
584 .as_slice();
585 let code_dim = self.code_dim();
586 let code_bytes = rabit_binary_code_bytes(code_dim);
587 let ex_code_bytes = if ex_bits == 0 {
588 0
589 } else {
590 crate::vector::bq::ex_dot::blocked_ex_code_bytes(code_dim, ex_bits)
591 };
592
593 let mut encoded_codes = vec![0u8; n * code_bytes];
594 let mut encoded_ex_codes = (ex_bits != 0).then(|| vec![0u8; n * ex_code_bytes]);
595 let mut ex_res_dot_dists = (ex_bits != 0).then(|| vec![0.0f32; n]);
596 let mut rotated_residuals = vec![0.0f32; n * code_dim];
597 let mut ex_code_values = (ex_bits != 0).then(|| vec![0u8; n * code_dim]);
598
599 match self.rotation_type() {
600 RQRotationType::Matrix => {
601 let vectors = ndarray::ArrayView2::from_shape((n, dim), values)
602 .map_err(|e| Error::invalid_input(e.to_string()))?;
603 let vectors = vectors.t();
604 let rotated_vectors = self.rotate_vectors::<T>(vectors);
605
606 encoded_codes
607 .chunks_mut(code_bytes)
608 .zip(rotated_residuals.chunks_mut(code_dim))
609 .enumerate()
610 .for_each(|(row_idx, (code_dst, rotated_dst))| {
611 for (dst, value) in rotated_dst
612 .iter_mut()
613 .zip(rotated_vectors.column(row_idx).iter())
614 {
615 *dst = *value;
616 }
617 pack_sign_bits(code_dst, rotated_dst);
618 });
619 }
620 RQRotationType::Fast => {
621 let signs = self.fast_rotation_signs();
622 encoded_codes
623 .par_chunks_mut(code_bytes)
624 .zip(rotated_residuals.par_chunks_mut(code_dim))
625 .zip(values.par_chunks_exact(dim))
626 .for_each(|((code_dst, rotated_dst), input)| {
627 apply_fast_rotation(input, rotated_dst, signs);
628 pack_sign_bits(code_dst, rotated_dst);
629 });
630 }
631 }
632
633 if ex_bits != 0 {
634 let encoded_ex_codes = encoded_ex_codes
635 .as_mut()
636 .expect("ex-code buffer should exist for multi-bit RQ");
637 let ex_res_dot_dists = ex_res_dot_dists
638 .as_mut()
639 .expect("ex dot buffer should exist for multi-bit RQ");
640 let ex_code_values = ex_code_values
641 .as_mut()
642 .expect("ex-code value buffer should exist for multi-bit RQ");
643 encoded_ex_codes
644 .par_chunks_mut(ex_code_bytes)
645 .zip(ex_code_values.par_chunks_mut(code_dim))
646 .zip(ex_res_dot_dists.par_iter_mut())
647 .zip(rotated_residuals.par_chunks(code_dim))
648 .for_each(|(((ex_dst, ex_values_dst), ex_dot_dst), rotated)| {
649 *ex_dot_dst = quantize_ex_code(rotated, ex_bits, ex_dst, ex_values_dst);
650 });
651 }
652
653 let binary_codes = UInt8Array::from(encoded_codes);
654 let ex_codes = encoded_ex_codes.map(UInt8Array::from);
655 Ok(RabitQuantizedBatch {
656 binary_codes: Arc::new(FixedSizeListArray::try_new_from_values(
657 binary_codes,
658 code_bytes as i32,
659 )?),
660 ex_codes: ex_codes
661 .map(|ex_codes| {
662 FixedSizeListArray::try_new_from_values(ex_codes, ex_code_bytes as i32)
663 .map(|array| Arc::new(array) as ArrayRef)
664 })
665 .transpose()?,
666 ex_res_dot_dists,
667 rotated_residuals: Some(rotated_residuals),
668 ex_code_values,
669 })
670 }
671}
672
673impl Quantization for RabitQuantizer {
674 type BuildParams = RQBuildParams;
675 type Metadata = RabitQuantizationMetadata;
676 type Storage = RabitQuantizationStorage;
677
678 fn build(
679 data: &dyn Array,
680 _: lance_linalg::distance::DistanceType,
681 params: &Self::BuildParams,
682 ) -> Result<Self> {
683 validate_rq_num_bits(params.num_bits)?;
684
685 let dim = data.as_fixed_size_list().value_length() as usize;
686 if !dim.is_multiple_of(u8::BITS as usize) {
687 return Err(Error::invalid_input(
688 "vector dimension must be divisible by 8 for IVF_RQ",
689 ));
690 }
691 if let Some(q) = Self::from_supplied_rotation(params, dim)? {
692 return Ok(q);
693 }
694
695 let q = match data.as_fixed_size_list().value_type() {
696 DataType::Float16 => Self::new_with_rotation::<Float16Type>(
697 params.num_bits,
698 data.as_fixed_size_list().value_length(),
699 params.rotation_type,
700 ),
701 DataType::Float32 => Self::new_with_rotation::<Float32Type>(
702 params.num_bits,
703 data.as_fixed_size_list().value_length(),
704 params.rotation_type,
705 ),
706 DataType::Float64 => Self::new_with_rotation::<Float64Type>(
707 params.num_bits,
708 data.as_fixed_size_list().value_length(),
709 params.rotation_type,
710 ),
711 dt => {
712 return Err(Error::invalid_input(format!(
713 "Unsupported data type: {:?}",
714 dt
715 )));
716 }
717 };
718 Ok(q)
719 }
720
721 fn retrain(&mut self, _data: &dyn Array) -> Result<()> {
722 Ok(())
723 }
724
725 fn code_dim(&self) -> usize {
726 if self.metadata.code_dim > 0 {
727 self.metadata.code_dim as usize
728 } else {
729 self.metadata
730 .rotate_mat
731 .as_ref()
732 .map(|rotate_mat| rotate_mat.len())
733 .unwrap_or(0)
734 }
735 }
736
737 fn column(&self) -> &'static str {
738 RABIT_CODE_COLUMN
739 }
740
741 fn use_residual(_: lance_linalg::distance::DistanceType) -> bool {
742 true
743 }
744
745 fn quantize(&self, vectors: &dyn Array) -> Result<arrow_array::ArrayRef> {
746 let vectors = vectors.as_fixed_size_list();
747 match vectors.value_type() {
748 DataType::Float16 => self.transform::<Float16Type>(vectors),
749 DataType::Float32 => self.transform::<Float32Type>(vectors),
750 DataType::Float64 => self.transform::<Float64Type>(vectors),
751 value_type => Err(Error::invalid_input(format!(
752 "Unsupported data type: {:?}",
753 value_type
754 ))),
755 }
756 }
757
758 fn metadata_key() -> &'static str {
759 RABIT_METADATA_KEY
760 }
761
762 fn quantization_type() -> crate::vector::quantizer::QuantizationType {
763 crate::vector::quantizer::QuantizationType::Rabit
764 }
765
766 fn metadata(
767 &self,
768 args: Option<crate::vector::quantizer::QuantizationMetadata>,
769 ) -> Self::Metadata {
770 let mut metadata = self.metadata.clone();
771 metadata.packed = args.map(|args| args.transposed).unwrap_or_default();
772 metadata
773 }
774
775 fn from_metadata(
776 metadata: &Self::Metadata,
777 _: lance_linalg::distance::DistanceType,
778 ) -> Result<Quantizer> {
779 validate_rq_num_bits(metadata.num_bits)?;
780 Ok(Quantizer::Rabit(Self {
781 metadata: metadata.clone(),
782 }))
783 }
784
785 fn field(&self) -> Field {
786 rabit_binary_code_field(self.code_dim())
787 }
788
789 fn extra_fields(&self) -> Vec<Field> {
790 let mut fields = vec![ADD_FACTORS_FIELD.clone(), SCALE_FACTORS_FIELD.clone()];
791 if self.metadata.query_estimator == RabitQueryEstimator::RawQuery {
792 fields.push(ERROR_FACTORS_FIELD.clone());
793 }
794 if let Some(ex_code_field) = rabit_ex_code_field(self.code_dim(), self.metadata.num_bits)
795 .expect("RabitQ num_bits should be validated")
796 {
797 fields.push(ex_code_field);
798 fields.push(EX_ADD_FACTORS_FIELD.clone());
799 fields.push(EX_SCALE_FACTORS_FIELD.clone());
800 }
801 fields
802 }
803}
804
805impl TryFrom<Quantizer> for RabitQuantizer {
806 type Error = Error;
807
808 fn try_from(quantizer: Quantizer) -> Result<Self> {
809 match quantizer {
810 Quantizer::Rabit(quantizer) => Ok(quantizer),
811 _ => Err(Error::invalid_input(
812 "Cannot convert non-RabitQuantizer to RabitQuantizer",
813 )),
814 }
815 }
816}
817
818impl From<RabitQuantizer> for Quantizer {
819 fn from(quantizer: RabitQuantizer) -> Self {
820 Self::Rabit(quantizer)
821 }
822}
823
824fn random_normal_matrix(n: usize) -> ndarray::Array2<f64> {
825 let mut rng = rand::rng();
826 let normal = rand_distr::Normal::new(0.0, 1.0).unwrap();
827 ndarray::Array2::from_shape_simple_fn((n, n), || normal.sample(&mut rng))
828}
829
830fn householder_qr(a: ndarray::Array2<f64>) -> (ndarray::Array2<f64>, ndarray::Array2<f64>) {
832 let (m, n) = a.dim();
833 let mut q = ndarray::Array2::eye(m);
834 let mut r = a;
835
836 for k in 0..n.min(m - 1) {
837 let mut x = r.slice(s![k.., k]).to_owned();
838 let x_norm = x.dot(&x).sqrt();
839
840 if x_norm < f64::EPSILON {
841 continue;
842 }
843
844 let sign = if x[0] >= 0.0 { 1.0 } else { -1.0 };
846 x[0] += sign * x_norm;
847 let u = &x / x.dot(&x).sqrt();
848
849 let mut u_outer = ndarray::Array2::zeros((m - k, m - k));
852 for i in 0..(m - k) {
853 for j in 0..(m - k) {
854 u_outer[[i, j]] = u[i] * u[j];
855 }
856 }
857 let h = ndarray::Array2::eye(m - k) - 2.0 * u_outer;
858
859 let r_block = r.slice(s![k.., k..]).to_owned();
861 let h_r = h.dot(&r_block);
862 r.slice_mut(s![k.., k..]).assign(&h_r);
863
864 let q_block = q.slice(s![.., k..]).to_owned();
866 let q_h = q_block.dot(&h);
867 q.slice_mut(s![.., k..]).assign(&q_h);
868 }
869
870 (q, r)
871}
872
873fn random_orthogonal<T: ArrowFloatType>(n: usize) -> ndarray::Array2<T::Native>
874where
875 T::Native: FromPrimitive,
876{
877 let a = random_normal_matrix(n);
878 let (q, _) = householder_qr(a);
879
880 q.mapv(|v| T::Native::from_f64(v).unwrap())
882}
883
884#[cfg(test)]
885mod tests {
886 use super::*;
887 use approx::assert_relative_eq;
888 use arrow::datatypes::Float32Type;
889 use arrow_array::{FixedSizeListArray, Float32Array};
890 use lance_linalg::distance::DistanceType;
891 use rstest::rstest;
892
893 use crate::vector::bq::storage::RABIT_BLOCKED_EX_CODE_COLUMN;
894
895 #[rstest]
896 #[case(8)]
897 #[case(16)]
898 #[case(32)]
899 fn test_householder_qr(#[case] n: usize) {
900 let a = random_normal_matrix(n);
901 let (m, n) = a.dim();
902
903 let (q, r) = householder_qr(a.clone());
904
905 let q_t_q = q.t().dot(&q);
907 for i in 0..m {
908 for j in 0..m {
909 let expected = if i == j { 1.0 } else { 0.0 };
910 assert_relative_eq!(q_t_q[[i, j]], expected, epsilon = 1e-5);
911 }
912 }
913
914 let qr = q.dot(&r);
916 for i in 0..m {
917 for j in 0..n {
918 assert_relative_eq!(qr[[i, j]], a[[i, j]], epsilon = 1e-5);
919 }
920 }
921
922 for i in 1..n.min(m) {
924 for j in 0..i {
925 assert_relative_eq!(r[[i, j]], 0.0, epsilon = 1e-5);
926 }
927 }
928
929 assert_eq!(q.dim(), (m, m));
931 assert_eq!(r.dim(), (m, n));
932 }
933
934 #[test]
935 fn test_rabit_quantizer_rotation_modes() {
936 let fast_q = RabitQuantizer::new_with_rotation::<Float32Type>(1, 128, RQRotationType::Fast);
937 assert_eq!(fast_q.rotation_type(), RQRotationType::Fast);
938 assert_eq!(fast_q.dim(), 128);
939 assert_eq!(fast_q.code_dim(), 128);
940
941 let matrix_q =
942 RabitQuantizer::new_with_rotation::<Float32Type>(1, 128, RQRotationType::Matrix);
943 assert_eq!(matrix_q.rotation_type(), RQRotationType::Matrix);
944 assert_eq!(matrix_q.dim(), 128);
945 assert_eq!(matrix_q.code_dim(), 128);
946 }
947
948 #[test]
949 fn test_rabit_quantizer_field_uses_binary_code_size() {
950 let q = RabitQuantizer::new_with_rotation::<Float32Type>(1, 128, RQRotationType::Fast);
951 let field = q.field();
952 let DataType::FixedSizeList(_, code_bytes) = field.data_type() else {
953 panic!("RabitQ code field should be FixedSizeList");
954 };
955 assert_eq!(*code_bytes, 16);
956 }
957
958 #[test]
959 fn test_rabit_quantizer_extra_fields_include_raw_query_error_factor() {
960 let q = RabitQuantizer::new_with_rotation::<Float32Type>(1, 128, RQRotationType::Fast);
961 let fields = q.extra_fields();
962 assert!(
963 fields
964 .iter()
965 .any(|field| field.name() == ERROR_FACTORS_FIELD.name())
966 );
967 assert!(
968 !fields
969 .iter()
970 .any(|field| field.name() == RABIT_BLOCKED_EX_CODE_COLUMN)
971 );
972
973 let q = RabitQuantizer::new_with_rotation::<Float32Type>(3, 128, RQRotationType::Fast);
974 let fields = q.extra_fields();
975 for expected in [
976 ERROR_FACTORS_FIELD.name().as_str(),
977 RABIT_BLOCKED_EX_CODE_COLUMN,
978 EX_ADD_FACTORS_FIELD.name().as_str(),
979 EX_SCALE_FACTORS_FIELD.name().as_str(),
980 ] {
981 assert!(
982 fields.iter().any(|field| field.name().as_str() == expected),
983 "missing {expected}"
984 );
985 }
986 }
987
988 #[test]
989 fn test_rabit_quantizer_requires_dim_divisible_by_8() {
990 let vectors = Float32Array::from(vec![0.0f32; 4 * 30]);
991 let fsl = FixedSizeListArray::try_new_from_values(vectors, 30).unwrap();
992 let params = RQBuildParams::new(1);
993
994 let err = RabitQuantizer::build(&fsl, DistanceType::L2, ¶ms).unwrap_err();
995 assert!(
996 err.to_string()
997 .contains("vector dimension must be divisible by 8 for IVF_RQ"),
998 "{}",
999 err
1000 );
1001 }
1002
1003 #[test]
1004 fn test_rabit_quantizer_reuses_supplied_rotation() {
1005 let vectors = Float32Array::from(vec![0.0f32; 4 * 32]);
1006 let fsl = FixedSizeListArray::try_new_from_values(vectors, 32).unwrap();
1007 let supplied =
1008 RabitQuantizer::new_with_rotation::<Float32Type>(3, 32, RQRotationType::Fast)
1009 .metadata(None);
1010 let supplied_signs = supplied.fast_rotation_signs.clone();
1011
1012 let mut params = RQBuildParams::with_rotation_type(3, RQRotationType::Fast);
1013 params.rotation = Some(supplied);
1014
1015 let quantizer = RabitQuantizer::build(&fsl, DistanceType::L2, ¶ms).unwrap();
1016 let metadata = quantizer.metadata_ref();
1017 assert_eq!(metadata.num_bits, 3);
1018 assert_eq!(metadata.rotation_type, RQRotationType::Fast);
1019 assert_eq!(metadata.fast_rotation_signs, supplied_signs);
1020 }
1021
1022 #[test]
1023 fn test_rabit_quantizer_validates_supplied_rotation() {
1024 let vectors = Float32Array::from(vec![0.0f32; 4 * 32]);
1025 let fsl = FixedSizeListArray::try_new_from_values(vectors, 32).unwrap();
1026 let supplied =
1027 RabitQuantizer::new_with_rotation::<Float32Type>(3, 32, RQRotationType::Fast)
1028 .metadata(None);
1029
1030 let mut wrong_num_bits = supplied.clone();
1031 wrong_num_bits.num_bits = 1;
1032 let mut params = RQBuildParams::with_rotation_type(3, RQRotationType::Fast);
1033 params.rotation = Some(wrong_num_bits);
1034 let err = RabitQuantizer::build(&fsl, DistanceType::L2, ¶ms).unwrap_err();
1035 assert!(
1036 err.to_string()
1037 .contains("does not match requested num_bits")
1038 );
1039
1040 let mut wrong_dim = supplied.clone();
1041 wrong_dim.code_dim = 64;
1042 let mut params = RQBuildParams::with_rotation_type(3, RQRotationType::Fast);
1043 params.rotation = Some(wrong_dim);
1044 let err = RabitQuantizer::build(&fsl, DistanceType::L2, ¶ms).unwrap_err();
1045 assert!(err.to_string().contains("does not match vector dimension"));
1046
1047 let mut wrong_sign_len = supplied;
1048 wrong_sign_len.fast_rotation_signs.as_mut().unwrap().pop();
1049 let mut params = RQBuildParams::with_rotation_type(3, RQRotationType::Fast);
1050 params.rotation = Some(wrong_sign_len);
1051 let err = RabitQuantizer::build(&fsl, DistanceType::L2, ¶ms).unwrap_err();
1052 assert!(err.to_string().contains("fast_rotation_signs length"));
1053 }
1054
1055 #[test]
1056 fn test_rabit_quantizer_accepts_multi_bit_range() {
1057 let vectors = Float32Array::from(vec![0.0f32; 4 * 32]);
1058 let fsl = FixedSizeListArray::try_new_from_values(vectors, 32).unwrap();
1059
1060 let err =
1061 RabitQuantizer::build(&fsl, DistanceType::L2, &RQBuildParams::new(0)).unwrap_err();
1062 assert!(
1063 err.to_string().contains("IVF_RQ num_bits must be in"),
1064 "{}",
1065 err
1066 );
1067
1068 for rotation_type in [RQRotationType::Fast, RQRotationType::Matrix] {
1069 let quantizer = RabitQuantizer::build(
1070 &fsl,
1071 DistanceType::L2,
1072 &RQBuildParams::with_rotation_type(9, rotation_type),
1073 )
1074 .unwrap();
1075 let quantized = quantizer.quantize_split(&fsl).unwrap();
1076 assert!(quantized.ex_codes.is_some());
1077 assert_eq!(
1078 quantized.binary_codes.as_fixed_size_list().value_length(),
1079 4
1080 );
1081 assert_eq!(
1082 quantized
1083 .ex_codes
1084 .unwrap()
1085 .as_fixed_size_list()
1086 .value_length(),
1087 64
1089 );
1090 }
1091
1092 let err =
1093 RabitQuantizer::build(&fsl, DistanceType::L2, &RQBuildParams::new(10)).unwrap_err();
1094 assert!(
1095 err.to_string().contains("IVF_RQ num_bits must be in"),
1096 "{}",
1097 err
1098 );
1099 }
1100}