Skip to content

Commit 425734a

Browse files
committed
rebase on main
2 parents f6240b6 + 02fea87 commit 425734a

File tree

79 files changed

+2237
-1115
lines changed

Some content is hidden

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

79 files changed

+2237
-1115
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
target/
2+
local/
23
.mvn/wrapper/maven-wrapper.jar
34
.java-version
45

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,25 @@ All notable changes to this project will be documented in this file. Dates are d
44

55
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
66

7+
#### [4.0.0-rc.6](https://github.com/datastax/jvector/compare/4.0.0-rc.5...4.0.0-rc.6)
8+
9+
- release 4.0.0-rc.6 [`#571`](https://github.com/datastax/jvector/pull/571)
10+
- fix javadoc error [`#570`](https://github.com/datastax/jvector/pull/570)
11+
- Ignoring testIncrementalInsertionFromOnDiskIndex_withNonIdentityOrdinalMapping and adding a TODO in buildAndMergeNewNodes [`#569`](https://github.com/datastax/jvector/pull/569)
12+
- Computation of reconstruction errors for vector compressors [`#567`](https://github.com/datastax/jvector/pull/567)
13+
- Add NVQ paper in README [`#560`](https://github.com/datastax/jvector/pull/560)
14+
- Add ImmutableGraphIndex.isHierarchical [`#563`](https://github.com/datastax/jvector/pull/563)
15+
- Harden tests for heap graph reconstruction [`#543`](https://github.com/datastax/jvector/pull/543)
16+
- Make the thresholds in TestLowCardinalityFiltering tighter [`#559`](https://github.com/datastax/jvector/pull/559)
17+
- Begin development on 4.0.0-rc.6 [`#558`](https://github.com/datastax/jvector/pull/558)
18+
- Revert "Start development on 4.0.0-rc.6-SNAPSHOT" [`4f661d9`](https://github.com/datastax/jvector/commit/4f661d99ca840f22e5b700ae4e28653d2316bc21)
19+
- Start development on 4.0.0-rc.6-SNAPSHOT [`fdee577`](https://github.com/datastax/jvector/commit/fdee5779a27b46934a2770e260ea5d9e5335a505)
20+
721
#### [4.0.0-rc.5](https://github.com/datastax/jvector/compare/4.0.0-rc.4...4.0.0-rc.5)
822

23+
> 22 October 2025
24+
25+
- chore: update changelog for 4.0.0-rc.5 [`#557`](https://github.com/datastax/jvector/pull/557)
926
- Release 4.0.0-rc.5 [`#556`](https://github.com/datastax/jvector/pull/556)
1027
- Add RemappedRandomAccessVectorValues to fix BuildScoreProvider::randomAccessScoreProvider [`#555`](https://github.com/datastax/jvector/pull/555)
1128
- Fix ScoreTracker initialization and reset methods [`#551`](https://github.com/datastax/jvector/pull/551)

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ The upper layers of the hierarchy are represented by an in-memory adjacency list
2525
The bottom layer of the graph is represented by an on-disk adjacency list per node. JVector uses additional data stored inline to support two-pass searches, with the first pass powered by lossily compressed representations of the vectors kept in memory, and the second by a more accurate representation read from disk. The first pass can be performed with
2626
* Product quantization (PQ), optionally with [anisotropic weighting](https://arxiv.org/abs/1908.10396)
2727
* [Binary quantization](https://huggingface.co/blog/embedding-quantization) (BQ)
28-
* Fused ADC, where PQ codebooks are transposed and written inline with the graph adjacency list
28+
* Fused PQ, where PQ codebooks are written inline with the graph adjacency list
2929

3030
The second pass can be performed with
3131
* Full resolution float32 vectors
@@ -265,13 +265,13 @@ Commentary:
265265

266266
* Embeddings models produce output from a consistent distribution of vectors. This means that you can save and re-use ProductQuantization codebooks, even for a different set of vectors, as long as you had a sufficiently large training set to build it the first time around. ProductQuantization.MAX_PQ_TRAINING_SET_SIZE (128,000 vectors) has proven to be sufficiently large.
267267
* JDK ThreadLocal objects cannot be referenced except from the thread that created them. This is a difficult design into which to fit caching of Closeable objects like GraphSearcher. JVector provides the ExplicitThreadLocal class to solve this.
268-
* Fused ADC is only compatible with Product Quantization, not Binary Quantization. This is no great loss since [very few models generate embeddings that are best suited for BQ](https://thenewstack.io/why-vector-size-matters/). That said, BQ continues to be supported with non-Fused indexes.
268+
* Fused PQ is only compatible with Product Quantization, not Binary Quantization. This is no great loss since [very few models generate embeddings that are best suited for BQ](https://thenewstack.io/why-vector-size-matters/). That said, BQ continues to be supported with non-Fused indexes.
269269
* JVector heavily utilizes the Panama Vector API(SIMD) for ANN indexing and search. We have seen cases where the memory bandwidth is saturated during indexing and product quantization and can cause the process to slow down. To avoid this, the batch methods for index and PQ builds use a [PhysicalCoreExecutor](https://javadoc.io/doc/io.github.jbellis/jvector/latest/io/github/jbellis/jvector/util/PhysicalCoreExecutor.html) to limit the amount of operations to the physical core count. The default value is 1/2 the processor count seen by Java. This may not be correct in all setups (e.g. no hyperthreading or hybrid architectures) so if you wish to override the default use the `-Djvector.physical_core_count` property, or pass in your own ForkJoinPool instance.
270270

271271

272272
### Advanced features
273273

274-
* Fused ADC is represented as a Feature that is supported during incremental index construction, like InlineVectors above. [See the Grid class for sample code](https://github.com/jbellis/jvector/blob/main/jvector-examples/src/main/java/io/github/jbellis/jvector/example/Grid.java).
274+
* Fused PQ is represented as a Feature that is supported during incremental index construction, like InlineVectors above. [See the Grid class for sample code](https://github.com/jbellis/jvector/blob/main/jvector-examples/src/main/java/io/github/jbellis/jvector/example/Grid.java).
275275
* Anisotropic PQ is built into the ProductQuantization class and can improve recall, but nobody knows how to tune it (with the T/threshold parameter) except experimentally on a per-model basis, and choosing the wrong setting can make things worse. From Figure 3 in the paper:
276276
![APQ performnce on Glove first improves and then degrades as T increases](https://github.com/jbellis/jvector/assets/42158/fd459222-6929-43ca-a405-ac34dbaf6646)
277277

@@ -284,7 +284,7 @@ Commentary:
284284
* Foundational work: [HNSW](https://ieeexplore.ieee.org/abstract/document/8594636) and [DiskANN](https://suhasjs.github.io/files/diskann_neurips19.pdf) papers, and [a higher level explainer](https://www.datastax.com/guides/hierarchical-navigable-small-worlds)
285285
* [Anisotropic PQ paper](https://arxiv.org/abs/1908.10396)
286286
* [Quicker ADC paper](https://arxiv.org/abs/1812.09162)
287-
287+
* [NVQ paper](https://arxiv.org/abs/2509.18471)
288288

289289
## Developing and Testing
290290
This project is organized as a [multimodule Maven build](https://maven.apache.org/guides/mini/guide-multiple-modules.html). The intent is to produce a multirelease jar suitable for use as

UPGRADING.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@
88
- Support for hierarchical graph indices. This new type of index blends HNSW and DiskANN in a novel way. An
99
HNSW-like hierarchy resides in memory for quickly seeding the search. This also reduces the need for caching the
1010
DiskANN graph near the entrypoint. The base layer of the hierarchy is a DiskANN-like index and inherits its
11-
properties. This hierarchical structure can be disabled, ending up with just the base DiskANN layer.
11+
properties. This hierarchical structure can be disabled, ending up with just the base DiskANN layer.
12+
- The feature previously known as Fused ADC has been renamed to Fused PQ. This feature allows to offload the PQ
13+
codebooks from memory during search, storing them within the graph in a way that does not slow down the search.
14+
Implementation notes: The implementation of this feature has been overhauled to not require native code acceleration.
15+
This explores a design space allowing for packed representations of vectors fused into the graph in shapes optimal
16+
for approximate score calculation. This new feature of graph indexes is opt-in but fully functional now. Any graph
17+
degree limitations have been lifted. At this time, only 256-cluster ProductQuantization can use fused PQ.
18+
Version 6 or greater of the file disk format is required to use this feature.
19+
1220

1321
## API changes
1422
- MemorySegmentReader.Supplier and SimpleMappedReader.Supplier must now be explicitly closed, instead of being
@@ -20,7 +28,6 @@
2028
we do early termination of the search. In certain cases, this can accelerate the search at the potential cost of some
2129
accuracy. It is set to false by default.
2230
- The constructors of GraphIndexBuilder allow to specify different maximum out-degrees for the graphs in each layer.
23-
However, this feature does not work with FusedADC in this version.
2431

2532
### API changes in 3.0.6
2633

benchmarks-jmh/src/main/java/io/github/jbellis/jvector/bench/ParallelWriteBenchmark.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import io.github.jbellis.jvector.graph.disk.OrdinalMapper;
2727
import io.github.jbellis.jvector.graph.disk.feature.Feature;
2828
import io.github.jbellis.jvector.graph.disk.feature.FeatureId;
29-
import io.github.jbellis.jvector.graph.disk.feature.FusedADC;
29+
import io.github.jbellis.jvector.graph.disk.feature.FusedPQ;
3030
import io.github.jbellis.jvector.graph.disk.feature.NVQ;
3131
import io.github.jbellis.jvector.graph.similarity.BuildScoreProvider;
3232
import io.github.jbellis.jvector.quantization.NVQuantization;
@@ -85,7 +85,7 @@ public class ParallelWriteBenchmark {
8585

8686
// Feature state reused between iterations
8787
private NVQ nvqFeature;
88-
private FusedADC fusedAdcFeature;
88+
private FusedPQ fusedPQFeature;
8989
private OrdinalMapper identityMapper;
9090
private Map<FeatureId, IntFunction<Feature.State>> inlineSuppliers;
9191

@@ -119,7 +119,7 @@ public void setup() throws IOException {
119119
int nSubVectors = floatVectors.dimension() == 2 ? 1 : 2;
120120
var nvq = NVQuantization.compute(floatVectors, nSubVectors);
121121
nvqFeature = new NVQ(nvq);
122-
fusedAdcFeature = new FusedADC(graph.maxDegree(), pqVectors.getCompressor());
122+
fusedPQFeature = new FusedPQ(graph.maxDegree(), pqVectors.getCompressor());
123123

124124
inlineSuppliers = new EnumMap<>(FeatureId.class);
125125
inlineSuppliers.put(FeatureId.NVQ_VECTORS, ordinal -> new NVQ.State(nvq.encode(floatVectors.getVector(ordinal))));
@@ -189,13 +189,13 @@ private void writeGraph(ImmutableGraphIndex graph,
189189
try (var writer = new OnDiskGraphIndexWriter.Builder(graph, path)
190190
.withParallelWrites(parallel)
191191
.with(nvqFeature)
192-
.with(fusedAdcFeature)
192+
.with(fusedPQFeature)
193193
.withMapper(identityMapper)
194194
.build()) {
195195
var view = graph.getView();
196196
Map<FeatureId, IntFunction<Feature.State>> writeSuppliers = new EnumMap<>(FeatureId.class);
197197
writeSuppliers.put(FeatureId.NVQ_VECTORS, inlineSuppliers.get(FeatureId.NVQ_VECTORS));
198-
writeSuppliers.put(FeatureId.FUSED_ADC, ordinal -> new FusedADC.State(view, pqVectors, ordinal));
198+
writeSuppliers.put(FeatureId.FUSED_PQ, ordinal -> new FusedPQ.State(view, pqVectors, ordinal));
199199

200200
writer.write(writeSuppliers);
201201
view.close();

benchmarks-jmh/src/main/java/io/github/jbellis/jvector/bench/RecallWithRandomVectorsBenchmark.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,8 @@ private double calculateRecall(Set<Integer> predicted, int[] groundTruth, int k)
247247
int actualK = Math.min(k, Math.min(predicted.size(), groundTruth.length));
248248

249249
for (int i = 0; i < actualK; i++) {
250-
for (int j = 0; j < actualK; j++) {
251-
if (predicted.contains(groundTruth[j])) {
252-
hits++;
253-
break;
254-
}
250+
if (predicted.contains(groundTruth[i])) {
251+
hits++;
255252
}
256253
}
257254

jvector-base/src/main/java/io/github/jbellis/jvector/disk/BufferedRandomAccessWriter.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public class BufferedRandomAccessWriter implements RandomAccessWriter {
3535
private final RandomAccessFile raf;
3636
private final DataOutputStream stream;
3737

38+
/**
39+
* Creates a buffered random access writer for the given path.
40+
* @param path the path to write to
41+
* @throws FileNotFoundException if the file cannot be created
42+
*/
3843
public BufferedRandomAccessWriter(Path path) throws FileNotFoundException {
3944
raf = new RandomAccessFile(path.toFile(), "rw");
4045
stream = new DataOutputStream(new BufferedOutputStream(new RandomAccessOutputStream(raf)));
@@ -88,10 +93,9 @@ public void flush() throws IOException {
8893
}
8994

9095
/**
91-
* return the CRC32 checksum for the range [startOffset .. endOffset)
92-
* <p>
93-
* the file pointer will be left at endOffset.
94-
* <p>
96+
* Returns the CRC32 checksum for the range [startOffset .. endOffset)
97+
*
98+
* The file pointer will be left at endOffset.
9599
*/
96100
@Override
97101
public long checksum(long startOffset, long endOffset) throws IOException {

jvector-base/src/main/java/io/github/jbellis/jvector/disk/ByteBufferReader.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@
2323
* RandomAccessReader that reads from a ByteBuffer
2424
*/
2525
public class ByteBufferReader implements RandomAccessReader {
26+
/** The underlying ByteBuffer. */
2627
protected final ByteBuffer bb;
2728

29+
/**
30+
* Creates a ByteBufferReader from the given ByteBuffer.
31+
* @param sourceBB the source ByteBuffer
32+
*/
2833
public ByteBufferReader(ByteBuffer sourceBB) {
2934
bb = sourceBB;
3035
}

jvector-base/src/main/java/io/github/jbellis/jvector/disk/IndexWriter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@
2020
import java.io.DataOutput;
2121
import java.io.IOException;
2222

23+
/**
24+
* Interface for writing index data.
25+
*/
2326
public interface IndexWriter extends DataOutput, Closeable {
2427
/**
28+
* Returns the current position in the output.
2529
* @return the current position in the output
30+
* @throws IOException if an I/O error occurs
2631
*/
2732
long position() throws IOException;
2833
}

jvector-base/src/main/java/io/github/jbellis/jvector/disk/RandomAccessReader.java

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,99 @@
3232
* uses the ReaderSupplier API to create a RandomAccessReader per thread, as needed.
3333
*/
3434
public interface RandomAccessReader extends AutoCloseable {
35+
/**
36+
* Seeks to the specified offset.
37+
* @param offset the offset to seek to
38+
* @throws IOException if an I/O error occurs
39+
*/
3540
void seek(long offset) throws IOException;
3641

42+
/**
43+
* Returns the current position.
44+
* @return the current position
45+
* @throws IOException if an I/O error occurs
46+
*/
3747
long getPosition() throws IOException;
3848

49+
/**
50+
* Reads an integer.
51+
* @return the integer value
52+
* @throws IOException if an I/O error occurs
53+
*/
3954
int readInt() throws IOException;
4055

56+
/**
57+
* Reads a float.
58+
* @return the float value
59+
* @throws IOException if an I/O error occurs
60+
*/
4161
float readFloat() throws IOException;
4262

63+
/**
64+
* Reads a long.
65+
* @return the long value
66+
* @throws IOException if an I/O error occurs
67+
*/
4368
long readLong() throws IOException;
4469

70+
/**
71+
* Reads bytes into the array.
72+
* @param bytes the byte array to read into
73+
* @throws IOException if an I/O error occurs
74+
*/
4575
void readFully(byte[] bytes) throws IOException;
4676

77+
/**
78+
* Reads bytes into the buffer.
79+
* @param buffer the ByteBuffer to read into
80+
* @throws IOException if an I/O error occurs
81+
*/
4782
void readFully(ByteBuffer buffer) throws IOException;
4883

84+
/**
85+
* Reads floats into the array.
86+
* @param floats the float array to read into
87+
* @throws IOException if an I/O error occurs
88+
*/
4989
default void readFully(float[] floats) throws IOException {
5090
read(floats, 0, floats.length);
5191
}
5292

93+
/**
94+
* Reads longs into the array.
95+
* @param vector the long array to read into
96+
* @throws IOException if an I/O error occurs
97+
*/
5398
void readFully(long[] vector) throws IOException;
5499

100+
/**
101+
* Reads integers into the array.
102+
* @param ints the int array to read into
103+
* @param offset the offset in the array
104+
* @param count the number of integers to read
105+
* @throws IOException if an I/O error occurs
106+
*/
55107
void read(int[] ints, int offset, int count) throws IOException;
56108

109+
/**
110+
* Reads floats into the array.
111+
* @param floats the float array to read into
112+
* @param offset the offset in the array
113+
* @param count the number of floats to read
114+
* @throws IOException if an I/O error occurs
115+
*/
57116
void read(float[] floats, int offset, int count) throws IOException;
58117

118+
/**
119+
* Closes this reader.
120+
* @throws IOException if an I/O error occurs
121+
*/
59122
void close() throws IOException;
60123

61-
// Length of the reader slice
124+
/**
125+
* Returns the length of the reader slice.
126+
* @return the length
127+
* @throws IOException if an I/O error occurs
128+
*/
62129
long length() throws IOException;
63130
}

0 commit comments

Comments
 (0)