Skip to main content

lance_index/vector/
bq.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! Binary Quantization (BQ)
5
6use std::iter::once;
7use std::str::FromStr;
8use std::sync::Arc;
9
10use crate::pb::vector_index_details::RabitQuantization;
11use arrow_array::types::Float32Type;
12use arrow_array::{Array, ArrayRef, UInt8Array, cast::AsArray};
13use lance_core::{Error, Result};
14use num_traits::Float;
15use serde::{Deserialize, Serialize};
16
17use crate::vector::bq::storage::RabitQuantizationMetadata;
18use crate::vector::quantizer::QuantizerBuildParams;
19
20pub mod builder;
21pub(crate) mod dist_table_quant;
22pub mod ex_dot;
23pub mod prune;
24pub mod rotation;
25pub mod storage;
26pub mod transform;
27
28pub const RABIT_MIN_NUM_BITS: u8 = 1;
29pub const RABIT_MAX_NUM_BITS: u8 = 9;
30pub const RABIT_BINARY_NUM_BITS: u8 = 1;
31
32#[derive(Clone, Default)]
33pub struct BinaryQuantization {}
34
35impl BinaryQuantization {
36    /// Transform an array of float vectors to binary vectors.
37    pub fn transform(&self, data: &dyn Array) -> Result<ArrayRef> {
38        let fsl = data
39            .as_fixed_size_list_opt()
40            .ok_or(Error::index(format!(
41                "Expect to be a float vector array, got: {:?}",
42                data.data_type()
43            )))?
44            .clone();
45
46        let data = fsl
47            .values()
48            .as_primitive_opt::<Float32Type>()
49            .ok_or(Error::index(format!(
50                "Expect to be a float32 vector array, got: {:?}",
51                fsl.values().data_type()
52            )))?;
53        let dim = fsl.value_length() as usize;
54        let code = data
55            .values()
56            .chunks_exact(dim)
57            .flat_map(binary_quantization)
58            .collect::<Vec<_>>();
59
60        Ok(Arc::new(UInt8Array::from(code)))
61    }
62}
63
64/// Binary quantization.
65///
66/// Use the sign bit of the float vector to represent the binary vector.
67fn binary_quantization<T: Float>(data: &[T]) -> impl Iterator<Item = u8> + '_ {
68    let iter = data.chunks_exact(8);
69    iter.clone()
70        .map(|c| {
71            // Auto vectorized.
72            // Before changing this code, please check the assembly output.
73            let mut bits: u8 = 0;
74            c.iter().enumerate().for_each(|(idx, v)| {
75                bits |= (v.is_sign_positive() as u8) << idx;
76            });
77            bits
78        })
79        .chain(once(0).map(move |_| {
80            let mut bits: u8 = 0;
81            iter.remainder().iter().enumerate().for_each(|(idx, v)| {
82                bits |= (v.is_sign_positive() as u8) << idx;
83            });
84            bits
85        }))
86}
87
88#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90pub enum RQRotationType {
91    #[default]
92    Fast,
93    Matrix,
94}
95
96impl FromStr for RQRotationType {
97    type Err = Error;
98
99    fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
100        match value.to_lowercase().as_str() {
101            "fast" | "fht_kac" | "fht-kac" => Ok(Self::Fast),
102            "matrix" | "dense" => Ok(Self::Matrix),
103            _ => Err(Error::invalid_input(format!(
104                "Unknown RQ rotation type: {}. Expected one of: fast, matrix",
105                value
106            ))),
107        }
108    }
109}
110
111#[derive(Clone, Debug)]
112pub struct RQBuildParams {
113    pub num_bits: u8,
114    pub rotation_type: RQRotationType,
115    /// Optional pre-built rotation to reuse instead of generating a fresh random one.
116    ///
117    /// Distributed `IVF_RQ` builds mint one rotation and broadcast it so every segment
118    /// rotates vectors identically. This is transient build-time state and is never
119    /// persisted to the `RabitQuantization` params proto.
120    pub rotation: Option<RabitQuantizationMetadata>,
121}
122
123pub fn validate_rq_num_bits(num_bits: u8) -> Result<()> {
124    if !(RABIT_MIN_NUM_BITS..=RABIT_MAX_NUM_BITS).contains(&num_bits) {
125        return Err(Error::invalid_input(format!(
126            "IVF_RQ num_bits must be in {}..={}, got {}",
127            RABIT_MIN_NUM_BITS, RABIT_MAX_NUM_BITS, num_bits
128        )));
129    }
130    Ok(())
131}
132
133pub fn validate_supported_rq_num_bits(num_bits: u8) -> Result<()> {
134    validate_rq_num_bits(num_bits)
135}
136
137pub fn rabit_ex_bits(num_bits: u8) -> Result<u8> {
138    validate_rq_num_bits(num_bits)?;
139    Ok(num_bits - RABIT_BINARY_NUM_BITS)
140}
141
142pub fn rabit_binary_code_bytes(rotated_dim: usize) -> usize {
143    rotated_dim.div_ceil(u8::BITS as usize)
144}
145
146pub fn rabit_ex_code_bytes(rotated_dim: usize, ex_bits: u8) -> Result<usize> {
147    let total_bits = rotated_dim.checked_mul(ex_bits as usize).ok_or_else(|| {
148        Error::invalid_input(format!(
149            "IVF_RQ ex-code byte size overflow: rotated_dim={}, ex_bits={}",
150            rotated_dim, ex_bits
151        ))
152    })?;
153    Ok(total_bits.div_ceil(u8::BITS as usize))
154}
155
156impl RQBuildParams {
157    pub fn new(num_bits: u8) -> Self {
158        Self {
159            num_bits,
160            rotation_type: RQRotationType::default(),
161            rotation: None,
162        }
163    }
164
165    pub fn with_rotation_type(num_bits: u8, rotation_type: RQRotationType) -> Self {
166        Self {
167            num_bits,
168            rotation_type,
169            rotation: None,
170        }
171    }
172}
173
174impl From<&RQBuildParams> for RabitQuantization {
175    fn from(value: &RQBuildParams) -> Self {
176        use crate::pb::vector_index_details::rabit_quantization::RotationType;
177        Self {
178            num_bits: value.num_bits as u32,
179            rotation_type: match value.rotation_type {
180                RQRotationType::Fast => RotationType::Fast as i32,
181                RQRotationType::Matrix => RotationType::Matrix as i32,
182            },
183        }
184    }
185}
186
187impl QuantizerBuildParams for RQBuildParams {
188    fn sample_size(&self) -> usize {
189        0
190    }
191}
192
193impl Default for RQBuildParams {
194    fn default() -> Self {
195        Self {
196            num_bits: 1,
197            rotation_type: RQRotationType::default(),
198            rotation: None,
199        }
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    use half::{bf16, f16};
208
209    fn test_bq<T: Float>() {
210        let data: Vec<T> = [1.0, -1.0, 1.0, -5.0, -7.0, -1.0, 1.0, -1.0, -0.2, 1.2, 3.2]
211            .iter()
212            .map(|&v| T::from(v).unwrap())
213            .collect();
214        let expected = vec![0b01000101, 0b00000110];
215        let result = binary_quantization(&data).collect::<Vec<_>>();
216        assert_eq!(result, expected);
217    }
218
219    #[test]
220    fn test_binary_quantization() {
221        test_bq::<bf16>();
222        test_bq::<f16>();
223        test_bq::<f32>();
224        test_bq::<f64>();
225    }
226
227    #[test]
228    fn test_rotation_type_parse() {
229        assert_eq!(
230            "fast".parse::<RQRotationType>().unwrap(),
231            RQRotationType::Fast
232        );
233        assert_eq!(
234            "matrix".parse::<RQRotationType>().unwrap(),
235            RQRotationType::Matrix
236        );
237        assert!("invalid".parse::<RQRotationType>().is_err());
238    }
239
240    #[test]
241    fn test_rabit_num_bits_validation() {
242        validate_rq_num_bits(1).unwrap();
243        validate_rq_num_bits(9).unwrap();
244
245        let err = validate_rq_num_bits(0).unwrap_err();
246        assert!(
247            err.to_string().contains("IVF_RQ num_bits must be in"),
248            "{}",
249            err
250        );
251
252        let err = validate_rq_num_bits(10).unwrap_err();
253        assert!(
254            err.to_string().contains("IVF_RQ num_bits must be in"),
255            "{}",
256            err
257        );
258
259        validate_supported_rq_num_bits(1).unwrap();
260        validate_supported_rq_num_bits(9).unwrap();
261    }
262
263    #[test]
264    fn test_rabit_split_code_byte_sizing() {
265        assert_eq!(rabit_ex_bits(1).unwrap(), 0);
266        assert_eq!(rabit_ex_bits(9).unwrap(), 8);
267
268        assert_eq!(rabit_binary_code_bytes(128), 16);
269        assert_eq!(rabit_binary_code_bytes(129), 17);
270
271        assert_eq!(rabit_ex_code_bytes(128, 0).unwrap(), 0);
272        assert_eq!(rabit_ex_code_bytes(128, 3).unwrap(), 48);
273        assert_eq!(rabit_ex_code_bytes(128, 8).unwrap(), 128);
274        assert_eq!(rabit_ex_code_bytes(129, 3).unwrap(), 49);
275    }
276}