Skip to content

Commit 04db74e

Browse files
Merge branch 'main' into pkar/resolve-index-force-reconn
2 parents 782c355 + bc43cf5 commit 04db74e

File tree

772 files changed

+12663
-7283
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

772 files changed

+12663
-7283
lines changed

.buildkite/hooks/pre-command

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
#!/bin/bash
22

3+
if [[ "${USE_IPV6_TESTING:-}" == "true" ]]; then
4+
# Ensure that `localhost` resolves to only `::1`
5+
sudo bash -c 'echo "::1 localhost" | cat - /etc/hosts > temp_file && mv temp_file /etc/hosts'
6+
sudo sed -i '/^127\.0\.0\.1/d' /etc/hosts
7+
sudo resolvectl flush-caches || true
8+
fi
9+
310
# On some distros, this directory ends up not readable by the `elasticsearch` user that gets created during tests
411
# This fixes that
512
chmod 755 ~
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
steps:
2+
- group: ipv6-tests
3+
steps:
4+
- label: "{{matrix.GRADLE_TASK}} / ipv6-tests"
5+
command: .ci/scripts/run-gradle.sh --continue -Djava.net.preferIPv6Addresses=true -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints {{matrix.GRADLE_TASK}}
6+
timeout_in_minutes: 300
7+
agents:
8+
provider: aws
9+
imagePrefix: elasticsearch-aws-ubuntu-2404-nvidia
10+
instanceType: m7a.8xlarge
11+
diskSizeGb: 150
12+
diskType: gp3
13+
diskName: /dev/sda1
14+
buildDirectory: /dev/shm/bk
15+
ipv6Only: true
16+
env:
17+
GRADLE_TASK: "{{matrix.GRADLE_TASK}}"
18+
JAVA_TOOL_OPTIONS: "-Djava.net.preferIPv6Addresses=true"
19+
_JAVA_OPTIONS: "-Djava.net.preferIPv6Addresses=true"
20+
USE_IPV6_TESTING: "true"
21+
matrix:
22+
setup:
23+
GRADLE_TASK:
24+
- checkPart1
25+
- checkPart2
26+
- checkPart3
27+
- checkPart4
28+
- checkPart5
29+
- checkPart6
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
config:
2+
allow-labels: test-ipv6
3+
skip-labels:
4+
- ">test-mute"
5+
steps:
6+
- group: ipv6-tests
7+
steps:
8+
- label: "{{matrix.GRADLE_TASK}} / ipv6-tests"
9+
key: "ipv6-tests"
10+
command: .ci/scripts/run-gradle.sh --continue -Djava.net.preferIPv6Addresses=true -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints {{matrix.GRADLE_TASK}}
11+
timeout_in_minutes: 300
12+
agents:
13+
provider: aws
14+
imagePrefix: elasticsearch-aws-ubuntu-2404-nvidia
15+
instanceType: m7a.8xlarge
16+
diskSizeGb: 150
17+
diskType: gp3
18+
diskName: /dev/sda1
19+
buildDirectory: /dev/shm/bk
20+
ipv6Only: true
21+
env:
22+
GRADLE_TASK: "{{matrix.GRADLE_TASK}}"
23+
JAVA_TOOL_OPTIONS: "-Djava.net.preferIPv6Addresses=true"
24+
_JAVA_OPTIONS: "-Djava.net.preferIPv6Addresses=true"
25+
USE_IPV6_TESTING: "true"
26+
matrix:
27+
setup:
28+
GRADLE_TASK:
29+
- checkPart1
30+
- checkPart2
31+
- checkPart3
32+
- checkPart4
33+
- checkPart5
34+
- checkPart6

benchmarks/src/main/java/org/elasticsearch/benchmark/vector/scorer/BenchmarkUtils.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import org.apache.lucene.backward_codecs.lucene99.OffHeapQuantizedByteVectorValues;
1313
import org.apache.lucene.codecs.hnsw.FlatVectorScorerUtil;
14+
import org.apache.lucene.codecs.lucene104.Lucene104ScalarQuantizedVectorScorer;
1415
import org.apache.lucene.codecs.lucene95.OffHeapByteVectorValues;
1516
import org.apache.lucene.codecs.lucene95.OffHeapFloatVectorValues;
1617
import org.apache.lucene.codecs.lucene99.Lucene99ScalarQuantizedVectorScorer;
@@ -134,6 +135,21 @@ static RandomVectorScorer luceneScorer(QuantizedByteVectorValues values, VectorS
134135
return new Lucene99ScalarQuantizedVectorScorer(null).getRandomVectorScorer(sim, values, queryVec);
135136
}
136137

