Skip to content

Add PyLucene integration and CPU/GPU end-to-end tests - #174

Draft
nvzm123 wants to merge 24 commits into
NVIDIA:mainfrom
nvzm123:pr-147
Draft

Add PyLucene integration and CPU/GPU end-to-end tests#174
nvzm123 wants to merge 24 commits into
NVIDIA:mainfrom
nvzm123:pr-147

Conversation

@nvzm123

@nvzm123 nvzm123 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds PyLucene integration support and a pytest-owned CPU/GPU end-to-end suite for cuVS-Lucene.

The test layout follows the current cuVS/cuML test_*.py convention:

  • src/test/python/test_pylucene_end_to_end.py owns test cases, parametrization, assertions, and reporting.
  • src/test/python/pylucene_test_support.py contains reusable PyLucene index/search helpers.
  • src/test/java/com/nvidia/cuvs/lucene/PyLuceneTestSupport.java provides test-only codecs, execution-path diagnostics, and graph verification.
  • Tests run directly with python3 -m pytest -q -s src/test/python/test_pylucene_end_to_end.py once the documented PyLucene, jar, JVM, and native-library environment is available.

The standard thin cuvs-lucene jar remains the artifact under test; Lucene and cuvs-java remain external classpath dependencies, and the Java diagnostics stay in target/test-classes rather than the published jar.

The PR also:

  • fixes every default codec provider to Lucene 10.2's Lucene101Codec delegate instead of dynamically selecting an older outer codec
  • requires the PyLucene wrapper's Lucene version to match the POM before JVM initialization
  • makes scalar-quantized format initialization lazy so SPI discovery does not eagerly construct it
  • adds the correctly typed scalar HNSW provider API while retaining the legacy JVM descriptor with an actionable failure
  • preserves the existing binary-provider descriptors and correctly typed APIs
  • uses cuVS 26.10's padded-dataset view API when serializing GPU-built indexes
  • documents published 26.08 artifacts separately from this 26.10 development checkout

Apache does not publish PyLucene 10.2.0, so the full suite uses a custom PyLucene wrapper generated against the same Lucene 10.2.0 sources as this project.

PyLucene test coverage

The suite explicitly proves these paths:

  • CPU HNSW build and HNSW search
  • GPU CAGRA build followed by one-layer or three-layer HNSW search
  • GPU CAGRA build and CAGRA search

GPU-required cases assert the concrete accelerated writer, reader, and query implementations and fail on unavailable cuVS or CPU fallback. CPU cases construct and report a stock Lucene HNSW path.

Coverage includes:

  • a single-live-document CAGRA-search case
  • one and ten segments
  • 10-to-1 and 100-to-10 force merges
  • CAGRA searchWidth values 1, 16, and 32
  • deleted-document exclusion
  • selective filtering through all three execution paths
  • persisted HNSW graph degree and layer verification
  • cold-JVM codec and vector-format SPI discovery
  • an exact PyLucene/Lucene version preflight

Vectors and queries are deterministic, and expected neighbors are computed by brute force. Assertions verify rank-one self matches where applicable, exact hit counts, no duplicates, inactive or filter-rejected document exclusion, and a configurable recall floor.

CAGRA configurations use graphDegree=32 and intermediateGraphDegree=64. Cases construct enough vectors to avoid cuVS graph-parameter clamping, including 24,832 vectors for the three-layer case.

Validation

Validated on an NVIDIA A10G with matching cuVS Java/native 26.10 and Lucene/PyLucene 10.2 environments:

  • mvn -Dtest=TestBackCompat,TestAcceleratedHNSWDeletedDocuments test: 17 tests, 0 failures, 0 errors
  • mvn clean verify -Dtests.seed=5A17C10120260817: 303 tests, 0 failures, 0 errors, 30 skipped
  • python3 -m pytest -q -s src/test/python/test_pylucene_end_to_end.py: 24 passed in 36.25 seconds
  • mvn -q spotless:check: passed
  • Python compilation and diff checks: passed
  • minimum observed HNSW recall: 0.85; all CAGRA-search cases: 1.0

