Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions conda/recipes/cuvs-bench-cpu/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,7 @@ tests:
pip_check: false
- script:
content: |
python -c "from importlib.metadata import entry_points; assert any(item.name == 'lucene' for item in entry_points(group='cuvs_bench.backends'))"
python -c "from importlib.metadata import entry_points; assert any(item.name == 'lucene' for item in entry_points(group='cuvs_bench.config_loaders'))"
python -c "from cuvs_bench.orchestrator import BenchmarkOrchestrator; assert BenchmarkOrchestrator('lucene').backend_type == 'lucene'"
python -m cuvs_bench.tests.test_lucene_scaffold

about:
homepage: ${{ load_from_file("python/cuvs_bench/pyproject.toml").project.urls.Homepage }}
Expand Down
4 changes: 1 addition & 3 deletions conda/recipes/cuvs-bench/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ tests:
pip_check: false
- script:
content: |
python -c "from importlib.metadata import entry_points; assert any(item.name == 'lucene' for item in entry_points(group='cuvs_bench.backends'))"
python -c "from importlib.metadata import entry_points; assert any(item.name == 'lucene' for item in entry_points(group='cuvs_bench.config_loaders'))"
python -c "from cuvs_bench.orchestrator import BenchmarkOrchestrator; assert BenchmarkOrchestrator('lucene').backend_type == 'lucene'"
python -m cuvs_bench.tests.test_lucene_scaffold

