Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ The example below plugs the GPU-accelerated HNSW codec into a standard Lucene `I

Before running it, make sure cuVS is installed and available on your system library load path. The cuVS [tarball install instructions](https://docs.rapids.ai/api/cuvs/stable/build/#download-extract) show how to set this up.

### RMM async allocation for GPU search

Applications using `CuVS2510GPUSearchCodec` can opt into RMM's stream-ordered asynchronous device
allocator during startup:

```java
CuVSProvider.provider().enableRMMAsyncMemory();
```

Call this before creating any cuVS resources, codecs, writers, or readers. The setting affects the
entire process on the current CUDA device, so allocator policy belongs to the application rather
than an individual Lucene codec. Async allocation is optional for correctness and recommended for
GPU workloads with repeated device allocations, especially concurrent or multi-stream searches.
Applications that do not opt in use the default RMM device-memory resource.

In a Maven project that includes the `cuvs-lucene` dependency shown above, create `src/main/java/com/nvidia/cuvs/lucene/examples/HelloCuvsLucene.java`:

```java
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.benchmarks;
Expand All @@ -9,6 +9,7 @@
import static com.nvidia.cuvs.lucene.benchmarks.Utils.index;

import com.nvidia.cuvs.lucene.CuVS2510GPUSearchCodec;
import com.nvidia.cuvs.spi.CuVSProvider;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -57,6 +58,7 @@ public class CagraIndexingBenchmarks {

@Setup(Level.Trial)
public void setup() throws Exception {
CuVSProvider.provider().enableRMMAsyncMemory();
random = new Random(222);
indexDirPath = Paths.get(UUID.randomUUID().toString());
codec = new CuVS2510GPUSearchCodec();
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.benchmarks;
Expand All @@ -11,6 +11,7 @@

import com.nvidia.cuvs.lucene.CuVS2510GPUSearchCodec;
import com.nvidia.cuvs.lucene.GPUKnnFloatVectorQuery;
import com.nvidia.cuvs.spi.CuVSProvider;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -62,6 +63,7 @@ public class CagraSearchBenchmarks {

@Setup(Level.Trial)
public void setup() throws Exception {
CuVSProvider.provider().enableRMMAsyncMemory();
random = new Random(222);
indexDirPath = Paths.get(UUID.randomUUID().toString());
codec = new CuVS2510GPUSearchCodec();
Expand Down
17 changes: 1 addition & 16 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,7 @@ if hasArg --build-cuvs-java; then
pushd $CUVS_WORKDIR
fi

# libcuvs comes from the conda packages, so normally only the java bindings have
# to be built. For a pull request, the conda packages do not contain the PR's
# changes, so CI downloads the pre-built libcuvs artifact from the PR's own CI run.
CUVS_BUILD_TARGETS=("java")
if hasArg --use-pr-libcuvs && [[ "$BRANCH" == pull-request/* ]]; then
PR_NUM="${BRANCH#pull-request/}"
echo "Downloading libcuvs conda artifact from cuvs PR #${PR_NUM}..."
LIBCUVS_CONDA_DIR=$(rapids-get-pr-artifact NVIDIA/cuvs "$PR_NUM" cpp conda)
LIBCUVS_DIR=$(rapids-extract-conda-files "$LIBCUVS_CONDA_DIR")
# The downloaded library has to take precedence over the one provided by the
# conda packages, both here and while running the java tests.
LD_LIBRARY_PATH="$LIBCUVS_DIR/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export LD_LIBRARY_PATH
echo "LD_LIBRARY_PATH is: $LD_LIBRARY_PATH"
fi
./build.sh "${CUVS_BUILD_TARGETS[@]}"
./build.sh java
popd
fi

Expand Down
26 changes: 21 additions & 5 deletions ci/build_java.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ fi
# Always build cuvs-java when running the pipeline
EXTRA_BUILD_ARGS+=("--build-cuvs-java")

# When RAPIDS_BRANCH points at a cuvs pull request, the libcuvs from the conda
# packages does not contain the PR's changes, so download the pre-built artifact
# from the PR's CI run instead. Only done in CI, local builds use the conda packages.
EXTRA_BUILD_ARGS+=("--use-pr-libcuvs")

# shellcheck disable=SC1091
. /opt/conda/etc/profile.d/conda.sh

Expand Down Expand Up @@ -66,6 +61,27 @@ else
exit 1
fi

# When RAPIDS_BRANCH points at a cuvs PR, the conda packages do not contain the
# PR's changes, so download the pre-built libcuvs artifact from the PR's own CI run.
BRANCH=$(cat "RAPIDS_BRANCH")
if [[ "$BRANCH" == pull-request/* ]]; then
rapids-logger "Remove libcuvs from conda environment"
# Uninstall the conda libcuvs so the JVM's RPATH (which points to the conda
# env's lib dir) cannot find the old libcuvs_c.so ahead of the PR artifact.
set +u
conda remove --yes --force-remove libcuvs
set -u
# Download PR artifact
PR_NUM="${BRANCH#pull-request/}"
rapids-logger "Downloading libcuvs conda artifact from cuvs PR #${PR_NUM}"
LIBCUVS_CONDA_DIR=$(rapids-get-pr-artifact NVIDIA/cuvs "$PR_NUM" cpp conda)
LIBCUVS_ARTIFACT_DIR=$(rapids-extract-conda-files "$LIBCUVS_CONDA_DIR")
# The PR artifact must take precedence over the conda-installed libcuvs both
# at runtime and for cmake's find_package (to pick up new C API headers for jextract).
export LD_LIBRARY_PATH="$LIBCUVS_ARTIFACT_DIR/lib:$LD_LIBRARY_PATH"
export cuvs_ROOT="$LIBCUVS_ARTIFACT_DIR"
fi

rapids-logger "Run Java build"

bash ./build.sh "${EXTRA_BUILD_ARGS[@]}"
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 All @@ -10,6 +10,7 @@
import com.nvidia.cuvs.lucene.CuVS2510GPUSearchCodec;
import com.nvidia.cuvs.lucene.GPUKnnFloatVectorQuery;
import com.nvidia.cuvs.lucene.GPUSearchParams;
import com.nvidia.cuvs.spi.CuVSProvider;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand All @@ -30,9 +31,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 @@ -46,6 +50,9 @@ public class IndexAndSearchonGPUExample {

public static void main(String[] args) throws Exception {

// Select the process-wide RMM allocator before creating any cuVS resources or codecs.
CuVSProvider.provider().enableRMMAsyncMemory();

GPUSearchParams params = new GPUSearchParams.Builder().build();
Codec codec = new CuVS2510GPUSearchCodec(params);
IndexWriterConfig config = new IndexWriterConfig().setCodec(codec).setUseCompoundFile(false);
Expand All @@ -56,6 +63,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 +78,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 +114,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 +127,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
*/
29 changes: 29 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -133,6 +151,13 @@
</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,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 @@ -29,8 +29,11 @@ public class CuVS2510GPUSearchCodec extends FilterCodec {
* @throws Exception
*/
public CuVS2510GPUSearchCodec() throws Exception {
this(NAME, LuceneProvider.getCodec("101"));
initializeFormat(new GPUSearchParams.Builder().build());
this(
NAME,
LuceneProvider.getCodec("101"),
new GPUSearchParams.Builder().build(),
FilterBitsetCacheConfig.DEFAULT);
}

/**
Expand All @@ -41,8 +44,11 @@ public CuVS2510GPUSearchCodec() throws Exception {
* @param delegate the delegate codec
*/
public CuVS2510GPUSearchCodec(String name, Codec delegate) {
super(name, delegate);
initializeFormat(new GPUSearchParams.Builder().build());
this(
name,
delegate,
new GPUSearchParams.Builder().build(),
FilterBitsetCacheConfig.DEFAULT);
}

/**
Expand All @@ -53,18 +59,48 @@ public CuVS2510GPUSearchCodec(String name, Codec delegate) {
* @throws Exception Exception raised when initializing the codec
*/
public CuVS2510GPUSearchCodec(GPUSearchParams params) throws Exception {
this(NAME, LuceneProvider.getCodec("101"));
initializeFormat(params);
this(NAME, LuceneProvider.getCodec("101"), params, FilterBitsetCacheConfig.DEFAULT);
}

/**
* Initialize the codec with GPU search and filter-bitset-cache parameters.
*
* @param params GPU index and search parameters
* @param filterCacheConfig filter-bitset-cache configuration
* @throws Exception Exception raised when initializing the codec
*/
public CuVS2510GPUSearchCodec(
GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) throws Exception {
this(NAME, LuceneProvider.getCodec("101"), params, filterCacheConfig);
}

/**
* Initialize a named codec with explicit delegate, GPU search, and filter-cache parameters.
*
* @param name the name of the codec
* @param delegate the delegate codec
* @param params GPU index and search parameters
* @param filterCacheConfig filter-bitset-cache configuration
*/
public CuVS2510GPUSearchCodec(
String name,
Codec delegate,
GPUSearchParams params,
FilterBitsetCacheConfig filterCacheConfig) {
super(name, delegate);
initializeFormat(params, filterCacheConfig);
}

/**
* Initialize the {@link CuVS2510GPUVectorsFormat} instance using {@link GPUSearchParams}.
*
* @param params an instance of {@link GPUSearchParams}
* @param filterCacheConfig filter-bitset-cache configuration
*/
private void initializeFormat(GPUSearchParams params) {
private void initializeFormat(
GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) {
try {
format = new CuVS2510GPUVectorsFormat(params);
format = new CuVS2510GPUVectorsFormat(params, filterCacheConfig);
setKnnFormat(format);
} catch (LibraryException ex) {
log.log(
Expand Down
Loading
Loading