Skip to content

Commit ab1de59

Browse files
authored
Fixing quantization interval initialization for optimized sq (#14374)
* Fixing quantization interval initialization for optimized sq * adding changes along with original binary quantization change * adjusting test
1 parent 45ef977 commit ab1de59

File tree

3 files changed

+67
-6
lines changed

3 files changed

+67
-6
lines changed

lucene/CHANGES.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ New Features
8686
These queries allow for the vector search entry points to be initialized via a `seed` query. This follows
8787
the research provided via https://arxiv.org/abs/2307.16779. (Sean MacAvaney, Ben Trent).
8888

89+
* GITHUB#14078, GITHUB#14374: Adds two new binary quantized vector codecs `Lucene102HnswBinaryQuantizedVectorsFormat`
90+
and `Lucene102BinaryQuantizedVectorsFormat`. These new formats allow a ~32x from raw float32 vectors while
91+
achieving high recall (with oversampling and reranking vectors) for typical modern knn vector search tasks.
92+
(Mayya Sharipova, Ben Trent, Tom Veasey, John Wagster, Chris Hegarty)
93+
8994
* GITHUB#13974,GITHUB#14276: Introducing DocValuesMultiRangeQuery.SortedSetStabbingBuilder into sandbox.
9095
(Mikhail Khludnev)
9196

lucene/core/src/java/org/apache/lucene/util/quantization/OptimizedScalarQuantizer.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,9 @@ public QuantizationResult[] multiScalarQuantize(
149149
// Linearly scale the interval to the standard deviation of the vector, ensuring we are within
150150
// the min/max bounds
151151
intervalScratch[0] =
152-
(float) clamp((MINIMUM_MSE_GRID[bits[i] - 1][0] + vecMean) * vecStd, min, max);
152+
(float) clamp(MINIMUM_MSE_GRID[bits[i] - 1][0] * vecStd + vecMean, min, max);
153153
intervalScratch[1] =
154-
(float) clamp((MINIMUM_MSE_GRID[bits[i] - 1][1] + vecMean) * vecStd, min, max);
154+
(float) clamp(MINIMUM_MSE_GRID[bits[i] - 1][1] * vecStd + vecMean, min, max);
155155
optimizeIntervals(intervalScratch, vector, norm2, points);
156156
float nSteps = ((1 << bits[i]) - 1);
157157
float a = intervalScratch[0];
@@ -214,10 +214,8 @@ public QuantizationResult scalarQuantize(
214214
double vecStd = Math.sqrt(vecVar);
215215
// Linearly scale the interval to the standard deviation of the vector, ensuring we are within
216216
// the min/max bounds
217-
intervalScratch[0] =
218-
(float) clamp((MINIMUM_MSE_GRID[bits - 1][0] + vecMean) * vecStd, min, max);
219-
intervalScratch[1] =
220-
(float) clamp((MINIMUM_MSE_GRID[bits - 1][1] + vecMean) * vecStd, min, max);
217+
intervalScratch[0] = (float) clamp(MINIMUM_MSE_GRID[bits - 1][0] * vecStd + vecMean, min, max);
218+
intervalScratch[1] = (float) clamp(MINIMUM_MSE_GRID[bits - 1][1] * vecStd + vecMean, min, max);
221219
optimizeIntervals(intervalScratch, vector, norm2, points);
222220
float nSteps = ((1 << bits) - 1);
223221
// Now we have the optimized intervals, quantize the vector

lucene/core/src/test/org/apache/lucene/util/quantization/TestOptimizedScalarQuantizer.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,64 @@
2727
public class TestOptimizedScalarQuantizer extends LuceneTestCase {
2828
static final byte[] ALL_BITS = new byte[] {1, 2, 3, 4, 5, 6, 7, 8};
2929

30+
static float[] deQuantize(byte[] quantized, byte bits, float[] interval, float[] centroid) {
31+
float[] dequantized = new float[quantized.length];
32+
float a = interval[0];
33+
float b = interval[1];
34+
int nSteps = (1 << bits) - 1;
35+
double step = (b - a) / nSteps;
36+
for (int h = 0; h < quantized.length; h++) {
37+
double xi = (double) (quantized[h] & 0xFF) * step + a;
38+
dequantized[h] = (float) (xi + centroid[h]);
39+
}
40+
return dequantized;
41+
}
42+
43+
public void testQuantizationQuality() {
44+
int dims = 16;
45+
int numVectors = 32;
46+
float[][] vectors = new float[numVectors][];
47+
float[] centroid = new float[dims];
48+
for (int i = 0; i < numVectors; ++i) {
49+
vectors[i] = new float[dims];
50+
for (int j = 0; j < dims; ++j) {
51+
vectors[i][j] = randomFloat();
52+
centroid[j] += vectors[i][j];
53+
}
54+
}
55+
for (int j = 0; j < dims; ++j) {
56+
centroid[j] /= numVectors;
57+
}
58+
// similarity doesn't matter for this test
59+
OptimizedScalarQuantizer osq =
60+
new OptimizedScalarQuantizer(VectorSimilarityFunction.DOT_PRODUCT);
61+
float[] scratch = new float[dims];
62+
for (byte bit : ALL_BITS) {
63+
float eps = (1f / (float) (1 << (bit)));
64+
byte[] destination = new byte[dims];
65+
for (int i = 0; i < numVectors; ++i) {
66+
System.arraycopy(vectors[i], 0, scratch, 0, dims);
67+
OptimizedScalarQuantizer.QuantizationResult result =
68+
osq.scalarQuantize(scratch, destination, bit, centroid);
69+
assertValidResults(result);
70+
assertValidQuantizedRange(destination, bit);
71+
72+
float[] dequantized =
73+
deQuantize(
74+
destination,
75+
bit,
76+
new float[] {result.lowerInterval(), result.upperInterval()},
77+
centroid);
78+
float mae = 0;
79+
for (int k = 0; k < dims; ++k) {
80+
mae += Math.abs(dequantized[k] - vectors[i][k]);
81+
}
82+
mae /= dims;
83+
assertTrue("bits: " + bit + " mae: " + mae + " > eps: " + eps, mae <= eps);
84+
}
85+
}
86+
}
87+
3088
public void testAbusiveEdgeCases() {
3189
// large zero array
3290
for (VectorSimilarityFunction vectorSimilarityFunction : VectorSimilarityFunction.values()) {

0 commit comments

Comments
 (0)