The PyLucene suite emitted no cuVS graph-clamping or CPU-fallback warnings. Its only warning was the JVM notice for the incubating vector module.

Known randomized-test failure

An unseeded full Maven run selected seed CC0EA94328BAB3E5 and exposed a pre-existing, seed-dependent failure in TestCuVSVectorsFormat.testRandomWithUpdatesAndGraph: IllegalStateException: Index not found for field:field at CuVS2510GPUVectorsReader.java:425.

It reproduces identically both before and after the final Lucene/PyLucene compatibility changes; neither the failing reader logic nor the randomized test is changed by that patch. Reproduction command:

mvn -Dtest=TestCuVSVectorsFormat#testRandomWithUpdatesAndGraph \
  -Dtests.seed=CC0EA94328BAB3E5 \
  -Dtests.locale=ti-Ethi-ET \
  -Dtests.timezone=Greenwich test

The fixed-seed full validation listed above passes.

Follow-up multithreaded concurrency coverage is tracked in NVIDIA/cuvs#2407.

@nvzm123
nvzm123 requested review from a team as code owners July 8, 2026 22:50
@nvzm123
nvzm123 requested a review from msarahan July 8, 2026 22:50
@copy-pr-bot

copy-pr-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@nvzm123
nvzm123 marked this pull request as draft July 9, 2026 19:58
Convert the PyLucene smoke runner to a pytest-backed case matrix covering GPU CAGRA search, CAGRA-built HNSW, and forced CPU HNSW fallback across segment and force-merge topologies.

Add named one-layer and three-layer CAGRA-to-HNSW codecs plus writer-path telemetry so the e2e suite can assert which CPU/GPU path was exercised.

