Skip to content

Commit 51d4f0b

Browse files
author
Jake Luciani
authored
SimdOps and NativeSimd ops refactored, VectorUtilSupport simplified (#498)
* SimdOps and NativeSimd ops refactored to remove duplicate code, VectorUtilSupport simplified for Native vs Panama * Add back missing calls for assemble and sum and ensure assemble isn't tried with MemorySegments * Clean up more unnessisary casts and add missing intoByteSequence call * Use type support in SimdOps when returning a new vector * Review points: remove SimdOps and NativeSimdOps, move logic to VectorUtilSupport
1 parent c5c3ff9 commit 51d4f0b

8 files changed

Lines changed: 1430 additions & 2572 deletions

File tree

jvector-base/src/main/java/io/github/jbellis/jvector/quantization/PQVectors.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,16 @@ public ScoreFunction.ApproximateScoreFunction precomputedScoreFunctionFor(Vector
225225

226226
public ScoreFunction.ApproximateScoreFunction scoreFunctionFor(VectorFloat<?> q, VectorSimilarityFunction similarityFunction) {
227227
VectorFloat<?> centeredQuery = pq.globalCentroid == null ? q : VectorUtil.sub(q, pq.globalCentroid);
228+
229+
final int subspaceCount = pq.getSubspaceCount();
228230
switch (similarityFunction) {
229231
case DOT_PRODUCT:
230232
return (node2) -> {
231233
var encodedChunk = getChunk(node2);
232234
var encodedOffset = getOffsetInChunk(node2);
233235
// compute the dot product of the query and the codebook centroids corresponding to the encoded points
234236
float dp = 0;
235-
for (int m = 0; m < pq.getSubspaceCount(); m++) {
237+
for (int m = 0; m < subspaceCount; m++) {
236238
int centroidIndex = Byte.toUnsignedInt(encodedChunk.get(m + encodedOffset));
237239
int centroidLength = pq.subvectorSizesAndOffsets[m][0];
238240
int centroidOffset = pq.subvectorSizesAndOffsets[m][1];
@@ -249,7 +251,7 @@ public ScoreFunction.ApproximateScoreFunction scoreFunctionFor(VectorFloat<?> q,
249251
// compute the dot product of the query and the codebook centroids corresponding to the encoded points
250252
float sum = 0;
251253
float norm2 = 0;
252-
for (int m = 0; m < pq.getSubspaceCount(); m++) {
254+
for (int m = 0; m < subspaceCount; m++) {
253255
int centroidIndex = Byte.toUnsignedInt(encodedChunk.get(m + encodedOffset));
254256
int centroidLength = pq.subvectorSizesAndOffsets[m][0];
255257
int centroidOffset = pq.subvectorSizesAndOffsets[m][1];
@@ -267,7 +269,7 @@ public ScoreFunction.ApproximateScoreFunction scoreFunctionFor(VectorFloat<?> q,
267269
var encodedOffset = getOffsetInChunk(node2);
268270
// compute the euclidean distance between the query and the codebook centroids corresponding to the encoded points
269271
float sum = 0;
270-
for (int m = 0; m < pq.getSubspaceCount(); m++) {
272+
for (int m = 0; m < subspaceCount; m++) {
271273
int centroidIndex = Byte.toUnsignedInt(encodedChunk.get(m + encodedOffset));
272274
int centroidLength = pq.subvectorSizesAndOffsets[m][0];
273275
int centroidOffset = pq.subvectorSizesAndOffsets[m][1];

jvector-base/src/main/java/io/github/jbellis/jvector/vector/VectorUtilSupport.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ public interface VectorUtilSupport {
115115

116116
int hammingDistance(long[] v1, long[] v2);
117117

118-
119118
// default implementation used here because Panama SIMD can't express necessary SIMD operations and degrades to scalar
120119
/**
121120
* Calculates the similarity score of multiple product quantization-encoded vectors against a single query vector,

jvector-native/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@
118118
</exclusion>
119119
</exclusions>
120120
</dependency>
121+
<dependency>
122+
<groupId>io.github.jbellis</groupId>
123+
<artifactId>jvector-twenty</artifactId>
124+
<version>${project.version}</version>
125+
</dependency>
121126
<dependency>
122127
<groupId>org.junit.jupiter</groupId>
123128
<artifactId>junit-jupiter-engine</artifactId>

jvector-native/src/main/java/io/github/jbellis/jvector/vector/NativeVectorUtilSupport.java

Lines changed: 24 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -16,103 +16,54 @@
1616

1717
package io.github.jbellis.jvector.vector;
1818

19+
import java.nio.ByteOrder;
20+
1921
import io.github.jbellis.jvector.vector.cnative.NativeSimdOps;
2022
import io.github.jbellis.jvector.vector.types.ByteSequence;
2123
import io.github.jbellis.jvector.vector.types.VectorFloat;
22-
23-
import java.util.List;
24+
import jdk.incubator.vector.ByteVector;
25+
import jdk.incubator.vector.FloatVector;
26+
import jdk.incubator.vector.VectorMask;
27+
import jdk.incubator.vector.VectorSpecies;
2428

2529
/**
2630
* VectorUtilSupport implementation that prefers native/Panama SIMD.
2731
*/
28-
final class NativeVectorUtilSupport implements VectorUtilSupport
32+
final class NativeVectorUtilSupport extends PanamaVectorUtilSupport
2933
{
3034
@Override
31-
public float dotProduct(VectorFloat<?> a, VectorFloat<?> b) {
32-
return this.dotProduct(a, 0, b, 0, a.length());
33-
}
34-
35-
@Override
36-
public float cosine(VectorFloat<?> v1, VectorFloat<?> v2) {
37-
return VectorSimdOps.cosineSimilarity((MemorySegmentVectorFloat)v1, (MemorySegmentVectorFloat)v2);
38-
}
39-
40-
@Override
41-
public float cosine(VectorFloat<?> a, int aoffset, VectorFloat<?> b, int boffset, int length) {
42-
return VectorSimdOps.cosineSimilarity((MemorySegmentVectorFloat)a, aoffset, (MemorySegmentVectorFloat)b, boffset, length);
43-
}
44-
45-
@Override
46-
public float squareDistance(VectorFloat<?> a, VectorFloat<?> b) {
47-
return this.squareDistance(a, 0, b, 0, a.length());
48-
}
49-
50-
@Override
51-
public float squareDistance(VectorFloat<?> a, int aoffset, VectorFloat<?> b, int boffset, int length) {
52-
return VectorSimdOps.squareDistance((MemorySegmentVectorFloat) a, aoffset, (MemorySegmentVectorFloat) b, boffset, length);
53-
}
54-
55-
@Override
56-
public float dotProduct(VectorFloat<?> a, int aoffset, VectorFloat<?> b, int boffset, int length) {
57-
return VectorSimdOps.dotProduct((MemorySegmentVectorFloat) a, aoffset, (MemorySegmentVectorFloat) b, boffset, length);
58-
}
59-
60-
@Override
61-
public VectorFloat<?> sum(List<VectorFloat<?>> vectors) {
62-
return VectorSimdOps.sum(vectors);
63-
}
64-
65-
@Override
66-
public float sum(VectorFloat<?> vector) {
67-
return VectorSimdOps.sum((MemorySegmentVectorFloat) vector);
68-
}
69-
70-
@Override
71-
public void scale(VectorFloat<?> vector, float multiplier) {
72-
VectorSimdOps.scale((MemorySegmentVectorFloat) vector, multiplier);
73-
}
74-
75-
@Override
76-
public void addInPlace(VectorFloat<?> v1, VectorFloat<?> v2) {
77-
VectorSimdOps.addInPlace((MemorySegmentVectorFloat)v1, (MemorySegmentVectorFloat)v2);
78-
}
79-
80-
@Override
81-
public void addInPlace(VectorFloat<?> vector, float value) {
82-
VectorSimdOps.addInPlace((MemorySegmentVectorFloat) vector, value);
83-
}
84-
85-
@Override
86-
public void subInPlace(VectorFloat<?> v1, VectorFloat<?> v2) {
87-
VectorSimdOps.subInPlace((MemorySegmentVectorFloat)v1, (MemorySegmentVectorFloat)v2);
35+
protected FloatVector fromVectorFloat(VectorSpecies<Float> SPEC, VectorFloat<?> vector, int offset)
36+
{
37+
return FloatVector.fromMemorySegment(SPEC, ((MemorySegmentVectorFloat) vector).get(), vector.offset(offset), ByteOrder.LITTLE_ENDIAN);
8838
}
8939

9040
@Override
91-
public void subInPlace(VectorFloat<?> vector, float value) {
92-
VectorSimdOps.subInPlace((MemorySegmentVectorFloat)vector, value);
41+
protected FloatVector fromVectorFloat(VectorSpecies<Float> SPEC, VectorFloat<?> vector, int offset, int[] indices, int indicesOffset)
42+
{
43+
throw new UnsupportedOperationException("Assembly not supported with memory segments.");
9344
}
9445

9546
@Override
96-
public VectorFloat<?> sub(VectorFloat<?> a, VectorFloat<?> b) {
97-
if (a.length() != b.length()) {
98-
throw new IllegalArgumentException("Vectors must be the same length");
99-
}
100-
return sub(a, 0, b, 0, a.length());
47+
protected void intoVectorFloat(FloatVector vector, VectorFloat<?> v, int offset)
48+
{
49+
vector.intoMemorySegment(((MemorySegmentVectorFloat) v).get(), v.offset(offset), ByteOrder.LITTLE_ENDIAN);
10150
}
10251

10352
@Override
104-
public VectorFloat<?> sub(VectorFloat<?> a, float value) {
105-
return VectorSimdOps.sub((MemorySegmentVectorFloat) a, 0, value, a.length());
53+
protected ByteVector fromByteSequence(VectorSpecies<Byte> SPEC, ByteSequence<?> vector, int offset)
54+
{
55+
return ByteVector.fromMemorySegment(SPEC, ((MemorySegmentByteSequence) vector).get(), offset, ByteOrder.LITTLE_ENDIAN);
10656
}
10757

10858
@Override
109-
public VectorFloat<?> sub(VectorFloat<?> a, int aOffset, VectorFloat<?> b, int bOffset, int length) {
110-
return VectorSimdOps.sub((MemorySegmentVectorFloat) a, aOffset, (MemorySegmentVectorFloat) b, bOffset, length);
59+
protected void intoByteSequence(ByteVector vector, ByteSequence<?> v, int offset)
60+
{
61+
vector.intoMemorySegment(((MemorySegmentByteSequence) v).get(), offset, ByteOrder.LITTLE_ENDIAN);
11162
}
11263

11364
@Override
114-
public void minInPlace(VectorFloat<?> v1, VectorFloat<?> v2) {
115-
VectorSimdOps.minInPlace((MemorySegmentVectorFloat) v1, (MemorySegmentVectorFloat) v2);
65+
protected void intoByteSequence(ByteVector vector, ByteSequence<?> v, int offset, VectorMask<Byte> mask) {
66+
vector.intoMemorySegment(((MemorySegmentByteSequence) v).get(), offset, ByteOrder.LITTLE_ENDIAN, mask);
11667
}
11768

11869
@Override
@@ -128,20 +79,6 @@ public float assembleAndSum(VectorFloat<?> data, int dataBase, ByteSequence<?> b
12879
return NativeSimdOps.assemble_and_sum_f32_512(((MemorySegmentVectorFloat) data).get(), dataBase, ((MemorySegmentByteSequence) baseOffsets).get(), baseOffsetsOffset, baseOffsetsLength);
12980
}
13081

131-
@Override
132-
public int hammingDistance(long[] v1, long[] v2) {
133-
return VectorSimdOps.hammingDistance(v1, v2);
134-
}
135-
136-
@Override
137-
public float max(VectorFloat<?> vector) {
138-
return VectorSimdOps.max((MemorySegmentVectorFloat) vector);
139-
}
140-
141-
@Override
142-
public float min(VectorFloat<?> vector) {
143-
return VectorSimdOps.min((MemorySegmentVectorFloat) vector);
144-
}
14582

14683
@Override
14784
public void calculatePartialSums(VectorFloat<?> codebook, int codebookBase, int size, int clusterCount, VectorFloat<?> query, int queryOffset, VectorSimilarityFunction vsf, VectorFloat<?> partialSums) {
@@ -161,11 +98,6 @@ public void calculatePartialSums(VectorFloat<?> codebook, int codebookBase, int
16198
}
16299
}
163100

164-
@Override
165-
public void quantizePartials(float delta, VectorFloat<?> partials, VectorFloat<?> partialBases, ByteSequence<?> quantizedPartials) {
166-
VectorSimdOps.quantizePartials(delta, (MemorySegmentVectorFloat) partials, (MemorySegmentVectorFloat) partialBases, (MemorySegmentByteSequence) quantizedPartials);
167-
}
168-
169101
@Override
170102
public void bulkShuffleQuantizedSimilarity(ByteSequence<?> shuffles, int codebookCount, ByteSequence<?> quantizedPartials, float delta, float bestDistance, VectorSimilarityFunction vsf, VectorFloat<?> results) {
171103
assert shuffles.offset() == 0 : "Bulk shuffle shuffles are expected to have an offset of 0. Found: " + shuffles.offset();
@@ -199,50 +131,4 @@ public float pqDecodedCosineSimilarity(ByteSequence<?> encoded, int encodedOffse
199131
// encoded is a pointer into a PQ chunk - we need to index into it by encodedOffset and provide encodedLength to the native code
200132
return NativeSimdOps.pq_decoded_cosine_similarity_f32_512(((MemorySegmentByteSequence) encoded).get(), encodedOffset, encodedLength, clusterCount, ((MemorySegmentVectorFloat) partialSums).get(), ((MemorySegmentVectorFloat) aMagnitude).get(), bMagnitude);
201133
}
202-
203-
@Override
204-
public float nvqDotProduct8bit(VectorFloat<?> vector, ByteSequence<?> bytes, float growthRate, float midpoint, float minValue, float maxValue) {
205-
return VectorSimdOps.nvqDotProduct8bit(
206-
(MemorySegmentVectorFloat) vector, (MemorySegmentByteSequence) bytes,
207-
growthRate, midpoint, minValue, maxValue
208-
);
209-
}
210-
211-
@Override
212-
public float nvqSquareL2Distance8bit(VectorFloat<?> vector, ByteSequence<?> bytes, float growthRate, float midpoint, float minValue, float maxValue) {
213-
return VectorSimdOps.nvqSquareDistance8bit(
214-
(MemorySegmentVectorFloat) vector, (MemorySegmentByteSequence) bytes,
215-
growthRate, midpoint, minValue, maxValue
216-
);
217-
}
218-
219-
@Override
220-
public float[] nvqCosine8bit(VectorFloat<?> vector, ByteSequence<?> bytes, float growthRate, float midpoint, float minValue, float maxValue, VectorFloat<?> centroid) {
221-
return VectorSimdOps.nvqCosine8bit(
222-
(MemorySegmentVectorFloat) vector, (MemorySegmentByteSequence) bytes,
223-
growthRate, midpoint, minValue, maxValue,
224-
(MemorySegmentVectorFloat) centroid
225-
);
226-
}
227-
228-
@Override
229-
public void nvqShuffleQueryInPlace8bit(VectorFloat<?> vector) {
230-
VectorSimdOps.nvqShuffleQueryInPlace8bit((MemorySegmentVectorFloat) vector);
231-
}
232-
233-
@Override
234-
public void nvqQuantize8bit(VectorFloat<?> vector, float growthRate, float midpoint, float minValue, float maxValue, ByteSequence<?> destination) {
235-
VectorSimdOps.nvqQuantize8bit((MemorySegmentVectorFloat) vector, growthRate, midpoint, minValue, maxValue, (MemorySegmentByteSequence) destination);
236-
}
237-
238-
@Override
239-
public float nvqLoss(VectorFloat<?> vector, float growthRate, float midpoint, float minValue, float maxValue, int nBits) {
240-
return VectorSimdOps.nvqLoss((MemorySegmentVectorFloat) vector, growthRate, midpoint, minValue, maxValue, nBits);
241-
}
242-
243-
@Override
244-
public float nvqUniformLoss(VectorFloat<?> vector, float minValue, float maxValue, int nBits) {
245-
return VectorSimdOps.nvqUniformLoss((MemorySegmentVectorFloat) vector, minValue, maxValue, nBits);
246-
}
247-
248134
}

0 commit comments

Comments
 (0)