about:
homepage: ${{ load_from_file("python/cuvs_bench/pyproject.toml").project.urls.Homepage }}
Expand Down
2 changes: 1 addition & 1 deletion fern/pages/cuvs_bench/lucene_backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ least 3 GB of disk space.
```bash
export PYLUCENE_BUILD_ROOT="$HOME/.local/share/cuvs/pylucene-10.2.0"

python/cuvs_bench/tools/pylucene/build_pylucene_10_2.sh \
conda/recipes/cuvs-bench/build_pylucene_10_2.sh \
--python python3 \
--build-root "$PYLUCENE_BUILD_ROOT"

Expand Down
77 changes: 44 additions & 33 deletions python/cuvs_bench/cuvs_bench/backends/_lucene_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@
CAGRA_CODEC = "CuVS2510GPUSearchCodec"
MAX_CAGRA_TOP_K = 1024
REQUIRED_PYLUCENE_VERSION = "10.2.0"
_PYLUCENE_SETUP_GUIDANCE = (
"Setup documentation: fern/pages/cuvs_bench/lucene_backend.md "
"(published at https://docs.nvidia.com/cuvs/user-guide/benchmarking-guide/"
"cu-vs-bench-tool/lucene-backend). Source-checkout helper: "
"python/cuvs_bench/tools/pylucene/build_pylucene_10_2.sh "
"(run it with --build-root <stable-absolute-path>, then source "
"<stable-absolute-path>/activate.sh)."
)

_ID_FIELD = "id"
_VECTOR_FIELD = "vector"
Expand All @@ -45,8 +37,22 @@
_CAGRA_FORMAT_VERSION = 0
_FLOAT32_ENCODING_ORDINAL = 1
_EUCLIDEAN_SIMILARITY_ORDINAL = 0
_CUVS_JAVA_PACKAGE = "com.nvidia.cuvs"
_CUVS_LUCENE_PACKAGE = f"{_CUVS_JAVA_PACKAGE}.lucene"
_CUVS_JAVA_PACKAGE_DIRECTORY = _CUVS_JAVA_PACKAGE.replace(".", "/")
_CUVS_LUCENE_PACKAGE_DIRECTORY = _CUVS_LUCENE_PACKAGE.replace(".", "/")
_MAVEN_METADATA_DIRECTORY = "META-INF/maven"
_CUVS_JAVA_MAVEN_PROPERTIES = (
f"{_MAVEN_METADATA_DIRECTORY}/{_CUVS_JAVA_PACKAGE}/"
"cuvs-java/pom.properties"
)
_CUVS_LUCENE_MAVEN_PROPERTIES = (
f"{_MAVEN_METADATA_DIRECTORY}/{_CUVS_LUCENE_PACKAGE}/"
"cuvs-lucene/pom.properties"
)
_CODEC_SERVICE_DESCRIPTOR = "META-INF/services/org.apache.lucene.codecs.Codec"
_INDEX_SEARCHER_TIMING_BRIDGE = (
"com.nvidia.cuvs.lucene.IndexSearcherTimingBridge"
f"{_CUVS_LUCENE_PACKAGE}.IndexSearcherTimingBridge"
)
_SEARCHER_REQUEST_KEY = "searcher"
_QUERY_REQUEST_KEY = "query"
Expand Down Expand Up @@ -124,9 +130,9 @@ def _rollback_writer(writer: Any, error: BaseException) -> None:
def _read_jar(path: Path, label: str) -> tuple[set[str], dict[str, bytes]]:
inspected = {
"META-INF/MANIFEST.MF",
"META-INF/maven/com.nvidia.cuvs/cuvs-java/pom.properties",
"META-INF/maven/com.nvidia.cuvs.lucene/cuvs-lucene/pom.properties",
"META-INF/services/org.apache.lucene.codecs.Codec",
_CUVS_JAVA_MAVEN_PROPERTIES,
_CUVS_LUCENE_MAVEN_PROPERTIES,
_CODEC_SERVICE_DESCRIPTOR,
}
try:
with zipfile.ZipFile(path) as archive:
Expand Down Expand Up @@ -247,9 +253,10 @@ def _validate_artifacts(
}
java_entries, java_contents = _read_jar(java_jar, "cuvs_java_jar")
java_required = {
"com/nvidia/cuvs/CagraIndex.class",
"com/nvidia/cuvs/CuVSResources.class",
"META-INF/versions/22/com/nvidia/cuvs/spi/JDKProvider.class",
f"{_CUVS_JAVA_PACKAGE_DIRECTORY}/CagraIndex.class",
f"{_CUVS_JAVA_PACKAGE_DIRECTORY}/CuVSResources.class",
"META-INF/versions/22/"
f"{_CUVS_JAVA_PACKAGE_DIRECTORY}/spi/JDKProvider.class",
}
missing = sorted(java_required - java_entries)
if missing:
Expand All @@ -273,11 +280,12 @@ def _validate_artifacts(

lucene_entries, lucene_contents = _read_jar(lucene_jar, "cuvs_lucene_jar")
lucene_required = {
"com/nvidia/cuvs/lucene/CuVS2510GPUVectorsFormat.class",
"com/nvidia/cuvs/lucene/CuVS2510GPUSearchCodec.class",
"com/nvidia/cuvs/lucene/IndexSearcherTimingBridge.class",
"com/nvidia/cuvs/lucene/Lucene101AcceleratedHNSWCodec.class",
"META-INF/services/org.apache.lucene.codecs.Codec",
f"{_CUVS_LUCENE_PACKAGE_DIRECTORY}/CuVS2510GPUVectorsFormat.class",
f"{_CUVS_LUCENE_PACKAGE_DIRECTORY}/CuVS2510GPUSearchCodec.class",
f"{_CUVS_LUCENE_PACKAGE_DIRECTORY}/IndexSearcherTimingBridge.class",
f"{_CUVS_LUCENE_PACKAGE_DIRECTORY}/"
"Lucene101AcceleratedHNSWCodec.class",
_CODEC_SERVICE_DESCRIPTOR,
}
missing = sorted(lucene_required - lucene_entries)
if missing:
Expand All @@ -304,36 +312,37 @@ def _validate_artifacts(
)
providers = {
line.partition("#")[0].strip()
for line in lucene_contents[
"META-INF/services/org.apache.lucene.codecs.Codec"
]
for line in lucene_contents[_CODEC_SERVICE_DESCRIPTOR]
.decode("utf-8")
.splitlines()
if line.partition("#")[0].strip()
}
if "com.nvidia.cuvs.lucene.CuVS2510GPUSearchCodec" not in providers:
if f"{_CUVS_LUCENE_PACKAGE}.CuVS2510GPUSearchCodec" not in providers:
raise RuntimeError(
"cuvs_lucene_jar does not advertise CuVS2510GPUSearchCodec"
)
if "com.nvidia.cuvs.lucene.Lucene101AcceleratedHNSWCodec" not in providers:
if (
f"{_CUVS_LUCENE_PACKAGE}.Lucene101AcceleratedHNSWCodec"
not in providers
):
raise RuntimeError(
"cuvs_lucene_jar does not advertise Lucene101AcceleratedHNSWCodec"
)

java_coordinates = _maven_coordinates(
java_contents,
"META-INF/maven/com.nvidia.cuvs/cuvs-java/pom.properties",
_CUVS_JAVA_MAVEN_PROPERTIES,
)
lucene_coordinates = _maven_coordinates(
lucene_contents,
"META-INF/maven/com.nvidia.cuvs.lucene/cuvs-lucene/pom.properties",
_CUVS_LUCENE_MAVEN_PROPERTIES,
)
if java_coordinates[:2] != ("com.nvidia.cuvs", "cuvs-java"):
if java_coordinates[:2] != (_CUVS_JAVA_PACKAGE, "cuvs-java"):
raise RuntimeError(
f"Unexpected cuvs-java coordinates: {java_coordinates[:2]}"
)
if lucene_coordinates[:2] != (
"com.nvidia.cuvs.lucene",
_CUVS_LUCENE_PACKAGE,
"cuvs-lucene",
):
raise RuntimeError(
Expand Down Expand Up @@ -375,8 +384,9 @@ def _load_pylucene() -> Any:
except ImportError as error:
raise ImportError(
"The Lucene backend requires the custom PyLucene 10.2.0 runtime, "
"which is not included in cuVS Bench packages. "
f"{_PYLUCENE_SETUP_GUIDANCE} PyLucene import failed: {error}"
"which is not included in cuVS Bench packages. Install and "
"activate the optional Lucene runtime before selecting this "
f"backend. PyLucene import failed: {error}"
) from error


Expand Down Expand Up @@ -432,8 +442,9 @@ def initialize_pylucene(
if actual_version != REQUIRED_PYLUCENE_VERSION:
raise RuntimeError(
"PyLucene must match cuvs-lucene's Lucene version: expected "
f"{REQUIRED_PYLUCENE_VERSION}, found {actual_version}. "
f"{_PYLUCENE_SETUP_GUIDANCE}"
f"{REQUIRED_PYLUCENE_VERSION}, found {actual_version}. Install "
"and activate a compatible PyLucene runtime before selecting "
"this backend."
)
vmargs = _vmargs(config)
with _JVM_LOCK:
Expand Down
11 changes: 4 additions & 7 deletions python/cuvs_bench/cuvs_bench/backends/_lucene_runtime_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
_PACKAGE_ROOT = Path(__file__).resolve().parents[1]
_REPOSITORY_ROOT = Path(__file__).resolve().parents[4]
_VERSION_FILE = _PACKAGE_ROOT / "VERSION"
_CUVS_MAVEN_DIRECTORY = Path("com/nvidia/cuvs")
_CUVS_LUCENE_MAVEN_DIRECTORY = _CUVS_MAVEN_DIRECTORY / "lucene"

_CUDA_TARGET_BY_MACHINE = {
"aarch64": "sbsa-linux",
Expand Down Expand Up @@ -57,9 +59,7 @@ def _artifact_candidates(kind: str) -> tuple[Path, ...]:
/ "target"
/ f"cuvs-java-{version}.jar",
repository
/ "com"
/ "nvidia"
/ "cuvs"
/ _CUVS_MAVEN_DIRECTORY
/ "cuvs-java"
/ version
/ f"cuvs-java-{version}.jar",
Expand All @@ -72,10 +72,7 @@ def _artifact_candidates(kind: str) -> tuple[Path, ...]:
/ "target"
/ f"cuvs-lucene-{version}.jar",
repository
/ "com"
/ "nvidia"
/ "cuvs"
/ "lucene"
/ _CUVS_LUCENE_MAVEN_DIRECTORY
/ "cuvs-lucene"
/ version
/ f"cuvs-lucene-{version}.jar",
Expand Down
13 changes: 12 additions & 1 deletion python/cuvs_bench/cuvs_bench/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Dict, List, Any, Optional, TYPE_CHECKING
from typing import Dict, List, Any, Optional, Sequence, TYPE_CHECKING
import numpy as np

if TYPE_CHECKING:
Expand Down Expand Up @@ -368,6 +368,10 @@ class BenchmarkBackend(ABC):
- requires_network : bool - Whether backend requires network (default: False)
"""

# Registration exposes this optional class-level default to the generic
# CLI.
default_algorithm: Optional[str] = None

def __init__(self, config: Dict[str, Any]):
"""Initialize backend with configuration."""
self.config = config
Expand Down Expand Up @@ -495,6 +499,13 @@ def cleanup(self) -> None:
"""
pass

@classmethod
def result_failure_message(
cls, results: Sequence[BuildResult | SearchResult]
) -> Optional[str]:
"""Return a fatal result summary when the backend requires one."""
return None

def _check_gpu_available(self) -> bool:
"""
Check if GPU is available (same logic as run.py rmm_present()).
Expand Down
2 changes: 2 additions & 0 deletions python/cuvs_bench/cuvs_bench/backends/cpp_gbench.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class CppGoogleBenchmarkBackend(BenchmarkBackend):
>>> print(backend.algo) # "cuvs_ivf_flat" (from config)
"""

default_algorithm = "cuvs_cagra"

def __init__(self, config: Dict[str, Any]):
"""Initialize C++ benchmark backend."""
super().__init__(config)
Expand Down
17 changes: 16 additions & 1 deletion python/cuvs_bench/cuvs_bench/backends/lucene.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from dataclasses import dataclass
from numbers import Integral
from pathlib import Path
from typing import Any, Callable, Mapping, Optional
from typing import Any, Callable, Mapping, Optional, Sequence

import numpy as np

Expand Down Expand Up @@ -855,6 +855,8 @@ def _build_benchmark_configs(
class LuceneBackend(BenchmarkBackend):
"""Build and query one immutable Lucene vector index per configuration."""

default_algorithm = CAGRA_ALGORITHM

def __init__(
self,
config: dict[str, Any],
Expand All @@ -874,6 +876,19 @@ def __init__(
self._runtime_factory = runtime_factory
self._runtime: LuceneRuntime | None = None

@classmethod
def result_failure_message(
cls, results: Sequence[BuildResult | SearchResult]
) -> str | None:
"""Fail the public command when any Lucene operation failed."""
failures = [result for result in results if not result.success]
if not failures:
return None
return "; ".join(
result.error_message or "unknown Lucene backend failure"
for result in failures
)

@property
def algo(self) -> str:
"""Return the selected cuVS Bench algorithm name."""
Expand Down
6 changes: 6 additions & 0 deletions python/cuvs_bench/cuvs_bench/orchestrator/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ def __init__(self, backend_type: str = "cpp_gbench"):
# Instantiate config loader
self.config_loader = loader_class()

def result_failure_message(
self, results: List[Union[BuildResult, SearchResult]]
) -> Optional[str]:
"""Return the selected backend's fatal result summary, if any."""
return self.backend_class.result_failure_message(results)

def run_benchmark(
self,
mode: str = "sweep",
Expand Down
Loading
Loading