From f6101bbd7b05f8b3023514ab62d68d5eb1c4b6b5 Mon Sep 17 00:00:00 2001 From: Igor Motov Date: Thu, 15 Oct 2026 10:28:58 -1000 Subject: [PATCH 1/6] Prepare for supporting multiple Lucene versions This PR lays the groundwork for supporting multiple Lucene versions in the same release. It doesn't add any new Lucene versions and still releases only the Lucene 10.2 jar, but it creates the infrastructure for adding new versions in the future, adds backward compatibility tests, and makes sure the jar can still be loaded on JDK 21, where it builds indexes on the CPU only. The main code change is the replacement of the reflection-based `LuceneProvider` with the new `CuVSCodecs` class as the main entry point into the cuVS-Lucene ecosystem. `LuceneProvider` is now deprecated but will still be supported with Lucene 10.2. The biggest structural change is that each supported Lucene version will get a separate Maven module with its version-specific pieces. This PR splits `java/cuvs-lucene/src` into `java/cuvs-lucene/src` with the shared code and `java/cuvs-lucene/lucene-10.2/src` with the Lucene 10.2-specific pieces. Every version-specific module will have its own `LuceneCompat` class, which isolates the calls whose signature or location differs between Lucene releases, so that the shared code compiles against every supported version. The artifact is now published as `cuvs-lucene-10.2`, and the old `cuvs-lucene` coordinates are relocated to it. Relates to #2702 --- .github/workflows/build.yaml | 2 +- .github/workflows/pr.yaml | 28 +- .pre-commit-config.yaml | 2 +- build.sh | 4 +- ci/checks/run_spotless.sh | 2 +- ci/release/update-version.sh | 2 +- ci/test_lucene.sh | 12 +- ci/test_lucene_jdk21.sh | 93 +++++ dependencies.yaml | 11 + examples/java/cuvs-lucene/README.md | 3 + examples/java/cuvs-lucene/pom.xml | 10 +- .../examples/AcceleratedHnswExample.java | 4 +- .../examples/IndexAndSearchonGPUExample.java | 4 +- fern/docs.yml | 4 +- fern/pages/installation/java.md | 20 +- fern/pages/lucene_api/index.md | 2 +- ...nvidia-cuvs-lucene-acceleratedhnswutils.md | 24 +- ...idia-cuvs-lucene-cuvs2510gpusearchcodec.md | 71 +--- ...ia-cuvs-lucene-cuvs2510gpuvectorsformat.md | 14 +- ...ia-cuvs-lucene-cuvs2510gpuvectorsreader.md | 40 +- ...ia-cuvs-lucene-cuvs2510gpuvectorswriter.md | 22 +- ...e-api-com-nvidia-cuvs-lucene-cuvscodecs.md | 229 +++++++++++ ...vs-lucene-lucene101acceleratedhnswcodec.md | 59 +-- ...ne-lucene99acceleratedhnswvectorsformat.md | 12 +- ...ne-lucene99acceleratedhnswvectorswriter.md | 16 +- ...ceneacceleratedhnswbinaryquantizedcodec.md | 7 +- ...leratedhnswbinaryquantizedvectorsformat.md | 12 +- ...ceneacceleratedhnswscalarquantizedcodec.md | 7 +- ...leratedhnswscalarquantizedvectorsformat.md | 12 +- ...leratedhnswscalarquantizedvectorswriter.md | 16 +- ...i-com-nvidia-cuvs-lucene-luceneprovider.md | 15 - fern/pages/user_guide/lucene.md | 50 ++- fern/scripts/generate_api_reference.py | 2 + java/cuvs-lucene/README.md | 69 +++- java/cuvs-lucene/bench/pom.xml | 3 +- .../AcceleratedHnswIndexingBenchmarks.java | 14 +- .../AcceleratedHnswSearchBenchmarks.java | 14 +- .../benchmarks/CagraIndexingBenchmarks.java | 4 +- .../benchmarks/CagraSearchBenchmarks.java | 4 +- java/cuvs-lucene/build.sh | 21 +- java/cuvs-lucene/lucene-10.2/pom.xml | 36 ++ .../com/nvidia/cuvs/lucene/LuceneCompat.java | 60 +++ .../nvidia/cuvs/lucene/LuceneProvider.java | 16 + .../services/org.apache.lucene.codecs.Codec | 0 .../org.apache.lucene.codecs.KnnVectorsFormat | 2 - .../cuvs/lucene/TestLuceneProvider.java | 42 ++ java/cuvs-lucene/pom.xml | 385 +++++++++++++----- java/cuvs-lucene/relocation/pom.xml | 129 ++++++ .../src/assembly/jar-with-dependencies.xml | 32 ++ .../cuvs/lucene/AcceleratedHNSWUtils.java | 14 +- .../cuvs/lucene/CuVS2510GPUSearchCodec.java | 75 +--- .../cuvs/lucene/CuVS2510GPUVectorsFormat.java | 17 +- .../cuvs/lucene/CuVS2510GPUVectorsReader.java | 14 +- .../cuvs/lucene/CuVS2510GPUVectorsWriter.java | 14 +- .../com/nvidia/cuvs/lucene/CuVSCodecs.java | 152 +++++++ .../nvidia/cuvs/lucene/CuVSFilterCodec.java | 246 +++++++++++ .../cuvs/lucene/GPUKnnFloatVectorQuery.java | 1 + .../lucene/Lucene101AcceleratedHNSWCodec.java | 78 +--- .../Lucene99AcceleratedHNSWVectorsFormat.java | 50 +-- .../Lucene99AcceleratedHNSWVectorsWriter.java | 16 +- ...neAcceleratedHNSWBinaryQuantizedCodec.java | 56 +-- ...ratedHNSWBinaryQuantizedVectorsFormat.java | 46 +-- ...ratedHNSWBinaryQuantizedVectorsWriter.java | 6 +- ...neAcceleratedHNSWScalarQuantizedCodec.java | 60 +-- ...ratedHNSWScalarQuantizedVectorsFormat.java | 61 +-- ...ratedHNSWScalarQuantizedVectorsWriter.java | 16 +- .../cuvs/lucene/LuceneVersionGuard.java | 68 ++++ .../backcompat/GenerateReleasedIndices.java | 152 +++++++ .../backcompat/generate-released-indices.sh | 49 +++ .../BaseCuVSKnnVectorsFormatTestCase.java | 58 +++ .../TestAcceleratedHNSWOddGraphDegree.java | 2 +- .../nvidia/cuvs/lucene/TestBackCompat.java | 117 +++++- .../cuvs/lucene/TestBackCompatIndices.java | 302 ++++++++++++++ ...TestCagraToHnswSerializationAndSearch.java | 4 +- ...ializationAndSearchWithFallbackWriter.java | 2 +- .../cuvs/lucene/TestCuVSVectorsFormat.java | 48 +-- .../TestGPUSelectiveFilterFallback.java | 4 +- ...tLucene99AcceleratedHNSWVectorsFormat.java | 48 +-- .../TestMultiSegmentGPUFilterConcurrency.java | 5 +- .../lucene/TestPerSegmentGPUFilterSearch.java | 2 +- .../lucene/TestQuantizedFallbackWriters.java | 6 +- .../lucene/TestQuantizedVectorsFormats.java | 46 +-- .../lucene/TestSegmentMaxConnConsistency.java | 13 +- .../src/test/resources/backcompat/README.md | 44 ++ ...1-Lucene99AcceleratedHNSWVectorsFormat.zip | Bin 0 -> 12484 bytes ...101AcceleratedHNSWBinaryQuantizedCodec.zip | Bin 0 -> 14327 bytes .../10.2/Lucene101AcceleratedHNSWCodec.zip | Bin 0 -> 15868 bytes ...101AcceleratedHNSWScalarQuantizedCodec.zip | Bin 0 -> 17970 bytes ...101AcceleratedHNSWBinaryQuantizedCodec.zip | Bin 0 -> 15346 bytes .../10.2/Lucene101AcceleratedHNSWCodec.zip | Bin 0 -> 15890 bytes ...101AcceleratedHNSWScalarQuantizedCodec.zip | Bin 0 -> 18017 bytes 91 files changed, 2598 insertions(+), 936 deletions(-) create mode 100755 ci/test_lucene_jdk21.sh create mode 100644 fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md delete mode 100644 fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneprovider.md create mode 100644 java/cuvs-lucene/lucene-10.2/pom.xml create mode 100644 java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneCompat.java rename java/cuvs-lucene/{ => lucene-10.2}/src/main/java/com/nvidia/cuvs/lucene/LuceneProvider.java (93%) rename java/cuvs-lucene/{ => lucene-10.2}/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec (100%) rename java/cuvs-lucene/{ => lucene-10.2}/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat (72%) create mode 100644 java/cuvs-lucene/lucene-10.2/src/test/java/com/nvidia/cuvs/lucene/TestLuceneProvider.java create mode 100644 java/cuvs-lucene/relocation/pom.xml create mode 100644 java/cuvs-lucene/src/assembly/jar-with-dependencies.xml create mode 100644 java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java create mode 100644 java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java create mode 100644 java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneVersionGuard.java create mode 100644 java/cuvs-lucene/src/test/backcompat/GenerateReleasedIndices.java create mode 100755 java/cuvs-lucene/src/test/backcompat/generate-released-indices.sh create mode 100644 java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/BaseCuVSKnnVectorsFormatTestCase.java create mode 100644 java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompatIndices.java create mode 100644 java/cuvs-lucene/src/test/resources/backcompat/README.md create mode 100644 java/cuvs-lucene/src/test/resources/backcompat/releases/25.12.0/10.2/Lucene101-Lucene99AcceleratedHNSWVectorsFormat.zip create mode 100644 java/cuvs-lucene/src/test/resources/backcompat/releases/26.08.0/10.2/Lucene101AcceleratedHNSWBinaryQuantizedCodec.zip create mode 100644 java/cuvs-lucene/src/test/resources/backcompat/releases/26.08.0/10.2/Lucene101AcceleratedHNSWCodec.zip create mode 100644 java/cuvs-lucene/src/test/resources/backcompat/releases/26.08.0/10.2/Lucene101AcceleratedHNSWScalarQuantizedCodec.zip create mode 100644 java/cuvs-lucene/src/test/resources/backcompat/releases/26.10.0/10.2/Lucene101AcceleratedHNSWBinaryQuantizedCodec.zip create mode 100644 java/cuvs-lucene/src/test/resources/backcompat/releases/26.10.0/10.2/Lucene101AcceleratedHNSWCodec.zip create mode 100644 java/cuvs-lucene/src/test/resources/backcompat/releases/26.10.0/10.2/Lucene101AcceleratedHNSWScalarQuantizedCodec.zip diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e1bfb24038..57ba9a9724 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -147,7 +147,7 @@ jobs: matrix_filter: map(select(.ARCH == "amd64")) script: "ci/build_lucene.sh" artifact-name-prefix: "cuvs-lucene" - file_to_upload: "java/cuvs-lucene/target/" + file_to_upload: "java/cuvs-lucene/*/target/" python-build: needs: [build-details, cpp-build] permissions: diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 2412d6e463..d02e497ce0 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -27,6 +27,7 @@ jobs: - conda-java-tests - conda-lucene-build - conda-lucene-tests + - conda-lucene-tests-jdk21 - rust-build - go-build - docs-build @@ -240,6 +241,7 @@ jobs: - '!ci/test_cpp.sh' - '!ci/test_java.sh' - '!ci/test_lucene.sh' + - '!ci/test_lucene_jdk21.sh' - '!ci/test_standalone_c.sh' - '!ci/test_wheel_cuvs.sh' - '!ci/validate_wheel.sh' @@ -296,6 +298,7 @@ jobs: - '!ci/test_cpp.sh' - '!ci/test_java.sh' - '!ci/test_lucene.sh' + - '!ci/test_lucene_jdk21.sh' - '!ci/test_python.sh' - '!ci/test_standalone_c.sh' - '!ci/validate_wheel.sh' @@ -356,6 +359,7 @@ jobs: - '!ci/test_cpp.sh' - '!ci/test_java.sh' - '!ci/test_lucene.sh' + - '!ci/test_lucene_jdk21.sh' - '!ci/test_python.sh' - '!ci/test_wheel_cuvs.sh' - '!ci/validate_wheel.sh' @@ -409,6 +413,7 @@ jobs: - '!ci/test_cpp.sh' - '!ci/test_java.sh' - '!ci/test_lucene.sh' + - '!ci/test_lucene_jdk21.sh' - '!ci/test_python.sh' - '!ci/test_standalone_c.sh' - '!ci/test_wheel_cuvs.sh' @@ -636,7 +641,7 @@ jobs: matrix_filter: map(select(.ARCH == "amd64")) script: "ci/build_lucene.sh" artifact-name-prefix: "cuvs-lucene" - file_to_upload: "java/cuvs-lucene/target/" + file_to_upload: "java/cuvs-lucene/*/target/" conda-lucene-tests: needs: conda-lucene-build permissions: @@ -649,6 +654,27 @@ jobs: with: build_type: pull-request script: "ci/test_lucene.sh" + conda-lucene-tests-jdk21: + # Verifies that cuvs-lucene, compiled with --release 21, loads and works without the GPU on + # JDK 21, the oldest JDK it supports, by running its test suite against the amd64-built + # classes without recompiling -- see ci/test_lucene_jdk21.sh. It runs once, not per CUDA + # version: it uses no GPU, so the artifacts of any amd64 entry of the build matrix will do. + # This matters because many applications built on Lucene 10 support JDK 21 as their minimum. + # cuvs-lucene only uses the GPU on JDK 22+, but it can't stop such an application from starting + # on JDK 21, so there it has to load and fall back to the CPU. + needs: conda-lucene-build + permissions: + actions: read + contents: read + id-token: write + packages: read + pull-requests: read + uses: ./.github/workflows/matrix-job.yaml + with: + build_type: pull-request + node_type: cpu8 + matrix_filter: '[map(select(.ARCH == "amd64")) | max_by(.CUDA_VER | split(".") | map(tonumber))]' + script: "ci/test_lucene_jdk21.sh" rust-build: needs: [conda-cpp-build, changed-files] permissions: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 02bf88d226..fdc28622f7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -121,7 +121,7 @@ repos: name: spotless-fmt entry: ci/checks/run_spotless.sh pass_filenames: false - files: ^(java|examples/java)/(cuvs-java|cuvs-lucene)/([^/]+/)?src/.*\.java$ + files: ^(java|examples/java)/(cuvs-java|cuvs-lucene)/([^/]+/){0,2}src/.*\.java$ exclude: .*/panama/.* language: script verbose: true diff --git a/build.sh b/build.sh index 40ffefdc87..3b5f4a1472 100755 --- a/build.sh +++ b/build.sh @@ -81,7 +81,7 @@ RUST_BUILD_DIR=${REPODIR}/rust/target JAVA_INTERNAL_BUILD_DIR=${REPODIR}/java/internal/build JAVA_BUILD_DIR=${REPODIR}/java/cuvs-java/target JAVA_PANAMA_DIR=${REPODIR}/java/cuvs-java/src/main/java22/com/nvidia/cuvs/internal/panama -LUCENE_BUILD_DIR=${REPODIR}/java/cuvs-lucene/target +LUCENE_BUILD_DIRS=("${REPODIR}"/java/cuvs-lucene/target "${REPODIR}"/java/cuvs-lucene/*/target) BUILD_DIRS=( "${LIBCUVS_BUILD_DIR}" "${PYTHON_BUILD_DIR}" @@ -89,7 +89,7 @@ BUILD_DIRS=( "${JAVA_INTERNAL_BUILD_DIR}" "${JAVA_BUILD_DIR}" "${JAVA_PANAMA_DIR}" - "${LUCENE_BUILD_DIR}" + "${LUCENE_BUILD_DIRS[@]}" ) # Set defaults for vars modified by flags to this script diff --git a/ci/checks/run_spotless.sh b/ci/checks/run_spotless.sh index da18413a8b..f588f60757 100755 --- a/ci/checks/run_spotless.sh +++ b/ci/checks/run_spotless.sh @@ -15,7 +15,7 @@ set -euo pipefail # Keep these in sync with the spotless-fmt hook's 'files'/'exclude' entries in # .pre-commit-config.yaml. -JAVA_SRC_PATTERN='^(java|examples/java)/(cuvs-java|cuvs-lucene)/([^/]+/)?src/.*\.java$' +JAVA_SRC_PATTERN='^(java|examples/java)/(cuvs-java|cuvs-lucene)/([^/]+/){0,2}src/.*\.java$' JAVA_SRC_EXCLUDE='.*/panama/.*' java_sources_modified() { diff --git a/ci/release/update-version.sh b/ci/release/update-version.sh index 5eef5500c5..dde2078f6e 100755 --- a/ci/release/update-version.sh +++ b/ci/release/update-version.sh @@ -172,7 +172,7 @@ done NEXT_FULL_JAVA_TAG="${NEXT_SHORT_TAG}.${PATCH_PEP440}" sed_runner "s/VERSION=\".*\"/VERSION=\"${NEXT_FULL_JAVA_TAG}\"/g" java/build.sh sed_runner "s/VERSION=\".*\"/VERSION=\"${NEXT_FULL_JAVA_TAG}\"/g" java/cuvs-lucene/build.sh -for FILE in java/*/pom.xml java/cuvs-lucene/bench/pom.xml examples/java/cuvs-java/pom.xml examples/java/cuvs-lucene/pom.xml; do +for FILE in java/*/pom.xml java/cuvs-lucene/lucene-*/pom.xml java/cuvs-lucene/relocation/pom.xml java/cuvs-lucene/bench/pom.xml examples/java/cuvs-java/pom.xml examples/java/cuvs-lucene/pom.xml; do sed_runner "/.*/s//${NEXT_FULL_JAVA_TAG}<\/version>/g" "${FILE}" done diff --git a/ci/test_lucene.sh b/ci/test_lucene.sh index 94d3d591fb..11a35d0c57 100755 --- a/ci/test_lucene.sh +++ b/ci/test_lucene.sh @@ -79,11 +79,11 @@ pushd java/cuvs-lucene mvn --batch-mode install:install-file -Dfile="${CUVS_JAVA_JARS[0]}" -DpomFile="${CUVS_JAVA_POM}" popd -rapids-logger "Restore the amd64-built cuvs-lucene target/ directory so Maven can run the tests without recompiling" +rapids-logger "Restore the amd64-built cuvs-lucene target/ directories so Maven can run the tests without recompiling" -rm -rf java/cuvs-lucene/target -mkdir -p java/cuvs-lucene/target -cp -a "${CUVS_LUCENE_DIR}/." java/cuvs-lucene/target/ +# The artifact holds one /target/ per module. +rm -rf java/cuvs-lucene/*/target +cp -a "${CUVS_LUCENE_DIR}/." java/cuvs-lucene/ EXITCODE=0 trap "EXITCODE=1" ERR @@ -93,9 +93,9 @@ rapids-logger "Run cuvs-lucene tests against the amd64-built classes" # -Dskip.compile activates the pom's "skip-compile" profile, which disables all # compilation for this run, forcing test to use amd64-compiled jar instead of -# local code. +# local code. --fail-at-end tests every module even if an earlier one fails. pushd java/cuvs-lucene -mvn --batch-mode test -Dskip.compile=true +mvn --batch-mode --fail-at-end test -Dskip.compile=true popd rapids-logger "Test script exiting with value: $EXITCODE" diff --git a/ci/test_lucene_jdk21.sh b/ci/test_lucene_jdk21.sh new file mode 100755 index 0000000000..13899fc173 --- /dev/null +++ b/ci/test_lucene_jdk21.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +# Runs cuvs-lucene's test suite on JDK 21 against the classes from a prior amd64 build job, +# without recompiling. cuvs-lucene is compiled with --release 21 so that applications on JDK 21 +# (Solr, for example) can load it, but cuvs-java needs JDK 22+ to reach the GPU. On JDK 21 the +# GPU tests skip themselves, and what runs checks that the codecs still load, read existing +# indexes and fall back to the CPU. None of that depends on the GPU or on the Lucene release, so +# this runs on a CPU node and tests only the newest Lucene module. + +CUVS_JAVA_ARTIFACT="cuvs-java-cuda${RAPIDS_CUDA_VERSION}" +CUVS_LUCENE_ARTIFACT="cuvs-lucene-cuda${RAPIDS_CUDA_VERSION}" + +if [ -e "/opt/conda/etc/profile.d/conda.sh" ]; then + . /opt/conda/etc/profile.d/conda.sh +fi + +rapids-logger "Configuring conda strict channel priority" +conda config --set channel_priority strict + +rapids-logger "Downloading artifacts from previous jobs" +CUVS_JAVA_DIR=$(rapids-download-from-github "${CUVS_JAVA_ARTIFACT}") +CUVS_LUCENE_DIR=$(rapids-download-from-github "${CUVS_LUCENE_ARTIFACT}") + +rapids-logger "Generate JDK 21 testing dependencies" + +ENV_YAML_DIR="$(mktemp -d)" + +rapids-dependency-file-generator \ + --output conda \ + --file-key java_jdk21 \ + --matrix "" | tee "${ENV_YAML_DIR}/env.yaml" + +rapids-mamba-retry env create --yes -f "${ENV_YAML_DIR}/env.yaml" -n java_jdk21 + +# Temporarily allow unbound variables for conda activation. +set +u +conda activate java_jdk21 +set -u + +rapids-print-env + +# Maven, and the JVM surefire forks for the tests, run on the JDK of JAVA_HOME rather than the java +# on PATH, so check the one Maven reports. +MAVEN_JAVA_VERSION="$(cd java/cuvs-lucene && mvn --batch-mode -v | sed -n 's/^Java version: \([0-9]*\).*/\1/p')" +if [ "${MAVEN_JAVA_VERSION}" != "21" ]; then + echo "Expected Maven to run on JDK 21, but it runs on JDK '${MAVEN_JAVA_VERSION}'" >&2 + exit 1 +fi + +rapids-logger "Install the amd64-built cuvs-java artifact into the local Maven repository" + +CUVS_JAVA_POM="${CUVS_JAVA_DIR}/pom.xml" +if [ ! -f "${CUVS_JAVA_POM}" ]; then + echo "Could not find pom.xml in the cuvs-java artifact at ${CUVS_JAVA_DIR}" >&2 + exit 1 +fi + +mapfile -t CUVS_JAVA_JARS < <(find "${CUVS_JAVA_DIR}" -maxdepth 1 -name 'cuvs-java-*.jar' \ + ! -name '*-sources.jar' ! -name '*-javadoc.jar' ! -name '*-tests.jar' ! -name '*-cuda*.jar') +if [ "${#CUVS_JAVA_JARS[@]}" -ne 1 ]; then + echo "Expected exactly one cuvs-java jar in ${CUVS_JAVA_DIR}, found: ${CUVS_JAVA_JARS[*]:-none}" >&2 + exit 1 +fi + +# cd is needed to pick up pom.xml in order to avoid rate limit of main maven repo +pushd java/cuvs-lucene +mvn --batch-mode install:install-file -Dfile="${CUVS_JAVA_JARS[0]}" -DpomFile="${CUVS_JAVA_POM}" +popd + +rapids-logger "Restore the amd64-built cuvs-lucene target/ directories so Maven can run the tests without recompiling" + +rm -rf java/cuvs-lucene/*/target +cp -a "${CUVS_LUCENE_DIR}/." java/cuvs-lucene/ + +EXITCODE=0 +trap "EXITCODE=1" ERR +set +e + +rapids-logger "Run cuvs-lucene tests on JDK 21 against the amd64-built classes" + +# -Dskip.compile disables all compilation, see ci/test_lucene.sh. +pushd java/cuvs-lucene +NEWEST_MODULE="$(find . -maxdepth 1 -type d -name 'lucene-*' -printf '%f\n' | sort -V | tail -1)" +rapids-logger "Testing ${NEWEST_MODULE}" +mvn --batch-mode test -Dskip.compile=true -pl "${NEWEST_MODULE}" +popd + +rapids-logger "Test script exiting with value: $EXITCODE" +exit ${EXITCODE} diff --git a/dependencies.yaml b/dependencies.yaml index bedafbb05f..27b1d9a921 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -120,6 +120,10 @@ files: - depends_on_nccl - java - rapids_build + java_jdk21: + output: none + includes: + - java_jdk21 py_build_libcuvs: output: pyproject pyproject_dir: python/libcuvs @@ -658,6 +662,13 @@ dependencies: - libboost-devel - maven - *openjdk + java_jdk21: + common: + - output_types: conda + packages: + - maven + # The oldest JDK cuvs-lucene supports; cuvs-java needs 22+ to use the GPU. + - openjdk=21.* bench: common: - output_types: [conda, pyproject, requirements] diff --git a/examples/java/cuvs-lucene/README.md b/examples/java/cuvs-lucene/README.md index 960efb97d8..19686a9b0e 100644 --- a/examples/java/cuvs-lucene/README.md +++ b/examples/java/cuvs-lucene/README.md @@ -15,6 +15,9 @@ First build `cuvs-lucene` and install it into your local Maven repository, as de ./build.sh libcuvs java lucene ``` +The examples use Lucene 10.2 and the matching `cuvs-lucene-10.2` artifact, set by the `lucene.version` and +`cuvs.lucene.artifactId` properties in `pom.xml`. + Then return to this directory: ```sh diff --git a/examples/java/cuvs-lucene/pom.xml b/examples/java/cuvs-lucene/pom.xml index fa3207f1a2..82c33208e0 100644 --- a/examples/java/cuvs-lucene/pom.xml +++ b/examples/java/cuvs-lucene/pom.xml @@ -19,6 +19,8 @@ 22 UTF-8 UTF-8 + 10.2.0 + cuvs-lucene-10.2 @@ -79,18 +81,18 @@ org.apache.lucene lucene-core - 10.2.0 + ${lucene.version} org.apache.lucene lucene-codecs - 10.2.0 + ${lucene.version} test org.apache.lucene lucene-backward-codecs - 10.2.0 + ${lucene.version} commons-io @@ -99,7 +101,7 @@ com.nvidia.cuvs.lucene - cuvs-lucene + ${cuvs.lucene.artifactId} 26.12.0 diff --git a/examples/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/examples/AcceleratedHnswExample.java b/examples/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/examples/AcceleratedHnswExample.java index d9fbffe9f0..3c963b87f3 100644 --- a/examples/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/examples/AcceleratedHnswExample.java +++ b/examples/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/examples/AcceleratedHnswExample.java @@ -8,7 +8,7 @@ import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; import com.nvidia.cuvs.lucene.AcceleratedHNSWParams; -import com.nvidia.cuvs.lucene.Lucene101AcceleratedHNSWCodec; +import com.nvidia.cuvs.lucene.CuVSCodecs; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; @@ -46,7 +46,7 @@ public class AcceleratedHnswExample { public static void main(String[] args) throws Exception { AcceleratedHNSWParams params = new AcceleratedHNSWParams.Builder().build(); - Codec codec = new Lucene101AcceleratedHNSWCodec(params); + Codec codec = CuVSCodecs.acceleratedHNSW(params); IndexWriterConfig config = new IndexWriterConfig().setCodec(codec).setUseCompoundFile(false); random = new Random(222); diff --git a/examples/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/examples/IndexAndSearchonGPUExample.java b/examples/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/examples/IndexAndSearchonGPUExample.java index 1fd0a39f1f..5116722197 100644 --- a/examples/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/examples/IndexAndSearchonGPUExample.java +++ b/examples/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/examples/IndexAndSearchonGPUExample.java @@ -7,7 +7,7 @@ import static com.nvidia.cuvs.lucene.examples.Utils.generateDataset; import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; -import com.nvidia.cuvs.lucene.CuVS2510GPUSearchCodec; +import com.nvidia.cuvs.lucene.CuVSCodecs; import com.nvidia.cuvs.lucene.GPUKnnFloatVectorQuery; import com.nvidia.cuvs.lucene.GPUSearchParams; import com.nvidia.cuvs.spi.CuVSProvider; @@ -54,7 +54,7 @@ public static void main(String[] args) throws Exception { CuVSProvider.provider().enableRMMAsyncMemory(); GPUSearchParams params = new GPUSearchParams.Builder().build(); - Codec codec = new CuVS2510GPUSearchCodec(params); + Codec codec = CuVSCodecs.gpuSearch(params); IndexWriterConfig config = new IndexWriterConfig().setCodec(codec).setUseCompoundFile(false); random = new Random(222); diff --git a/fern/docs.yml b/fern/docs.yml index 77ee0dae2a..4708dc7649 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -567,6 +567,8 @@ navigation: path: "./pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-acceleratedhnswutils.md" - page: "CagraIndexParamsFactory" path: "./pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cagraindexparamsfactory.md" + - page: "CuVSCodecs" + path: "./pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md" - page: "FilterBitsetCacheConfig" path: "./pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-filterbitsetcacheconfig.md" - page: "FilterCuVSServiceProvider" @@ -575,8 +577,6 @@ navigation: path: "./pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-gpuindex.md" - page: "GPUSearchParams" path: "./pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-gpusearchparams.md" - - page: "LuceneProvider" - path: "./pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneprovider.md" - page: "ThreadLocalCuVSResourcesProvider" path: "./pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-threadlocalcuvsresourcesprovider.md" - page: "Utils" diff --git a/fern/pages/installation/java.md b/fern/pages/installation/java.md index cb91be0a9e..972d112e66 100644 --- a/fern/pages/installation/java.md +++ b/fern/pages/installation/java.md @@ -69,22 +69,30 @@ java/panama-bindings/generate-bindings.sh `cuvs-lucene` provides Apache Lucene codecs that offload vector index build, and optionally search, to the GPU. It is published as a separate artifact that builds on the Java API described above. For usage guidance, see the [Lucene Integration](/user-guide/lucene) guide. -`cuvs-lucene` targets Lucene 10.2, requires JDK 22 and Maven 3.9.6 or newer, and inherits the NVIDIA cuVS [CUDA GPU requirements](/installation#cuda-gpu-requirements). +`cuvs-lucene` supports Lucene 10.2 and requires Maven 3.9.6 or newer. It compiles and runs on JDK 21, the minimum of Lucene 10, but uses the GPU only on a JDK 22 or newer runtime: on JDK 21 the accelerated HNSW codecs build graphs on the CPU and the GPU search codec is unavailable. It inherits the NVIDIA cuVS [CUDA GPU requirements](/installation#cuda-gpu-requirements). -`Lucene101AcceleratedHNSWCodec` falls back to CPU index construction when no GPU or native library is available, so an application using it works on GPU and non-GPU hosts alike. The other three codecs require a working cuVS installation. See [Lucene Integration](/user-guide/lucene) for details. +The three accelerated HNSW codecs (plain, scalar-quantized and binary-quantized) fall back to CPU index construction when no GPU or native library is available, so an application using them works on GPU and non-GPU hosts alike. The GPU search codec requires a working cuVS installation. See [Lucene Integration](/user-guide/lucene) for details. ### Add the Maven Dependency -To use `cuvs-lucene` in a Maven project, add the following dependency to your `pom.xml`: +Lucene changes its codec APIs between minor releases, so `cuvs-lucene` is published as one artifact per supported Lucene minor release, named `cuvs-lucene-.`. Pick the one that matches the Lucene version your application uses: + +| Lucene | Artifact | +| --- | --- | +| 10.2.x | `cuvs-lucene-10.2` | + +For Lucene 10.2, add the following dependency to your `pom.xml`: ```xml com.nvidia.cuvs.lucene - cuvs-lucene - 26.10.0 + cuvs-lucene-10.2 + 26.12.0 ``` +Against any other Lucene minor release, an artifact's codecs fail when they are used, with an error naming the artifact to use instead, and the problem is logged at `SEVERE` the first time Lucene looks up its codecs. Call `CuVSCodecs.checkLuceneVersion()` at startup to fail early instead. Releases before 26.12 published a single `cuvs-lucene` artifact, built for Lucene 10.2. Its coordinates are relocated to `cuvs-lucene-10.2`, so Maven builds that only bump its version to 26.12 or later keep working and print a warning; switch to the artifact matching your Lucene version. Build tools other than Maven may not follow the relocation; update the artifact name there. + The native NVIDIA cuVS libraries are not bundled with the artifact. Install a matching version of `libcuvs` and `libcuvs_c` as described above, and make sure the directory containing them is on `LD_LIBRARY_PATH` before starting the JVM. ### Build cuVS Lucene From Source @@ -108,6 +116,6 @@ cd java/cuvs-lucene ./build.sh ``` -The resulting artifacts are written to `java/cuvs-lucene/target`. +This builds and installs every `cuvs-lucene-10.X` artifact into the local Maven repository. The resulting artifacts are written to `java/cuvs-lucene/lucene-10.X/target`. Add `--run-java-tests` to any of these commands to run the test suite, and `--build-java-examples` to build the example projects against the jars that were just built. Each target builds only its own examples: the `lucene` target builds `examples/java/cuvs-lucene`, and the `java` target builds `examples/java/cuvs-java`. diff --git a/fern/pages/lucene_api/index.md b/fern/pages/lucene_api/index.md index 3acfc3b798..62fe249776 100644 --- a/fern/pages/lucene_api/index.md +++ b/fern/pages/lucene_api/index.md @@ -9,11 +9,11 @@ For an introduction to the codecs, configuration, and tuning, see the [Lucene In - [AcceleratedHNSWParams](/api-reference/lucene-api-com-nvidia-cuvs-lucene-acceleratedhnswparams) - [AcceleratedHNSWUtils](/api-reference/lucene-api-com-nvidia-cuvs-lucene-acceleratedhnswutils) - [CagraIndexParamsFactory](/api-reference/lucene-api-com-nvidia-cuvs-lucene-cagraindexparamsfactory) +- [CuVSCodecs](/api-reference/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs) - [FilterBitsetCacheConfig](/api-reference/lucene-api-com-nvidia-cuvs-lucene-filterbitsetcacheconfig) - [FilterCuVSServiceProvider](/api-reference/lucene-api-com-nvidia-cuvs-lucene-filtercuvsserviceprovider) - [GPUIndex](/api-reference/lucene-api-com-nvidia-cuvs-lucene-gpuindex) - [GPUSearchParams](/api-reference/lucene-api-com-nvidia-cuvs-lucene-gpusearchparams) -- [LuceneProvider](/api-reference/lucene-api-com-nvidia-cuvs-lucene-luceneprovider) - [ThreadLocalCuVSResourcesProvider](/api-reference/lucene-api-com-nvidia-cuvs-lucene-threadlocalcuvsresourcesprovider) - [Utils](/api-reference/lucene-api-com-nvidia-cuvs-lucene-utils) diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-acceleratedhnswutils.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-acceleratedhnswutils.md index 4331a73bc6..6ed4a1556e 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-acceleratedhnswutils.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-acceleratedhnswutils.md @@ -21,7 +21,7 @@ public static GPUBuiltHnswGraph createSingleVectorHnswGraph(int size, int dimens Creates a dummy HNSW graph for a single vector. The graph will have 1 level with 1 node and no neighbors. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:55`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:47`_ ### createMultiLayerHnswGraph @@ -33,7 +33,7 @@ Creates up to `hnswLayers` total layers. Layer 0 uses the full CAGRA graph. Each layer samples `max(2, floor(previousLayerSize / M))` nodes and is built with graph degree at most `M`. The value `M` is the ceiling of half the layer-0 graph degree. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:73`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:65`_ ### createMultiLayerHnswGraph @@ -54,7 +54,7 @@ copies them into arrays of maxConn + 1 slots and fails with "No growth is allowe | --- | --- | | `maxConn` | the HNSW maxConn the segment is written with | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:230`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:222`_ ### writeGraph @@ -81,7 +81,7 @@ a 2D array of offsets | --- | --- | | `IOException` | I/O Exceptions | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:360`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:352`_ ### writeMeta @@ -114,7 +114,7 @@ than the maxConn of a CPU writer merging the segment, which makes that merge fai | --- | --- | | `IOException` | I/O Exceptions | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:428`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:420`_ ### writeMeta @@ -144,7 +144,7 @@ Writes the meta information for the index. | --- | --- | | `IOException` | I/O Exceptions | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:465`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:457`_ ### printInfoStream @@ -160,7 +160,7 @@ A utility method to print info/debugging messages using InfoStream. | --- | --- | | `msg` | the debugging message to print | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:552`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:544`_ ### writeEmpty @@ -185,7 +185,7 @@ overload that also takes maxConn. | --- | --- | | `IOException` | I/O Exceptions | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:566`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:558`_ ### writeEmpty @@ -208,7 +208,7 @@ Writes an empty meta information for the field. | --- | --- | | `IOException` | I/O Exceptions | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:578`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:570`_ ### quantizeFloatVectorsToBinary @@ -231,7 +231,7 @@ Bits are packed: 8 dimensions per byte. A list of byte binary representation for the input vectors -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:592`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:584`_ ### quantizeFloatVectorsToScalar @@ -251,6 +251,6 @@ Scalar quantization to unsigned 7-bit values stored in Java bytes. A list of byte scalar representation for the input vectors -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:634`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:626`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:32`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java:33`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpusearchcodec.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpusearchcodec.md index c7120dc9d4..4f0b9f11b2 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpusearchcodec.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpusearchcodec.md @@ -7,29 +7,26 @@ slug: api-reference/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpusearchcodec _Java package: `com.nvidia.cuvs.lucene`_ ```java -public class CuVS2510GPUSearchCodec extends FilterCodec +public class CuVS2510GPUSearchCodec extends CuVSFilterCodec ``` cuVS based codec for GPU based vector search that enables both - indexing and search on the GPU. cuVS serialization formats are in experimental phase and hence backward compatibility cannot be guaranteed. +This codec wraps `Lucene101Codec`, the default codec of Lucene 10.2. `CuVSCodecs#gpuSearch` +returns the codec of this family for the Lucene release in use. + ## Public Members ### CuVS2510GPUSearchCodec ```java -public CuVS2510GPUSearchCodec() throws Exception +public CuVS2510GPUSearchCodec() ``` Default constructor for `CuVS2510GPUSearchCodec`. -**Throws** - -| Type | Description | -| --- | --- | -| `Exception` | | - -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:30`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:24`_ ### CuVS2510GPUSearchCodec @@ -47,12 +44,12 @@ having default parameter values. | `name` | the name of the codec | | `delegate` | the delegate codec | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:45`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:35`_ ### CuVS2510GPUSearchCodec ```java -public CuVS2510GPUSearchCodec(GPUSearchParams params) throws Exception +public CuVS2510GPUSearchCodec(GPUSearchParams params) ``` Initialize the codec with an instance of `GPUSearchParams` having either default @@ -64,18 +61,12 @@ or overridden parameter values. | --- | --- | | `params` | An instance of `GPUSearchParams` | -**Throws** - -| Type | Description | -| --- | --- | -| `Exception` | Exception raised when initializing the codec | - -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:56`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:45`_ ### CuVS2510GPUSearchCodec ```java -public CuVS2510GPUSearchCodec(GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) throws Exception +public CuVS2510GPUSearchCodec(GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) ``` Initialize the codec with GPU search and filter-bitset-cache parameters. @@ -87,13 +78,7 @@ Initialize the codec with GPU search and filter-bitset-cache parameters. | `params` | GPU index and search parameters | | `filterCacheConfig` | filter-bitset-cache configuration | -**Throws** - -| Type | Description | -| --- | --- | -| `Exception` | Exception raised when initializing the codec | - -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:67`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:55`_ ### CuVS2510GPUSearchCodec @@ -112,36 +97,6 @@ Initialize a named codec with explicit delegate, GPU search, and filter-cache pa | `params` | GPU index and search parameters | | `filterCacheConfig` | filter-bitset-cache configuration | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:80`_ - -### knnVectorsFormat - -```java -@Override public KnnVectorsFormat knnVectorsFormat() -``` - -Get the configured `KnnVectorsFormat`. - -**Returns** - -the instance of the `KnnVectorsFormat` - -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:111`_ - -### setKnnFormat - -```java -public void setKnnFormat(KnnVectorsFormat format) -``` - -Set the `KnnVectorsFormat`. - -**Parameters** - -| Name | Description | -| --- | --- | -| `format` | the `KnnVectorsFormat` to set | - -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:121`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:70`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:20`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java:18`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorsformat.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorsformat.md index b747d12460..1a49ab1a01 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorsformat.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorsformat.md @@ -29,7 +29,7 @@ Initializes the `CuVS2510GPUVectorsFormat` with default parameter values. | --- | --- | | `LibraryException` | if the native library fails to load | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:55`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:46`_ ### CuVS2510GPUVectorsFormat @@ -51,7 +51,7 @@ Initializes the `CuVS2510GPUVectorsFormat` with an instance of `GPUSearchParams` | --- | --- | | `LibraryException` | if the native library fails to load | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:65`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:56`_ ### CuVS2510GPUVectorsFormat @@ -74,7 +74,7 @@ Initializes the format with GPU search and filter-bitset-cache parameters. | --- | --- | | `LibraryException` | if the native library fails to load | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:76`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:67`_ ### fieldsWriter @@ -84,7 +84,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Returns a KnnVectorsReader instance to write the vectors to the index. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:86`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:77`_ ### fieldsReader @@ -94,7 +94,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Returns a KnnVectorsReader instance to read the vectors from the index. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:96`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:88`_ ### getMaxDimensions @@ -104,6 +104,6 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Returns the maximum number of vector dimensions supported by this codec for the given field name. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:106`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:99`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:25`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java:26`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorsreader.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorsreader.md index a4ec97ad2b..8ff135ea25 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorsreader.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorsreader.md @@ -35,7 +35,7 @@ Initializes the `CuVS2510GPUVectorsReader`, checks and loads the index. | --- | --- | | `IOException` | I/O exception | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:99`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:91`_ ### CuVS2510GPUVectorsReader @@ -59,7 +59,7 @@ Initializes the reader with the cache owned by its vectors format. | --- | --- | | `IOException` | I/O exception | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:112`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:104`_ ### readSimilarityFunction @@ -85,7 +85,7 @@ an instance of VectorSimilarityFunction | --- | --- | | `IOException` | | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:238`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:230`_ ### readVectorEncoding @@ -111,7 +111,7 @@ the vector encoding | --- | --- | | `IOException` | | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:253`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:245`_ ### getFieldEntry @@ -132,7 +132,7 @@ cuVS index for it. the meta information for the field, or `null` -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:316`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:308`_ ### openCagraIndexForMerge @@ -165,7 +165,7 @@ a freshly loaded CAGRA index, or `null` if this segment has none for the field | --- | --- | | `IOException` | I/O exception | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:335`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:327`_ ### lazilyLoadedIndexCount @@ -176,7 +176,7 @@ int lazilyLoadedIndexCount() Returns the number of indexes a merge-opened reader has loaded so far. Package-private so that tests can check that loading is per field. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:412`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:404`_ ### close @@ -186,7 +186,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Closes the resources. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:453`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:445`_ ### checkIntegrity @@ -196,7 +196,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Checks consistency of this reader. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:483`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:475`_ ### getFloatVectorValues @@ -206,7 +206,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Returns the FloatVectorValues for the given field. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:492`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:484`_ ### getByteVectorValues @@ -218,7 +218,7 @@ Returns the ByteVectorValues for the given field. This is not supported. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:502`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:494`_ ### search @@ -228,7 +228,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Returns the k nearest neighbor documents using cuVS's CAGRA or brute force algorithm for this field, to the given vector. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:526`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:518`_ ### search @@ -240,7 +240,7 @@ Return the k nearest neighbor documents as determined by comparison of their vec This is not supported. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:687`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:679`_ ### readEntry @@ -268,7 +268,7 @@ an instance of FieldEntry | --- | --- | | `IOException` | I/O Exceptions | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:715`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:707`_ ### getCagraIndexForField @@ -299,7 +299,7 @@ the CAGRA index, or `null` | --- | --- | | `IOException` | I/O exception | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:772`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:764`_ ### getFilterBitsetCache @@ -309,7 +309,7 @@ FilterBitsetCache getFilterBitsetCache() Returns the filter cache owned by the vectors format that created this reader. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:781`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:773`_ ### getFieldInfos @@ -323,7 +323,7 @@ Gets the instance of FieldInfos. the instance of FieldInfos -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:790`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:782`_ ### getCuvsIndexes @@ -337,7 +337,7 @@ Gets the map of `GPUIndex` objects. the map of GPU index objects, or `null` if this reader was opened for a merge -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:799`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:791`_ ### getFieldEntries @@ -351,6 +351,6 @@ Gets the map of FieldEntry objects that hold the meta information for the field. the map of FieldEntry objects -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:808`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:800`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:62`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java:63`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorswriter.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorswriter.md index cd259fb4a7..098c9a0b2d 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorswriter.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpuvectorswriter.md @@ -22,7 +22,7 @@ CAGRA(true, false), /** Builds a Brute Force index. */ BRUTE_FORCE(false, true), Builds a CAGRA index. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:89`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:81`_ ### BRUTE_FORCE @@ -32,7 +32,7 @@ BRUTE_FORCE(false, true), /** Builds both - CAGRA and Brute Force indexes. */ CA Builds a Brute Force index. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:92`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:84`_ ### CAGRA_AND_BRUTE_FORCE @@ -42,7 +42,7 @@ CAGRA_AND_BRUTE_FORCE(true, true) Builds both - CAGRA and Brute Force indexes. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:95`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:87`_ ### CuVS2510GPUVectorsWriter @@ -66,7 +66,7 @@ Initializes `CuVS2510GPUVectorsWriter`. | --- | --- | | `IOException` | I/O exceptions | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:121`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:113`_ ### addField @@ -76,7 +76,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Add new field for indexing. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:160`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:152`_ ### flush @@ -86,7 +86,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Creates the CAGRA and/or brute force indexes and writes them to the disk. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:303`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:295`_ ### mergeOneField @@ -96,7 +96,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Write field for merging. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:676`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:668`_ ### ramBytesUsed @@ -106,7 +106,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Returns the memory usage of this object in bytes. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:687`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:679`_ ### finish @@ -116,7 +116,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Called once at the end before close. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:699`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:691`_ ### close @@ -126,6 +126,6 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVecto Close the applicable resources. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:719`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:711`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:59`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java:60`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md new file mode 100644 index 0000000000..4db47dac55 --- /dev/null +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md @@ -0,0 +1,229 @@ +--- +slug: api-reference/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs +--- + +# CuVSCodecs + +_Java package: `com.nvidia.cuvs.lucene`_ + +```java +public final class CuVSCodecs +``` + +Creates the cuvs-lucene codecs and vectors formats that write indexes on the Lucene release this +artifact is built for. + +Each cuvs-lucene codec wraps the default codec of one Lucene release, for example `Lucene101AcceleratedHNSWCodec` wraps Lucene 10.2's `Lucene101Codec`, so the codec to write +with changes along with Lucene. Creating codecs through this class instead of naming them keeps +application code unchanged when it moves to another Lucene release and the matching cuvs-lucene +artifact. + +Every method throws `IllegalStateException` if the Lucene release in use is not the one +this cuvs-lucene artifact is built for. + +## Public Members + +### checkLuceneVersion + +```java +public static void checkLuceneVersion() +``` + +Checks that the Lucene release in use is the one this cuvs-lucene artifact is built for. + +A cuvs-lucene artifact on the wrong Lucene release does not break Lucene: Lucene keeps +working, and so do indexes that do not use the cuvs-lucene codecs, while using one of these +codecs fails. Applications that would rather not start at all in that case can call this method +during startup. + +**Throws** + +| Type | Description | +| --- | --- | +| `IllegalStateException` | if the artifact is not built for the Lucene release in use; the message names the artifact to use instead | + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:39`_ + +### acceleratedHNSW + +```java +public static Codec acceleratedHNSW() +``` + +Returns the codec that builds HNSW graphs on the GPU and searches them on the CPU, with default +parameters. + +**Returns** + +the codec + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:49`_ + +### acceleratedHNSW + +```java +public static Codec acceleratedHNSW(AcceleratedHNSWParams params) +``` + +Returns the codec that builds HNSW graphs on the GPU and searches them on the CPU. + +**Parameters** + +| Name | Description | +| --- | --- | +| `params` | the index build parameters | + +**Returns** + +the codec + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:59`_ + +### acceleratedHNSWScalarQuantized + +```java +public static Codec acceleratedHNSWScalarQuantized() +``` + +Returns the codec that builds HNSW graphs on the GPU over scalar-quantized vectors, with +default parameters. + +**Returns** + +the codec + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:70`_ + +### acceleratedHNSWScalarQuantized + +```java +public static Codec acceleratedHNSWScalarQuantized(AcceleratedHNSWParams params) +``` + +Returns the codec that builds HNSW graphs on the GPU over scalar-quantized vectors. + +**Parameters** + +| Name | Description | +| --- | --- | +| `params` | the index build parameters | + +**Returns** + +the codec + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:80`_ + +### acceleratedHNSWScalarQuantizedFormat + +```java +public static KnnVectorsFormat acceleratedHNSWScalarQuantizedFormat( AcceleratedHNSWParams params) +``` + +Returns the vectors format used by `#acceleratedHNSWScalarQuantized(AcceleratedHNSWParams)`, for use with a per-field codec. + +**Parameters** + +| Name | Description | +| --- | --- | +| `params` | the index build parameters | + +**Returns** + +the vectors format + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:92`_ + +### acceleratedHNSWBinaryQuantized + +```java +public static Codec acceleratedHNSWBinaryQuantized() +``` + +Returns the codec that builds HNSW graphs on the GPU over binary-quantized vectors, with +default parameters. + +**Returns** + +the codec + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:104`_ + +### acceleratedHNSWBinaryQuantized + +```java +public static Codec acceleratedHNSWBinaryQuantized(AcceleratedHNSWParams params) +``` + +Returns the codec that builds HNSW graphs on the GPU over binary-quantized vectors. + +**Parameters** + +| Name | Description | +| --- | --- | +| `params` | the index build parameters | + +**Returns** + +the codec + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:114`_ + +### gpuSearch + +```java +public static Codec gpuSearch() +``` + +Returns the codec that both builds and searches CAGRA indexes on the GPU, with default +parameters. + +**Returns** + +the codec + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:125`_ + +### gpuSearch + +```java +public static Codec gpuSearch(GPUSearchParams params) +``` + +Returns the codec that both builds and searches CAGRA indexes on the GPU, with the default +filter-bitset-cache configuration. + +**Parameters** + +| Name | Description | +| --- | --- | +| `params` | the GPU index and search parameters | + +**Returns** + +the codec + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:136`_ + +### gpuSearch + +```java +public static Codec gpuSearch(GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) +``` + +Returns the codec that both builds and searches CAGRA indexes on the GPU. + +**Parameters** + +| Name | Description | +| --- | --- | +| `params` | the GPU index and search parameters | +| `filterCacheConfig` | the filter-bitset-cache configuration | + +**Returns** + +the codec + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:147`_ + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:25`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene101acceleratedhnswcodec.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene101acceleratedhnswcodec.md index 7f7dde5bbf..a998925387 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene101acceleratedhnswcodec.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene101acceleratedhnswcodec.md @@ -7,30 +7,27 @@ slug: api-reference/lucene-api-com-nvidia-cuvs-lucene-lucene101acceleratedhnswco _Java package: `com.nvidia.cuvs.lucene`_ ```java -public class Lucene101AcceleratedHNSWCodec extends FilterCodec +public class Lucene101AcceleratedHNSWCodec extends CuVSFilterCodec ``` A codec that enables GPU-based accelerated HNSW capability and can be used to accelerated indexing using GPUs and search using CPUs. Fallbacks to CPU based indexing when used on a machine without a GPU and/or cuVS. +This codec wraps `Lucene101Codec`, the default codec of Lucene 10.2. `CuVSCodecs#acceleratedHNSW` +returns the codec of this family for the Lucene release in use. + ## Public Members ### Lucene101AcceleratedHNSWCodec ```java -public Lucene101AcceleratedHNSWCodec() throws Exception +public Lucene101AcceleratedHNSWCodec() ``` Default constructor for `Lucene101AcceleratedHNSWCodec`. -**Throws** - -| Type | Description | -| --- | --- | -| `Exception` | | - -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java:31`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java:25`_ ### Lucene101AcceleratedHNSWCodec @@ -47,12 +44,12 @@ Constructor for `Lucene101AcceleratedHNSWCodec`. | `name` | the codec's name | | `delegate` | the delegate codec to filter | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java:41`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java:35`_ ### Lucene101AcceleratedHNSWCodec ```java -public Lucene101AcceleratedHNSWCodec(AcceleratedHNSWParams acceleratedHNSWParams) throws Exception +public Lucene101AcceleratedHNSWCodec(AcceleratedHNSWParams acceleratedHNSWParams) ``` Constructor for `Lucene101AcceleratedHNSWCodec`. @@ -63,42 +60,6 @@ Constructor for `Lucene101AcceleratedHNSWCodec`. | --- | --- | | `acceleratedHNSWParams` | instance of `AcceleratedHNSWParams` | -**Throws** - -| Type | Description | -| --- | --- | -| `Exception` | exception | - -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java:52`_ - -### knnVectorsFormat - -```java -@Override public KnnVectorsFormat knnVectorsFormat() -``` - -Get the configured `KnnVectorsFormat`. - -**Returns** - -the instance of the `KnnVectorsFormat` - -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java:87`_ - -### setKnnFormat - -```java -public void setKnnFormat(KnnVectorsFormat format) -``` - -Set the `KnnVectorsFormat`. - -**Parameters** - -| Name | Description | -| --- | --- | -| `format` | the `KnnVectorsFormat` to set | - -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java:97`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java:48`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java:21`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java:19`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene99acceleratedhnswvectorsformat.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene99acceleratedhnswvectorsformat.md index e84e99efc6..0abc2652ea 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene99acceleratedhnswvectorsformat.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene99acceleratedhnswvectorsformat.md @@ -29,7 +29,7 @@ of `AcceleratedHNSWParams` with default parameter values. | --- | --- | | `LibraryException` | if the native library fails to load | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:56`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:55`_ ### Lucene99AcceleratedHNSWVectorsFormat @@ -46,7 +46,7 @@ of `AcceleratedHNSWParams`. | --- | --- | | `acceleratedHNSWParams` | An instance of `AcceleratedHNSWParams` | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:66`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:65`_ ### fieldsWriter @@ -56,7 +56,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99Accelera Returns a KnnVectorsWriter to write the vectors to the index. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:74`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:73`_ ### fieldsReader @@ -66,7 +66,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99Accelera Returns a KnnVectorsReader to read the vectors from the index. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:101`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:97`_ ### getMaxDimensions @@ -76,6 +76,6 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99Accelera Returns the maximum number of vector dimensions supported by this codec for the given field name. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:114`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:106`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:27`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java:31`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene99acceleratedhnswvectorswriter.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene99acceleratedhnswvectorswriter.md index ea2f810836..93a999b4c2 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene99acceleratedhnswvectorswriter.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-lucene99acceleratedhnswvectorswriter.md @@ -37,7 +37,7 @@ Initializes `Lucene99AcceleratedHNSWVectorsWriter` | --- | --- | | `IOException` | IOException | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:90`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:80`_ ### addField @@ -47,7 +47,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99Accelera Add new field for indexing. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:133`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:123`_ ### flush @@ -57,7 +57,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99Accelera Build the indexes and writes it to the disk. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:234`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:224`_ ### mergeOneField @@ -67,7 +67,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99Accelera Write field for merging. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:393`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:383`_ ### finish @@ -77,7 +77,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99Accelera Called once at the end before close. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:402`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:392`_ ### close @@ -87,7 +87,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99Accelera Closes the resources. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:422`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:412`_ ### ramBytesUsed @@ -97,6 +97,6 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99Accelera Returns the memory usage of this object in bytes. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:432`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:422`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:55`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java:56`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswbinaryquantizedcodec.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswbinaryquantizedcodec.md index 981e432792..3ed7890ae3 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswbinaryquantizedcodec.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswbinaryquantizedcodec.md @@ -7,9 +7,12 @@ slug: api-reference/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswbinar _Java package: `com.nvidia.cuvs.lucene`_ ```java -public class LuceneAcceleratedHNSWBinaryQuantizedCodec extends FilterCodec +public class LuceneAcceleratedHNSWBinaryQuantizedCodec extends CuVSFilterCodec ``` CuVS based codec for GPU based vector search -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedCodec.java:19`_ +This codec, named `Lucene101AcceleratedHNSWBinaryQuantizedCodec`, wraps `Lucene101Codec`, the default codec of Lucene 10.2. `CuVSCodecs#acceleratedHNSWBinaryQuantized` returns the codec of +this family for the Lucene release in use. + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedCodec.java:18`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswbinaryquantizedvectorsformat.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswbinaryquantizedvectorsformat.md index 8de43c14c9..d2c11c8278 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswbinaryquantizedvectorsformat.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswbinaryquantizedvectorsformat.md @@ -28,7 +28,7 @@ Initializes `LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat` with default val | --- | --- | | `LibraryException` | if the native library fails to load | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:53`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:44`_ ### LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat @@ -44,7 +44,7 @@ Initializes `LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat` with the given t | --- | --- | | `acceleratedHNSWParams` | An instance of `AcceleratedHNSWParams` | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:62`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:53`_ ### fieldsWriter @@ -54,7 +54,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Returns a KnnVectorsWriter to write the binary quantized vectors to the index. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:71`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:62`_ ### fieldsReader @@ -64,7 +64,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Returns a KnnVectorsReader to read the binary quantized vectors from the index. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:104`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:92`_ ### getMaxDimensions @@ -74,6 +74,6 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Returns the maximum number of vector dimensions supported by this codec for the given field name. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:117`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:101`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:27`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java:30`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedcodec.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedcodec.md index e31e66c7fc..ce57feb558 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedcodec.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedcodec.md @@ -7,9 +7,12 @@ slug: api-reference/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscala _Java package: `com.nvidia.cuvs.lucene`_ ```java -public class LuceneAcceleratedHNSWScalarQuantizedCodec extends FilterCodec +public class LuceneAcceleratedHNSWScalarQuantizedCodec extends CuVSFilterCodec ``` CuVS based codec for GPU based vector search -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedCodec.java:19`_ +This codec, named `Lucene101AcceleratedHNSWScalarQuantizedCodec`, wraps `Lucene101Codec`, the default codec of Lucene 10.2. `CuVSCodecs#acceleratedHNSWScalarQuantized` returns the codec of +this family for the Lucene release in use. + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedCodec.java:18`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedvectorsformat.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedvectorsformat.md index b33e8bd2e2..8c68b0f939 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedvectorsformat.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedvectorsformat.md @@ -28,7 +28,7 @@ Initializes `LuceneAcceleratedHNSWScalarQuantizedVectorsFormat` with default val | --- | --- | | `LibraryException` | if the native library fails to load | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:48`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:41`_ ### LuceneAcceleratedHNSWScalarQuantizedVectorsFormat @@ -44,7 +44,7 @@ Initializes `LuceneAcceleratedHNSWScalarQuantizedVectorsFormat` with the given t | --- | --- | | `acceleratedHNSWParams` | An instance of `AcceleratedHNSWParams` | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:57`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:50`_ ### fieldsWriter @@ -54,7 +54,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Returns a KnnVectorsWriter to write the scalar quantized vectors to the index. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:66`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:59`_ ### fieldsReader @@ -64,7 +64,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Returns a KnnVectorsReader to read the scalar quantized vectors from the index. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:95`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:85`_ ### getMaxDimensions @@ -74,6 +74,6 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Returns the maximum number of vector dimensions supported by this Codec for the given field name. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:108`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:113`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:25`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java:27`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedvectorswriter.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedvectorswriter.md index de6400b8fd..afe6501907 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedvectorswriter.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedvectorswriter.md @@ -37,7 +37,7 @@ Initializes `LuceneAcceleratedHNSWScalarQuantizedVectorsWriter` | --- | --- | | `IOException` | IOException | -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:90`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:80`_ ### addField @@ -47,7 +47,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Add new field for indexing. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:138`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:128`_ ### flush @@ -57,7 +57,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Build the indexes and writes it to the disk. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:242`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:232`_ ### mergeOneField @@ -67,7 +67,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Write field for merging. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:326`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:316`_ ### finish @@ -77,7 +77,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Called once at the end before close. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:357`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:347`_ ### close @@ -87,7 +87,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Closes the resources. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:377`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:367`_ ### ramBytesUsed @@ -97,6 +97,6 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAccelerate Returns the memory usage of this object in bytes. -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:386`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:376`_ -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:56`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java:57`_ diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneprovider.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneprovider.md deleted file mode 100644 index e46ee10813..0000000000 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-luceneprovider.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -slug: api-reference/lucene-api-com-nvidia-cuvs-lucene-luceneprovider ---- - -# LuceneProvider - -_Java package: `com.nvidia.cuvs.lucene`_ - -```java -public class LuceneProvider -``` - -Dynamically loads Lucene format, reader, and writer classes with a fallback mechanism. - -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneProvider.java:31`_ diff --git a/fern/pages/user_guide/lucene.md b/fern/pages/user_guide/lucene.md index 34aa9402d4..fef52faceb 100644 --- a/fern/pages/user_guide/lucene.md +++ b/fern/pages/user_guide/lucene.md @@ -6,7 +6,7 @@ slug: user-guide/lucene NVIDIA cuVS Lucene (`cuvs-lucene`) is a pluggable [KnnVectorsFormat](https://lucene.apache.org/core/10_2_0/core/org/apache/lucene/codecs/KnnVectorsFormat.html) that offloads vector index build, and optionally search, from the CPU to NVIDIA GPUs. Because it plugs in through a standard Lucene codec, an existing Lucene application adopts it by setting a codec on its `IndexWriterConfig`; the rest of the indexing and query code is unchanged. -`cuvs-lucene` targets Lucene 10.2 and is built on the [NVIDIA cuVS Java APIs](/api-reference/java-api-documentation). For installation and build instructions, see [cuVS Lucene](/installation/java#cuvs-lucene) in the Java installation guide. For class-level details, see the [Lucene API Documentation](/api-reference/lucene-api-documentation). +`cuvs-lucene` supports Lucene 10.2, with one artifact per Lucene minor release, and is built on the [NVIDIA cuVS Java APIs](/api-reference/java-api-documentation). For installation and build instructions, see [cuVS Lucene](/installation/java#cuvs-lucene) in the Java installation guide. For class-level details, see the [Lucene API Documentation](/api-reference/lucene-api-documentation). ## When To Use It @@ -18,26 +18,32 @@ If you are running Apache Solr, you do not need to integrate `cuvs-lucene` direc ## Choosing a Codec -Four codecs are available. All four build the vector index on the GPU; they differ in where search runs and in what is written to disk. +Four codecs are available, created through [`CuVSCodecs`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs). All four build the vector index on the GPU; they differ in where search runs and in what is written to disk. | Codec | Search | On-disk format | Use when | | --- | --- | --- | --- | -| [`Lucene101AcceleratedHNSWCodec`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-lucene101acceleratedhnswcodec) | CPU | Standard Lucene HNSW | You want faster index builds without changing the search path or index format. | -| [`LuceneAcceleratedHNSWScalarQuantizedCodec`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswscalarquantizedcodec) | CPU | Standard Lucene HNSW, scalar-quantized | As above, with a smaller index footprint and some loss of precision. | -| [`LuceneAcceleratedHNSWBinaryQuantizedCodec`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-luceneacceleratedhnswbinaryquantizedcodec) | CPU | Standard Lucene HNSW, binary-quantized | As above, with the smallest footprint and the largest loss of precision. | -| [`CuVS2510GPUSearchCodec`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-cuvs2510gpusearchcodec) | GPU | cuVS format | Search throughput matters and GPUs are available on the query path. | +| `CuVSCodecs.acceleratedHNSW` | CPU | Standard Lucene HNSW | You want faster index builds without changing the search path or index format. | +| `CuVSCodecs.acceleratedHNSWScalarQuantized` | CPU | Standard Lucene HNSW, scalar-quantized | As above, with a smaller index footprint and some loss of precision. | +| `CuVSCodecs.acceleratedHNSWBinaryQuantized` | CPU | Standard Lucene HNSW, full-precision vectors | As above, with the graph built over binary-quantized vectors. The vectors themselves are stored and scored in full precision. | +| `CuVSCodecs.gpuSearch` | GPU | cuVS format | Search throughput matters and GPUs are available on the query path. | The three accelerated HNSW codecs write the stock Lucene HNSW vector format and read it back through the standard Lucene reader. GPU acceleration therefore applies to index build only: the query API and the read path are unchanged, and the read path touches neither a GPU nor the native cuVS library. The graph itself is built by CAGRA rather than Lucene's HNSW builder, so benchmark recall if you are migrating an existing index. -Deployment does change, though. Lucene resolves codecs by name through its service loader, so search nodes need the same `cuvs-lucene` and `cuvs-java` jars and a JDK 22 or newer runtime as indexing nodes, or the segments cannot be opened. +Deployment does change, though. Lucene resolves codecs by name through its service loader, so search nodes need the same `cuvs-lucene` and `cuvs-java` jars as indexing nodes, or the segments cannot be opened. Searching the accelerated HNSW codecs' indexes needs no GPU, so search nodes can run any JDK 21 or newer. -`CuVS2510GPUSearchCodec` writes a cuVS-specific format instead. That format is experimental and backward compatibility across releases is not guaranteed, so plan on being able to rebuild indexes when upgrading. +The GPU search codec writes a cuVS-specific format instead. That format is experimental and backward compatibility across releases is not guaranteed, so plan on being able to rebuild indexes when upgrading. -`Lucene101AcceleratedHNSWCodec` falls back to CPU index construction when cuVS resources cannot be created, which happens when no GPU is present or the native library cannot be loaded. It logs a warning and hands the segment to the stock Lucene HNSW writer, so the same application runs on GPU and non-GPU hosts and produces the same index format either way. +The three accelerated HNSW codecs fall back to CPU index construction when cuVS resources cannot be created, which happens when no GPU is present or the native library cannot be loaded. They log a warning and hand the segment to the stock Lucene HNSW writer, so the same application runs on GPU and non-GPU hosts and produces the same index format either way. -The two quantized variants attempt the same fallback but do not currently complete it, so treat a working cuVS installation as required when using them. +The GPU search codec has no such fallback. Its on-disk format has no CPU reader, so both indexing and search throw `UnsupportedOperationException` when cuVS is unavailable. -`CuVS2510GPUSearchCodec` has no such fallback. Its on-disk format has no CPU reader, so both indexing and search throw `UnsupportedOperationException` when cuVS is unavailable. +## Lucene Versions and Upgrades + +Lucene changes its codec APIs between minor releases, so `cuvs-lucene` is published as one artifact per Lucene minor release, such as `cuvs-lucene-10.2`; see [cuVS Lucene](/installation/java#cuvs-lucene). Use the one that matches your application's Lucene version. Against another Lucene minor release, its codecs fail when they are used; see [Troubleshooting](#troubleshooting). + +Each codec also wraps the default codec of one Lucene release, and Lucene records the codec's name in every segment it writes. On Lucene 10.2 these are `Lucene101AcceleratedHNSWCodec`, `Lucene101AcceleratedHNSWScalarQuantizedCodec`, `Lucene101AcceleratedHNSWBinaryQuantizedCodec` and `CuVS2510GPUSearchCodec`, all wrapping `Lucene101Codec`. `CuVSCodecs` returns the codecs for the Lucene release in use, so code that creates its codecs through it does not change when the codec names change with a later Lucene release. + +Releases before 26.12 published a single `cuvs-lucene` artifact, built for Lucene 10.2. Its coordinates are relocated to `cuvs-lucene-10.2`; see [cuVS Lucene](/installation/java#cuvs-lucene). ## Quickstart @@ -47,7 +53,7 @@ Setting one of the codecs on an `IndexWriterConfig` is the only change an existi import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; import com.nvidia.cuvs.lucene.AcceleratedHNSWParams; -import com.nvidia.cuvs.lucene.Lucene101AcceleratedHNSWCodec; +import com.nvidia.cuvs.lucene.CuVSCodecs; import java.nio.file.Files; import java.nio.file.Path; import java.util.Random; @@ -81,7 +87,7 @@ public class AcceleratedHnswQuickstart { Path indexDirPath = Files.createTempDirectory("cuvs-lucene-quickstart"); AcceleratedHNSWParams params = new AcceleratedHNSWParams.Builder().build(); - Codec codec = new Lucene101AcceleratedHNSWCodec(params); + Codec codec = CuVSCodecs.acceleratedHNSW(params); IndexWriterConfig config = new IndexWriterConfig().setCodec(codec); // Indexing @@ -122,18 +128,18 @@ public class AcceleratedHnswQuickstart { } ``` -Substituting `LuceneAcceleratedHNSWScalarQuantizedCodec` or `LuceneAcceleratedHNSWBinaryQuantizedCodec` is the only change needed to use a quantized variant; both take the same [`AcceleratedHNSWParams`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-acceleratedhnswparams). +Substituting `CuVSCodecs.acceleratedHNSWScalarQuantized` or `CuVSCodecs.acceleratedHNSWBinaryQuantized` is the only change needed to use a quantized variant; both take the same [`AcceleratedHNSWParams`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-acceleratedhnswparams). This program is a condensed version of `AcceleratedHnswExample`. That example and a GPU search one live in the [`examples/java/cuvs-lucene`](https://github.com/NVIDIA/cuvs/tree/main/examples/java/cuvs-lucene) directory of the NVIDIA cuVS repository, which the build compiles through `./build.sh lucene --build-java-examples`. ## Searching on the GPU -`CuVS2510GPUSearchCodec` runs both index build and search on the GPU. Configure it with [`GPUSearchParams`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-gpusearchparams). A stock `KnnFloatVectorQuery` already searches on the GPU against this codec, so existing query code keeps working; [`GPUKnnFloatVectorQuery`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-gpuknnfloatvectorquery) adds control over the CAGRA search parameters and lets cuVS serve all segments in a single request, which is usually faster. This program is the GPU counterpart of the quickstart, with a filter applied to the search: +The GPU search codec, from `CuVSCodecs.gpuSearch`, runs both index build and search on the GPU. Configure it with [`GPUSearchParams`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-gpusearchparams). A stock `KnnFloatVectorQuery` already searches on the GPU against this codec, so existing query code keeps working; [`GPUKnnFloatVectorQuery`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-gpuknnfloatvectorquery) adds control over the CAGRA search parameters and lets cuVS serve all segments in a single request, which is usually faster. This program is the GPU counterpart of the quickstart, with a filter applied to the search: ```java import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; -import com.nvidia.cuvs.lucene.CuVS2510GPUSearchCodec; +import com.nvidia.cuvs.lucene.CuVSCodecs; import com.nvidia.cuvs.lucene.GPUKnnFloatVectorQuery; import com.nvidia.cuvs.lucene.GPUSearchParams; import com.nvidia.cuvs.spi.CuVSProvider; @@ -177,7 +183,7 @@ public class GpuSearchQuickstart { Path indexDirPath = Files.createTempDirectory("cuvs-lucene-gpu-search"); GPUSearchParams params = new GPUSearchParams.Builder().build(); - Codec codec = new CuVS2510GPUSearchCodec(params); + Codec codec = CuVSCodecs.gpuSearch(params); IndexWriterConfig config = new IndexWriterConfig().setCodec(codec); // Indexing @@ -239,7 +245,7 @@ Switching either class to the `CUSTOM` strategy exposes the underlying CAGRA par A few settings matter once `cuvs-lucene` runs in a long-lived, multi-threaded application such as a search server. -**Device memory allocator.** Applications using `CuVS2510GPUSearchCodec` can opt into RMM's stream-ordered asynchronous device allocator during startup: +**Device memory allocator.** Applications using the GPU search codec can opt into RMM's stream-ordered asynchronous device allocator during startup: ```java CuVSProvider.provider().enableRMMAsyncMemory(); @@ -266,7 +272,7 @@ Install the handle on each search thread before that thread runs its first searc **Filter bitsets.** Filtered GPU searches upload an acceptance mask per segment. Two independent settings control this: -- [`FilterBitsetCacheConfig`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-filterbitsetcacheconfig) caches the packed host-side bitsets for one vectors-format instance, with a 128 MiB budget by default. Pass a custom configuration to the `CuVS2510GPUSearchCodec` constructor to change or disable it. The budget is a retention cap, not a preallocation. +- [`FilterBitsetCacheConfig`](/api-reference/lucene-api-com-nvidia-cuvs-lucene-filterbitsetcacheconfig) caches the packed host-side bitsets for one vectors-format instance, with a 128 MiB budget by default. Pass a custom configuration to `CuVSCodecs.gpuSearch` to change or disable it. The budget is a retention cap, not a preallocation. - The `com.nvidia.cuvs.filterBitsetPoolSize` system property sizes the process-wide RMM device pool that backs filter bitset uploads, defaulting to a 4 MiB initial reservation that can grow. Set it to `0` to disable pooling. ## Troubleshooting @@ -275,10 +281,12 @@ Install the handle on each search thread before that thread runs its first searc **An index written with these codecs will not open.** Lucene looks codecs up by name through its service loader, so a reader without `cuvs-lucene` on its classpath fails with an SPI lookup error naming the missing codec. Deploy the same `cuvs-lucene` and `cuvs-java` jars to search nodes as to indexing nodes. -**`UnsupportedOperationException: cuVS is not supported`.** `CuVS2510GPUSearchCodec` raises this when cuVS resources cannot be created, on both the indexing and the search path. The causes are the same as above; unlike the accelerated HNSW codecs, this codec cannot continue without a GPU. +**`UnsupportedOperationException: cuVS is not supported`.** The GPU search codec raises this when cuVS resources cannot be created, on both the indexing and the search path. The causes are the same as above; unlike the accelerated HNSW codecs, this codec cannot continue without a GPU. + +**`IllegalStateException: cuvs-lucene-10.X is built for Lucene 10.X.x, but Lucene 10.Y.z is in use`.** The `cuvs-lucene` artifact on the classpath was built for another Lucene minor release than the one the application uses. Lucene itself keeps working, and so do indexes that do not use the cuvs-lucene codecs; getting a codec from `CuVSCodecs`, or reading or writing an index that uses a cuvs-lucene codec, fails with this message. A codec created with its constructor fails only once it is used. The same problem is logged at `SEVERE` the first time Lucene looks up its codecs, usually at startup. Applications that would rather refuse to start can call `CuVSCodecs.checkLuceneVersion()` during startup, which throws this exception. Replace the artifact with the one the message names, if it exists; see [Lucene Versions and Upgrades](#lucene-versions-and-upgrades). **Native libraries do not match.** A missing or mismatched native cuVS library is caught during resource creation and logged at `WARNING` rather than thrown, so it surfaces as one of the two symptoms above rather than as a load error. Make sure the installed native libraries match the `cuvs-lucene` version; see [cuVS Lucene](/installation/java#cuvs-lucene). -**Errors about native access or an unsupported class file version.** `cuvs-lucene` is compiled for Java 22 and uses the Panama FFI APIs, so it needs a JDK 22 or newer runtime on Linux (`amd64` or `aarch64`). Run the JVM with `--enable-native-access=ALL-UNNAMED` to suppress native-access warnings, or `--enable-native-access=com.nvidia.cuvs` if you put `cuvs-java` on the module path. +**Errors about native access, or indexing never uses the GPU.** `cuvs-lucene` is compiled for Java 21, but `cuvs-java` reaches the GPU through the Panama FFI APIs, which need a JDK 22 or newer runtime on Linux (`amd64` or `aarch64`). On JDK 21, cuVS reports that it is not supported: the accelerated HNSW codecs build graphs on the CPU, and the GPU search codec throws `UnsupportedOperationException`. Run the JVM with `--enable-native-access=ALL-UNNAMED` to suppress native-access warnings, or `--enable-native-access=com.nvidia.cuvs` if you put `cuvs-java` on the module path. **GPU search is slower than expected.** A stock `KnnFloatVectorQuery` searches each segment separately; switching to `GPUKnnFloatVectorQuery` lets cuVS serve all segments in one request. Indexes with many small segments also benefit from force-merging to fewer, more evenly sized segments. diff --git a/fern/scripts/generate_api_reference.py b/fern/scripts/generate_api_reference.py index 7619714979..3f09f9fc9a 100755 --- a/fern/scripts/generate_api_reference.py +++ b/fern/scripts/generate_api_reference.py @@ -142,6 +142,8 @@ "KnnVectorsFormat", "KnnVectorsReader", "KnnVectorsWriter", + # cuvs-lucene's own base for its codecs, a FilterCodec. + "CuVSFilterCodec", } ) JAVA_SUPERTYPE_RE = re.compile(r"\b(?:extends|implements)\s+(?P\w+)") diff --git a/java/cuvs-lucene/README.md b/java/cuvs-lucene/README.md index 1836d71ab5..b859eb75f5 100644 --- a/java/cuvs-lucene/README.md +++ b/java/cuvs-lucene/README.md @@ -8,18 +8,23 @@ This is a project for using [cuVS](https://github.com/NVIDIA/cuvs), NVIDIA's GPU 2. [Installing cuvs-lucene](#installing-cuvs-lucene) 3. [Getting Started](#getting-started) 4. [Contributing](#contributing) -5. [References](#references) +5. [Supporting several Lucene releases](#supporting-several-lucene-releases) +6. [References](#references) ## What is cuvs-lucene? -`cuvs-lucene` provides a pluggable [KnnVectorsFormat](https://lucene.apache.org/core/10_2_0/core/org/apache/lucene/codecs/KnnVectorsFormat.html) that uses cuVS to offload vector index build — and optionally search — to NVIDIA GPUs. Because it plugs in through a standard Lucene codec, existing Lucene applications can take advantage of GPU acceleration with minimal code changes. `Lucene101AcceleratedHNSWCodec` also falls back to the stock CPU codec when no GPU is present; the other three require a working cuVS installation. +`cuvs-lucene` provides a pluggable [KnnVectorsFormat](https://lucene.apache.org/core/10_2_0/core/org/apache/lucene/codecs/KnnVectorsFormat.html) that uses cuVS to offload vector index build — and optionally search — to NVIDIA GPUs. Because it plugs in through a standard Lucene codec, existing Lucene applications can take advantage of GPU acceleration with minimal code changes. It supports Lucene 10.2. -Four codecs are currently provided: +Four codecs are currently provided, created through `CuVSCodecs`: -- `Lucene101AcceleratedHNSWCodec` — GPU-accelerated HNSW build with CPU HNSW search. The on-disk format is standard Lucene HNSW, so indexes built on the GPU are read back through the standard Lucene 10.2 reader and searching them needs no GPU. Lucene still resolves the codec by name, so search nodes need the `cuvs-lucene` and `cuvs-java` jars on their classpath. - - `LuceneAcceleratedHNSWScalarQuantizedCodec` — scalar-quantized vectors for a smaller index footprint. - - `LuceneAcceleratedHNSWBinaryQuantizedCodec` — binary-quantized vectors for an even smaller index footprint. -- `CuVS2510GPUSearchCodec` — GPU-accelerated HNSW build and GPU search +- `CuVSCodecs.acceleratedHNSW()` — GPU-accelerated HNSW build with CPU HNSW search. The on-disk format is standard Lucene HNSW, so indexes built on the GPU are read back through the standard Lucene reader and searching them needs no GPU. Lucene still resolves the codec by name, so search nodes need the `cuvs-lucene` and `cuvs-java` jars on their classpath. Falls back to building the graph on the CPU when no GPU is present. + - `CuVSCodecs.acceleratedHNSWScalarQuantized()` — scalar-quantized vectors for a smaller index footprint. + - `CuVSCodecs.acceleratedHNSWBinaryQuantized()` — graph built over binary-quantized vectors. +- `CuVSCodecs.gpuSearch()` — GPU-accelerated build and GPU search. Requires a GPU. + +Each codec wraps the default codec of one Lucene release, for example `Lucene101AcceleratedHNSWCodec` wraps +`Lucene101Codec`, the default codec of Lucene 10.2. Creating the codecs through `CuVSCodecs` rather than by class +name keeps application code unchanged when the codec to use changes with a later Lucene release. For guidance on choosing between them, configuring builds, and tuning GPU resources, see the [Lucene Integration](https://docs.nvidia.com/cuvs/user-guide/lucene) guide. @@ -30,22 +35,31 @@ For guidance on choosing between them, configuring builds, and tuning GPU resour - An NVIDIA GPU, to use the GPU-accelerated paths (the accelerated HNSW codecs fall back to CPU index construction without one; `CuVS2510GPUSearchCodec` requires a GPU) - [CUDA Toolkit 12.2+](https://developer.nvidia.com/cuda-toolkit-archive) and an Ampere architecture GPU or newer, matching the [cuVS requirements](https://docs.nvidia.com/cuvs/installation) -- [JDK 22](https://jdk.java.net/archive/) +- JDK 21 or newer. GPU acceleration needs a [JDK 22](https://jdk.java.net/archive/) or newer runtime; on JDK 21 + the accelerated HNSW codecs build graphs on the CPU, and the GPU search codec is unavailable - [Maven 3.9.6+](https://maven.apache.org/download.cgi) - A matching version of the [cuVS libraries](https://docs.nvidia.com/cuvs/installation/java). For Maven usage, install the cuVS libraries and add them to your system library load path. ### Maven -To pull `cuvs-lucene` into a Maven project, add the following dependency to your `pom.xml`: +Lucene changes its codec APIs between minor releases, so `cuvs-lucene` is published once per supported Lucene +minor release, as `cuvs-lucene-10.X`. Pick the one matching the Lucene version your application uses. For +Lucene 10.2, add the following dependency to your `pom.xml`: ```xml com.nvidia.cuvs.lucene - cuvs-lucene + cuvs-lucene-10.2 26.12.0 ``` +Until 26.10 the artifact was called `cuvs-lucene`; those coordinates now relocate to `cuvs-lucene-10.2`. + +Against another Lucene minor release, an artifact's codecs fail when they are used, with an error that names +the artifact to use; the problem is also logged at `SEVERE` the first time Lucene looks up its codecs. Call +`CuVSCodecs.checkLuceneVersion()` at startup to fail early instead. + ### Building from source `cuvs-lucene` lives in the [cuVS repository](https://github.com/NVIDIA/cuvs) and builds against the cuVS @@ -55,7 +69,8 @@ Java bindings. If the libcuvs libraries and the Java bindings have not been buil Alternatively, if libcuvs is already built and the `cuvs-java` artifact is already installed in your local Maven repository, do `./build.sh lucene` in the top level directory or just do `./build.sh` in this directory. -The resulting artifacts are written to `target/`. +This builds, and installs into your local Maven repository, every `cuvs-lucene-10.X` artifact. They are +written to `lucene-10.X/target/`. To run the tests, add `--run-java-tests` to any of the commands above. Be sure to set (manually, if needed) your `LD_LIBRARY_PATH` to include the directory with the appropriate (matching) version of `libcuvs.so`, as @@ -68,8 +83,8 @@ codec into a standard Lucene `IndexWriter`, searching on the GPU, tuning index b resources in a long-lived application. Class-level documentation is in the [Lucene API reference](https://docs.nvidia.com/cuvs/api-reference/lucene-api-documentation). -Runnable examples of CAGRA-accelerated HNSW indexing, and of indexing and searching entirely on the GPU with -`CuVS2510GPUSearchCodec`, are in the [`examples/`](../../examples/java/cuvs-lucene) directory. +Runnable examples of CAGRA-accelerated HNSW indexing, and of indexing and searching entirely on the GPU, are +in the [`examples/`](../../examples/java/cuvs-lucene) directory. ### Accelerated HNSW build memory @@ -145,6 +160,34 @@ If you are interested in contributing to cuvs-lucene, please read the cuVS [Cont > [!NOTE] > The code style format is enforced using the [Spotless maven plugin](https://github.com/diffplug/spotless/tree/main/plugin-maven), which runs as a `pre-commit` hook. Run `pre-commit run --all-files`, or `mvn spotless:apply` in this directory, to format the sources. +## Supporting several Lucene releases + +All `cuvs-lucene-10.X` artifacts are built from the same sources: + +``` +java/cuvs-lucene/ + pom.xml parent pom, lists one module per Lucene minor release + src/main/java shared code: everything that compiles against every supported Lucene release + src/test/java shared tests + src/test/resources/backcompat indexes written by each release, see below + lucene-10.X/ + pom.xml the Lucene 10.X dependencies + src/main/java LuceneCompat: the calls that differ on Lucene 10.X + src/main/resources the META-INF/services files: the codecs and formats Lucene 10.X can load + relocation/pom.xml relocates the pre-26.12 cuvs-lucene coordinates to cuvs-lucene-10.2 +``` + +Each module compiles the shared sources against its own Lucene release, so the compiler checks every release. +The shared code never refers to a Lucene API that differs between releases. Instead it goes through +`LuceneCompat`, for calls whose signature or class location differs, such as the codec each release writes with. + +Do not look up Lucene classes or constants by reflection. It hides API changes from the compiler, and constants +such as `Lucene99HnswVectorsFormat.VERSION_CURRENT` change meaning between releases. + +`TestBackCompatIndices` reads indexes written by each module, stored as zips in `src/test/resources/backcompat`, +and checks that every later module can still read and search them. `TestBackCompat` checks that the codecs from +`CuVSCodecs` are registered. + ## References - [Bring Massive-Scale Vector Search to the GPU with Apache Lucene](https://www.nvidia.com/en-us/on-demand/session/gtc25-S71286/) — NVIDIA GTC 2025 session video diff --git a/java/cuvs-lucene/bench/pom.xml b/java/cuvs-lucene/bench/pom.xml index 7a8f54ee60..18115f7e03 100644 --- a/java/cuvs-lucene/bench/pom.xml +++ b/java/cuvs-lucene/bench/pom.xml @@ -82,9 +82,10 @@ ${jmh.version} provided + com.nvidia.cuvs.lucene - cuvs-lucene + cuvs-lucene-10.2 26.12.0 diff --git a/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/AcceleratedHnswIndexingBenchmarks.java b/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/AcceleratedHnswIndexingBenchmarks.java index 08a41f6a5b..4bbdfe9577 100644 --- a/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/AcceleratedHnswIndexingBenchmarks.java +++ b/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/AcceleratedHnswIndexingBenchmarks.java @@ -8,7 +8,8 @@ import static com.nvidia.cuvs.lucene.benchmarks.Utils.generateDataset; import static com.nvidia.cuvs.lucene.benchmarks.Utils.index; -import com.nvidia.cuvs.lucene.Lucene101AcceleratedHNSWCodec; +import com.nvidia.cuvs.lucene.AcceleratedHNSWParams; +import com.nvidia.cuvs.lucene.CuVSCodecs; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; @@ -59,7 +60,16 @@ public class AcceleratedHnswIndexingBenchmarks { public void setup() throws Exception { random = new Random(222); indexDirPath = Paths.get(UUID.randomUUID().toString()); - codec = new Lucene101AcceleratedHNSWCodec(32, 128, 64, 3, 16, 100); + codec = + CuVSCodecs.acceleratedHNSW( + new AcceleratedHNSWParams.Builder() + .withWriterThreads(32) + .withIntermediateGraphDegree(128) + .withGraphDegree(64) + .withHNSWLayer(3) + .withMaxConn(16) + .withBeamWidth(100) + .build()); numDocs = 1000; dimension = 128; dataset = generateDataset(random, numDocs, dimension); diff --git a/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/AcceleratedHnswSearchBenchmarks.java b/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/AcceleratedHnswSearchBenchmarks.java index 32c8d6f970..2f6c03ef3a 100644 --- a/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/AcceleratedHnswSearchBenchmarks.java +++ b/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/AcceleratedHnswSearchBenchmarks.java @@ -9,7 +9,8 @@ import static com.nvidia.cuvs.lucene.benchmarks.Utils.index; import static com.nvidia.cuvs.lucene.benchmarks.Utils.search; -import com.nvidia.cuvs.lucene.Lucene101AcceleratedHNSWCodec; +import com.nvidia.cuvs.lucene.AcceleratedHNSWParams; +import com.nvidia.cuvs.lucene.CuVSCodecs; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; @@ -63,7 +64,16 @@ public class AcceleratedHnswSearchBenchmarks { public void setup() throws Exception { random = new Random(222); indexDirPath = Paths.get(UUID.randomUUID().toString()); - codec = new Lucene101AcceleratedHNSWCodec(32, 128, 64, 3, 16, 100); + codec = + CuVSCodecs.acceleratedHNSW( + new AcceleratedHNSWParams.Builder() + .withWriterThreads(32) + .withIntermediateGraphDegree(128) + .withGraphDegree(64) + .withHNSWLayer(3) + .withMaxConn(16) + .withBeamWidth(100) + .build()); numDocs = 1000; dimension = 128; topK = 5; diff --git a/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/CagraIndexingBenchmarks.java b/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/CagraIndexingBenchmarks.java index 01cf540c85..af207d280c 100644 --- a/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/CagraIndexingBenchmarks.java +++ b/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/CagraIndexingBenchmarks.java @@ -8,7 +8,7 @@ import static com.nvidia.cuvs.lucene.benchmarks.Utils.generateDataset; import static com.nvidia.cuvs.lucene.benchmarks.Utils.index; -import com.nvidia.cuvs.lucene.CuVS2510GPUSearchCodec; +import com.nvidia.cuvs.lucene.CuVSCodecs; import com.nvidia.cuvs.spi.CuVSProvider; import java.io.IOException; import java.nio.file.Path; @@ -61,7 +61,7 @@ public void setup() throws Exception { CuVSProvider.provider().enableRMMAsyncMemory(); random = new Random(222); indexDirPath = Paths.get(UUID.randomUUID().toString()); - codec = new CuVS2510GPUSearchCodec(); + codec = CuVSCodecs.gpuSearch(); numDocs = 1000; dimension = 128; dataset = generateDataset(random, numDocs, dimension); diff --git a/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/CagraSearchBenchmarks.java b/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/CagraSearchBenchmarks.java index 111d8c1417..2f7e205495 100644 --- a/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/CagraSearchBenchmarks.java +++ b/java/cuvs-lucene/bench/src/main/java/com/nvidia/cuvs/lucene/benchmarks/CagraSearchBenchmarks.java @@ -9,7 +9,7 @@ import static com.nvidia.cuvs.lucene.benchmarks.Utils.index; import static com.nvidia.cuvs.lucene.benchmarks.Utils.search; -import com.nvidia.cuvs.lucene.CuVS2510GPUSearchCodec; +import com.nvidia.cuvs.lucene.CuVSCodecs; import com.nvidia.cuvs.lucene.GPUKnnFloatVectorQuery; import com.nvidia.cuvs.spi.CuVSProvider; import java.io.IOException; @@ -66,7 +66,7 @@ public void setup() throws Exception { CuVSProvider.provider().enableRMMAsyncMemory(); random = new Random(222); indexDirPath = Paths.get(UUID.randomUUID().toString()); - codec = new CuVS2510GPUSearchCodec(); + codec = CuVSCodecs.gpuSearch(); numDocs = 1000; dimension = 128; topK = 5; diff --git a/java/cuvs-lucene/build.sh b/java/cuvs-lucene/build.sh index dcf6668e99..adcce00ba5 100755 --- a/java/cuvs-lucene/build.sh +++ b/java/cuvs-lucene/build.sh @@ -9,7 +9,6 @@ ARGS="$*" NUMARGS=$# VERSION="26.12.0" # Note: The version is updated automatically when ci/release/update-version.sh is invoked -GROUP_ID="com.nvidia.cuvs.lucene" function hasArg { (( NUMARGS != 0 )) && (echo " ${ARGS} " | grep -q " $1 ") @@ -34,22 +33,20 @@ if [ -d "${CUVS_LIB_DIR}" ]; then export LD_LIBRARY_PATH="${CUVS_LIB_DIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" fi -MAVEN_VERIFY_ARGS=() +# A test failure in one module doesn't stop the others: every module is tested and reported, +# and the build still fails at the end. +MAVEN_INSTALL_ARGS=("--fail-at-end") if ! hasArg --run-java-tests; then - MAVEN_VERIFY_ARGS=("-DskipTests") + MAVEN_INSTALL_ARGS+=("-DskipTests") fi cd "${LUCENE_DIR}" -mvn clean verify "${MAVEN_VERIFY_ARGS[@]}" - -# Coverage data only exists when the tests actually ran. -if hasArg --run-java-tests; then - mvn jacoco:report -fi - -mvn install:install-file -Dfile=./target/cuvs-lucene-$VERSION.jar -DgroupId=$GROUP_ID -DartifactId=cuvs-lucene -Dversion=$VERSION -Dpackaging=jar -cp pom.xml ./target/ +# Builds every lucene-X.Y module (one artifact per supported Lucene release), runs their tests when +# asked to, and installs them into the local Maven repository. Each module's target/ also gets a +# standalone pom.xml, so that it holds all the files needed to publish that module. When the tests +# run, the build also writes their coverage report to each module's target/site/jacoco. +mvn clean install "${MAVEN_INSTALL_ARGS[@]}" # Build the cuvs-lucene examples against the jar just installed above, to catch drift between the # examples and the cuvs-lucene API. diff --git a/java/cuvs-lucene/lucene-10.2/pom.xml b/java/cuvs-lucene/lucene-10.2/pom.xml new file mode 100644 index 0000000000..5650114e5e --- /dev/null +++ b/java/cuvs-lucene/lucene-10.2/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + com.nvidia.cuvs.lucene + cuvs-lucene-parent + + 26.12.0 + + cuvs-lucene-10.2 + cuvs-lucene-10.2 + jar + + + Pluggable codecs for Apache Lucene 10.2.x that enable GPU-based accelerated vector search. + + + + 10.2.0 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + diff --git a/java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneCompat.java b/java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneCompat.java new file mode 100644 index 0000000000..03a380f89a --- /dev/null +++ b/java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneCompat.java @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.nvidia.cuvs.lucene; + +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; +import org.apache.lucene.codecs.lucene101.Lucene101Codec; +import org.apache.lucene.codecs.lucene99.Lucene99ScalarQuantizedVectorsFormat; + +/** + * The Lucene calls that differ between the Lucene releases cuvs-lucene supports, for Lucene 10.2. + * + *

Every lucene-X.Y module has its own copy of this class, compiled against that release. The + * shared sources go through it instead of calling those parts of Lucene directly. + */ +final class LuceneCompat { + + // The Lucene release this module is built for. Both must stay compile-time constants: javac + // copies them into the classes that read them, so LuceneVersionGuard can check the Lucene version + // without loading this class, which does not link against other Lucene releases. + static final int LUCENE_MAJOR = 10; + static final int LUCENE_MINOR = 2; + + private LuceneCompat() {} + + /** Returns Lucene 10.2's {@code Lucene101Codec}. */ + static Codec lucene101Codec() { + return new Lucene101Codec(); + } + + /** Returns the flat format that stores the vectors of the scalar-quantized codecs. */ + static FlatVectorsFormat lucene99ScalarQuantizedFlatFormat() { + return new Lucene99ScalarQuantizedVectorsFormat(); + } + + // The codecs for this Lucene release, returned by CuVSCodecs. + + static Codec acceleratedHNSWCodec(AcceleratedHNSWParams params) { + return new Lucene101AcceleratedHNSWCodec(params); + } + + static Codec acceleratedHNSWScalarQuantizedCodec(AcceleratedHNSWParams params) { + return new LuceneAcceleratedHNSWScalarQuantizedCodec(params); + } + + static KnnVectorsFormat acceleratedHNSWScalarQuantizedFormat(AcceleratedHNSWParams params) { + return new LuceneAcceleratedHNSWScalarQuantizedVectorsFormat(params); + } + + static Codec acceleratedHNSWBinaryQuantizedCodec(AcceleratedHNSWParams params) { + return new LuceneAcceleratedHNSWBinaryQuantizedCodec(params); + } + + static Codec gpuSearchCodec(GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) { + return new CuVS2510GPUSearchCodec(params, filterCacheConfig); + } +} diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneProvider.java b/java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneProvider.java similarity index 93% rename from java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneProvider.java rename to java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneProvider.java index 576e0163e9..ddaca3f97d 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneProvider.java +++ b/java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneProvider.java @@ -26,8 +26,24 @@ /** * Dynamically loads Lucene format, reader, and writer classes with a fallback mechanism. * + *

Remains unchanged, for source compatibility, only in {@code cuvs-lucene-10.2}; the artifacts + * for later Lucene releases do not have it. To migrate off it: + * + *

    + *
  • Get the cuvs-lucene codecs from {@link CuVSCodecs} instead of building them from {@link + * #getCodec}. + *
  • Create Lucene formats, readers and writers directly, for example {@code new + * Lucene99FlatVectorsFormat(scorer)}. With one cuvs-lucene artifact per Lucene release, the + * classes are known at compile time. + *
  • Don't read Lucene constants through {@link #getStaticIntParam}: a {@code VERSION_CURRENT} + * read this way does not match the data written on Lucene 10.3 and later. + *
+ * * @since 25.12 + * @deprecated only available with Lucene 10.2 and will be removed together with it; see above for + * replacements. */ +@Deprecated(since = "26.12", forRemoval = true) public class LuceneProvider { static final Logger log = Logger.getLogger(LuceneProvider.class.getName()); diff --git a/java/cuvs-lucene/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec b/java/cuvs-lucene/lucene-10.2/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec similarity index 100% rename from java/cuvs-lucene/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec rename to java/cuvs-lucene/lucene-10.2/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec diff --git a/java/cuvs-lucene/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat b/java/cuvs-lucene/lucene-10.2/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat similarity index 72% rename from java/cuvs-lucene/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat rename to java/cuvs-lucene/lucene-10.2/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat index 6625ac72a4..1f9ceeda67 100644 --- a/java/cuvs-lucene/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat +++ b/java/cuvs-lucene/lucene-10.2/src/main/resources/META-INF/services/org.apache.lucene.codecs.KnnVectorsFormat @@ -1,8 +1,6 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION. # SPDX-License-Identifier: Apache-2.0 -org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat -org.apache.lucene.codecs.lucene99.Lucene99HnswScalarQuantizedVectorsFormat com.nvidia.cuvs.lucene.CuVS2510GPUVectorsFormat com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat com.nvidia.cuvs.lucene.LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat diff --git a/java/cuvs-lucene/lucene-10.2/src/test/java/com/nvidia/cuvs/lucene/TestLuceneProvider.java b/java/cuvs-lucene/lucene-10.2/src/test/java/com/nvidia/cuvs/lucene/TestLuceneProvider.java new file mode 100644 index 0000000000..c3b1f821b5 --- /dev/null +++ b/java/cuvs-lucene/lucene-10.2/src/test/java/com/nvidia/cuvs/lucene/TestLuceneProvider.java @@ -0,0 +1,42 @@ +/* + * 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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; +import org.junit.Test; + +/** + * Tests the deprecated {@link LuceneProvider}, which only {@code cuvs-lucene-10.2} still ships. + * + * @since 25.12 + */ +@SuppressWarnings("removal") +public class TestLuceneProvider { + + @Test + public void testFallback() throws Exception { + // Lucene99Codec exists in the org.apache.lucene.backward_codecs.lucene99 + Codec c = LuceneProvider.getCodec("99"); + assertEquals(c.getName(), "Lucene99"); + } + + @Test(expected = ClassNotFoundException.class) + public void testNonexistentCodec() throws Exception { + LuceneProvider.getCodec("0"); + } + + @Test + public void testExistingComponents() throws Exception { + LuceneProvider provider = LuceneProvider.getInstance("99"); + assertTrue(provider.getLuceneFlatVectorsFormatInstance(null) instanceof FlatVectorsFormat); + assertEquals(provider.getStaticIntParam("VERSION_CURRENT"), 0); + assertNotEquals(provider.getSimilarityFunctions().size(), 0); + } +} diff --git a/java/cuvs-lucene/pom.xml b/java/cuvs-lucene/pom.xml index a9c7483ea0..70ce9b0788 100644 --- a/java/cuvs-lucene/pom.xml +++ b/java/cuvs-lucene/pom.xml @@ -2,19 +2,26 @@ SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> + + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" + child.project.url.inherit.append.path="false"> 4.0.0 com.nvidia.cuvs.lucene - cuvs-lucene + cuvs-lucene-parent 26.12.0 - cuvs-lucene - jar + cuvs-lucene-parent + pom This project provides pluggable codecs for Apache Lucene to enable GPU-based accelerated vector search. + Lucene changes its codec APIs between minor releases, so there is one artifact per supported Lucene + minor version (cuvs-lucene-10.2 for Lucene 10.2), all built from the same sources. https://rapids.ai @@ -26,7 +33,9 @@ SPDX-License-Identifier: Apache-2.0 A business-friendly OSS license - + scm:git:https://github.com/rapidsai/cuvs.git scm:git:git@github.com:rapidsai/cuvs.git HEAD @@ -45,11 +54,28 @@ SPDX-License-Identifier: Apache-2.0 + + + lucene-10.2 + + relocation + + - 22 - 22 + 21 UTF-8 UTF-8 + + 10.2.0 + + ${project.basedir}/../src @@ -106,148 +132,301 @@ SPDX-License-Identifier: Apache-2.0 + org.apache.lucene lucene-core - 10.2.0 + ${lucene.version} org.apache.lucene - lucene-codecs - 10.2.0 - test + lucene-backward-codecs + ${lucene.version} org.apache.lucene - lucene-backward-codecs - 10.2.0 + lucene-codecs + ${lucene.version} + test org.apache.lucene lucene-test-framework - 10.2.0 + ${lucene.version} test commons-io commons-io - 2.18.0 test com.nvidia.cuvs cuvs-java - 26.12.0 + + + + com.nvidia.cuvs + cuvs-java + 26.12.0 + + + commons-io + commons-io + 2.18.0 + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.2 + + + add-shared-sources + generate-sources + + add-source + + + + ${cuvs.lucene.shared.dir}/main/java + + + + + add-shared-resources + generate-resources + + add-resource + + + + + ${cuvs.lucene.shared.dir}/main/resources + + + + + + add-shared-test-sources + generate-test-sources + + add-test-source + + + + ${cuvs.lucene.shared.dir}/test/java + + + + + add-shared-test-resources + generate-test-resources + + add-test-resource + + + + + ${cuvs.lucene.shared.dir}/test/resources + + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.8.0 + + ossrh + ${project.build.directory} + pom.xml + + + + flatten + process-resources + + flatten + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.6.0 + + + ${cuvs.lucene.shared.dir}/assembly/jar-with-dependencies.xml + + + + + true + + + + true + + + + + + make-assembly + package + + single + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.4 + + plain + false + false + false + 0 + + ${cuvs.lucene.shared.dir}/main/resources/logging.properties + + + ${project.build.directory}/cufile.log + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.12.0 + + + attach-javadocs + + jar + + + com.nvidia.cuvs.lucene + all,-missing + ${project.build.directory}/javadocs + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.14 + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + + + - com.diffplug.spotless - spotless-maven-plugin - 2.44.5 - - - - src/**/*.java - - - 1.27.0 - - true - false - - - + org.codehaus.mojo + build-helper-maven-plugin - org.apache.maven.plugins - maven-assembly-plugin - 3.6.0 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - + org.codehaus.mojo + flatten-maven-plugin org.apache.maven.plugins maven-surefire-plugin - 3.5.4 - - plain - false - false - false - 0 - - ${project.basedir}/src/main/resources/logging.properties - - - ${project.build.directory}/cufile.log - - org.apache.maven.plugins maven-source-plugin - 3.3.1 - - - attach-sources - - jar-no-fork - - - org.apache.maven.plugins maven-javadoc-plugin - 3.12.0 - - - attach-javadocs - - jar - - - com.nvidia.cuvs.lucene - all,-missing - ${project.build.directory}/javadocs - - - - org.jacoco - jacoco-maven-plugin - 0.8.14 - - - - prepare-agent - - - - report - prepare-package - - report - - - + org.jacoco + jacoco-maven-plugin + + + + com.diffplug.spotless + spotless-maven-plugin + 2.44.5 + + + + src/**/*.java + + + 1.27.0 + + true + false + + + diff --git a/java/cuvs-lucene/relocation/pom.xml b/java/cuvs-lucene/relocation/pom.xml new file mode 100644 index 0000000000..fafc24abb0 --- /dev/null +++ b/java/cuvs-lucene/relocation/pom.xml @@ -0,0 +1,129 @@ + + + + 4.0.0 + com.nvidia.cuvs.lucene + cuvs-lucene + + 26.12.0 + pom + cuvs-lucene + + Relocated: cuvs-lucene is now published once per Lucene minor release, as cuvs-lucene-10.2 for + Lucene 10.2. + + https://rapids.ai + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + scm:git:https://github.com/rapidsai/cuvs.git + scm:git:git@github.com:rapidsai/cuvs.git + HEAD + https://github.com/rapidsai/cuvs + + + + rapidsai + rapidsai + cuvs@rapids.ai + + + + + + cuvs-lucene-10.2 + cuvs-lucene is now published once per Lucene minor release: depend on the cuvs-lucene-X.Y artifact matching the Lucene version you use. This relocation points to cuvs-lucene-10.2, which matches the Lucene release the old artifact was built for. + + + + + + + gcs-maven-central-mirror + GCS Maven Central mirror + https://maven-central.storage-download.googleapis.com/maven2/ + + true + + + false + + + + central + Maven Plugin Repository + https://repo.maven.apache.org/maven2 + + true + + + false + + + + + + + + + com.diffplug.spotless + spotless-maven-plugin + 2.44.5 + + true + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + copy-pom-to-target + package + + copy-resources + + + ${project.build.directory} + + + ${project.basedir} + + pom.xml + + + + + + + + + + diff --git a/java/cuvs-lucene/src/assembly/jar-with-dependencies.xml b/java/cuvs-lucene/src/assembly/jar-with-dependencies.xml new file mode 100644 index 0000000000..6ab70061da --- /dev/null +++ b/java/cuvs-lucene/src/assembly/jar-with-dependencies.xml @@ -0,0 +1,32 @@ + + + + jar-with-dependencies + + jar + + false + + + / + true + true + runtime + + + + + metaInf-services + + + diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java index c82586f161..795f04904e 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/AcceleratedHNSWUtils.java @@ -20,6 +20,7 @@ import java.util.Random; import java.util.SortedSet; import java.util.TreeSet; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.VectorSimilarityFunction; import org.apache.lucene.store.IndexOutput; @@ -37,17 +38,8 @@ public enum QuantizationType { NONE } - private static final LuceneProvider LUCENE_PROVIDER; - private static final List VECTOR_SIMILARITY_FUNCTIONS; - - static { - try { - LUCENE_PROVIDER = LuceneProvider.getInstance("99"); - VECTOR_SIMILARITY_FUNCTIONS = LUCENE_PROVIDER.getSimilarityFunctions(); - } catch (Exception e) { - throw new ExceptionInInitializerError(e.getMessage()); - } - } + private static final List VECTOR_SIMILARITY_FUNCTIONS = + Lucene99HnswVectorsReader.SIMILARITY_FUNCTIONS; /** * Creates a dummy HNSW graph for a single vector. diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java index ad286904cf..8a16c10ea8 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.java @@ -4,36 +4,26 @@ */ package com.nvidia.cuvs.lucene; -import com.nvidia.cuvs.LibraryException; -import java.util.logging.Level; -import java.util.logging.Logger; import org.apache.lucene.codecs.Codec; -import org.apache.lucene.codecs.FilterCodec; -import org.apache.lucene.codecs.KnnVectorsFormat; /** * cuVS based codec for GPU based vector search that enables both - indexing and search on the GPU. * cuVS serialization formats are in experimental phase and hence backward compatibility cannot be guaranteed. * + *

This codec wraps {@code Lucene101Codec}, the default codec of Lucene 10.2. {@link CuVSCodecs#gpuSearch} + * returns the codec of this family for the Lucene release in use. + * * @since 25.10 */ -public class CuVS2510GPUSearchCodec extends FilterCodec { +public class CuVS2510GPUSearchCodec extends CuVSFilterCodec { - private static final Logger log = Logger.getLogger(CuVS2510GPUSearchCodec.class.getName()); private static final String NAME = "CuVS2510GPUSearchCodec"; - private KnnVectorsFormat format; /** * Default constructor for {@link CuVS2510GPUSearchCodec}. - * - * @throws Exception */ - public CuVS2510GPUSearchCodec() throws Exception { - this( - NAME, - LuceneProvider.getCodec("101"), - new GPUSearchParams.Builder().build(), - FilterBitsetCacheConfig.DEFAULT); + public CuVS2510GPUSearchCodec() { + this(new GPUSearchParams.Builder().build(), FilterBitsetCacheConfig.DEFAULT); } /** @@ -52,10 +42,9 @@ public CuVS2510GPUSearchCodec(String name, Codec delegate) { * or overridden parameter values. * * @param params An instance of {@link GPUSearchParams} - * @throws Exception Exception raised when initializing the codec */ - public CuVS2510GPUSearchCodec(GPUSearchParams params) throws Exception { - this(NAME, LuceneProvider.getCodec("101"), params, FilterBitsetCacheConfig.DEFAULT); + public CuVS2510GPUSearchCodec(GPUSearchParams params) { + this(params, FilterBitsetCacheConfig.DEFAULT); } /** @@ -63,11 +52,12 @@ public CuVS2510GPUSearchCodec(GPUSearchParams params) throws Exception { * * @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); + public CuVS2510GPUSearchCodec(GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) { + super( + NAME, + delegate(NAME, () -> LuceneCompat.lucene101Codec()), + () -> new CuVS2510GPUVectorsFormat(params, filterCacheConfig)); } /** @@ -83,43 +73,6 @@ public CuVS2510GPUSearchCodec( 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, FilterBitsetCacheConfig filterCacheConfig) { - try { - format = new CuVS2510GPUVectorsFormat(params, filterCacheConfig); - setKnnFormat(format); - } catch (LibraryException ex) { - log.log( - Level.SEVERE, - "Couldn't load native library, possible classloader issue. " + ex.getMessage()); - } - } - - /** - * Get the configured {@link KnnVectorsFormat}. - * - * @return the instance of the {@link KnnVectorsFormat} - */ - @Override - public KnnVectorsFormat knnVectorsFormat() { - return format; - } - - /** - * Set the {@link KnnVectorsFormat}. - * - * @param format the {@link KnnVectorsFormat} to set - */ - public void setKnnFormat(KnnVectorsFormat format) { - this.format = format; + super(name, delegate, () -> new CuVS2510GPUVectorsFormat(params, filterCacheConfig)); } } diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java index 3e62bd71b5..d0a9ca1119 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.java @@ -13,6 +13,7 @@ import org.apache.lucene.codecs.KnnVectorsWriter; import org.apache.lucene.codecs.hnsw.DefaultFlatVectorScorer; import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99FlatVectorsFormat; import org.apache.lucene.index.SegmentReadState; import org.apache.lucene.index.SegmentWriteState; @@ -25,8 +26,8 @@ public class CuVS2510GPUVectorsFormat extends KnnVectorsFormat { private static final int MAX_DIMENSIONS = 4096; - private static final LuceneProvider LUCENE_PROVIDER; - private static final FlatVectorsFormat FLAT_VECTORS_FORMAT; + private static final FlatVectorsFormat FLAT_VECTORS_FORMAT = + new Lucene99FlatVectorsFormat(DefaultFlatVectorScorer.INSTANCE); public static final String CUVS_META_CODEC_NAME = "Lucene102CuVSVectorsFormatMeta"; public static final String CUVS_META_CODEC_EXT = "vemc"; @@ -38,16 +39,6 @@ public class CuVS2510GPUVectorsFormat extends KnnVectorsFormat { private final GPUSearchParams gpuSearchParams; private final FilterBitsetCache filterBitsetCache; - static { - try { - LUCENE_PROVIDER = LuceneProvider.getInstance("99"); - FLAT_VECTORS_FORMAT = - LUCENE_PROVIDER.getLuceneFlatVectorsFormatInstance(DefaultFlatVectorScorer.INSTANCE); - } catch (Exception e) { - throw new ExceptionInInitializerError(e.getMessage()); - } - } - /** * Initializes the {@link CuVS2510GPUVectorsFormat} with default parameter values. * @@ -86,6 +77,7 @@ public CuVS2510GPUVectorsFormat( */ @Override public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException { + LuceneVersionGuard.ensureCompatible(); assertIsSupported(); var flatWriter = FLAT_VECTORS_FORMAT.fieldsWriter(state); return new CuVS2510GPUVectorsWriter(state, gpuSearchParams, flatWriter); @@ -96,6 +88,7 @@ public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException */ @Override public KnnVectorsReader fieldsReader(SegmentReadState state) throws IOException { + LuceneVersionGuard.ensureCompatible(); assertIsSupported(); return new CuVS2510GPUVectorsReader( state, FLAT_VECTORS_FORMAT.fieldsReader(state), filterBitsetCache); diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java index 8517438835..ebbe90071b 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsReader.java @@ -32,6 +32,7 @@ import org.apache.lucene.codecs.CodecUtil; import org.apache.lucene.codecs.KnnVectorsReader; import org.apache.lucene.codecs.hnsw.FlatVectorsReader; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader; import org.apache.lucene.index.ByteVectorValues; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.FieldInfo; @@ -61,8 +62,8 @@ */ public class CuVS2510GPUVectorsReader extends KnnVectorsReader { - private static final LuceneProvider LUCENE_PROVIDER; - private static final List VECTOR_SIMILARITY_FUNCTIONS; + private static final List VECTOR_SIMILARITY_FUNCTIONS = + Lucene99HnswVectorsReader.SIMILARITY_FUNCTIONS; private final FlatVectorsReader flatVectorsReader; private final FieldInfos fieldInfos; @@ -80,15 +81,6 @@ public class CuVS2510GPUVectorsReader extends KnnVectorsReader { private final IndexInput cuvsIndexInput; private final FilterBitsetCache filterBitsetCache; - static { - try { - LUCENE_PROVIDER = LuceneProvider.getInstance("99"); - VECTOR_SIMILARITY_FUNCTIONS = LUCENE_PROVIDER.getSimilarityFunctions(); - } catch (Exception e) { - throw new ExceptionInInitializerError(e.getMessage()); - } - } - /** * Initializes the {@link CuVS2510GPUVectorsReader}, checks and loads the index. * diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java index 1a84f13e19..061ff23a94 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVS2510GPUVectorsWriter.java @@ -35,6 +35,7 @@ import org.apache.lucene.codecs.KnnVectorsWriter; import org.apache.lucene.codecs.hnsw.FlatFieldVectorsWriter; import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader; import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; import org.apache.lucene.index.DocsWithFieldSet; import org.apache.lucene.index.FieldInfo; @@ -61,8 +62,8 @@ public class CuVS2510GPUVectorsWriter extends KnnVectorsWriter { private static final long SHALLOW_RAM_BYTES_USED = shallowSizeOfInstance(CuVS2510GPUVectorsWriter.class); private static final String COMPONENT = "CuVS2510GPUVectorsWriter"; - private static final LuceneProvider LUCENE_PROVIDER; - private static final List VECTOR_SIMILARITY_FUNCTIONS; + private static final List VECTOR_SIMILARITY_FUNCTIONS = + Lucene99HnswVectorsReader.SIMILARITY_FUNCTIONS; private static final int MIN_CAGRA_INDEX_SIZE = 2; private final GPUSearchParams gpuSearchParams; @@ -72,15 +73,6 @@ public class CuVS2510GPUVectorsWriter extends KnnVectorsWriter { private IndexOutput meta = null, cuvsIndex = null; private boolean finished; - static { - try { - LUCENE_PROVIDER = LuceneProvider.getInstance("99"); - VECTOR_SIMILARITY_FUNCTIONS = LUCENE_PROVIDER.getSimilarityFunctions(); - } catch (Exception e) { - throw new ExceptionInInitializerError(e.getMessage()); - } - } - /** * The cuVS index Types. */ diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java new file mode 100644 index 0000000000..67df08bdac --- /dev/null +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.nvidia.cuvs.lucene; + +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.KnnVectorsFormat; + +/** + * Creates the cuvs-lucene codecs and vectors formats that write indexes on the Lucene release this + * artifact is built for. + * + *

Each cuvs-lucene codec wraps the default codec of one Lucene release, for example {@code + * Lucene101AcceleratedHNSWCodec} wraps Lucene 10.2's {@code Lucene101Codec}, so the codec to write + * with changes along with Lucene. Creating codecs through this class instead of naming them keeps + * application code unchanged when it moves to another Lucene release and the matching cuvs-lucene + * artifact. + * + *

Every method throws {@link IllegalStateException} if the Lucene release in use is not the one + * this cuvs-lucene artifact is built for. + * + * @since 26.12 + */ +public final class CuVSCodecs { + + private CuVSCodecs() {} + + /** + * Checks that the Lucene release in use is the one this cuvs-lucene artifact is built for. + * + *

A cuvs-lucene artifact on the wrong Lucene release does not break Lucene: Lucene keeps + * working, and so do indexes that do not use the cuvs-lucene codecs, while using one of these + * codecs fails. Applications that would rather not start at all in that case can call this method + * during startup. + * + * @throws IllegalStateException if the artifact is not built for the Lucene release in use; the + * message names the artifact to use instead + */ + public static void checkLuceneVersion() { + LuceneVersionGuard.ensureCompatible(); + } + + /** + * Returns the codec that builds HNSW graphs on the GPU and searches them on the CPU, with default + * parameters. + * + * @return the codec + */ + public static Codec acceleratedHNSW() { + return acceleratedHNSW(new AcceleratedHNSWParams.Builder().build()); + } + + /** + * Returns the codec that builds HNSW graphs on the GPU and searches them on the CPU. + * + * @param params the index build parameters + * @return the codec + */ + public static Codec acceleratedHNSW(AcceleratedHNSWParams params) { + LuceneVersionGuard.ensureCompatible(); + return LuceneCompat.acceleratedHNSWCodec(params); + } + + /** + * Returns the codec that builds HNSW graphs on the GPU over scalar-quantized vectors, with + * default parameters. + * + * @return the codec + */ + public static Codec acceleratedHNSWScalarQuantized() { + return acceleratedHNSWScalarQuantized(new AcceleratedHNSWParams.Builder().build()); + } + + /** + * Returns the codec that builds HNSW graphs on the GPU over scalar-quantized vectors. + * + * @param params the index build parameters + * @return the codec + */ + public static Codec acceleratedHNSWScalarQuantized(AcceleratedHNSWParams params) { + LuceneVersionGuard.ensureCompatible(); + return LuceneCompat.acceleratedHNSWScalarQuantizedCodec(params); + } + + /** + * Returns the vectors format used by {@link + * #acceleratedHNSWScalarQuantized(AcceleratedHNSWParams)}, for use with a per-field codec. + * + * @param params the index build parameters + * @return the vectors format + */ + public static KnnVectorsFormat acceleratedHNSWScalarQuantizedFormat( + AcceleratedHNSWParams params) { + LuceneVersionGuard.ensureCompatible(); + return LuceneCompat.acceleratedHNSWScalarQuantizedFormat(params); + } + + /** + * Returns the codec that builds HNSW graphs on the GPU over binary-quantized vectors, with + * default parameters. + * + * @return the codec + */ + public static Codec acceleratedHNSWBinaryQuantized() { + return acceleratedHNSWBinaryQuantized(new AcceleratedHNSWParams.Builder().build()); + } + + /** + * Returns the codec that builds HNSW graphs on the GPU over binary-quantized vectors. + * + * @param params the index build parameters + * @return the codec + */ + public static Codec acceleratedHNSWBinaryQuantized(AcceleratedHNSWParams params) { + LuceneVersionGuard.ensureCompatible(); + return LuceneCompat.acceleratedHNSWBinaryQuantizedCodec(params); + } + + /** + * Returns the codec that both builds and searches CAGRA indexes on the GPU, with default + * parameters. + * + * @return the codec + */ + public static Codec gpuSearch() { + return gpuSearch(new GPUSearchParams.Builder().build()); + } + + /** + * Returns the codec that both builds and searches CAGRA indexes on the GPU, with the default + * filter-bitset-cache configuration. + * + * @param params the GPU index and search parameters + * @return the codec + */ + public static Codec gpuSearch(GPUSearchParams params) { + return gpuSearch(params, FilterBitsetCacheConfig.DEFAULT); + } + + /** + * Returns the codec that both builds and searches CAGRA indexes on the GPU. + * + * @param params the GPU index and search parameters + * @param filterCacheConfig the filter-bitset-cache configuration + * @return the codec + */ + public static Codec gpuSearch(GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) { + LuceneVersionGuard.ensureCompatible(); + return LuceneCompat.gpuSearchCodec(params, filterCacheConfig); + } +} diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java new file mode 100644 index 0000000000..cf815943ca --- /dev/null +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java @@ -0,0 +1,246 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.nvidia.cuvs.lucene; + +import com.nvidia.cuvs.LibraryException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.CompoundFormat; +import org.apache.lucene.codecs.DocValuesFormat; +import org.apache.lucene.codecs.FieldInfosFormat; +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.KnnVectorsReader; +import org.apache.lucene.codecs.KnnVectorsWriter; +import org.apache.lucene.codecs.LiveDocsFormat; +import org.apache.lucene.codecs.NormsFormat; +import org.apache.lucene.codecs.PointsFormat; +import org.apache.lucene.codecs.PostingsFormat; +import org.apache.lucene.codecs.SegmentInfoFormat; +import org.apache.lucene.codecs.StoredFieldsFormat; +import org.apache.lucene.codecs.TermVectorsFormat; +import org.apache.lucene.index.SegmentReadState; +import org.apache.lucene.index.SegmentWriteState; +import org.apache.lucene.util.Version; + +/** + * Base class of the cuvs-lucene codecs: a {@link FilterCodec} that replaces its delegate's + * {@link KnnVectorsFormat}. + * + *

Each cuvs-lucene codec wraps the default codec of one Lucene release, for example + * {@code Lucene101Codec} for Lucene 10.2. Use {@link CuVSCodecs} to get the codecs for the Lucene + * release in use. + */ +abstract class CuVSFilterCodec extends FilterCodec { + + private static final Logger log = Logger.getLogger(CuVSFilterCodec.class.getName()); + // Every registered codec runs into the same problem, so it is logged only once. + private static final AtomicBoolean unavailableLogged = new AtomicBoolean(); + + private KnnVectorsFormat format; + + /** + * Creates a codec. + * + * @param name the codec's name + * @param delegate the codec to use for everything but vectors; for the codecs of this module, + * the result of {@link #delegate(String, Supplier)} + * @param format creates the {@link KnnVectorsFormat} to use for vectors + */ + protected CuVSFilterCodec(String name, Codec delegate, Supplier format) { + super(name, delegate); + try { + setKnnFormat(format.get()); + } catch (LibraryException | LinkageError e) { + // Lucene's service loader creates every codec eagerly, and an exception here would break + // every codec lookup in the JVM. A format that does not link on the Lucene release in use, or + // cannot reach the cuVS library, costs only this codec, which then fails when used, like an + // unavailable delegate. Other exceptions, such as invalid arguments, reach the caller. + String problem = "Its vectors format is not available: " + e; + logUnavailable(name, problem); + setKnnFormat(new UnavailableFormat(name, problem)); + } + } + + /** + * Returns the Lucene codec a cuvs-lucene codec delegates to, or, if it cannot be created on the + * Lucene release in use, a codec that fails with an explanation when it is used. + * + *

Lucene's service loader creates every registered codec when it first looks one up, and an + * exception there breaks every codec lookup in the JVM, Lucene's own included. A cuvs-lucene + * artifact running on another Lucene release than it was built for must therefore still create + * its codecs, and only fail once they are used. + * + *

Pass {@code delegate} as a lambda, such as {@code () -> LuceneCompat.lucene101Codec()}, not + * as a method reference: the JVM resolves a method reference, and so loads {@code LuceneCompat}, + * before this method runs, which is exactly what can fail on the wrong Lucene release. + * + *

The first time a codec is unavailable, the problem is logged at {@code SEVERE}, so that it + * shows up when Lucene first looks up its codecs, typically at startup, even though it only fails + * once the codec is used. Applications that would rather not start can call {@link + * CuVSCodecs#checkLuceneVersion()}. + * + * @param name the name of the cuvs-lucene codec, for the error message + * @param delegate creates the Lucene codec to delegate to + * @return the Lucene codec, or a codec that throws {@link IllegalStateException} when used + */ + static Codec delegate(String name, Supplier delegate) { + String problem = LuceneVersionGuard.mismatchMessage(Version.LATEST); + if (problem == null) { + try { + return delegate.get(); + } catch (LinkageError e) { + problem = "The Lucene codec " + name + " delegates to is not available: " + e; + } + } + logUnavailable(name, problem); + return new UnavailableCodec(name, problem); + } + + /** Logs, once per JVM, that a codec cannot be used. */ + private static void logUnavailable(String name, String problem) { + if (unavailableLogged.compareAndSet(false, true)) { + log.log( + Level.SEVERE, + "The cuvs-lucene codecs cannot be used, starting with " + + name + + ": " + + problem + + " Lucene and indexes that do not use these codecs are not affected; reading or" + + " writing an index that does fails."); + } + } + + /** + * Sets whether the problem {@link #delegate} logs has already been logged, and returns the + * previous value. The flag is static, so a test that goes through {@link #delegate}'s failure path + * restores it afterwards, leaving later tests in the same JVM unaffected. + */ + static boolean setUnavailableLoggedForTesting(boolean logged) { + return unavailableLogged.getAndSet(logged); + } + + /** + * Get the configured {@link KnnVectorsFormat}. + * + * @return the instance of the {@link KnnVectorsFormat} + */ + @Override + public KnnVectorsFormat knnVectorsFormat() { + return format; + } + + /** + * Set the {@link KnnVectorsFormat}. + * + * @param format the {@link KnnVectorsFormat} to set + */ + public void setKnnFormat(KnnVectorsFormat format) { + this.format = format; + } + + /** Stands in for a delegate codec that cannot be created, and throws when any format is used. */ + private static final class UnavailableCodec extends Codec { + + private final String problem; + + UnavailableCodec(String name, String problem) { + super(name); + this.problem = problem; + } + + private IllegalStateException unavailable() { + return new IllegalStateException(getName() + " cannot be used: " + problem); + } + + @Override + public PostingsFormat postingsFormat() { + throw unavailable(); + } + + @Override + public DocValuesFormat docValuesFormat() { + throw unavailable(); + } + + @Override + public StoredFieldsFormat storedFieldsFormat() { + throw unavailable(); + } + + @Override + public TermVectorsFormat termVectorsFormat() { + throw unavailable(); + } + + @Override + public FieldInfosFormat fieldInfosFormat() { + throw unavailable(); + } + + @Override + public SegmentInfoFormat segmentInfoFormat() { + throw unavailable(); + } + + @Override + public NormsFormat normsFormat() { + throw unavailable(); + } + + @Override + public LiveDocsFormat liveDocsFormat() { + throw unavailable(); + } + + @Override + public CompoundFormat compoundFormat() { + throw unavailable(); + } + + @Override + public PointsFormat pointsFormat() { + throw unavailable(); + } + + @Override + public KnnVectorsFormat knnVectorsFormat() { + throw unavailable(); + } + } + + /** Stands in for a vectors format that cannot be created, and throws when it is used. */ + private static final class UnavailableFormat extends KnnVectorsFormat { + + private final String problem; + + UnavailableFormat(String codecName, String problem) { + super(codecName); + this.problem = problem; + } + + private IllegalStateException unavailable() { + return new IllegalStateException(getName() + " cannot be used: " + problem); + } + + @Override + public KnnVectorsWriter fieldsWriter(SegmentWriteState state) { + throw unavailable(); + } + + @Override + public KnnVectorsReader fieldsReader(SegmentReadState state) { + throw unavailable(); + } + + @Override + public int getMaxDimensions(String fieldName) { + throw unavailable(); + } + } +} diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/GPUKnnFloatVectorQuery.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/GPUKnnFloatVectorQuery.java index 0d2e41a11c..dfce8dcaef 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/GPUKnnFloatVectorQuery.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/GPUKnnFloatVectorQuery.java @@ -168,6 +168,7 @@ public GPUKnnFloatVectorQuery( int maxIterations, CagraSearchParams.SearchAlgo searchAlgo) { super(field, target, k, filter); + LuceneVersionGuard.ensureCompatible(); validateSearchParameters(iTopK, searchWidth, k, threadBlockSize, maxIterations, searchAlgo); this.iTopK = iTopK; this.searchWidth = searchWidth; diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java index e9f4d6fead..860118c3b3 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.java @@ -4,33 +4,27 @@ */ package com.nvidia.cuvs.lucene; -import com.nvidia.cuvs.LibraryException; -import java.util.logging.Level; -import java.util.logging.Logger; import org.apache.lucene.codecs.Codec; -import org.apache.lucene.codecs.FilterCodec; -import org.apache.lucene.codecs.KnnVectorsFormat; /** * A codec that enables GPU-based accelerated HNSW capability and can be used * to accelerated indexing using GPUs and search using CPUs. Fallbacks to CPU * based indexing when used on a machine without a GPU and/or cuVS. * + *

This codec wraps {@code Lucene101Codec}, the default codec of Lucene 10.2. {@link CuVSCodecs#acceleratedHNSW} + * returns the codec of this family for the Lucene release in use. + * * @since 25.10 */ -public class Lucene101AcceleratedHNSWCodec extends FilterCodec { +public class Lucene101AcceleratedHNSWCodec extends CuVSFilterCodec { - private static final Logger log = Logger.getLogger(Lucene101AcceleratedHNSWCodec.class.getName()); private static final String NAME = "Lucene101AcceleratedHNSWCodec"; - private KnnVectorsFormat format; /** * Default constructor for {@link Lucene101AcceleratedHNSWCodec}. - * - * @throws Exception */ - public Lucene101AcceleratedHNSWCodec() throws Exception { - this(NAME, LuceneProvider.getCodec("101")); + public Lucene101AcceleratedHNSWCodec() { + this(new AcceleratedHNSWParams.Builder().build()); } /** @@ -40,62 +34,22 @@ public Lucene101AcceleratedHNSWCodec() throws Exception { * @param delegate the delegate codec to filter */ public Lucene101AcceleratedHNSWCodec(String name, Codec delegate) { - super(name, delegate); - initializeFormatDefaultValues(); + super( + name, + delegate, + () -> + new Lucene99AcceleratedHNSWVectorsFormat(new AcceleratedHNSWParams.Builder().build())); } /** * Constructor for {@link Lucene101AcceleratedHNSWCodec}. * * @param acceleratedHNSWParams instance of {@link AcceleratedHNSWParams} - * @throws Exception exception - */ - public Lucene101AcceleratedHNSWCodec(AcceleratedHNSWParams acceleratedHNSWParams) - throws Exception { - this(NAME, LuceneProvider.getCodec("101")); - initializeFormat(acceleratedHNSWParams); - } - - /** - * Initialize an instance of {@link Lucene99AcceleratedHNSWVectorsFormat} - * with an instance of {@link AcceleratedHNSWParams} with default parameter values. - */ - private void initializeFormatDefaultValues() { - initializeFormat(new AcceleratedHNSWParams.Builder().build()); - } - - /** - * Initialize an instance of {@link Lucene99AcceleratedHNSWVectorsFormat}. - * - * @param acceleratedHNSWParams instance of {@link AcceleratedHNSWParams} to use - */ - private void initializeFormat(AcceleratedHNSWParams acceleratedHNSWParams) { - try { - format = new Lucene99AcceleratedHNSWVectorsFormat(acceleratedHNSWParams); - setKnnFormat(format); - } catch (LibraryException ex) { - log.log( - Level.SEVERE, - "Couldn't load native library, possible classloader issue. " + ex.getMessage()); - } - } - - /** - * Get the configured {@link KnnVectorsFormat}. - * - * @return the instance of the {@link KnnVectorsFormat} - */ - @Override - public KnnVectorsFormat knnVectorsFormat() { - return format; - } - - /** - * Set the {@link KnnVectorsFormat}. - * - * @param format the {@link KnnVectorsFormat} to set */ - public void setKnnFormat(KnnVectorsFormat format) { - this.format = format; + public Lucene101AcceleratedHNSWCodec(AcceleratedHNSWParams acceleratedHNSWParams) { + super( + NAME, + delegate(NAME, () -> LuceneCompat.lucene101Codec()), + () -> new Lucene99AcceleratedHNSWVectorsFormat(acceleratedHNSWParams)); } } diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java index 1b1bc3cbd2..8501a52e84 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsFormat.java @@ -15,6 +15,10 @@ import org.apache.lucene.codecs.KnnVectorsWriter; import org.apache.lucene.codecs.hnsw.DefaultFlatVectorScorer; import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99FlatVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsWriter; import org.apache.lucene.index.SegmentReadState; import org.apache.lucene.index.SegmentWriteState; import org.apache.lucene.search.TaskExecutor; @@ -28,25 +32,20 @@ public class Lucene99AcceleratedHNSWVectorsFormat extends KnnVectorsFormat { private static final Logger log = Logger.getLogger(Lucene99AcceleratedHNSWVectorsFormat.class.getName()); - private static final FlatVectorsFormat FLAT_VECTORS_FORMAT; + private static final FlatVectorsFormat FLAT_VECTORS_FORMAT = + new Lucene99FlatVectorsFormat(DefaultFlatVectorScorer.INSTANCE); private static final int MAX_DIMENSIONS = 4096; private final AcceleratedHNSWParams acceleratedHNSWParams; static final String HNSW_META_CODEC_NAME = "Lucene99HnswVectorsFormatMeta"; + // The version the GPU writers record in the HNSW files. AcceleratedHNSWUtils.writeGraph writes + // plain vInt-encoded neighbor lists, the encoding of Lucene99HnswVectorsFormat.VERSION_START. + // Lucene 10.3 added a group-varint encoding and made it VERSION_CURRENT, so the version has to be + // pinned to the encoding actually written. + static final int HNSW_VERSION = Lucene99HnswVectorsFormat.VERSION_START; static final String HNSW_META_CODEC_EXT = "vem"; static final String HNSW_INDEX_CODEC_NAME = "Lucene99HnswVectorsFormatIndex"; static final String HNSW_INDEX_EXT = "vex"; - static final LuceneProvider LUCENE_PROVIDER; - - static { - try { - LUCENE_PROVIDER = LuceneProvider.getInstance("99"); - FLAT_VECTORS_FORMAT = - LUCENE_PROVIDER.getLuceneFlatVectorsFormatInstance(DefaultFlatVectorScorer.INSTANCE); - } catch (Exception e) { - throw new ExceptionInInitializerError(e.getMessage()); - } - } /** * Initializes {@link Lucene99AcceleratedHNSWVectorsFormat} with an instance @@ -74,6 +73,7 @@ public Lucene99AcceleratedHNSWVectorsFormat(AcceleratedHNSWParams acceleratedHNS */ @Override public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException { + LuceneVersionGuard.ensureCompatible(); var flatWriter = FLAT_VECTORS_FORMAT.fieldsWriter(state); if (isSupported()) { log.log(Level.FINE, "cuVS is supported so using the Lucene99AcceleratedHNSWVectorsWriter"); @@ -82,17 +82,13 @@ public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException log.log( Level.WARNING, "GPU based indexing not supported, falling back to using the Lucene99HnswVectorsWriter"); - try { - return LUCENE_PROVIDER.getLuceneHnswVectorsWriterInstance( - state, - acceleratedHNSWParams.getMaxConn(), - acceleratedHNSWParams.getBeamWidth(), - flatWriter, - acceleratedHNSWParams.getNumMergeWorkers(), - new TaskExecutor(acceleratedHNSWParams.getMergeExec())); - } catch (Exception e) { - throw Utils.handleThrowable(e); - } + return new Lucene99HnswVectorsWriter( + state, + acceleratedHNSWParams.getMaxConn(), + acceleratedHNSWParams.getBeamWidth(), + flatWriter, + acceleratedHNSWParams.getNumMergeWorkers(), + new TaskExecutor(acceleratedHNSWParams.getMergeExec())); } } @@ -101,12 +97,8 @@ public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException */ @Override public KnnVectorsReader fieldsReader(SegmentReadState state) throws IOException { - try { - return LUCENE_PROVIDER.getLuceneHnswVectorsReaderInstance( - state, FLAT_VECTORS_FORMAT.fieldsReader(state)); - } catch (Exception e) { - throw Utils.handleThrowable(e); - } + LuceneVersionGuard.ensureCompatible(); + return new Lucene99HnswVectorsReader(state, FLAT_VECTORS_FORMAT.fieldsReader(state)); } /** diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java index db21e89d60..d8811001c8 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/Lucene99AcceleratedHNSWVectorsWriter.java @@ -14,6 +14,7 @@ import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_INDEX_EXT; import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_META_CODEC_EXT; import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_META_CODEC_NAME; +import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_VERSION; import static com.nvidia.cuvs.lucene.ThreadLocalCuVSResourcesProvider.closeCuVSResourcesInstance; import static com.nvidia.cuvs.lucene.ThreadLocalCuVSResourcesProvider.getCuVSResourcesInstance; import static org.apache.lucene.index.VectorEncoding.FLOAT32; @@ -57,8 +58,6 @@ public class Lucene99AcceleratedHNSWVectorsWriter extends KnnVectorsWriter { private static final long SHALLOW_RAM_BYTES_USED = shallowSizeOfInstance(Lucene99AcceleratedHNSWVectorsWriter.class); private static final String COMPONENT = "Lucene99AcceleratedHNSWVectorsWriter"; - private static final LuceneProvider LUCENE_PROVIDER; - private static final Integer VERSION_CURRENT; private final AcceleratedHNSWParams acceleratedHNSWParams; private final FlatVectorsWriter flatVectorsWriter; @@ -71,15 +70,6 @@ public class Lucene99AcceleratedHNSWVectorsWriter extends KnnVectorsWriter { private String vexFileName; private boolean finished; - static { - try { - LUCENE_PROVIDER = LuceneProvider.getInstance("99"); - VERSION_CURRENT = LUCENE_PROVIDER.getStaticIntParam("VERSION_CURRENT"); - } catch (Exception e) { - throw new ExceptionInInitializerError(e.getMessage()); - } - } - /** * Initializes {@link Lucene99AcceleratedHNSWVectorsWriter} * @@ -110,13 +100,13 @@ public Lucene99AcceleratedHNSWVectorsWriter( CodecUtil.writeIndexHeader( hnswMeta, HNSW_META_CODEC_NAME, - VERSION_CURRENT, + HNSW_VERSION, state.segmentInfo.getId(), state.segmentSuffix); CodecUtil.writeIndexHeader( hnswVectorIndex, HNSW_INDEX_CODEC_NAME, - VERSION_CURRENT, + HNSW_VERSION, state.segmentInfo.getId(), state.segmentSuffix); success = true; diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedCodec.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedCodec.java index 0b1653bc14..2549b17049 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedCodec.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedCodec.java @@ -4,58 +4,38 @@ */ package com.nvidia.cuvs.lucene; -import com.nvidia.cuvs.LibraryException; -import java.util.logging.Level; -import java.util.logging.Logger; import org.apache.lucene.codecs.Codec; -import org.apache.lucene.codecs.FilterCodec; -import org.apache.lucene.codecs.KnnVectorsFormat; /** * CuVS based codec for GPU based vector search * + *

This codec, named {@code Lucene101AcceleratedHNSWBinaryQuantizedCodec}, wraps {@code + * Lucene101Codec}, the default codec of Lucene 10.2. {@link CuVSCodecs#acceleratedHNSWBinaryQuantized} returns the codec of + * this family for the Lucene release in use. + * * @since 26.02 */ -public class LuceneAcceleratedHNSWBinaryQuantizedCodec extends FilterCodec { +public class LuceneAcceleratedHNSWBinaryQuantizedCodec extends CuVSFilterCodec { - private static final Logger log = - Logger.getLogger(LuceneAcceleratedHNSWBinaryQuantizedCodec.class.getName()); private static final String NAME = "Lucene101AcceleratedHNSWBinaryQuantizedCodec"; - private KnnVectorsFormat format; - - public LuceneAcceleratedHNSWBinaryQuantizedCodec() throws Exception { - this(NAME, LuceneProvider.getCodec("101")); + public LuceneAcceleratedHNSWBinaryQuantizedCodec() { + this(new AcceleratedHNSWParams.Builder().build()); } public LuceneAcceleratedHNSWBinaryQuantizedCodec(String name, Codec delegate) { - super(name, delegate); - initializeFormat(new AcceleratedHNSWParams.Builder().build()); - } - - public LuceneAcceleratedHNSWBinaryQuantizedCodec(AcceleratedHNSWParams acceleratedHNSWParams) - throws Exception { - this(NAME, LuceneProvider.getCodec("101")); - initializeFormat(acceleratedHNSWParams); - } - - private void initializeFormat(AcceleratedHNSWParams acceleratedHNSWParams) { - try { - format = new LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat(acceleratedHNSWParams); - setKnnFormat(format); - } catch (LibraryException ex) { - log.log( - Level.SEVERE, - "Couldn't load native library, possible classloader issue. " + ex.getMessage()); - } - } - - @Override - public KnnVectorsFormat knnVectorsFormat() { - return format; + super( + name, + delegate, + () -> + new LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat( + new AcceleratedHNSWParams.Builder().build())); } - public void setKnnFormat(KnnVectorsFormat format) { - this.format = format; + public LuceneAcceleratedHNSWBinaryQuantizedCodec(AcceleratedHNSWParams acceleratedHNSWParams) { + super( + NAME, + delegate(NAME, () -> LuceneCompat.lucene101Codec()), + () -> new LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat(acceleratedHNSWParams)); } } diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java index c50e3769b5..b9bcff3911 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.java @@ -15,6 +15,9 @@ import org.apache.lucene.codecs.KnnVectorsWriter; import org.apache.lucene.codecs.hnsw.DefaultFlatVectorScorer; import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99FlatVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsWriter; import org.apache.lucene.index.SegmentReadState; import org.apache.lucene.index.SegmentWriteState; import org.apache.lucene.search.TaskExecutor; @@ -28,24 +31,12 @@ public class LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat extends KnnVector private static final Logger log = Logger.getLogger(LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat.class.getName()); - private static final LuceneProvider LUCENE102_PROVIDER; - private static final LuceneProvider LUCENE99_PROVIDER; - private static final FlatVectorsFormat FLAT_VECTORS_FORMAT; + private static final FlatVectorsFormat FLAT_VECTORS_FORMAT = + new Lucene99FlatVectorsFormat(DefaultFlatVectorScorer.INSTANCE); private static final int MAX_DIMENSIONS = 4096; private final AcceleratedHNSWParams acceleratedHNSWParams; - static { - try { - LUCENE99_PROVIDER = LuceneProvider.getInstance("99"); - LUCENE102_PROVIDER = LuceneProvider.getInstance("102"); - FLAT_VECTORS_FORMAT = - LUCENE102_PROVIDER.getLuceneFlatVectorsFormatInstance(DefaultFlatVectorScorer.INSTANCE); - } catch (Exception e) { - throw new ExceptionInInitializerError(e.getMessage()); - } - } - /** * Initializes {@link LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat} with default values. * @@ -71,6 +62,7 @@ public LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat( */ @Override public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException { + LuceneVersionGuard.ensureCompatible(); var flatWriter = FLAT_VECTORS_FORMAT.fieldsWriter(state); if (isSupported()) { log.log( @@ -85,17 +77,13 @@ public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException log.log( Level.WARNING, "GPU based indexing not supported, falling back to using the Lucene99HnswVectorsWriter"); - try { - return LUCENE99_PROVIDER.getLuceneHnswVectorsWriterInstance( - state, - acceleratedHNSWParams.getMaxConn(), - acceleratedHNSWParams.getBeamWidth(), - flatWriter, - acceleratedHNSWParams.getNumMergeWorkers(), - new TaskExecutor(acceleratedHNSWParams.getMergeExec())); - } catch (Exception e) { - throw Utils.handleThrowable(e); - } + return new Lucene99HnswVectorsWriter( + state, + acceleratedHNSWParams.getMaxConn(), + acceleratedHNSWParams.getBeamWidth(), + flatWriter, + acceleratedHNSWParams.getNumMergeWorkers(), + new TaskExecutor(acceleratedHNSWParams.getMergeExec())); } } @@ -104,12 +92,8 @@ public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException */ @Override public KnnVectorsReader fieldsReader(SegmentReadState state) throws IOException { - try { - return LUCENE99_PROVIDER.getLuceneHnswVectorsReaderInstance( - state, FLAT_VECTORS_FORMAT.fieldsReader(state)); - } catch (Exception e) { - throw Utils.handleThrowable(e); - } + LuceneVersionGuard.ensureCompatible(); + return new Lucene99HnswVectorsReader(state, FLAT_VECTORS_FORMAT.fieldsReader(state)); } /** diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsWriter.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsWriter.java index 52e6f1bcaa..4ac331f4b9 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsWriter.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWBinaryQuantizedVectorsWriter.java @@ -15,6 +15,7 @@ import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_INDEX_EXT; import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_META_CODEC_EXT; import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_META_CODEC_NAME; +import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_VERSION; import static com.nvidia.cuvs.lucene.ThreadLocalCuVSResourcesProvider.closeCuVSResourcesInstance; import static com.nvidia.cuvs.lucene.ThreadLocalCuVSResourcesProvider.getCuVSResourcesInstance; import static org.apache.lucene.index.VectorEncoding.FLOAT32; @@ -33,7 +34,6 @@ import org.apache.lucene.codecs.KnnFieldVectorsWriter; import org.apache.lucene.codecs.KnnVectorsWriter; import org.apache.lucene.codecs.hnsw.FlatVectorsWriter; -import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat; import org.apache.lucene.index.DocsWithFieldSet; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.FloatVectorValues; @@ -104,13 +104,13 @@ public LuceneAcceleratedHNSWBinaryQuantizedVectorsWriter( CodecUtil.writeIndexHeader( hnswMeta, HNSW_META_CODEC_NAME, - Lucene99HnswVectorsFormat.VERSION_CURRENT, + HNSW_VERSION, state.segmentInfo.getId(), state.segmentSuffix); CodecUtil.writeIndexHeader( hnswVectorIndex, HNSW_INDEX_CODEC_NAME, - Lucene99HnswVectorsFormat.VERSION_CURRENT, + HNSW_VERSION, state.segmentInfo.getId(), state.segmentSuffix); diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedCodec.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedCodec.java index 0705ed0a55..bd278c4719 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedCodec.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedCodec.java @@ -4,62 +4,38 @@ */ package com.nvidia.cuvs.lucene; -import com.nvidia.cuvs.LibraryException; -import java.util.logging.Level; -import java.util.logging.Logger; import org.apache.lucene.codecs.Codec; -import org.apache.lucene.codecs.FilterCodec; -import org.apache.lucene.codecs.KnnVectorsFormat; /** * CuVS based codec for GPU based vector search * + *

This codec, named {@code Lucene101AcceleratedHNSWScalarQuantizedCodec}, wraps {@code + * Lucene101Codec}, the default codec of Lucene 10.2. {@link CuVSCodecs#acceleratedHNSWScalarQuantized} returns the codec of + * this family for the Lucene release in use. + * * @since 26.02 */ -public class LuceneAcceleratedHNSWScalarQuantizedCodec extends FilterCodec { +public class LuceneAcceleratedHNSWScalarQuantizedCodec extends CuVSFilterCodec { - private static final Logger log = - Logger.getLogger(LuceneAcceleratedHNSWScalarQuantizedCodec.class.getName()); private static final String NAME = "Lucene101AcceleratedHNSWScalarQuantizedCodec"; - private KnnVectorsFormat format; - - public LuceneAcceleratedHNSWScalarQuantizedCodec() throws Exception { - this(NAME, LuceneProvider.getCodec("101")); + public LuceneAcceleratedHNSWScalarQuantizedCodec() { + this(new AcceleratedHNSWParams.Builder().build()); } public LuceneAcceleratedHNSWScalarQuantizedCodec(String name, Codec delegate) { - super(name, delegate); - initializeFormatDefaultValues(); - } - - public LuceneAcceleratedHNSWScalarQuantizedCodec(AcceleratedHNSWParams acceleratedHNSWParams) - throws Exception { - this(NAME, LuceneProvider.getCodec("101")); - initializeFormat(acceleratedHNSWParams); - } - - private void initializeFormatDefaultValues() { - initializeFormat(new AcceleratedHNSWParams.Builder().build()); - } - - private void initializeFormat(AcceleratedHNSWParams acceleratedHNSWParams) { - try { - format = new LuceneAcceleratedHNSWScalarQuantizedVectorsFormat(acceleratedHNSWParams); - setKnnFormat(format); - } catch (LibraryException ex) { - log.log( - Level.SEVERE, - "Couldn't load native library, possible classloader issue. " + ex.getMessage()); - } - } - - @Override - public KnnVectorsFormat knnVectorsFormat() { - return format; + super( + name, + delegate, + () -> + new LuceneAcceleratedHNSWScalarQuantizedVectorsFormat( + new AcceleratedHNSWParams.Builder().build())); } - public void setKnnFormat(KnnVectorsFormat format) { - this.format = format; + public LuceneAcceleratedHNSWScalarQuantizedCodec(AcceleratedHNSWParams acceleratedHNSWParams) { + super( + NAME, + delegate(NAME, () -> LuceneCompat.lucene101Codec()), + () -> new LuceneAcceleratedHNSWScalarQuantizedVectorsFormat(acceleratedHNSWParams)); } } diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java index 8c041882a5..66a757332a 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.java @@ -13,6 +13,8 @@ import org.apache.lucene.codecs.KnnVectorsReader; import org.apache.lucene.codecs.KnnVectorsWriter; import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsReader; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsWriter; import org.apache.lucene.index.SegmentReadState; import org.apache.lucene.index.SegmentWriteState; import org.apache.lucene.search.TaskExecutor; @@ -26,20 +28,11 @@ public class LuceneAcceleratedHNSWScalarQuantizedVectorsFormat extends KnnVector private static final Logger log = Logger.getLogger(LuceneAcceleratedHNSWScalarQuantizedVectorsFormat.class.getName()); - private static final LuceneProvider LUCENE_PROVIDER; - private static final FlatVectorsFormat FLAT_VECTORS_FORMAT; private static final int MAX_DIMENSIONS = 4096; private final AcceleratedHNSWParams acceleratedHNSWParams; - - static { - try { - LUCENE_PROVIDER = LuceneProvider.getInstance("99"); - FLAT_VECTORS_FORMAT = LUCENE_PROVIDER.getLuceneScalarQuantizedVectorsFormatInstance(); - } catch (Exception e) { - throw new ExceptionInInitializerError(e.getMessage()); - } - } + // Created on first use and then reused; see flatVectorsFormat(). + private volatile FlatVectorsFormat flatVectorsFormat; /** * Initializes {@link LuceneAcceleratedHNSWScalarQuantizedVectorsFormat} with default values. @@ -66,7 +59,8 @@ public LuceneAcceleratedHNSWScalarQuantizedVectorsFormat( */ @Override public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException { - var flatWriter = FLAT_VECTORS_FORMAT.fieldsWriter(state); + LuceneVersionGuard.ensureCompatible(); + var flatWriter = flatVectorsFormat().fieldsWriter(state); if (isSupported()) { log.info("cuVS is supported so using the Lucene99AcceleratedHNSWQuantizedVectorsWriter"); return new LuceneAcceleratedHNSWScalarQuantizedVectorsWriter( @@ -76,17 +70,13 @@ public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException // what Lucene99HnswScalarQuantizedVectorsFormat writes, so the reader below reads it. log.warning( "GPU based indexing not supported, falling back to using the Lucene99HnswVectorsWriter"); - try { - return LUCENE_PROVIDER.getLuceneHnswVectorsWriterInstance( - state, - acceleratedHNSWParams.getMaxConn(), - acceleratedHNSWParams.getBeamWidth(), - flatWriter, - acceleratedHNSWParams.getNumMergeWorkers(), - new TaskExecutor(acceleratedHNSWParams.getMergeExec())); - } catch (Exception e) { - throw Utils.handleThrowable(e); - } + return new Lucene99HnswVectorsWriter( + state, + acceleratedHNSWParams.getMaxConn(), + acceleratedHNSWParams.getBeamWidth(), + flatWriter, + acceleratedHNSWParams.getNumMergeWorkers(), + new TaskExecutor(acceleratedHNSWParams.getMergeExec())); } } @@ -95,12 +85,27 @@ public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException */ @Override public KnnVectorsReader fieldsReader(SegmentReadState state) throws IOException { - try { - return LUCENE_PROVIDER.getLuceneHnswVectorsReaderInstance( - state, FLAT_VECTORS_FORMAT.fieldsReader(state)); - } catch (Exception e) { - throw Utils.handleThrowable(e); + LuceneVersionGuard.ensureCompatible(); + return new Lucene99HnswVectorsReader(state, flatVectorsFormat().fieldsReader(state)); + } + + /** + * Returns the format that stores the quantized vectors, creating it on first use. + * + *

Not created when this format is: Lucene's service loader creates every registered format + * eagerly, and on a Lucene release that moved {@code Lucene99ScalarQuantizedVectorsFormat} to its + * backward codecs, creating it there would break every format and codec lookup in the JVM before + * {@link LuceneVersionGuard} could report the mismatch. It is created through {@link + * LuceneCompat}, which is not loaded, and so not linked, until then. + */ + private FlatVectorsFormat flatVectorsFormat() { + FlatVectorsFormat format = flatVectorsFormat; + if (format == null) { + // Flat formats hold no state, so two threads racing here at most create a spare one. + format = LuceneCompat.lucene99ScalarQuantizedFlatFormat(); + flatVectorsFormat = format; } + return format; } /** diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java index fd80b2fc16..ce6fce2bea 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.java @@ -15,6 +15,7 @@ import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_INDEX_EXT; import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_META_CODEC_EXT; import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_META_CODEC_NAME; +import static com.nvidia.cuvs.lucene.Lucene99AcceleratedHNSWVectorsFormat.HNSW_VERSION; import static com.nvidia.cuvs.lucene.ThreadLocalCuVSResourcesProvider.closeCuVSResourcesInstance; import static com.nvidia.cuvs.lucene.ThreadLocalCuVSResourcesProvider.getCuVSResourcesInstance; import static org.apache.lucene.index.VectorEncoding.FLOAT32; @@ -58,8 +59,6 @@ public class LuceneAcceleratedHNSWScalarQuantizedVectorsWriter extends KnnVector private static final long SHALLOW_RAM_BYTES_USED = shallowSizeOfInstance(LuceneAcceleratedHNSWScalarQuantizedVectorsWriter.class); private static final String COMPONENT = "Lucene99AcceleratedHNSWQuantizedVectorsWriter"; - private static final LuceneProvider LUCENE_PROVIDER; - private static final Integer VERSION_CURRENT; private final FlatVectorsWriter flatVectorsWriter; private final List fields = new ArrayList<>(); @@ -71,15 +70,6 @@ public class LuceneAcceleratedHNSWScalarQuantizedVectorsWriter extends KnnVector private String vemFileName; private String vexFileName; - static { - try { - LUCENE_PROVIDER = LuceneProvider.getInstance("99"); - VERSION_CURRENT = LUCENE_PROVIDER.getStaticIntParam("VERSION_CURRENT"); - } catch (Exception e) { - throw new ExceptionInInitializerError(e.getMessage()); - } - } - /** * Initializes {@link LuceneAcceleratedHNSWScalarQuantizedVectorsWriter} * @@ -114,13 +104,13 @@ public LuceneAcceleratedHNSWScalarQuantizedVectorsWriter( CodecUtil.writeIndexHeader( hnswMeta, HNSW_META_CODEC_NAME, - VERSION_CURRENT, + HNSW_VERSION, state.segmentInfo.getId(), state.segmentSuffix); CodecUtil.writeIndexHeader( hnswVectorIndex, HNSW_INDEX_CODEC_NAME, - VERSION_CURRENT, + HNSW_VERSION, state.segmentInfo.getId(), state.segmentSuffix); success = true; diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneVersionGuard.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneVersionGuard.java new file mode 100644 index 0000000000..4388419821 --- /dev/null +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/LuceneVersionGuard.java @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package com.nvidia.cuvs.lucene; + +import java.util.Locale; +import org.apache.lucene.util.Version; + +/** + * Detects a cuvs-lucene artifact running against a Lucene release other than the one it was built + * for. + * + *

Lucene changes its codec APIs between minor releases, and some of those changes do not show + * up as linkage errors: a method whose signature changed simply stops overriding its Lucene + * counterpart, and Lucene silently runs its own implementation instead. Checking the version turns + * that into a clear error. + * + *

The check must only run where cuvs-lucene is actually used, never while Lucene's service + * loader instantiates the registered codecs and formats: an exception there would break every codec + * and format lookup in the JVM, including Lucene's own. + */ +final class LuceneVersionGuard { + + private LuceneVersionGuard() {} + + /** + * Throws unless the Lucene on the classpath is the release this artifact is built for. + * + * @throws IllegalStateException if another Lucene release is on the classpath + */ + static void ensureCompatible() { + String message = mismatchMessage(Version.LATEST); + if (message != null) { + throw new IllegalStateException(message); + } + } + + /** + * Returns why this artifact cannot run on the given Lucene release, or {@code null} if it can. + * + * @param running the Lucene release in use + * @return the error message, or null + */ + static String mismatchMessage(Version running) { + int major = LuceneCompat.LUCENE_MAJOR; + int minor = LuceneCompat.LUCENE_MINOR; + if (running.major == major && running.minor == minor) { + return null; + } + // Only this artifact's own release is known here, so name the one to use without claiming it + // exists: this cuvs-lucene release may not support that Lucene release at all. + return String.format( + Locale.ROOT, + "cuvs-lucene-%d.%d is built for Lucene %d.%d.x, but Lucene %s is in use. Use the" + + " cuvs-lucene-%d.%d artifact of this cuvs-lucene release; if there is none, this" + + " cuvs-lucene release does not support Lucene %d.%d.", + major, + minor, + major, + minor, + running, + running.major, + running.minor, + running.major, + running.minor); + } +} diff --git a/java/cuvs-lucene/src/test/backcompat/GenerateReleasedIndices.java b/java/cuvs-lucene/src/test/backcompat/GenerateReleasedIndices.java new file mode 100644 index 0000000000..9b31255e38 --- /dev/null +++ b/java/cuvs-lucene/src/test/backcompat/GenerateReleasedIndices.java @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Random; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.FilterCodec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.document.StringField; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.NoMergePolicy; +import org.apache.lucene.index.SegmentInfos; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.util.Version; + +/** + * Writes the back-compat indexes of a released cuvs-lucene artifact, for TestBackCompatIndices. + * + *

Not part of the build: generate-released-indices.sh compiles and runs it against the published + * jars of one release. It writes the same documents as TestBackCompatIndices#writeIndex, so that + * the same checks apply to indexes written by released code. + * + *

It writes one index with each cuvs-lucene codec the release registers. Releases that + * registered no codecs (25.12 and earlier) instead get one index per cuvs-lucene vectors format, + * used per field by Lucene's default codec, which is how those releases were used. The GPU search + * codec and format are left out: their on-disk format is documented as experimental. + * + *

Arguments: the output directory and the cuvs-lucene version. + */ +public class GenerateReleasedIndices { + + // Keep in sync with TestBackCompatIndices. + private static final int NUM_DOCS = 200; + private static final int DIMENSIONS = 8; + + public static void main(String[] args) throws Exception { + String cuvsVersion = args[1]; + String lucene = Version.LATEST.major + "." + Version.LATEST.minor; + // See TestBackCompatIndices for the layout. + Path outDir = Path.of(args[0], "releases", cuvsVersion, lucene); + Files.createDirectories(outDir); + List codecs = + Codec.availableCodecs().stream() + .filter(GenerateReleasedIndices::isCuVSCodec) + .sorted() + .toList(); + List formats = + codecs.isEmpty() + ? KnnVectorsFormat.availableKnnVectorsFormats().stream() + .filter(GenerateReleasedIndices::isCuVSFormat) + .sorted() + .toList() + : List.of(); + for (String name : codecs) { + write(Codec.forName(name), outDir, name + ".zip"); + } + for (String name : formats) { + Codec base = Codec.getDefault(); + KnnVectorsFormat format = KnnVectorsFormat.forName(name); + Codec codec = + new FilterCodec(base.getName(), base) { + private final KnnVectorsFormat perField = + new PerFieldKnnVectorsFormat() { + @Override + public KnnVectorsFormat getKnnVectorsFormatForField(String field) { + return format; + } + }; + + @Override + public KnnVectorsFormat knnVectorsFormat() { + return perField; + } + }; + write(codec, outDir, base.getName() + "-" + name + ".zip"); + } + } + + private static boolean isCuVSCodec(String name) { + String cls = Codec.forName(name).getClass().getName(); + return cls.startsWith("com.nvidia.cuvs.") && !cls.contains("GPU"); + } + + private static boolean isCuVSFormat(String name) { + String cls = KnnVectorsFormat.forName(name).getClass().getName(); + return cls.startsWith("com.nvidia.cuvs.") && !cls.contains("GPU"); + } + + private static void write(Codec codec, Path outDir, String fileName) throws Exception { + Path indexDir = Files.createTempDirectory("backcompat"); + writeIndex(indexDir, codec); + zip(indexDir, outDir.resolve(fileName)); + System.out.println("wrote " + fileName); + } + + private static void writeIndex(Path indexDir, Codec codec) throws Exception { + Random random = new Random(42); + IndexWriterConfig config = + new IndexWriterConfig() + .setCodec(codec) + .setUseCompoundFile(false) + .setMergePolicy(NoMergePolicy.INSTANCE); + try (Directory dir = FSDirectory.open(indexDir); + IndexWriter writer = new IndexWriter(dir, config)) { + for (int i = 0; i < NUM_DOCS; i++) { + float[] vector = new float[DIMENSIONS]; + for (int d = 0; d < DIMENSIONS; d++) { + vector[d] = random.nextFloat(); + } + Document doc = new Document(); + doc.add(new StringField("id", Integer.toString(i), Field.Store.YES)); + doc.add(new KnnFloatVectorField("vector", vector, VectorSimilarityFunction.EUCLIDEAN)); + writer.addDocument(doc); + } + writer.commit(); + } + } + + private static void zip(Path indexDir, Path zip) throws Exception { + List files; + try (Directory dir = FSDirectory.open(indexDir)) { + files = SegmentInfos.readLatestCommit(dir).files(true).stream().sorted().toList(); + } + try (OutputStream out = Files.newOutputStream(zip); + ZipOutputStream zipOut = new ZipOutputStream(out)) { + for (String file : files) { + ZipEntry entry = new ZipEntry(file); + entry.setTime(0); + zipOut.putNextEntry(entry); + try (InputStream in = Files.newInputStream(indexDir.resolve(file))) { + in.transferTo(zipOut); + } + zipOut.closeEntry(); + } + } + } +} diff --git a/java/cuvs-lucene/src/test/backcompat/generate-released-indices.sh b/java/cuvs-lucene/src/test/backcompat/generate-released-indices.sh new file mode 100755 index 0000000000..08e3deff33 --- /dev/null +++ b/java/cuvs-lucene/src/test/backcompat/generate-released-indices.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Writes the back-compat indexes of a released cuvs-lucene version into +# src/test/resources/backcompat/releases//, +# using the jars published to Maven Central. Released cuvs-lucene versions up to 26.10 were a single +# com.nvidia.cuvs.lucene:cuvs-lucene artifact built for one Lucene release. +# +# Usage: generate-released-indices.sh +# Needs a GPU, a JDK 22+ and the native libcuvs libraries on LD_LIBRARY_PATH. + +set -euo pipefail + +VERSION="${1:?Usage: $0 }" +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OUT="${HERE}/../resources/backcompat" +CENTRAL="https://repo.maven.apache.org/maven2" +WORK="$(mktemp -d)" +trap 'rm -rf "${WORK}"' EXIT + +# Runs in a command substitution, where set -e does not apply, so failures are checked explicitly. +fetch() { # group/path artifact version + if [ -z "$3" ]; then + echo "No version found for $2" >&2 + return 1 + fi + curl -sfL -o "${WORK}/$2-$3.jar" "${CENTRAL}/$1/$2/$3/$2-$3.jar" || { + echo "Could not download ${CENTRAL}/$1/$2/$3/$2-$3.jar" >&2 + return 1 + } + echo "${WORK}/$2-$3.jar" +} + +POM="$(curl -sfL "${CENTRAL}/com/nvidia/cuvs/lucene/cuvs-lucene/${VERSION}/cuvs-lucene-${VERSION}.pom")" +dependency_version() { # artifactId + # Prints nothing if there is no such dependency, which fetch then reports. + echo "${POM}" | grep -A2 "$1" | grep -o '[^<]*' | head -1 | sed 's///' || true +} +LUCENE_VERSION="$(dependency_version lucene-core)" +CUVS_JAVA_VERSION="$(dependency_version cuvs-java)" + +CP="$(fetch com/nvidia/cuvs/lucene cuvs-lucene "${VERSION}")" +CP="${CP}:$(fetch com/nvidia/cuvs cuvs-java "${CUVS_JAVA_VERSION}")" +CP="${CP}:$(fetch org/apache/lucene lucene-core "${LUCENE_VERSION}")" +CP="${CP}:$(fetch org/apache/lucene lucene-backward-codecs "${LUCENE_VERSION}")" + +javac -cp "${CP}" -d "${WORK}" "${HERE}/GenerateReleasedIndices.java" +java --enable-native-access=ALL-UNNAMED -cp "${CP}:${WORK}" GenerateReleasedIndices "${OUT}" "${VERSION}" diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/BaseCuVSKnnVectorsFormatTestCase.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/BaseCuVSKnnVectorsFormatTestCase.java new file mode 100644 index 0000000000..7186ad8a85 --- /dev/null +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/BaseCuVSKnnVectorsFormatTestCase.java @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 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.isSupported; + +import org.apache.lucene.index.VectorEncoding; +import org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase; +import org.junit.BeforeClass; +import org.junit.Ignore; + +/** + * Runs Lucene's vectors-format test suite against a cuvs-lucene format, adapted to what all of them + * have in common: they need cuVS, and they index float vectors only. + * + *

It also absorbs the differences between the versions of Lucene's test suite that cuvs-lucene + * supports, so that the subclasses compile against all of them. + */ +public abstract class BaseCuVSKnnVectorsFormatTestCase extends BaseKnnVectorsFormatTestCase { + + @BeforeClass + public static void assumeCuVSSupported() { + assumeTrue("cuVS is not supported", isSupported()); + } + + @Override + protected VectorEncoding randomVectorEncoding() { + return VectorEncoding.FLOAT32; + } + + // The tests below index byte vectors, which the cuvs-lucene formats do not support. + + @Ignore + @Override + public void testByteVectorScorerIteration() {} + + @Ignore + @Override + public void testEmptyByteVectorData() {} + + @Ignore + @Override + public void testMergingWithDifferentByteKnnFields() {} + + @Ignore + @Override + public void testMismatchedFields() {} + + @Ignore + @Override + public void testRandomBytes() {} + + @Ignore + @Override + public void testSortedIndexBytes() {} +} diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestAcceleratedHNSWOddGraphDegree.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestAcceleratedHNSWOddGraphDegree.java index 89012b1930..cfab27e91e 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestAcceleratedHNSWOddGraphDegree.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestAcceleratedHNSWOddGraphDegree.java @@ -70,7 +70,7 @@ public void testOddGraphDegreeIndexesAndSearches() throws Exception { .withGraphDegree(ODD_GRAPH_DEGREE) .build(); - Codec codec = new Lucene101AcceleratedHNSWCodec(params); + Codec codec = CuVSCodecs.acceleratedHNSW(params); IndexWriterConfig config = new IndexWriterConfig().setCodec(codec).setUseCompoundFile(false); int numDocs = 1000; diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompat.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompat.java index d638180b06..9f09fc94ac 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompat.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompat.java @@ -5,37 +5,124 @@ package com.nvidia.cuvs.lucene; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import com.nvidia.cuvs.LibraryException; +import java.util.List; import org.apache.lucene.codecs.Codec; -import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.util.Version; import org.junit.Test; /** - * Tests the backward compatibility mechanism. + * Tests that the codecs from {@link CuVSCodecs} are registered, and that a cuvs-lucene artifact + * on another Lucene release than it is built for fails only when its codecs are used. * * @since 25.12 */ public class TestBackCompat { + private static List codecs() { + return List.of( + CuVSCodecs.acceleratedHNSW(), + CuVSCodecs.acceleratedHNSWScalarQuantized(), + CuVSCodecs.acceleratedHNSWBinaryQuantized(), + CuVSCodecs.gpuSearch()); + } + + @Test + public void testCodecsAreRegistered() { + for (Codec codec : codecs()) { + Codec registered = Codec.forName(codec.getName()); + assertSame(codec.getName(), codec.getClass(), registered.getClass()); + } + } + + @Test + public void testLuceneFormatsStillResolve() { + // Listing a Lucene format in this module's services file used to break every lookup once a + // later Lucene release moved that format. + String name = "Lucene99HnswVectorsFormat"; + assertEquals(name, KnnVectorsFormat.forName(name).getName()); + } + + @Test + public void testMismatchMessage() { + assertNull(LuceneVersionGuard.mismatchMessage(Version.LATEST)); + String message = LuceneVersionGuard.mismatchMessage(Version.fromBits(9, 12, 1)); + assertNotNull(message); + assertTrue(message, message.contains("Lucene 9.12.1 is in use")); + assertTrue(message, message.contains("cuvs-lucene-9.12 artifact")); + assertTrue( + message, + message.contains( + "cuvs-lucene-" + LuceneCompat.LUCENE_MAJOR + "." + LuceneCompat.LUCENE_MINOR)); + } + + @Test + public void testUnavailableDelegate() { + // What a codec gets when its Lucene delegate does not link, e.g. on another Lucene release: + // creating the codec must not throw, since Lucene's service loader does it for every codec. + // The problem is logged once per JVM; mark it as logged, so that this test neither prints a + // misleading SEVERE message nor uses up the one log a later test in this JVM might expect. + boolean logged = CuVSFilterCodec.setUnavailableLoggedForTesting(true); + try { + Codec codec = + CuVSFilterCodec.delegate( + "TestCodec", + () -> { + throw new NoClassDefFoundError("org/apache/lucene/codecs/Missing"); + }); + assertEquals("TestCodec", codec.getName()); + IllegalStateException e = assertThrows(IllegalStateException.class, codec::postingsFormat); + assertTrue(e.getMessage(), e.getMessage().contains("org/apache/lucene/codecs/Missing")); + assertThrows(IllegalStateException.class, codec::knnVectorsFormat); + } finally { + CuVSFilterCodec.setUnavailableLoggedForTesting(logged); + } + } + @Test - public void testFallback() throws Exception { - // Lucene99Codec exists in the org.apache.lucene.backward_codecs.lucene99 - Codec c = LuceneProvider.getCodec("99"); - assertEquals(c.getName(), "Lucene99"); + public void testUnavailableFormat() { + // What a codec gets when its vectors format does not link, e.g. because a later Lucene release + // moved a class it uses, or cannot reach the cuVS library: + // creating the codec must not throw either, for the same reason. + checkUnavailableFormat(new NoClassDefFoundError("org/apache/lucene/codecs/Missing")); + checkUnavailableFormat(new LibraryException("cuVS library not available")); } - @Test(expected = ClassNotFoundException.class) - public void testNonexistentCodec() throws Exception { - LuceneProvider.getCodec("0"); + private static void checkUnavailableFormat(Throwable cause) { + boolean logged = CuVSFilterCodec.setUnavailableLoggedForTesting(true); + try { + CuVSFilterCodec codec = + new CuVSFilterCodec( + "TestCodec", + Codec.getDefault(), + () -> { + if (cause instanceof Error error) { + throw error; + } + throw (RuntimeException) cause; + }) {}; + KnnVectorsFormat format = codec.knnVectorsFormat(); + IllegalStateException e = + assertThrows(IllegalStateException.class, () -> format.fieldsWriter(null)); + assertTrue(e.getMessage(), e.getMessage().contains(cause.getMessage())); + assertThrows(IllegalStateException.class, () -> format.fieldsReader(null)); + // Everything but vectors still works. + assertNotNull(codec.postingsFormat()); + } finally { + CuVSFilterCodec.setUnavailableLoggedForTesting(logged); + } } @Test - public void testExistingComponents() throws Exception { - LuceneProvider provider = LuceneProvider.getInstance("99"); - assertTrue(provider.getLuceneFlatVectorsFormatInstance(null) instanceof FlatVectorsFormat); - assertEquals(provider.getStaticIntParam("VERSION_CURRENT"), 0); - assertNotEquals(provider.getSimilarityFunctions().size(), 0); + public void testLuceneVersionMatches() { + // Loading the compatibility layer checks that this is the Lucene release it was built for. + CuVSCodecs.checkLuceneVersion(); } } diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompatIndices.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompatIndices.java new file mode 100644 index 0000000000..4d8fbb148b --- /dev/null +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompatIndices.java @@ -0,0 +1,302 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 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.isSupported; +import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.document.StringField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.KnnVectorValues; +import org.apache.lucene.index.LeafReader; +import org.apache.lucene.index.NoMergePolicy; +import org.apache.lucene.index.SegmentCommitInfo; +import org.apache.lucene.index.SegmentInfos; +import org.apache.lucene.index.StoredFields; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.KnnFloatVectorQuery; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.tests.util.LuceneTestCase; +import org.apache.lucene.tests.util.LuceneTestCase.SuppressSysoutChecks; +import org.apache.lucene.util.Version; + +/** + * Opens and upgrades indexes written with the cuvs-lucene codecs on earlier Lucene releases and by + * earlier cuvs-lucene releases, to make sure an application can still use its indexes after it + * moves to a later Lucene release, cuvs-lucene release, or both. + * + *

The indexes are in {@code src/test/resources/backcompat}, one zip each, at {@code + * releases///[-].zip} + * if the jars of a released cuvs-lucene version wrote them, or at {@code snapshots//.zip} if a module of this repository did, for a Lucene release that no + * released cuvs-lucene version supports yet. The path records the Lucene + * release they were written on, the codec recorded in their segments, and the cuvs-lucene vectors + * format used per field if the codec is Lucene's own. Each module reads the ones written on its own + * Lucene release and all earlier ones. See the {@code README.md} next to them for how to add more. + * + *

Reading and upgrading need no GPU, so those tests also run where cuVS is not available. + */ +@SuppressSysoutChecks(bugUrl = "") +public class TestBackCompatIndices extends LuceneTestCase { + + private static final String CREATE_PROPERTY = "tests.cuvs.createBackCompatIndices"; + private static final String RESOURCE_DIR = "/backcompat"; + // The path of an index below RESOURCE_DIR; see the class javadoc. + private static final Pattern INDEX_PATH = + Pattern.compile("(?:snapshots|releases/[\\d.]+)/(\\d+)\\.(\\d+)/(\\w+)(?:-(\\w+))?\\.zip"); + private static final String ID_FIELD = "id"; + private static final String VECTOR_FIELD = "vector"; + private static final int NUM_DOCS = 200; + private static final int DIMENSIONS = 8; + private static final int TOP_K = 10; + private static final int UPGRADE_DOCS = 50; + + /** The codecs that write the indexes; the GPU search codec's format makes no such promise. */ + private static List codecsToTest() { + return List.of( + CuVSCodecs.acceleratedHNSW(), + CuVSCodecs.acceleratedHNSWScalarQuantized(), + CuVSCodecs.acceleratedHNSWBinaryQuantized()); + } + + public void testReadIndices() throws Exception { + for (Path zip : indicesReadableByThisRelease()) { + Matcher matcher = indexPath(zip); + Path indexDir = createTempDir(zip.getFileName().toString()); + unzip(zip, indexDir); + try (Directory dir = newFSDirectory(indexDir)) { + checkIndex(dir, matcher.group(3), matcher.group(4), NUM_DOCS); + } catch (AssertionError | Exception e) { + throw new AssertionError("Failed to read " + matcher.group(), e); + } + } + } + + /** + * Opens each index for writing with the codec that writes on this Lucene release, adds documents + * and merges everything into one segment, which rewrites the old segments with the current codec, + * as an application does after moving to a later release. + */ + public void testUpgradeIndices() throws Exception { + for (Path zip : indicesReadableByThisRelease()) { + Matcher matcher = indexPath(zip); + Codec codec = currentCodecFor(matcher.group(3)); + Path indexDir = createTempDir(zip.getFileName().toString()); + unzip(zip, indexDir); + try (Directory dir = newFSDirectory(indexDir)) { + IndexWriterConfig config = + new IndexWriterConfig() + .setCodec(codec) + .setOpenMode(IndexWriterConfig.OpenMode.APPEND) + .setUseCompoundFile(false); + try (IndexWriter writer = new IndexWriter(dir, config)) { + Random random = new Random(7); + for (int i = NUM_DOCS; i < NUM_DOCS + UPGRADE_DOCS; i++) { + writer.addDocument(document(i, randomVector(random))); + } + writer.forceMerge(1); + } + checkIndex(dir, codec.getName(), null, NUM_DOCS + UPGRADE_DOCS); + } catch (AssertionError | Exception e) { + throw new AssertionError("Failed to upgrade " + matcher.group(), e); + } + } + } + + public void testCreateIndices() throws Exception { + // Written on the GPU, like the indexes of the applications this test stands for. + assumeTrue("cuVS not supported", isSupported()); + String target = System.getProperty(CREATE_PROPERTY); + assumeTrue("set -D" + CREATE_PROPERTY + " to write the indexes", target != null); + Path targetDir = + Path.of(target, "snapshots", Version.LATEST.major + "." + Version.LATEST.minor); + Files.createDirectories(targetDir); + for (Codec codec : codecsToTest()) { + Path indexDir = createTempDir(codec.getName()); + writeIndex(indexDir, codec); + zip(indexDir, targetDir.resolve(codec.getName() + ".zip")); + } + } + + /** Returns the codec that writes on this Lucene release in the family of the given codec. */ + private static Codec currentCodecFor(String codecName) { + if (codecName.contains("ScalarQuantized")) { + return CuVSCodecs.acceleratedHNSWScalarQuantized(); + } else if (codecName.contains("BinaryQuantized")) { + return CuVSCodecs.acceleratedHNSWBinaryQuantized(); + } + // The accelerated HNSW codecs, and Lucene's own codecs with its vectors format used per field. + return CuVSCodecs.acceleratedHNSW(); + } + + private static Path resourceDir() throws URISyntaxException { + URL url = TestBackCompatIndices.class.getResource(RESOURCE_DIR); + assertNotNull(RESOURCE_DIR + " not found on the test classpath", url); + return Path.of(url.toURI()); + } + + /** Matches the path of an index zip below {@link #RESOURCE_DIR} against {@link #INDEX_PATH}. */ + private static Matcher indexPath(Path zip) throws URISyntaxException { + String path = resourceDir().relativize(zip).toString().replace('\\', '/'); + Matcher matcher = INDEX_PATH.matcher(path); + assertTrue("unexpected back-compat index path: " + path, matcher.matches()); + return matcher; + } + + private static List indicesReadableByThisRelease() throws IOException, URISyntaxException { + List zips = new ArrayList<>(); + try (Stream files = Files.walk(resourceDir())) { + for (Path f : files.filter(f -> f.toString().endsWith(".zip")).sorted().toList()) { + // Every zip has to be where the tests expect it, or a misplaced one would go unread. + Matcher matcher = indexPath(f); + int major = Integer.parseInt(matcher.group(1)); + int minor = Integer.parseInt(matcher.group(2)); + if (major < Version.LATEST.major + || (major == Version.LATEST.major && minor <= Version.LATEST.minor)) { + zips.add(f); + } + } + } + assertFalse("no back-compat indexes found under " + RESOURCE_DIR, zips.isEmpty()); + return zips; + } + + private static void writeIndex(Path indexDir, Codec codec) throws IOException { + Random random = new Random(42); + IndexWriterConfig config = + new IndexWriterConfig() + .setCodec(codec) + .setUseCompoundFile(false) + .setMergePolicy(NoMergePolicy.INSTANCE); + try (Directory dir = FSDirectory.open(indexDir); + IndexWriter writer = new IndexWriter(dir, config)) { + for (int i = 0; i < NUM_DOCS; i++) { + writer.addDocument(document(i, randomVector(random))); + } + writer.commit(); + } + } + + private static float[] randomVector(Random random) { + float[] vector = new float[DIMENSIONS]; + for (int d = 0; d < DIMENSIONS; d++) { + vector[d] = random.nextFloat(); + } + return vector; + } + + private static Document document(int id, float[] vector) { + Document doc = new Document(); + doc.add(new StringField(ID_FIELD, Integer.toString(id), Field.Store.YES)); + doc.add(new KnnFloatVectorField(VECTOR_FIELD, vector, EUCLIDEAN)); + return doc; + } + + /** + * Checks that every segment was written by the expected codec (and, for Lucene's own codecs, that + * the vector field uses the expected cuvs-lucene format), and that searching for each indexed + * vector finds its own document. + * + * @param perFieldFormat the vectors format name recorded for the vector field, or null to skip + */ + private static void checkIndex( + Directory dir, String codecName, String perFieldFormat, int numDocs) throws IOException { + for (SegmentCommitInfo info : SegmentInfos.readLatestCommit(dir)) { + assertEquals(codecName, info.info.getCodec().getName()); + } + try (DirectoryReader reader = DirectoryReader.open(dir)) { + assertEquals(numDocs, reader.numDocs()); + if (perFieldFormat != null) { + for (var context : reader.leaves()) { + FieldInfo field = context.reader().getFieldInfos().fieldInfo(VECTOR_FIELD); + assertEquals( + perFieldFormat, field.getAttribute(PerFieldKnnVectorsFormat.PER_FIELD_FORMAT_KEY)); + } + } + IndexSearcher searcher = new IndexSearcher(reader); + StoredFields allStoredFields = searcher.storedFields(); + int found = 0; + for (var context : reader.leaves()) { + LeafReader leaf = context.reader(); + FloatVectorValues vectors = leaf.getFloatVectorValues(VECTOR_FIELD); + assertEquals(DIMENSIONS, vectors.dimension()); + StoredFields storedFields = leaf.storedFields(); + KnnVectorValues.DocIndexIterator it = vectors.iterator(); + for (int doc = it.nextDoc(); doc != NO_MORE_DOCS; doc = it.nextDoc()) { + String id = storedFields.document(doc).get(ID_FIELD); + float[] query = vectors.vectorValue(it.index()).clone(); + ScoreDoc[] hits = + searcher.search(new KnnFloatVectorQuery(VECTOR_FIELD, query, TOP_K), TOP_K).scoreDocs; + for (ScoreDoc hit : hits) { + if (id.equals(allStoredFields.document(hit.doc).get(ID_FIELD))) { + found++; + break; + } + } + } + } + // Quantized codecs may rank a few near-duplicates ahead of the exact match. + assertTrue( + "only " + found + " of " + numDocs + " vectors found themselves", found >= numDocs * 0.9); + } + } + + private static void unzip(Path zip, Path targetDir) throws IOException { + try (ZipInputStream in = new ZipInputStream(Files.newInputStream(zip))) { + for (ZipEntry entry = in.getNextEntry(); entry != null; entry = in.getNextEntry()) { + Files.copy(in, targetDir.resolve(entry.getName())); + } + } + } + + /** Zips the files of the index's latest commit, leaving out anything else in the directory. */ + private static void zip(Path indexDir, Path zip) throws IOException { + List files; + try (Directory dir = FSDirectory.open(indexDir)) { + files = SegmentInfos.readLatestCommit(dir).files(true).stream().sorted().toList(); + } + try (OutputStream out = Files.newOutputStream(zip); + ZipOutputStream zipOut = new ZipOutputStream(out)) { + for (String file : files) { + ZipEntry entry = new ZipEntry(file); + // A fixed time keeps regenerated zips identical when the index is. + entry.setTime(0); + zipOut.putNextEntry(entry); + try (InputStream in = Files.newInputStream(indexDir.resolve(file))) { + in.transferTo(zipOut); + } + zipOut.closeEntry(); + } + } + } +} diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCagraToHnswSerializationAndSearch.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCagraToHnswSerializationAndSearch.java index 40fc3da663..16eb37ed8b 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCagraToHnswSerializationAndSearch.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCagraToHnswSerializationAndSearch.java @@ -47,7 +47,7 @@ public void beforeTest() throws Exception { @Test public void testCagraToHnswSerializationAndSearch() throws Exception { AcceleratedHNSWParams params = new AcceleratedHNSWParams.Builder().build(); - Codec codec = new Lucene101AcceleratedHNSWCodec(params); + Codec codec = CuVSCodecs.acceleratedHNSW(params); IndexWriterConfig config = new IndexWriterConfig().setCodec(codec).setUseCompoundFile(false); final int COMMIT_FREQ = 2000; @@ -119,7 +119,7 @@ public void testSingleVectorIndex() throws Exception { // Test single vector index support with dummy HNSW graph // TODO: This test can be removed once https://github.com/rapidsai/cuvs/pull/1256 is merged // and CAGRA natively supports single vector indexes - Codec codec = new Lucene101AcceleratedHNSWCodec(); + Codec codec = CuVSCodecs.acceleratedHNSW(); final String ID_FIELD = "id"; final String VECTOR_FIELD = "vector_field"; diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCagraToHnswSerializationAndSearchWithFallbackWriter.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCagraToHnswSerializationAndSearchWithFallbackWriter.java index 20ae613e55..f875631e02 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCagraToHnswSerializationAndSearchWithFallbackWriter.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCagraToHnswSerializationAndSearchWithFallbackWriter.java @@ -58,7 +58,7 @@ public void testCagraToHnswSerializationAndSearchWithFallbackWriter() throws Exc .withMaxConn(16) .withBeamWidth(100) .build(); - Codec codec = new Lucene101AcceleratedHNSWCodec(params); + Codec codec = CuVSCodecs.acceleratedHNSW(params); IndexWriterConfig config = new IndexWriterConfig().setCodec(codec).setUseCompoundFile(false); diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCuVSVectorsFormat.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCuVSVectorsFormat.java index a09b306152..9cb4ed6153 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCuVSVectorsFormat.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestCuVSVectorsFormat.java @@ -5,7 +5,6 @@ package com.nvidia.cuvs.lucene; import static com.nvidia.cuvs.lucene.TestUtils.assertVectorsKeepTheirDocuments; -import static com.nvidia.cuvs.lucene.ThreadLocalCuVSResourcesProvider.isSupported; import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; import java.util.List; @@ -19,21 +18,12 @@ import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LogDocMergePolicy; -import org.apache.lucene.index.VectorEncoding; import org.apache.lucene.store.Directory; -import org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase; import org.apache.lucene.tests.util.LuceneTestCase.SuppressSysoutChecks; import org.apache.lucene.tests.util.TestUtil; -import org.junit.BeforeClass; -import org.junit.Ignore; @SuppressSysoutChecks(bugUrl = "") -public class TestCuVSVectorsFormat extends BaseKnnVectorsFormatTestCase { - - @BeforeClass - public static void beforeClass() { - assumeTrue("cuVS is not supported", isSupported()); - } +public class TestCuVSVectorsFormat extends BaseCuVSKnnVectorsFormatTestCase { @Override protected Codec getCodec() { @@ -107,40 +97,4 @@ public void testTwoVectorFieldsPerDoc() throws Exception { } } } - - @Override - // Overriding this method from superclass for the tests to only use float vector encoding - protected VectorEncoding randomVectorEncoding() { - return VectorEncoding.FLOAT32; - } - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testByteVectorScorerIteration() {} - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testEmptyByteVectorData() {} - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testMergingWithDifferentByteKnnFields() {} - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testMismatchedFields() {} - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testRandomBytes() {} - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testSortedIndexBytes() {} } diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestGPUSelectiveFilterFallback.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestGPUSelectiveFilterFallback.java index a031a10669..9e705f37b1 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestGPUSelectiveFilterFallback.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestGPUSelectiveFilterFallback.java @@ -82,7 +82,7 @@ public static void beforeClass() throws Exception { // Disable merges so the commits below yield one GPU segment each -> multi-partition search. IndexWriterConfig config = new IndexWriterConfig() - .setCodec(new CuVS2510GPUSearchCodec()) + .setCodec(CuVSCodecs.gpuSearch()) .setMergePolicy(NoMergePolicy.INSTANCE); try (IndexWriter writer = new IndexWriter(directory, config)) { for (int i = 0; i < DATASET_SIZE; i++) { @@ -199,7 +199,7 @@ public void singleSegmentSelectiveFilterIsAnsweredExactly() throws Exception { try (Directory dir = newDirectory(new ByteBuffersDirectory())) { IndexWriterConfig config = new IndexWriterConfig() - .setCodec(new CuVS2510GPUSearchCodec()) + .setCodec(CuVSCodecs.gpuSearch()) .setMergePolicy(NoMergePolicy.INSTANCE); try (IndexWriter writer = new IndexWriter(dir, config)) { for (int i = 0; i < DOCS_PER_SEGMENT; i++) { diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestLucene99AcceleratedHNSWVectorsFormat.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestLucene99AcceleratedHNSWVectorsFormat.java index 8f56c7f45e..bb5d5b2299 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestLucene99AcceleratedHNSWVectorsFormat.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestLucene99AcceleratedHNSWVectorsFormat.java @@ -5,7 +5,6 @@ package com.nvidia.cuvs.lucene; import static com.nvidia.cuvs.lucene.TestUtils.assertVectorsKeepTheirDocuments; -import static com.nvidia.cuvs.lucene.ThreadLocalCuVSResourcesProvider.isSupported; import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; import java.util.List; @@ -19,21 +18,12 @@ import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LogDocMergePolicy; -import org.apache.lucene.index.VectorEncoding; import org.apache.lucene.store.Directory; -import org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase; import org.apache.lucene.tests.util.LuceneTestCase.SuppressSysoutChecks; import org.apache.lucene.tests.util.TestUtil; -import org.junit.BeforeClass; -import org.junit.Ignore; @SuppressSysoutChecks(bugUrl = "") -public class TestLucene99AcceleratedHNSWVectorsFormat extends BaseKnnVectorsFormatTestCase { - - @BeforeClass - public static void beforeClass() { - assumeTrue("cuVS is not supported", isSupported()); - } +public class TestLucene99AcceleratedHNSWVectorsFormat extends BaseCuVSKnnVectorsFormatTestCase { @Override protected Codec getCodec() { @@ -107,40 +97,4 @@ public void testTwoVectorFieldsPerDoc() throws Exception { } } } - - @Override - // Overriding this method from superclass for the tests to only use float vector encoding - protected VectorEncoding randomVectorEncoding() { - return VectorEncoding.FLOAT32; - } - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testByteVectorScorerIteration() {} - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testEmptyByteVectorData() {} - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testMergingWithDifferentByteKnnFields() {} - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testMismatchedFields() {} - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testRandomBytes() {} - - @Ignore - @Override - // Ignoring this test from superclass as we do not support byte vectors - public void testSortedIndexBytes() {} } diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestMultiSegmentGPUFilterConcurrency.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestMultiSegmentGPUFilterConcurrency.java index 7c61e50817..14eb82c41e 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestMultiSegmentGPUFilterConcurrency.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestMultiSegmentGPUFilterConcurrency.java @@ -83,7 +83,7 @@ public static void beforeClass() throws Exception { assumeTrue("cuVS not supported: " + unsupported.getMessage(), false); } assumeTrue("cuVS not supported", isSupported()); - codec = new CuVS2510GPUSearchCodec(new GPUSearchParams.Builder().build(), TEST_CACHE_CONFIG); + codec = CuVSCodecs.gpuSearch(new GPUSearchParams.Builder().build(), TEST_CACHE_CONFIG); int datasetSize = 2000; int dimensions = 128; @@ -114,8 +114,7 @@ public static void beforeClass() throws Exception { // DirectoryReader resolves the stored codec name through SPI. Give that singleton the same // deliberately small cache budget while it constructs this test's segment readers, then restore // its previous format so the test does not alter later reader construction in this JVM. - CuVS2510GPUSearchCodec spiCodec = - (CuVS2510GPUSearchCodec) Codec.forName("CuVS2510GPUSearchCodec"); + CuVSFilterCodec spiCodec = (CuVSFilterCodec) Codec.forName(codec.getName()); KnnVectorsFormat previousFormat = spiCodec.knnVectorsFormat(); spiCodec.setKnnFormat( new CuVS2510GPUVectorsFormat(new GPUSearchParams.Builder().build(), TEST_CACHE_CONFIG)); diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestPerSegmentGPUFilterSearch.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestPerSegmentGPUFilterSearch.java index df2ba21189..cf91052157 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestPerSegmentGPUFilterSearch.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestPerSegmentGPUFilterSearch.java @@ -53,7 +53,7 @@ public void testSelectiveFilterDoesNotLeakTrailingOrdinals() throws Exception { try (Directory directory = newDirectory(new ByteBuffersDirectory())) { float[][] dataset = generateDataset(random(), datasetSize, dimensions); - IndexWriterConfig config = new IndexWriterConfig().setCodec(new CuVS2510GPUSearchCodec()); + IndexWriterConfig config = new IndexWriterConfig().setCodec(CuVSCodecs.gpuSearch()); try (IndexWriter writer = new IndexWriter(directory, config)) { for (int i = 0; i < datasetSize; i++) { Document doc = new Document(); diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedFallbackWriters.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedFallbackWriters.java index 2d02b064ce..bec77dcef7 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedFallbackWriters.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedFallbackWriters.java @@ -65,9 +65,9 @@ public void testFallbackWriters() throws Exception { new AcceleratedHNSWParams.Builder().withMaxConn(16).withBeamWidth(100).build(); for (Codec codec : List.of( - new Lucene101AcceleratedHNSWCodec(params), - new LuceneAcceleratedHNSWScalarQuantizedCodec(params), - new LuceneAcceleratedHNSWBinaryQuantizedCodec(params))) { + CuVSCodecs.acceleratedHNSW(params), + CuVSCodecs.acceleratedHNSWScalarQuantized(params), + CuVSCodecs.acceleratedHNSWBinaryQuantized(params))) { checkWritesAndReads(codec); } } diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedVectorsFormats.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedVectorsFormats.java index 67cae2b4e9..e490ef58a6 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedVectorsFormats.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedVectorsFormats.java @@ -5,7 +5,6 @@ package com.nvidia.cuvs.lucene; import static com.nvidia.cuvs.lucene.TestUtils.assertVectorsKeepTheirDocuments; -import static com.nvidia.cuvs.lucene.ThreadLocalCuVSResourcesProvider.isSupported; import static org.apache.lucene.index.VectorSimilarityFunction.COSINE; import static org.apache.lucene.index.VectorSimilarityFunction.EUCLIDEAN; @@ -27,17 +26,13 @@ import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LogDocMergePolicy; -import org.apache.lucene.index.VectorEncoding; import org.apache.lucene.store.ByteBuffersDirectory; import org.apache.lucene.store.Directory; -import org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase; import org.apache.lucene.tests.util.LuceneTestCase.SuppressSysoutChecks; import org.apache.lucene.tests.util.TestUtil; -import org.junit.BeforeClass; -import org.junit.Ignore; @SuppressSysoutChecks(bugUrl = "") -public class TestQuantizedVectorsFormats extends BaseKnnVectorsFormatTestCase { +public class TestQuantizedVectorsFormats extends BaseCuVSKnnVectorsFormatTestCase { private static final Logger log = Logger.getLogger(TestQuantizedVectorsFormats.class.getName()); @@ -52,15 +47,13 @@ public static List parameters() { return Arrays.asList( new Object[][] { {new LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat()}, - {new LuceneAcceleratedHNSWScalarQuantizedVectorsFormat()} + { + CuVSCodecs.acceleratedHNSWScalarQuantizedFormat( + new AcceleratedHNSWParams.Builder().build()) + } }); } - @BeforeClass - public static void beforeClass() { - assumeTrue("cuVS is not supported so skipping these tests", isSupported()); - } - @Override protected Codec getCodec() { log.log(Level.FINE, "Running tests for: " + knnVectorsFormat.getName()); @@ -168,33 +161,4 @@ public void testCosineSimilarity() throws Exception { } } } - - @Override - protected VectorEncoding randomVectorEncoding() { - return VectorEncoding.FLOAT32; - } - - @Ignore - @Override - public void testByteVectorScorerIteration() {} - - @Ignore - @Override - public void testEmptyByteVectorData() {} - - @Ignore - @Override - public void testMergingWithDifferentByteKnnFields() {} - - @Ignore - @Override - public void testMismatchedFields() {} - - @Ignore - @Override - public void testRandomBytes() {} - - @Ignore - @Override - public void testSortedIndexBytes() {} } diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestSegmentMaxConnConsistency.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestSegmentMaxConnConsistency.java index d6edccca94..90ebe6a392 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestSegmentMaxConnConsistency.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestSegmentMaxConnConsistency.java @@ -75,7 +75,7 @@ public void testRecordedMCoversEachSegmentsGraphAndMaxConn() throws Exception { IndexWriterConfig cfg = new IndexWriterConfig() .setCodec( - new Lucene101AcceleratedHNSWCodec( + CuVSCodecs.acceleratedHNSW( new AcceleratedHNSWParams.Builder() .withStrategy(AcceleratedHNSWParams.Strategy.HEURISTIC) .withMaxConn(MAX_CONN) @@ -133,8 +133,7 @@ public void testSingleVectorSegmentDoesNotUseConfiguredGraphDegree() throws Exce .build(); try (Directory dir = newDirectory()) { - IndexWriterConfig cfg = - new IndexWriterConfig().setCodec(new Lucene101AcceleratedHNSWCodec(params)); + IndexWriterConfig cfg = new IndexWriterConfig().setCodec(CuVSCodecs.acceleratedHNSW(params)); cfg.setMergePolicy(NoMergePolicy.INSTANCE); try (IndexWriter w = new IndexWriter(dir, cfg)) { addDocs(w, 1); @@ -160,8 +159,7 @@ public void testCustomGraphDegreeAtTheLimit() throws Exception { AcceleratedHNSWParams params = customParams(2 * MAX_CONN); try (Directory dir = newDirectory()) { - IndexWriterConfig cfg = - new IndexWriterConfig().setCodec(new Lucene101AcceleratedHNSWCodec(params)); + IndexWriterConfig cfg = new IndexWriterConfig().setCodec(CuVSCodecs.acceleratedHNSW(params)); try (IndexWriter w = new IndexWriter(dir, cfg)) { addDocs(w, 3000); w.forceMerge(1); @@ -241,8 +239,7 @@ public void testUpperLayersKeepTheirDegreeUpToMaxConn() throws Exception { .withHNSWLayer(2) .build(); try (Directory dir = newDirectory()) { - IndexWriterConfig cfg = - new IndexWriterConfig().setCodec(new Lucene101AcceleratedHNSWCodec(params)); + IndexWriterConfig cfg = new IndexWriterConfig().setCodec(CuVSCodecs.acceleratedHNSW(params)); try (IndexWriter w = new IndexWriter(dir, cfg)) { addRandomDocs(w, 3000, 64); w.forceMerge(1); @@ -294,7 +291,7 @@ private void cpuMergeGpuSegments(AcceleratedHNSWParams params, int[] segmentSize try (Directory dir = newDirectory()) { IndexWriterConfig gpuCfg = - new IndexWriterConfig().setCodec(new Lucene101AcceleratedHNSWCodec(params)); + new IndexWriterConfig().setCodec(CuVSCodecs.acceleratedHNSW(params)); gpuCfg.setMergePolicy(NoMergePolicy.INSTANCE); try (IndexWriter w = new IndexWriter(dir, gpuCfg)) { for (int size : segmentSizes) { diff --git a/java/cuvs-lucene/src/test/resources/backcompat/README.md b/java/cuvs-lucene/src/test/resources/backcompat/README.md new file mode 100644 index 0000000000..3721588d2c --- /dev/null +++ b/java/cuvs-lucene/src/test/resources/backcompat/README.md @@ -0,0 +1,44 @@ +# Back-compat indexes + +Indexes read and upgraded by `TestBackCompatIndices`, one zip each. They all hold the same 200 documents +(see `TestBackCompatIndices#writeIndex`). Every module reads the zips written on its own Lucene release and +on all earlier ones. The directories record who wrote each index and on which Lucene release: + +``` +releases///.zip written by a published release +releases///-.zip +snapshots//.zip written by this repository +``` + +`` is the codec recorded in the index's segments. When that is Lucene's own codec, `` +is the cuvs-lucene vectors format its vector field uses. `TestBackCompatIndices` fails on a zip placed +anywhere else, so that it can't go unread. + +## Written by released cuvs-lucene versions + +`releases//`: written by the jars of a cuvs-lucene release published to Maven Central, with +`src/test/backcompat/generate-released-indices.sh `. They catch changes that make indexes written +by released code unreadable. Releases up to 26.10 were a single `com.nvidia.cuvs.lucene:cuvs-lucene` +artifact, built for Lucene 10.2. + +| Release | Written with | Notes | +| --- | --- | --- | +| 26.10.0 | each of its codecs, except the GPU search codec | built on the GPU, with the 26.10 native libraries | +| 26.08.0 | each of its codecs, except the GPU search codec | built on the GPU | +| 25.12.0 | Lucene's default codec, with `Lucene99AcceleratedHNSWVectorsFormat` per field (the release registered no codecs; this is how Solr uses it) | built by the release's CPU fallback: its cuvs-java cannot load against libcuvs 26.06 or later, whose CUDA runtime is linked statically | + +## Snapshots: Lucene releases that no published version supports yet + +`snapshots//`: written by this repository's `lucene-X.Y` module, with each codec from `CuVSCodecs` +except the GPU search one. They exist only so that a newly added Lucene release is tested by the later +modules before any cuvs-lucene release has written indexes on it. Add them in the change that adds the +module: + +```bash +mvn -pl lucene-10.X test -Dtest=TestBackCompatIndices -Dtests.method=testCreateIndices \ + -Dtests.cuvs.createBackCompatIndices=$PWD/src/test/resources/backcompat +``` + +Once a published release supports that Lucene release and its `releases///` fixtures are +added, delete `snapshots//`. Don't regenerate snapshots otherwise: a fixture that is rewritten with +each change can no longer catch a change that makes older indexes unreadable. diff --git a/java/cuvs-lucene/src/test/resources/backcompat/releases/25.12.0/10.2/Lucene101-Lucene99AcceleratedHNSWVectorsFormat.zip b/java/cuvs-lucene/src/test/resources/backcompat/releases/25.12.0/10.2/Lucene101-Lucene99AcceleratedHNSWVectorsFormat.zip new file mode 100644 index 0000000000000000000000000000000000000000..c78e454f0f12a6e5af19d5472d55cb7b68f27ebf GIT binary patch literal 12484 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 S2ng}zWAnll=cE4q*Z%=m_H%py literal 0 HcmV?d00001 diff --git a/java/cuvs-lucene/src/test/resources/backcompat/releases/26.08.0/10.2/Lucene101AcceleratedHNSWBinaryQuantizedCodec.zip b/java/cuvs-lucene/src/test/resources/backcompat/releases/26.08.0/10.2/Lucene101AcceleratedHNSWBinaryQuantizedCodec.zip new file mode 100644 index 0000000000000000000000000000000000000000..3f64f576008485159e7eb6b4eebabf7c8c4eb556 GIT binary patch literal 14327 zcmb7rV|bq3(spbojqNnHZM%(aqcI!Xw%ypajmEZ<#{Syw=i5))jrV<5=K7H%IcJ^M zy=G?J$3RXJ7z70X0s;a60D$28^Y;P+fCSK{qcJkDR8|570Q~WXnvM)2nlr_ew3I|M z(~X4Wlvst65w$ z1g$q%sNkAT<;#RZRldbE0;$Wm#^s2;J%dvRO}U2K{o~N{gGNjGZs^CWm)Ax2DzAkJ zxWqf7k>sGHLbr8KYR`7}=huUoWq512_Mp}l{Jkv$bBBJc^C8|<_5F^wiKaHEeMDo9 zbe^O&yz7$F7!Wb30i%LP(4v}my+Or)~yjpZX0Cyr%6 zw2hi6ZXH=z!d%wKt#fGZ_*4&fC+ZwKyd3tWqI?XtL)#%BcKj>yc)>T0tasu28Wic$ z(w5muimAsP6IdMTvRaZ4>1s;?`Hw8DU-Hb;zlh52^DRr1WHv;44h}tdI^2Dw|8e9J zNLIe}-%acz$p7ldo&R~{KP__a-&ka_LKgrCMmB5vzWe(yEf$yk);UG!aR%(kq-P&M zsPVPb*&jQBLkv>@EOCD~G`|0pv044$3p;=9y!FEuwvrPQv`4?YLEOK%0Wh+n5fm^o z5pa(exlRnM7&VYN)F3U*(5NJ>;|T4rG}V+Ouwoj5JuEDYZ80T{wB$&c4wH_Kj)4xP zq%_SCEf8j8rT6F0zR{aMd<90X(l&HU^(X%}ECX5J@7!ztD}T^`__Dp}KR-CbXnILt zdh~#bc=I|viVrrk@=>_r6*mXO?<(*=A(YF&4cHFVMN8U|Hch|DYv5=;gIYz<(9k?0 zr)hpFHPshB0Vmk;$UNUATE8@JRzKgTH);e1;>t@ZW=yb=oIlt)WqfIDZ%yS{Sz9-l z_#hlAJfgIDzAc`)Xwf#9ze+}xX`v;&J&2(n&{*_fE5Q(a*AiqLvh&GUHzbWhpen$R zI88a#5}b`19JgD<3&CQPyO=P=lD`6&YyL)K!OV?h$R1IjQ{0}tq#(2@5s?HUtpNDF z22FfQAtQf}e|YvDNbtx~s1R2*QAmW^oB{+B=JI2V0VhcI0|Z3YhiedfUjFM2U*}dJ zMT3ptrnq1&)3Ke9JTapU@x^Q6PzVudw+=UX4V_scHsR_4(C|1x_U?FOUUa{%rt?g- zNbs4vOhXlO+SLl{A2#Gb#Ni7Ka?-(E5C(zJ$lPI7eM_J)mm zu=g%h@Yb4J^V>lEjyd5}d2l(^X$o1Ihpwdj0WpDQ8;G%&n$*0gi;Csx;6wC>FgYH| zUbo(v54)h(R&!Y#XFzI}S2nNKoFRHzL~`@KF+#ElyX9G~7eKdX=>AtDxI~M4kY`?` z>i)!LcN)F4*=t-?1BkBrBHaT5i34fxH$A8UlmXdQq8U=B+ZmlPEx_OM`^h6`jpTYo zTGOa?^?WHJBj*gP^_nW*ONM4B7y9q+B1MQ_8ucPZSIT?97f3xr4_nUYB5jSNsYd9{ z1X5ohNEfgi#(>-L; z=Q}tIzJBnX)_;GYt`L45G~nEvjq{mfPs8kMI6v>=jQAzDdrV^GQXU%Rd=bOzkwr+Y z2t>#`qHJ7Law)0Ip0;$YG=&E!0@)Q)XM8-OJM_zO0$Gq<9R8!PkScgBaZgVERm4s3 z3fmKd*B^Hy9PZ>=T)i7*x{@yONWYyXU9NO=1sN+0Pd>6~v$2cDx~aYStq<6Jeu_8) zomr5-Q*qzgkS&wOrI~ei4-I0>oMbzD2NkJy@mYh0V>l%$`3IJ%STLufx)XHb}8R5jkSdJ$TM)A0C#3&CLRGyH0Ukiof zT?JtoF18zY$QL|I`Yf)`OZALRmx%&FqX7a_WXU-86VUZ55!3#Wz3QI^E;hK7LxfA| zs7nF9@Uz-fF@$IKsfLY)?py7$Ek&^Tq>PluNrfUyMwo|kV)$*Y3GStE=LB*xlw;IKM|4fXA#zRf_k4&#nnQX5|0=_ zWslSIB1_YG9s5QODc`<`7sxEz!)pZA?b`cX%$xf1 zVxOF!D)aXCzREfUJ9|&&2Hp$$42MPuVaJq9CPUa)@Sw^LDqfGwey%_*&rn4npD_;a z=$#G2Tt>4~Mb~}}q@5ngRzd0-l`=+WwpY{SeuL=2aLV|@NA87}2E4)wy8N+C9g(_P zy%u3q7scdonvg*~Fn64r2PVi%8_Akl;E{RaB zscAClP2-Mtsj zXR+PTe6_+HiGBmE8f-;Yn}N^Wb79T+P%j`U2{`l)P$2sgQvqm1-d;D;2Xz%)Q8#KP z?Q4Ew)uUUR2GZCa=*e2S=%+StdGjLV&{P9PJzYVEbO?fK#-wp*yUX1WvOYECB5A7L z*7oS@Dt7QWcFGM@8d356iLLt8RK%GM*k(?Kg(4e-I5Rtw0?^YnFT@?>aExnnP9GLe z0lDb+(&zr$(hqcBm7c2n%WGfS!R$H?6&`O$YLPvX$60haa_$}X^gPeGA}8RD8JNbx z-gvM^A&)-SR?lxnS4#REU1(qKx&kQ%j&=9)KVma}@GtyS=kk;bNm3yljnLpT%uT7% zavF8N1Z`Q4VKVB)P%!;)L9}B;{%!I-cf$K;nw`QU#?oD^G}bY}M;madjML6W?)9mb z#kkzO!&mS|6wx{^QBs@;H z7@|^*nLycHRWF%gPunVB_i8s=?gH@c_;&rKtotuqAW@b7I+e>C@R&2T04&I{+<#W za}6pmj?MK%ZFo9N6Wzg)e%_?`(!Y+^ZFb`t=70L(l8U9F3PjpO5#=V)e zmHS6>k9P%GUY<>I?rh+0s0~@yQzW`>Cvv#tVn#w6%mglOwQ{Vokqm{b`* z;hthLQ8GNp7e@%@>U(Kep)WRK@(DO1+?wxdTw)Emsr&leWC*Fwx{$&ilWVO+PK5}mw zDGxobW?>DWFSm6w-h6PU%c>Q5%GZZB*dp0rr26dexn4E4X?x2CUp@0FkhsPufXIH9 zXDjj)?s{@?qnYA)4P0@8OkI zXd#8s#R`cdhc|RJf5yda6NsZJ>8dw?5WV1}uVmhODn%ds)YuyF;*wB`bz*DxVXrog z6AEp#jNS4Z;h73&OiXcRG>M;J%oNQ^RNsefMxE9G63wzgd8HlyTw8YRp@nwfYx z>;@cGD8=?OnMP%U%ON+1fi{|ZzS3xLe0H4b_uPV^u5ceWo6RYW#-9Q55ew-;>C51k zBNSg>AR3B$9&ZA5uEjsQ;jRd{%(L%+SUEhAb;af6%J>}dIJwV>8*Un~vsJjl+}!%l zGGf7XM5&2^sX~EkCDlB8!cmSx4#mjEM! zCX=NxtO$gR4EusC&@ohXbIWnPL!JAy>sHx%Alvi#bJU`i7akjb<4Re|*&4Feh|qia~{rCCh6W4`y=Ug&+;c*3^Qu_zx) zG#Ya$lc8@P-vO_+ZU$QzjhN2BvF%3{FD|n5fLV#uc;`4@*e=UThTEQzq zhSou+Ygc^V1NfB78KqgdJKsu)I}_fze;RK>wU#|$Ve+h?5{Vt-h@+9)fR|VVjcD^= zr26|A)jU;@JNa_=w+d1$)B}DGMd%$T$C0%-M6xueJp@(M>)XDrGrbZUQwQ!cyCq%K z-OtmM@dynS15>daXQx)^1=n_<;Nd{&f_t2XC$x6r>YaKKAohBKHn)8_GT z18bHz_peQ4z)OP^m`v6b=qp9tT;0cbp2U|GEDY2Wjubtpn%C#NElrx7G|xt~SG%o; zaV({ZZer{?ugQEpYe-!>iQJjzGKHBTCs+*O)NfRU$}jJwuyOfjpkSrc2y?DefXhO- zFlaOTa?UUpRIz5(~-+{Aut8}HSk+v;#5&B za&!W;Gb4NGv8aYni%=pf=eJ2_Nyqi|0{0Y|ab-wdc}P{cR1z^G7AVI7?8z!^RrEJk zpE4&dA-iCXX?tOf6lVYBy7JA(&Y`qgBXAZj%)VX=+7(l=Eq;H9HI7o*NGS6Rx_bQ< zMMcPQ?SQER9~c!22mUZ3Xs@?|Dc9+MD6H&vmRY)&V)=FWv3bjn;j8 zgSE6DaWhAKsDb@HCvIV2AIffTdWN}TjwW-8Of^0d<{|5&S3R&0C+qi0W4~CkAT^`fEttuadmcwIg9IB5{-*QPF5Y@l6$GE|esJHy-a)Lv zuh5G01RJrHat83|aT0LmJ2MIW6w*DVIAyEDcBbjHZzK(RJ9NTu5)p^9CLs_UHO#Ya zVKo?DxXWwo=cA2{pkOq#<`FoxcrM(7=CxsDQ$IPG=R43M6z9d`i^WoE_cZ0KMqS<; zvqC=1Dw|a@rXkk;Mrnmr6b3gV4^!PjD@0}8-!jFou}Wh&9qMq=%7w%;r77?&#GlJ& zz~?Srr5)F#&j%wx;H^lV&3tFHnZXSewU%JueY7m|)x|u&o0Y{!!Af5X5f(4B62@=Y zavZU-u(jmhF!7Ucwf#neUBjRpcMYPUW)dg7El2n@I_Lum!@CbO9hKtIM~nwW5gI>6d3<<;8*sYHr-)aexb>dfY$Fub^bw>iawm z6$EwwIm{XfQI&1iYM3#^QfkA|rd+t+B9d$vp?B!Z&cxLSzFBQGhlD<=kJgZL(?RO~ zaW&)}??iUjRlbF1y3BDbw5>xe5bXnw_h8<~vVA*jnM9=pc>~fQ_=L^*-uX%`FTf#+ z@m5wFD*FWCU|CrD&Bj1gs)U=&I?8G1_bVZ)tKb{jn&5%96c-GPJDkO%{93yZcy_1} z=W}di6DjJob~@}|R86*^NO={zpNbo*2#wf9%W=D~5A9jgd6}E$$dfa`BcZkXSA@+v zxppw;vYEc{MJ*!=2FgR=>-cqEk@$TMv*_)iG~txVtk0?o7nUkS4`Q z)Mg}AxJ>3SU|9paL|d$@DhJ!dm{h^_k-FD7va^E`B<5N5WC}vyhS)*EX%4!*1W0*} zty*VS;8W<4O>UnrqO4Ys>_KZMMY^yQg{{G4y@{FZiucw>z$hj`v2r`aaV}bKrirj+ zU~Sz#*y=fQ$V^$a8bS?;nZt_c4ubSprW3)|OJ#sFIndJAUt!Q;2+ckdk!svSg3)mb9vSn*%2@Uh&=A=TosHf}$1l?F)V_D(CO z@f0I^K&K)!Dn!6LlFl^Af?|n=Z;lZX4L@NhxH{1|jm{g$Yy)+<@1il;1?rqpd$UfUcAg*clA(!l$L=aujAEN~}_)Xm_gCAX*1g&A$ODBkO# ziuhjDx4O)tc`%~ZXzJsp--69N)`9GE&9V_{bWb9I&K8aH`#oCP>dL`9rp(>1rwgsI z(;mY%&4bQ;QdaRl^BI!VZ~nSIkbAR7<#sr>k4tFupp@efgj@T5FN@E($fopbG}O5V z#w2SZmPK0fX*=RaOq60LKF~z8{%@1CyUqw~8Gzb-VdZlAQ9Vd1UpT$-I;it*2MJL$ zYte!mDTjS2XL?elzFf<1pE z;0>@IdJ=_^#F8SH8hZ*^icggNGk`>zQd6s9J*AKq%=OoenraFZiZ}1N4?F@MedJ;^ z$lX^+N%t0L33Fl#ahsh)ct(7R<^+-!%Qg}TL}>G)24iC&?<S%71 zuS;7{0RY4>{#SYLKWZn(f7MP7qd#ikCZuS@Xe*s0Xvd|hZ2hPv{=4E09S|r#ItZ0a zo?RNiG*lQ&KLR2k7@LWm9v}GI=Z&9LM9)?5^7EgCk>JY=Z=LT&k--027yvlL0tZhyUP=(ro3@Vs==emmIbeK?$Ge~Qg~z2bd-ep-Lto_~8a@_IcCe0v$ddpqxV zeertv#`^Yfto<@_zRsf%9amBkXMiV>@Im3jlg6~BP>P5Cy!}kdsoikhVqz2VQT0@0-dK5F-Ywh{*_^QLi{L#2)~rZ8^Dgx=xdIy& zmP?(sqIBsJc`$*ysk*<31YX7$+2zL7P} z4H4NX<7A!+Umt`p(~T;GNRX<hBS+RBz5Vmpa0p%qNPcTc-ep%Et6Dip{Fi!xg^jPRL2U_L4NSo96&;Ka~azQ z#XeC9cvlthjzz2<1aG4mvaoQ>>cy6dprRhydf^c*LW#8{Lfx|Yw$^u}y7c zPIvLL(ZZI{S-mUPR)9HE&b7KNya?lbt7V9I5Vs%3pdH=Th2X-)3>`tZh-j~tjwbB3 zM-uJ*yGx7(sK%g##^||1I!glyEV7NKBBmTo9t?Te2#a|*5v6Av(JcU$)An!=tZ$jf zP>#khI+D4RiG>+l;YeMRIJ%`qzAGlc@v^Y!ldDOWk&Tgai758xWHNS-*;SZCSnt9q{{uLH7jImYMyd_85!wX=ZtOnI;t+6@|LSoo_6@v*l9%&cLn z1){o5H77@a z=hFfv+W|n)e<6|(ts{%_34gB-Fd`Aw&t2LXe~cUcJgEE$i#)NCGp6a#?j~(v+Pw-AkH#}S{eqp91rG-#wy&5H6yYMPu{#j(R2s)oueKg`Pf{rwn-{5zLVyIAT^%~4Ey+iA=Y_RIGt7r z_tCK&MuKy|zRmB7J64yJre{z{SFfosL#3A;U>?!MANsPanw{8%?cIX1j7Y80l`AC2 z=c7WOVg}TtCDcry8Il@ptNB?G>q=7t7(rPCSxX?kg;;$DDRivQY$`!e4ijNoH4AOc zL<$IfmV4+QnGY#F{Jf3s6vYOS=c}r zGr)KV+hov*x>G(W@bbupw#(S5ye#Ds zVgYqu39R?$hA8yF7iC{>)!n*&1_F2nI`jy<$=?QS+jI;7&f$Oz@zw+>8DVcbF2^@{ zt;x9zH`vnwDKu3X9(#z-^S;GwQIEo`!M3pBg%YPey7P^i#R$=+lbTNhS6p?-qU$y? z*p^VzO(s&z2LZASjEt-va75@lV#y;*@$v)&PedN$L<*CCja{`E8Z(ZP7UbxgZ2P2{ zYll+>(EWC(=5aTz>`FKKDKn#pk2uiI8Kc@ffNV=STGL-W8!@~cat9n-Wj#12!&wh4 zCrWkrvx!#sqEA*kd57E7SMzgCL3XkLEf3bKR*9wwb8B|~m^l>L?&PL6ocq_+nrHlH zW*4SI3^_tnQa~McbMi!$MvHCDVSB{e@J2rtlCcsFc{QC?ExN?Hw`S;*c7ud`AEkgAG8Gc_r==M@Gl;W~rvj9DHvSTswUM zY)L#mP2MC&KqVU!Re8E7DVT^^<;5(qGPJeMxN+w#|I8B`Ae}8Ou#$4`Sg~@5Q=&YA zFu{>w!(~9&4}cjMTP#r?XOr#WZVwYsuQTQu&I)wnRdmOn9io^Z?YIqD0YdWYE4$b+ zTOYryf{m!Ah@KhrH#5xj;6};e-Qow83;-p)cgU;3nuO&PjmeDyK}fJ6sxcNNj^7(T z&}dz#z7%hOP_U6qI#E%i&@v8qVio62i^-#A2OLRLXpizvqj2Un&AhCzG=eV0?m7*w3TU} zjnk9JF7J#r60RU@JfC6|Tt^6^ax|_H9hu?>D$zt?(a63h&zg{)k1J}@H!=kEHqeSa zAQCY}YGZY^hAdpoh+Ihs9YA9_K=Hmj8~!wuG$aO5`DmFmJ{$PK6Zn32gD|WjJ>J9! z_+9;z@%tV2O_kvkL|Q#e$_v3`n4-j^sC1cDuiP*IjOAokl*0azZg zL_VbS))`GJx-44L<0=h)V(Skf)~ar~khs8*Lh?CXqsrYX@A^tc0I;nb57nG!&jRBkL)$lP~WEcl; z8HW;4!!7xIWI8>4f+=ZC0|>-m!AY!n-|b7E%#2XKg$&OI#$RcoRj5xcyWBobL{+zP zC3HoOHf@bMnl(heRC4Z?7(YgrJS-+By~NjMMpV>{Y!nF&%Uq_?lvwTbH0TsWa~?cL z+vj3oGbaZhw~T(N5Ct^#0zpl?Ig(}UJ$`VT?s|?}5_Vsv)!nlC!Jg)RBmLCgoBw!V z_(XU_HQ6Y$YLW|k3`noEC>~ovM^1>H91cenI&tHw#}pwys9Q!|+--lk{@dAHNa>p4 zj11FMXXC785U7+HMO&B8Y_03eEL*V@4%{dz5On{IPwtI^%boDKEVJs`TpRwb`NP|z z6l&}vF~A1(xhZeya@tPtNzsV_QrjXAx7B3FQ!&d9qgxi(`(w8Av{q=eDjR4j+Yhch znkwky8{UId>=9h^=@jswe&tcVxdunJP0dUL*X;zpHPDG^XPi5()&QUfU{|Y$61B?eC%>RJRXc%xj*6drxInpf|MSs&TbdnL9pA*MCb~?^jY1qnBj`djdP$JnA z&!W->%wd4;hTAsX9w~XTf?XV7%mhicgL$};*QIC^!e#=A#+hCl_tIG9vOL!P5KY+P zwl+gvrjEuZN7g9`xI$up;u`JIlt3_SWKA-g4ZOMbPU|S$IMIXnD;A*`sCuBj=DWUT zr(#tZ*mmiGjSn9RUR2YxQ-r${?!?&Abq+RlWVgcrw@WfnzdDH%^yk04$z4EzJ2KrN zGb6+rET%c)i>j|C6})xwoho3AbI6$FOn#B|(*zJub<@j^@yn~nvy{9$GPbj?a00qe zo;=iZ9p}cBh3e)QaXG?7K?Zk7Q)siw^}gt$wY`Ep&6K}2%J7zH(^QeOSTVJnfL^;` zI(|8`_I%*AzTD^aeENd-4EZDRB)(Nq1&s&*fWY|Q5>H4!b358n4*G^xhV*pw-<#PD ztp9Gl{F&z2{YMhYL5NISfPJvbcL z(0-C4_vdDi`cBB?|4QnU>EEQnV`PKE<;^KsZkJW?RS!t9;*dgzy1Tofpd~4LEmSD#sYscJ8#Xv=8fR9FZ=TyLX09^s*q?aA z9WBnoX<)|3XyF2{diwlFUTS+vKNI-;NSa4ur;#fkg+Gqjh7~^ zbs`Q``~>p#2gw{hJ~1IF0SOVAXm&a}2Kt)H>MDB_NFX19Zo(YBEhIOrNPC@+H8=Xh z&&sbdHWjXN4mMl1UocTn;^UN+=4P2wK!F8`^pO=A?L*JHx~7H}`{u@0^}|kSi285A zenNzxwbIf!cr}*r=_$@ts%?jSUI(MzhsiIIp;#rRaG9|xiyj}j^rFME$-Pf zO4mQgCAzz2*ED?Nd{{CgLuGjhC@ih_jAmHhAA$VvjTiFPle~b|VuHi8#fc>qH{bZB z#5r~2=H%Y(R`zb-sr?!BhaX(>2s5UBZ#vEf{y+M`CsUi>)14=Av6*Q&3UMizF=}z@ zKZiQH{%t3FgRH5YnN~^mXWj31H=@N@;NLHG8J-sK)Bbe4fvDw?Eq~hIOjcN+;&|M@2(No?-)6+l(3^nRD5R)J>A~XrUs8W*B$!TOZ3>Oyx41ApeMi3r+`rn|7 z5PZ90LA3#YoIu*xo?rU+Hyr)1Zwd8164@IXTN+w@ve%~n?d`_p(^4{0Q$8yEh&S9O%!D zf9?MN6Z~t%;&-qZ_Fv$?BlDl&UrP%=iW+~f-G74rE^+*c{`ErreQOt-zqbC*pZ2fU z-;Yb~@6|=~1NwWj9qF;m9?}~=# z|Ao=7LG@2Xzs8B*6&W)8tD;{b;Gc?q_0}I@VS+Q-~=3jNYN8h@)`}P0c&OR9VVv{0I#2y3k$?d331l`1Isy}5 zH6SqOrA-i%ZmbbKU?3?vjF>YT#MqFF-<8k6E;_v{&zol>fUE>K#PI--q2ABcGJjsO z|9^N6|AJROMPse-1!drhsZ~=B5(*d$#xI)d!X5?8N_jIC%r`}<6_Ny-+~J7?7R|Ry zr;5D;sN@Y|2pb0Jy;i}(l+6?(j@!86aj%&pC73KMtueE0Evqx#ys_2e&d6)BGno!9 z@_A|Mddh+W8S%#B;`U&B@WdEDsDwrUO%qTG3DsvUAsUT+h%!JS zBr%f1f_-1c3$JPQ&CM_ z)ejJg_tC=(?2k~ZUT+=c`59)P|5u@A^|wse{pZTtzcOJvB`HyR^k)*p|639OBg-2> z0V5Lw_j;4-#KMZw0GUG#(b4`Gm7sGPp&OQ>o{|8TPe-tag@v&#rlOUS7%9_X*3r>1 z(4mr$qWwV!gcVigs!0)cITkro9d+mS45BiTxwm1FPgENd_ zkN{@D2&xP;uh*kcvzZkGIf}Vm#>)`Ag5n3?I^MOx5vz#Mie>td@E)~&T;$)`H8wRT z=IvJ-rZ45L)aQl3zy9!~#e0q0{)ubl4F*xVg@8anxQTbCx=J#FcD*xpB5ma5Xl8B4 z^4Ux=T&3=s^3}5~nJKLtHL&I--Z(Cz)C7k^zo$By1^*MXqQM15n8Hl>9D2cMQ$aX} zC?%y!{gRW0lWOvN3eqE-V z*+jE)#jd2t%o>XrSkiI|tXd>fLy;KgbtU--7${5oat>4=y6=bxpYxI-_r1cGyuqRC zfSA&I<6fs-Z^g|^lt`pza5Cd>GF=e6RdpYn>C{C1+~Y}9?g9*AoRYh~+iipU)3ZOk z$)7ywJYUtqoGh<-KZ5;=-kj)iLD8SlyZH0^|CTE!L;Zhk0?mS{#@NvZ*d?)8JV~+a zGP=~K;=1$;M_faSSn*|rWiARaEE+ime`<>F3?xF|zUpTo$U}dm-?+R^E$oI8nrE#F#9gkR>|NAdGiZ<&wLUL>H*})$-Bg%;a&GHB{~N*w_bA zWTNV6>YL`@0xdV1$>Q1rmMgike>AcPeXZQ8+!Gh`!yd7#Eylu_SjW}3^?K4LQRVG^ zn=mMi7LCXZd;GNMPj3#Z%5O9r)4@dx|qI|3csSOHCu6Ya3Xw z0L#9SW+YN(jH^cwmV458=u*bHBPB;Qm9Mm0j|T8TxQLM0iVr%^@txU=tk6L>?I}!y z(iXQ>pcT2M;jrR`nRC23#iN<`5~xe8W$>NnDxYI6SXH!P{d1q>)>pFo)jr`X$z%cb zeqhR~DF>HXvyk%?fMn&*i9)-F7h*4ORhsq~4y<8Z$HAWMZS+!^Qk25FSKCD}>Q|2E zi+a`(XfuG75We@s*SW5)9E~3(RMLNhK;Oab>W|`iu5;`L`Lq#>E^%U>cQm*2^p=i9ou!WnNw z<4>;g87p-_V3>=WS+UnqEEQwr-FHla2@Nz&cW^`MX<@PP$)TcRAieKMp^P)E zphb$khSHyxm7$HoNzm?e!=-~Slk2>0D|(q!R0^(pz@2q(KS@{kje05EA(wz3q<=Mx zzwlKtVAdbGLZs?04;3t7@N4gI9JV(_z>o|3ZhPz^qJvh|wo^Q<8_9YuOC~rvxjeO6 zU`h(832c*ZSVHkD^|*nH7PT|gz@*tN;;e>Ku7Odwik+ZTI-eojU8Xg{#RO&_Wobr{ zdFJ2^Z?7w_0ZO~y`A%^?OBQpWtYbA)zcn9`I4 z$21&vsdkK;6=bV`2Zkx8(0pq3sw!UInPU+SfqNxBU!F%T9b9QfkoBxZ!0opud2h6r z7ysnq1mTSj%4+L<0RH~K68v{G4Ne>m+337(K@K$744H+5nPLQ?Q zSv~|Zd5a>)2dp+;Y$7r>BCD38Zn^v~IPXcs?OyHy6{D&eE^`TM-Y+&v4Tv=G;EcKX zs4G%TKhi=i9O6YtXSOU0d()kT`9}_NZgH~%R-ET#~Q2P z2kfVCu6CLMiF~5-5hBfy&2q$GnP|PLt3f?J298nKB0~`$)GheHD4`KItS{zps$1Yc z6T%maH5`X`E@k*y0U#B&+q|I%)?)w9;K+a|CHrfCf^$$Xh7g!cH=y9b5s-)Yc=UICp-2_X<8Ng{x&iQ?do+y+N(h z3gJ~fI+h00qR+?oiR|7jks+z1gB-fW2f{iA&#eDPOpdg!)+1Ee@ne{GlU5qo9X?99IN_PQ#sA(e5dyS~zwo+U>JZrS1 zPm4FBRY@jT`J+7W?XHj4R2H#V4CeZuz&b_u#*_>E#D|?E8*j50mPs(^8uJ&lC$0$W z$(9dz{KjnstZv-{!(U1ilxhU_QC#Qu$IyeQ;Bwj;cs;$;!nPfjB9jcm$7X?DF6V-h zklI}acAzNhiA^VGL4Oom`?aiblAk-|ocYMe@~8yEjBpLfoqQglfaQgT&#jw=;2<`n z(mOIsHVoFYOv3S}ZR3}FhXP3%uH7r<#f72_(DzK3WOg!^D5Vgh6sOKZAG3Q8W?*7E zvOMk!VYM4IdkG24BCXY4C1HL~2&+B;9k*cC8($TXZRrfa&I7xi1(0fRPTV{JzKpB` zEJ^gga`z$5P$!$-^ftG-xrb1YII#anBt(H_KuqdTqiVywkg?4tCi`HC6}G0qD0mBV zIRfw&$EE`5B=kI_D;Dfp5Qt{ws5y-w+w;#*#orwNJOhJ;g0A*^~3N zyhd5J?R4$+uy=h(CRyq3GtiYU^d!ojr#NO1~6nQ4$q2%*E zZe;7@a@S9^(_H@8!0qH51>_Ymd+R3`46~PTyD_OQ;b$>CT#KE3T5+F1{GE&1X}7+nJG&}>^w@&1S- zd=@)`kyP0i?cr{vIFaq%I_m94Y2)H8TE(gEJF1N@&`9m0xDs>|kjc?1Y5nEw%^ISX zQCCiuh?9sfO!IrKi~CpZGh8VbJpshq*ayA2lSY%V>Y#zz#>*EMMB%i9%WHn*RM3ci zP<`m}^oC@v`N$lD0b>KGoapb3&};_PbB{2|%st_eXiOB8c7u1eCj@E^Yft7w);uO0*9J-Fi&cHBO;z4RLVx6illYq9S_dswV{ZWvw+`u-*i7Oj(nP;dK`df=x2{ zpxHtdnvZ1km3A%rpX&sgXm7d;puq{6Gd{g$R!Cc}l-exUqh@B}XVX8U?9+$d5w6${ zH{$0cI;wQOBi!=A?q-5F%C0-YalET%e1tIN9u~D{w@rX*v&gHkTLR!H@dTN@5Sd{| z`Axj4i3TM@f@>vLz50-v)3pnE>)`chOBbYxkeHFB*ea)gX1Y-++aM7!mH-wHB84QA zq1CS}C5)0RLiX!~)j2tHJp7?9oiy^G<204z=KrSpV%%6|eIS>Lo6zhP0j78c!YXJb zAT$f{e(j9K1wYJmUP6ZK#p@@7Xs1!Lk}yF7+)Oj?r=177UMwjje9Tr?dR+6mA?SgR zJ-ohXQxUG-vk$&63w@J!*V!7_G1WYzf-^LPZB|z{XSnZ+((z88$f0$;ok~hkp}=7$ zqKvfj@GRRp7$h!bd+O$mv*o{P;9Bv8ZH6*u>qJyAs%8(o^GkHH6cRdVM>jRN{d=i? z7ukqF@^|g9(6}pVK}s1c*nRDJOy(@!EVsDjfbUsY4BdJ}LcP+Q-sEFKi_cl6hD*S6 zD|`VwPtB92N_p02L}=EFVW#GzDRkC9zF?QTcu(V?y_bd;KL!ORLD}k6QR!39=X!;M zAF#GeS%0vd%6fyi>Zds7lYvaoIE7QP`=f{e;-vvEQBd@kE(weRqe2qh9r?e@|n zm{QA8`HiTMb$lpCz+NrOuFV29XROt*O%7}*K}dz-WlE%>uSymU~J zU5FzIO+en|t0CjbNF_nzN{}&HGLz*DYB$>w!l%~x-Vw@BYqbYm*Bu?Ovitp+M&s=K zOD$1Pikt2f0Fd;hPYP{ji0hu2WALzcv$fvjiO!w@c-W3XXR9=ajv0RK%G{{ld=bo_ zD0p~Fv!zyI#1o*(+%Shq7)YtF6AKN@fjR~;7`R81S{NtRj_>m2jhM6w)Mi+%h@lco zKQrnBn71V|olIDf7s2MT+k(0=3tyTSon{>f$-l_L=L$=1<8W}OoiiHV^ahf@GROC7 z3C}Dm!@1IgmMw(u1wtd4(G~e`XA<|I-Ke{;&PL!WhVXBtA}`iyCZPoa>ol04_|;$J za2T^JLzO-11VazPIvv!M?L0opCS5d^35buts!+-3FZcJWoS64U!iad)#yr_|Q;Vl5 z7BiILAbv;|XjzA3w~ho&I2TTdh}~!K#nF6}WxKvnN@DJbF z#tE&lRI&1aJ%}3*OPuS~j8@$Q7uby$xqG#{gK=ob{_GGUlkhnAeIyK2U0lJpd2;){ z8G~qz;f|fz)9Pdg(!k@-6!e|vU8Xfd(s?`YCnbhhz|`+g^=c__UGl|uwuK7^W?q!kZVOGafNuCc)f@!USs$+u?nMy3&(G?sTd}dag$I?ywa^>%TBzaB35I0= zIa2PC^SHs=eT8;Czjayv@#u%Q*G!)>2g6qBxVXa^yunD+d{<07bA6vz??S?ib}UQS zhL5`hLsaZ~iMT;-bDIMzWrSvzLPFniD%ztH%;WG3TDYgdv3IY!r zPdWF=t%?KMC=K#W-+HU81NdavLPcdV2(;^u{MGH7C=5o~#J4o`95X|hkfbmb*A>HfwXs332k+=OKAA<;i{Z0DDcQ>= zH1l#}+mAJNH>a1W9XH0e<@@c-ZGwSMq_!`$6yz#g=(N2LI*V$?iEuHwW_?^kG^IW! z(GA%yH{TF&xqTWX85fQ4#9taq9NZVkYe&CQyVFOxfj@S$su{T2~55n$#$^u5fybrC%jp}`mMdAt#2fgZMd`-Q?LOY=&pjh&K z0F%)fzlOLf)(uL3q>t;bd9P*VN+{$XOIh*+=$Lb?%1DNB*5~Pn8^+nyik#Fn)_u?l zyt$@s65;_jVoD1nHDWqS0a~AEia0Go#i?~;w*Nkn!SbQ7qx447g3C?of$5D-;RPLC zsvSH%3#8i>Nxtl*21zydHpJ0Bpjw9gdE!#(n;+Aa_7=`_=faL#3NoA&di6&)Rl4&+ zp^F5RqCi9dV*8ge_O@u}tXJQ>{6c-ydMoRlBX0~wk zb!sC=JGOLxi()rI%)wB*?3kF(M@NkaSv)`+<4@7M6S@=M02E{t^Bt7iH_NGIk0NlP zm1y}Ji{RMQG%)XX10MtSq0^@|Mt708z%0VP55&Wgd+mXXF$H|56xkZR0TR6lZ(jRA zt+9?Se-3%{nD#@>k;58ui+F`KP3PkxK&H8$+a4JE2kJ0>2M7dThk;bdVzg#O@-Q4H^%$1Qm~b{{VET=EPCU2@s0pYSg>1m&uVRno9U%cBEjMq8jJ`m!#4;8Zt5Bs2!S0&sPE2XzRu$# zZUX0yUpS;N_q$tl9;bbz?;fs9+95}@+%B6-zolp;nBW$`-i5l7PvX$7melWbJ*?lJ zKyQOdfEzVi^d*3~n&Hd#>beV@ba%}aDaQ?02wbD*NfZ@yh&W^eR6O7<{UmN zD@UDw&jz`Z)N&c&ipv`jsed@DGoLY{*Q+l#{?4Q}qQ8YO)Xl1l-=F$kaCr-aCT{cqYqgC)SxSP6G<;X&Y( zNxM23{z-EwrnW{}jwuGptSu~?J7+aS2nnAm+w_GZhzg2EBM{z5fDC}GKxp|c5`7}s zf*f)IC2|4LLjpn?U=u#$bsBk>dFYreNljuH#HscA;PZZN?R*&Yeu%s9x?hX(ehub( z+1vP7a^`!QoAACG;d^|ddw-_$x)b4h8M%1h3iEnDmiajG_I~E`emVHKKKc3E`>n9^ zDT>ZH#c@{dZbF2jyncDu%rQPkd6*$ctIVxDe#-r%{Fz_-ZRSLCLp>kI@yfQm<2Gylg}n6Pdg`{lHA5a6-LvJs0W6Dh&+P6ib{Jf{#^QiZ zckPk&Tljh%-^c15^~98mqPHWBBEDLlbQb>vjnqo=-SkV@cY#~g<0*0VHn|JNSHsA{ z8+e`p;YsA{Z*F#dtamjsU&ohEJQDNZEx$id%O20wy>J>08YJK8JRS4h(QRB%AMDk; zmGaGs-7c)X31eDnBr9vr6~nhPs(#@*E97GcD{HQ6Y}P8(Wp~Y?_IB8;(2YW&W?QoR zpoFjUCEL&8s|S2vVFw*JcQgTC&Pwm+plqFoQb`YHOXqDxn;IGH8|F+YBUaX{-@)0- zy!Wn$tS2DzGRa}B8T_$ju^a?%Q0!G_oEF?zl4K1>)LEAo!Dd;%MIn6#s|U}@;vTi2 zT5Y>dd!UhzyJr=Un3&0F%rn1xrwAU z;eJ#Lx;g)1rz-KV^idAdfG%By&sWyAx9^+9d+`MRI{C(Kw?4ZJx>0=T84oU+_K8e? zw-{8yOWC5-c+Xm>hi56@ZF~2WKm544^yiX#M})S;HLi}%bhYM(2QeZxYf1K=_27Gk zJ3Tsh@GoW2*~M{DRcUDmlo!b&U)J}1b|L(d ztmPN!4@*Pi{Fe%+#VQ&WUhDz(q+Xo&(?D*rCjcp%f<9ezZl{E4ta^pDTmTYy22ub) z0tl#0G4aa$N-zAGhr9WikDg~QzJvB2k)Leu{9*7y* z3GAg9%4QNy%=rEeh}kfB*54`yPm;*+DR{4x(^v5agFG581gZer2^5ry7N<_SE(A?^ zzHHBLGw$SaPY$?7T4giM1HdgG!%Bln1Tk$9j^ax@hK?E5A*?rwxgA2YcGQmho@-R8 zJL;79!0}m%g9Ck>^bxB`#n#;C4PI=}o!s7Y zT8{_V*%~c3#`97W(Jk-r9^16!}o6B!m_}SQ7_JIYQ1JsM{;bN&}9pIqoSU zgd2U)+`~ptN{=AfUIC;&Zj*iyF49*;WltjSfms3c9`gY$YX(>W!@i_A@||AH!@%34 z;W_abZJ;edQU^;Tk5JoEio^?3=<6@<_+(g*vUS+B9Stt3Pq-{1QQGf07iOUQN@fMs*m*5C=xc=ZYa%*!*?cd zi#>QNLxpH_6THORP*P_>SaqO_&3a~lGHjK_m^l74@_Y=oc5ub0+lIH8LxcE&Dxp45 zbj$21R34UVx_+rmnrks!>@F{WCNcxUhynS129%}#qbpiY03C^W8<8N^AD?|;1d7%s z7xqAjq1hnn^vBnn1eAQT^P)Y?gyJmUf{~E*D!*$w$b6~rz8kLDl}~iZikgkxiY9nP z7sojd@qPg=xFcovikbXc$GTMrl4Z|_kijbhMW0O33FdqkAPU7fL%L7ecK}1B7FKr% z=%vpNv<^s)RuIE?V4#V+k8a=Ixi#qIQ^5M&k=xQ2vpL7)!3`uNGFbY+@OJg?xV15+ z>#z1*NCEk3x}I!M&v;aeFa7n8Qp_lH;=~K|G5R2Uuy!R->ou*aW74<2z{)bFO6qdl zlBeP zK+I2)W#Z*(l#A0X9$Tr9>3|ql)R{TZ=z7pL&H%LH4aOrT zsea3WN)#ao1}%bPZoD8J_UULYlv}-mK%SZ29=3lX#I-A|GwbzfLRmJfw7=bXTSZ!DkGSL2bLIW=Y$DUKD>>;vvG&n=TxjHHEY8w#Dy z(jFoE-fX`9om&H3L8+mti1>QDIvCh%(Xrjiv`^hh8_bLlPO?$gSsXU8U);ZP?p;6V4p)8%+^=ns38J zpyLVhhIN`CcVT^8L3p^tu=W1Vr`;xoZX4^fC~5wp6?5=;0{vF8VIbAj!7?M(bA;l8 zmoMrp-seq4Lmmws{$sMfDYvLm5ND{f5mFWwedynh7EtwoODxncqO)Y6ayPifsdsk5 zxe(`8`&m@e3ks)1dB0(i4LgtkKC62s??c9cn5L2OHlQgU%?sCoIOf1csv}xAxLoJ* z&OsF37Zu#`*q}ntlSZK*RE}lHYtKk>xu6uZ#N`+sFm68z%ceT(3>z2;eQt1|Hf5f~ z57=72YkdJh2(jnVI$8*cJ_o8E7tk)6M;XOcH2`1(U)I4)6gltO0AM!QKaDAyi3 zKT}wkPr5^@MP+n>E~rz&hIRu9r(c@|?U)q!xYHm8@QZ*En3*L%7*nDx@O|z}`;s-V zeSUX@3oCExOkuvfIr*&rX-XD((85;{8pzP9g)b@WRM{VoMO&)n!by*7R;PCsv{_o-)OXxhU)Cs>=Z ztwZ*4_y=Sw|*(xz&1C#AvmdA5d!1YMJc!nh= T6bAZLCFcr4V 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 literal 0 HcmV?d00001 diff --git a/java/cuvs-lucene/src/test/resources/backcompat/releases/26.08.0/10.2/Lucene101AcceleratedHNSWScalarQuantizedCodec.zip b/java/cuvs-lucene/src/test/resources/backcompat/releases/26.08.0/10.2/Lucene101AcceleratedHNSWScalarQuantizedCodec.zip new file mode 100644 index 0000000000000000000000000000000000000000..e9af9af1c5bedeafda9db222878efff9c5bbe462 GIT binary patch literal 17970 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

vUqD|mBhA3)>`l0`?DUzmu`EKF2? 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 Re}ez`nfXXyGWK7;{Xb_-X;J_H literal 0 HcmV?d00001 diff --git a/java/cuvs-lucene/src/test/resources/backcompat/releases/26.10.0/10.2/Lucene101AcceleratedHNSWScalarQuantizedCodec.zip b/java/cuvs-lucene/src/test/resources/backcompat/releases/26.10.0/10.2/Lucene101AcceleratedHNSWScalarQuantizedCodec.zip new file mode 100644 index 0000000000000000000000000000000000000000..25821515ef77be290cd8b60b6b32fd3e0f985851 GIT binary patch literal 18017 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 Gfd3DpM7V$e literal 0 HcmV?d00001 From 358c411251c1be6916c00afd1c64fcee6b8a6265 Mon Sep 17 00:00:00 2001 From: Igor Motov Date: Thu, 22 Oct 2026 08:39:08 -1000 Subject: [PATCH 2/6] Address Paul's review comments --- ...e-api-com-nvidia-cuvs-lucene-cuvscodecs.md | 157 +++++++++++++++++- java/cuvs-lucene/lucene-10.2/pom.xml | 7 +- .../com/nvidia/cuvs/lucene/LuceneCompat.java | 13 ++ .../com/nvidia/cuvs/lucene/CuVSCodecs.java | 88 ++++++++++ .../backcompat/GenerateReleasedIndices.java | 2 +- .../cuvs/lucene/TestBackCompatIndices.java | 38 +++-- .../lucene/TestQuantizedVectorsFormats.java | 7 +- .../src/test/resources/backcompat/README.md | 9 +- ... Lucene99AcceleratedHNSWVectorsFormat.zip} | Bin 9 files changed, 287 insertions(+), 34 deletions(-) rename java/cuvs-lucene/src/test/resources/backcompat/releases/25.12.0/10.2/{Lucene101-Lucene99AcceleratedHNSWVectorsFormat.zip => Lucene99AcceleratedHNSWVectorsFormat.zip} (100%) diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md index 4db47dac55..e58ae4270c 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md @@ -79,6 +79,41 @@ the codec _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:59`_ +### acceleratedHNSWFormat + +```java +public static KnnVectorsFormat acceleratedHNSWFormat() +``` + +Returns the vectors format used by `#acceleratedHNSW()`, for use with a per-field codec. + +**Returns** + +the vectors format + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:69`_ + +### acceleratedHNSWFormat + +```java +public static KnnVectorsFormat acceleratedHNSWFormat(AcceleratedHNSWParams params) +``` + +Returns the vectors format used by `#acceleratedHNSW(AcceleratedHNSWParams)`, for use +with a per-field codec. + +**Parameters** + +| Name | Description | +| --- | --- | +| `params` | the index build parameters | + +**Returns** + +the vectors format + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:80`_ + ### acceleratedHNSWScalarQuantized ```java @@ -92,7 +127,7 @@ default parameters. the codec -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:70`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:91`_ ### acceleratedHNSWScalarQuantized @@ -112,7 +147,22 @@ Returns the codec that builds HNSW graphs on the GPU over scalar-quantized vecto the codec -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:80`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:101`_ + +### acceleratedHNSWScalarQuantizedFormat + +```java +public static KnnVectorsFormat acceleratedHNSWScalarQuantizedFormat() +``` + +Returns the vectors format used by `#acceleratedHNSWScalarQuantized()`, for use with a +per-field codec. + +**Returns** + +the vectors format + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:112`_ ### acceleratedHNSWScalarQuantizedFormat @@ -132,7 +182,7 @@ Returns the vectors format used by `#acceleratedHNSWScalarQuantized(AcceleratedH the vectors format -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:92`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:123`_ ### acceleratedHNSWBinaryQuantized @@ -147,7 +197,7 @@ default parameters. the codec -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:104`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:135`_ ### acceleratedHNSWBinaryQuantized @@ -167,7 +217,42 @@ Returns the codec that builds HNSW graphs on the GPU over binary-quantized vecto the codec -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:114`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:145`_ + +### acceleratedHNSWBinaryQuantizedFormat + +```java +public static KnnVectorsFormat acceleratedHNSWBinaryQuantizedFormat() +``` + +Returns the vectors format used by `#acceleratedHNSWBinaryQuantized()`, for use with a +per-field codec. + +**Returns** + +the vectors format + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:156`_ + +### acceleratedHNSWBinaryQuantizedFormat + +```java +public static KnnVectorsFormat acceleratedHNSWBinaryQuantizedFormat( AcceleratedHNSWParams params) +``` + +Returns the vectors format used by `#acceleratedHNSWBinaryQuantized(AcceleratedHNSWParams)`, for use with a per-field codec. + +**Parameters** + +| Name | Description | +| --- | --- | +| `params` | the index build parameters | + +**Returns** + +the vectors format + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:167`_ ### gpuSearch @@ -182,7 +267,7 @@ parameters. the codec -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:125`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:179`_ ### gpuSearch @@ -203,7 +288,7 @@ filter-bitset-cache configuration. the codec -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:136`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:190`_ ### gpuSearch @@ -224,6 +309,62 @@ Returns the codec that both builds and searches CAGRA indexes on the GPU. the codec -_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:147`_ +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:201`_ + +### gpuSearchFormat + +```java +public static KnnVectorsFormat gpuSearchFormat() +``` + +Returns the vectors format used by `#gpuSearch()`, for use with a per-field codec. + +**Returns** + +the vectors format + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:211`_ + +### gpuSearchFormat + +```java +public static KnnVectorsFormat gpuSearchFormat(GPUSearchParams params) +``` + +Returns the vectors format used by `#gpuSearch(GPUSearchParams)`, for use with a +per-field codec. + +**Parameters** + +| Name | Description | +| --- | --- | +| `params` | the GPU index and search parameters | + +**Returns** + +the vectors format + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:222`_ + +### gpuSearchFormat + +```java +public static KnnVectorsFormat gpuSearchFormat( GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) +``` + +Returns the vectors format used by FilterBitsetCacheConfig), for use with a per-field codec. + +**Parameters** + +| Name | Description | +| --- | --- | +| `params` | the GPU index and search parameters | +| `filterCacheConfig` | the filter-bitset-cache configuration | + +**Returns** + +the vectors format + +_Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:234`_ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java:25`_ diff --git a/java/cuvs-lucene/lucene-10.2/pom.xml b/java/cuvs-lucene/lucene-10.2/pom.xml index 5650114e5e..01814ab005 100644 --- a/java/cuvs-lucene/lucene-10.2/pom.xml +++ b/java/cuvs-lucene/lucene-10.2/pom.xml @@ -27,7 +27,12 @@ SPDX-License-Identifier: Apache-2.0 - + org.apache.maven.plugins maven-assembly-plugin diff --git a/java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneCompat.java b/java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneCompat.java index 03a380f89a..0ef3b2fa11 100644 --- a/java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneCompat.java +++ b/java/cuvs-lucene/lucene-10.2/src/main/java/com/nvidia/cuvs/lucene/LuceneCompat.java @@ -42,6 +42,10 @@ static Codec acceleratedHNSWCodec(AcceleratedHNSWParams params) { return new Lucene101AcceleratedHNSWCodec(params); } + static KnnVectorsFormat acceleratedHNSWFormat(AcceleratedHNSWParams params) { + return new Lucene99AcceleratedHNSWVectorsFormat(params); + } + static Codec acceleratedHNSWScalarQuantizedCodec(AcceleratedHNSWParams params) { return new LuceneAcceleratedHNSWScalarQuantizedCodec(params); } @@ -54,7 +58,16 @@ static Codec acceleratedHNSWBinaryQuantizedCodec(AcceleratedHNSWParams params) { return new LuceneAcceleratedHNSWBinaryQuantizedCodec(params); } + static KnnVectorsFormat acceleratedHNSWBinaryQuantizedFormat(AcceleratedHNSWParams params) { + return new LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat(params); + } + static Codec gpuSearchCodec(GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) { return new CuVS2510GPUSearchCodec(params, filterCacheConfig); } + + static KnnVectorsFormat gpuSearchFormat( + GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) { + return new CuVS2510GPUVectorsFormat(params, filterCacheConfig); + } } diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java index 67df08bdac..ac0811d893 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java @@ -62,6 +62,27 @@ public static Codec acceleratedHNSW(AcceleratedHNSWParams params) { return LuceneCompat.acceleratedHNSWCodec(params); } + /** + * Returns the vectors format used by {@link #acceleratedHNSW()}, for use with a per-field codec. + * + * @return the vectors format + */ + public static KnnVectorsFormat acceleratedHNSWFormat() { + return acceleratedHNSWFormat(new AcceleratedHNSWParams.Builder().build()); + } + + /** + * Returns the vectors format used by {@link #acceleratedHNSW(AcceleratedHNSWParams)}, for use + * with a per-field codec. + * + * @param params the index build parameters + * @return the vectors format + */ + public static KnnVectorsFormat acceleratedHNSWFormat(AcceleratedHNSWParams params) { + LuceneVersionGuard.ensureCompatible(); + return LuceneCompat.acceleratedHNSWFormat(params); + } + /** * Returns the codec that builds HNSW graphs on the GPU over scalar-quantized vectors, with * default parameters. @@ -83,6 +104,16 @@ public static Codec acceleratedHNSWScalarQuantized(AcceleratedHNSWParams params) return LuceneCompat.acceleratedHNSWScalarQuantizedCodec(params); } + /** + * Returns the vectors format used by {@link #acceleratedHNSWScalarQuantized()}, for use with a + * per-field codec. + * + * @return the vectors format + */ + public static KnnVectorsFormat acceleratedHNSWScalarQuantizedFormat() { + return acceleratedHNSWScalarQuantizedFormat(new AcceleratedHNSWParams.Builder().build()); + } + /** * Returns the vectors format used by {@link * #acceleratedHNSWScalarQuantized(AcceleratedHNSWParams)}, for use with a per-field codec. @@ -117,6 +148,29 @@ public static Codec acceleratedHNSWBinaryQuantized(AcceleratedHNSWParams params) return LuceneCompat.acceleratedHNSWBinaryQuantizedCodec(params); } + /** + * Returns the vectors format used by {@link #acceleratedHNSWBinaryQuantized()}, for use with a + * per-field codec. + * + * @return the vectors format + */ + public static KnnVectorsFormat acceleratedHNSWBinaryQuantizedFormat() { + return acceleratedHNSWBinaryQuantizedFormat(new AcceleratedHNSWParams.Builder().build()); + } + + /** + * Returns the vectors format used by {@link + * #acceleratedHNSWBinaryQuantized(AcceleratedHNSWParams)}, for use with a per-field codec. + * + * @param params the index build parameters + * @return the vectors format + */ + public static KnnVectorsFormat acceleratedHNSWBinaryQuantizedFormat( + AcceleratedHNSWParams params) { + LuceneVersionGuard.ensureCompatible(); + return LuceneCompat.acceleratedHNSWBinaryQuantizedFormat(params); + } + /** * Returns the codec that both builds and searches CAGRA indexes on the GPU, with default * parameters. @@ -149,4 +203,38 @@ public static Codec gpuSearch(GPUSearchParams params, FilterBitsetCacheConfig fi LuceneVersionGuard.ensureCompatible(); return LuceneCompat.gpuSearchCodec(params, filterCacheConfig); } + + /** + * Returns the vectors format used by {@link #gpuSearch()}, for use with a per-field codec. + * + * @return the vectors format + */ + public static KnnVectorsFormat gpuSearchFormat() { + return gpuSearchFormat(new GPUSearchParams.Builder().build()); + } + + /** + * Returns the vectors format used by {@link #gpuSearch(GPUSearchParams)}, for use with a + * per-field codec. + * + * @param params the GPU index and search parameters + * @return the vectors format + */ + public static KnnVectorsFormat gpuSearchFormat(GPUSearchParams params) { + return gpuSearchFormat(params, FilterBitsetCacheConfig.DEFAULT); + } + + /** + * Returns the vectors format used by {@link #gpuSearch(GPUSearchParams, + * FilterBitsetCacheConfig)}, for use with a per-field codec. + * + * @param params the GPU index and search parameters + * @param filterCacheConfig the filter-bitset-cache configuration + * @return the vectors format + */ + public static KnnVectorsFormat gpuSearchFormat( + GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) { + LuceneVersionGuard.ensureCompatible(); + return LuceneCompat.gpuSearchFormat(params, filterCacheConfig); + } } diff --git a/java/cuvs-lucene/src/test/backcompat/GenerateReleasedIndices.java b/java/cuvs-lucene/src/test/backcompat/GenerateReleasedIndices.java index 9b31255e38..0a3f312909 100644 --- a/java/cuvs-lucene/src/test/backcompat/GenerateReleasedIndices.java +++ b/java/cuvs-lucene/src/test/backcompat/GenerateReleasedIndices.java @@ -87,7 +87,7 @@ public KnnVectorsFormat knnVectorsFormat() { return perField; } }; - write(codec, outDir, base.getName() + "-" + name + ".zip"); + write(codec, outDir, name + ".zip"); } } diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompatIndices.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompatIndices.java index 4d8fbb148b..f6701a14a2 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompatIndices.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestBackCompatIndices.java @@ -25,6 +25,7 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.KnnVectorsFormat; import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; @@ -56,13 +57,13 @@ * moves to a later Lucene release, cuvs-lucene release, or both. * *

The indexes are in {@code src/test/resources/backcompat}, one zip each, at {@code - * releases///[-].zip} - * if the jars of a released cuvs-lucene version wrote them, or at {@code snapshots//.zip} if a module of this repository did, for a Lucene release that no - * released cuvs-lucene version supports yet. The path records the Lucene - * release they were written on, the codec recorded in their segments, and the cuvs-lucene vectors - * format used per field if the codec is Lucene's own. Each module reads the ones written on its own - * Lucene release and all earlier ones. See the {@code README.md} next to them for how to add more. + * releases///.zip} if the jars of a released + * cuvs-lucene version wrote them, or at {@code snapshots//.zip} if a + * module of this repository did, for a Lucene release that no released cuvs-lucene version supports + * yet. The path records the Lucene release they were written on and either the cuvs-lucene codec + * recorded in their segments or, for an index written with Lucene's own codec, the cuvs-lucene + * vectors format its vector field uses. Each module reads the ones written on its own Lucene + * release and all earlier ones. See the {@code README.md} next to them for how to add more. * *

Reading and upgrading need no GPU, so those tests also run where cuVS is not available. */ @@ -73,7 +74,7 @@ public class TestBackCompatIndices extends LuceneTestCase { private static final String RESOURCE_DIR = "/backcompat"; // The path of an index below RESOURCE_DIR; see the class javadoc. private static final Pattern INDEX_PATH = - Pattern.compile("(?:snapshots|releases/[\\d.]+)/(\\d+)\\.(\\d+)/(\\w+)(?:-(\\w+))?\\.zip"); + Pattern.compile("(?:snapshots|releases/[\\d.]+)/(\\d+)\\.(\\d+)/(\\w+)\\.zip"); private static final String ID_FIELD = "id"; private static final String VECTOR_FIELD = "vector"; private static final int NUM_DOCS = 200; @@ -95,7 +96,14 @@ public void testReadIndices() throws Exception { Path indexDir = createTempDir(zip.getFileName().toString()); unzip(zip, indexDir); try (Directory dir = newFSDirectory(indexDir)) { - checkIndex(dir, matcher.group(3), matcher.group(4), NUM_DOCS); + String name = matcher.group(3); + if (KnnVectorsFormat.availableKnnVectorsFormats().contains(name)) { + // Lucene's own codec with a cuvs-lucene format per field: check the field's format. + checkIndex(dir, null, name, NUM_DOCS); + } else { + // A cuvs-lucene codec: check the codec recorded in every segment. + checkIndex(dir, name, null, NUM_DOCS); + } } catch (AssertionError | Exception e) { throw new AssertionError("Failed to read " + matcher.group(), e); } @@ -223,16 +231,18 @@ private static Document document(int id, float[] vector) { } /** - * Checks that every segment was written by the expected codec (and, for Lucene's own codecs, that - * the vector field uses the expected cuvs-lucene format), and that searching for each indexed - * vector finds its own document. + * Checks that every segment was written by the expected codec, or that the vector field uses the + * expected cuvs-lucene format, and that searching for each indexed vector finds its own document. * + * @param codecName the codec recorded in every segment, or null to skip * @param perFieldFormat the vectors format name recorded for the vector field, or null to skip */ private static void checkIndex( Directory dir, String codecName, String perFieldFormat, int numDocs) throws IOException { - for (SegmentCommitInfo info : SegmentInfos.readLatestCommit(dir)) { - assertEquals(codecName, info.info.getCodec().getName()); + if (codecName != null) { + for (SegmentCommitInfo info : SegmentInfos.readLatestCommit(dir)) { + assertEquals(codecName, info.info.getCodec().getName()); + } } try (DirectoryReader reader = DirectoryReader.open(dir)) { assertEquals(numDocs, reader.numDocs()); diff --git a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedVectorsFormats.java b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedVectorsFormats.java index e490ef58a6..5053ad13a9 100644 --- a/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedVectorsFormats.java +++ b/java/cuvs-lucene/src/test/java/com/nvidia/cuvs/lucene/TestQuantizedVectorsFormats.java @@ -46,11 +46,8 @@ public TestQuantizedVectorsFormats(@Name("knnVectorsWriter") KnnVectorsFormat kn public static List parameters() { return Arrays.asList( new Object[][] { - {new LuceneAcceleratedHNSWBinaryQuantizedVectorsFormat()}, - { - CuVSCodecs.acceleratedHNSWScalarQuantizedFormat( - new AcceleratedHNSWParams.Builder().build()) - } + {CuVSCodecs.acceleratedHNSWBinaryQuantizedFormat()}, + {CuVSCodecs.acceleratedHNSWScalarQuantizedFormat()} }); } diff --git a/java/cuvs-lucene/src/test/resources/backcompat/README.md b/java/cuvs-lucene/src/test/resources/backcompat/README.md index 3721588d2c..ed7f74ca22 100644 --- a/java/cuvs-lucene/src/test/resources/backcompat/README.md +++ b/java/cuvs-lucene/src/test/resources/backcompat/README.md @@ -5,13 +5,12 @@ Indexes read and upgraded by `TestBackCompatIndices`, one zip each. They all hol on all earlier ones. The directories record who wrote each index and on which Lucene release: ``` -releases///.zip written by a published release -releases///-.zip -snapshots//.zip written by this repository +releases///.zip written by a published release +snapshots//.zip written by this repository ``` -`` is the codec recorded in the index's segments. When that is Lucene's own codec, `` -is the cuvs-lucene vectors format its vector field uses. `TestBackCompatIndices` fails on a zip placed +`` is the cuvs-lucene codec recorded in the index's segments or, for an index written with Lucene's own +codec, the cuvs-lucene vectors format its vector field uses. `TestBackCompatIndices` fails on a zip placed anywhere else, so that it can't go unread. ## Written by released cuvs-lucene versions diff --git a/java/cuvs-lucene/src/test/resources/backcompat/releases/25.12.0/10.2/Lucene101-Lucene99AcceleratedHNSWVectorsFormat.zip b/java/cuvs-lucene/src/test/resources/backcompat/releases/25.12.0/10.2/Lucene99AcceleratedHNSWVectorsFormat.zip similarity index 100% rename from java/cuvs-lucene/src/test/resources/backcompat/releases/25.12.0/10.2/Lucene101-Lucene99AcceleratedHNSWVectorsFormat.zip rename to java/cuvs-lucene/src/test/resources/backcompat/releases/25.12.0/10.2/Lucene99AcceleratedHNSWVectorsFormat.zip From cdd2b166dc16d192453babb68cd83dd9a0decb75 Mon Sep 17 00:00:00 2001 From: Igor Motov Date: Thu, 22 Oct 2026 09:18:52 -1000 Subject: [PATCH 3/6] Rename setKnnFormat -> setKnnVectorsFormat --- .../nvidia/cuvs/lucene/CuVSFilterCodec.java | 18 +++++++++++++++--- .../TestMultiSegmentGPUFilterConcurrency.java | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java index cf815943ca..30dd8dc9ee 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java @@ -55,7 +55,7 @@ abstract class CuVSFilterCodec extends FilterCodec { protected CuVSFilterCodec(String name, Codec delegate, Supplier format) { super(name, delegate); try { - setKnnFormat(format.get()); + setKnnVectorsFormat(format.get()); } catch (LibraryException | LinkageError e) { // Lucene's service loader creates every codec eagerly, and an exception here would break // every codec lookup in the JVM. A format that does not link on the Lucene release in use, or @@ -63,7 +63,7 @@ protected CuVSFilterCodec(String name, Codec delegate, Supplier Date: Thu, 22 Oct 2026 09:43:08 -1000 Subject: [PATCH 4/6] Address coderabbitai's comments --- dependencies.yaml | 2 +- .../java-api-com-nvidia-cuvs-spi-cuvsprovider.md | 2 +- .../lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md | 2 +- fern/scripts/generate_api_reference.py | 9 +++++++-- java/cuvs-lucene/README.md | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dependencies.yaml b/dependencies.yaml index 68012777b8..decfa81514 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -666,7 +666,7 @@ dependencies: common: - output_types: conda packages: - - maven + - maven=3.9.* # The oldest JDK cuvs-lucene supports; cuvs-java needs 22+ to use the GPU. - openjdk=21.* bench: diff --git a/fern/pages/java_api/java-api-com-nvidia-cuvs-spi-cuvsprovider.md b/fern/pages/java_api/java-api-com-nvidia-cuvs-spi-cuvsprovider.md index 89f02315c6..c05471a375 100644 --- a/fern/pages/java_api/java-api-com-nvidia-cuvs-spi-cuvsprovider.md +++ b/fern/pages/java_api/java-api-com-nvidia-cuvs-spi-cuvsprovider.md @@ -336,7 +336,7 @@ CagraIndex mergeCagraIndexes(CagraIndex[] indexes, CagraIndexParams mergeParams, ``` Merges multiple CAGRA indexes into a single index, keeping only the rows selected by -`rowFilter`. See CagraIndexParams, BitSet) for the +`rowFilter`. See `CagraIndex#merge(CagraIndex[], CagraIndexParams, BitSet)` for the meaning of the filter. **Parameters** diff --git a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md index e58ae4270c..2030651654 100644 --- a/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md +++ b/fern/pages/lucene_api/lucene-api-com-nvidia-cuvs-lucene-cuvscodecs.md @@ -352,7 +352,7 @@ _Source: `java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSCodecs.java: public static KnnVectorsFormat gpuSearchFormat( GPUSearchParams params, FilterBitsetCacheConfig filterCacheConfig) ``` -Returns the vectors format used by FilterBitsetCacheConfig), for use with a per-field codec. +Returns the vectors format used by `#gpuSearch(GPUSearchParams, FilterBitsetCacheConfig)`, for use with a per-field codec. **Parameters** diff --git a/fern/scripts/generate_api_reference.py b/fern/scripts/generate_api_reference.py index 3f09f9fc9a..a66a9f5737 100755 --- a/fern/scripts/generate_api_reference.py +++ b/fern/scripts/generate_api_reference.py @@ -4845,8 +4845,13 @@ def words(value: str) -> str: r"\{@literal\s+([^}]+)\}", lambda m: keep(words(m.group(1))), text ) text = re.sub( - r"\{@link(?:plain)?\s+([^}\s]+)(?:\s+([^}]+))?\}", - lambda m: keep(words(m.group(2)) if m.group(2) else f"`{m.group(1)}`"), + # The reference may wrap inside its argument list, e.g. #m(A,\nB). + r"\{@link(?:plain)?\s+([^}\s(]+(?:\([^)]*\))?)(?:\s+([^}]+))?\}", + lambda m: keep( + words(m.group(2)) + if m.group(2) + else f"`{' '.join(m.group(1).split())}`" + ), text, ) # Any other inline tag shows its text. diff --git a/java/cuvs-lucene/README.md b/java/cuvs-lucene/README.md index b859eb75f5..f4dc7cff90 100644 --- a/java/cuvs-lucene/README.md +++ b/java/cuvs-lucene/README.md @@ -54,7 +54,7 @@ Lucene 10.2, add the following dependency to your `pom.xml`: ``` -Until 26.10 the artifact was called `cuvs-lucene`; those coordinates now relocate to `cuvs-lucene-10.2`. +Releases up to 26.10 published it as `cuvs-lucene`; those coordinates now relocate to `cuvs-lucene-10.2`. Against another Lucene minor release, an artifact's codecs fail when they are used, with an error that names the artifact to use; the problem is also logged at `SEVERE` the first time Lucene looks up its codecs. Call From f9ebb544a7f9b38aca55f24a943c6c24a4dbc95f Mon Sep 17 00:00:00 2001 From: Igor Motov Date: Thu, 22 Oct 2026 11:34:32 -1000 Subject: [PATCH 5/6] Pin Maven Install Plugin for rellocation pom --- java/cuvs-lucene/relocation/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/cuvs-lucene/relocation/pom.xml b/java/cuvs-lucene/relocation/pom.xml index ffd25ad477..aa06492ddf 100644 --- a/java/cuvs-lucene/relocation/pom.xml +++ b/java/cuvs-lucene/relocation/pom.xml @@ -95,6 +95,12 @@ SPDX-License-Identifier: Apache-2.0 true + + + org.apache.maven.plugins + maven-install-plugin + 3.1.4 + org.apache.maven.plugins maven-install-plugin - 3.1.4 + 3.2.0 org.apache.maven.plugins maven-resources-plugin - 3.3.1 + 3.5.0 copy-pom-to-target diff --git a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java index 30dd8dc9ee..d837d67cc1 100644 --- a/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java +++ b/java/cuvs-lucene/src/main/java/com/nvidia/cuvs/lucene/CuVSFilterCodec.java @@ -148,7 +148,7 @@ public void setKnnVectorsFormat(KnnVectorsFormat format) { * Set the {@link KnnVectorsFormat}. * * @param format the {@link KnnVectorsFormat} to set - * @deprecated use {@link #setKnnVectorsFormat(KnnVectorsFormat)}, which matches the name of + * @deprecated use {@code setKnnVectorsFormat(KnnVectorsFormat)}, which matches the name of * {@link #knnVectorsFormat()} */ @Deprecated(since = "26.12", forRemoval = true)