138+
static RandomVectorScorerSupplier lucene104ScoreSupplier(
139+
org.apache.lucene.codecs.lucene104.QuantizedByteVectorValues values,
140+
VectorSimilarityFunction sim
141+
) throws IOException {
142+
return new Lucene104ScalarQuantizedVectorScorer(null).getRandomVectorScorerSupplier(sim, values);
143+
}
144+
145+
static RandomVectorScorer lucene104Scorer(
146+
org.apache.lucene.codecs.lucene104.QuantizedByteVectorValues values,
147+
VectorSimilarityFunction sim,
148+
float[] queryVec
149+
) throws IOException {
150+
return new Lucene104ScalarQuantizedVectorScorer(null).getRandomVectorScorer(sim, values, queryVec);
151+
}
152+
137153
static RuntimeException rethrow(Throwable t) {
138154
if (t instanceof Error err) {
139155
throw err;
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.benchmark.vector.scorer;
11+
12+
import org.apache.lucene.codecs.lucene104.Lucene104ScalarQuantizedVectorsFormat;
13+
import org.apache.lucene.codecs.lucene104.QuantizedByteVectorValues;
14+
import org.apache.lucene.index.VectorSimilarityFunction;
15+
import org.apache.lucene.search.VectorScorer;
16+
import org.apache.lucene.util.VectorUtil;
17+
import org.apache.lucene.util.hnsw.RandomVectorScorer;
18+
import org.apache.lucene.util.hnsw.UpdateableRandomVectorScorer;
19+
import org.apache.lucene.util.quantization.OptimizedScalarQuantizer;
20+
21+
import java.io.IOException;
22+
import java.util.Arrays;
23+
import java.util.concurrent.ThreadLocalRandom;
24+
25+
import static org.elasticsearch.benchmark.vector.scorer.ScalarOperations.applyI4Corrections;
26+
import static org.elasticsearch.benchmark.vector.scorer.ScalarOperations.dotProductI4SinglePacked;
27+
import static org.elasticsearch.simdvec.internal.vectorization.VectorScorerTestUtils.unpackNibbles;
28+
29+
public class Int4BenchmarkUtils {
30+
31+
/**
32+
* In-memory implementation of {@link QuantizedByteVectorValues} for int4 (PACKED_NIBBLE) benchmarks.
33+
* Stores pre-quantized packed nibble vectors with synthetic corrective terms.
34+
*/
35+
static class InMemoryInt4QuantizedByteVectorValues extends QuantizedByteVectorValues {
36+
37+
private final int dims;
38+
private final byte[][] packedVectors;
39+
private final OptimizedScalarQuantizer.QuantizationResult[] correctiveTerms;
40+
private final float[] centroid;
41+
private final float centroidDP;
42+
private final OptimizedScalarQuantizer quantizer;
43+
44+
InMemoryInt4QuantizedByteVectorValues(
45+
int dims,
46+
byte[][] packedVectors,
47+
OptimizedScalarQuantizer.QuantizationResult[] correctiveTerms,
48+
float[] centroid,
49+
float centroidDP
50+
) {
51+
this.dims = dims;
52+
this.packedVectors = packedVectors;
53+
this.correctiveTerms = correctiveTerms;
54+
this.centroid = centroid;
55+
this.centroidDP = centroidDP;
56+
this.quantizer = new OptimizedScalarQuantizer(VectorSimilarityFunction.DOT_PRODUCT);
57+
}
58+
59+
@Override
60+
public int dimension() {
61+
return dims;
62+
}
63+
64+
@Override
65+
public int size() {
66+
return packedVectors.length;
67+
}
68+
69+
@Override
70+
public byte[] vectorValue(int ord) throws IOException {
71+
return packedVectors[ord];
72+
}
73+
74+
@Override
75+
public OptimizedScalarQuantizer.QuantizationResult getCorrectiveTerms(int vectorOrd) throws IOException {
76+
return correctiveTerms[vectorOrd];
77+
}
78+
79+
@Override
80+
public OptimizedScalarQuantizer getQuantizer() {
81+
return quantizer;
82+
}
83+
84+
@Override
85+
public Lucene104ScalarQuantizedVectorsFormat.ScalarEncoding getScalarEncoding() {
86+
return Lucene104ScalarQuantizedVectorsFormat.ScalarEncoding.PACKED_NIBBLE;
87+
}
88+
89+
@Override
90+
public float[] getCentroid() throws IOException {
91+
return centroid;
92+
}
93+
94+
@Override
95+
public float getCentroidDP() throws IOException {
96+
return centroidDP;
97+
}
98+
99+
@Override
100+
public VectorScorer scorer(float[] query) throws IOException {
101+
return null;
102+
}
103+
104+
@Override
105+
public InMemoryInt4QuantizedByteVectorValues copy() throws IOException {
106+
return new InMemoryInt4QuantizedByteVectorValues(dims, packedVectors, correctiveTerms, centroid, centroidDP);
107+
}
108+
}
109+
110+
private static class ScalarScorer implements UpdateableRandomVectorScorer {
111+
private final QuantizedByteVectorValues values;
112+
private final int dims;
113+
private final VectorSimilarityFunction similarityFunction;
114+
115+
private byte[] queryUnpacked;
116+
private OptimizedScalarQuantizer.QuantizationResult queryCorrections;
117+
118+
ScalarScorer(QuantizedByteVectorValues values, VectorSimilarityFunction similarityFunction) {
119+
this.values = values;
120+
this.dims = values.dimension();
121+
this.similarityFunction = similarityFunction;
122+
}
123+
124+
@Override
125+
public float score(int node) throws IOException {
126+
byte[] packed = values.vectorValue(node);
127+
int rawDot = dotProductI4SinglePacked(queryUnpacked, packed);
128+
var nodeCorrections = values.getCorrectiveTerms(node);
129+
return applyI4Corrections(rawDot, dims, nodeCorrections, queryCorrections, values.getCentroidDP(), similarityFunction);
130+
}
131+
132+
@Override
133+
public int maxOrd() {
134+
return values.size();
135+
}
136+
137+
@Override
138+
public void setScoringOrdinal(int node) throws IOException {
139+
byte[] packed = values.vectorValue(node);
140+
queryUnpacked = unpackNibbles(packed, dims);
141+
queryCorrections = values.getCorrectiveTerms(node);
142+
}
143+
}
144+
145+
static QuantizedByteVectorValues createI4QuantizedVectorValues(int dims, byte[][] packedVectors) {
146+
var random = ThreadLocalRandom.current();
147+
var correctiveTerms = new OptimizedScalarQuantizer.QuantizationResult[packedVectors.length];
148+
for (int i = 0; i < packedVectors.length; i++) {
149+
correctiveTerms[i] = new OptimizedScalarQuantizer.QuantizationResult(
150+
random.nextFloat(-1f, 1f),
151+
random.nextFloat(-1f, 1f),
152+
random.nextFloat(-1f, 1f),
153+
random.nextInt(0, dims * 15)
154+
);
155+
}
156+
float[] centroid = new float[dims];
157+
for (int i = 0; i < dims; i++) {
158+
centroid[i] = random.nextFloat();
159+
}
160+
float centroidDP = random.nextFloat();
161+
return new InMemoryInt4QuantizedByteVectorValues(dims, packedVectors, correctiveTerms, centroid, centroidDP);
162+
}
163+
164+
static UpdateableRandomVectorScorer createI4ScalarScorer(
165+
QuantizedByteVectorValues values,
166+
VectorSimilarityFunction similarityFunction
167+
) {
168+
return new ScalarScorer(values, similarityFunction);
169+
}
170+
171+
static RandomVectorScorer createI4ScalarQueryScorer(
172+
QuantizedByteVectorValues values,
173+
VectorSimilarityFunction similarityFunction,
174+
float[] queryVector
175+
) throws IOException {
176+
int dims = values.dimension();
177+
OptimizedScalarQuantizer quantizer = values.getQuantizer();
178+
float[] centroid = values.getCentroid();
179+
Lucene104ScalarQuantizedVectorsFormat.ScalarEncoding encoding = values.getScalarEncoding();
180+
181+
byte[] queryQuantized = new byte[encoding.getDiscreteDimensions(dims)];
182+
float[] queryCopy = Arrays.copyOf(queryVector, queryVector.length);
183+
if (similarityFunction == VectorSimilarityFunction.COSINE) {
184+
VectorUtil.l2normalize(queryCopy);
185+
}
186+
var queryCorrections = quantizer.scalarQuantize(queryCopy, queryQuantized, encoding.getQueryBits(), centroid);
187+
float centroidDP = values.getCentroidDP();
188+
189+
return new RandomVectorScorer.AbstractRandomVectorScorer(values) {
190+
@Override
191+
public float score(int node) throws IOException {
192+
byte[] packed = values.vectorValue(node);
193+
int rawDot = dotProductI4SinglePacked(queryQuantized, packed);
194+
var nodeCorrections = values.getCorrectiveTerms(node);
195+
return applyI4Corrections(rawDot, dims, nodeCorrections, queryCorrections, centroidDP, similarityFunction);
196+
}
197+
};
198+
}
199+
}

benchmarks/src/main/java/org/elasticsearch/benchmark/vector/scorer/ScalarOperations.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99

1010
package org.elasticsearch.benchmark.vector.scorer;
1111

12+
import org.apache.lucene.index.VectorSimilarityFunction;
13+
import org.apache.lucene.util.VectorUtil;
14+
import org.apache.lucene.util.quantization.OptimizedScalarQuantizer;
15+
16+
import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN;
17+
import static org.apache.lucene.index.VectorSimilarityFunction.MAXIMUM_INNER_PRODUCT;
18+
1219
/**
1320
* Basic scalar implementations of similarity operations.
1421
* <p>
@@ -17,6 +24,8 @@
1724
*/
1825
class ScalarOperations {
1926

27+
private static final float FOUR_BIT_SCALE = 1f / ((1 << 4) - 1);
28+
2029
static float cosine(byte[] a, byte[] b) {
2130
int sum = 0;
2231
int norm1 = 0;
@@ -65,4 +74,54 @@ static int squareDistance(byte[] a, byte[] b) {
6574
}
6675
return res;
6776
}
77+
78+
static int dotProductI4SinglePacked(byte[] unpacked, byte[] packed) {
79+
int total = 0;
80+
for (int i = 0; i < packed.length; i++) {
81+
byte packedByte = packed[i];
82+
byte unpacked1 = unpacked[i];
83+
byte unpacked2 = unpacked[i + packed.length];
84+
total += (packedByte & 0x0F) * unpacked2;
85+
total += ((packedByte & 0xFF) >> 4) * unpacked1;
86+
}
87+
return total;
88+
}
89+
90+
public static float applyI4Corrections(
91+
int rawDot,
92+
int dims,
93+
OptimizedScalarQuantizer.QuantizationResult nodeCorrections,
94+
OptimizedScalarQuantizer.QuantizationResult queryCorrections,
95+
float centroidDP,
96+
VectorSimilarityFunction similarityFunction
97+
) {
98+
float ax = nodeCorrections.lowerInterval();
99+
float lx = (nodeCorrections.upperInterval() - ax) * FOUR_BIT_SCALE;
100+
float ay = queryCorrections.lowerInterval();
101+
float ly = (queryCorrections.upperInterval() - ay) * FOUR_BIT_SCALE;
102+
float x1 = nodeCorrections.quantizedComponentSum();
103+
float y1 = queryCorrections.quantizedComponentSum();
104+
105+
float score = ax * ay * dims + ay * lx * x1 + ax * ly * y1 + lx * ly * rawDot;
106+
107+
// For euclidean, we need to invert the score and apply the additional correction, which is
108+
// assumed to be the squared l2norm of the centroid centered vectors.
109+
if (similarityFunction == EUCLIDEAN) {
110+
score = queryCorrections.additionalCorrection() + nodeCorrections.additionalCorrection() - 2 * score;
111+
// Ensure that 'score' (the squared euclidean distance) is non-negative. The computed value
112+
// may be negative as a result of quantization loss.
113+
return VectorUtil.normalizeDistanceToUnitInterval(Math.max(score, 0f));
114+
} else {
115+
// For cosine and max inner product, we need to apply the additional correction, which is
116+
// assumed to be the non-centered dot-product between the vector and the centroid
117+
score += queryCorrections.additionalCorrection() + nodeCorrections.additionalCorrection() - centroidDP;
118+
if (similarityFunction == MAXIMUM_INNER_PRODUCT) {
119+
return VectorUtil.scaleMaxInnerProductScore(score);
120+
}
121+
// Ensure that 'score' (a normalized dot product) is in [-1,1]. The computed value may be out
122+
// of bounds as a result of quantization loss.
123+
score = Math.clamp(score, -1, 1);
124+
return VectorUtil.normalizeToUnitInterval(score);
125+
}
126+
}
68127
}

0 commit comments

Comments
 (0)