Skip to content
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ff9949f
Add GPU-optimized multi-segment CAGRA search in GPUKnnFloatVectorQuery
jamxia155 Mar 31, 2026
bdb8e66
Enable async memory resource and fix query vector upload in GPU search
jamxia155 Apr 2, 2026
7a8b26b
Expose CAGRA SearchAlgo parameter
jamxia155 Apr 13, 2026
aeda8bf
Add persistent kernel support and fix runner hash stability
jamxia155 Apr 13, 2026
2521261
Remove persistent kernel support; add workspace pool configuration
jamxia155 Apr 16, 2026
19773fa
Merge remote-tracking branch 'origin/main' into multi-segment-cagra-s…
jamxia155 May 9, 2026
9abe58a
Modify example to demo prefilter.
narangvivek10 May 6, 2026
92f4f3f
Enable GPU multi-segment search with explicit Lucene filter queries
jamxia155 May 16, 2026
c49a1a0
Adapt GPU search path to merged multi-partition cuVS API
jamxia155 May 21, 2026
7f394de
Allow rewrite() to use multi-partition path for k beyond 1024
jamxia155 May 30, 2026
1b0355a
Revert "Allow rewrite() to use multi-partition path for k beyond 1024"
jamxia155 Jun 1, 2026
05d61b4
Remove computeMaxIterations from GPUKnnFloatVectorQuery
jamxia155 Jun 1, 2026
2160b63
Drop k>1024 gate from GPUKnnFloatVectorQuery.rewrite()
jamxia155 Jun 2, 2026
32e07bf
Merge remote-tracking branch 'origin/main' into multi-segment-cagra-s…
jamxia155 Jun 10, 2026
51e03b1
Updates to pom to buidl snapshot
jolorunyomi Jun 25, 2026
7f99c97
Bump cuvs-lucene to 26.08.0-SNAPSHOT
jamxia155 Jun 26, 2026
f6a5542
Adapt to multi-partition CAGRA Java API refactor
jamxia155 Jun 30, 2026
c840848
Merge remote-tracking branch 'origin/main' into multi-segment-cagra-s…
jamxia155 Jun 30, 2026
91684e8
Fix duplicate <repositories> tag in pom.xml
jamxia155 Jul 1, 2026
bdf69dd
Expose CAGRA max_iterations as a configurable search setting
jamxia155 Jul 1, 2026
62f6302
Remove unnecessary formatting updates
jamxia155 Jul 1, 2026
480ae28
Adapt multi-partition filter to the offset-free cuVS API
jamxia155 Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
# SPDX-License-Identifier: Apache-2.0

# Maven
*.jar
target
**/.DS_Store
bin
target/

# IDE - IntelliJ
.idea/
*.iml

# IDE - Eclipse
.project
cuvs-workdir
4 changes: 2 additions & 2 deletions bench/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<groupId>com.nvidia.cuvs.lucene.benchmarks</groupId>
<artifactId>cuvs-lucene-benchmarks</artifactId>
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0-SNAPSHOT</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
<packaging>jar</packaging>

<name>cuvs-lucene-benchmarks</name>
Expand Down Expand Up @@ -85,7 +85,7 @@
<dependency>
<groupId>com.nvidia.cuvs.lucene</groupId>
<artifactId>cuvs-lucene</artifactId>
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0-SNAPSHOT</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
</dependency>
<dependency>
<groupId>commons-io</groupId>
Expand Down
4 changes: 2 additions & 2 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/bin/bash

# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -e -u -o pipefail

ARGS="$*"
NUMARGS=$#

VERSION="26.08.0" # Note: The version is updated automatically when ci/release/update-version.sh is invoked
VERSION="26.08.0-SNAPSHOT" # Note: The version is updated automatically when ci/release/update-version.sh is invoked
GROUP_ID="com.nvidia.cuvs.lucene"

function hasArg {
Expand Down
4 changes: 2 additions & 2 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<groupId>com.nvidia.cuvs.lucene.examples</groupId>
<artifactId>examples</artifactId>
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0-SNAPSHOT</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
<name>examples</name>

<properties>
Expand Down Expand Up @@ -100,7 +100,7 @@
<dependency>
<groupId>com.nvidia.cuvs.lucene</groupId>
<artifactId>cuvs-lucene</artifactId>
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0-SNAPSHOT</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
</dependency>
</dependencies>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.nvidia.cuvs.lucene.examples;
Expand Down Expand Up @@ -30,9 +30,12 @@
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.KnnFloatVectorQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
Expand All @@ -56,6 +59,7 @@ public static void main(String[] args) throws Exception {
final int COMMIT_FREQ = 2000;
final String ID_FIELD = "id";
final String VECTOR_FIELD = "vector_field";
final String STATUS_FIELD = "status";

int numDocs = 2000;
int dimension = 32;
Expand All @@ -70,6 +74,9 @@ public static void main(String[] args) throws Exception {
Document document = new Document();
document.add(new StringField(ID_FIELD, Integer.toString(i), Field.Store.YES));
document.add(new KnnFloatVectorField(VECTOR_FIELD, dataset[i], EUCLIDEAN));
// Alternatively flip the status's for documents as an example.
document.add(
new StringField(STATUS_FIELD, i % 2 == 0 ? "active" : "in-active", Field.Store.YES));
indexWriter.addDocument(document);
count -= 1;
if (count == 0) {
Expand Down Expand Up @@ -103,8 +110,11 @@ public static void main(String[] args) throws Exception {
float[] queryVector = generateDataset(random, 1, dimension)[0];
log.log(Level.FINE, "Query vector: " + Arrays.toString(queryVector));

// Make a filter query
Query filter = new TermQuery(new Term(STATUS_FIELD, "active"));

KnnFloatVectorQuery query =
new GPUKnnFloatVectorQuery(VECTOR_FIELD, queryVector, topK, null, topK, 1);
new GPUKnnFloatVectorQuery(VECTOR_FIELD, queryVector, topK, filter, topK, 1);
TopDocs results = searcher.search(query, topK);

log.log(Level.FINE, "Search results (" + results.totalHits + " total hits):");
Expand All @@ -113,12 +123,15 @@ public static void main(String[] args) throws Exception {
ScoreDoc scoreDoc = results.scoreDocs[i];
Document doc = searcher.storedFields().document(scoreDoc.doc);
String id = doc.get(ID_FIELD);
String status = doc.get(STATUS_FIELD);
log.log(
Level.FINE,
" Rank "
+ (i + 1)
+ ": doc "
+ scoreDoc.doc
+ " status "
+ status
+ " (id="
+ id
+ "), score="
Expand Down
4 changes: 4 additions & 0 deletions license-header.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
* SPDX-FileCopyrightText: Copyright (c) $YEAR, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
33 changes: 31 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<groupId>com.nvidia.cuvs.lucene</groupId>
<artifactId>cuvs-lucene</artifactId>
<!-- NOTE: The version automatically gets updated when ci/release/update-version.sh is invoked. -->
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0-SNAPSHOT</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
<name>cuvs-lucene</name>
<packaging>jar</packaging>

Expand All @@ -36,6 +36,18 @@
<url>https://github.com/rapidsai/cuvs-lucene</url>
</scm>

<developers>
<developer>
<id>rapidsai</id>
<name>rapidsai</name>
<email>cuvs@rapids.ai</email>
<roles>
<role>Maintainer</role>
<role>Committer</role>
</roles>
</developer>
</developers>

<properties>
<maven.compiler.target>22</maven.compiler.target>
<maven.compiler.source>22</maven.compiler.source>
Expand Down Expand Up @@ -68,6 +80,12 @@
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>central-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<snapshots><enabled>true</enabled></snapshots>
<releases><enabled>false</enabled></releases>
</repository>
</repositories>

<pluginRepositories>
Expand Down Expand Up @@ -129,10 +147,17 @@
<dependency>
<groupId>com.nvidia.cuvs</groupId>
<artifactId>cuvs-java</artifactId>
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
<!--CUVS_LUCENE#VERSION_UPDATE_MARKER_START--><version>26.08.0-SNAPSHOT</version><!--CUVS_LUCENE#VERSION_UPDATE_MARKER_END-->
</dependency>
</dependencies>

<distributionManagement>
<snapshotRepository>
<id>central</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>

<build>
<plugins>
<plugin>
Expand All @@ -158,6 +183,9 @@
<reflowLongStrings>true</reflowLongStrings>
<formatJavadoc>false</formatJavadoc>
</googleJavaFormat>
<licenseHeader>
<file>${project.basedir}/license-header.txt</file>
</licenseHeader>
</java>
</configuration>
</plugin>
Expand Down Expand Up @@ -219,6 +247,7 @@
<goal>jar</goal>
</goals>
<configuration>
<includePackageNames>com.nvidia.cuvs.lucene</includePackageNames>
<doclint>all,-missing</doclint>
<outputDirectory>${project.build.directory}/javadocs</outputDirectory>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.nvidia.cuvs.lucene;

import static com.nvidia.cuvs.lucene.ThreadLocalCuVSResourcesProvider.assertIsSupported;

import com.nvidia.cuvs.LibraryException;
import com.nvidia.cuvs.spi.CuVSProvider;
import java.io.IOException;
import org.apache.lucene.codecs.KnnVectorsFormat;
import org.apache.lucene.codecs.KnnVectorsReader;
Expand Down Expand Up @@ -39,6 +40,7 @@ public class CuVS2510GPUVectorsFormat extends KnnVectorsFormat {

static {
try {
CuVSProvider.provider().enableRMMAsyncMemory();
LUCENE_PROVIDER = LuceneProvider.getInstance("99");
FLAT_VECTORS_FORMAT =
LUCENE_PROVIDER.getLuceneFlatVectorsFormatInstance(DefaultFlatVectorScorer.INSTANCE);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.nvidia.cuvs.lucene;
Expand Down Expand Up @@ -443,6 +443,9 @@ public void search(String field, float[] target, KnnCollector knnCollector, Bits
new CagraSearchParams.Builder()
.withItopkSize(Math.max(collector.getiTopK(), topK))
.withSearchWidth(collector.getSearchWidth())
.withThreadBlockSize(collector.getThreadBlockSize())
.withMaxIterations(collector.getMaxIterations())
.withAlgo(collector.getSearchAlgo())
.build();
} else {
// Setting itopK as topK because in any case iTopK should be ATLEAST equal to topK
Expand Down Expand Up @@ -600,6 +603,37 @@ private static void checkVersion(int versionMeta, int versionVectorData, IndexIn
}
}

/**
* Maps a local segment ordinal to a Lucene doc ID within this segment.
*
* <p>Used by {@link GPUKnnFloatVectorQuery} after a multi-segment GPU search to convert
* select_k result ordinals to doc IDs before adding {@code docBase}.
*
* @param field the vector field name
* @param ordinal the local ordinal returned by CAGRA
* @return the Lucene doc ID within this segment
* @throws IOException if the vector values cannot be read
*/
public int ordToDoc(String field, int ordinal) throws IOException {
return flatVectorsReader.getFloatVectorValues(field).ordToDoc(ordinal);
}

/**
* Returns the {@link CagraIndex} for the given field, or {@code null} if unavailable
* (e.g., during a merge or when the field is missing).
*
* @param field the vector field name
* @return the CAGRA index, or {@code null}
*/
public CagraIndex getCagraIndexForField(String field) {
if (cuvsIndices == null) return null;
FieldInfo info = fieldInfos.fieldInfo(field);
if (info == null) return null;
GPUIndex gpuIndex = cuvsIndices.get(info.number);
if (gpuIndex == null) return null;
return gpuIndex.getCagraIndex();
}

/**
* Gets the instance of FieldInfos.
*
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/com/nvidia/cuvs/lucene/FilterBitsetCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.nvidia.cuvs.lucene;

import com.nvidia.cuvs.FilterBitsetHandle;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.lucene.search.Query;

/**
* Shared LRU cache mapping (filter Query, per-segment reader keys, field) → {@link
* FilterBitsetHandle}.
*
* <p>Host-side cache holding packed bitset arrays; the device-side upload is managed inside
* {@link FilterBitsetHandle} itself (per-thread LRU). Entries are evicted in LRU order and closed
* on eviction to free the host arrays and signal the device cache.
*/
final class FilterBitsetCache {

private static final int MAX_HOST_ENTRIES = 16;

private record FilterCacheKey(Query filter, List<Object> segReaderKeys, String field) {
@Override
public boolean equals(Object o) {
if (!(o instanceof FilterCacheKey other)) return false;
return Objects.equals(filter, other.filter)
&& Objects.equals(segReaderKeys, other.segReaderKeys)
&& Objects.equals(field, other.field);
}

@Override
public int hashCode() {
return Objects.hash(filter, segReaderKeys, field);
}
}

private static final LinkedHashMap<FilterCacheKey, FilterBitsetHandle> CACHE =
new LinkedHashMap<>(MAX_HOST_ENTRIES + 2, 0.75f, /* access-order= */ true) {
@Override
protected boolean removeEldestEntry(Map.Entry<FilterCacheKey, FilterBitsetHandle> eldest) {
if (size() > MAX_HOST_ENTRIES) {
eldest.getValue().close();
return true;
}
return false;
}
};

private FilterBitsetCache() {}

static synchronized FilterBitsetHandle get(
Query filter, List<Object> segReaderKeys, String field) {
return CACHE.get(new FilterCacheKey(filter, segReaderKeys, field));
}

static synchronized void put(
Query filter, List<Object> segReaderKeys, String field, FilterBitsetHandle handle) {
CACHE.put(new FilterCacheKey(filter, segReaderKeys, field), handle);
}
}
Loading
Loading