vectlite 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -14
- package/index.d.ts +60 -0
- package/index.js +135 -8
- package/native/Cargo.toml +1 -1
- package/native/src/lib.rs +80 -47
- package/native/vectlite-core/Cargo.toml +1 -1
- package/native/vectlite-core/src/lib.rs +512 -152
- package/native/vectlite-core/src/quantization.rs +234 -49
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/vectlite.node +0 -0
- package/prebuilds/darwin-x64/vectlite.node +0 -0
- package/prebuilds/linux-x64-gnu/vectlite.node +0 -0
- package/prebuilds/win32-x64-msvc/vectlite.node +0 -0
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
//! Vector quantization module for memory-efficient similarity search.
|
|
2
2
|
//!
|
|
3
3
|
//! Supports three quantization strategies:
|
|
4
|
-
//! - **Scalar (int8)**:
|
|
5
|
-
//! - **Binary**:
|
|
4
|
+
//! - **Scalar (int8)**: compact in-memory candidate index with minimal recall loss
|
|
5
|
+
//! - **Binary**: smallest in-memory candidate index, uses Hamming distance for fast filtering
|
|
6
6
|
//! - **Product Quantization (PQ)**: Configurable compression for very large datasets
|
|
7
7
|
//!
|
|
8
8
|
//! All strategies support a 2-stage pipeline: fast quantized search followed by
|
|
9
9
|
//! exact float32 rescoring of top candidates.
|
|
10
10
|
|
|
11
|
-
use std::io::{Read, Write};
|
|
11
|
+
use std::io::{Error, ErrorKind, Read, Write};
|
|
12
|
+
|
|
13
|
+
use crate::{DistanceMetric, Result, VectLiteError};
|
|
12
14
|
|
|
13
15
|
// ---------------------------------------------------------------------------
|
|
14
16
|
// Public types
|
|
@@ -18,10 +20,10 @@ use std::io::{Read, Write};
|
|
|
18
20
|
#[derive(Clone, Debug, PartialEq)]
|
|
19
21
|
pub enum QuantizationConfig {
|
|
20
22
|
/// Scalar quantization: maps each f32 dimension to int8 using per-dimension
|
|
21
|
-
/// min/max calibration
|
|
23
|
+
/// min/max calibration for a compact in-memory candidate index.
|
|
22
24
|
Scalar(ScalarQuantizationConfig),
|
|
23
25
|
/// Binary quantization: maps each f32 dimension to a single bit.
|
|
24
|
-
///
|
|
26
|
+
/// Smallest in-memory candidate index. Best for high-dimensional normalized embeddings.
|
|
25
27
|
Binary(BinaryQuantizationConfig),
|
|
26
28
|
/// Product quantization: splits vector into sub-vectors and quantizes each
|
|
27
29
|
/// to a centroid index. Highest compression for large datasets.
|
|
@@ -31,14 +33,14 @@ pub enum QuantizationConfig {
|
|
|
31
33
|
#[derive(Clone, Debug, PartialEq)]
|
|
32
34
|
pub struct ScalarQuantizationConfig {
|
|
33
35
|
/// Number of top candidates from quantized search to rescore with float32.
|
|
34
|
-
/// Default:
|
|
36
|
+
/// Default: 10x top_k.
|
|
35
37
|
pub rescore_multiplier: usize,
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
impl Default for ScalarQuantizationConfig {
|
|
39
41
|
fn default() -> Self {
|
|
40
42
|
Self {
|
|
41
|
-
rescore_multiplier:
|
|
43
|
+
rescore_multiplier: 10,
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
}
|
|
@@ -46,7 +48,7 @@ impl Default for ScalarQuantizationConfig {
|
|
|
46
48
|
#[derive(Clone, Debug, PartialEq)]
|
|
47
49
|
pub struct BinaryQuantizationConfig {
|
|
48
50
|
/// Number of top candidates from Hamming search to rescore with float32.
|
|
49
|
-
/// Default: 10x top_k
|
|
51
|
+
/// Default: 10x top_k.
|
|
50
52
|
pub rescore_multiplier: usize,
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -69,6 +71,7 @@ pub struct ProductQuantizationConfig {
|
|
|
69
71
|
/// Number of k-means training iterations.
|
|
70
72
|
pub training_iterations: usize,
|
|
71
73
|
/// Number of top candidates from PQ search to rescore with float32.
|
|
74
|
+
/// Default: 10x top_k.
|
|
72
75
|
pub rescore_multiplier: usize,
|
|
73
76
|
}
|
|
74
77
|
|
|
@@ -83,6 +86,53 @@ impl Default for ProductQuantizationConfig {
|
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
/// Choose a valid default PQ sub-vector count for a database dimension.
|
|
90
|
+
///
|
|
91
|
+
/// Prefer the historical default of 16 when possible, then fall back to smaller
|
|
92
|
+
/// common divisors so dimensions such as 100, 146, and 200 do not require an
|
|
93
|
+
/// explicit `num_sub_vectors`.
|
|
94
|
+
pub fn default_product_num_sub_vectors(dimension: usize) -> usize {
|
|
95
|
+
[16, 12, 10, 8, 6, 4, 3, 2, 1]
|
|
96
|
+
.into_iter()
|
|
97
|
+
.find(|candidate| dimension % candidate == 0)
|
|
98
|
+
.unwrap_or(1)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// List every valid PQ sub-vector count for a database dimension.
|
|
102
|
+
pub fn valid_product_num_sub_vectors(dimension: usize) -> Vec<usize> {
|
|
103
|
+
if dimension == 0 {
|
|
104
|
+
return Vec::new();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
(1..=dimension)
|
|
108
|
+
.filter(|candidate| dimension % candidate == 0)
|
|
109
|
+
.collect()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Validate quantization settings before an index build can panic.
|
|
113
|
+
pub fn validate_quantization_config(config: &QuantizationConfig, dimension: usize) -> Result<()> {
|
|
114
|
+
if let QuantizationConfig::Product(cfg) = config {
|
|
115
|
+
if cfg.num_sub_vectors == 0 {
|
|
116
|
+
return Err(VectLiteError::InvalidFormat(
|
|
117
|
+
"num_sub_vectors must be greater than 0".to_owned(),
|
|
118
|
+
));
|
|
119
|
+
}
|
|
120
|
+
if dimension % cfg.num_sub_vectors != 0 {
|
|
121
|
+
return Err(VectLiteError::InvalidFormat(format!(
|
|
122
|
+
"dimension ({dimension}) must be divisible by num_sub_vectors ({})",
|
|
123
|
+
cfg.num_sub_vectors
|
|
124
|
+
)));
|
|
125
|
+
}
|
|
126
|
+
if cfg.num_centroids == 0 || cfg.num_centroids > 256 {
|
|
127
|
+
return Err(VectLiteError::InvalidFormat(
|
|
128
|
+
"num_centroids must be between 1 and 256".to_owned(),
|
|
129
|
+
));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Ok(())
|
|
134
|
+
}
|
|
135
|
+
|
|
86
136
|
// ---------------------------------------------------------------------------
|
|
87
137
|
// Scalar Quantization
|
|
88
138
|
// ---------------------------------------------------------------------------
|
|
@@ -173,18 +223,27 @@ impl ScalarQuantizer {
|
|
|
173
223
|
.collect()
|
|
174
224
|
}
|
|
175
225
|
|
|
176
|
-
/// Compute approximate cosine
|
|
226
|
+
/// Compute approximate cosine similarity between the query and all stored vectors.
|
|
177
227
|
/// Returns indices sorted by approximate similarity (best first).
|
|
178
228
|
pub fn search(&self, query: &[f32], top_k: usize) -> Vec<(usize, f32)> {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
229
|
+
self.search_with_metric(query, top_k, DistanceMetric::Cosine)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/// Compute approximate metric scores between the query and all stored vectors.
|
|
233
|
+
/// Returns indices sorted by approximate score (best first).
|
|
234
|
+
pub fn search_with_metric(
|
|
235
|
+
&self,
|
|
236
|
+
query: &[f32],
|
|
237
|
+
top_k: usize,
|
|
238
|
+
metric: DistanceMetric,
|
|
239
|
+
) -> Vec<(usize, f32)> {
|
|
240
|
+
assert_eq!(query.len(), self.dimension);
|
|
241
|
+
let rescore_count = rescore_count(top_k, self.config.rescore_multiplier, self.count);
|
|
183
242
|
let mut scores: Vec<(usize, f32)> = (0..self.count)
|
|
184
243
|
.map(|idx| {
|
|
185
244
|
let offset = idx * self.dimension;
|
|
186
245
|
let code_slice = &self.codes[offset..offset + self.dimension];
|
|
187
|
-
let sim =
|
|
246
|
+
let sim = self.approximate_score(query, code_slice, metric);
|
|
188
247
|
(idx, sim)
|
|
189
248
|
})
|
|
190
249
|
.collect();
|
|
@@ -195,6 +254,71 @@ impl ScalarQuantizer {
|
|
|
195
254
|
scores
|
|
196
255
|
}
|
|
197
256
|
|
|
257
|
+
fn approximate_score(&self, query: &[f32], code_slice: &[u8], metric: DistanceMetric) -> f32 {
|
|
258
|
+
match metric {
|
|
259
|
+
DistanceMetric::Cosine => {
|
|
260
|
+
let mut dot = 0.0_f32;
|
|
261
|
+
let mut query_norm = 0.0_f32;
|
|
262
|
+
let mut vector_norm = 0.0_f32;
|
|
263
|
+
|
|
264
|
+
for (((&query_value, &code), &min), &scale) in query
|
|
265
|
+
.iter()
|
|
266
|
+
.zip(code_slice.iter())
|
|
267
|
+
.zip(self.mins.iter())
|
|
268
|
+
.zip(self.scales.iter())
|
|
269
|
+
{
|
|
270
|
+
let value = dequantize_scalar(code, min, scale);
|
|
271
|
+
dot += query_value * value;
|
|
272
|
+
query_norm += query_value * query_value;
|
|
273
|
+
vector_norm += value * value;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if query_norm == 0.0 || vector_norm == 0.0 {
|
|
277
|
+
0.0
|
|
278
|
+
} else {
|
|
279
|
+
dot / (query_norm.sqrt() * vector_norm.sqrt())
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
DistanceMetric::Euclidean => {
|
|
283
|
+
let mut sum = 0.0_f32;
|
|
284
|
+
for (((&query_value, &code), &min), &scale) in query
|
|
285
|
+
.iter()
|
|
286
|
+
.zip(code_slice.iter())
|
|
287
|
+
.zip(self.mins.iter())
|
|
288
|
+
.zip(self.scales.iter())
|
|
289
|
+
{
|
|
290
|
+
let delta = query_value - dequantize_scalar(code, min, scale);
|
|
291
|
+
sum += delta * delta;
|
|
292
|
+
}
|
|
293
|
+
-sum.sqrt()
|
|
294
|
+
}
|
|
295
|
+
DistanceMetric::DotProduct => {
|
|
296
|
+
let mut dot = 0.0_f32;
|
|
297
|
+
for (((&query_value, &code), &min), &scale) in query
|
|
298
|
+
.iter()
|
|
299
|
+
.zip(code_slice.iter())
|
|
300
|
+
.zip(self.mins.iter())
|
|
301
|
+
.zip(self.scales.iter())
|
|
302
|
+
{
|
|
303
|
+
dot += query_value * dequantize_scalar(code, min, scale);
|
|
304
|
+
}
|
|
305
|
+
dot
|
|
306
|
+
}
|
|
307
|
+
DistanceMetric::Manhattan => {
|
|
308
|
+
let mut sum = 0.0_f32;
|
|
309
|
+
for (((&query_value, &code), &min), &scale) in query
|
|
310
|
+
.iter()
|
|
311
|
+
.zip(code_slice.iter())
|
|
312
|
+
.zip(self.mins.iter())
|
|
313
|
+
.zip(self.scales.iter())
|
|
314
|
+
{
|
|
315
|
+
sum += (query_value - dequantize_scalar(code, min, scale)).abs();
|
|
316
|
+
}
|
|
317
|
+
-sum
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
198
322
|
/// Rebuild codes from training vectors (used after deserialization with new vectors).
|
|
199
323
|
pub fn rebuild_codes(&mut self, vectors: &[&[f32]]) {
|
|
200
324
|
self.codes.clear();
|
|
@@ -311,9 +435,7 @@ impl BinaryQuantizer {
|
|
|
311
435
|
/// Search using Hamming distance. Returns candidate indices sorted by
|
|
312
436
|
/// Hamming similarity (fewest differing bits first).
|
|
313
437
|
pub fn search(&self, query: &[f32], top_k: usize) -> Vec<(usize, u32)> {
|
|
314
|
-
let rescore_count = (top_k
|
|
315
|
-
.max(100)
|
|
316
|
-
.min(self.count);
|
|
438
|
+
let rescore_count = rescore_count(top_k, self.config.rescore_multiplier, self.count);
|
|
317
439
|
let query_binary = self.binarize_query(query);
|
|
318
440
|
let mut distances: Vec<(usize, u32)> = (0..self.count)
|
|
319
441
|
.map(|idx| {
|
|
@@ -476,9 +598,7 @@ impl ProductQuantizer {
|
|
|
476
598
|
/// Search using asymmetric distance computation (ADC).
|
|
477
599
|
/// Returns candidate indices sorted by approximate L2 distance.
|
|
478
600
|
pub fn search(&self, query: &[f32], top_k: usize) -> Vec<(usize, f32)> {
|
|
479
|
-
let rescore_count = (top_k
|
|
480
|
-
.max(100)
|
|
481
|
-
.min(self.count);
|
|
601
|
+
let rescore_count = rescore_count(top_k, self.config.rescore_multiplier, self.count);
|
|
482
602
|
let distance_table = self.compute_distance_table(query);
|
|
483
603
|
|
|
484
604
|
let mut distances: Vec<(usize, f32)> = (0..self.count)
|
|
@@ -542,6 +662,20 @@ impl ProductQuantizer {
|
|
|
542
662
|
let num_centroids = read_usize(reader)?;
|
|
543
663
|
let training_iterations = read_usize(reader)?;
|
|
544
664
|
let rescore_multiplier = read_usize(reader)?;
|
|
665
|
+
if num_sub_vectors == 0 || dimension % num_sub_vectors != 0 {
|
|
666
|
+
return Err(Error::new(
|
|
667
|
+
ErrorKind::InvalidData,
|
|
668
|
+
format!(
|
|
669
|
+
"dimension ({dimension}) must be divisible by num_sub_vectors ({num_sub_vectors})"
|
|
670
|
+
),
|
|
671
|
+
));
|
|
672
|
+
}
|
|
673
|
+
if num_centroids == 0 || num_centroids > 256 {
|
|
674
|
+
return Err(Error::new(
|
|
675
|
+
ErrorKind::InvalidData,
|
|
676
|
+
"num_centroids must be between 1 and 256",
|
|
677
|
+
));
|
|
678
|
+
}
|
|
545
679
|
let sub_dimension = dimension / num_sub_vectors;
|
|
546
680
|
|
|
547
681
|
// Read codebooks
|
|
@@ -586,7 +720,7 @@ impl ProductQuantizer {
|
|
|
586
720
|
#[derive(Clone, Debug, PartialEq)]
|
|
587
721
|
pub struct TwoBitQuantizationConfig {
|
|
588
722
|
/// Number of top candidate docs from quantized search to rescore with
|
|
589
|
-
/// exact float32 MaxSim. Default: 4x top_k
|
|
723
|
+
/// exact float32 MaxSim. Default: 4x top_k.
|
|
590
724
|
pub rescore_multiplier: usize,
|
|
591
725
|
}
|
|
592
726
|
|
|
@@ -619,11 +753,7 @@ pub struct TwoBitQuantizer {
|
|
|
619
753
|
|
|
620
754
|
impl TwoBitQuantizer {
|
|
621
755
|
/// Train a 2-bit quantizer by computing per-dimension quartiles.
|
|
622
|
-
pub fn train(
|
|
623
|
-
vectors: &[&[f32]],
|
|
624
|
-
dimension: usize,
|
|
625
|
-
config: TwoBitQuantizationConfig,
|
|
626
|
-
) -> Self {
|
|
756
|
+
pub fn train(vectors: &[&[f32]], dimension: usize, config: TwoBitQuantizationConfig) -> Self {
|
|
627
757
|
assert!(!vectors.is_empty(), "need at least one vector to train");
|
|
628
758
|
|
|
629
759
|
// Collect values per dimension and compute quartile boundaries
|
|
@@ -672,9 +802,7 @@ impl TwoBitQuantizer {
|
|
|
672
802
|
/// Search for top-k candidates using approximate 2-bit dot products.
|
|
673
803
|
/// Returns (index, approx_score) pairs sorted best-first.
|
|
674
804
|
pub fn search(&self, query: &[f32], top_k: usize) -> Vec<(usize, i32)> {
|
|
675
|
-
let rescore_count = (top_k
|
|
676
|
-
.max(50)
|
|
677
|
-
.min(self.count);
|
|
805
|
+
let rescore_count = rescore_count(top_k, self.config.rescore_multiplier, self.count);
|
|
678
806
|
let query_codes = self.quantize_vector(query);
|
|
679
807
|
|
|
680
808
|
let mut scores: Vec<(usize, i32)> = (0..self.count)
|
|
@@ -691,8 +819,11 @@ impl TwoBitQuantizer {
|
|
|
691
819
|
self.codes.clear();
|
|
692
820
|
self.codes.reserve(vectors.len() * self.bytes_per_vector);
|
|
693
821
|
for vector in vectors {
|
|
694
|
-
self.codes
|
|
695
|
-
|
|
822
|
+
self.codes.extend_from_slice(&quantize_two_bit(
|
|
823
|
+
vector,
|
|
824
|
+
&self.boundaries,
|
|
825
|
+
self.bytes_per_vector,
|
|
826
|
+
));
|
|
696
827
|
}
|
|
697
828
|
self.count = vectors.len();
|
|
698
829
|
}
|
|
@@ -824,9 +955,11 @@ impl MultiVectorQuantizedIndex {
|
|
|
824
955
|
|
|
825
956
|
/// Search: returns candidate document indices sorted by approximate MaxSim.
|
|
826
957
|
pub fn search(&self, query_tokens: &[&[f32]], top_k: usize) -> Vec<usize> {
|
|
827
|
-
let rescore_count = (
|
|
828
|
-
|
|
829
|
-
|
|
958
|
+
let rescore_count = rescore_count(
|
|
959
|
+
top_k,
|
|
960
|
+
self.quantizer.config.rescore_multiplier,
|
|
961
|
+
self.doc_ranges.len(),
|
|
962
|
+
);
|
|
830
963
|
if query_tokens.is_empty() || self.doc_ranges.is_empty() {
|
|
831
964
|
return Vec::new();
|
|
832
965
|
}
|
|
@@ -846,10 +979,7 @@ impl MultiVectorQuantizedIndex {
|
|
|
846
979
|
}
|
|
847
980
|
|
|
848
981
|
/// Rebuild from document token vectors (after loading parameters from disk).
|
|
849
|
-
pub fn rebuild(
|
|
850
|
-
&mut self,
|
|
851
|
-
doc_token_vectors: &[&[Vec<f32>]],
|
|
852
|
-
) {
|
|
982
|
+
pub fn rebuild(&mut self, doc_token_vectors: &[&[Vec<f32>]]) {
|
|
853
983
|
let all_tokens: Vec<&[f32]> = doc_token_vectors
|
|
854
984
|
.iter()
|
|
855
985
|
.flat_map(|tokens| tokens.iter().map(|v| v.as_slice()))
|
|
@@ -936,10 +1066,23 @@ impl QuantizedIndex {
|
|
|
936
1066
|
/// Search the quantized index. Returns candidate indices sorted by
|
|
937
1067
|
/// approximate similarity (best first), to be rescored with exact vectors.
|
|
938
1068
|
pub fn search_candidates(&self, query: &[f32], top_k: usize) -> Vec<usize> {
|
|
1069
|
+
self.search_candidates_with_metric(query, top_k, DistanceMetric::Cosine)
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/// Search the quantized index with the database metric.
|
|
1073
|
+
/// Returns candidate indices sorted by approximate score (best first).
|
|
1074
|
+
pub fn search_candidates_with_metric(
|
|
1075
|
+
&self,
|
|
1076
|
+
query: &[f32],
|
|
1077
|
+
top_k: usize,
|
|
1078
|
+
metric: DistanceMetric,
|
|
1079
|
+
) -> Vec<usize> {
|
|
939
1080
|
match self {
|
|
940
|
-
QuantizedIndex::Scalar(q) =>
|
|
941
|
-
|
|
942
|
-
|
|
1081
|
+
QuantizedIndex::Scalar(q) => q
|
|
1082
|
+
.search_with_metric(query, top_k, metric)
|
|
1083
|
+
.into_iter()
|
|
1084
|
+
.map(|(i, _)| i)
|
|
1085
|
+
.collect(),
|
|
943
1086
|
QuantizedIndex::Binary(q) => {
|
|
944
1087
|
q.search(query, top_k).into_iter().map(|(i, _)| i).collect()
|
|
945
1088
|
}
|
|
@@ -1020,6 +1163,14 @@ impl QuantizedIndex {
|
|
|
1020
1163
|
// Internal helper functions
|
|
1021
1164
|
// ---------------------------------------------------------------------------
|
|
1022
1165
|
|
|
1166
|
+
#[inline]
|
|
1167
|
+
fn rescore_count(top_k: usize, rescore_multiplier: usize, count: usize) -> usize {
|
|
1168
|
+
top_k
|
|
1169
|
+
.max(1)
|
|
1170
|
+
.saturating_mul(rescore_multiplier.max(1))
|
|
1171
|
+
.min(count)
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1023
1174
|
/// Quantize a single f32 value to u8 using the given min and scale.
|
|
1024
1175
|
#[inline]
|
|
1025
1176
|
fn quantize_scalar(val: f32, min: f32, scale: f32) -> u8 {
|
|
@@ -1030,15 +1181,13 @@ fn quantize_scalar(val: f32, min: f32, scale: f32) -> u8 {
|
|
|
1030
1181
|
}
|
|
1031
1182
|
}
|
|
1032
1183
|
|
|
1033
|
-
/// Approximate dot product between two u8-quantized vectors.
|
|
1034
|
-
/// Higher value = more similar (analogous to cosine similarity for normalized vectors).
|
|
1035
1184
|
#[inline]
|
|
1036
|
-
fn
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1185
|
+
fn dequantize_scalar(code: u8, min: f32, scale: f32) -> f32 {
|
|
1186
|
+
if scale == 0.0 {
|
|
1187
|
+
min
|
|
1188
|
+
} else {
|
|
1189
|
+
min + (code as f32 / scale)
|
|
1040
1190
|
}
|
|
1041
|
-
sum as f32
|
|
1042
1191
|
}
|
|
1043
1192
|
|
|
1044
1193
|
/// Convert a float vector to a binary representation (1 bit per dimension).
|
|
@@ -1258,6 +1407,39 @@ mod tests {
|
|
|
1258
1407
|
assert_eq!(results[0].1, 0); // Hamming distance 0
|
|
1259
1408
|
}
|
|
1260
1409
|
|
|
1410
|
+
#[test]
|
|
1411
|
+
fn rescore_multiplier_controls_candidate_count_without_hidden_floor() {
|
|
1412
|
+
let vectors = random_vectors(200, 64, 7);
|
|
1413
|
+
let refs: Vec<&[f32]> = vectors.iter().map(Vec::as_slice).collect();
|
|
1414
|
+
|
|
1415
|
+
let scalar = ScalarQuantizer::train(
|
|
1416
|
+
&refs,
|
|
1417
|
+
64,
|
|
1418
|
+
ScalarQuantizationConfig {
|
|
1419
|
+
rescore_multiplier: 1,
|
|
1420
|
+
},
|
|
1421
|
+
);
|
|
1422
|
+
assert_eq!(scalar.search(&vectors[0], 10).len(), 10);
|
|
1423
|
+
|
|
1424
|
+
let scalar = ScalarQuantizer::train(
|
|
1425
|
+
&refs,
|
|
1426
|
+
64,
|
|
1427
|
+
ScalarQuantizationConfig {
|
|
1428
|
+
rescore_multiplier: 4,
|
|
1429
|
+
},
|
|
1430
|
+
);
|
|
1431
|
+
assert_eq!(scalar.search(&vectors[0], 10).len(), 40);
|
|
1432
|
+
|
|
1433
|
+
let mut binary = BinaryQuantizer::new(
|
|
1434
|
+
64,
|
|
1435
|
+
BinaryQuantizationConfig {
|
|
1436
|
+
rescore_multiplier: 2,
|
|
1437
|
+
},
|
|
1438
|
+
);
|
|
1439
|
+
binary.add_vectors(&refs);
|
|
1440
|
+
assert_eq!(binary.search(&vectors[0], 10).len(), 20);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1261
1443
|
#[test]
|
|
1262
1444
|
fn product_quantization_basic() {
|
|
1263
1445
|
let vectors = random_vectors(200, 128, 42);
|
|
@@ -1520,7 +1702,10 @@ mod tests {
|
|
|
1520
1702
|
for (a, b) in original.boundaries.iter().zip(restored.boundaries.iter()) {
|
|
1521
1703
|
assert!((a - b).abs() < 1e-6);
|
|
1522
1704
|
}
|
|
1523
|
-
assert_eq!(
|
|
1705
|
+
assert_eq!(
|
|
1706
|
+
original.config.rescore_multiplier,
|
|
1707
|
+
restored.config.rescore_multiplier
|
|
1708
|
+
);
|
|
1524
1709
|
}
|
|
1525
1710
|
|
|
1526
1711
|
#[test]
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|