Tighten sidecar and Lucene delegate-codec validation to avoid misleading expected probe warnings while still failing on unsupported delegate codecs.
@nvzm123 nvzm123 changed the title Fix PyLucene sidecar packaging and add smoke test Fix PyLucene sidecar packaging and add smoke tests Jul 16, 2026
@nvzm123
nvzm123 marked this pull request as ready for review July 16, 2026 20:19
@Override
public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException {
var flatWriter = FLAT_VECTORS_FORMAT.fieldsWriter(state);
System.setProperty(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These System.setProperty methods make sense in the context of this PR as a way to communicate between the Java and Python layers. However, they introduce unnecessary overhead for most other applications. Moreover, this approach would not work reliably in a multithreaded environment, since the same property is reused across requests. I think it would be better to separate the telemetry concerns and extract them into a dedicated method that is invoked only when telemetry data is actually needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Removed the JVM-global system properties from the indexing path. Telemetry is now computed on demand from the vectors format only when the PyLucene test requests it.

@cjnolet cjnolet added improvement Improves an existing functionality non-breaking Introduces a non-breaking change labels Jul 17, 2026
@cjnolet cjnolet moved this to In Progress in Unstructured Data Processing Jul 17, 2026
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
To run the PyLucene pytest smoke suite against a local PyLucene environment:

```sh
./ci/test_pylucene_smoke.sh

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that we have ci scripts for this , but a user should not have to invoke ci scripts in order to run tests. Rather, we should document how to run these pytests without the need to call scripts inside the CI directory while also providing the scripts in the CI directory for GitHub actions to run automatically.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cjnolet I moved the user-facing logic to test_pylucene.sh and kept a thin wrapper under ci/. The current GitHub Actions workflows do not invoke it yet. Did you intend for this PR to add a PyLucene Actions job as well, or is providing the CI entry-point sufficient for now?

Comment thread README.md Outdated
./ci/test_pylucene_smoke.sh --gpu-e2e
```

The expanded suite runs the `gpu-basic`, `gpu-segments`, `cpu-hnsw`, and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great description here. How to build and run tests should really be in a separate build and install guide. I think this is okay for now, especially since we are moving cuVS-Lucene to cuVS, but it's something to consider.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense

Comment thread ci/test_pylucene_smoke.sh Outdated
Comment thread examples/Python/test_pylucene_smoke.py Outdated
.withCagraGraphBuildAlgo(CagraGraphBuildAlgo.NN_DESCENT)
.withGraphDegree(CAGRA_GRAPH_DEGREE)
.withIntermediateGraphDegree(CAGRA_INTERMEDIATE_GRAPH_DEGREE)
.withHNSWLayer(1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't this an issue that you resolved? Why layer 1?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that issue was resolved. Layer 1 is intentional test coverage, paired with the three-layer case -- that said, I am not sure that these two particular tests will provide much utility

@nvzm123
nvzm123 requested review from cjnolet and imotov July 22, 2026 03:04
Move the PyLucene suite into a pytest-owned test tree and add explicit coverage for CPU HNSW, CAGRA-built HNSW, and CAGRA search.

Cover segment and force-merge topologies, one- and three-layer HNSW, CAGRA search widths, deletions, vectorless documents, filtering, and single-document behavior. Verify execution paths, persisted graph configuration, and brute-force recall with deterministic vectors.

Keep the shell runner focused on environment and classpath setup, and document direct pytest and full GPU execution.
@nvzm123
nvzm123 requested a review from imotov July 27, 2026 13:06
nvzm123 added 2 commits July 27, 2026 06:12
Remove redundant edge cases and reuse canonical topology cases for one-layer HNSW and searchWidth=1 coverage.

Add selective brute-force-validated filters across CPU HNSW, CAGRA-built HNSW, and ten-segment CAGRA search, then make cpu-hnsw-1-segment the default documented smoke case.

@imotov imotov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how comprehensive we want these cases to be, but they all follow the same pattern - index records under some configuration, close the writer, then search. These are solid, deterministic tests, and I don't want to hold up the PR over this, but there are several important use cases where I think the next round of coverage could pay off.

Everything here is single-threaded: SerialMergeScheduler runs merges inline, IndexSearcher is built without an executor, and numMergeWorkers defaults to 1. So CuVSResources, which is managed per-thread, is only ever exercised in its simplest configuration - one thread, one resource.

In production, indexing and searching frequently overlap: new documents arriving, existing documents being updated, deletes landing, merges running (sometimes on several threads), and queries served against the index the whole time. In my experience that's where the tricky bugs live. A couple of other specific gaps:

  • No updateDocument/softUpdateDocument at all, so the delete-then-add path is untested even on a single thread.
  • No near-real-time search — the reader always opens after the writer closes, so a segment is never read while it's still being written, and deletes are always committed before anything searches. That should be covered by Lucene, but it would be nice to ensure that Readers still play by the book, don't leak any resources and do what Lucene expects them to do.
  • No search concurrent with a merge, so a segment is never dropped from under a live searcher. Same here, Lucene should take care of it, but we should cooperate.

Comment thread src/main/java/com/nvidia/cuvs/lucene/LuceneProvider.java
nvzm123 added 2 commits July 27, 2026 23:15
Create flat vector writers only when the accelerated writer consumes them. CPU fallback paths construct their own writers, so eager allocation leaked the unused flat writer for both binary and scalar quantization.
Keep the existing Lucene 102 binary-format initialization while preserving the reminder to replace version-specific partial providers in a separate initiative.
@nvzm123 nvzm123 changed the title Fix PyLucene sidecar packaging and add smoke tests Add PyLucene integration and CPU/GPU end-to-end tests Jul 27, 2026

@cjnolet cjnolet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR Still needs a lot of work and I'm honestly not sure we should hold up the consolidation of cuVS-lucene to cuVS over it.

@nvzm123 please do make sure when you use AI to make changes that you familiarize yourself enough with our conventions that you can guide the AI to follow them. As it is, this PR is very different than how we do things and having to review changes like this becomes time consuming for everyone else. The pytest infra and related docs do not follow convention. Please make sure to follow all conventions in cuVS and other RAPIDS/cuda-x repos (like RAFT, cuml, etc...).

Comment thread README.md Outdated

cuvs_java_jar = Path(os.environ["CUVS_LUCENE_CUVS_JAVA_JAR"])
cuvs_lucene_jar = next(
jar

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These steps still look overly complex for a user that just wants to use pylucene.

Boilerplate like this should ideally be provided in a Python library or scripts somewhere that can be easily included in their code. Ideally they should just have to import the codec and call codec.forCodec call.

Have you looked at other Pylucene extensions on GitHub to see their "usage" docs?

Comment thread README.md Outdated

#### PyLucene end-to-end tests

Pytest cases are under `src/test/python`. The parametrized cases and assertions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need to expend this many words on tests then it's too complicated.

The only thing you need to say about the tests, other than a very terse description, is a 1 or 2-liner copy/paste example of how to run them. Please see the cuVS build docs for and example.

Also, the standard for test file naming should "XXX_test.py".

Comment thread README.md Outdated
`src/test/java/com/nvidia/cuvs/lucene/PyLuceneTestSupport.java` are compiled to
`target/test-classes` and are not included in the published jar.

`ci/run_pylucene_pytests.sh` builds the Maven artifacts when requested, resolves

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't reference CI scripts in user docs. These are specifically for CI, not for user consumption.

Comment thread README.md Outdated
Pytest cases are under `src/test/python`. The parametrized cases and assertions
are in `test_pylucene_end_to_end.py`; reusable index and search code is in
`pylucene_test_support.py`. The test-only Java adapters in
`src/test/java/com/nvidia/cuvs/lucene/PyLuceneTestSupport.java` are compiled to

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too much info.. users don't need this.

Comment thread README.md Outdated
`target/test-classes` and are not included in the published jar.

`ci/run_pylucene_pytests.sh` builds the Maven artifacts when requested, resolves
the PyLucene classpath inputs, and invokes pytest. `test_pylucene.sh` at the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not standard practice in cuVS or other cuda-x repos. Please see cuVS build docs- we use a standard consolidated "build.sh" in the root of the repository.

We are in the process of consolidating cuVs-lucene with cuvs, and the build scripts need to be consolidated as well. We do not provide separate shell scripts for simple 1-liner test run commands. As you can imagine that adds to maintenance overhead for very little benefit. The pytest command should be reference in the build docs for running the tests (as a verification the build succeeded).

Comment thread README.md Outdated
Select a focused group or set the minimum document count per scenario:

```sh
./test_pylucene.sh --cases=cagra-search-widths \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. Too complex. User should not require a complex script with flags just to run pytests.

Comment thread ci/run_pylucene_pytests.sh Outdated
@@ -0,0 +1,232 @@
#!/bin/bash

# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned above. Remove. Please use pytests from cuVS Python package as a guide- the individual test cases should be parameterized.

@@ -0,0 +1,22 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait- why? Aren't we already building this? cc @imotov

this(
NAME,
LuceneProvider.getCodec("101"),
LuceneProvider.getDefaultDelegateCodec(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we want to change this, but will let Igor weigh in. We need to make sure we dont break anything.

*/
public CuVS2510GPUSearchCodec(GPUSearchParams params) throws Exception {
this(NAME, LuceneProvider.getCodec("101"), params, FilterBitsetCacheConfig.DEFAULT);
this(NAME, LuceneProvider.getDefaultDelegateCodec(), params, FilterBitsetCacheConfig.DEFAULT);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure this doesn't break anythingz

Signed-off-by: Zack Meeks <zmeeks@nvidia.com>
@nvzm123
nvzm123 marked this pull request as draft August 14, 2026 16:16
Signed-off-by: Zack Meeks <zmeeks@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improves an existing functionality non-breaking Introduces a non-breaking change

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants