Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions java/lance-jni/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions protos/ann.proto
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ message VectorQueryProto {
* indexes, such as IVF_RQ. Other index types ignore this setting.
*/
VectorApproxMode approx_mode = 14;
// Empty selects full precision; otherwise sign, high or full.
string rq_precision = 17;
// Absent scans full codes; present reranks k times this positive factor.
optional uint32 rq_cascade_factor = 18;
/* Which covering columns the index must materialize for this query. Absent means
* "not computed" -- see CoveringProjection. Carried across the wire so a remote
* executor declares the same search output schema the planner did; without it the
Expand Down
1 change: 1 addition & 0 deletions python/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion rust/lance-index/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ async-channel = "2.3.1"
rand_distr.workspace = true
rangemap.workspace = true

uuid.workspace = true

[dev-dependencies]
approx.workspace = true
criterion.workspace = true
Expand All @@ -92,7 +94,6 @@ test-log.workspace = true
rstest.workspace = true
serial_test.workspace = true
chrono.workspace = true
uuid.workspace = true

[features]
geo = ["dep:lance-geo", "lance-geo/geo", "dep:geoarrow-array", "dep:geoarrow-schema", "dep:geo-types"]
Expand Down
12 changes: 10 additions & 2 deletions rust/lance-index/benches/rq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ fn ex_code_storage_load(c: &mut Criterion) {
code_dim: LOAD_DIM as u32,
num_bits: NUM_BITS,
packed: true,
layered: false,
query_estimator: RabitQueryEstimator::RawQuery,
};
let code_len = LOAD_DIM / 8;
Expand Down Expand Up @@ -465,7 +466,10 @@ fn ex_bulk_paths(c: &mut Criterion) {
0.0,
None,
&mut f32_scratch,
DistanceCalculatorOptions { approx_mode },
DistanceCalculatorOptions {
rq_precision: Default::default(),
approx_mode,
},
);
let mut dists = Vec::new();
let mut u16_scratch = Vec::new();
Expand Down Expand Up @@ -613,7 +617,10 @@ fn heap_topk(c: &mut Criterion) {
1.0,
None,
&mut f32_scratch,
DistanceCalculatorOptions { approx_mode },
DistanceCalculatorOptions {
rq_precision: Default::default(),
approx_mode,
},
);
let mut heap = BinaryHeap::with_capacity(TOPK_K + 1);
let mut dists = Vec::new();
Expand Down Expand Up @@ -670,6 +677,7 @@ fn build_transform(c: &mut Criterion) {
code_dim: dim as u32,
num_bits,
packed: false,
layered: false,
query_estimator: RabitQueryEstimator::RawQuery,
};
let rq = RabitQuantizer::try_from(
Expand Down
1 change: 1 addition & 0 deletions rust/lance-index/protos-cache/cache.proto
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,5 @@ message RabitPartitionHeader {
optional bytes fast_rotation_signs = 5;
// Estimator the RabitQ storage uses at query time (residual vs raw query).
RabitQueryEstimator query_estimator = 6;
bool layered = 7;
}
4 changes: 4 additions & 0 deletions rust/lance-index/src/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ pub struct Query {
/// This currently only affects RQ-quantized vector indexes, such as IVF_RQ.
/// Other index types ignore this setting.
pub approx_mode: ApproxMode,
/// Precision of a layered RaBitQ query; defaults to full.
pub rq_precision: bq::layered::RQPrecision,
/// Candidate oversampling for a high-to-full cascade. None scans full codes.
pub rq_cascade_factor: Option<u32>,
}

impl Query {
Expand Down
14 changes: 13 additions & 1 deletion rust/lance-index/src/vector/bq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub(crate) mod dist_table_quant;
pub mod ex_dot;
pub mod layered;
pub(crate) mod pairwise;
mod plane_cache;
pub mod prune;
pub mod rotation;
pub mod storage;
Expand Down Expand Up @@ -124,6 +125,8 @@ pub struct RQBuildParams {
/// rotates vectors identically. This is transient build-time state and is never
/// persisted to the `RabitQuantization` params proto.
pub rotation: Option<RabitQuantizationMetadata>,
/// Store prefix code planes; supported for 3 through 9 bits. Default off.
pub layered: bool,
}

pub fn validate_rq_num_bits(num_bits: u8) -> Result<()> {
Expand Down Expand Up @@ -160,11 +163,18 @@ pub fn rabit_ex_code_bytes(rotated_dim: usize, ex_bits: u8) -> Result<usize> {
}

impl RQBuildParams {
/// Opt into the layered format. Requires a reader with layered RQ support.
pub fn with_layered(mut self, layered: bool) -> Self {
self.layered = layered;
self
}

pub fn new(num_bits: u8) -> Self {
Self {
num_bits,
rotation_type: RQRotationType::default(),
rotation: None,
layered: false,
}
}

Expand All @@ -173,6 +183,7 @@ impl RQBuildParams {
num_bits,
rotation_type,
rotation: None,
layered: false,
}
}
}
Expand All @@ -182,7 +193,7 @@ impl From<&RQBuildParams> for RabitQuantization {
use crate::pb::vector_index_details::rabit_quantization::{RotationType, RowLayout};
Self {
num_bits: value.num_bits as u32,
layered: false,
layered: value.layered,
row_layout: RowLayout::Columns as i32,
rotation_type: match value.rotation_type {
RQRotationType::Fast => RotationType::Fast as i32,
Expand All @@ -204,6 +215,7 @@ impl Default for RQBuildParams {
num_bits: RABIT_DEFAULT_NUM_BITS,
rotation_type: RQRotationType::default(),
rotation: None,
layered: false,
}
}
}
Expand Down
32 changes: 21 additions & 11 deletions rust/lance-index/src/vector/bq/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,8 @@ pub struct RabitQuantizer {
pub(crate) struct RabitQuantizedBatch {
pub binary_codes: ArrayRef,
pub ex_codes: Option<ArrayRef>,
#[cfg(test)]
pub ex_res_dot_dists: Option<Vec<f32>>,
#[cfg(test)]
pub rotated_residuals: Option<Vec<f32>>,
#[cfg(test)]
pub ex_code_values: Option<Vec<u8>>,
}

Expand Down Expand Up @@ -542,11 +539,8 @@ impl RabitQuantizer {
} else {
None
},
#[cfg(test)]
rotated_residuals: None,
#[cfg(test)]
ex_code_values: None,
#[cfg(test)]
ex_res_dot_dists: None,
})
}
Expand Down Expand Up @@ -583,6 +577,7 @@ impl RabitQuantizer {
code_dim: code_dim as u32,
num_bits,
packed: false,
layered: false,
query_estimator: RabitQueryEstimator::RawQuery,
}
}
Expand All @@ -594,6 +589,7 @@ impl RabitQuantizer {
code_dim: code_dim as u32,
num_bits,
packed: false,
layered: false,
query_estimator: RabitQueryEstimator::RawQuery,
},
};
Expand Down Expand Up @@ -1035,11 +1031,8 @@ impl RabitQuantizer {
.map(|array| Arc::new(array) as ArrayRef)
})
.transpose()?,
#[cfg(test)]
ex_res_dot_dists,
#[cfg(test)]
rotated_residuals: Some(rotated_residuals),
#[cfg(test)]
ex_code_values,
})
}
Expand All @@ -1056,18 +1049,22 @@ impl Quantization for RabitQuantizer {
params: &Self::BuildParams,
) -> Result<Self> {
validate_rq_num_bits(params.num_bits)?;
if params.layered {
super::layered::RQLayout::try_new(params.num_bits)?;
}

let dim = data.as_fixed_size_list().value_length() as usize;
if !dim.is_multiple_of(u8::BITS as usize) {
return Err(Error::invalid_input(
"vector dimension must be divisible by 8 for IVF_RQ",
));
}
if let Some(q) = Self::from_supplied_rotation(params, dim)? {
if let Some(mut q) = Self::from_supplied_rotation(params, dim)? {
q.metadata.layered = params.layered;
return Ok(q);
}

let q = match data.as_fixed_size_list().value_type() {
let mut q = match data.as_fixed_size_list().value_type() {
DataType::Float16 => Self::new_with_rotation::<Float16Type>(
params.num_bits,
data.as_fixed_size_list().value_length(),
Expand All @@ -1090,6 +1087,7 @@ impl Quantization for RabitQuantizer {
)));
}
};
q.metadata.layered = params.layered;
Ok(q)
}

Expand Down Expand Up @@ -1152,6 +1150,14 @@ impl Quantization for RabitQuantizer {
_: lance_linalg::distance::DistanceType,
) -> Result<Quantizer> {
validate_rq_num_bits(metadata.num_bits)?;
if metadata.layered {
super::layered::RQLayout::try_new(metadata.num_bits)?;
if metadata.query_estimator != RabitQueryEstimator::RawQuery {
return Err(Error::invalid_input(
"layered IVF_RQ requires the raw-query estimator",
));
}
}
Ok(Quantizer::Rabit(Self {
metadata: metadata.clone(),
}))
Expand All @@ -1166,6 +1172,10 @@ impl Quantization for RabitQuantizer {
if self.metadata.query_estimator == RabitQueryEstimator::RawQuery {
fields.push(ERROR_FACTORS_FIELD.clone());
}
if self.metadata.layered {
return super::layered::storage_fields(self.code_dim(), self.metadata.num_bits, fields)
.expect("layered layout validated at build");
}
if let Some(ex_code_field) = rabit_ex_code_field(self.code_dim(), self.metadata.num_bits)
.expect("RabitQ num_bits should be validated")
{
Expand Down
Loading
Loading